Pular para o conteúdo

Seu app do Supabase é seguro?

O Supabase pode guardar dados reais de usuários? Pode, e foi feito para ser consultado do navegador. É justamente por isso que duas configurações decidem tudo.

Vlad Tkachenko5 min de leitura
O cartão de verificação de segurança da Reeve para apps do Supabase, com o logo do Supabase num ladrilho branco.

Os logotipos são propriedade de seus respectivos donos e são exibidos apenas para indicar compatibilidade.

Em resumo

  • O Supabase pode guardar dados reais de usuários, e foi feito para ser consultado direto de um navegador. É justamente por isso que as regras das suas tabelas fazem todo o trabalho.
  • A chave publicável pertence ao seu app. sb_secret_… e service_role ignoram todas as regras que você escreveu.
  • Tabelas feitas no Table Editor ganham Row Level Security automaticamente. Tabelas criadas rodando SQL não.

O Supabase te dá um banco Postgres de verdade com uma API já na frente dele, e é por isso que um app consegue estar conversando com dados reais vinte minutos depois de você começar. O que surpreende as pessoas mais tarde é que essa mesma API é alcançável por qualquer um, de qualquer lugar, com credenciais impressas dentro do seu app.

Aqui está o que guia após guia erra: isso não é defeito, e esconder não é a correção. O Supabase foi projetado para ser consultado direto de um navegador. A proteção sempre foi para morar em outro lugar, e saber onde é quase tudo o que há para saber sobre tocar um app com Supabase em paz.

Por que o seu banco de dados está na internet de propósito

Porque o seu app fala com ele a partir do navegador do seu visitante, e não de um servidor que você mantém.

Pense numa loja. O seu app é a vitrine, entregue inteira a cada visitante. O seu banco de dados é o estoque, e no Supabase o estoque tem, de propósito, uma porta própria para a rua, para a sua vitrine se abastecer sem um serviço de entrega no meio. É essa porta que te poupa de escrever um backend.

O que significa que a porta é pública, e o endereço dela está no seu app porque o seu app precisa bater nela. Não existe arranjo em que esse endereço seja secreto. Então a pergunta nunca foi "estranhos conseguem chegar ao meu banco?". Conseguem, por projeto. A pergunta é o que ele entrega quando eles pedem.

A chave do Supabase no meu app deveria estar ali?

Uma delas sim. A outra nunca, e elas parecem quase idênticas.

As duas se chamam chave de API. Só uma delas foi feita para ser lida.

A chave publicável (sb_publishable_… nos projetos novos, anon nos antigos) diz a qual projeto um pedido pertence. Ela não carrega privilégio nenhum próprio, e é por isso que a própria documentação do Supabase a descreve como segura para empacotar em páginas web e apps de celular. Encontrá-la no seu app não é um achado.

A chave secreta, sb_secret_… ou a antiga service_role, é o oposto. Ela ignora as suas regras por completo e lê e escreve em toda linha de toda tabela, de qualquer lugar. O lugar dela é um servidor, uma edge function, um worker: nenhum lugar que um navegador alcance.

As duas ficam lado a lado na mesma página do painel e têm o mesmo formato. Se alguma já foi colada em código de frontend, troque a chave no painel antes de editar qualquer coisa, porque apagar a linha não fecha a porta.

Essa página é Settings e depois API Keys, e ela guarda quatro valores em vez de dois.

A configuração que decide o que a porta entrega

Row Level Security: uma chave por tabela que decide, linha a linha, quem pode ler o quê. É todo o motivo pelo qual uma chave publicável é segura.

O seu app e um estranho batem na mesma porta. A configuração decide quem entra, não as telas do seu app.

Com ela desligada, essa chave devolve a tabela inteira a qualquer um que peça. Com ela ligada e uma política escrita, o próprio banco filtra cada pedido, inclusive pedidos que nunca passaram pelo seu app.

Dois detalhes decidem se você realmente tem isso, e nenhum é visível de dentro do seu app:

Como a tabela foi criada. O Supabase liga o Row Level Security automaticamente nas tabelas feitas no Table Editor do painel. Tabelas criadas rodando SQL não ganham isso e precisam ser ligadas de propósito. Arquivos de migração, scripts e esquemas gerados por IA criam tabelas todos rodando SQL, então um projeto feito assim pode ter uma fileira de tabelas desprotegidas que parecem exatamente iguais às protegidas.

O que a política diz. Ligado quer dizer fechado até uma política abrir. Uma política que permite todo mundo abre de vez enquanto o painel continua informando que a tabela está com Row Level Security ligado, o estado que parece protegido e não está.

