RLS no Supabase sem GRANT: por que a tela fica vazia e não dá erro?

Sem GRANT na tabela, a política de RLS nem chega a rodar. Como o Supabase e o Postgres tratam isso, por que a tela abre vazia e como conferir em uma consulta.

Pedro Henrique Quadroatualizado em 5 min de leitura

Porque RLS (Row Level Security, a regra que decide quais linhas cada usuário enxerga) só entra em ação depois que o banco confere se aquele usuário tem permissão na tabela, e essa permissão é dada pelo comando GRANT. Se o GRANT não existe, a política nunca é avaliada. Dependendo do caminho que a leitura percorre, o resultado aparece como erro ou como lista vazia, e foi a lista vazia que me custou tempo. A solução é escrever o GRANT na migration, junto com a política, em vez de contar com um padrão do projeto. Conferi as fontes em 10 de outubro de 2026.

O que a documentação diz

A documentação do Supabase descreve duas checagens em fila: primeiro os grants, depois as políticas que filtram linhas. Uma requisição sem o grant necessário falha com o código 42501 (permissão negada) antes de qualquer política ser avaliada, e a mensagem típica é permission denied for table. O próprio texto também avisa que adicionar políticas não retira grants que já existam.

A documentação do PostgreSQL diz a mesma coisa de outro jeito: as políticas de segurança por linha existem além do sistema de privilégios do SQL (o GRANT). Com RLS ligado e nenhuma política, vale um default-deny, isto é, nenhuma linha fica visível.

Então são duas camadas com papéis diferentes. O GRANT é o portão da tabela inteira. A política é a régua que escolhe linhas dentro dela. Uma não substitui a outra.

O que vi na prática

Num projeto em que trabalhei, o banco foi recriado a partir das migrations em um projeto Supabase novo. Subiram as tabelas, os dados e todas as políticas. O app abriu com todas as telas vazias, sem mensagem de erro na interface. Nenhuma migration escrevia GRANT.

Dois detalhes tornaram o problema difícil de enxergar:

  • O coletor que grava dados (um job de ingestão) usava a chave service_role. Essa função tem o atributo bypassrls, que pula as políticas, mas pular política não é ter privilégio de tabela. A documentação do Supabase diz que service_role ignora o RLS e não diz que ela dispensa o GRANT. No meu caso, a escrita também parou.
  • Uma tela continuava funcionando. Ela lia por uma função com SECURITY DEFINER, que roda com a permissão de quem a criou e não depende do privilégio de quem chama. Por isso aquela página dava a impressão de que o banco estava saudável, enquanto as 16 tabelas estavam inacessíveis para o usuário logado.

Por que isso aparece quando se recria o projeto

Em projetos criados no padrão antigo, o Supabase concede privilégios automaticamente a toda tabela nova no schema public. No projeto que vi, isso vinha de ALTER DEFAULT PRIVILEGES. Esse comando, segundo a documentação do PostgreSQL, define os privilégios de objetos criados no futuro e não mexe nos que já existem. A documentação do Supabase sobre segurança da API diz que a plataforma está mudando o padrão para revogar esses grants automáticos, de modo que expor uma tabela passe a ser uma escolha explícita. Eu medi a diferença no meu projeto: no padrão novo, a configuração de privilégios padrão não incluía SELECT, INSERT, UPDATE nem DELETE para os papéis da API.

O resultado é que um schema que só funcionava porque o projeto de origem dava o GRANT de graça deixa de funcionar quando é recriado em outro. Esse schema não era versionado de verdade: parte do que ele precisava estava fora das migrations.

Sobre o sintoma de tela vazia em vez de erro: a documentação do Supabase afirma que o 42501 é devolvido, e eu vi telas vazias. Não encontrei na documentação uma explicação de por que a interface do meu app engoliu o erro, e isso depende de como cada tela trata a resposta. O que posso afirmar é o que observei.

Como conferir em uma consulta

Esta consulta lista as tabelas do schema public e os privilégios de DML (leitura e escrita) do papel authenticated. Tabela com a coluna vazia está sem acesso:

SELECT t.tablename, string_agg(g.privilege_type, ',') AS dml
FROM pg_tables t
LEFT JOIN information_schema.role_table_grants g
  ON g.table_name = t.tablename
 AND g.table_schema = 'public'
 AND g.grantee = 'authenticated'
WHERE t.schemaname = 'public'
  AND (g.privilege_type IS NULL
       OR g.privilege_type IN ('SELECT','INSERT','UPDATE','DELETE'))
GROUP BY 1
ORDER BY 2 NULLS FIRST;

Rode essa consulta com um usuário administrador: a information_schema.role_table_grants só mostra os grants que o papel que consulta consegue ver, e não segue herança de papéis.

O que fazer

  1. Escrever o GRANT na própria migration, ao lado da política, espelhando o que a política pretende. Exemplo para uma tabela deals lida e editada por usuários logados (o GRANT dá o portão, a política afina):
ALTER TABLE public.deals ENABLE ROW LEVEL SECURITY;

GRANT SELECT, INSERT, UPDATE, DELETE ON public.deals TO authenticated;

-- política mínima de exemplo: em produção, troque o true pelo filtro do seu tenant
CREATE POLICY deals_leitura ON public.deals
  FOR SELECT TO authenticated
  USING (true);
  1. Dar privilégio padrão à identidade de ingestão (service_role) faz sentido. Para authenticated prefiro não dar: assim, toda tabela nova precisa declarar que a interface pode lê-la.
  2. Rodar a consulta acima depois de cada recriação de ambiente, antes de abrir o app.
  3. Testar o app com um usuário comum logado, e não só com a chave de serviço. A chave de serviço esconde justamente este defeito.

Se você precisa de um sistema com banco, permissões e multi-tenant bem fechados desde o começo, veja como eu trabalho em sistemas e SaaS.

Fontes

  1. Supabase: grants vêm antes das políticas, erro 42501 e service_role com bypassrls
  2. Supabase: tabela só é alcançável pela Data API com GRANT, e a mudança do padrão de grants automáticos
  3. PostgreSQL: row security policies existem além do sistema de GRANT, e default-deny sem política
  4. PostgreSQL: ALTER DEFAULT PRIVILEGES vale só para objetos criados depois
Pedro Henrique QuadroEngenheiro de software e IA. Constrói aplicativos, sistemas, painéis e agentes de IA em produção, e tem código aceito no Supabase, no Kestra e no QuestDB.

Continue lendo