Saltar para o conteúdo

Supabase, 30 de outubro: o que falha com os novos grants

Ver como MarkdownAbrir em ClaudeAbrir em ChatGPTAbrir em Perplexity

A pergunta também é copiada. Se o chat abrir vazio, cole-a.

Em resumo

A 30 de outubro de 2026, o Supabase deixa de dar grants automáticos a anon, authenticated e service_role nas tabelas novas de public em todos os projetos existentes. As tabelas que já tem continuam a funcionar. Cada tabela criada depois responde 42501 permission denied ao supabase-js e à sua secret key até uma migration lhe dar o grant. Reproduzi a mudança em local: tabelas novas, views novas e tabelas antigas que uma migration apaga e recria falham todas; tabelas existentes, funções RPC e inserts em serial continuam a funcionar. Verifique com duas queries SQL e um grep, abaixo, e corrija com grant, RLS e política na mesma migration, nunca com grant all on all tables.

Tenho um verificador de segurança do Supabase gratuito que lê os grants de todas as tabelas expostas, por isso quis ver exatamente o que esta mudança faz antes de as pessoas começarem a tropeçar nela. Montei uma stack local do Supabase, dei-lhe os defaults antigos, apliquei a mudança como o Supabase a descreve e fiz um pedido a cada tipo de objeto em que consegui pensar. Este artigo é o que veio de volta e como verificar o seu projeto antes do dia 30.

O que muda a 30 de outubro de 2026?

Até agora, cada tabela criada no schema public recebia select, insert, update e delete para anon, authenticated e service_role através dos default privileges do Postgres. É por isso que uma tabela nova ficava acessível a partir do browser no instante em que existia, protegida por RLS ou não. O aviso de breaking change do Supabase retira esses defaults em três passos:

Data O que acontece
28 de abril de 2026 Os projetos novos podem desativar “Automatically expose new tables” na criação
30 de maio de 2026 O novo comportamento passa a ser o default nos projetos novos, em implementação ao longo de algumas semanas
30 de outubro de 2026 O novo comportamento é aplicado a todos os projetos existentes

No seu projeto, a mudança são duas instruções, as mesmas que o Supabase indica para quem quer aderir mais cedo:

alter default privileges for role postgres in schema public
  revoke select, insert, update, delete on tables from anon, authenticated, service_role;

alter default privileges for role postgres in schema public
  revoke usage, select on sequences from anon, authenticated, service_role;

Os default privileges só se aplicam a objetos criados depois. Os grants que já existem não são tocados, e é por isso que as suas tabelas atuais continuam a funcionar. O que não é afetado, segundo o aviso: os outros schemas (auth, storage, realtime, os seus), as apps que falam com o Postgres por ligação direta (psql, um ORM, um servidor com connection string) e o próprio RLS. A mudança diz respeito à Data API: REST, GraphQL e todas as bibliotecas cliente construídas sobre ela.

O que falha e o que continua a funcionar?

O meu setup: Supabase CLI 2.109.1, Postgres 17.6, PostgREST 14.14, a 26 de outubro de 2026. Pus auto_expose_new_tables = true no config.toml para obter os defaults antigos, criei uma tabela com duas linhas, RLS ligado e uma política de leitura, e depois corri as duas instruções acima. A seguir criei objetos novos e chamei cada um com a chave pública local (anon) e com a secret key (service_role), um pedido cada.

O que deixa de responder depois da mudança de 30 de outubro. Supabase local, objetos criados depois do revoke do changelog, chave pública salvo indicação. Tabela nova com RLS e uma política: create table → política, sem grant → 42501, HTTP 401; A mesma tabela com a secret key: create table → política, sem grant → 42501, HTTP 403; View nova sobre uma tabela que ainda funciona: create view → 42501, HTTP 401, sem hint; Tabela antiga apagada e recriada por uma migration: drop + create table → grants perdidos → 42501, HTTP 401.
A minha reprodução numa stack local do Supabase (CLI 2.109.1, Postgres 17.6, PostgREST 14.14), 26 de outubro de 2026.
O que continua a funcionar depois da mudança de 30 de outubro. Mesma stack local, mesmo revoke, chave pública. Tabela criada antes da mudança: tabela existente → grants mantidos → 2 linhas, HTTP 200; Função nova chamada por RPC: create function → execute via PUBLIC → HTTP 200; Tabela serial nova, só select e insert com grant: id serial → sequence com UPDATE → insert, HTTP 201.
A minha reprodução numa stack local do Supabase (CLI 2.109.1, Postgres 17.6, PostgREST 14.14), 26 de outubro de 2026.
Objeto, criado depois da mudança salvo indicação Chave Resultado
Tabela criada antes da mudança pública HTTP 200, 2 linhas
Tabela nova com RLS e uma política to anon, authenticated pública HTTP 401, 42501, hint GRANT SELECT ON public.new_orders TO anon;
A mesma tabela nova secret HTTP 403, 42501, hint … TO service_role;
View nova (security_invoker) sobre a tabela antiga pública HTTP 401, 42501, permission denied for view, hint null
Cópia feita com create table … (like old_orders including all) pública HTTP 401, 42501
A tabela antiga depois de drop table e novo create table pública HTTP 401, 42501
Função nova public.ping() em /rest/v1/rpc pública HTTP 200
Tabela nova com id serial, só select, insert dados a anon pública insert HTTP 201

