A regra que ninguém escreveu: lendo uma tabela do Supabase sem Row Level Security
O desafio
Um perfil pede duas colunas ao Supabase. Peça todas as colunas de todas as linhas. Traga a chave de API do admin.
O que você vai aprender
- Ler uma URL do PostgREST: tabela, lista select, filtros
- Explicar por que a chave anon do Supabase é pública e o que de fato protege os dados
- Ampliar uma consulta de duas colunas de uma linha para a tabela inteira
- Distinguir um nome de exibição escolhido pelo usuário de um papel definido no servidor
- Proteger uma tabela do Supabase com RLS, uma view pública e permissões por coluna
Habilidades testadas
Pré-requisitos
- O que é uma query string
- O que são uma tabela de banco de dados e suas colunas
Como funciona
O Supabase coloca uma API REST (PostgREST) na frente do Postgres. O navegador fala com ela diretamente: a URL indica a tabela (/rest/v1/profiles), as colunas (select=display_name,avatar_url) e as linhas (id=eq.<uuid>). Toda requisição leva a chave anon do projeto, um JWT com "role":"anon" que vai no JavaScript do app.
Essa chave é pública por design. O que protege os dados é o Row Level Security: políticas escritas no Postgres que decidem, por papel, quais linhas uma consulta pode ver. Com RLS desativado em uma tabela que o papel anon pode ler, a URL é o único limite, e quem escreve a URL é o navegador. A página pedir duas colunas de uma linha é uma convenção, não um controle.
Em 2025, pesquisadores descobriram que apps gerados com o Lovable, um construtor de apps com IA, muitas vezes saíam com tabelas sem RLS (CVE-2025-48757): qualquer pessoa com a chave pública podia listar usuários, e-mails e chaves de API armazenadas.
Erros comuns
- Parar em select=*. Com o filtro
id=eq.ainda presente, você recebe todas as colunas só da linha do Sam, e o Sam é um membro. Apague o filtro para ver as outras linhas. - Pegar a linha chamada Admin.
display_nameé o que o usuário digitou no cadastro. Essa linha tem"role":"member"; o admin é a linha com"role":"admin". - Responder com a chave do Sam. É a primeira chave que aparece, mas pertence ao perfil que está na tela.
- Forjar um token service_role. Editar o JWT quebra a assinatura HS256, e o PostgREST responde
PGRST301. Não é preciso forjar nada: a chave anon já lê tudo. - Remover o header apikey. O gateway do Supabase recusa a requisição antes de o PostgREST vê-la.
Como se proteger
- Ative o RLS em toda tabela de um schema exposto:
alter table profiles enable row level security;O Security Advisor do Supabase aponta as tabelas em que ele está desativado. - Escreva políticas explícitas, por exemplo, cada dono lê a própria linha:
create policy "own row" on profiles for select to authenticated using (auth.uid() = id); - Exponha os campos públicos por uma view ou uma função que devolva apenas
display_nameeavatar_url, ou revogue os privilégios de coluna do anon. - Mantenha segredos fora de tabelas legíveis pelo navegador: guarde as chaves de API com hash, em um schema que a Data API não exponha, e rotacione qualquer chave que já tenha sido legível.
- Nunca decida privilégios a partir de campos editáveis pelo usuário, como um nome de exibição.