Vibe Coding

Segurança de Apps Feitos com IA: RLS, Chaves de API e os Erros que Vazam Dados [2026]

Time da MarfinTime da Marfin20 min de leitura
Segurança de Apps Feitos com IA: RLS, Chaves de API e os Erros que Vazam Dados [2026]

TL;DR

  • Quase todo vazamento em app de vibe coding vem de tabela sem Row Level Security ou de política que libera tudo, e isso se corrige com poucas linhas de SQL.

Levar para a IA

Leve este artigo para o ChatGPT, o Claude ou a sua IA preferida.

Um app feito com IA em uma tarde pode expor o banco de dados inteiro na tarde seguinte, e a culpa raramente está no código que a ferramenta escreveu. Na Marfin, já publicamos dezenas de projetos com Lovable, Cursor e Claude Code, e a segurança de apps feitos com IA virou a primeira coisa que checamos antes de qualquer deploy, porque a IA entrega telas bonitas e fluxos funcionando, mas deixa as travas do banco no estado padrão, que quase sempre é aberto.

O problema tem um padrão. Ferramentas de vibe coding geram um front-end que fala direto com o Supabase, com a chave pública do projeto embutida no JavaScript que qualquer pessoa consegue ler no navegador. Isso é o desenho normal da arquitetura e funciona bem, desde que o Row Level Security esteja ativado e as políticas estejam certas. Quando isso falta, cada visitante do site tem o mesmo acesso ao banco que o dono do projeto.

Aqui mostramos como o RLS funciona de verdade, quais chaves podem ir pro navegador e quais precisam ficar no servidor, os erros que mais vimos vazar dados em apps gerados por IA e o roteiro de auditoria que usamos com Claude Code e Cursor antes de colocar qualquer coisa no ar. Se você ainda está começando, vale ler primeiro nosso guia sobre o que é vibe coding para entender a arquitetura típica desses projetos.

Como a segurança de apps feitos com IA costuma falhar

Em julho de 2025, o app Tea, voltado a mulheres que trocam informações sobre encontros, teve cerca de 72 mil imagens de usuárias expostas, incluindo selfies e documentos de identidade enviados para verificação de conta. O motivo foi um bucket de armazenamento do Firebase sem autenticação, acessível para qualquer pessoa que soubesse o endereço. Bastou pedir os arquivos e o servidor entregou, sem criptografia para quebrar nem vulnerabilidade sofisticada para explorar.

No mesmo ano, um levantamento independente analisou 1.645 apps publicados com Lovable e encontrou 170 com dados de usuários acessíveis a qualquer visitante, por tabelas sem RLS ou políticas que liberavam tudo. O caso ganhou até um identificador CVE e forçou a Lovable a lançar um scanner de segurança dentro da própria plataforma. Nós acompanhamos esse episódio de perto porque tínhamos protótipos rodando na ferramenta, e o resultado da nossa auditoria interna foi desconfortável: dois deles tinham tabelas sem política nenhuma.

Esses dois casos mostram o padrão. Apps de vibe coding seguem uma arquitetura em que o navegador conversa direto com o backend, seja Supabase ou Firebase, sem uma camada de servidor no meio filtrando pedidos. Isso reduz custo e tempo de desenvolvimento, e é o que permite que alguém sem experiência em programação publique um produto funcional em um fim de semana. O preço desse desenho é que toda a segurança do dado passa a viver dentro do banco, em regras que o modelo de IA precisa escrever corretamente e que ninguém enxerga na interface do app.

A IA falha nisso por um motivo simples. Quando você pede "crie uma tela que lista os pedidos do usuário", o modelo otimiza para a tela funcionar no seu teste. Ele cria a tabela, escreve a consulta, renderiza a lista. Ativar RLS e escrever a política correta faz a consulta quebrar até que a autenticação esteja plugada, então o caminho de menor resistência para o modelo é deixar a tabela aberta. Se o prompt deixar de cobrar segurança, o resultado padrão é um banco público.