Cinco coisas nesta tabela merecem ser sabidas antes da mudança.

“As tabelas existentes mantêm os grants” só vale enquanto a tabela existir. Uma migration que apaga e recria uma tabela, uma cópia com create table … (like …) ou um restauro a partir de um dump criam uma tabela nova, e uma tabela nova não recebe nada. A política voltou com o meu create policy, os grants não. Tudo o que, no seu pipeline, reconstrói tabelas em vez de as alterar falha na primeira execução depois da mudança.

A secret key também falha. O service_role ignora o RLS, mas continua a precisar de um grant. Por isso uma Edge Function, um cron job ou um handler de webhook que escreve numa tabela nova falha com 403, e não só o browser. A hint nomeia o service_role, e é assim que se distingue.

As views não dão hint. Nas tabelas, o PostgREST devolve o GRANT exato de que precisa. Na minha view nova a hint foi null e a mensagem dizia permission denied for view v_old_orders. Uma ferramenta de IA que corrige o 42501 a partir da hint não tem aqui nada para copiar; o grant é grant select on public.v_old_orders to authenticated;, e a view deve manter security_invoker = true para o RLS da tabela continuar a aplicar-se.

As funções não fazem parte desta mudança. As duas instruções tocam em tabelas e sequences. A minha função nova continuou chamável por anon, authenticated e service_role, porque o Postgres dá execute em cada função nova a PUBLIC e um revoke ao nível do schema não o consegue retirar, como explica o guia de segurança da API do Supabase. As chamadas RPC continuam a funcionar depois de 30 de outubro; isso significa também que uma função security definer nova é chamável com a sua chave pública logo que existe.

Os inserts em serial continuaram a funcionar sem grant na sequence. Muitos conselhos dizem que um id serial precisa de grant usage on sequence … to authenticated depois da mudança. Na minha execução o insert passou sem ele: o revoke retira usage e select nas sequences novas, mas o update fica, e o update chega para o nextval. Se revogar todos os default privileges das sequences, como faz o revoke completo do guia de segurança da API, aí precisa do grant de usage. Uma coluna identity (generated by default as identity) não precisa de nada, em qualquer dos casos.

Mais uma coisa que o revoke deixa para trás: a tabela nova continuou a vir com TRUNCATE, REFERENCES e TRIGGER para as três funções, e no Postgres 17 os defaults trazem também MAINTAIN. Nenhuma política de RLS se aplica ao TRUNCATE. Tratei disso na nota sobre o 42501.

Onde o encontra antes de 30 de outubro

A mudança no seu projeto alojado é no dia 30, mas há três sítios que já se comportam como depois dela.

supabase db reset numa CLI recente. A CLI v2.102.0 acrescentou [api] auto_expose_new_tables ao config.toml. Na 2.109.1, com a chave omitida, a minha stack nova já tinha os defaults novos: as tabelas só recebiam MAINTAIN, REFERENCES, TRIGGER e TRUNCATE, as sequences só UPDATE. Pô-la a true traz de volta os grants antigos e imprime api.auto_expose_new_tables is deprecated and will be removed on 2026-10-30. Por isso, se as suas tabelas locais ainda funcionam depois de um reset, veja primeiro essa chave. É também o sítio mais barato para testar: retire-a, faça reset, corra a sua app.

