Fifty-two days from now, Supabase changes what happens to every table you create in an existing project. Not a setting you toggle — a platform default enforcement that lands on all projects at once.
Here is the quiet part nobody warns you about: the migration you already wrote and shipped is the one that breaks, and the leak that matters was probably already there before today. (If you came here from the earlier post on the six real causes of 42501 and RLS leaks, this is the October 30 wrinkle on top of it.)
What actually happens on October 30
Supabase published this as changelog entry #45329 on April 28, 2026. The dates:
| Date | What changes |
|---|---|
| 2026-05-30 | New projects default to opt-in exposure (grant required) |
| 2026-10-30 | The same rule is enforced on every existing project |
Before the change, a table in public was automatically granted select, insert, update, delete to anon, authenticated, and service_role — reachable through the Data API the moment it existed. After the flip, new tables in public are reachable only if your migration explicitly GRANTs the role.
Existing tables keep their current grants. That is the trap, in two opposite ways:
Trap 1 — your next migration breaks with a 403 that is silent in CI.
-- supabase/migrations/20260910_add_invoices.sql — ships fine, applies fine
create table invoices (
id uuid primary key default gen_random_uuid(),
customer_id uuid not null,
amount_cents integer not null,
created_at timestamptz default now()
);
Deploy that after October 30 and the first SDK call dies in production with error 42501 permission denied for table invoices — PostgREST short-circuits at the privilege layer before RLS is ever evaluated. Your policy file is correct and irrelevant, because the check never gets that far. On an app already running with real users, that is the deploy where the "it worked in staging" email starts.
Trap 2 — you are about to "fix" it by opening the whole database.
The first search hit for permission denied for table supabase is the barrel-load answer:
grant all on all tables in schema public to anon, authenticated;
It works. Your table appears in the API immediately. It also hands anonymous visitors select, insert, update, and delete on every table in the database — including and especially the ones you never meant to expose. On a project where RLS is off on even one table, this command is a full database exposure, and it is the exact CVE-2025-48757 class of leak (170 production apps, 303 endpoints, public anon key).
The leak that was already there
While you are thinking about October 30, this is the query that finds your current problem. Paste it into the SQL editor — it lists every table in public, whether RLS is on, and which Data API roles hold which privileges:
select n.nspname as schema,
c.relname as table,
c.relrowsecurity as rls_enabled,
coalesce(string_agg(
g.grantee || ':' || g.privilege_type, ', '
order by g.grantee), '(none)') as grants
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
left join information_schema.role_table_grants g
on g.table_schema = n.nspname
and g.table_name = c.relname
and g.grantee in ('anon', 'authenticated')
where n.nspname = 'public'
and c.relkind in ('r','p')
group by 1,2,3
order by c.relrowsecurity, c.relname;
Read it like this:
-
rls_enabled = falsewith anyanon:...grant → every profile in your database returns to a stranger withcurland your anon key. That key ships in your JavaScript bundle. It is not a secret. -
rls_enabled = falsewithanon:UPDATEon top → the same stranger can also rewrite rows. -
rls_enabled = truewith(none)→ not a leak, but your client will return empty sets and you will think it is broken.
The row that hurts looks like profiles | false | anon:SELECT, anon:UPDATE. That is not a hypothetical — it is the combination that keeps showing up in the CVE disclosures.
And the anon key check, one curl:
curl -s "https://YOUR_PROJECT.supabase.co/rest/v1/profiles?select=*" \
-H "apikey: YOUR_ANON_KEY" | head -20
-
[]→ RLS is doing its job. - rows → anonymous visitors can read this table. Verify you meant that.
-
42501→ the grant did not apply to the role you tested (which is the new default where things are going).
What to ship so both traps are impossible
Make grants part of the migration file, next to the table, next to the policies. A table definition that does not say who can read it is incomplete — after October 30 it is visibly incomplete instead of silently permissive.
create table invoices (
id uuid primary key default gen_random_uuid(),
customer_id uuid not null,
amount_cents integer not null,
created_at timestamptz default now()
);
grant select on public.invoices to anon; -- only if genuinely public
grant select, insert, update, delete on public.invoices to authenticated;
grant select, insert, update, delete on public.invoices to service_role;
alter table invoices enable row level security;
create policy "users see own invoices" on invoices
for select to authenticated using (customer_id = auth.uid());
Three rules fit in one sentence:
-
Grant to
authenticated, notanon, unless you can name who should read this table while logged out. Most tables have no answer. -
Never
grant ... to anonwith RLS off. That is the CVE class. -
Never
grant all on all tables ... to anonas the fix for a 42501. It is a full database exposure wearing a helper costume.
The honest version of the deadline
October 30 does not turn your RLS off. It does not touch existing tables. What it does is stop silently exposing the tables you create from now on — which is good — while leaving all the tables you already created exactly as open or closed as they were. That is why the two jobs are separate: grant hygiene going forward, and an audit of what is already reachable today. Both are the same hour of work. The second one is the one with the CVE on the other side.
If you want a runnable version of the isolation check — same test suite, red on one branch, green on the other, the whole difference one policy file, runs on PGlite in about two seconds with no credentials — it is here:
https://github.com/cekuu35/supabase-rls-leak-demo
And if the query above returned rows you did not expect, the checklist that catches the rest of these patterns — missing WITH CHECK, using(true), partition inheritance, SECURITY DEFINER functions, service-role keys that crossed into the client bundle — is in the free PDF, no email wall:
https://cengokurtoglu.gumroad.com/l/nextjs-supabase-10-checks-free
Run the query above before October 30. The date is the excuse; the leak was the actual news.
Top comments (0)