Home / RLS / Why is my table public? (RLS off)

Supabase RLS · Leak

Why is my Supabase table public? (RLS is off)

Postgres 15 · Supabase✓ code tested against a real database
TL;DR

Your anon key is public (it ships in the browser). With RLS off, that key can read the whole table over the REST API. Confirm with one curl, then enable row level security and add a policy.

01 The anon key is public — that's by design

Supabase's anon key is meant to ship in your frontend. It's not a secret. The thing that stops it from reading everyone's data is RLS. So a table with RLS off is readable by anyone who opens your site's network tab and copies that key. This is the single most common Supabase data leak.

02 See the leak in one command

terminal

# with RLS off, the public anon key returns every row
curl "$SUPABASE_URL/rest/v1/notes?select=*" \
  -H "apikey: $ANON_KEY"
-- → returns ALL rows, from every user

If that returns data you expected to be private, the table is public.

03 Confirm which tables are exposed

audit.sql

select tablename
from pg_tables t
join pg_class c on c.relname = t.tablename
where t.schemaname = 'public'
  and c.relrowsecurity = false;   -- RLS is off

04 Close it

migration.sql

alter table notes enable row level security;

create policy "owner reads own notes" on notes for select
  using (user_id = (select auth.uid()));

Then re-run the curl as an anonymous caller — it should now return an empty set. To make sure a new table can never ship like this again, add an RLS gate in CI.

◆ FREE · MIT · npm + GitHub Action

Catch this before it ships

airlock-rls is a CI gate that fails your build when a table ships exposed or a policy is permissive — the same class of bug, caught on the pull request instead of in prod.

npx airlock-rls

Or start from nextjs-supabase-starter — auth + a table with RLS + an isolation test, so a fresh table is safe by default.

FREE PDF

Grab the Supabase RLS cheat sheet

The golden rules, the footguns that leak in prod, correct policy snippets, and the isolation test — on one page.

FAQ

Isn't the anon key protected by my app's login?

No. The anon key works without any login — that's what 'anonymous' means. Your app's login controls your UI, not the REST API. Anyone can call the API directly with the anon key; RLS is what makes that safe.

Should I hide the anon key instead of enabling RLS?

You can't — it ships to every browser that loads your app. Hiding it is impossible and unnecessary. Enable RLS and the public anon key becomes harmless.