Supabase security · 7 minute read
7 ways your Supabase app still leaks data with RLS enabled
You asked your AI to add row-level security, the dashboard shows RLS enabled on every table, and you shipped. But "RLS enabled" and "RLS correct" are two different things, and the gap between them is where real user data leaks. Here are seven surfaces that pass a casual glance, each with a read-only check you can paste into the Supabase SQL editor and the fix.
1. A policy with USING (true)
RLS is on, but the policy waves everyone through. It reads as secure in the dashboard.
select schemaname, tablename, policyname, qual
from pg_policies
where schemaname = 'public' and qual = 'true';Any row here is a table anyone, including the anonymous role, can read. Fix: scope it, for example using (auth.uid() = user_id).
2. A policy with no TO clause
No TO means the policy applies to every role, including anon. This is the single most common real leak I find.
select tablename, policyname, roles
from pg_policies
where schemaname = 'public' and roles = '{public}';A roles value of {public} means it also applies to anonymous visitors. Fix: add to authenticated.
3. A permissive policy silently cancelling a strict one
PostgreSQL combines permissive policies with OR. One loose USING (true) next to your careful auth.uid() = user_id means the loose one wins, for everyone. Fix:audit each table's full set of policies together, never one at a time.
4. A SECURITY DEFINER function callable by anon
Functions run with the owner's rights and bypass RLS. If anon can execute one that reads a table, RLS on that table is irrelevant.
select p.proname
from pg_proc p
join pg_namespace n on n.oid = p.pronamespace
where n.nspname = 'public' and p.prosecdef
and has_function_privilege('anon', p.oid, 'execute');Fix: revoke execute ... from anon, public; and grant only to the role that truly needs it.
5. Policies on a table where RLS is off
The policies exist and look right, but if row level security was never enabled on the table, none of them are enforced.
select c.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;Cross-reference this against tables that have policies. The overlap is policies doing nothing. Fix: alter table ... enable row level security;. Whether those rows actually reach a caller still depends on table grants and API exposure, so confirm those before calling it externally reachable.
6. The service_role key in your browser bundle
If your AI wired a NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY, or imported the admin client into a client component, the key that bypasses all RLS ships inside your JavaScript. Anyone who opens dev tools has full read and write.
npm run build && grep -ro "service_role" .next/staticAny hit is critical. Fix: move it server-side with import 'server-only', and rotate the key. Fixing the code is not enough, because the old key is already published.
7. Storage and Realtime that table RLS does not cover
Your messages table is locked down, but the Storage bucket with the attachments is public, or the Realtime publication broadcasts row changes to subscribers who should not see them. Fix: check bucket policies and your supabase_realtime publication separately from table RLS.
Run those seven. If they all come back clean, you are ahead of most shipped Supabase apps. A related, deeper walkthrough of the testing method is in how to test Supabase RLS policies before launch.
Run a public isolation fixture
The public Supabase RLS leak demo runs a read-only harness that queries as anon and as an authenticated user, so you can prove which role a request actually runs as. It uses synthetic data and no credentials.
Turn these checks into a repeatable audit
Supabase RLS Audit Kit · $29 one-time
Run seven commented SQL audits and 60 checks, then verify cross-user and cross-tenant isolation with a two-user staging harness. The ZIP also includes report, remediation, safe-test and fix-SQL templates.
Use it only on projects you own or are authorized to test. It is a self-run toolkit, not a managed penetration test or security certification.
Get the kit - $29 ↗Review the files, scope and license →Who this is and is not for
It is not for you if you are pre-launch with no real users, or you are happy writing these audits yourself. It is for you if you built something real, it holds actual user data, and you want to know it is not leaking before more people sign up. When several tables share tenant rules or a launch depends on proving cross-user paths are closed, a fixed-price Supabase RLS audit from $99 returns reproducible evidence and prioritized fixes rather than a generic score.