← Field Notes
Engineering11 August 2026·11 min read·Chris Ma

THE
OPEN
DOOR.

What AI-scaffolded apps get wrong about security, and the fixes that actually close it.

SecuritySupabaseRLSOWASPVibe Coding

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.

Key Takeaways
  • A 2025 analysis found 10.3% of tested AI-scaffolded apps exposed vulnerable Supabase endpoints due to missing or misconfigured Row Level Security.
  • Without RLS, the Supabase anon key (which ships inside your JavaScript bundle by design) is a skeleton key to your entire public schema accessible to anyone with a curl command.
  • Functional correctness and access control are different concerns. Only one shows up in a manual click-through. AI coding tools verify the former; you must verify the latter separately.
  • OWASP LLM Top 10:2025 adds two new categories specific to AI systems: Unbounded Consumption and Vector and Embedding Weaknesses. Both are already exploitable in production.
01

WHY THE DEMO LIES

#

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.

02

THE THREE STATES OF A TABLE

#

RLS has three states, and confusing them is the most common mistake. Only one of the three is actually safe.

RLS OFFAnyone with your anon keycan read/write everything.default on every new tableenable RLSRLS ON, NO POLICIESAll queries return zero rows.Your app looks broken.safer failure — still not rightwrite policiesRLS ON + POLICIESAccess scoped to authenticateduser. Controlled, tested.where you want to be
StateRLS off

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.

StateRLS on, no policies

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.

StateRLS on + policies

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.

03

OWASP TOP 10:2025, TRANSLATED

#

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.

CODECATEGORYYOUR MOVEPRIORITYA01Broken Access ControlMissing/wrong RLS is exactly thisA02Security MisconfigurationDefault settings left openA03Supply Chain FailuresVerify before npm installA04Cryptographic FailuresNever store secrets plaintextA05InjectionDon't bypass the client libraryA06Insecure DesignThreat-model before you buildA07Authentication FailuresUse Supabase Auth, not custom sessionsA08Integrity FailuresVerify webhooks and third-party payloadsA09Logging & Alerting FailuresSet one alert for anomalous auth activityA10Mishandling Exceptional ConditionsFail closed; never leak raw DB errorsA01 + A02 ACCOUNT FOR THE MAJORITY OF INCIDENTS IN SMALL, FAST-SHIPPED APPS
The two that actually matter

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.

04

FIVE FIXES, IN ORDER

#

These are the highest-leverage moves, ordered by when to apply them.

1. Audit what is open right now

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.

2. Enable RLS and write real policies

There is a pattern that looks like RLS but does nothing. Know it so you can spot it in generated code.

BAD — USING (true)CREATE POLICY "users can read" ON profiles FOR SELECT USING (true);→ Every row. Every user. No access control.RLS enabled in name only — functionally open.Treat USING(true) the same as no RLS at all.GOOD — USING (auth.uid() = user_id)CREATE POLICY "users read own row" ON profiles FOR SELECT USING (auth.uid() = user_id);→ Only the authenticated user's own rows.UPDATE policies need both USING and WITH CHECK.auth.uid() comes from the JWT — no extra query.
-- 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.

3. Test from the client, not the SQL editor

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.

4. Never expose the service_role key

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:

NEXT_PUBLIC_ prefix

Ships the key straight into the browser bundle. Any env variable prefixed NEXT_PUBLIC_ is public by design.

Committed to a repo

Even briefly. Even with "just for testing" in the commit message. Rotation is the only recovery.

Logged at server boot

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.

5. Index every column referenced in your policies

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.

05

WHEN YOU'RE BUILDING WITH AI

#

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.

Standing habit

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.

06

PRE-LAUNCH CHECKLIST

#

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.

Access control

  • Run the RLS audit query on every table in the public schema
  • Confirm every table has RLS enabled, not just some
  • Confirm no USING (true) policies unless the data is genuinely meant to be public
  • UPDATE policies have both USING and WITH CHECK clauses
  • Policies tested from the client SDK under real, different user accounts

Secrets and keys

  • service_role key does not appear in client-side code or NEXT_PUBLIC_ variables
  • No secrets committed to the repo, including in commit history
  • .env files are gitignored — verified in the actual repo, not assumed

Configuration

  • Storage buckets reviewed — confirm which are intentionally public
  • Production error responses do not expose stack traces or raw database errors
  • Dependencies reviewed for anything unfamiliar or recently added

Ongoing

  • At least one alert configured for anomalous auth or access activity
  • This checklist re-run after any schema change, not only at initial launch

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.

Recommended Reading

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 →
Field Notes · PodcastHost + Expert · Gemini TTS

THE OPEN DOOR

~6-8 min

1× · Two speakers · tap to play