How to check if your Supabase RLS actually works
Checking that a policy exists is not the same as checking that it denies anything. Test it the way an attacker would.
Supabase gives your browser a key and lets it query the database directly. Row Level Security is what decides which rows come back. Get it wrong and the answer is: all of them, to anyone.
The two-minute test
Query your own database as a stranger
- 1Open your live app, press F12, Network tab, reload the page.
- 2Click any request to *.supabase.co and copy the apikey header value and the project URL.
- 3List every table the key can see by requesting /rest/v1/ — Supabase returns an OpenAPI document naming them.
- 4For each interesting table, run the query below.
List the tables, then read one
# what tables can this key see? curl -s "https://YOUR-PROJECT.supabase.co/rest/v1/" \ -H "apikey: YOUR_ANON_KEY" | head -c 2000 # can an anonymous caller read rows? curl "https://YOUR-PROJECT.supabase.co/rest/v1/profiles?select=*&limit=3" \ -H "apikey: YOUR_ANON_KEY" \ -H "Authorization: Bearer YOUR_ANON_KEY"
Reading the result
- [] — the table is protected. RLS is on and no policy grants anonymous reads.
- Rows of real data — the table is public to the internet. Fix before anything else.
- {"message":"permission denied for table ..."} — RLS is on and denying at the role level. Also fine.
- Something about the relation not existing — wrong table name, not a security result.
Common ways RLS ends up not working
- RLS was never enabled on the table — policies are ignored entirely until you ALTER TABLE ... ENABLE ROW LEVEL SECURITY.
- A policy with USING (true), often added to 'make it work' during development and never revisited.
- A policy on SELECT only, leaving INSERT, UPDATE and DELETE unrestricted.
- Rules based on a role stored in user metadata the client can edit — the user can promote themselves.
- Views that bypass the underlying table's policies.
Paste this into your AI coding tool
Audit Row Level Security across my whole Supabase project. For every table in the public schema: 1. Report whether RLS is enabled. 2. List its policies, with the full USING and WITH CHECK clauses. 3. Flag any policy that is unconditional (USING true) or that depends on a value the client controls, such as a role stored in editable user metadata. 4. Confirm coverage for all four commands — SELECT, INSERT, UPDATE, DELETE — not just SELECT. Then fix them: enable RLS everywhere, replace unconditional policies with ownership checks written as USING ((select auth.uid()) = user_id), and for tables no browser should touch, enable RLS with no policy so they deny by default. Finish with a table: table name, RLS on/off, policies per command, and whether an anonymous user can read it.
This is one check out of 40+
Paste your site address and we run the whole list from the outside — leaked keys, open databases, unprotected pages — then hand you one prompt that fixes what we find. Free, about 30 seconds, no signup.
Check my site — free