How to Check Supabase Row Level Security Holes (Free SQL Audit)

# supabase# postgres# security# webdev
How to Check Supabase Row Level Security Holes (Free SQL Audit)Tori TIC

Row level security in Supabase fails quietly. There is no crash, no red error banner, nothing in your...

Row level security in Supabase fails quietly. There is no crash, no red error banner, nothing in your logs that says "this table is wide open." The app keeps working in the demo. The first sign anything is wrong is usually someone else's data showing up where it shouldn't, and by then it already happened.

Here is how to check for the five holes that cause almost all of it, using nothing but a read-only SQL query against your own database's catalog.

The five holes, in order of how often they actually happen

1. RLS switched off entirely. The single most common leak. It usually happens to a table created after row security was already turned on for the rest of the schema, and the new table never got the same treatment. Anyone with your anon key can read and write it.

2. RLS is on, but zero policies exist. This is the most common cause of the 42501 error ("new row violates row-level security policy"), and it goes both ways: depending on the default deny/allow behavior for the operation, the table either blocks everything (your own app breaks) or exposes everything (your data leaks), and it is easy to not notice which one happened.

3. A write policy with no WITH CHECK. A policy can correctly gate who is allowed to write, using USING, while leaving the actual row values unchecked. That lets an authenticated user insert or update a row that claims to belong to someone else, because nothing verified what they wrote, only that they were allowed to write.

4. A policy that resolves to true for everyone. This one is sneaky because it looks like security. There is a policy. It has a name. It shows up in your dashboard as "protected." But the condition inside it evaluates to true for every role, so it protects nothing while giving every appearance of doing so.

5. The anon role holds a direct grant. Someone ran a GRANT statement straight against the anonymous role at some point, usually while debugging, and it bypasses your row-level policies entirely regardless of how carefully those policies are written.

The query

You can check for most of these directly from Supabase's own SQL editor, because the answers live in Postgres's own catalog tables (pg_policies, pg_class, information_schema), not in anything Supabase adds. A useful starting query:

select
  n.nspname as schema,
  c.relname as table_name,
  c.relrowsecurity as rls_enabled,
  count(p.polname) as policy_count
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
left join pg_policies p on p.tablename = c.relname and p.schemaname = n.nspname
where n.nspname = 'public' and c.relkind = 'r'
group by n.nspname, c.relname, c.relrowsecurity
order by rls_enabled asc, policy_count asc;
Enter fullscreen mode Exit fullscreen mode

Any row with rls_enabled = false is hole #1. Any row with rls_enabled = true and policy_count = 0 is hole #2. Holes #3 through #5 need a look at the actual policy definitions (pg_policies.qual and .with_check) and at information_schema.role_table_grants for direct grants to anon, which is more than fits cleanly in one query.

Why this is worth checking even if nothing looks broken

The whole point of these five is that none of them produce an error under normal use. Your own testing, as the table's owner or as an authenticated user hitting your own rows, will not surface a policy that is technically present but too permissive, or a grant that quietly overrides your policies. The only way to know is to check the catalog directly, which is exactly what this kind of query is for.

We built a free, one-file version of this check that runs the fuller version of the above (read-only, checks all five holes, plain-language output with the fix for each finding) and never asks for your project URL or your keys, because it runs inside your own SQL editor. It is a SQL file, so you can read exactly what it does before you run it: https://ticassociation.com/supabase-rls-audit

Nothing here is a substitute for reading your own policies. It is a fast way to find out where to start looking.