Vigia › Help › Supabase RLS disabled
Supabase RLS disabled? How to check and fix it
Short answer: Your Supabase publishable (or anon) key is meant to be public, so it is not what protects your data. Row Level Security is. If RLS is off on a table in an exposed schema, Supabase's own Security Advisor warns: "Anyone with your project URL can read, edit, and delete all data in this table because Row-Level Security is not enabled." Turn RLS on for every such table, add one policy per operation, and test it.
On this page: Why it matters · How it happens · How to check yourself · How to fix it · Prompts for Lovable, Bolt and Cursor · FAQ · How Vigia checks this · Sources
Why it matters
Supabase's docs: "A table in an exposed schema without RLS is readable and writable by any role with a grant on it. Enable RLS on every table in an exposed schema." Your publishable key is in your site's JavaScript by design, and Supabase lists it as "safe to expose online" because it "only reaches what Row Level Security allows". With RLS off, that limit is gone.
How it happens
- A table is created from SQL, a migration or an AI tool, and RLS is never switched on.
- A policy is written but RLS stays disabled. The Security Advisor has a separate warning for this (lint 0007, "policy exists rls disabled").
- RLS is on, but the roles still have grants they don't need. Supabase: "Adding policies doesn't take those grants back."
- A view reads the table without obeying its policies (see "How to fix it", step 4).
How to check yourself
- Run the Security Advisor. Supabase dashboard → Advisors → Security Advisor. Look for "RLS disabled in public" (lint 0013), "policy exists RLS disabled" (0007) and "security definer view" (0010).
- Check each table. In the Table Editor, every table in
publicshould show RLS as enabled. - Test as a stranger. Open your app in a private window, logged out, and check that private data never loads. For a proper test, follow step 5 below.
How to fix it
- Enable RLS on each table:
alter table public.<table> enable row level security;Once RLS is on, "no data is accessible through the API when using a publishable key, until you create policies". Expect parts of the app to stop working until step 3. - Set the grants. Grant
anonandauthenticatedonly the operations your app needs, and revoke the rest. A missing grant raises error 42501 before any policy runs. - Write one policy per operation (select, insert, update, delete). Logged out,
auth.uid()returns null, so writeauth.uid() IS NOT NULL AND auth.uid() = user_id. Don't base policies onuser_metadata: users can edit it. - Fix views. On Postgres 15+, recreate views with
security_invoker = trueso they obey the tables' policies. On older versions, revoke access fromanonandauthenticatedor move the view to an unexposed schema. - Test. Add a
.sqlfile undersupabase/tests/that asserts allow and deny for each operation asanonand asauthenticated, then runsupabase test db. In Supabase's words: "Until the suite passes, you don't know whether the policies do what you intended." - Run the Security Advisor again until it shows no RLS warnings.
Prompts for Lovable, Bolt and Cursor
Enable Row Level Security on every table in the public schema. For each table, revoke grants that anon and authenticated don't need, and write one policy per operation (select, insert, update, delete) so each user only reaches their own rows, with an explicit auth.uid() IS NOT NULL check. Recreate every view with (security_invoker = true). Show me the SQL before running it.
Add tests under supabase/tests/ that assert allow and deny for select, insert, update and delete, for anon and authenticated, on every public table. Run supabase test db and fix every policy that fails.
FAQ
Is it safe to have my Supabase anon or publishable key in the frontend?
Yes, that is what it's for. Supabase lists the publishable key as safe to expose. It is only safe if RLS is on, with correct policies, on every table in an exposed schema.
I enabled RLS and now my app shows no data. Did I break it?
No, that is expected. With RLS on and no policies, the publishable key can't read any rows. Add a policy for each operation your app needs.
I wrote policies. Is that enough?
Not on its own. Policies do nothing while RLS is disabled, and they don't remove grants. Check both, then test with supabase test db.
Do policies apply to the secret or service_role key?
No. Secret keys bypass RLS, so they must never be in the browser. See Supabase service_role key exposed?
What about views?
A view can bypass RLS. On Postgres 15+, create it with security_invoker = true.
How Vigia checks this
Vigia is a read-only check for apps you own. It downloads your home page and up to 6 same-origin JavaScript files linked from it (<script src> and module preloads; scripts from a CDN or subdomain aren't checked). It finds Supabase keys and decodes the legacy JWT ones (long eyJ… strings) to read their role. A publishable or anon key is reported as info, with a reminder that every table needs RLS. A secret or service_role key is reported as critical, with a fix prompt for Cursor, Lovable or Bolt. It asks you to prove you own the site (a token file or meta tag) before it runs the check. Only the browser tab that proved it can run the check, for 24 hours; after that, you prove it again with a new token. It masks every value it shows (first and last 4 characters at most), including the start of an exposed .git file.
Honest limits: Vigia does not test your RLS policies or read your database, so it cannot tell you whether a table is public. Use the Security Advisor and supabase test db for that. A clean report is not a security guarantee.
The free check is available now. Vigia Pro (€12/month or €120/year, VAT included) re-checks up to 3 apps every week and emails you what changed. Run the free check
Sources
- Supabase: Row Level Security
- Supabase: Database Advisors (lints 0007, 0010, 0013)
- Supabase: API keys
Supabase, Postgres, Lovable, Cursor and Bolt are trademarks of their respective owners. Vigia is not affiliated with or endorsed by them.