<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: fixier</title>
    <description>The latest articles on DEV Community by fixier (@fixier_audit).</description>
    <link>https://dev.to/fixier_audit</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4229215%2Fade77156-dddb-418c-86d5-bbf4419f4d26.gif</url>
      <title>DEV Community: fixier</title>
      <link>https://dev.to/fixier_audit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fixier_audit"/>
    <language>en</language>
    <item>
      <title>From Prototype to Production: A Layered Checklist for Apps Built with Lovable, Bolt and v0</title>
      <dc:creator>fixier</dc:creator>
      <pubDate>Fri, 06 Nov 2026 02:31:16 +0000</pubDate>
      <link>https://dev.to/fixier_audit/from-prototype-to-production-a-layered-checklist-for-apps-built-with-lovable-bolt-and-v0-29hh</link>
      <guid>https://dev.to/fixier_audit/from-prototype-to-production-a-layered-checklist-for-apps-built-with-lovable-bolt-and-v0-29hh</guid>
      <description>&lt;p&gt;“It works” is the standard a prototype has to meet. Production adds a harder one: it keeps working for strangers, under load, when something fails, and when you are not looking. AI builders get you to the first standard quickly, which is exactly why teams skip the second.&lt;br&gt;
The fix is a layered review. Instead of one vague “make it secure” pass, work through the layers in order, because failures in lower layers (data, auth) make higher layers (UX, SEO) irrelevant.&lt;/p&gt;

&lt;p&gt;Layer 1: Data access and authorization&lt;br&gt;
Verify that every table, view, function and storage bucket enforces who can see what. Test with two accounts and an anonymous visitor. This is the layer where the most damaging bugs live.&lt;/p&gt;

&lt;p&gt;Layer 2: Authentication and sessions&lt;br&gt;
Check password reset flows, email verification, session expiry, OAuth redirect URLs and what happens when a user is deleted. Confirm that sensitive routes are protected on the server, not just hidden in the UI.&lt;/p&gt;

&lt;p&gt;Layer 3: Secrets and configuration&lt;br&gt;
Separate public and private keys, use different credentials for staging and production, and confirm that nothing sensitive is in the client bundle or the repository history.&lt;/p&gt;

&lt;p&gt;Layer 4: Input validation and payments&lt;br&gt;
Validate on the server. If money is involved, compute totals server-side, verify webhook signatures, and make payment handlers idempotent so a retried webhook cannot grant access twice.&lt;/p&gt;

&lt;p&gt;Layer 5: Dependencies&lt;br&gt;
AI tools sometimes pull in outdated, abandoned or unnecessary packages, and in rare cases suggest package names that do not exist or are typosquatted. Review the lockfile, remove what you do not use, and run an audit tool such as npm audit.&lt;/p&gt;

&lt;p&gt;Layer 6: Database performance and integrity&lt;br&gt;
Prototype data is tiny; production data is not. Look for missing indexes on columns used in filters and joins, queries that run inside loops (the N+1 pattern), unbounded list endpoints without pagination, and missing foreign keys or constraints that let bad data in.&lt;/p&gt;

&lt;p&gt;Layer 7: Errors, logging and monitoring&lt;br&gt;
If you do not know it broke, you cannot fix it. Add error tracking, structured logs, uptime checks and alerts that reach a person. Replace silent catch blocks with handling that reports the failure and shows users something useful.&lt;/p&gt;

&lt;p&gt;Layer 8: Deployment and environments&lt;br&gt;
Confirm that the app behaves the same on your host (Vercel, Cloudflare, Railway or Fly.io) as it did in the builder preview. Check environment variables, build settings, caching, and that migrations run in a controlled order. Enable backups and actually test a restore.&lt;/p&gt;

&lt;p&gt;Layer 9: Reliability under real conditions&lt;br&gt;
Try slow networks, double clicks, expired sessions, empty states and large inputs. Load-test the two or three endpoints that matter most, not the whole app.&lt;/p&gt;

