← Voltar ao blog
SegurançaScanaris Team · 7 min de leitura · October 03, 2026

Segurança no Supabase: o que tens de rever antes de lançar

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 definer corre com as permissões do dono. Usa-a só quando precisares mesmo, valida os parâmetros lá dentro e limita quem a pode executar com revoke 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.

Como está o teu site?

Passa-lhe o Scanaris e verifica em 30 segundos, grátis.

Analisar o meu site

Verificações relacionadas

Guias de segurança por stack