DEV Community

Tori TIC
Tori TIC

Posted on

The 5 RLS holes that quietly leak data in AI-built Supabase apps

Your app works. Sign-ups land, dashboards load, and Stripe pays out. Then one day a stranger reads another user's data, and you find out the door was never locked.

That is what broken Row Level Security looks like in a Supabase app. It is not a crash. There is no error in the console. The app behaves perfectly for you while quietly serving other people's rows to anyone who asks the API directly. If your app was built with Lovable, Bolt, Cursor, or Replit, this is the single most common security hole it shipped with.

Here are the five ways it happens, and a free read-only auditor that finds all five in about a minute.

1. RLS is switched off on a table

The number one leak. Someone (often the AI assistant) created a table after row security was set up, and nobody remembered to lock it. With RLS off and the standard grants in place, that table is world-readable and writable through your public API.

Check it yourself:

select relname
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public'
  and c.relkind = 'r'
  and not c.relrowsecurity;
Enter fullscreen mode Exit fullscreen mode

Anything this returns is unprotected right now.

2. RLS is on, but there are zero policies

Row security without policies means Postgres denies everything for regular roles. This is the classic cause of the 42501 permission denied error that appears after a deploy. It fails closed, which is safer than open, but it also means the feature you shipped does not work, and the usual "fix" people paste from a forum is to switch RLS off. See hole number 1.

3. A write policy with no WITH CHECK

A policy's USING clause controls which rows you can see. The WITH CHECK clause controls what you are allowed to write. A policy that sets only USING on insert or update lets an authenticated user write rows as somebody else, for example inserting messages with another user's id. Reads look secure, writes are wide open.

4. A policy that is true for everyone

A policy like using (true) on a sensitive table passes every review that only checks "RLS is on and policies exist". It protects nothing. These usually appear as leftover debugging or as AI-generated scaffolding that was never tightened.

5. The anon role holds a direct write grant

Even with sensible policies, a stray grant insert on ... to anon gives the anonymous API role a door of its own. This one hides because nobody looks at grants after setup.

The one-minute audit

I bundled all five checks into a single read-only SQL file. You paste it into your Supabase SQL editor, run it, and get a prioritized findings table: severity, table, what the hole is in plain language, and the copy-paste fix.

It reads system catalogs only. It never touches your rows, changes nothing, and you can read every line before you run it. No install, no account access, no keys shared.

Download it free here: ticassociation.com/supabase-rls-audit

Honest limits

The auditor catches the common, repeated failure patterns fast. It is not a penetration test, and an app holding sensitive data still deserves a real security review before scale. Treat it as your pre-launch preflight: five minutes that rule out the five most likely leaks.

If it flags something you cannot fix, that is literally what we do all day: we repair broken AI-built apps, RLS most of all. Free diagnosis at rescue.ticassociation.com, and you pay only after the fix is verified working.

Top comments (0)