&lt;p&gt;Layer 10: Discoverability and accessibility&lt;br&gt;
Single-page apps generated by AI tools often ship weak metadata, no sitemap and poor semantic markup. Fix titles, descriptions, canonical tags, heading structure, alt text and keyboard navigation. These are the last layer, not the first, but they decide whether anyone finds and can use the product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to use the checklist&lt;/strong&gt;&lt;br&gt;
Work top to bottom, record each finding with a severity, and fix by severity rather than by how easy the fix is. Re-run the layers after any large AI-generated change, since regeneration can quietly undo earlier fixes.&lt;/p&gt;

&lt;p&gt;A team that has built many such apps will tell you the same thing: the issues are rarely exotic. They are ordinary gaps that no one was assigned to look for. For a longer version of this process, see &lt;a href="https://fixier.co/13-layer-production-check" rel="noopener noreferrer"&gt;Fixier’s 13-layer production check &lt;/a&gt;and vibe coded app audit guide.&lt;/p&gt;

&lt;p&gt;Aakshar works at Fixier, a team that audits, fixes and supports apps built with Lovable, Bolt, v0 and Cursor. Learn more at &lt;a href="https://audit.fixier.co/#top" rel="noopener noreferrer"&gt;fixier.co&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Fixing vibecoded website's errors</title>
      <dc:creator>fixier</dc:creator>
      <pubDate>Fri, 06 Nov 2026 02:03:34 +0000</pubDate>
      <link>https://dev.to/fixier_audit/fixing-vibecoded-websites-errors-4b1k</link>
      <guid>https://dev.to/fixier_audit/fixing-vibecoded-websites-errors-4b1k</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/fixier_audit/why-ai-generated-supabase-apps-leak-user-data-and-how-to-audit-row-level-security-before-launch-3kdj" class="crayons-story__hidden-navigation-link"&gt;Why AI-Generated Supabase Apps Leak User Data, and How to Audit Row-Level Security Before Launch&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/fixier_audit" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4229215%2Fade77156-dddb-418c-86d5-bbf4419f4d26.gif" alt="fixier_audit profile" class="crayons-avatar__image" width="96" height="96"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/fixier_audit" class="crayons-story__secondary fw-medium m:hidden"&gt;
              fixier
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                fixier
                
                
              
              &lt;div id="story-author-preview-content-4985247" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/fixier_audit" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4229215%2Fade77156-dddb-418c-86d5-bbf4419f4d26.gif" class="crayons-avatar__image" alt="" width="96" height="96"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;fixier&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/fixier_audit/why-ai-generated-supabase-apps-leak-user-data-and-how-to-audit-row-level-security-before-launch-3kdj" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 6&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/fixier_audit/why-ai-generated-supabase-apps-leak-user-data-and-how-to-audit-row-level-security-before-launch-3kdj" id="article-link-4985247"&gt;
          Why AI-Generated Supabase Apps Leak User Data, and How to Audit Row-Level Security Before Launch
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/vibecoding"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;vibecoding&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/security"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;security&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/fixier_audit/why-ai-generated-supabase-apps-leak-user-data-and-how-to-audit-row-level-security-before-launch-3kdj#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
          
          &lt;div class="only-sidebar-menu-item trusted-visible-block print-hidden align-center"&gt;
                
                    
                
          &lt;/div&gt;

        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>ai</category>
      <category>database</category>
      <category>security</category>
    </item>
    <item>
      <title>Why AI-Generated Supabase Apps Leak User Data, and How to Audit Row-Level Security Before Launch</title>
      <dc:creator>fixier</dc:creator>
      <pubDate>Fri, 06 Nov 2026 02:00:44 +0000</pubDate>
      <link>https://dev.to/fixier_audit/why-ai-generated-supabase-apps-leak-user-data-and-how-to-audit-row-level-security-before-launch-3kdj</link>
      <guid>https://dev.to/fixier_audit/why-ai-generated-supabase-apps-leak-user-data-and-how-to-audit-row-level-security-before-launch-3kdj</guid>
      <description>&lt;p&gt;AI app builders are very good at producing screens, forms and working demos. They are far less reliable at the part nobody sees: who is allowed to read which row. In a typical Lovable or Bolt app backed by Supabase, the browser talks almost directly to Postgres. That means Row-Level Security (RLS) is not one security layer among many. It is the only thing standing between one user and everyone else’s data.&lt;br&gt;
