A regra que ninguém escreveu: lendo uma tabela do Supabase sem Row Level Security

Segurança Web & API Nível 3/4 ~5 min 8 de novembro de 2026

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

Teste de APIBroken access controlAdulteração de requisições

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_name e avatar_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.

Solução completa

Membros Pro e Max desbloqueiam o passo a passo completo.

Assinar Pro

Estatísticas da comunidade

132 resoluções
71% taxa de sucesso
M2F14M3 Primeiro sangue

Hacks de hoje relacionados

38.000+ Hackers Labs reais Grátis
Comece Grátis ou resolva o hack de hoje, sem conta