Matt Turley, Continuum. Updated November 5, 2026.
For founders with a Supabase backend, usually generated by an AI builder, who want to know whether strangers can read or change their users' data.
Query pg_tables for rows where rowsecurity is false, or open the Security Advisor in the Supabase dashboard. A table in the public schema with RLS off can be read and written by anyone who holds your public anon key, if the role has been granted access. Turn RLS on and write the policies in the same migration.
Supabase exposes your Postgres tables through an API. The key your frontend uses (the anon key) is public by design: it is in your JavaScript bundle and anyone can copy it. What stops a stranger from using it to read the whole profiles table is not the key, it is row level security, the policies that decide which rows each caller may touch.
When RLS is off for a table that the API roles can reach, there is no per-row check at all. When it is on with no policies, nothing is readable, which is safe but breaks the app, and that is exactly why AI builders and tired founders switch it off or add a policy that allows everything.
Two things are easy to confuse. A missing GRANT returns a permission denied error (code 42501). A missing or permissive policy returns data you did not mean to return. This page is about the second. Our page on the Data API grant change covers the first.
How to tell The query on pg_tables shows rowsecurity = false for a table in public, or the Table Editor shows the RLS disabled warning. Tables created by SQL migrations or by an AI builder's schema step are the usual ones, because enabling RLS is a separate statement.
The fix Run alter table public.your_table enable row level security; and write the policies in the same migration so the app keeps working.
How to tell A policy with using (true) or with check (true) for the anon or authenticated role. The padlock looks closed and the table is wide open. A policy named something like "Enable read access for all users" is the classic.
The fix Replace it with a condition tied to the caller, for example user_id = (select auth.uid()), and decide deliberately which tables, if any, should be public.
How to tell Policies that read role or tenant information from user_metadata in the JWT. Users can change their own user_metadata, so a policy built on it can be satisfied by the attacker.
The fix Base authorization on app_metadata (which users cannot edit) or on a membership table the user cannot write to.
How to tell Views run with their owner's rights by default, so a view over a protected table can return everything. A security definer function exposed through the API runs with its creator's rights.
The fix Create views with security_invoker = true, keep security definer functions out of exposed schemas, and give each one an explicit permission check inside.
How to tell Uploaded files (avatars, invoices, documents) open without being signed in, or the storage.objects table has permissive policies. RLS on your tables does nothing for files.
The fix Make private buckets private, serve files through short-lived signed URLs, and scope the storage policies to the owner's folder.
How to tell A search of your frontend bundle or environment shows SUPABASE_SERVICE_ROLE_KEY, or a variable with that key behind a NEXT_PUBLIC_ prefix. The service role bypasses RLS entirely.
The fix Remove it from the client immediately, rotate it in the dashboard because it should be treated as exposed, and use it only in server code.
pg_tables for the public schema and read rowsecurity. Then list the policies from pg_policies. A table with RLS on and zero policies, and one with RLS off, are both findings.using and with check expressions for the roles anon and authenticated and flag any that are always true, ignore the caller or depend on a user-editable claim.security_invoker, exposed security definer functions, public buckets and the service role key anywhere in client code or build output.If the advisor flags a few tables and your policies are simple (a user owns their own rows), the fix is a handful of SQL statements you can write with an AI assistant and verify with the stranger test above. Pay for a review when the data is sensitive, when policies depend on teams or roles, or when you cannot say with confidence what the anon key can reach today.
The RLS checker is free and runs in your browser: paste your schema and it flags tables with RLS off and permissive policies. If it finds something on data that matters, the Supabase security review gives ranked findings with corrected SQL and a proof for each.
your schema into the free RLS checker: Paste your Supabase schema or policies and it flags tables with row level security off, permissive policies and risky grants. It runs in your browser, so the SQL is not sent anywhere. Or: Get the Supabase security review.