The anon key is public by design&lt;br&gt;
Founders often panic when they see the Supabase anon key in their browser bundle. That key is meant to be public. The real question is what that key is allowed to do once someone copies it. If RLS is off or too loose, anyone can open the browser console and query your tables with the same client your app uses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Six RLS failures that show up again and again in generated code&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;RLS was never enabled. Tables created through SQL migrations do not have RLS switched on automatically. A generated migration that creates a table but skips alter table ... enable row level security ships an open table.&lt;/li&gt;
&lt;li&gt;Policies written as using (true). The AI makes the “permission denied” error go away by adding a policy that allows everything. It works, and it also exposes every row to every logged-in user (or to anonymous visitors, if the policy targets anon).&lt;/li&gt;
&lt;li&gt;INSERT policies that do not bind ownership. A policy with with check (true) lets a user insert a row with someone else’s user_id. Pair it with a permissive select policy and you have impersonation. Ownership must be enforced in the check clause:
alter table orders enable row level security;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="n"&gt;orders_select_own&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;authenticated&lt;/span&gt;
  &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="n"&gt;orders_insert_own&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;authenticated&lt;/span&gt;
  &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="k"&gt;check&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Wrapping&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;subselect&lt;/span&gt; &lt;span class="n"&gt;also&lt;/span&gt; &lt;span class="n"&gt;lets&lt;/span&gt; &lt;span class="n"&gt;Postgres&lt;/span&gt; &lt;span class="n"&gt;evaluate&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="n"&gt;once&lt;/span&gt; &lt;span class="n"&gt;per&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="k"&gt;instead&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;once&lt;/span&gt; &lt;span class="n"&gt;per&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;which&lt;/span&gt; &lt;span class="n"&gt;matters&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tables&lt;/span&gt; &lt;span class="n"&gt;grow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Authorizing on user_metadata. Claims inside user_metadata can be edited by the user. A policy such as “role is admin in user metadata” can be satisfied by the user themselves. Authorization data belongs in app_metadata or in a server-controlled table.&lt;/li&gt;
&lt;li&gt;Views and functions that bypass RLS. A view runs with the privileges of its owner by default, so it can return rows that the underlying table’s policies would block. On Postgres 15 and later, create views with security_invoker = true. Treat every security definer function as a potential policy bypass and review its inputs carefully.&lt;/li&gt;
&lt;li&gt;Storage buckets and the service role key. Public buckets and missing policies on storage.objects leak uploads such as invoices and IDs. Worse, generated code sometimes uses the service role key in client-side code. That key bypasses RLS entirely and must live only on the server.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;A practical pre-launch audit&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inventory tables without RLS. Start with a query that lists them:
select tablename
from pg_tables
where schemaname = 'public' and rowsecurity = false;&lt;/li&gt;
&lt;li&gt;Read every policy as an attacker. For each one, ask: who can run this, what rows can they touch, and can they change ownership columns?&lt;/li&gt;
&lt;li&gt;Test with two real users. Sign in as user A, create data, then try to read, update and delete it as user B. Repeat as an anonymous visitor. This catches what policy review misses.&lt;/li&gt;
&lt;li&gt;Check the exposed surface. Search the built JavaScript for service_role, review RPC functions callable by anon, and confirm storage bucket visibility.&lt;/li&gt;
&lt;li&gt;Use the platform’s own advisors. Supabase’s security advisor flags several of these issues, but it cannot know your business rules. A table can be correctly “protected” and still allow the wrong person to see it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Make it permanent&lt;/strong&gt;&lt;br&gt;
RLS bugs return whenever an AI tool regenerates a migration. Put policy tests in CI: create two test users, run queries under each role, and assert that cross-user access fails. Supabase supports pgTAP tests through its CLI, so a failing test can block the deploy that would have reopened a table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway&lt;/strong&gt;&lt;br&gt;
If your app has user accounts and was built mostly by prompts, treat RLS as the first thing to verify, not the last. For a deeper walkthrough, see Fixier’s Supabase RLS checklist and the guide on users seeing each other’s data.&lt;br&gt;
Jiya works at Fixier, a team that audits, fixes and supports apps built with Lovable, Bolt, v0 and Cursor. Learn more at &lt;a href="https://fixier.co/" rel="noopener noreferrer"&gt;fixier.co&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>vibecoding</category>
      <category>security</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