O armazenamento é separado de novo. Um bucket público é listável e baixável por qualquer um com o endereço do seu projeto, digam o que disserem as regras das suas tabelas.

Como conferir o seu próprio projeto do Supabase em uns dez minutos

Authentication → Policies. Leia a lista inteira. Qualquer tabela mostrando Row Level Security como desligado pode ser lida por qualquer um. Nas que mostram ligado, abra a política e leia o que ela de fato permite.

Settings → API. Confirme que a chave que o seu app entrega é a publicável. Se uma chave secreta já esteve em código de frontend, troque a chave aqui primeiro. Se o seu painel mostra chaves que começam com sb_publishable_ e sb_secret_ em vez de anon e service_role, esse é o formato mais novo, e a mesma regra decide qual das duas fica no seu app.

Storage → Buckets. Veja quais estão públicos e se era isso mesmo que você queria para os arquivos dentro deles.

E então olhe de fora. Tudo acima é o que o seu painel diz que está configurado. O que um pedido anônimo realmente recebe de volta é outra pergunta, e é a que o nosso scan gratuito responde: ele consulta do jeito que um estranho consultaria, sem ler nenhuma das suas linhas, e te dá uma nota em uns 20 segundos: veja o que o seu app expõe.

O que fazer

  • A sua API do Supabase é pública de propósito. A proteção são as regras nas suas tabelas, nunca o sigilo do endereço.
  • A chave publicável pertence ao seu app. sb_secret_… e service_role nunca, e uma vazada ignora todas as regras que você escreveu.
  • Tabelas feitas no Table Editor ganham Row Level Security automaticamente. Tabelas criadas rodando SQL não.
  • Ligado não é restrito. Leia a política, não só a chave.
  • Buckets de armazenamento têm configurações próprias, então tabelas trancadas não dizem nada sobre os seus arquivos.

Abra Authentication → Policies e leia a lista uma vez de cima a baixo. As tabelas marcadas como desligado são por onde começar, e se você preferir percorrer toda a superfície em forma de lista, o checklist de segurança de 10 minutos é a versão curta. Ache o que achar, saber que você consegue colocar os dados de volta importa tanto quanto trancá-los, e isso depende inteiramente de o que você está copiando.

Se você prefere que isso não dependa de lembrar, veja como funcionam os backups automáticos do Supabase.

O que pode dar errado de verdade com o Supabase

Nada disso significa que você fez algo errado: são as brechas habituais quando se está indo rápido. Aqui está o que vale a conferida:

  • Row Level Security (RLS) desligada

    O RLS é a regra do Supabase sobre quem pode ler ou alterar cada linha de uma tabela. Com a chave pública “anon” (que deve estar no seu app), qualquer um pode consultar seu banco de dados diretamente. O RLS é o que o impede de ver linhas que não são dele. Se estiver desligada, uma tabela pode ser totalmente legível, ou até editável, por qualquer um. É a configuração mais importante do Supabase, e o Reeve a verifica contando linhas, nunca lendo-as.

  • Uma chave service_role vazada

    O Supabase te dá duas chaves. A chave “anon” é pública por design e segura no navegador. A chave “service_role” ignora todas as suas regras de segurança e deve viver apenas em um servidor. Se ela algum dia acabar no código do front-end do seu app, alguém pode fazer qualquer coisa com seus dados. O Reeve decodifica as chaves que encontra e diz exatamente qual está exposta: a “anon” segura recebe um tique verde, a “service_role” é um alerta vermelho.

  • Buckets de armazenamento públicos

    Os arquivos que as pessoas enviam vivem em “buckets” do Supabase Storage. Um bucket definido como público significa que qualquer um pode listar e baixar o que há dentro, então envios privados podem se tornar visíveis para todos. O Reeve verifica se os seus buckets são listáveis; ele nunca baixa os arquivos de ninguém.

  • Sua API aberta a qualquer site (CORS)

    O Supabase dá ao seu banco de dados um endereço web (via PostgREST). Combinado com uma configuração de compartilhamento permissiva demais, outro site poderia chamar seus dados a partir do navegador de um visitante. O Reeve verifica se os seus endpoints respondem a estranhos e a outros sites, sem nunca usá-los para mudar nada.

  • Tabelas e endpoints expostos sem regras

    Cada tabela que você cria é acessível pela API do Supabase; isso é por design, e o RLS deve protegê-la. Mas uma tabela nova adicionada com pressa, antes de suas regras serem definidas, pode ficar brevemente (ou de forma duradoura) aberta. O Reeve verifica o que está de fato acessível por fora.

  • Chaves ou configuração deixadas no app implantado

    Além das próprias chaves do Supabase, os apps muitas vezes carregam outras configurações e segredos no código front-end ou em um .env exposto. O Reeve lê o código carregado do seu app e verifica se há arquivos de configuração acessíveis, depois diz quais valores são seguros de tornar públicos e quais não são.

