Security6 min read

The Supabase RLS Mistakes Hiding in Your AI-Generated App

Row-Level Security (RLS) is Postgres's per-row permission system, and in Supabase it is the only thing standing between your public anon key and your users' data. AI builders routinely create tables without RLS or with policies that look right and aren't. The ten-minute test: sign up as a second user, open the browser console, and try to select another user's rows with the anon client. If anything comes back, you have a breach in waiting.

By Mohit Sengar — fractional CTO, 14+ years, 40+ shipped engagements.

Why is RLS the single biggest risk in AI-built apps?

Supabase's architecture puts your database one HTTP call away from the browser, authenticated with a public anon key. That design is safe exactly when RLS is on and correct for every table. AI generators create tables on demand as your prompts evolve — and each new table is another chance for the security layer to be skipped. Nothing fails visibly: the app works identically with or without RLS. That's what makes it dangerous.

What are the five most common RLS mistakes?

  • RLS never enabled — the table was created mid-prompt and policies were never mentioned. Every row is public to anyone with the anon key.
  • SELECT protected, mutations open — you can't read other users' rows but you can UPDATE or DELETE them. Policies must cover all four verbs.
  • USING (true) — a policy exists, so the dashboard looks green, but it grants everyone everything. Read what the policy says, not that it exists.
  • Trusting client-supplied user_id instead of auth.uid() — an attacker just sends someone else's id in the payload. The policy must derive identity from the JWT.
  • Storage buckets forgotten — table RLS is perfect while uploaded files (invoices, exports, avatars) sit in a public bucket with guessable URLs.

How do I test my RLS in ten minutes?

Create a second account. In the browser console (your app already exposes the anon client), run a select on each sensitive table filtered to the FIRST user's id — then an update, then a delete. Everything should return empty or a policy error. Then list your tables in the Supabase dashboard and confirm RLS is enabled on every one, and open each policy and read its USING clause. Finish with storage: try fetching another user's file URL logged out.

What if I find a hole and users are already live?

Don't push a hasty policy that breaks your own app's queries — that's how a security fix becomes an outage. Enable a deny-by-default policy on the exposed table, verify your app's legitimate paths still work through server routes, then write proper per-verb policies with auth.uid(). If payments or PII were exposed, rotate keys and assess disclosure obligations. This is also the moment to get a professional audit rather than prompting the same generator that created the hole.

Quick answers

Does enabling RLS slow down my queries?

Policies add a WHERE clause, effectively. With an index on the column used (usually user_id), the overhead is negligible for typical app workloads.

Is the Supabase anon key secret?

No — it's designed to be public. That's precisely why RLS must carry all authorization. If your security depends on hiding the anon key, you don't have security.

Can AI write correct RLS policies?

Sometimes — but you can't tell by whether the app works, because it works either way. Correctness needs adversarial testing as a second user, which is exactly what generators don't do.

Keep reading