Supabase RLS
Supabase Row Level Security, done right
RLS is where your app's real authorization lives — written in SQL, at the row. This is the hub: the one idea that matters, the correct patterns, and free tools to write, verify, and prove your policies. Start with what you need.
npx @aegiskit/cli scanScan your whole repo locally — no install, contacts nothing.
9.2%of 1,000 public Supabase apps ship RLS that authenticates but doesn't authorize (a lower bound).
Start here
What do you want to do?
Write a correct policy
Pick an ownership model and get ready-to-paste SQL with the performance idiom and WITH CHECK.
Open the policy generatorCheck a policy you have
Paste a policy and see instantly whether it scopes rows to the caller — 100% in your browser.
Open the RLS checkerProve it for SOC 2 / ISO 27001
Map your findings to control evidence you can hand an auditor or a security questionnaire.
See the evidence mappingLearn the patterns
From your first policy to multi-tenant isolation, testing, and performance — the deep guides.
Browse the guidesHave it reviewed
Want the access-control design checked end to end? I take that on as a security audit.
Get an audit
The one idea
Authenticate ≠ authorize
RLS being enabled is not the same as RLS being correct. A policy that only proves a session exists (auth.role() = 'authenticated', auth.uid() is not null) lets every logged-in user read every row. Authorization binds the row to its owner.
create policy "notes_read" on public.notes
for select to authenticated
using (auth.role() = 'authenticated');create policy "notes_read" on public.notes
for select to authenticated
using ((select auth.uid()) = user_id);"Not owner-scoped" isn't automatically "vulnerable" — a shared reference table legitimately allows all authenticated reads. The point is to verify intent, not to cry wolf. No tool proves your authorization is correct; it can only point accurately.
The patterns
Four shapes cover almost everything
Most tables fit one of these ownership models. Generate the exact SQL for yours in seconds.
Personal
Each row belongs to one user; they read and write only their own.
Multi-tenant
Rows belong to a tenant; users access rows for tenants they're a member of.
Shared read + owner write
Any signed-in user reads; only the owner writes. For reference/shared data.
Locked
No client access at all — reads and writes go only through the server.
Go deeper
The Supabase RLS guides
Practical, source-backed deep dives — the spokes to this hub.
Getting started: enable RLS and write your first policy
The GRANT vs POLICY layers, USING / WITH CHECK, and the role model.
When RLS authenticates but doesn't authorize
The exact pattern the field study measured, and how to fix it per case.
Production multi-tenancy patterns
Membership tables, tenant isolation, and reusable helpers.
The write bypass from a missing WITH CHECK
Why USING isn't enough for writes, and how the hole opens.
Test your RLS with pgTAP
Prove allow and deny in CI so a policy can never silently regress.
Detecting RLS misconfigurations
The five dangerous patterns and how to catch them statically.
RLS performance optimization
The (select auth.uid()) wrapper, indexing, and EXPLAIN ANALYZE.
Field study: 1,000 public Supabase apps
The data behind 9.2% — method, false positives, and precision 1.0.
Get your Supabase RLS right
Generate a correct policy in seconds, or have the whole access-control design reviewed end to end.
FAQ
What is Row Level Security in Supabase?
RLS is PostgreSQL's per-row access control. In Supabase it's the real authorization boundary: because tables are exposed over an API, a policy on each table decides which rows a caller may read or write. Enable it, then add policies that bind rows to the caller.
Is enabling RLS enough to be secure?
No. Enabling RLS with no policy denies all access (safe), but a policy that only checks the caller is logged in (auth.role() = 'authenticated') lets every user read every row. Correct policies scope rows to their owner, e.g. using ((select auth.uid()) = user_id).
How do I write a correct Supabase RLS policy?
Bind each row to the caller: for reads use USING ((select auth.uid()) = user_id); for writes add a matching WITH CHECK. The free policy generator produces the exact SQL for personal, multi-tenant, shared-read, or locked tables.
How do I check if my RLS is correct?
Paste a policy into the free in-browser RLS checker to see whether it scopes rows, or run npx @aegiskit/cli scan over your whole repo. Neither sends your code anywhere. For a definitive answer on your data model, a human review or audit is the right tool.
Does RLS help with SOC 2 or ISO 27001?
Row-level access control is technical evidence for logical-access controls (SOC 2 CC6.1, ISO 27001 A.8.3). Aegis maps findings to those controls — a reference mapping for auditors, not a certification.