What AI-scaffolded apps get wrong about security, and the fixes that actually close it.
In January 2026, a startup called Moltbook shipped with their Supabase database wide open. No breach, no sophisticated attack: anyone who copied the project URL from the browser could make a raw HTTP request and read 1.5 million API keys. The app functioned perfectly. Every manual test passed. The database was just sitting there.
This is not a story about bad developers. It is a story about a gap that AI-assisted tooling creates by default: the gap between an app that works and an app that is secured. Nothing in the demo experience tells you the difference.
Your Supabase anon key ships inside your JavaScript bundle. That is not a mistake — Supabase designed it that way. The anon key is public by intent, meant to be visible in browser dev tools. It is safe for exactly one reason: Row Level Security. With RLS correctly in place, the anon key can only do what your policies allow. Without it, the anon key is a skeleton key to your entire public schema.
An AI coding tool will generate a complete, working frontend against that open database. Click through every screen. Create a user, load data, submit a form. It all functions. Nothing in that experience surfaces the fact that the same data is readable by anyone on the internet with a curl command. A 2025 analysis found that 10.3% of tested AI-scaffolded apps exposed vulnerable Supabase endpoints due to missing or misconfigured RLS.
Functional correctness and access control are different concerns, and only one of them shows up in a manual click-through. That gap is the central failure mode.
RLS has three states, and confusing them is the most common mistake. Only one of the three is actually safe.
The default on every new Supabase table. The table is fully public — anyone with your anon key can read or write everything, with no authentication required. Your frontend auth checks do not protect this; those run on the client you control, not on the database.
The opposite failure. The table is fully locked — including from your own app. Every query returns zero rows, every write is rejected. Your app looks broken, not insecure. This is actually the safer failure mode to land in by accident, because it surfaces immediately rather than silently.
Where you want to be. Access is scoped to what your policies explicitly permit — typically the authenticated user's own data, or data their role grants them access to. This is the only state that is actually secure.
The OWASP Top 10 is the industry-consensus list of the most critical web application security risks. The 2025 edition added two new categories that reflect how cloud-native, AI-assisted apps actually break. The list matters, but not equally — for a solo builder shipping on Supabase, two categories are doing most of the work.
A01 (Broken Access Control) and A02 (Security Misconfiguration) account for the overwhelming majority of real incidents in small, fast-shipped apps. A01 is the category that missing or misconfigured RLS falls into — it has been the number-one risk on the OWASP list for four years running. A02 jumped from fifth to second in 2025, driven by cloud platform defaults being left open. Every other category on this list is real, but these two are where the actual incidents happen.
These are the highest-leverage moves, ordered by when to apply them.
Run this in the Supabase SQL editor on every project you own. It takes five minutes and it is the check that would have caught every real-world incident cited in this article.
SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public' AND rowsecurity = false;
Every table this query returns is publicly readable and writable through the REST and GraphQL APIs right now, regardless of what your frontend appears to enforce. Do this before anything else in this guide.
There is a pattern that looks like RLS but does nothing. Know it so you can spot it in generated code.
-- Enable on every table, every time ALTER TABLE profiles ENABLE ROW LEVEL SECURITY; -- The pattern you want CREATE POLICY "users read own profile" ON profiles FOR SELECT USING (auth.uid() = user_id); -- UPDATE needs both clauses — USING governs reads, -- WITH CHECK governs writes CREATE POLICY "users update own profile" ON profiles FOR UPDATE USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id);
The USING / WITH CHECK distinction matters: UPDATE policies need both, or a user can read their own row but silently overwrite it with another user's data. Add ALTER TABLE ... ENABLE ROW LEVEL SECURITY to every table-creation migration as a standing habit — not an afterthought once data exists.
The Supabase SQL editor runs as an elevated role that bypasses RLS entirely. Testing a policy there tells you nothing about what a real user can actually do. Test from the client SDK, logged in as different real accounts, and verify each policy behaves as intended. Testing as yourself while you are also the database admin is how policies that do nothing ship to production looking correct.
The service_role key bypasses RLS completely — full database access, no restrictions. It should never appear in client-side code. The most common real-world leak paths:
Ships the key straight into the browser bundle. Any env variable prefixed NEXT_PUBLIC_ is public by design.
Even briefly. Even with "just for testing" in the commit message. Rotation is the only recovery.
Or returned in an error response from an Edge Function. Scan your logs if you suspect this.
If you suspect a service_role key has leaked, rotate it immediately in the Supabase dashboard, then audit query logs for anything that could not plausibly have come from your own app.
An unindexed policy check is the single most common performance killer in production Supabase apps. A policy on an unindexed column can turn a 2ms query into a multi-second one — which then becomes the reason someone disables the policy to "fix" a performance problem. Every column referenced inside a USING or WITH CHECK clause needs an index. Do not give anyone a reason to remove security for speed.
Treat "it works" and "it's secured" as two separate checkpoints, not one. An AI coding tool will happily scaffold a fully functional app against a completely open database, because functional correctness and access control are different problems and only the first one shows up in a demo.
Ask for RLS policies in the same prompt as the table — not as a follow-up. "Create this table with RLS enabled and a policy scoping rows to the authenticated user's own data" gets you the right output. "Create this table" followed by "oh, and secure it later" produces a table that is live and open until you remember.
Review AI-generated error handling specifically. Generated code often returns raw exception details to help with debugging during development — stack traces, database error messages, table names. That pattern, shipped to production unchanged, is an information leak. Fail closed by default: deny access when something goes wrong, return a generic error to the client, and log the detail server-side.
Run the audit query from Fix 1 before every deploy, not just at initial launch. It takes five minutes. A significant schema change is enough to introduce an unprotected table — normalising the check means you catch it before it ships rather than after.
Run this against any project before it goes live, and again after any significant schema change. The ongoing items belong in your regular review cycle.
The security gap in AI-scaffolded apps is not a model problem or a tooling problem — it is a checkpoint problem. The five fixes above are not complex. None of them require specialised knowledge. They require only that you treat "it works" and "it's secured" as two separate questions, and answer both before you ship.
OWASP Foundation · owasp.org
The definitive ranked list of critical web application security risks, updated regularly by the open security community — the baseline for any security review.
Stuttard & Pinto · Wiley
Comprehensive attack-and-defend coverage of SQL injection, authentication flaws, access control, and CSRF — understanding the attack is the prerequisite for the defence.
Ross Anderson · Wiley
The most thorough academic treatment of building secure systems, covering cryptography, protocols, and psychology of security failure — freely available online.
Continue the conversation
If this changed how you think about it — or you think I'm wrong — I want to know.
Corrections, disagreements, and applications all welcome. Replies go directly to Chris.
Get in touch →THE OPEN DOOR
~6-8 min1× · Two speakers · tap to play