Segurança em Vibe Coding e Supabase: RLS, Anon Key, Service Role e Como Auditar Apps do Lovable, Bolt e Cursor
Em resumo: Sim, o Supabase é seguro quando configurado corretamente, mas 8 em cada 10 aplicativos criados com Vibe Coding (no Lovable, Bolt.new, v0, Replit, Cursor e Claude Code) vão para o ar com falhas críticas porque conectam o navegador diretamente ao banco usando a supabase anon key sem políticas rigorosas de Row Level Security (supabase rls). Como a anon key é pública no front-end, qualquer pessoa que abrir o DevTools pode ler, editar ou apagar tabelas inteiras via PostgREST (/rest/v1/) se o RLS estiver desativado, se faltar a cláusula WITH CHECK em um UPDATE ou se uma VIEW não tiver security_invoker = true.
Neste guia prático de engenharia de segurança e auditoria de código, você vai aprender a diferença exata entre anon key e service_role key, como corrigir as 7 vulnerabilidades mais exploradas em apps Lovable + Supabase, como conectar o Supabase MCP (supabase mcp) em modo somente leitura no Claude Code, Cursor e Antigravity para auditar seu schema automaticamente, e quando blindar sua operação com um BFF em Cloudflare Workers.
Por que o Vibe Coding no Lovable e Bolt cria falhas silenciosas no Supabase?
Ferramentas como Lovable (lovable supabase), Bolt.new e Replit Agent foram desenhadas para maximizar a velocidade do primeiro protótipo. Para evitar que você precise programar um servidor back-end (API), elas geram código React/Vite que chama supabase.from('tabela').select('*') ou .update(...) diretamente do navegador do usuário.
O problema é que agentes de IA otimizam para o código funcionar na primeira tentativa, não para o princípio do menor privilégio. Quando uma query falha por falta de permissão durante o Vibe Coding, o próprio agente frequentemente aplica um destes três "atalhos" fatais na migração SQL:
- Cria novas tabelas sem executar
ALTER TABLE ... ENABLE ROW LEVEL SECURITY; - Cria uma política permissiva genérica (
USING (true)) que libera leitura ou escrita pública para qualquer visitante anônimo; - Permite que o próprio front-end envie colunas sensíveis no
UPDATE(comorole: 'admin',is_pro: trueoucredits: 99999) sem bloquear a alteração viaWITH CHECKou trigger.
Se você ainda está dando os primeiros passos no banco, recomendamos assistir em paralelo ao nosso Curso Grátis de Supabase sem Mistério (14 aulas), onde mostramos na prática a arquitetura interna do PostgREST, GoTrue (supabase authentication) e Storage.
Supabase Anon Key vs. Service Role Key: o que pode e o que NUNCA pode ir para o front-end
Uma das dúvidas mais buscadas por quem pesquisa se o Supabase é seguro é: "Vazar a SUPABASE_ANON_KEY no código JavaScript do navegador é uma falha de segurança?"
A resposta curta é: a anon key (ou publishable key sb_publishable_...) foi feita para ser pública, desde que 100% das suas tabelas tenham RLS ativo. Já a service_role key (ou secret key sb_secret_...) ignora completamente todo o RLS — se ela vazar no front-end, seu banco inteiro está comprometido.
| Chave / Papel Postgres | Onde deve ficar? | Respeita RLS (supabase rls)? | Risco se exposta no navegador (VITE_ / NEXT_PUBLIC_ / NUXT_PUBLIC_) |
|---|---|---|---|
anon (sb_publishable_...) | Front-end / App Mobile / BFF | Sim. Só acessa o que tiver política explícita para TO anon. | Crítico se houver 1 tabela sem RLS; seguro se todas as tabelas e views tiverem RLS restritivo. |
authenticated (JWT da sessão) | Front-end (após login no Supabase Auth) | Sim. Identifica o usuário via auth.uid() e auth.jwt(). | Permite escalada de privilégio se políticas UPDATE não protegerem colunas como role, plan_id ou balance. |
service_role (sb_secret_...) | Apenas no Back-end / Cloudflare Workers (Secrets) | Não (Bypass total de RLS). Tem poder de superusuário nos dados. | Catastrófico. Qualquer pessoa lê, altera ou apaga todo o banco e todos os buckets do Storage sem autenticação. |
service_role key em variáveis prefixadas com VITE_, NEXT_PUBLIC_, NUXT_PUBLIC_ ou EXPO_PUBLIC_. Qualquer variável com esses prefixos é embutida no bundle JavaScript público e pode ser extraída em 5 segundos pelo DevTools.As 7 vulnerabilidades fatais em apps Vibe Coding + Supabase (e como corrigir em SQL)
1. Tabelas sem RLS ativo ou com USING (true)
Por padrão no Postgres, qualquer tabela recém-criada no schema public com CREATE TABLE nasce com o RLS desligado — o que significa que a anon key tem acesso total de SELECT, INSERT, UPDATE e DELETE via API REST automática do Supabase.
Além disso, quando o Lovable ou Cursor tenta corrigir um erro 42501 (new row violates row-level security policy), é comum ele gerar CREATE POLICY "Enable all" ON public.orders FOR ALL USING (true);. Isso equivale a deixar a porta destrancada.
2. UPDATE sem WITH CHECK (Auto-Promoção para Admin ou Plano Pro)
Muitos desenvolvedores escrevem a seguinte política na tabela profiles:
-- ❌ VULNERÁVEL: Permite que o usuário edite a própria linha, INCLUSIVE mudando role = 'admin' ou plan = 'enterprise'!
CREATE POLICY "Users can update own profile"
ON public.profiles
FOR UPDATE
TO authenticated
USING ((SELECT auth.uid()) = id);Com a política acima, o usuário logado abre o console do navegador e executa:
await supabase.from('profiles').update({ role: 'admin', credits: 999999 }).eq('id', myUserId)Como corrigir? Combine USING + WITH CHECK e revogue a permissão de escrita em colunas sensíveis no nível do Postgres (ou separe billing/roles em uma tabela que somente o back-end escreve):
-- 1. Ative e force o RLS na tabela
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.profiles FORCE ROW LEVEL SECURITY;
-- 2. Revogue UPDATE irrestrito e libere apenas colunas seguras de perfil para o cliente
REVOKE UPDATE ON public.profiles FROM anon, authenticated;
GRANT UPDATE (full_name, avatar_url, bio, updated_at) ON public.profiles TO authenticated;
-- 3. Use (SELECT auth.uid()) em USING e WITH CHECK (otimizado com InitPlan e seguro)
CREATE POLICY "profiles_select_own"
ON public.profiles
FOR SELECT
TO authenticated
USING ((SELECT auth.uid()) = id);
CREATE POLICY "profiles_update_own"
ON public.profiles
FOR UPDATE
TO authenticated
USING ((SELECT auth.uid()) = id)
WITH CHECK ((SELECT auth.uid()) = id);3. Views SQL criadas sem security_invoker = true
No PostgreSQL, uma VIEW padrão executa com as permissões do dono da view (postgres), ignorando completamente as políticas de RLS das tabelas subjacentes! Se o Lovable ou o Cursor criar uma CREATE VIEW public.user_billing_summary AS ..., qualquer visitante com a anon key conseguirá consultar a view inteira.
Sempre declare security_invoker = true (disponível no Postgres 15+ do Supabase):
CREATE OR REPLACE VIEW public.user_billing_summary
WITH (security_invoker = true)
AS
SELECT id, user_id, plan_status, current_period_end
FROM public.subscriptions;4. Funções RPC (SECURITY DEFINER) sem search_path fixo
Funções criadas com SECURITY DEFINER rodam com os poderes de quem as criou. Se você criar uma RPC acessível via supabase.rpc('minha_funcao'), precisa obrigatoriamente:
- Definir
SET search_path = ''para impedir sequestro de objetos por schema injection; - Validar
(SELECT auth.uid())dentro do corpo da função; - Revogar a execução de
PUBLICeanonse a função for restrita:
REVOKE EXECUTE ON FUNCTION public.admin_grant_credits(uuid, int) FROM PUBLIC, anon, authenticated;
GRANT EXECUTE ON FUNCTION public.admin_grant_credits(uuid, int) TO service_role;5. Buckets do Supabase Storage sem validação de pasta por auth.uid()
Marcar um bucket como "Public" no Supabase Storage permite que qualquer pessoa baixe qualquer arquivo se descobrir o caminho. Para documentos privados (contratos, notas fiscais, fotos de clientes, comprovantes), mantenha o bucket privado e exija que o primeiro segmento da pasta seja o auth.uid() do usuário:
CREATE POLICY "storage_user_private_folder"
ON storage.objects
FOR ALL
TO authenticated
USING (
bucket_id = 'documentos-privados'
AND (storage.foldername(name))[1] = (SELECT auth.uid()::text)
)
WITH CHECK (
bucket_id = 'documentos-privados'
AND (storage.foldername(name))[1] = (SELECT auth.uid()::text)
);6. Proteção de Senhas Vazadas (Leaked Password Protection Supabase) e Auth
No painel do Supabase Authentication (Authentication -> Providers -> Email e Attack Protection), três configurações costumam ficar desligadas em projetos recém-criados:
- Leaked Password Protection (
leaked password protection supabase): Integra o Supabase Auth com a base do HaveIBeenPwned.org via k-anonymity para impedir que usuários cadastrem senhas já expostas em vazamentos globais; - Confirm Email + Secure Email Change: Se "Confirm email" estiver desativado, qualquer atacante pode criar uma conta usando o e-mail de outra pessoa antes dela e sequestrar pré-cadastros ligados pelo endereço de e-mail;
- Rate Limits + Cloudflare Turnstile: Sempre ative o CAPTCHA nativo (Cloudflare Turnstile) nas rotas de
signUp,signInWithPasswordesignInWithOtppara bloquear ataques de força bruta e exaustão de cota de e-mail/SMS.
7. Performance + Segurança: (SELECT auth.uid()) vs auth.uid()
Chamar auth.uid() = user_id diretamente na política RLS faz o Postgres reexecutar a função a cada linha varrida na tabela. Quando você envolve em um subselect escalar — (SELECT auth.uid()) = user_id — e cria um índice CREATE INDEX idx_tabela_user_id ON public.tabela (user_id);, o planejador do Postgres faz o cache do UID uma única vez por query (InitPlan), acelerando consultas em até 100x e evitando ataques de negação de serviço (DoS) no banco.
[ CONTEÚDO_INTEGRAL_DISPONÍVEL ]
Veja também: Quanto custa o Supabase em produção, limites do Free Tier e quando migrar para SurrealDB CLIQUE PARA DESBLOQUEAR A LEITURA
Construindo aplicações robustas com arquitetura de alto nível e infraestrutura otimizada na edge do Cloudflare Workers com SurrealDB.
Segurança de dados e conformidade com privacidade desde o primeiro dia de código com tratamento de estados e sessões resilientes.
Métricas de performance para monitorar a experiência do usuário e otimizar custos operacionais em produção com dashboards estruturados.
Construindo aplicações robustas com arquitetura de alto nível e infraestrutura otimizada na edge do Cloudflare Workers com SurrealDB.
Segurança de dados e conformidade com privacidade desde o primeiro dia de código com tratamento de estados e sessões resilientes.
Métricas de performance para monitorar a experiência do usuário e otimizar custos operacionais em produção com dashboards estruturados.
Query SQL de Auditoria Completa: descubra tabelas e views vulneráveis em 10 segundos
Cole o script abaixo no SQL Editor do seu Supabase. Ele funciona como um raio-X instantâneo de auditoria de código e banco, listando qualquer tabela do schema public com RLS desligado, tabelas com RLS ativo mas sem políticas, ou políticas que usam USING (true) para escrita:
-- 1. Lista tabelas no schema public com RLS DESATIVADO (Vulnerabilidade Crítica)
SELECT
n.nspname AS schema_name,
c.relname AS table_name,
c.relrowsecurity AS rls_enabled,
c.relforcerowsecurity AS rls_forced,
'CRÍTICO: Tabela exposta na API PostgREST sem RLS!' AS diagnostico
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'public'
AND c.relkind = 'r'
AND NOT c.relrowsecurity
ORDER BY c.relname;
-- 2. Lista políticas permissivas perigosas (FOR ALL / INSERT / UPDATE / DELETE com true)
SELECT
schemaname,
tablename,
policyname,
cmd,
roles,
qual AS using_expression,
with_check AS with_check_expression
FROM pg_policies
WHERE schemaname = 'public'
AND (
(cmd IN ('ALL', 'UPDATE', 'DELETE') AND COALESCE(qual, '') = 'true')
OR (cmd IN ('ALL', 'INSERT', 'UPDATE') AND COALESCE(with_check, '') = 'true')
);Se você quiser validar suas políticas antes mesmo de rodar no banco, use também o nosso Linter de Políticas RLS no Supabase.
Como usar o Supabase MCP (supabase mcp) com Segurança no Claude Code, Cursor e Antigravity
O termo supabase mcp (junto com mcp supabase claude code, supabase mcp cursor e supabase mcp antigravity) explodiu em buscas porque permite que o seu agente de código inspecione tabelas, políticas de RLS e logs de auditoria diretamente da IDE.
Porém, conectar o servidor MCP do Supabase em produção sem restrições permite que uma alucinação do modelo execute DROP TABLE ou exponha dados reais de clientes (PII/LGPD) no contexto do LLM. Siga estas 3 travas obrigatórias:
- Use sempre a flag
--read-onlyquando conectar em bancos com dados importantes; - Trave o escopo em um único projeto com
--project-ref=<ID_DO_PROJETO>(preferencialmente o seu ambiente de staging ou branch de desenvolvimento, nunca produção com dados sensíveis de clientes); - Combine o MCP com um arquivo
SKILL.md/AGENTS.mdde auditoria (que você pode gerar gratuitamente no nosso Gerador de SKILL.md, AGENTS.md e Servidor MCP).
{
"mcpServers": {
"supabase": {
"command": "npx",
"args": [
"-y",
"@supabase/mcp-server-supabase@latest",
"--read-only",
"--project-ref=SEU_PROJECT_REF_DE_STAGING"
],
"env": {
"SUPABASE_ACCESS_TOKEN": "${SUPABASE_ACCESS_TOKEN}"
}
},
"escola-aplicativos": {
"type": "http",
"url": "https://www.escoladeaplicativos.com.br/api/publico/mcp"
}
}
}No Claude Code, você também pode registrar no terminal com um único comando:
claude mcp add supabase -- npx -y @supabase/mcp-server-supabase@latest --read-only --project-ref=SEU_PROJECT_REFDepois de conectar o MCP, use o prompt abaixo junto com a nossa Skill de Security Audit (/arsenal/skill-security-audit-cloudflare) para fazer um pente-fino completo no seu projeto:
Atue como um Engenheiro de Segurança Sênior especialista em Supabase, PostgreSQL RLS e Cloudflare Workers. Inspecione o repositório atual e o schema do banco via MCP (somente leitura) e gere um relatório de auditoria verificando:
- Se existe qualquer tabela no schema
publicsemENABLE ROW LEVEL SECURITYou com políticaUSING (true)emUPDATE/DELETE/INSERT; - Se todas as políticas de
UPDATEpossuemWITH CHECKe se colunas sensíveis (role,plan,credits,status,balance) estão protegidas contra edição direta peloauthenticatedrole; - Se todas as Views no schema
publicdeclaramWITH (security_invoker = true)e se funçõesSECURITY DEFINERdefinemSET search_path = ''; - Se alguma chave privada (
SUPABASE_SERVICE_ROLE_KEY,sb_secret_, tokens de API) está exposta em variáveisVITE_,NEXT_PUBLIC_ouNUXT_PUBLIC_; - Se a aplicação configura os Security Headers essenciais (
Content-Security-Policy,Strict-Transport-Security,X-Content-Type-Options: nosniff,Referrer-PolicyePermissions-Policy) e logs de auditoria estruturados sem vazamento de PII. Para cada falha encontrada, entregue a migração SQL exata e o patch de código para corrigir.
A Solução Definitiva: Arquitetura BFF em Cloudflare Workers + Security Headers
Por que grandes produtos em produção não deixam o navegador fazer queries direto no banco de dados? Porque quando você coloca um BFF (Backend-for-Frontend) em Cloudflare Workers (usando Nuxt 4 ou Hono) entre o navegador e o banco:
- O navegador nunca toca no banco de dados: O front-end chama rotas de negócio tipadas e validadas com Zod (ex.:
POST /api/checkout/sessionouPATCH /api/profile), eliminando a exposição do PostgREST; - Security Headers e WAF na Borda: No Cloudflare Workers, você aplica Security Headers (
Strict-Transport-Security,Content-Security-Policy,X-Frame-Options: DENY,X-Content-Type-Options: nosniff), Rate Limiting por IP/usuário e validação de Cloudflare Turnstile antes mesmo da requisição chegar ao seu banco; - Logs de Auditoria Estruturados (Wide Events): Em vez de depender apenas dos logs brutos do Postgres, cada mutação crítica (login, alteração de permissão, pagamento, webhook) grava logs de auditoria sanitizados (sem PII ou tokens) com rastreabilidade ponta a ponta;
- Liberdade para migrar para SurrealDB + Cloudflare R2: Quando o custo de múltiplos projetos, Compute Add-ons e Egress do Supabase começa a pesar, o seu BFF permite migrar para Cloudflare Workers + R2 (Zero Egress) + SurrealDB reduzindo até 85% da fatura mensal. Simule a diferença exata para o seu SaaS na nossa Calculadora de Preço e Limites do Supabase vs Cloudflare + SurrealDB.
Para ver um checklist completo de defesa em profundidade para Workers e BFF, acesse gratuitamente a página da nossa Skill: Security Audit para Apps em Cloudflare e BFF. E se você também quer usar agentes de IA e automações para atrair tráfego orgânico e escalar perfis no Instagram e TikTok no piloto automático, garanta sua vaga na lista de espera da Usina Viral.
Perguntas frequentes (FAQ)
O que é RLS no Supabase e por que ele é obrigatório no Vibe Coding?
O RLS (Row Level Security ou Segurança em Nível de Linha) é o mecanismo nativo do PostgreSQL que restringe quais linhas cada usuário pode ler, inserir, atualizar ou deletar em uma tabela. No Supabase, como a API PostgREST é exposta diretamente ao navegador através da anon key, deixar uma tabela no schema public sem RLS ativo permite que qualquer pessoa na internet leia ou apague todos os registros daquela tabela.
Qual a diferença entre Supabase Anon Key e Service Role Key?
A supabase anon key (ou publishable key) é uma chave pública projetada para rodar no navegador ou app mobile e obedece rigorosamente às políticas de RLS. Já a supabase service role key (ou secret key) é uma chave secreta de superusuário que ignora 100% do RLS; ela deve ficar exclusivamente em variáveis secretas de servidor (como Cloudflare Workers Secrets) e jamais pode ser exposta no front-end.
O Lovable Cloud com Supabase é seguro para colocar um SaaS em produção?
Um protótipo criado no Lovable com Supabase (lovable supabase) pode ir para produção com segurança somente após uma auditoria humana e técnica das migrações SQL. Você deve verificar se todas as tabelas possuem ENABLE ROW LEVEL SECURITY, se políticas UPDATE possuem WITH CHECK bloqueando alteração de colunas de permissão/saldo, se todas as views usam security_invoker = true e se operações financeiras ou de IA rodam em rotas de back-end autenticadas.
Como conectar o Supabase MCP no Claude Code, Cursor e Antigravity com segurança?
Para usar o Supabase MCP (supabase mcp) no Claude Code, Cursor ou Google Antigravity sem risco de apagar tabelas ou vazar dados de produção, configure o pacote @supabase/mcp-server-supabase@latest sempre passando as flags --read-only e --project-ref=<ID_DO_PROJETO_STAGING>, utilizando um Personal Access Token de escopo mínimo.
Como ativar o Leaked Password Protection no Supabase Auth?
No painel do Supabase, acesse Authentication -> Attack Protection (ou Providers -> Email) e ative a opção Prevent use of leaked passwords (leaked password protection supabase). O Supabase Auth passará a consultar os hashes parciais da base HaveIBeenPwned.org em cada cadastro ou troca de senha, bloqueando credenciais comprometidas sem expor a senha do usuário.
Quando vale a pena trocar o acesso direto ao Supabase por um BFF em Cloudflare Workers + SurrealDB?
Assim que o seu produto passa da fase de protótipo para um SaaS comercial com pagamentos, webhooks, múltiplos papéis de acesso (RBAC), agentes de IA ou alto volume de arquivos. Um BFF em Cloudflare Workers + R2 (Zero Egress) + SurrealDB elimina a exposição de chaves do banco no navegador, centraliza os logs de auditoria e reduz em até 85% os custos mensais de infraestrutura.