Row Level Security: a trava que a IA esquece de ligar

Row Level Security, ou RLS, é um recurso nativo do PostgreSQL que o Supabase expõe como o principal mecanismo de controle de acesso. A ideia é que cada linha de uma tabela só aparece para quem uma política autoriza. Sem RLS ativado, a tabela responde qualquer consulta que chegue com uma chave válida do projeto, e como a anon key está no front-end, isso significa qualquer pessoa.

Como o RLS funciona no Supabase

O fluxo tem três peças. Primeiro, o usuário faz login e recebe um JWT do Supabase Auth. Segundo, toda requisição à API REST ou ao cliente JavaScript carrega esse token, e o Postgres consegue ler a identidade dele por meio da função auth.uid(). Terceiro, as políticas de RLS comparam essa identidade com as colunas da tabela para decidir o que devolver, atualizar ou apagar.

Na prática, o mínimo aceitável para uma tabela com dados de usuário se parece com isto:

alter table public.pedidos enable row level security;

create policy "usuario le os proprios pedidos"
on public.pedidos for select
to authenticated
using (auth.uid() = user_id);

create policy "usuario cria pedidos para si"
on public.pedidos for insert
to authenticated
with check (auth.uid() = user_id);

Três detalhes desse trecho salvam projetos. O enable row level security precisa estar lá, porque criar política em tabela sem RLS ativado tem efeito zero. O to authenticated restringe a política a usuários logados, então visitantes anônimos recebem lista vazia em vez de erro. E a diferença entre using e with check define o que a pessoa consegue ler versus o que ela consegue gravar, e são coisas distintas que precisam ser pensadas separadamente.

Quando geramos um projeto no Cursor ou no Claude Code, a nossa regra é que toda migração que cria tabela no schema public vem acompanhada, no mesmo arquivo, do comando que ativa RLS e de pelo menos uma política. Colocamos isso nas instruções do projeto, e o modelo obedece. No Lovable, a integração nativa com Supabase já ativa RLS nas tabelas novas por padrão desde 2025, mas as políticas que ele gera ainda merecem revisão, como explicamos no nosso tutorial de Lovable.

As políticas que parecem certas e vazam mesmo assim

O erro mais comum que encontramos em auditorias é a política permissiva disfarçada. Ela tem esta cara:

create policy "acesso total"
on public.pedidos for all
using (true);

Isso ativa RLS no papel e abre a tabela inteira na prática. A IA gera esse trecho com frequência quando o desenvolvedor reclama que "a consulta parou de funcionar" depois de ativar RLS, porque é a correção mais rápida para o erro desaparecer. Se você vir using (true) em qualquer política de uma tabela que guarda dado de usuário, trate como uma tabela aberta.

O segundo erro é a política que confia em um campo controlado pelo cliente. Uma política que filtra por um header como x-user-id parece separar os usuários, mas o header vem do navegador e qualquer pessoa pode mandar o id que quiser. A identidade confiável vem só do JWT, ou seja, de auth.uid() e de auth.jwt().

O terceiro erro é a escalada de privilégio por update. Muitos apps gerados por IA guardam um campo role ou is_admin na tabela profiles, e criam uma política de update que deixa o usuário editar a própria linha. Como a política cobre a linha inteira, o usuário consegue trocar o próprio papel para admin com uma chamada à API. A correção é separar o que o usuário pode editar em uma tabela ou view própria, ou usar um trigger que bloqueia mudança nesses campos, ou ainda guardar o papel nos app_metadata do JWT, que só o servidor consegue alterar.

O quarto erro é esquecer as tabelas de apoio. O modelo protege pedidos e profiles, mas deixa pedido_itens, enderecos e pagamentos sem RLS porque elas surgiram em prompts posteriores. Um join pela API REST expõe o que a tabela principal escondia. Por isso a auditoria olha o schema inteiro, e o painel do Supabase mostra em vermelho todas as tabelas públicas sem RLS na aba Security Advisor.

Chaves de API: o que pode ir pro navegador e o que fica no servidor

