"Segurança no Supabase" para uma app feita com IA são duas coisas diferentes. A primeira é a configuração do projeto: o que o Supabase expõe por desenho e o que te cabe fechar a ti. A segunda é o que o Lovable, o Bolt ou o Cursor fazem com o Supabase quando geram o código, que é de onde vêm quase todos os buracos. Este guia cobre as duas, com três consultas SQL para auditar o teu projeto num minuto, um prompt de revisão para a tua IA e a forma de verificar de fora que está tudo fechado.
Parte 1: como funciona a segurança no Supabase
A API de dados é pública por desenho
O Supabase põe à frente do teu Postgres uma API REST (PostgREST) que o navegador chama diretamente com a chave anon (ou a nova chave publishable). Essa chave vai no JavaScript de todos os teus utilizadores e isso é normal: identifica o projeto, não dá permissões. O que decide quem pode ler ou escrever cada linha é o Row Level Security. A outra chave, service_role (ou secret), salta o RLS por completo e só pode viver num servidor. Se percebes esta separação, percebes 80 % da segurança no Supabase.
RLS e políticas: a única barreira real
Uma tabela nova no esquema public fica acessível pela API assim que a crias. Sem RLS ativado, qualquer pessoa com a chave anon lê e escreve todas as linhas. Com RLS ativado e sem políticas, ninguém acede. Com políticas, acede quem elas disserem. Por isso a regra é: RLS em todas as tabelas e uma política explícita por operação (select, insert, update, delete). Os padrões concretos (cada utilizador as suas linhas, pertença a uma equipa, conteúdo público só de leitura) estão em o que rever no Supabase antes de lançar.
O que salta o RLS sem dares por isso
- Vistas. Por defeito uma vista executa-se com as permissões de quem a criou, ou seja, ignora o RLS das tabelas que consulta. Cria-as com
with (security_invoker = on). - Funções security definer. Uma função RPC marcada assim corre como o seu dono e lê o que quiser. Usa-as só quando for imprescindível, com
set search_path = ''e revogando a execução aanonse um visitante não a deve chamar. - Esquemas expostos. Tudo o que está em
public(tabelas, vistas e funções) é alcançável pela API. Se uma função é interna, passa-a para outro esquema. - A chave service_role. No frontend, num repositório público ou num prompt colado numa IA, é controlo total da base de dados.
Auth: definições que convém ver
Em Authentication tens a confirmação de email (deixa-a ativada), a lista de Redirect URLs permitidas (se estiver vazia ou com wildcards, um link mágico pode acabar num domínio alheio), os limites de pedidos, o captcha para registo e recuperação, a política de palavras-passe, a proteção contra palavras-passe expostas e o início de sessão anónimo (desativado a menos que o uses de propósito). Cada um é uma caixa; nenhum vem mal por defeito, mas a IA costuma mexer-lhes para que "os testes passem".
Storage, Realtime e Edge Functions
Os buckets públicos servem qualquer ficheiro a quem tiver o URL: para uploads de utilizadores usa buckets privados, políticas sobre storage.objects e URLs assinados com validade, além de limite de tamanho e tipos permitidos por bucket. No Realtime, as alterações de Postgres respeitam o RLS, mas os canais de broadcast e presença precisam das suas próprias políticas se levarem dados privados. Nas Edge Functions, os segredos vão com supabase secrets set, nunca no código, e se desativares a verificação de JWT para um webhook, valida a assinatura do remetente tu mesmo.
O projeto em si
A string de ligação direta ao Postgres (com a palavra-passe da base de dados) nunca deve sair do teu servidor nem das tuas variáveis de ambiente. Ativa a imposição de SSL, pondera as restrições de rede se tens plano pago, e revê de vez em quando o Security Advisor do painel (Database, Advisors): deteta tabelas sem RLS, vistas security definer, funções com search_path mutável e tabelas de auth expostas.
Parte 2: o que a IA faz mal com o Supabase
Estes são os erros que mais se repetem em apps geradas com Lovable, Bolt ou Cursor, por ordem de frequência:
- Criar tabelas sem RLS. O gerador faz a migração, a app funciona e ninguém ativa nada. É a origem da CVE-2025-48757 do Lovable, que expôs dados de centenas de projetos.
- Políticas
using (true). Quando o RLS bloqueia algo, a IA "resolve" com uma política que deixa passar tudo. É RLS desativado com outro nome. - service_role no navegador. Uma consulta falha por permissões e a solução rápida é trocar a chave. Procura
service_roleno teu bundle agora mesmo. - Funções security definer para contornar erros. Mesmo padrão: em vez de escrever a política correta, embrulha-se a consulta numa função que corre como superutilizador.
- Buckets públicos para ficheiros de utilizadores. Avatares, tudo bem; faturas, documentos ou exportações, não.
- Campos de privilégio editáveis pelo utilizador. Um
roleouis_adminna tabelaprofilescom uma política de update que não exclui essas colunas. Qualquer pessoa se torna administrador a partir da consola do navegador. - Desativar a confirmação de email ou os limites para os testes irem mais depressa, e esquecer isso em produção.
Três consultas SQL para auditar o teu projeto
Cola-as no editor SQL do painel. A primeira lista as tabelas públicas sem RLS; o ideal é que não devolva nada:
select n.nspname as schema, c.relname as table
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where c.relkind = 'r' and n.nspname = 'public' and not c.relrowsecurity;
A segunda encontra políticas que deixam passar tudo:
select tablename, policyname, cmd, roles
from pg_policies
where schemaname = 'public' and (qual = 'true' or with_check = 'true');
Uma política assim numa tabela de conteúdo público só de leitura pode estar certa; em profiles, orders ou messages, não. A terceira lista as funções que correm com permissões do seu dono:
select p.proname, pg_get_function_identity_arguments(p.oid) as args
from pg_proc p
join pg_namespace n on n.oid = p.pronamespace
where n.nspname = 'public' and p.prosecdef;
Cada resultado devia ter um motivo que consigas explicar. Se não tem, tira-lhe o security definer ou move-a para fora de public.
Um prompt de revisão antes de fazer deploy
Cola-o no Lovable, no Bolt ou no Cursor com acesso ao teu esquema e ao teu código:
Revê a segurança desta app com Supabase e não mudes nada ainda. Responde com uma lista:
1. Tabelas do esquema public sem RLS ativado.
2. Políticas com using (true) ou with check (true), e se estão justificadas.
3. Colunas de privilégio (role, is_admin, plan, credits) que um utilizador possa modificar com as políticas de update atuais.
4. Vistas sem security_invoker e funções security definer, com o motivo de cada uma.
5. Qualquer uso da chave service_role ou da string de ligação do Postgres fora do servidor.
6. Buckets públicos que contenham ficheiros de utilizadores.
7. Definições de Auth que tenham sido desativadas (confirmação de email, limites, captcha) e redirect URLs com wildcards.
Para cada ponto, propõe o SQL ou a alteração exata, e espera pela minha confirmação.
Verifica o resultado de fora
A prova definitiva não é o painel, é o que a API responde a um desconhecido. Com o URL do teu projeto e a chave anon (as duas estão no código do navegador, por isso um atacante também as tem):
curl -s "https://O_TEU_PROJETO.supabase.co/rest/v1/profiles?select=*" -H "apikey: A_TUA_CHAVE_ANON" -H "Authorization: Bearer A_TUA_CHAVE_ANON"
Se devolver linhas, essa tabela está aberta. Se devolver [] ou um erro de permissões, o RLS funciona. Repete com cada tabela sensível e com um utilizador normal autenticado para confirmar que só vê o que é dele. O Scanaris faz esta verificação por ti: deteta o URL e a chave do Supabase no teu bundle, testa as tabelas habituais sem sessão, procura a chave service_role e a string de ligação no JavaScript, revê onde a tua app guarda o token de sessão e dá-te um fix prompt por cada achado. Se a tua app foi gerada pelo Lovable, vê também o guia de segurança para Lovable; se o problema são as chaves no cliente, segredos que a tua app deixa fugir no navegador.
Perguntas frequentes
É um problema a chave anon estar no navegador?
Não. Foi desenhada para isso e só identifica o projeto. O que protege os dados é o RLS. A que nunca pode estar no navegador é a service_role (ou a nova chave secret).
As chaves novas publishable e secret mudam alguma coisa?
Mudam o formato e permitem rodá-las sem tocar no JWT, mas o modelo é o mesmo: a publishable é pública e passa pelo RLS; a secret salta-o e vive no servidor. As chaves antigas continuam a funcionar enquanto não as desativares.
Se a minha app foi feita pelo Lovable ou pelo Bolt, tenho de entrar na mesma no painel do Supabase?
Sim. A ferramenta gera o esquema, mas RLS, políticas, buckets e definições de Auth são teus. Dez minutos com as três consultas acima e o Security Advisor dizem-te em que estado está o projeto.