Todos os guias
Severidade CríticaSegurança de banco de dados

Row Level Security (RLS) no Supabase: proteja suas tabelas

O Supabase expõe suas tabelas do Postgres diretamente ao navegador pela Data API, autenticada com a anon key (publishable) pública. As políticas de Row Level Security (RLS, segurança em nível de linha) é que decidem quais linhas cada usuário pode ler ou alterar. Uma tabela no schema public sem RLS pode ser lida — e muitas vezes alterada — por qualquer pessoa que abra o seu site.

O que é

Todo projeto do Supabase vem com uma anon key (ou sb_publishable_) feita para ficar no seu frontend. Ela identifica o seu projeto, não o seu usuário — qualquer um pode copiá-la do seu bundle de JavaScript e enviar requisições por conta própria para https://<project>.supabase.co/rest/v1/, contornando totalmente a sua interface.

Row Level Security é um recurso do Postgres que associa políticas a uma tabela: condições SQL verificadas para cada linha em todo SELECT, INSERT, UPDATE e DELETE. Com o RLS ativado e sem políticas, nada fica acessível pela API. Cada política que você adiciona abre exatamente uma porta — por exemplo, "usuários logados podem ler as linhas cujo user_id corresponde ao próprio id".

Por que importa

Uma política ausente ou ampla demais é um vazamento de dados completo, não um bug menor: e-mails de usuários, perfis, pedidos, mensagens privadas — o que estiver na tabela — podem ser despejados com um único comando curl. Ferramentas de programação com IA costumam criar tabelas via migrações SQL, onde o RLS vem desativado por padrão, e geram políticas provisórias permissivas como using (true) só para o app funcionar.

A service_role key (sb_secret_) ignora o RLS por completo. Se ela for parar no código do cliente, todas as políticas que você escreveu deixam de importar — quem a tiver ganha acesso total de leitura e escrita ao seu banco de dados.

Como corrigir

Encontre as tabelas sem RLS

Execute isto no editor SQL do Supabase para listar todas as tabelas do schema public exposto que ainda estão com o RLS desativado. O Security Advisor do dashboard reporta o mesmo problema como "RLS disabled in public".

select tablename
from pg_tables
where schemaname = 'public'
  and rowsecurity = false;
Ative o RLS e escreva políticas só para o dono

Ative o RLS em cada tabela e depois adicione uma política por operação. Restrinja as políticas ao papel authenticated e compare a coluna de dono da linha com auth.uid(). INSERTs e UPDATEs precisam de with check para que os usuários não consigam gravar linhas que pertencem a outra pessoa.

alter table public.profiles enable row level security;

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);
Evite os erros de política mais comuns

Troque políticas provisórias como using (true) por condições reais. Não baseie a autorização em user_metadata — os próprios usuários podem editá-lo; use app_metadata ou uma tabela de papéis. Views ignoram o RLS a menos que sejam criadas com security_invoker = true, e os buckets de storage precisam de políticas próprias em storage.objects.

Teste de fora com a anon key

Consulte sua API exatamente como um atacante faria: só com a chave pública e sem sessão de usuário. Se isto retornar linhas que não deveriam ser públicas, suas políticas estão abertas demais.

curl "https://<project>.supabase.co/rest/v1/profiles?select=*" \
  -H "apikey: <anon-key>" \
  -H "Authorization: Bearer <anon-key>"
Mantenha a service_role key no servidor

Use a service_role key (sb_secret_) apenas em código de servidor, como rotas de API ou Edge Functions, nunca atrás de um prefixo NEXT_PUBLIC_ ou VITE_. Se ela já foi enviada ao navegador alguma vez, rotacione-a no dashboard.

FAQ

É seguro a minha anon key do Supabase ficar visível no navegador?

Sim — a anon key (publishable) foi feita para ser pública, mas só se toda tabela exposta tiver o RLS ativado com políticas corretas. A chave em si não concede nada que as suas políticas não permitam.

Ativei o RLS e agora meu app retorna resultados vazios. Quebrou?

Esse é o RLS funcionando como deveria: sem políticas, todas as linhas são negadas. Adicione políticas para as operações de que o seu app precisa, restritas aos usuários certos, em vez de desativar o RLS de novo.

O AppSafe testa minhas políticas de RLS?

O AppSafe não faz login no seu banco de dados, então não consegue avaliar suas políticas — use as verificações com SQL e curl acima. Mas ele varre o seu JavaScript em busca de chaves do Supabase. Se alguma for reportada, decodifique a parte do meio do JWT e confira o claim role: anon é o esperado no navegador; service_role é um vazamento crítico.

Seu app está afetado?

O AppSafe verifica isso e dezenas de outros problemas em um único escaneamento grátis.

Escanear meu app grátis