Depois do RLS, o segundo grande foco da segurança de apps feitos com IA são as chaves. Todo projeto de vibe coding lida com pelo menos duas famílias: as chaves do backend, como as do Supabase, e as chaves de serviços de terceiros, como OpenAI, Anthropic, Stripe e Resend. A regra de bolso é curta. Uma chave que pode ir pro navegador foi desenhada para isso e depende de outra camada de proteção, como RLS. Todas as outras ficam no servidor.

Anon key e service_role key

O Supabase entrega duas chaves por projeto. A anon key, que desde 2025 aparece também no formato sb_publishable_..., foi feita para ser pública. Ela identifica o projeto e assume o papel anon no Postgres, o que significa que só enxerga o que as políticas de RLS permitem para visitantes. Ela vai no código do front-end e não há problema nisso, contanto que o RLS esteja certo.

A service_role key, no formato novo sb_secret_..., ignora RLS por completo. Ela existe para tarefas administrativas em ambiente de servidor, como rodar uma Edge Function que precisa ler dados de todos os usuários, ou um script de migração. Se ela chegar ao navegador, cada visitante do site vira administrador do banco. Já vimos isso acontecer em projeto gerado por IA porque o desenvolvedor colou a chave errada na variável VITE_SUPABASE_ANON_KEY, e o Vite, como faz com qualquer variável com prefixo VITE_, embutiu o valor no bundle público.

Um jeito rápido de conferir: abra o site publicado, vá em ferramentas do desenvolvedor, aba Sources ou Network, e procure a string supabase.co nos arquivos JavaScript. A chave logo ao lado deve ser a anon ou a publishable. Se aparecer algo com service_role no payload do JWT decodificado, ou um prefixo sb_secret_, o projeto está comprometido e a chave precisa ser rotacionada no painel do Supabase imediatamente.

Chaves de OpenAI, Anthropic e outras APIs pagas

Apps gerados por IA quase sempre chamam algum modelo de linguagem, e o primeiro rascunho da ferramenta costuma fazer isso direto do front-end com a chave da OpenAI ou da Anthropic no código. Isso expõe a chave a qualquer visitante, que pode usar seu crédito para rodar o que quiser. Conhecemos um founder que acordou com uma fatura de alguns milhares de dólares em uma manhã porque a chave dele estava em um bundle público e alguém a encontrou com uma busca simples.

A solução padrão no ecossistema Supabase é uma Edge Function que recebe a requisição do front-end, valida o JWT do usuário, chama a API externa com a chave guardada como secret do projeto e devolve a resposta. A chave fica só no servidor, e a função pode aplicar limite de uso por usuário, o que também protege contra abuso. No Vercel, o equivalente é uma Route Handler ou Server Action lendo a chave de uma variável de ambiente sem o prefixo público.

Uma Edge Function mínima para esse caso segue esta lógica: ler o header Authorization, chamar supabase.auth.getUser() com o token para confirmar quem é o usuário, rejeitar com 401 se falhar, montar o prompt no servidor e só então chamar o modelo com Deno.env.get('ANTHROPIC_API_KEY'). O front-end recebe apenas o texto final. Pedimos para o Claude Code gerar exatamente esse padrão toda vez, e ele faz bem quando o prompt deixa claro que a chave fica no servidor.

Arquivos .env, repositórios públicos e histórico do git

O terceiro vazamento clássico de chave vem do repositório. Ferramentas como Lovable e Bolt.new sincronizam o projeto com o GitHub, e se o repositório for público e o arquivo .env tiver sido commitado em algum momento, a chave está lá para sempre no histórico, mesmo que você apague o arquivo depois. Bots varrem o GitHub em busca de padrões como sk- e sb_secret_ em minutos após o push.

A prevenção é garantir que .env e variantes estejam no .gitignore antes do primeiro commit, e que valores sensíveis fiquem só nas variáveis de ambiente da plataforma de deploy. Se uma chave já vazou pelo histórico, rotacione a chave na origem, seja o painel do Supabase, da OpenAI ou da Anthropic, e depois limpe o histórico se o repositório for continuar público. Apagar só o arquivo deixa a chave viva nos commits antigos. Cobrimos a parte de variáveis de ambiente por plataforma em deploy de app com IA.

