VibeRaven
Free Supabase RLS checker
Paste your Supabase migration SQL, or drop the .sql files from supabase/migrations, and this checker lists public tables without row level security, using (true) conditions that let every caller through, and security definer functions the anon key can call, each with the file and line, what it means and the fix. It runs the same SQL engine as the VibeRaven CLI. It is advice, not a gate: it reads the SQL you give it, not a live database. Runs in your browser. Nothing you paste is sent anywhere.
The checker
The checker on this page needs JavaScript. It applies the files in name order, the order the Supabase CLI applies timestamped migrations in, treats pasted SQL as a file named pasted.sql, and skips files that look like SQL tests. Nothing you paste or drop leaves the page: there is no upload, and the checker is kept out of the page analytics.
Check the whole repo
For AI-built Vercel + Supabase apps, npx -y viberaven@1.6.3 check runs these SQL rules over every migration in the repo, and also flags a Supabase service role key in client code, env vars the code reads that .env.example does not list (it cannot see Vercel settings), and Stripe webhook handlers with no signature check.
It is advice, not a gate, and a repository check, not a live database test. It exits with code 1 when it finds a blocker and writes a .viberaven folder that it does not add to .gitignore.
For pull requests and pushes to main, the GitHub Action ohad6k/viberaven-action@v1 runs viberaven check in the repository's own runner and comments the result. It is advice by default.
What the checker looks for
Six rules, the same ones viberaven check applies to SQL. Statements run in order, so RLS enabled in a later file counts and a later disable row level security undoes it. Only the public schema is tracked, the one the Supabase Data API exposes by default, and SQL inside a function body, a DO block, a comment or a string never counts.
A public table without row level security
Rule rls_disabled, critical. Supabase warns that a table in an exposed schema without RLS is readable and writable by any role with a grant on it. Tables created in the dashboard get RLS by default; tables created in SQL, including migrations, need it enabled explicitly, and a table created without a schema name lands in public.
Enabling RLS with no policy locks the table to the service role, so add a policy for each operation the app needs. The manual version of this check is in How do I find Supabase tables missing RLS in my migrations?.
-- flagged: rls_disabled (critical)
create table public.invoices (id bigint primary key, user_id uuid);
-- fixed
alter table public.invoices enable row level security;A policy that lets anyone write every row
Rule rls_policy_allows_all_write, critical. An insert, update, delete or all policy whose condition is a bare true, for anon, authenticated or no role at all. A policy with no to clause applies to every role. An update or all policy with no with check uses its using expression for new rows too, so using (true) lets anyone write any values.
An update policy with a real using clause and with check (true) is reported as high: callers touch only the rows using matches, but can set any values on them, including an owner column such as user_id.
-- flagged: rls_policy_allows_all_write (critical)
create policy "Users can update their own profile"
on public.profiles for update using (true);
-- fixed
drop policy "Users can update their own profile" on public.profiles;
create policy "Users can update their own profile"
on public.profiles for update to authenticated
using ((select auth.uid()) = id)
with check ((select auth.uid()) = id);A policy that lets anyone read every row
Rule rls_policy_allows_all_read, high. A select policy with using (true): fine for content meant to be public, wrong for anything that belongs to one user. The checker cannot know which you meant, so the call is yours.
-- flagged: rls_policy_allows_all_read (high)
create policy "Anyone can read orders"
on public.orders for select using (true);
-- fixed: each user reads their own orders
create policy "Owners can read their orders"
on public.orders for select to authenticated
using ((select auth.uid()) = user_id);A security definer function the anon key can call
Rule security_definer_function_executable, warning. Postgres gives EXECUTE on every new function to PUBLIC, and Supabase also grants it to anon and authenticated on functions in public, so anyone holding the anon key can call it at /rest/v1/rpc/<name>. A security definer function runs with its owner's rights, so RLS does not limit what it touches. Revoke EXECUTE from public, anon and authenticated in one statement; leave one out and it stays callable.
Trigger functions are skipped, because nothing can call them over the API. A helper your policies call, such as has_role, needs the opposite fix: keep EXECUTE for the roles whose policies call it, and move it to a schema the API does not expose.
Each case is worked through in Can anyone call my Supabase RPC functions with the anon key?.
-- flagged: security_definer_function_executable (warning)
create function public.make_admin(target uuid) returns void
language sql security definer as $$ ... $$;
-- fixed: only the server calls it
revoke execute on function public.make_admin(uuid)
from public, anon, authenticated;A table granted to the Data API with RLS off
Rule data_api_grant_without_rls, critical. A grant to anon or authenticated on a table with no row level security. The grant lets the role reach the table and nothing filters the rows, so the Data API serves every row to that role. Enable RLS and add policies before granting.
-- flagged: data_api_grant_without_rls (critical)
grant select, insert on public.notes to anon, authenticated;
-- fixed
alter table public.notes enable row level security;
-- then a policy for each operation, or revoke the grantA table with RLS on and no policies
Rule rls_no_policies, info. 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, so it is listed as info.
-- info: rls_no_policies
alter table public.audit_log enable row level security;
-- no create policy: only the service role can reach it
How to get your migrations out of Lovable, Bolt or Supabase
The checker reads .sql files. Where they are depends on how the app was built, and each source shows something different.
From the repo: supabase/migrations
Projects that use the Supabase CLI keep one SQL file per change in supabase/migrations, named with a timestamp so they apply in order. Drop every file in that folder together; the checker sorts them by name. Add seed.sql too: it runs after the migrations, as with supabase db reset.
From Lovable or Bolt
Lovable can sync a project to GitHub. Clone the repo and look for a supabase/migrations folder; the rest of the launch list is in My Lovable + Supabase app launches Friday. What should I check first?. For a Bolt project, export the code and look in the same place (see the Bolt Production Readiness Checklist).
If the folder is missing or short, the schema was probably made in the dashboard or the SQL editor, and only the live database has it, so use the next option.
From the live project: supabase db dump
Link the project with supabase link, then run supabase db dump. It runs pg_dump in a container, so Docker has to be running. It dumps the schema of the linked remote database, without data and without Supabase managed schemas such as auth and storage. The dump comes from the live database, so it includes tables and policies made in the dashboard. Drop the file it writes on the checker.
supabase db pull, which needs Docker too, reads the remote schema and writes it as a new migration file under supabase/migrations.
supabase link
supabase db dump -f supabase/schema.sqlFrom the SQL editor
Without the CLI, paste the SQL you ran in the dashboard SQL editor, which only shows what you pasted. To list the policies the live project has, run this there and read qual and with_check, where true means every row:
select tablename, policyname, cmd, roles, qual, with_check
from pg_policies
where schemaname = 'public';
Why policy names lie
Coding agents often write a policy to make a failing query work, and using (true) is the shortest policy that makes every query succeed. The name describes the intent while the clause grants everything, so the migration looks right when you skim it.
Postgres enforces the using and with check clauses, not the name. When an open policy's name promises a restriction, with words such as own, owner, their, admin or member, the checker adds a note that quotes the name and says nothing in the policy checks who the caller is.
A policy named for the service role gets its own note. Supabase says the service role "has the bypassrls attribute", so it never needs a policy, and a service role policy without a to clause only opens the table to everyone else.
-- flagged: the name says service role, the clause says everyone
create policy "Service role can manage all sessions"
on public.sessions for all using (true);
-- fixed: drop it, the service role does not need it
drop policy "Service role can manage all sessions" on public.sessions;
More on this pattern, with a query that finds every open policy on a live project: What does using (true) mean in a Supabase RLS policy, and when is it wrong?.
Grants and policies are separate
Postgres runs two checks before a client touches a table: grants decide whether a role can run an operation at all, and policies decide which rows it applies to. Postgres rejects a query with no grant before any policy runs, and a grant on a table without RLS opens every row.
On existing projects Supabase grants new public tables to anon and authenticated by default, so a table without RLS is reachable as soon as it exists. Supabase has announced that new tables will need an explicit grant while existing tables keep theirs; what is confirmed is in Will the Supabase October 30 Data API change break my AI-built app?.
A revoke closes a table or a function to a role. Use it for anything the browser should never reach, and use RLS with policies for tables it reads row by row:
revoke select, insert, update, delete on public.invoices from anon;
revoke execute on function public.make_admin(uuid) from public, anon, authenticated;
Which key maps to which role (anon, authenticated, service_role) is explained in Is my Supabase anon key safe in the frontend, and what replaces it?.
Read the grants on a live table
The checker reads grant, revoke and alter default privileges statements in your SQL, not grants made in the dashboard or given by default on the live project. To see what a live table is granted, run this query from Supabase's 42501 troubleshooting page with your table name:
select grantee, privilege_type
from information_schema.role_table_grants
where table_name = 'your_table';
What it cannot see
The checker reads SQL text. A clean result means the files you gave it have none of the patterns above. It does not prove the database is safe.
- Changes made in the dashboard SQL editor or Table Editor. They never reach your migration files; a dump from the live project does include them.
- Which migrations production has applied. supabase migration list shows the local and the remote history.
- The grants the live roles hold.
- Whether a policy lets the right user in and keeps the wrong one out. Supabase's RLS guide tests that with pgTAP and supabase test db.
- Views. Views bypass RLS by default; on Postgres 15 and above, create them with security_invoker = true. The checker does not read views.
- Schemas other than public. Tables created there are listed but not assessed.
- Storage policies and Auth settings, such as redirect URLs and email confirmation.
- Your app code: which tables the client queries, and whether the service role key reaches the browser. For those, run the repo check on the whole repo.
The split between what files can show and what needs the live database is laid out in What can a repo scan prove about Supabase security, and what needs a live test?.
How it compares to the Supabase Security Advisor
The Security Advisor is Supabase's own set of checks on the live schema, in the dashboard, with supabase db advisors, through the MCP get_advisors tool and the Management API. It sees dashboard changes, and it cannot see a migration you have not applied yet. This checker is the other way round: it reads SQL before it is applied, and never sees the deployed database.
They look for overlapping problems:
- A public table without RLS: the Advisor's rls_disabled_in_public, and rls_disabled here.
- RLS on and no policies: the Advisor's rls_enabled_no_policy, and rls_no_policies here.
- A policy that allows everyone: lint 0024 in the Advisor, and rls_policy_allows_all_read or rls_policy_allows_all_write here.
- A security definer function the API roles can call: lints 0028 and 0029 in the Advisor, and security_definer_function_executable here.
Use both: this checker or the repo check on a migration before you push it, and the Advisor on the live project after.
The two are set side by side in VibeRaven vs Supabase Advisor, and other file and live checkers are described in Which tools check Supabase migrations or projects for tables missing RLS?.
Related guides
- How do I find Supabase tables missing RLS in my migrations?
- What Is RLS on Supabase? (AI-Built Apps)
- Supabase Auth and RLS Checklist for AI-Built Apps
- Supabase RLS Production Checklist for AI-Built Apps
- Can anyone call my Supabase RPC functions with the anon key?
- How do I check the Stripe webhooks, env vars and Supabase policies in an app Claude Code wrote?
- Guides for shipping AI-built apps
Sources
- Supabase, Row Level Security
- Supabase, Securing your API
- Supabase, Advisors
- Supabase, Understanding API keys
- Supabase, Tables and data
- Supabase, Database API 42501 errors
- Supabase, Database migrations
- Supabase CLI, db dump
- Supabase CLI, db pull
- Supabase CLI, migration list
- Supabase CLI, test db
- PostgreSQL, CREATE POLICY
- Lovable, Sync your project with GitHub
Is this Supabase RLS checker free?
Yes. It runs in your browser, with no account and no limit, and nothing you paste is sent anywhere.
Does the checker connect to my Supabase project?
No. It never asks for a project URL or key. It reads only the SQL you paste or drop, so it cannot see the live database, changes made in the dashboard or the grants your roles hold.
Which files should I drop in?
Every .sql file in supabase/migrations, plus seed.sql, or the file supabase db dump writes. They run in name order, as Supabase applies migrations by timestamp, with seed files last. Files that look like SQL tests, such as a .test.sql file, are skipped.
The checker found nothing. Is my database safe?
Not necessarily. A clean result means the SQL you gave it has none of the patterns it looks for. The live database can differ: a migration that was never applied, a table or policy changed in the dashboard, or grants you did not expect. Run the Security Advisor on the live project, and test your policies with pgTAP and supabase test db.
Why is a policy called "Users can update their own profile" flagged?
Because its clause is using (true). Postgres enforces the clause, not the name, so the policy lets every role it applies to update every row. A policy that matches its name checks the caller, for example using ((select auth.uid()) = id), with the same condition in with check.
Is a using (true) select policy always wrong?
No. It is fine for data that is meant to be public, such as published posts or a product catalog. It is wrong for anything that belongs to one user. The checker reports it as high so that you make the call.
Why is a security definer function flagged when my app never calls it?
Supabase's Advisors docs say /rest/v1/rpc accepts any function name the caller has EXECUTE on, whether or not your app ever calls it, and a security definer function runs with its owner's rights, past RLS. If only your server calls it, revoke EXECUTE from public, anon and authenticated.
How is this different from viberaven check?
It is the same SQL engine. For AI-built Vercel + Supabase apps, npx -y viberaven@1.6.3 check runs it on every migration in the repo and also looks at app code, such as a service role key in client code and Stripe webhook handlers. It is advice, not a gate, and a repository check, not a live database test.