Pular para o conteúdo

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.

Vlad Tkachenko10 min de leitura
Uma fileira de portas para tabelas de um banco. As mais antigas estão abertas sobre linhas acesas, e a mais nova, no fim, está fechada.

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.

DataO que aconteceu ou vai acontecer
28 de abril de 2026Projetos novos passaram a poder recusar os grants automáticos na hora da criação.
30 de maio de 2026A ausência de grants automáticos começou a virar o padrão para projetos novos.
30 de outubro de 2026Os 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.

As tabelas que existem em 30 de outubro mantêm as portas que têm, inclusive uma que estava aberta a desconhecidos. Só as tabelas criadas depois começam com a porta fechada.

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, storage e auth, 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_role també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é o service_role ganhar 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.

O grant decide se uma requisição chega até a tabela. As regras atrás dele decidem quanto ela leva embora. Uma porta aberta sem regras atrás entrega a tabela inteira.

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 dizQual tranca recusouO que resolve
permission denied for tablea porta (o grant)um grant para a função que a dica aponta
new row violates row-level security policyas regrasuma política que permita essa linha
nenhum erro, e a lista vem vaziaas regrasuma 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á:

  1. 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.
  2. O Security Advisor, que segundo o Supabase lista as tabelas que vale a pena revisar antes da mudança.
  3. 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 security e 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. anon quer dizer todos os visitantes do seu app.
  • Não dê nenhum grant ao anon numa 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 o anon.
  • 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.

Escrito por

Vlad Tkachenko

Fundador da Reeve

Passo meus dias olhando apps criados com Lovable, Bolt, v0, Cursor e Replit, e a curta lista de erros que aparecem neles sem parar.

Mais sobre o autor

Leia depois

Todos os artigos

Não sabe como está o seu próprio app?

Faça um scan gratuito e receba uma nota clara de A a F em cerca de 20 segundos. Sem conta e sem cartão.

Verificar meu app grátis

Verificação externa automatizada, não uma auditoria completa. A ausência de achados não é garantia de segurança.