Os erros que mais vazam dados em apps feitos com IA

Além do RLS e das chaves, existe um conjunto de erros menores que aparecem em quase toda auditoria que fazemos. Todos são banais e vêm do mesmo lugar: a IA otimiza para o fluxo funcionar, e segurança é o que acontece quando alguém pede.

O primeiro é o bucket de Storage público por conveniência. Para exibir avatar e imagens de produto, o modelo cria um bucket público, o que é adequado para arquivos que todo mundo pode ver. O problema surge quando o mesmo bucket passa a receber comprovantes, documentos ou fotos privadas em um prompt posterior. Foi exatamente esse o caso do Tea. Buckets com conteúdo privado precisam ser privados, com políticas de RLS na tabela storage.objects e URLs assinadas com expiração para exibir o arquivo.

O segundo é a validação só no front-end. O formulário impede o usuário de digitar um valor negativo, o botão de excluir só aparece para o dono do item, o campo de preço vem desabilitado. Tudo isso desaparece quando alguém chama a API REST direto com a anon key, o que leva dez segundos com curl. Por isso, toda regra de negócio precisa existir no banco, como constraint, trigger ou política, ou em uma Edge Function.

O terceiro é a Edge Function que confia em quem chama. Por padrão, o Supabase exige um JWT válido para invocar uma função, mas a IA costuma desligar essa verificação quando encontra erro de CORS ou de autenticação durante o desenvolvimento, e a função vira um endpoint público que executa com a service_role key. Toda função precisa confirmar o usuário no início e usar a service_role só para o que de fato precisa.

O quarto é a enumeração por ID sequencial. Se pedidos usam ids 1, 2, 3 e a política de RLS falha em uma única tabela relacionada, um atacante percorre todos os registros em um loop. UUIDs, que o Supabase gera por padrão com gen_random_uuid(), encarecem esse ataque sem substituir o RLS.

O quinto é o log verboso em produção. Edge Functions geradas por IA adoram um console.log com o corpo inteiro da requisição, incluindo email, token e dados de cartão. Esses logs ficam no painel do Supabase e em qualquer ferramenta de observabilidade conectada, e viram um segundo banco de dados sensível que ninguém protege.

O sexto é o CORS aberto para qualquer origem junto com ausência de rate limit. Separadamente, cada um é discutível. Juntos em uma função que chama um modelo pago, eles permitem que qualquer site do mundo use seu backend como proxy gratuito de IA.

Como auditamos um app com Claude Code e Cursor

Na Marfin, o Claude Code é a ferramenta que mais usamos, e o Cursor é a IDE em que fazemos o building do dia a dia. Os dois entram no processo de segurança em momentos diferentes. Se você ainda está escolhendo em que ambiente trabalhar, o nosso comparativo das melhores IDEs com IA cobre isso em detalhe.

O primeiro passo da auditoria é mecânico e independente de IA. Abrimos o painel do Supabase, vamos em Advisors e lemos a lista do Security Advisor. Ele aponta tabelas sem RLS, políticas permissivas, funções com search_path inseguro e views expostas. Depois, fazemos o teste do estranho: pegamos a anon key do bundle publicado e rodamos consultas direto na API REST, sem login.

curl "https://SEU-PROJETO.supabase.co/rest/v1/profiles?select=*" \
  -H "apikey: SUA_ANON_KEY" \
  -H "Authorization: Bearer SUA_ANON_KEY"

Se essa chamada devolver qualquer linha de uma tabela que deveria ser privada, o app está vazando. Repetimos para cada tabela do schema e para os buckets de Storage. Esse teste leva cinco minutos e já pegou mais problema do que qualquer ferramenta automatizada que testamos.

