Skip to content

Supabase "RLS disabled in public": what it means

Supabase

View as MarkdownOpen in ClaudeOpen in ChatGPTOpen in Perplexity

The question is copied too. If the chat opens empty, paste it.

TL;DR

“RLS Disabled in Public” (lint 0013_rls_disabled_in_public, level ERROR) means a table in an exposed schema has row level security off while anon or authenticated can select from it. In my local test, the public key read all 3 of 3 rows from such a table and inserted a new one with 201 Created. Enable RLS, add a policy per operation, and check the grants, in one migration.

I built a free Supabase security checker, and its first rule, “RLS disabled on exposed tables”, is the same finding Supabase’s Security Advisor reports as an error. It is the most common hole I find in apps built with AI tools, so here is what it means and what it allowed when I reproduced it.

What does “RLS disabled in public” mean?

The Security Advisor runs a lint called rls_disabled_in_public. Its SQL flags a table when three things are true at once:

  1. It is an ordinary table with row level security off.
  2. anon or authenticated has SELECT on it.
  3. Its schema is in the Data API’s Exposed schemas list, which is public by default.

The lint page rates it ERROR and calls full create, read, update and delete over the internet “a critically unsafe configuration”. The “public” in the name is the schema, but the lint covers every exposed schema, which is why an extra schema on that list deserves a look too; I wrote about that in Supabase exposed schemas.

What can the public key do to such a table?

I created a table orders with three fake customers on a local Supabase stack (CLI 2.109.1, Postgres 17.6, PostgREST 14.14), with the grants existing projects give new tables, and RLS off. Then I called the Data API with the publishable key, as a browser does:

One table, the public key, RLS off and on. Local Supabase, 3 rows of fake orders. RLS off, read: Publishable key → orders, RLS off → 3 of 3 rows; RLS off, insert: Publishable key → orders, RLS off → 201 Created; RLS on, no policy yet: Publishable key → orders, RLS on → 0 rows.
My reproduction on a local Supabase stack, 23 October 2026.
Request with the publishable key RLS off RLS on, no policy
GET /rest/v1/orders?select=* 200, all 3 rows with names and emails 200, []
POST /rest/v1/orders with a new row 201 Created 401, 42501 “new row violates row-level security policy”

Anyone who opens your app can copy the publishable key from the page. With RLS off, the key is a read and write pass to that table.

How do you fix it without breaking the app?

Enabling RLS alone makes the table return nothing to the app, because there is no policy yet. That empty screen is when people, and AI tools, add a policy with using (true), which hands every row back to everyone. Why updates and deletes then report success while changing nothing is in how to enable RLS in Supabase. Write the real policies in the same migration:

alter table public.orders enable row level security;

revoke all on table public.orders from anon, authenticated;
grant select, insert on table public.orders to authenticated;

create policy "Customers read their orders"
on public.orders for select
to authenticated
using ((select auth.uid()) = user_id);

create policy "Customers create their orders"
on public.orders for insert
to authenticated
with check ((select auth.uid()) = user_id);

If a table is meant to be public, such as a list of published articles, keep RLS on anyway and write one select policy for anon, with no write grants. If you never use the Data API and talk to Postgres only from your server, remove public from Exposed schemas instead, as Supabase’s maintainers suggested in the same issue.

To make sure new tables never start without RLS, Supabase’s event triggers guide has a trigger that enables it on every table created in public. And from 30 October 2026 new tables get no Data API grants by default, so they answer 42501 until you grant them; I covered that in Supabase 42501 permission denied.

What does a clean result not prove?

That RLS is on says nothing about whether the policies are right. In the same test, a policy written as using (true) gave the public key all 3 rows again, and this lint stays quiet once RLS is on. The checker has a separate rule for that, “Policies that allow every row”, and how to test Supabase RLS shows how to prove each policy against real users.

© 2026 Denys Zaichenko. Original article: https://madsalt.tech/notes/supabase-rls-disabled-in-public/