Noções de segurança
Supabase "permission denied for table": o grant que falta
A partir de 30 de outubro, uma tabela nova no Supabase responde "permission denied for table" até receber um grant. O e-mail mostra só metade.

Em resumo
- A partir de 30 de outubro de 2026, uma tabela nova no seu projeto Supabase responde "permission denied for table" até alguém liberar o acesso a ela. As tabelas que você já tem continuam funcionando exatamente como hoje.
- Um grant decide se uma requisição chega até a tabela. O Row Level Security continua decidindo quais linhas ela leva de volta. Dar acesso ao anon numa tabela sem regras deixa todas as linhas legíveis por qualquer pessoa.
- A mudança não fecha nada que já esteja aberto. Confira hoje o que um desconhecido consegue ler, e de novo a cada tabela que você criar.
Se o seu app roda no Supabase, é provável que você tenha recebido um e-mail
sobre 30 de outubro. Ele diz que nada muda nas tabelas que você já tem, e depois
te entrega três comandos de SQL. Ou já é novembro: você pediu um recurso novo ao
Lovable, ao Bolt ou ao Cursor, a tela nova apareceu vazia, e em algum lugar do
console está uma mensagem que diz permission denied for table. As duas coisas
são a mesma mudança do Supabase, vista de um lado e do outro da data.
Aqui está a parte que o e-mail deixa de fora: o SQL que ele mostra é metade da correção. Rode essa metade sozinha, na tabela errada, e o seu app volta a funcionar enquanto a tabela nova entrega as linhas a quem pedir.
O que significa "permission denied for table" no Supabase?
Significa que o tipo de visitante que fez a requisição não recebeu acesso nenhum àquela tabela, então o banco recusou antes de olhar uma única linha.
O Supabase classifica cada requisição numa função (role, em inglês), que é o
tipo de visitante de onde ela vem. anon é alguém que não fez login.
authenticated é alguém que fez. service_role é o seu próprio código de
servidor usando a chave secreta. Um grant é o comando que dá a uma dessas
funções acesso a uma tabela no Postgres, o banco de dados sobre o qual o
Supabase roda. Sem grant, sem acesso, não importa o que mais você tenha escrito.
Quando falta o grant, o Supabase responde assim, normalmente como um 401 ou um 403:
{
"code": "42501",
"message": "permission denied for table comments",
"hint": "Grant the required privileges to the current role with: GRANT SELECT ON public.comments TO anon;"
}
A dica é a parte útil. Ela diz qual função foi recusada e o comando exato que a deixaria entrar.
Pense em cada tabela como uma sala. O grant é a porta, e existe uma porta separada para cada função. O Row Level Security, as regras por linha que você talvez já conheça, decide quais gavetas um visitante pode abrir depois de entrar. "Permission denied for table" quer dizer que alguém está parado diante de uma porta fechada. Essa pessoa nem chegou às suas regras.
O que muda em 30 de outubro?
As tabelas novas no schema public, a pasta de tabelas que o seu construtor usa
quando ninguém diz o contrário, passam a chegar com as portas fechadas.
Até agora, o Supabase abria as três portas de toda tabela nova automaticamente:
ler, adicionar, alterar e apagar, para anon, authenticated e service_role
por igual. Uma tabela ficava alcançável pelo seu app no momento em que passava a
existir, e as suas regras de Row Level Security eram a única coisa entre um
desconhecido e as linhas dela. O
changelog
do Supabase explica o motivo sem rodeios: agentes e plataformas de IA agora
criam tabelas sem ninguém revisar a mudança, e os grants automáticos expunham
tabelas que "a developer forgot to protect", que um desenvolvedor esqueceu de
proteger.
| Data | O que aconteceu ou vai acontecer |
|---|---|
| 28 de abril de 2026 | Projetos novos passaram a poder recusar os grants automáticos na hora da criação. |
| 30 de maio de 2026 | A ausência de grants automáticos começou a virar o padrão para projetos novos. |
| 30 de outubro de 2026 | Os projetos existentes também deixam de recebê-los. |
Se o seu projeto foi criado depois do fim de maio, ele talvez já funcione assim, porque o Supabase levou o novo padrão aos projetos novos ao longo das semanas seguintes a essa data.
Três detalhes valem a pena antes da data:
- Só o schema
public. O Storage e o login guardam as tabelas deles em schemas próprios,storageeauth, e o Supabase diz que os grants e os padrões desses schemas continuam como estão. - A chave secreta também é recusada. Os grants automáticos cobriam o
service_roletambém, então uma edge function (código de servidor que o Supabase roda para você) usando a sua chave secreta recebe o mesmo erro numa tabela nova até oservice_roleganhar um grant próprio. - Uma tabela apagada e criada de novo é uma tabela nova. Os grants pertencem à própria tabela e vão embora com ela quando ela é apagada. Se o seu construtor recria uma tabela para alterá-la, a recriada começa com a porta fechada.
Meu app vai quebrar em 30 de outubro?
Não no dia. Ele quebra na primeira vez que algo cria uma tabela nova sem criar também o grant.
Para a maioria de quem está lendo isto, esse algo é o construtor. Você pede uma
seção de comentários. O construtor escreve uma migração, que é o arquivo de
mudanças no banco que ele roda por você, e a migração cria uma tabela
comments. Se ela também escrever os grants, você nunca vai ver esse erro. Se
não escrever, a tela de comentários não mostra nada, ou salvar um comentário
falha, ou aparece uma mensagem em vermelho, dependendo de como o seu app lida
com um erro. Todas as telas que você já tinha continuam funcionando.
O Supabase publica uma agent skill para ferramentas de programação com IA que inclui o passo do grant. Se o seu construtor usa ou não depende do construtor, então o mais seguro é supor que a próxima tabela que ele criar pode chegar com a porta fechada.
É seguro rodar o GRANT que o erro sugere?
Só depois que a tabela estiver com o Row Level Security ligado e com uma regra escrita para ela. O grant decide quem passa pela porta. Nada nele decide quais gavetas a pessoa abre.
A chave que faz as requisições do seu app ao Supabase vai dentro do seu app,
então um grant para o anon quer dizer que qualquer visitante, com login ou
sem, agora pode pedir linhas àquela tabela. Com uma regra no lugar, ele recebe
as linhas que a regra permite. Com o Row Level Security desligado, ele recebe
todas as linhas da tabela, e nada no seu app vai parecer diferente.
O e-mail mostra três grants e para por aí. O próprio changelog do Supabase mostra três passos, e diz para tratá-los como uma unidade ("treat these three steps as a unit"):
-- 1. quem pode chegar à tabela
grant select on public.orders to authenticated;
grant select, insert, update, delete on public.orders to service_role;
-- 2. ligar as regras por linha
alter table public.orders enable row level security;
-- 3. a regra em si
create policy "Customers read their own orders"
on public.orders
for select
to authenticated
using (auth.uid() = user_id);
Não há nenhuma linha para o anon nesse exemplo, e é de propósito. Ninguém sem
login deveria chegar a uma tabela de pedidos, então a porta para desconhecidos
continua fechada, e os clientes com login recebem só a leitura de que a tela
deles precisa. A regra, depois, restringe isso às linhas de cada um. O conselho
do próprio Supabase é
dar a cada função o mínimo de que ela precisa.
Um grant para o anon cabe numa tabela cujas linhas são feitas para todo mundo,
como os comentários embaixo de um post público.
Se você quer ver de que lado dessa imagem as suas tabelas estão, o nosso escaneamento gratuito faz ao seu app no ar a pergunta que um desconhecido faria, conta as linhas que ele receberia sem buscar nenhuma delas, e leva uns 20 segundos, sem conta: escanear seu aplicativo.
Por que as correções de Row Level Security não mexem nesse erro
Porque o grant é verificado primeiro. Uma requisição recusada na porta nunca chega às suas regras, então mudar as regras não muda nada no erro.
Isso importa porque o código é compartilhado. 42501 também é o que o Postgres
devolve quando o Row Level Security recusa um salvamento, como em
new row violates row-level security policy.
Um assistente que se guia só pelo código pode partir para as correções que
pertencem àquele erro: desligar o Row Level Security, ou acrescentar uma regra
com using (true), que deixa todo mundo passar. Nenhuma das duas faz este erro
sumir. As duas ficam para trás depois que o grant finalmente entra, e aí a porta
está aberta para gavetas sem tranca.
As palavras da mensagem distinguem os dois casos, mesmo que o número não distinga:
| A mensagem diz | Qual tranca recusou | O que resolve |
|---|---|---|
permission denied for table | a porta (o grant) | um grant para a função que a dica aponta |
new row violates row-level security policy | as regras | uma política que permita essa linha |
| nenhum erro, e a lista vem vazia | as regras | uma política de leitura para essa função |
O que 30 de outubro não resolve
Nenhuma tabela que você já tem. Cada uma mantém exatamente o acesso que tem hoje, inclusive uma tabela que um desconhecido consegue ler neste momento.
A mudança é sobre tabelas que ainda não existem. Até agora toda tabela nova chegava com as portas abertas, então um app feito antes de 30 de outubro pode ter uma em que ninguém nunca instalou as trancas: o Row Level Security nunca foi ligado, ou foi ligado com uma regra que deixa todo mundo entrar. O dia 30 de outubro deixa essas salas exatamente como estão.
Três lugares mostram onde você está:
- A página de configurações da Data API, que o e-mail traz em link. No dashboard ela fica em Integrations, depois Data API, e lista quais das suas tabelas estão alcançáveis.
- O Security Advisor, que segundo o Supabase lista as tabelas que vale a pena revisar antes da mudança.
- A vista de fora. Nenhum dos dois primeiros diz o que um desconhecido recebe de volta. Para isso você pergunta ao seu app no ar do jeito que um desconhecido perguntaria, que é o que o nosso escaneamento faz.
Como o Reeve vigia as tabelas que você cria
Depois de 30 de outubro, cada tabela que o seu construtor cria é uma decisão nova sobre quem passa pela porta. O Reeve confere a resposta de fora, e o Monitor continua conferindo.
- O escaneamento gratuito pergunta a cada tabela que o seu app menciona quantas linhas um visitante sem login receberia, lê a contagem e para por aí, sem buscar nenhuma linha. Uns 20 segundos, sem conta: escanear seu aplicativo.
- O Reeve Monitor roda as nove verificações de hora em hora em até três apps e te manda um e-mail no dia em que a sua nota piora, para que um deploy que abriu alguma coisa não fique esperando você aparecer para olhar.
- O Care roda as mesmas verificações e guarda uma cópia criptografada do seu banco do Supabase fora da sua conta do Supabase, para que a correção de um assistente que recria uma tabela tenha uma cópia para onde voltar. Como a cópia é feita e restaurada.
O que cada plano cobre está na página de preços.
O que fazer antes de 30 de outubro
O que fazer
- Deixe as tabelas que você já tem como estão, se tudo o que você quer é que o app continue funcionando. Elas mantêm os grants.
- Peça ao seu construtor para escrever o grant, o
enable row level securitye uma política na mesma migração de cada tabela nova, como uma mudança só. - Quando o erro aparecer, leia qual função a dica aponta.
anonquer dizer todos os visitantes do seu app. - Não dê nenhum grant ao
anonnuma tabela antes de o Row Level Security estar ligado e de haver uma regra escrita. Tabelas com pessoas ou pedidos em geral não precisam de grant nenhum para oanon. - Recuse qualquer correção que desligue o Row Level Security ou acrescente
using (true)para limpar um erro de permissão. Nenhuma das duas mexe nele. - Confira o que um desconhecido consegue ler das tabelas que você já tem. O dia 30 de outubro deixa essas tabelas como estão.
Comece pela tabela que um desconhecido mais gostaria de ler, em geral a que tem pessoas dentro, e escaneie seu aplicativo para ver o que ela entrega hoje.
Perguntas frequentes
Meu app no Supabase vai parar de funcionar em 30 de outubro?
Não. Toda tabela que existir em 30 de outubro mantém o acesso que tem, e o Supabase confirmou que esses grants não serão revogados. O que muda é a próxima tabela criada depois dessa data. Ela nasce inalcançável pelo seu app até alguém liberar o acesso, então a primeira coisa a quebrar é o primeiro recurso novo que precisar de uma tabela nova.
O que significa "permission denied for table" no Supabase?
Significa que o tipo de visitante que fez a requisição não recebeu acesso nenhum àquela tabela, então o seu banco recusou antes de olhar uma única linha. A mensagem vem do Postgres, o banco de dados sobre o qual o Supabase roda, com o código 42501, e normalmente chega ao seu app como um 401 ou um 403. O Supabase acrescenta uma dica com o comando GRANT exato que deixaria esse visitante entrar.
É seguro rodar o GRANT que o erro sugere?
Só depois que a tabela estiver com o Row Level Security ligado e com uma regra escrita para ela. Um grant para o anon deixa qualquer visitante do seu app pedir linhas à tabela, porque a chave que faz essas requisições vai dentro do seu app. Com uma regra no lugar, ele recebe as linhas que a regra permite. Sem regra e com o Row Level Security desligado, ele recebe todas.
Essa mudança afeta o Storage, o login ou as minhas edge functions?
O Storage e o login guardam as tabelas deles em schemas próprios, storage e auth, e o Supabase diz que os grants e os padrões desses schemas continuam como estão. As edge functions são outra história. Uma que usa a sua chave secreta ainda precisa de um grant em qualquer tabela nova, porque os grants automáticos que estão saindo cobriam o service_role além das duas funções que os seus visitantes usam.
Dá para ligar o comportamento antigo de novo?
O Supabase documenta um jeito, como configuração na página Data API do dashboard ou como SQL, e recomenda não fazer isso. Com ele ligado, toda tabela nova fica alcançável por qualquer visitante no momento em que passa a existir, e o Row Level Security é a única coisa entre um desconhecido e as linhas dela. Era assim que todo projeto funcionava antes da mudança.