checklist · supabase · rls

Checklist de segurança do Supabase RLS para apps criados com IA

Row Level Security (RLS) é a camada que decide quem enxerga cada linha do seu banco. Em projetos de vibe coding — feitos com Cursor, Lovable, Bolt ou v0 — ela costuma ficar desligada ou permissiva demais, porque o app “funciona” do mesmo jeito. Este é o checklist de secure code review que aplicamos em cada auditoria.

1. RLS habilitado em toda tabela pública

Qualquer tabela no schema public exposta pela Data API é legível pela internet quando o RLS está desligado. Comece listando o que está exposto e ligue o RLS.

alter table public.leads enable row level security;

-- auditoria: tabelas públicas sem RLS
select tablename
from pg_tables t
where schemaname = 'public'
  and not exists (
    select 1 from pg_class c
    where c.relname = t.tablename and c.relrowsecurity
  );

RLS ligado sem nenhuma policy bloqueia tudo — isso é seguro. O perigoso é o contrário: policy larga com RLS ligado dá falsa sensação de proteção.

2. Elimine o USING (true)

A armadilha mais comum. O assistente cria uma policy que “resolve o erro de permissão” e libera a tabela inteira:

-- vulnerável: qualquer visitante lê todos os leads
create policy "leads_select" on public.leads
for select using (true);

-- corrigido: apenas o dono da linha
create policy "leads_select_own" on public.leads
for select to authenticated
using (auth.uid() = user_id);

Regra prática: toda policy de select, update e delete deve referenciar auth.uid() ou uma função de papel. Se não referencia nada, é candidata a vazamento.

3. Confira os GRANTs, não só as policies

RLS e privilégios são coisas diferentes. Sem grant, o PostgREST devolve erro de permissão; com grant amplo demais e policy fraca, você entrega a tabela. Conceda o mínimo por papel.

grant select, insert, update, delete on public.leads to authenticated;
grant all on public.leads to service_role;
-- grant select ... to anon;  -- apenas se houver policy pública de leitura

4. Papéis fora da tabela de perfis

Guardar role em profiles permite que o próprio usuário se promova a admin via update. Papéis vivem em tabela separada, lidos por função security definer.

create table public.user_roles (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users(id) on delete cascade not null,
  role app_role not null,
  unique (user_id, role)
);

create or replace function public.has_role(_user_id uuid, _role app_role)
returns boolean language sql stable security definer set search_path = public
as $$ select exists (select 1 from public.user_roles
  where user_id = _user_id and role = _role) $$;

create policy "admins_read_leads" on public.leads
for select to authenticated using (public.has_role(auth.uid(), 'admin'));

5. Service role key nunca no cliente

A service role key ignora RLS por definição. Ela só pode existir em código de servidor. Procure por ela em variáveis VITE_*, em componentes React, em repositórios públicos e em logs — é onde ela costuma aparecer em projetos gerados por IA. Chaves anon / publishable são públicas por design e podem ficar no bundle, desde que o RLS esteja correto.

6. Teste as policies como um atacante

Policy não revisada é policy não testada. Use o cliente com a chave pública e tente ler dados de outro usuário; depois repita autenticado com uma conta comum.

-- simule um usuário específico no SQL editor
set local role authenticated;
set local request.jwt.claims = '{"sub":"<uuid-de-outro-usuario>"}';
select * from public.leads;  -- deve voltar vazio

Repita para insert, update e delete: muita aplicação protege a leitura e esquece que qualquer visitante pode apagar linhas.

7. Storage, funções e realtime entram na conta

Buckets públicos, funções de borda sem verificação de sessão e canais realtime abertos expõem os mesmos dados que a policy da tabela protegia. Revise: bucket público só para asset realmente público, função de servidor validando o token antes de tocar em dados, e publicação realtime restrita às tabelas necessárias.

Checklist final de revisão

  • RLS habilitado em todas as tabelas do schema público
  • Nenhuma policy com using (true) em dados privados
  • GRANTs mínimos por papel (anon só onde há leitura pública)
  • Papéis em tabela dedicada + função security definer
  • Service role key ausente do bundle e dos logs
  • Policies testadas com JWT de outro usuário, nos quatro verbos
  • Storage, funções de servidor e realtime revisados junto

Perguntas frequentes

RLS desligado é sempre inseguro?+

Se a tabela está no schema public e exposta pela Data API, sim — ela fica legível pela internet sem nenhuma policy. RLS desligado só é aceitável em tabelas que nunca são acessadas via API pública.

A anon key pode ficar exposta no front-end?+

Sim, por design — chaves anon/publishable são públicas e podem ficar no bundle do cliente, desde que as policies de RLS estejam corretas. Quem nunca pode aparecer no cliente é a service role key.

Como testar se minhas policies de RLS estão certas?+

Simule outro usuário: autentique com um JWT de uma conta comum e tente ler, criar, editar e apagar dados de outro usuário. Se algum desses comandos retornar dados ou tiver sucesso, a policy está errada.

Esse checklist cobre Storage e Edge Functions também?+

O checklist foca nas tabelas, mas o item 7 lembra que buckets públicos, funções de borda sem verificação de sessão e canais realtime abertos expõem os mesmos dados — vale revisar os três junto com as tabelas.

Quer esse checklist aplicado no seu projeto?

Enviamos um relatório priorizado com as policies corrigidas em formato de diff, em 24–48h.

Solicitar revisão

Leia também: Conheça a revisão de segurança · Ver todos os posts · O que é security code review para apps de IA · OWASP Top 10 para aplicações LLM · Claude Code vs Cursor vs Windsurf