Supabase RLS audit: 9 policies to verify before launch
Row Level Security is your last line of defense. Here are the 9 RLS patterns that break silently and how to catch them before users do.
Row Level Security in Supabase is powerful but invisible when it fails. A missing policy does not throw an error — it returns an empty array. A permissive policy does not warn — it leaks every row. The only way to know your RLS is correct is to probe it before launch traffic arrives.
1. Default-deny on every table
If a table has no RLS policies and RLS is enabled, all queries return nothing. That is correct. The dangerous state is RLS disabled entirely — which means the anon key can read everything. Before launch, confirm every user-facing table has RLS enabled and at least one SELECT policy scoped to auth.uid().
2. INSERT policies that validate ownership
A common mistake is allowing any authenticated user to insert into any row. The policy should enforce that the user_id column matches auth.uid() on insert, not just on select. Without this, one user can write data attributed to another.
3. Service-role key never in the client
The service role bypasses all RLS. If it appears in a client bundle, source map, or environment variable exposed to the browser, your entire database is public. PreFlight scans your origin for leaked keys and flags this before launch.
4. UPDATE policies scoped to owned rows
Allow users to update only their own records. A policy like "using (auth.uid() = user_id)" on UPDATE prevents horizontal privilege escalation where one customer can modify another customer's data.
5. DELETE restricted or disabled
Most SaaS products should soft-delete. If you allow hard DELETE, scope it tightly. An open DELETE policy means any authenticated user can wipe rows they should not touch. Consider removing DELETE policies entirely and handling deletion through server-side functions.
6. JOIN-based data leaks
RLS applies per-table. If table A is locked down but table B references table A via a foreign key and table B has weaker policies, data can leak through the join. Audit every table in the relationship chain, not just the primary one.
7. Storage bucket policies
Supabase Storage has its own RLS layer. A public bucket means anyone with the URL can download. Private buckets need explicit policies for upload, download, and deletion. Check that user uploads are scoped to their own folder path.
8. Realtime subscriptions respect RLS
Supabase Realtime uses RLS to filter which rows are broadcast to which client. If your RLS is too permissive, users can subscribe to changes on rows they should not see. This is harder to catch manually — automated probing is the reliable path.
9. Edge Functions with service role
Edge Functions that use the service role client bypass RLS entirely. This is intentional for admin operations, but every function endpoint should validate the caller's identity independently. Do not rely on RLS when the function itself has god-mode access.
Automate the audit
PreFlight connects to your Supabase project and probes RLS behavior on every check run. It catches permissive policies, missing restrictions, exposed keys, and storage misconfigurations without you manually writing test queries. Run it before every launch and after every migration.
