Prevent Supabase RLS bypass
Row Level Security is the only thing standing between your anon key and your data. That key ships in the client bundle and is public by design, so any table PostgREST exposes without a working policy is readable by anyone who opens devtools.
There are three distinct ways to get this wrong, and they fail differently. RLS switched off entirely. RLS on with a policy that is a constant true. And a write policy with nothing constraining what gets written. RowShield checks all three on every scan.
Rules that check this
- criticalRow Level Security disabled
RLS_DISABLED - criticalPolicy always evaluates to true
RLS_TAUTOLOGY - highWrite policy without WITH CHECK
MISSING_WITH_CHECK - highRLS enabled but no policies
RLS_NO_POLICIES
RLS disabled is the loud one
A table with `relrowsecurity = false` in a PostgREST-exposed schema returns every row to an unauthenticated request. This is what almost every reported Supabase data leak turns out to be.
It is also the easiest to check, and the query below is exactly what RowShield runs — reading `pg_catalog` only, never your data.
SELECT n.nspname AS schema, c.relname AS table
FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r', 'p')
AND n.nspname = 'public'
AND c.relrowsecurity = false;A policy that says true is worse than no policy
`USING (true)` is the quiet failure. RLS is enabled, so every dashboard and advisor reports the table as protected, while the policy grants every row to every role it targets.
Two cases are deliberately not flagged, because reporting them is noise: a RESTRICTIVE policy can only ever narrow access, so `USING (true)` on one grants nothing; and a policy scoped to `service_role` is decorative, because that role already carries BYPASSRLS. The Supabase dashboard generates exactly that policy.
Writes need their own clause
A SELECT policy governs what a caller can read. It says nothing about what they can write. An INSERT policy without WITH CHECK lets a caller create rows attributed to anybody.
For UPDATE and ALL, Postgres falls back to the USING expression when WITH CHECK is absent. That is usually right and silently wrong the moment read and write conditions are meant to differ — so RowShield flags it and says which case you are in.
The four checks that catch most of it
Run these against any Supabase project you inherit and you will find the dangerous majority of real-world leaks. First, list every public table with RLS disabled — the query above does it in one round-trip, and anything it returns is exposed right now rather than theoretically.
Second, dump every permissive policy whose condition reduces to constant true, including the normalised forms like 1 = 1 or NOT false that eyeballs skip past. Third, list INSERT, UPDATE and ALL policies with no WITH CHECK clause at all, since those accept writes from anyone they target. Fourth, ask PostgREST directly what the anon key can fetch from your users-shaped tables, remembering that an empty response proves filtering only if you know the table has rows.
Four checks take fifteen minutes by hand, which raises the obvious question of why projects still leak: because the fifth migration after your audit is the one without policies, and nobody re-runs the checklist. That recurrence argument — state decaying between audits — is the entire reason scheduled monitoring exists, and why every check above runs automatically in each RowShield scan with transitions tracked over time.
Frequently asked
- Does enabling RLS break my app?
- It denies everything until a policy grants access, so yes, until you add policies. RowShield generates the four owner-scoped policies from your actual column names so you can enable and grant in one paste.
- Is FORCE ROW LEVEL SECURITY necessary?
- Without it the table owner bypasses every policy — and a Supabase migration or psql session runs as the owner. The generated SQL always includes it.
- How is this different from the Supabase Security Advisor?
- The advisor is a point-in-time check you have to remember to open. RowShield runs on a schedule, diffs against the last run, and tells you when something that was fixed comes back.
Check your project in about ten seconds
Paste a URL. No signup, no writes, nothing stored.
Run the free audit