VibeRaven
How do I find Supabase tables missing RLS in my migrations?
List every table your migrations create in the public schema, then check that a statement enables row level security on it and that nothing after that disables it again. The reliable way is to apply the migrations to a local database with supabase db reset and query pg_tables for rows where rowsecurity is false. Migrations only describe what should exist, so finish with the Security Advisor on the live project, which reports public tables without RLS as an error.
Why this matters
A table is under row level security only when a statement turns it on. Supabase warns that a table in an exposed schema without RLS is readable and writable by any role with a grant on it, and on existing projects new public tables are granted to anon and authenticated by default. Tables created in the dashboard get RLS by default; tables created in SQL, including migrations, need it enabled explicitly. Reading files by eye misses the cases that matter: RLS enabled in a later file, disabled again after that, or a table created without a schema name, which Postgres puts in public by default.
Find them in this order
- List the create and RLS statements in the order Supabase applies them, which is timestamp order. Migration file names start with their timestamp, so sort the output by path:
rg -n -i --sort path "create table|row level security" supabase/migrations - For every table created in public, or with no schema name, find alter table ... enable row level security in the same or a later migration, and check that no later migration runs disable row level security on it.
- Do not count a create policy statement as protection. Postgres applies policies only when row level security is enabled on the table; the Security Advisor reports a policy on a table without RLS as "Security policy not enforced" (policy_exists_rls_disabled).
- Apply the migrations to a clean local database: run supabase start, then supabase db reset, which recreates the local database and applies every migration in supabase/migrations.
- Query the result. Each row is a public table without RLS:
select schemaname, tablename from pg_catalog.pg_tables where schemaname = 'public' and not rowsecurity; - Look at tables with RLS on and no policies. With no policy, Postgres denies every row, so the publishable key reads and writes nothing. That is right for server-only tables and a bug for tables the browser reads. The Advisor calls it "No access rules defined" (rls_enabled_no_policy).
- Check views as well. Views bypass RLS by default; on Postgres 15 and above, create them with security_invoker = true. The Advisor reports "View bypasses row-level security" (security_definer_view).
- Check the live project, not only the files. Run the Security Advisor in the dashboard or supabase db advisors; "Table publicly accessible" (rls_disabled_in_public) is an error-level finding. Changes made in the dashboard SQL editor or Table Editor never reach your migrations.
- Compare what production has applied with supabase migration list, which lists the migration history of the local and the remote database.
- For each table you fix, add a pgTAP test that asserts allow and deny for select, insert, update and delete as anon and authenticated, and run supabase test db.
Optional repo check
For AI-built Vercel + Supabase apps, `npx -y viberaven@1.5.3 check` does the file half of this list: it replays the SQL in your repo in migration order and lists public tables that end up without row level security, including a table a later migration turns RLS off for and a table created without a schema name, tables with RLS on and no policies, and policies whose condition is a bare `true` for anon or signed-in users. It tracks only the public schema and does not read views. It is advice, not a gate, and a repository check, not a live database test: it does not connect to your Supabase project, so dashboard-made tables and what production has applied stay with the Security Advisor and supabase migration list. It exits with code 1 when it finds a blocker and writes a `.viberaven` folder that it does not add to `.gitignore`.
Sources
Does a create policy statement turn RLS on?
No. Row level security is a table setting you turn on with alter table ... enable row level security. A policy on a table without RLS has no effect, which is why the Security Advisor reports that case on its own.
Why does my table return no rows after I enabled RLS?
With RLS on and no policy, Postgres denies every row. Add a policy for each operation the app needs, scoped to the signed-in user, for example using ((select auth.uid()) = user_id).
Do tables I created in the Supabase dashboard have RLS?
Supabase enables RLS by default on tables created through the dashboard. Tables created in the SQL editor, in migrations or with another tool need it enabled explicitly, and dashboard changes on the remote database do not appear in your migration files.