RowShield
Guides

Checking whether RLS is enabled: one query, read honestly

Run one query against pg_catalog and read the relrowsecurity column: true means Row Level Security is enabled for that table, false means ordinary grants decide who reads it — and in a Supabase project the anon role holds generous grants on the public schema, so a false on a public table means world-readable to anyone holding your anon key.

Below are the query itself, the second flag people miss (FORCE), the honest limits of a true, and the statements that repair a false. Everything reads metadata only; no rows leave your database.

RowShield does not detect this yet. This guide gives you the catalog queries to check it yourself. The nine rules that do ship are listed on the rules index.

The query

relrowsecurity is the catalog flag ENABLE ROW LEVEL SECURITY sets, and pg_class carries it for every relation. Filtered to the public schema — the surface PostgREST exposes — it produces the complete picture in one round-trip:

SELECT n.nspname AS schema,
       c.relname AS table_name,
       c.relrowsecurity AS rls_enabled,
       c.relforcerowsecurity AS forced
FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r', 'p')   -- tables and partitioned tables
  AND n.nspname = 'public'
ORDER BY c.relname;

Reading the result

Any row where rls_enabled is false is a finding, full stop. There is no context that makes an unprotected public-schema table safe while the anon key ships to every browser: lookup tables leak your product model, staging leftovers leak your real columns, and "nobody knows the URL" has never once held.

True is better but partial. It promises that policies will be consulted for non-owner callers; it says nothing about what those policies grant. The two follow-on states — a policy whose condition is a constant true, and an enabled table with zero policies — fail in opposite directions, so treat this query as step one of the audit rather than the verdict.

The flag people miss: FORCE

Without FORCE ROW LEVEL SECURITY, the table owner bypasses every policy — and migrations, psql sessions and many administrative paths run as the owner. The forced column in the query above shows it; a table showing true and false in those two columns is protected against the internet but naked against every script connecting with owner rights. Setting both is one extra statement:

ALTER TABLE public.projects ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.projects FORCE ROW LEVEL SECURITY;

Caveats worth knowing

Views carry no relrowsecurity of their own; whether a view respects the caller's policies depends on security_invoker being set, which is a different check. Partitioned tables appear with relkind p and are included by the query on purpose — policies attach per partition, so verify children too. And platform schemas are intentionally left out of the filter: auth, storage and extensions manage their own posture, and flagging them buries your real findings under noise that was never yours to fix.

Run it anywhere an authenticated connection exists — the SQL editor, psql, or a migration dry-run — because the catalog is identical from every seat.

Keeping the flag watched

The query decays the day after a migration creates the next table, which is why RowShield runs this exact class of check on every scheduled scan: the catalog snapshot is diffed against the previous run, a table appearing without RLS raises an alert naming it, and a fix that regresses is labelled a regression rather than rediscovered. To be plain about scope, RowShield does not automate anything beyond its nine shipped rules and the probe — this page is hand-written guidance, and the monitoring described covers precisely the mechanical layer shown above. RowShield is an independent product, not affiliated with or endorsed by Supabase. Run a free audit at rowshield.dev/audit — the public check needs no account.

Frequently asked

Can I run this in the Supabase SQL editor?
Yes. The statement reads only catalog metadata, so it runs safely from the SQL editor, psql or any authenticated connection, and returns identical results from each.
Does RLS apply to the table owner?
Not unless FORCE ROW LEVEL SECURITY is also set. Migrations and administrative sessions usually connect as the owner, which is why the forced column deserves the same glance as the enabled one.
Do views show up in the query?
No — relrowsecurity belongs to tables, and views rely on security_invoker to respect the caller's policies. Auditing views is a separate query, and mixing the two hides problems in both directions.

Check your project in about ten seconds

Paste a URL. No signup, no writes, nothing stored.

Run the free audit
check if rls enabled supabasesupabase relrowsecuritysql check row level securityfind tables without rls