Guides / RLS Disabled in Public
Supabase "RLS Disabled in Public": what it means and how to fix it
Security Advisor shows RLS Disabled in Public (lint 0013_rls_disabled_in_public). Supabase's Advisors docs summarize the same finding as Table publicly accessible. A table in an exposed schema such as public has Row Level Security turned off, so anyone with your project URL can create, read, update, and delete every row in that table. Enable RLS, then add policies before your app can use the table again through the API.
The short answer
Supabase's Security Advisor flags this as an ERROR. The official summary is "Table publicly accessible." The ramification, in Supabase's words: anyone with your project URL can read, edit, and delete all data in this table because Row-Level Security is not enabled. Tables in the public schema are reachable over the Data API, so leaving RLS off is a critically unsafe configuration.
This is not the same as a using (true) policy. That case is lint 0024 (RLS is on, but a policy lets every row through for the roles it names). Here RLS is absent entirely. Related Advisor findings you might see next to it:
- 0007
policy_exists_rls_disabled(INFO): policies exist, but RLS is still off, so they do nothing until you enable it. - 0008
rls_enabled_no_policy(INFO): RLS is on and there are no policies, so the API can read or write nothing until you add policies (or an intentional deny-all policy). - 0024
permissive_rls_policy: see RLS policies that are always true. - 0023
sensitive_columns_exposed: sensitive-looking columns plus RLS off on an API-exposed table.
Your publishable (or legacy anon) key is public by design. It is only safe when every exposed table has RLS and policies that match who should see which rows.
How to check
1. Security Advisor (dashboard or CLI)
In the Supabase dashboard open Advisors → Security Advisor and look for lint 0013_rls_disabled_in_public, titled "RLS Disabled in Public" or summarized as "Table publicly accessible." From the CLI:
supabase db advisors
You can also pull advisors through the Management API or MCP get_advisors with type security. Treat every ERROR finding against your intended access model before you dismiss it.
2. List public tables with RLS off
Run this in the SQL Editor. It prints table names and whether RLS is enabled, never keys or row data:
select tablename, rowsecurity from pg_tables where schemaname = 'public' order by tablename;
rowsecurity = false is the condition Advisor is warning about. Optionally confirm what the anon role can still do at the grant layer (grants are separate from policies):
select tablename,
has_table_privilege('anon', format('public.%I', tablename), 'select') as anon_select,
has_table_privilege('anon', format('public.%I', tablename), 'insert') as anon_insert,
has_table_privilege('anon', format('public.%I', tablename), 'update') as anon_update,
has_table_privilege('anon', format('public.%I', tablename), 'delete') as anon_delete
from pg_tables
where schemaname = 'public'
and rowsecurity = false
order by tablename;
How to fix it
1. Enable RLS on every flagged table
alter table public.your_table enable row level security;
Replace your_table with each table Advisor listed. After this, the anon role cannot read or write that table through Supabase APIs until you create row level security policies. If you enable RLS and stop there, your app will look empty or fail writes. That is expected, not a reason to turn RLS back off.
2. Set grants, then add owner policies
Postgres checks grants before policies. On many projects a new public table still has broad grants to anon and authenticated. Supabase's RLS guide recommends enabling RLS, revoking, then granting only what each role needs, in the same migration:
alter table public.your_table enable row level security; revoke all on table public.your_table from anon, authenticated; -- Example: signed-in users manage their own rows; signed-out visitors get nothing. grant select, insert, update, delete on table public.your_table to authenticated;
Then add a policy per operation. Prefer naming the role with to, and wrap auth.uid() in (select …) so it runs once per statement:
create policy "Users can view their own rows" on public.your_table for select to authenticated using ( (select auth.uid()) = user_id ); create policy "Users can create their own rows" on public.your_table for insert to authenticated with check ( (select auth.uid()) = user_id ); create policy "Users can update their own rows" on public.your_table for update to authenticated using ( (select auth.uid()) = user_id ) with check ( (select auth.uid()) = user_id ); create policy "Users can delete their own rows" on public.your_table for delete to authenticated using ( (select auth.uid()) = user_id );
Adapt the column name if your owner column is not user_id. For a longer checklist covering grants, the four-policy pattern, and supabase test db, see Supabase Row Level Security for vibe-coded apps.
3. Optional: keep the table off the Data API entirely
If the table should never be reached through the client APIs, Supabase also allows removing its schema (for example public) from Exposed schemas in API settings. That makes every entity in the removed schema inaccessible over APIs. Most apps still need public exposed, so prefer RLS plus policies for tables the app uses.
Prove it / common mistakes
- Empty app after enable. Until policies exist, API access is denied. Add the policies (and the grants you intend) in the same change as
enable row level security. - Don't "fix" it with
using (true). That clears the "no RLS" ERROR by turning RLS on, then creates lint0024: unrestricted access for the roles the policy names. See RLS policies that are always true. - Don't leave grants wide open. Policies do not revoke grants. If
anonstill has insert/update/delete, a later permissive policy or mistake reopens writes. Revoke, then grant only what you need. - Policies exist but RLS is off (0007). Enabling RLS is still required; existing policies stay inert until then.
- Re-run Advisor after the migration, and test as
anonand as a second signed-in user so you only see the rows you expect.
Turning protection off is not a fix for other errors either. Disabling RLS to clear an insert or recursion failure can make the table readable and writable by any role that still holds a grant, which on many projects includes anon. Keep RLS on and fix the policy.
next step
Want a second pair of eyes?
Grab the free Lockdown Pack (security rules for Cursor and Claude Code plus a pre-launch checklist), or have us check your repo with a $99 Scan + Report.
Sources
Every Postgres and Supabase detail above was checked against these official pages. Last checked Nov 2026. If behavior changed, the docs below are the source of truth.
- Supabase: Advisors (0013_rls_disabled_in_public ERROR "Table publicly accessible"; related 0007, 0008, 0023, 0024; Studio / CLI / MCP)
- Splinter: RLS Disabled in Public (0013 title, rationale, enable RLS, anon blocked until policies)
- Supabase: Row Level Security (grants vs policies, revoke/grant pattern, owner policies with (select auth.uid()), API denied until policies)
- Supabase: API keys (publishable key is public by design; only safe with RLS)
- PostgreSQL: pg_tables (rowsecurity column)
- PostgreSQL: has_table_privilege