O segundo passo é entregar o schema para o Claude Code com um prompt específico. Pedimos algo como: "Leia todas as migrações em supabase/migrations. Para cada tabela do schema public, liste se RLS está ativado, quais políticas existem, e aponte qualquer política com using (true), qualquer tabela sem política de select restrita a auth.uid() e qualquer coluna de permissão que o próprio usuário consiga editar. Depois proponha as correções em uma nova migração, sem aplicar." O Claude Code lê o projeto inteiro, cruza as tabelas com as funções e devolve um relatório com as políticas faltantes já escritas. Revisamos e aplicamos.

O terceiro passo acontece dentro do Cursor, durante o building. Mantemos um arquivo de regras do projeto dizendo que toda tabela nova nasce com RLS, que chaves secretas só aparecem em Edge Functions e que nenhuma variável com prefixo VITE_ ou NEXT_PUBLIC_ pode conter segredo. O Cursor aplica essas regras em cada geração, e isso reduz o retrabalho na auditoria final.

Com Opus 4.8, que chegou no fim de maio de 2026, o Claude Code passou a reportar com muito mais consistência o que ele mesmo deixou de fazer, e isso muda a auditoria. Quando ele cria uma tabela e pula a política porque a autenticação ainda não existe no projeto, agora ele avisa no resumo em vez de deixar passar em silêncio. Ainda assim, nós conferimos.

Lovable, Bolt.new, v0 e Cursor: o que cada ferramenta faz pela sua segurança

As ferramentas de vibe coding divergem bastante em quanto cuidado elas embutem por padrão. Comparamos as três principais plataformas visuais em Lovable vs Bolt.new vs v0, e a dimensão de segurança pesou no resultado.

O Lovable, depois do episódio de 2025, passou a incluir um scanner de segurança que roda antes da publicação e alerta sobre tabelas sem RLS, políticas permissivas e chaves expostas. A integração nativa com Supabase cria as tabelas já com RLS ativado e o Agent Mode tende a escrever políticas baseadas em auth.uid(). Ainda é a plataforma que mais ajuda um não programador a evitar o pior, e mesmo assim as políticas geradas merecem leitura, porque o scanner é bom em achar ausência de política e fraco em achar política errada.

O Bolt.new tem integração com Supabase e gera migrações razoáveis, mas depende mais de o usuário pedir segurança explicitamente. É uma opção válida para protótipo, e nos nossos testes exigiu mais prompts de correção do que o Lovable para chegar a um schema fechado. Se for a sua ferramenta, faça o teste do curl antes de compartilhar o link com qualquer pessoa.

O v0 da Vercel é orientado a componentes React e interface. Ele gera front-end e, quando pedido, rotas de API no Next.js, o que naturalmente empurra as chaves para o servidor, já que variáveis sem NEXT_PUBLIC_ ficam fora do bundle. O banco, porém, fica por sua conta, e a integração com Supabase precisa das políticas escritas por você ou por outra ferramenta.

Cursor e Claude Code são outra categoria. Neles, o código é seu de verdade, cada migração é um arquivo que você lê, e as regras do projeto são um contrato que o modelo segue. Isso exige mais de quem usa, e devolve controle total sobre o que vai pro banco e pro bundle. Para produto que vai receber dado de cliente, é o caminho que recomendamos.

Preços e planos

Segurança costuma ter custo zero em dinheiro e algum custo em atenção, mas alguns recursos ficam atrás de planos pagos. Os valores abaixo são os vigentes em junho de 2026.

