ProdGuard

Guide
Supabase · Postgres

How do I check if Row Level Security is enabled on all my Supabase tables?

Run this query in the Supabase SQL editor. It lists every table in your public schema, whether RLS is on, and how many policies each one has.

select
  c.relname                                as table_name,
  c.relrowsecurity                         as rls_enabled,
  c.relforcerowsecurity                    as rls_forced,
  (select count(*) from pg_policies p
     where p.schemaname = n.nspname
       and p.tablename  = c.relname)       as policy_count
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public'
  and c.relkind = 'r'
order by c.relrowsecurity asc, c.relname;

Any row where rls_enabled is false is readable and writable by anyone holding your anon key — which is shipped to every browser that loads your site. That is the whole finding.

The part that catches people

Enabled is not the same as protected

There are three states, and only one of them is safe. The query above distinguishes all three:

StateWhat actually happens
Open rls_enabled = false
Anyone with the anon key can read and write every row. No policy is consulted, because policies are only evaluated when RLS is on.
Locked out rls_enabled = true, policy_count = 0
RLS with no policies denies everything to anon and authenticated roles. Safe, but your app will appear broken — this is the state people "fix" by turning RLS back off, which is how the hole gets made.
Protected rls_enabled = true with a policy that actually restricts
The only state that does what you think it does.

There is a fourth state worth knowing about: RLS enabled, with a policy of using (true). That passes every "is RLS on?" check ever written, including the query above, and permits everything. Read your policies, not just the flag:

select schemaname, tablename, policyname, cmd, qual
from pg_policies
where schemaname = 'public'
order by tablename;

A qual of true means that policy allows the whole table.

Fixing it

Turning RLS on without locking yourself out

Enable it, then immediately add a policy — in the same migration, so the table is never live in the "locked out" state:

alter table public.documents enable row level security;

create policy "owners read their own documents"
  on public.documents
  for select
  using (auth.uid() = user_id);

create policy "owners write their own documents"
  on public.documents
  for all
  using (auth.uid() = user_id)
  with check (auth.uid() = user_id);

using controls which existing rows are visible; with check controls what a user is allowed to write. Omitting with check on a write policy is a common gap — it lets someone insert a row belonging to a different user.

Your service-role key bypasses RLS entirely, by design. That is why it must never reach the browser, and why testing your policies while authenticated as service-role tells you nothing.

Keeping it that way

Checking it automatically

The query above is a point-in-time answer. The failure worth guarding against is the one that happens later: a migration, or an AI coding agent asked to fix a query, quietly turning RLS off again. That change looks completely ordinary in a diff.

ProdGuard is a free, open-source command that reads your repository and fails your build when it finds that class of change — RLS disabled, a table created without RLS ever being enabled, a service-role key reachable from client code, and nine others:

npx prodguard check

Zero dependencies, MIT, nothing to sign up for, and no data leaves your machine. It reads text rather than parsing your program, so it is a safety net rather than a proof — what it does and does not catch is written out in full.

Worth knowing either way: settings you change in the Supabase dashboard rather than in a migration are not in your repository, so no repo scanner can see them. Only what is committed can be checked.