O que o Reeve é, e o que não é

O Reeve é uma verificação gratuita, somente leitura e por fora, como um inspetor que testa as portas sem entrar. É rápida e pega os erros comuns de grande impacto. Não é uma auditoria de segurança completa, e uma nota limpa não é garantia. Significa que as portas óbvias estão fechadas. Tudo o que o Reeve faz é passivo: ele conta linhas em vez de lê-las, e nunca baixa seus arquivos.

O Supabase te dá ferramentas de segurança reais (um Security Advisor e um linter de banco de dados que sinalizam problemas de RLS e de exposição bem no painel), e vale muito a pena usá-los. O que o Reeve acrescenta: a maioria de quem constrói no Supabase vive no seu criador de apps, não no editor SQL, e o advisor fala em linguagem de desenvolvedor. O Reeve verifica o seu app inteiro por fora, do jeito que um invasor chegaria aos seus dados, e explica o que encontra em palavras simples. E se você preferir não monitorar você mesmo, nós podemos.

Quer que seja resolvido, não só verificado?

O Reeve Care continua monitorando seu app, faz backup dos seus dados e ajuda você a corrigir as coisas quando quebram, para que você siga criando em vez de se preocupar.

Conheça o Reeve Care

Perguntas frequentes

O que é o RLS do Supabase e eu realmente preciso dele?

O RLS (Row Level Security) decide quem pode ver ou alterar cada linha das suas tabelas. Como o seu app envia uma chave pública “anon” capaz de consultar o banco de dados diretamente, o RLS é o que impede que os dados de um usuário fiquem visíveis para todos. Sim: para qualquer tabela com dados reais, você precisa dele ligado. O Reeve verifica se está, sem ler seus dados.

A chave anon do Supabase é segura de expor?

Sim. A chave “anon” foi feita para viver no seu front-end, e sozinha ela só faz o que suas regras de RLS permitem. A chave que nunca deve ser exposta é a “service_role”, que ignora todas as regras. O Reeve decodifica as chaves do seu app e diz qual é qual.

O que acontece se a minha chave service_role vazar?

A chave service_role ignora todas as regras de segurança, então quem a tiver pode ler, alterar ou apagar todos os seus dados. Se ela está no seu código front-end, trate-a como comprometida: rotacione-a no painel do Supabase e mova-a para um servidor. O Reeve sinaliza uma chave service_role exposta como um problema crítico.

O Reeve pode verificar meu Supabase sem a senha do meu banco de dados?

Sim. O Reeve só usa o que o seu app já expõe publicamente: a mesma chave anon e os mesmos endpoints que o navegador de qualquer visitante usa. Ele nunca precisa da senha do seu banco de dados, nunca faz login como administrador e nunca lê nem baixa suas linhas ou arquivos.

Por que o Supabase deixa um navegador falar com o meu banco de dados?

Porque esse é o produto. O Supabase coloca uma API na frente do Postgres para o seu app consultar dados sem que você escreva e hospede um backend, e é isso que torna a construção tão rápida. A segurança não vem de esconder essa API, que não dá para esconder. Vem do Postgres se recusando a devolver linhas a que o visitante não tem direito.

Ligar o Row Level Security numa tabela a torna privada?

Torna a tabela fechada até você escrever uma política, o que é o ponto de partida seguro. Mas uma política que permite todo mundo reabre a tabela enquanto o painel continua mostrando o Row Level Security como ligado. Ligado e restrito são dois estados diferentes, e só um deles se vê de relance.

Quais tabelas têm mais chance de estar desprotegidas?

As criadas rodando SQL em vez de clicando no Table Editor: arquivos de migração, scripts e esquemas escritos para você por um builder de IA. O Table Editor liga o Row Level Security automaticamente. O SQL não, e depois nada aponta a diferença.

O meu armazenamento no Supabase é coberto pelo Row Level Security?

O armazenamento tem configurações próprias e separadas, então um banco bem trancado não diz nada sobre os seus arquivos. Um bucket marcado como público pode ser listado e baixado por qualquer um que saiba o endereço do seu projeto, com ou sem link do seu app para o que há dentro.

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

Criou seu app em uma ferramenta específica? Aqui está o mesmo resumo honesto para:

← Ver todos os guias de segurança dos criadores

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