FerramentaPlano gratuitoPlanos pagosRecursos de segurança
SupabaseFree (2 projetos, pausa após inatividade)Pro US$ 25/mês, Team US$ 599/mês, EnterpriseRLS, Auth, Storage e Security Advisor em todos os planos. Backups diários no Pro. SSO e logs estendidos no Team.
LovableFree (créditos limitados)Pro a partir de US$ 25/mês, Business US$ 50/mês, EnterpriseScanner de segurança em todos os planos. Domínio próprio e mais créditos nos pagos.
Bolt.newFree (tokens diários)Pro a partir de US$ 20/mêsIntegração Supabase em todos os planos.
v0Free (créditos)Premium US$ 20/mês, Team US$ 30/usuárioVariáveis de ambiente no Vercel protegem chaves em qualquer plano.
CursorHobbyPro US$ 20/mês, Pro+ US$ 60/mês, Ultra US$ 200/mêsRegras de projeto e revisão de diff em todos os planos.
Claude CodeSem plano gratuitoIncluído no Claude Pro US$ 20/mês e no Max a partir de US$ 100/mêsAuditoria de schema completa em qualquer plano, com Opus 4.8 nos planos Max.

O que esses números dizem na prática: tudo que este artigo descreve, de RLS a Edge Functions com secrets, está disponível no plano gratuito do Supabase. O motivo para subir para o Pro em projeto real é o backup diário e a garantia de que o projeto continua ligado sem pausar por inatividade, o que em produção vira indisponibilidade. Na Lovable, o scanner roda no plano gratuito. A barreira para um app seguro é conhecimento, e a sua conta de nuvem cobre o resto.

8 dicas para a segurança de apps feitos com IA

1. Ative RLS na mesma migração que cria a tabela. Coloque isso nas regras do projeto no Cursor ou no Claude Code e em todo prompt no Lovable. Tabela sem RLS em schema public é o erro número um, e a correção leva uma linha.

2. Procure using (true) em todas as políticas. Rode uma busca no repositório e no SQL Editor do Supabase. Cada ocorrência em tabela com dado de usuário precisa virar uma comparação com auth.uid() ou ser justificada por escrito.

3. Faça o teste do estranho antes de cada deploy. Pegue a anon key do bundle e consulte todas as tabelas via REST sem login. O que voltar é o que qualquer pessoa na internet vê.

4. Mantenha a service_role key e as chaves de IA fora do bundle. Confira as variáveis com prefixo VITE_ e NEXT_PUBLIC_ e garanta que nenhuma delas contém segredo. Toda chamada a modelo de linguagem passa por Edge Function ou rota de servidor.

5. Separe o que o usuário pode editar do que define permissão. Campos como role e is_admin ficam fora da política de update do usuário ou nos app_metadata do JWT. Sem isso, promover a si mesmo a admin é uma chamada de API.

6. Trate cada bucket de Storage como uma tabela. Buckets com conteúdo privado ficam privados, com política em storage.objects e URLs assinadas. Público só o que pode aparecer no Google.

7. Rode o Security Advisor toda semana e depois de cada feature nova. Ele detecta tabelas sem RLS, funções inseguras e views expostas. Combine com o scanner do Lovable se estiver na plataforma.

8. Rotacione qualquer chave que já tocou um repositório público. Gere uma chave nova na origem, troque nas variáveis de ambiente e invalide a antiga, porque o arquivo apagado continua vivo nos commits antigos.

Apps feitos com IA são seguros na medida em que alguém pede segurança, e a boa notícia é que a lista do que pedir é curta e estável. RLS em toda tabela, políticas baseadas na identidade do JWT, chaves secretas só no servidor, buckets privados para conteúdo privado e um teste com curl antes de publicar cobrem a grande maioria dos vazamentos que vimos em três anos de vibe coding.

Nós tratamos essa lista como parte do processo de build, com as regras escritas no projeto e a auditoria do Claude Code antes de cada deploy. Custa vinte minutos por versão e evita a manhã em que um cliente manda um print do próprio banco de dados aberto no navegador de outra pessoa. Se você está publicando seu primeiro app com ferramentas de vibe coding, incorpore o teste do estranho no seu fluxo desde o primeiro projeto. É o hábito que separa um protótipo de um produto.


Leia também:

▸ THE_DOWNLOAD.SUBSCRIBE

Carregue a semana.
Instale na segunda.

Um digest do blog da Marfin. Todo sábado.

Grátis. Eject quando quiser.

A Marfin é uma venture builder de empresas tech.

Quer conhecer nossos serviços e produtos?