Preview branches e projetos criados desde 30 de maio. Reproduzem as suas migrations com os defaults novos. Toda a tabela cuja migration não tem grant já está inacessível aí. Na discussão no GitHub, quem tem um projeto por cliente relata exatamente isto: os projetos em produção estão bem, cada deploy novo a partir das mesmas migrations está partido.

Uma migration de baseline que traz de volta o default antigo. Um comentador da mesma discussão aponta que uma primeira migration feita com supabase db pull pode conter alter default privileges … grant all on tables to anon, authenticated, service_role. Corri essa instrução depois da mudança: a tabela seguinte que criei voltou a ficar acessível, HTTP 200. Em local isso esconde o problema. Aplicada ao projeto alojado, desfaz em silêncio a mudança e cada tabela futura volta a ficar aberta à chave pública, tenha RLS ou não. Procure alter default privileges nas suas migrations e leia cada ocorrência.

Como verificar o projeto antes de 30 de outubro

Quatro verificações, da mais rápida à mais completa. As duas primeiras correm no SQL Editor do projeto alojado.

1. O seu projeto já mudou?

Esta é a query do guia de segurança da API do Supabase. Lista o que os objetos novos criados por postgres em public vão receber:

select
  case d.defaclobjtype
    when 'r' then 'tables'
    when 'S' then 'sequences'
    when 'f' then 'functions'
  end as object_type,
  a.grantee::regrole as role,
  string_agg(a.privilege_type, ', ' order by a.privilege_type) as privileges
from pg_default_acl d
cross join lateral aclexplode(d.defaclacl) a
where d.defaclrole = 'postgres'::regrole
  and d.defaclnamespace = 'public'::regnamespace
  and a.grantee in ('anon'::regrole, 'authenticated'::regrole, 'service_role'::regrole)
group by 1, 2
order by 1, 2;

Se as linhas de tables contêm SELECT, INSERT, UPDATE, DELETE, o projeto ainda está no default antigo e muda no dia 30. Se só mostram MAINTAIN, REFERENCES, TRIGGER, TRUNCATE (ou nenhuma linha), as tabelas novas já estão fechadas. Também o vê no Dashboard, nas definições da Data API, “Automatically expose new tables”.

2. Que tabelas têm política mas não têm grant?

Uma política para uma função que não tem grant para esse comando nunca corre: o pedido falha antes do RLS. Esta query lista esses pares. No meu laboratório encontrou a tabela nova e a antiga recriada, e mais nada:

select p.schemaname || '.' || p.tablename as "table", r.role, p.cmd
from pg_policies p
cross join lateral unnest(case when 'public' = any(p.roles) then array['anon', 'authenticated']::name[] else p.roles end) as r(role)
join pg_class c on c.relname = p.tablename
join pg_namespace n on n.oid = c.relnamespace and n.nspname = p.schemaname
where p.schemaname = 'public'
  and r.role in ('anon', 'authenticated')
  and not has_table_privilege(r.role::text, c.oid,
        case p.cmd when 'ALL' then 'SELECT' else p.cmd end)
order by 1, 2, 3;

Corra-a depois de cada migration, no CI contra um db reset limpo ou no staging. Num projeto saudável não devolve nada; depois do dia 30, cada linha é uma tabela que a sua app não alcança. Verifica grants ao nível da tabela, por isso uma tabela aberta só com grants por coluna (grant select (id, name) on …) aparece aqui mesmo que essas colunas sejam legíveis.

3. Que migrations criam uma tabela sem grant?

A base de dados só mostra as tabelas que existem. Uma migration que ainda não aplicou só é visível no repositório. Este grep rudimentar lista cada create table em supabase/migrations sem grant … on para o mesmo nome:

