Encontrar a URL de um projeto Supabase e sua chave pública dentro do JavaScript do frontend não é, por si só, uma falha. Essas informações precisam chegar ao navegador para que a aplicação use a Data API. O problema começa quando essa chave pública encontra tabelas com permissões amplas e sem uma política de Row Level Security capaz de separar o que cada usuário pode acessar.
Um levantamento publicado pela UpGuard Research mostra o tamanho desse espaço para erro. A empresa reuniu cerca de 300 mil domínios com sinais de uso do Supabase e identificou 16.326 bancos com ao menos uma tabela legível publicamente. Mais da metade tinha indicadores de informações pessoais no schema. Em uma amostra menor, os pesquisadores confirmaram registros reais e avisaram os responsáveis pelas exposições mais relevantes.
O número não descreve uma invasão ao Supabase
Vale separar vulnerabilidade da plataforma, credencial vazada e configuração insegura. O estudo não afirma que alguém rompeu a infraestrutura do Supabase nem que 16 mil projetos tiveram todos os dados roubados. Ele encontrou bancos em que uma requisição feita pela interface pública conseguia ler alguma tabela porque as regras do próprio projeto permitiam.
A metodologia também ajuda a dimensionar corretamente o resultado. Os pesquisadores começaram procurando uma tabela chamada users. Quando ela não existia, respostas do PostgREST às vezes indicavam o nome de outra tabela acessível. Em grande parte do conjunto, a classificação dos dados foi feita pelos nomes e tipos das colunas, não pela leitura de todas as linhas. Portanto, 16.326 é uma contagem de bancos com tabelas legíveis, não uma contagem de pessoas afetadas ou de incidentes explorados por criminosos.
Mesmo com essa ressalva, o resultado é ruim. O pesquisador não precisou quebrar criptografia ou roubar uma senha de administrador. Bastou usar a superfície que o aplicativo já expunha para o navegador. Se um teste amplo encontra milhares de respostas, outras pessoas também conseguem automatizar a mesma procura.
A chave pública identifica o projeto, mas não decide o que pode ser lido
O Supabase gera uma API REST diretamente a partir do schema do PostgreSQL. No navegador, uma requisição sem usuário autenticado assume o papel anon. Depois do login, ela normalmente assume authenticated. A chave publicável participa da conexão com o projeto, mas a autorização efetiva depende dos controles aplicados dentro do banco.
Existem duas camadas que trabalham juntas. Os grants do PostgreSQL determinam se um papel pode executar SELECT, INSERT, UPDATE ou DELETE em uma tabela. As políticas de Row Level Security, o RLS, determinam quais linhas esse papel pode alcançar. Um grant amplo sem RLS abre a tabela inteira. Uma política cuidadosa não corrige automaticamente operações que ainda foram concedidas sem necessidade.
- Chave publicável: foi feita para aparecer no cliente e não deve carregar privilégios administrativos.
- Grant: libera uma operação para um papel do PostgreSQL.
- Política RLS: filtra as linhas que aquela operação pode ler ou alterar.
- Chave de serviço: ignora RLS e deve permanecer exclusivamente em código confiável no servidor.
Trocar o nome da variável, esconder a chave publicável durante o build ou dificultar a leitura do bundle não fecha essa porta. O navegador precisa enviar a credencial e qualquer pessoa consegue observar a requisição. A restrição precisa existir onde os dados são servidos.
Por que tabelas criadas por SQL merecem atenção extra
Segundo a documentação atual do Supabase, o Table Editor habilita RLS quando cria uma tabela pelo painel. Uma tabela criada no SQL Editor, por uma migration ou por outra ferramenta precisa habilitá-lo explicitamente. É justamente o caminho usado por boa parte dos projetos versionados e por agentes que geram migrations.
Em projetos existentes, novos objetos no schema public também podem herdar grants para anon, authenticated e service_role. O Supabase está mudando esse padrão para que a exposição seja opt-in, mas uma alteração de default não volta no tempo para revisar tabelas já criadas. O histórico do projeto continua sendo responsabilidade de quem o mantém.
Esse detalhe explica por que uma interface funcionando não prova que o backend está seguro. O frontend pode esconder um botão, filtrar registros pelo usuário atual e nunca oferecer uma tela para consultar dados de terceiros. Ainda assim, se a API aceita uma consulta mais ampla, basta ignorar a interface e chamar o endpoint diretamente.
Uma migration segura precisa reunir grants e políticas
O melhor momento para definir acesso é na mesma migration que cria ou altera a tabela. Assim, o banco não passa por um intervalo em que o schema já está disponível, mas a proteção depende de uma etapa manual no painel.
alter table public.profiles enable row level security;
revoke all on table public.profiles from anon;
revoke all on table public.profiles from authenticated;
grant select, update on table public.profiles to authenticated;
create policy "users_read_own_profile"
on public.profiles
for select
to authenticated
using ((select auth.uid()) = user_id);
create policy "users_update_own_profile"
on public.profiles
for update
to authenticated
using ((select auth.uid()) = user_id)
with check ((select auth.uid()) = user_id);
O exemplo não é uma política universal. Uma tabela de catálogo pode precisar de leitura pública, enquanto dados de cobrança talvez nem devam fazer parte do schema exposto pela Data API. O ponto é declarar cada operação e testar tanto o acesso permitido quanto o negado. Uma policy com using (true) é acesso total às linhas para os papéis indicados, não um atalho neutro.
Como revisar um projeto que já está no ar
A primeira auditoria pode começar pelo próprio catálogo do PostgreSQL. O objetivo é descobrir quais tabelas estão no schema público, se o RLS está ativo e quais políticas realmente existem.
select tablename, rowsecurity
from pg_tables
where schemaname = 'public'
order by tablename;
select tablename, policyname, cmd, roles
from pg_policies
where schemaname = 'public'
order by tablename, policyname;
Depois, é preciso revisar os grants, as views e as funções disponíveis pela API. Views merecem cuidado porque podem usar as permissões de quem as criou. Funções com SECURITY DEFINER também podem executar com privilégios elevados. Se o aplicativo não usa a Data API, desligá-la remove uma superfície desnecessária. Se usa, um schema dedicado para a API deixa mais claro o que foi feito para exposição externa.
- teste as rotas com uma chave pública e sem sessão;
- repita os testes como dois usuários diferentes para procurar vazamentos entre contas;
- verifique leitura, criação, alteração e exclusão, não apenas
SELECT; - mantenha a chave de serviço fora do bundle, de logs e de variáveis entregues ao navegador;
- inclua testes de RLS no CI sempre que uma migration mudar o schema ou as políticas.
A conclusão desconfortável do levantamento é simples: autenticação funcionando e telas escondendo dados não garantem isolamento. O Supabase permite falar com o PostgreSQL diretamente do frontend, e essa conveniência pode ser segura quando o banco conhece as regras de cada usuário. Quando a autorização fica só na interface, a chave pública vira o caminho mais curto até uma decisão que o backend nunca tomou.

