Início / RLS / Checklist de segurança: Lovable + Supabase · Read in English
Segurança no Supabase · Lovable
Checklist de segurança: Lovable + Supabase
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.
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.
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.
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.