Without FORCE RLS the table owner bypasses everything
A detail of Postgres that surprises nearly everyone once: Row Level Security does not apply to the table's owner. Enable RLS, write perfect policies, and sessions connecting as the owning role still see and modify every row.
RowShield does not automate a check for this page's subject — FORCE usage is a posture recommendation rather than a detectable defect — so treat this as guidance to apply in your own review, alongside the catalog queries below.
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.
Why it matters more on Supabase
Migrations, CLI pushes and dashboard SQL editors commonly run as the privileged role that owns your tables. Every policy you wrote was written to govern browser-presented roles — and does — but the same credentials your tooling uses sit outside that government by default.
Most of the time that is fine: your migration runner should be trusted. The exposure appears when owner-equivalent credentials leak into application code paths, or when a service intentionally connects as the owner for convenience and ends up serving request traffic.
Ownership is a stored fact, not a role you join: pg_class.relowner holds it, set at creation and changed only by an explicit ALTER TABLE OWNER. Everything that creates tables in a Supabase project — migrations, CLI pushes, the dashboard editor — creates them owned by the same privileged role, so the exemption concentrates on precisely the credentials your automation uses most.
Checking your tables
relforcerowsecurity is the flag FORCE sets. False means owners bypass policies on that table:
Read the two flags together in the output: rls_enabled false means the table is exposed outright, while rls_forced false on an enabled table means the owner path remains open. The pair turns a vague posture question into a concrete list you can hand to whoever owns the migration templates.
SELECT c.relname AS table_name,
c.relrowsecurity AS rls_enabled,
c.relforcerowsecurity AS rls_forced
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'
ORDER BY c.relname;The fix and its trade-offs
One statement per table. After it, the owner becomes subject to policy evaluation too — so ensure an administrative role outside RLS exists for genuine maintenance, or your own repairs will start returning empty sets:
Apply FORCE during a quiet window and watch error rates for a cycle afterwards: the most common surprise is an internal job connecting as the owner and receiving empty results, which surfaces immediately and points you at the credential to move.
ALTER TABLE public.billing_accounts FORCE ROW LEVEL SECURITY;
Making it stick
Add the FORCE line to your table-template snippet so every future table inherits the habit, and consider a periodic catalog assertion in CI comparing relforcerowsecurity against your expected list. RowShield's monitoring does not evaluate this flag today; the nine shipped rules are listed plainly on the rules index, and this page will gain automation if the rule graduates from roadmap.
Independence note: RowShield is an independent product, unaffiliated with Supabase; FORCE ROW LEVEL SECURITY is standard PostgreSQL behaviour described here as it applies to Supabase projects.
Common false leads
Two neighbours get blamed in place of the owner exemption. The service_role key comes first: its reach comes from BYPASSRLS, a different mechanism with a different remedy, and FORCE changes nothing for it. Sessions as postgres come second: superusers bypass regardless of both flags. If a leak traces to either of those, this page's fix is not the lever — look instead at which credentials reached the query.
The reverse mislead also occurs: seeing rows through the owner account during debugging and concluding RLS is broken everywhere. Owner visibility is documented behaviour, not corruption. Reproduce with the application roles before rewriting policies that were never the problem.
Verifying the posture from outside
After forcing a table, connect as the owning role and confirm the change bit: a plain select now returns only what policies allow, where previously it returned the world. Keep one maintenance role outside the forced set and verify it the same way, so genuine repairs retain a working path. The relforcerowsecurity flag is the durable record of intent; the behavioural check proves the deployment applied it.
Frequently asked
- Does FORCE affect the service_role key?
- No. BYPASSRLS is a role attribute that outranks policy evaluation either way, and service_role carries it by design. FORCE changes how the table treats its owner specifically; the superuser and BYPASSRLS exemptions stand untouched.
- Will FORCE break my admin scripts?
- Scripts connecting as the owner will suddenly respect policies, which is the point and the hazard. Run genuine maintenance from a role that keeps BYPASSRLS or ownership outside the forced set, and smoke-test those scripts in staging before the change reaches production.
- Why include FORCE in generated remediation SQL?
- Because the version without it protects less than it appears to: the moment anyone connects as the owning role, every policy evaporates for that session. Including FORCE makes the generated remediation match the posture the finding describes rather than a weaker cousin of it.
Check your project in about ten seconds
Paste a URL. No signup, no writes, nothing stored.
Run the free audit