Início / RLS / Checklist de segurança: Lovable + Supabase · Read in English

Segurança no Supabase · Lovable

Checklist de segurança: Lovable + Supabase

Postgres 15 · Supabase · Lovable✓ código testado num banco de verdade
TL;DR

A Lovable escreve o app; quem decide quem lê os dados são as suas policies no Supabase. Antes de divulgar o link: procure a chave service_role no app no ar, liste as tabelas que a anon key consegue ler, ache policies sempre verdadeiras e funções que qualquer um executa, e teste deslogado. Cada passo abaixo é uma query ou um clique.

01 Por que apps da Lovable precisam disso

Um app da Lovable fala com o Supabase direto do navegador, usando a sua anon key. Isso é normal, e significa que o Row Level Security é a única coisa protegendo seus dados. Quando as policies geradas estão faltando ou abertas demais, qualquer pessoa consegue ler ou escrever nas suas tabelas.

É isso que o CVE-2025-48757 descreve: policies de RLS insuficientes em sites gerados pela Lovable (até 15/04/2025) deixavam atacantes sem login ler ou escrever tabelas do banco. A Lovable contesta o CVE, dizendo que cada cliente é responsável pelos dados do próprio app. De um jeito ou de outro, a correção é a mesma e fica no seu banco: os passos abaixo.

Apps da Lovable são feitos com Vite, então toda variável que começa com VITE_ vai parar no JavaScript que cada visitante baixa.

02 A chave mestra está no seu frontend?

A chave service_role (ou a nova sb_secret_) ignora todas as policies de RLS. Se ela vai pro navegador, qualquer pessoa que abrir o site pode copiá-la e ler ou alterar o banco inteiro. Isso acontece quando ela cai numa variável VITE_ ou é colada no código do cliente.

Escaneie seu app no ar (grátis, é só colar a URL; página em inglês). Achou? Troque a chave no painel do Supabase (Settings → API), mova pra uma variável só do servidor, publique de novo e escaneie outra vez.

.env

# pode ir pro navegador: a URL e a chave anon / publishable
VITE_SUPABASE_URL=https://xyz.supabase.co
VITE_SUPABASE_ANON_KEY=...  # ou uma chave sb_publishable_
# nunca com o prefixo VITE_: iria pra todo visitante
SUPABASE_SERVICE_ROLE_KEY=...

03 Quais tabelas qualquer um consegue ler?

Sua anon key é pública por natureza; o RLS é a única coisa entre ela e seus dados. Rode no SQL editor do Supabase:

check.sql

select c.relname,
       has_table_privilege('anon', c.oid, 'select') as anon_can_read,
       c.relrowsecurity as rls_on
from pg_class c join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public' and c.relkind = 'r'
order by 1;

Toda linha com anon_can_read = true e rls_on = false é uma tabela pública: ligue o RLS e crie as policies.

04 O RLS está ligado, mas está fazendo alguma coisa?

"RLS ligado" não é o mesmo que "dado protegido". Uma policy sempre verdadeira deixa todo mundo entrar, e código gerado muitas vezes tem uma. Liste:

check.sql

select tablename, policyname, cmd, roles, qual, with_check
from pg_policies
where schemaname = 'public'
  and (qual = 'true' or with_check = 'true')
order by 1, 2;

Só mantenha a linha se a tabela for pública de propósito (um catálogo, uma página de status). Senão, troque por uma policy restrita ao usuário. O gerador de policies RLS escreve uma por operação, testada (em inglês).

05 Funções que qualquer um pode chamar

Uma função security definer roda como o dono dela e pula o RLS. No schema public, ela também vira um endpoint da API (/rest/v1/rpc/…). Liste as que um visitante deslogado consegue executar:

check.sql

select p.proname
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
where n.nspname = 'public'
  and p.prosecdef
  and has_function_privilege('anon', p.oid, 'execute')
order by 1;

Pra cada uma: era pra ser pública? Se não, revoke execute on function public.nome(...) from public, anon; ou mova pra um schema que a API não expõe.

06 Prove como um estranho

O painel e o SQL editor ignoram o RLS, então sempre parecem certos. Teste como um atacante faria: abra o app numa janela anônima, deslogado, copie a anon key na aba de rede e peça uma tabela privada:

terminal

curl "$SUPABASE_URL/rest/v1/orders?select=*" -H "apikey: $ANON_KEY"
# linhas de outros usuários → a policy está quebrada; [] → tudo certo

Depois, mantenha corrigido: npx airlock-rls no CI quebra o build quando uma tabela vai pro ar sem RLS ou com policy sempre verdadeira, não importa quem (ou o quê) escreveu a migration.

◆ GRÁTIS · MIT · npm + GitHub Action

Pegue isso antes de ir pro ar

airlock-rls é um gate de CI que quebra o build quando uma tabela vai pro ar exposta ou uma policy é permissiva: o mesmo tipo de bug, pego no CI contra um banco com as suas migrations aplicadas, antes de chegar em produção.

npx airlock-rls→

Ou comece pelo nextjs-supabase-starter: auth + uma tabela com RLS + um teste de isolamento, pra começar de um padrão que já vem selado.

PDF GRÁTIS

Baixe o cheat sheet de RLS do Supabase

As regras de ouro, as armadilhas que vazam em produção, policies corretas e o teste de isolamento, numa página só (em inglês).

FAQ

O scan de segurança da Lovable passou. Estou seguro?

Um scan aprovado é um bom começo, não uma prova. Uma policy pode existir e ainda deixar todo mundo entrar (USING (true)). Rode a query de policies sempre verdadeiras e o teste com curl deslogado acima: eles mostram o que um atacante realmente conseguiria.

Devo esconder a anon key?

Não dá: ela vai no app por natureza. Ela é segura quando o RLS está ligado e toda policy restringe as linhas ao usuário certo. A chave que precisa ficar secreta é a service_role / sb_secret_.

Posso pedir pra Lovable corrigir as policies?

Pode, e depois confira com as queries acima. Correções geradas pra erros de permissão às vezes abrem mais do que deveriam, como uma policy sempre verdadeira ou um grant amplo.