for t in $(grep -hioE 'create table( if not exists)? +[a-z0-9_."]+' supabase/migrations/*.sql | awk '{print $NF}' | sort -u); do
  grep -qiF "on $t " supabase/migrations/*.sql || grep -qiF "on table $t " supabase/migrations/*.sql || echo "no grant: $t"
done

É correspondência de texto, não parsing de SQL: um grant que lista várias tabelas numa instrução, ou uma tabela nomeada sem schema num sítio e com schema noutro, dá um falso positivo. Leia cada linha que imprime. As tabelas que só o código de servidor alcança por ligação direta não precisam de grant nenhum.

4. Reproduza tudo com os defaults novos

Retire auto_expose_new_tables do config.toml, corra supabase db reset e corra a sua app ou os testes contra isso. É o seu projeto depois do dia 30, mais uma cópia nova de cada tabela, que é o caso mais difícil. O Supabase também lista as tabelas afetadas no Security Advisor e marca a exposição na Data API no Table Editor.

Como corrigir sem abrir todas as tabelas

Escreva o grant, o RLS e as políticas na migration que cria a tabela, e dê a cada função só o que a app usa:

create table public.invoices (
  id bigint generated by default as identity primary key,
  user_id uuid not null default auth.uid(),
  total numeric not null
);

revoke all on table public.invoices from anon, authenticated, service_role;
grant select, insert on table public.invoices to authenticated;
grant select, insert, update, delete on table public.invoices to service_role;

alter table public.invoices enable row level security;

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

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

Corri este bloco na mesma stack local: um utilizador de teste com sessão iniciada inseriu uma linha e leu-a de volta (HTTP 201, depois 200), a chave pública sem sessão recebeu 42501, e a verificação 2 acima deixou de listar a tabela. O revoke all inicial limpa o TRUNCATE, o TRIGGER e o REFERENCES que vêm com uma tabela nova, e dá o mesmo resultado num projeto que ainda não mudou. Dê grant a anon só em dados pensados para visitantes sem sessão, e a service_role só se o código de servidor usar a secret key nesta tabela. Como grants e políticas se encaixam numa app inteira está na minha checklist de segurança para apps Lovable.

O que não fazer quando chega o primeiro 42501:

  • Não corra grant all on all tables in schema public to anon, authenticated. Repõe o default antigo em todas as tabelas de uma vez, incluindo qualquer tabela onde o RLS nunca foi ativado, e essas passam a ser legíveis por quem tiver a sua chave pública. A nota sobre o 42501 mostra porque é que o grant é o primeiro cadeado e o RLS o segundo.
  • Não volte a pôr alter default privileges … grant. É o rollback da mudança inteira.
  • Não desative o RLS para “testar” e não ponha a secret key no frontend. A secret key ignora o RLS para toda a gente que abrir o seu bundle; a diferença entre as chaves está em anon key vs service role key.

Se uma ferramenta de IA escreve as suas migrations, acrescente uma linha às instruções dela: “every new table in public gets explicit grants per role, enable row level security and policies in the same migration; never grant on all tables in schema public”. A hint do erro ensina-lhe o grant, nunca o RLS que lhe pertence.

Perguntas frequentes sobre a mudança

As minhas tabelas existentes vão deixar de funcionar a 30 de outubro? Não. Os default privileges aplicam-se a objetos criados depois, e na minha execução a tabela antiga respondeu HTTP 200 depois da mudança. A exceção é uma tabela que as suas migrations apagam e criam de novo.

Tenho de fazer alguma coisa se nunca crio tabelas? No dia 30, não. Verifique da próxima vez que você, um colega ou uma ferramenta de IA acrescentar uma, e verifique qualquer pipeline que crie projetos ou branches a partir das suas migrations.

Afeta as Edge Functions? Sim, se usarem supabase-js com a secret key numa tabela nova: o service_role também precisa de um grant. As funções que se ligam diretamente ao Postgres não são afetadas.

Afeta funções RPC, o Storage ou o Auth? Não nesta mudança. As funções novas continuam chamáveis através de PUBLIC, e storage e auth não são o schema public.

Posso mudar antes do prazo? Sim. As duas instruções do início são a mudança; corra-as primeiro no staging, depois em produção. As tabelas existentes mantêm os grants, por isso uma app em funcionamento não nota nada, e a tabela nova seguinte diz-lhe logo se as suas migrations estão prontas.

Como o verificador vê isto

O verificador liga-se em modo só de leitura e lê os grants de cada tabela dos seus schemas expostos: em que tabelas a chave pública pode escrever sem política, que tabelas têm o RLS desligado e que default privileges ainda dão a cada tabela nova TRUNCATE, REFERENCES ou TRIGGER. Também lista todas as políticas cuja função não tem grant para esse comando, as tabelas que a verificação 2 encontra, exceto as que têm RLS desligado ou grants por coluna. Não vê ficheiros de migration que ainda não aplicou, por isso a verificação 3 acima continua a ser sua. Se o RLS é a parte de que tem menos certeza, como testar o RLS do Supabase explica-o passo a passo, e a nota sobre os schemas expostos cobre os schemas que não são public, que esta mudança deixa em paz.

© 2026 Denys Zaichenko. Artigo original: https://madsalt.tech/pt/blog/supabase-october-30-grant-change/