O Supabase dá-te uma base de dados e uma API em minutos, e é precisamente esse o perigo: a API fica exposta desde o primeiro momento. Se não configurares a segurança, qualquer pessoa com a tua chave pública pode ler e escrever nas tuas tabelas. Este guia percorre, pela ordem em que convém fazê-lo, tudo o que tens de rever antes de abrir a tua app ao mundo: RLS e políticas com SQL de exemplo, que chave vai onde, Storage, vistas, a configuração de Auth e como confirmar no fim que está mesmo fechado.
Como o Supabase expõe os teus dados (e porque isso é bom)
À frente do teu Postgres há uma camada (PostgREST) que transforma cada tabela do esquema public num endpoint HTTP. A tua app no browser fala diretamente com ela usando uma chave pública. Isso poupa-te escrever um backend, mas significa que não há nenhum servidor teu pelo meio a verificar permissões: as políticas de Row Level Security são o teu backend. Se estiverem bem, o desenho é sólido; se faltarem, a tabela inteira está à distância de um URL.
1. RLS ativado em TODAS as tabelas
O Row Level Security é a barreira principal do Supabase. Sem ele, a chave pública consegue consultar tabelas inteiras a partir do browser. O erro clássico do vibe coder é criar uma tabela nova à pressa, ou deixar que a IA a crie, e esquecer-se de ativar o RLS. Foi exatamente a causa da vulnerabilidade do Lovable (CVE-2025-48757).
Revê tabela a tabela no Table Editor: as que aparecem com "RLS disabled" estão abertas ao público. Para não dependeres do olho, esta consulta no SQL Editor lista as que faltam:
select c.relname
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public' and c.relkind = 'r' and not c.relrowsecurity;
E para cada uma:
alter table public.nome_da_tabela enable row level security;
Ativar não chega: uma tabela com RLS ativo mas sem políticas nega tudo (a tua app deixa de funcionar até as escreveres), e uma com uma política demasiado permissiva não protege nada.
2. Políticas que refletem mesmo quem pode fazer o quê
Uma política using (true) para toda a gente deixa a tabela aberta mesmo com o RLS ativo; a IA sugere-as muitas vezes "para funcionar". Escreve políticas concretas e separa as regras por operação, porque às vezes queres permitir ler mas não apagar.
Padrão A: cada utilizador as suas linhas
create policy "notes: ler próprias" on public.notes
for select to authenticated
using ((select auth.uid()) = user_id);
create policy "notes: criar próprias" on public.notes
for insert to authenticated
with check ((select auth.uid()) = user_id);
create policy "notes: editar próprias" on public.notes
for update to authenticated
using ((select auth.uid()) = user_id)
with check ((select auth.uid()) = user_id);
O with check é o que impede que alguém insira ou edite uma linha em nome de outro utilizador. Envolver auth.uid() em (select …) faz com que o Postgres o avalie uma vez por consulta e não uma vez por linha; em tabelas grandes a diferença é enorme. Põe um índice em user_id.
Padrão B: pertença a uma equipa ou organização
create policy "projects: membros da equipa" on public.projects
for select to authenticated
using (
exists (
select 1 from public.team_members m
where m.team_id = projects.team_id
and m.user_id = (select auth.uid())
)
);
A tabela team_members também precisa das suas políticas, e convém que só um administrador possa inserir nela.
Padrão C: conteúdo público só de leitura
create policy "posts: leitura pública" on public.posts
for select to anon, authenticated
using (published = true);
É legítimo. O que não pode existir é uma política de escrita aberta nem colunas com dados pessoais numa tabela de leitura pública.
3. anon, service_role e as chaves novas
A chave anon é pública e vai no frontend: foi pensada para ser governada pelo RLS. A chave service_role salta o RLS por completo e tem acesso total. Nunca, mas nunca, pode aparecer no browser nem numa variável NEXT_PUBLIC_, VITE_ ou NUXT_PUBLIC_: esse prefixo significa "isto é enviado para o cliente".
O Supabase introduziu ainda um par de chaves novas com nomes que não deixam dúvidas: sb_publishable_… (para o browser, sujeita ao RLS) e sb_secret_… (só servidor, salta o RLS, revogável em separado). Se começas um projeto hoje, usa-as; se tens um antigo, migra quando puderes. Usa a secreta só no servidor, em Edge Functions ou em backends de confiança, e se suspeitares que fugiu, roda-a de imediato a partir do painel.
4. Storage: os buckets também têm regras
O armazenamento de ficheiros usa as suas próprias políticas, sobre a tabela storage.objects. Um bucket público serve qualquer ficheiro por URL a quem o tiver, para sempre. Decide de forma consciente que buckets são públicos (avatares, imagens de produto) e protege o resto. Um exemplo para que cada utilizador só leia e carregue dentro da sua própria pasta:
create policy "documentos: pasta própria" on storage.objects
for all to authenticated
using (
bucket_id = 'documents'
and (storage.foldername(name))[1] = (select auth.uid())::text
)
with check (
bucket_id = 'documents'
and (storage.foldername(name))[1] = (select auth.uid())::text
);
Carrega documentos privados para buckets privados e serve-os com URLs assinados de curta duração, não para um bucket público com nomes difíceis de adivinhar.
5. Vistas, funções e esquemas expostos
Três sítios por onde os dados fogem mesmo com as tabelas bem configuradas:
- Vistas. No Postgres uma vista corre com as permissões de quem a criou, por isso uma vista sobre uma tabela protegida pode devolver linhas que as políticas proibiriam. Cria-as com
with (security_invoker = true)(Postgres 15 ou superior) ou revê cada uma. - Funções RPC. Uma função
security definercorre com as permissões do dono. Usa-a só quando precisares mesmo, valida os parâmetros lá dentro e limita quem a pode executar comrevoke execute … from anon. - Esquemas expostos. Em Settings → API escolhes que esquemas a API publica. O que não tiver de sair por HTTP não deve estar nessa lista.
6. Configuração de Auth que convém rever
- Confirmação de email ativa, para que ninguém registe contas com endereços de outras pessoas.
- Limites de tentativas de login e de envio de emails: vêm ativados; confirma que ninguém os subiu "para testar".
- URLs de redirecionamento permitidos só para os teus domínios; uma lista aberta permite usar o teu login para redirecionar para sites de phishing.
- Fornecedores OAuth: só os que usas, com o ecrã de consentimento em nome da tua marca.
- Validade dos magic links e dos OTP curta; o valor por defeito é razoável, não o alargues.
7. Segredos e chaves fora do frontend
Além da chave secreta do Supabase, confirma que não há chaves de terceiros (Stripe, email, IA) embutidas no JavaScript do cliente. A IA às vezes coloca-as lá para pôr algo a funcionar depressa, e ficam visíveis para sempre no bundle. As chamadas a serviços pagos fazem-se a partir de uma Edge Function ou do teu servidor, nunca a partir do browser. Vê os segredos que a tua app deixa fugir no browser.
8. Ambientes: desenvolvimento não é produção
Usa um projeto Supabase diferente para desenvolvimento, com dados inventados. Um projeto de testes com uma cópia dos dados reais e políticas a meio está tão exposto como a produção, e ninguém o vigia.
Como confirmar que está fechado (o teste final)
- Teste anónimo. Com a app aberta, copia em F12 → Network uma chamada a
/rest/v1/e abre-a sem sessão. Tem de devolver uma lista vazia ou um erro. - Teste de "pedir a mais". Com sessão, tira o filtro ao URL (
?user_id=eq.…). Só devem voltar as tuas linhas. - Teste de duas contas. Cria dois utilizadores e confirma que nenhum vê, edita ou apaga o do outro, ficheiros do Storage incluídos.
- Security Advisor. No painel, a secção Advisors lista tabelas sem RLS, políticas permissivas e funções expostas. Mantém-na a verde.
O Scanaris revê o teu Supabase por ti
O Scanaris inclui uma auditoria opcional do Supabase que, com uma connection string só de leitura, verifica tabela a tabela se o RLS está ativo e se há políticas, e na análise normal deteta chaves expostas no frontend, CORS permissivo e endpoints que servem dados sem sessão. Cada resultado traz um prompt pronto a colar na tua IA. Corre-o antes de lançar e ficas a saber se deixaste alguma porta aberta; ativa a monitorização e avisa-te se a IA a voltar a abrir numa tabela nova.
Perguntas frequentes
Posso saltar o RLS pondo um backend meu à frente?
Podes, usando só a chave secreta a partir do teu servidor e nunca a pública no cliente. Mas então o teu backend tem de fazer todas as verificações de permissões que o RLS faria. Para a maioria das apps, o RLS dá menos trabalho e é mais difícil de partir.
O RLS afeta o desempenho?
Pouco, se as políticas estiverem bem escritas: (select auth.uid()) em vez de auth.uid(), índices nas colunas que as políticas usam e sem subconsultas pesadas. Uma política mal escrita pode, sim, tornar lenta uma tabela grande.
E as tabelas que a IA cria depois do lançamento?
Nascem sem RLS. Acrescenta à tua rotina (ou ao prompt da tua IA) "ativa o RLS e escreve as políticas" sempre que aparecer uma tabela nova, e usa uma nova análise automática para saberes se alguém se esqueceu.