# Segurança em Vibe Coding e Supabase: RLS, Anon Key, Service Role e Como Auditar Apps do Lovable, Bolt e Cursor
> Descubra se o Supabase é seguro no Vibe Coding, a diferença entre Anon Key e Service Role Key, como corrigir as 7 falhas fatais de RLS em apps do Lovable e Bolt, e como auditar seu banco com Supabase MCP no Claude Code e Cursor.
**Autores:** Mauro Mequelussi
**Publicado:** 2026-11-11T15:45:00.000Z
**Atualizado:** 2026-11-13T03:37:28.170534963Z
**Tags:** vibe-coding, seguranca, supabase, cloudflare, mcp, claude-code, lovable
---
# 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**.

::callout{type="warning" title="Seu app foi gerado no Lovable, Bolt ou Cursor?"}
Antes de colocar tráfego pago ou clientes reais no seu SaaS, execute a nossa **[Skill de Security Audit para Cloudflare, BFF e Bancos de Dados](/arsenal/skill-security-audit-cloudflare)** no seu repositório e valide suas tabelas com o **[Linter de Políticas RLS no Supabase](/ferramentas/casos-de-uso-jev/linter-de-politicas-rls-no-supabase)**.
::

---

## 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:

1. Cria novas tabelas sem executar `ALTER TABLE ... ENABLE ROW LEVEL SECURITY`;
2. Cria uma política permissiva genérica (`USING (true)`) que libera leitura ou escrita pública para qualquer visitante anônimo;
3. Permite que o próprio front-end envie colunas sensíveis no `UPDATE` (como `role: 'admin'`, `is_pro: true` ou `credits: 99999`) sem bloquear a alteração via `WITH CHECK` ou 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)](/blog/curso-gratis-supabase)**, 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. |

::callout{type="info" title="Regra de Ouro para Variáveis de Ambiente"}
Jamais coloque a `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`:

```sql
-- ❌ 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:

```javascript
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):

::code-demo{title="Blindando UPDATE no Supabase RLS contra escalada de privilégio" language="sql"}
```sql
-- 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):

```sql
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:
1. Definir `SET search_path = ''` para impedir sequestro de objetos por schema injection;
2. Validar `(SELECT auth.uid())` dentro do corpo da função;
3. Revogar a execução de `PUBLIC` e `anon` se a função for restrita:

```sql
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:

```sql
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`, `signInWithPassword` e `signInWithOtp` para 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.

::read-more{slug="supabase-limites-preco-rls-alternativa-surrealdb-cloudflare" label="Veja também: Quanto custa o Supabase em produção, limites do Free Tier e quando migrar para SurrealDB" placement="mid_content"}

---

## 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:

::code-demo{title="Script SQL de Auditoria de Segurança e Logs no Supabase" language="sql"}
```sql
-- 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](/ferramentas/casos-de-uso-jev/linter-de-politicas-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**:

1. **Use sempre a flag `--read-only`** quando conectar em bancos com dados importantes;
2. **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);
3. Combine o MCP com um arquivo `SKILL.md` / `AGENTS.md` de auditoria (que você pode gerar gratuitamente no nosso **[Gerador de SKILL.md, AGENTS.md e Servidor MCP](/ferramentas/gerador-mcp-skills)**).

::code-demo{title="Configuração segura do Supabase MCP (.mcp.json) em modo Read-Only" language="json"}
```json
{
  "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:

```bash
claude mcp add supabase -- npx -y @supabase/mcp-server-supabase@latest --read-only --project-ref=SEU_PROJECT_REF
```

Depois de conectar o MCP, use o prompt abaixo junto com a nossa **[Skill de Security Audit (`/arsenal/skill-security-audit-cloudflare`)](/arsenal/skill-security-audit-cloudflare)** para fazer um pente-fino completo no seu projeto:

::prompt-snippet{title="Prompt de Auditoria de Segurança (Supabase + Vibe Coding + Security Headers)"}
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:
1. Se existe qualquer tabela no schema `public` sem `ENABLE ROW LEVEL SECURITY` ou com política `USING (true)` em `UPDATE`/`DELETE`/`INSERT`;
2. Se todas as políticas de `UPDATE` possuem `WITH CHECK` e se colunas sensíveis (`role`, `plan`, `credits`, `status`, `balance`) estão protegidas contra edição direta pelo `authenticated` role;
3. Se todas as Views no schema `public` declaram `WITH (security_invoker = true)` e se funções `SECURITY DEFINER` definem `SET search_path = ''`;
4. Se alguma chave privada (`SUPABASE_SERVICE_ROLE_KEY`, `sb_secret_`, tokens de API) está exposta em variáveis `VITE_`, `NEXT_PUBLIC_` ou `NUXT_PUBLIC_`;
5. Se a aplicação configura os Security Headers essenciais (`Content-Security-Policy`, `Strict-Transport-Security`, `X-Content-Type-Options: nosniff`, `Referrer-Policy` e `Permissions-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:

1. **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/session` ou `PATCH /api/profile`), eliminando a exposição do PostgREST;
2. **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;
3. **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;
4. **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](/ferramentas/calculadora-custo-supabase)**.

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](/arsenal/skill-security-audit-cloudflare)**. 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](/lp/usina-viral/acesso-antecipado)**.

---

## 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.