Fix guide · tested 18 October 2026
How to fix "RLS disabled in public" in Supabase
Supabase's Security Advisor reports "RLS disabled in public" (lint 0013, level ERROR) when a table in the public schema has Row Level Security turned off.
Supabase flags "RLS disabled in public" when a table in the public schema has Row Level Security off. Because public tables are reachable through Supabase's APIs with the anon key that ships in your front end, anyone can then read, insert, update and delete every row. Fix it in three steps: run alter table public.your_table enable row level security;, add a policy for each operation your app needs (for example, users can only select their own rows), and revoke grants the anon role shouldn't have. Then test signed out and as a second user.
How we tested this: We ran every SQL statement on this page on PostgreSQL 17.11 with Supabase-style anon and authenticated roles and a stand-in for auth.uid(), not on a live Supabase project, on 18 October 2026. Before the fix, the anon role could count all rows; after it, anon got "permission denied", one signed-in user saw only their own row, and an update to another user's row changed 0 rows.
Why it matters
Supabase's own lint documentation puts it bluntly: if RLS isn't enabled on a public table, anyone with your project URL can create, read, update and delete rows in it. Your project URL and anon key aren't secrets. They're in the JavaScript every visitor downloads, so this isn't a theoretical risk. It's the mechanism behind CVE-2025-48757, which affected apps generated by Lovable (see is Lovable secure?).
Enabling RLS on its own makes the table deny everything to the anon and authenticated roles until you add policies. That's safe, but it can break your app, so add the policies your app needs in the same change.
Check it first (read-only)
Run this in the Supabase SQL editor. It only reads the system catalog and changes nothing. Every row it returns is a table in public with RLS off.
select n.nspname as schema, c.relname as table_name
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where c.relkind in ('r', 'p')
and n.nspname = 'public'
and not c.relrowsecurity
order by 2;You can also open Advisors → Security Advisor in the Supabase dashboard, which runs the same lint. To see it from outside, our free Supabase exposure checker shows which tables return rows to a signed-out visitor.
The fix, step by step
- Decide who should see each table. For each table the check returned, write down who should read and write it: nobody signed out, only the row's owner, or everyone (for example a public product list). That decides the policies.
- Enable RLS. Run
alter table public.profiles enable row level security;for each table, replacingprofileswith the table name. From this point the anon and authenticated roles can't read or write it until a policy allows it.alter table public.profiles enable row level security; - Add a policy per operation. Add only the policies your app needs. This pair lets signed-in users read and update only their own profile row. Supabase recommends wrapping
auth.uid()in a select and naming the role withto authenticated, so the policy doesn't run for signed-out requests.create policy "Users can read own profile" on public.profiles for select to authenticated using ((select auth.uid()) = id); create policy "Users can update own profile" on public.profiles for update to authenticated using ((select auth.uid()) = id) with check ((select auth.uid()) = id); - Revoke grants anon shouldn't have. Supabase's RLS guide notes that on many projects a new public table is granted select, insert, update and delete for anon and authenticated, and adding policies doesn't take those grants back. Revoke what signed-out visitors shouldn't do. In our test, a table with RLS on but no policy for insert still returned "new row violates row-level security policy" to anon, which is safe, but revoking the grant makes the intent explicit and stops a future permissive policy from opening it.
revoke insert, update, delete on public.profiles from anon; -- if signed-out visitors should not read it at all: revoke select on public.profiles from anon; - Test signed out and as a second user. Re-run the read-only check (it should return no rows), then use your app in a private window and with two test accounts. Signed out, private tables should return nothing or an error. Signed in as user A, you should never see user B's rows. Supabase's guide recommends pgTAP tests (
supabase test db) that assert allow and deny for each operation and role.
If a table should be public
Some tables are meant to be readable by everyone, such as a list of products or published blog posts. Keep RLS on anyway and add an explicit read policy, for example create policy "Anyone can read published posts" on public.posts for select to anon, authenticated using (published = true);. That keeps writes closed and documents the decision, and Supabase's advisor stops flagging the table.
If you don't use the Data API at all
Supabase's lint documentation gives one more option: if you never query these tables from the browser, remove the public schema from the exposed schemas in your project's API settings. That makes everything in it unreachable over the API. Most apps built with Lovable, Bolt or v0 do query from the browser, so for them RLS with policies is the fix.
Common mistakes
using (true)on private data. A policy whose condition is justtruelets every role it names read every row. It silences the advisor without fixing anything.- Checking
user_metadatain policies. Signed-in users can edit their ownuser_metadata, so don't base access decisions on it. - Fixing it with the service_role key. Moving queries to the service_role key in the browser bypasses RLS completely and makes things worse. That key must stay on a server.
- Forgetting views. A view over a protected table can still bypass RLS. See fix a security definer view.
FAQ
What does "RLS disabled in public" mean in Supabase?
A table in the public schema has Row Level Security turned off. Because public tables are reachable through Supabase's APIs with your anon key, anyone can read and change every row until you enable RLS and add policies.
Will enabling RLS break my app?
It can. With RLS on and no policies, the anon and authenticated roles get no rows back. Add the policies your app needs in the same change, then test signed out and as two users.
Is the Supabase anon key a secret?
No. It's designed to be public and ships in your front-end code. That's why RLS policies, not the key, are what protect your data.
Do I still need to revoke grants after adding policies?
Supabase's RLS guide says adding policies doesn't remove existing grants. With RLS on, anything no policy allows is still denied, but revoking grants anon doesn't need makes the intent explicit and safer against a future permissive policy.
How do I check RLS without changing anything?
Run the read-only catalog query on this page in the SQL editor, or open the Security Advisor in the dashboard. Both list public tables with RLS off and change nothing.
Want someone to check it for you?
We check this as part of the $49 AI Site Audit. See how we test and what we don't check.
How this guide was made: researched and drafted with AI assistance, then every step was run and checked as described above on 18 October 2026. Editorial responsibility: ChikuOS. Found a problem? Email hello@chikuos.com.