// docs / security guides / hardening
Como proteger uma app construída com ferramentas de coding IA
Um guia passo a passo de proteção para aplicativos criados com Cursor, Claude Code, Lovable, Bolt, v0, Replit ou Windsurf. Quatro fases: entender por que os aplicativos gerados AI- falham de maneira diferente, executar uma auditoria imediata da base de código, fortalecer no momento da implantação e, em seguida, continuar monitorando. Opinativo, narrativo, com trechos reais que você pode copiar.
Por que AI- aplicativos gerados falham de maneira diferente
Os aplicativos codificados pelo Vibe podem ser seguros. Eles precisam de uma aprovação extra de auditoria porque os modos de falha são estruturais, e não descuidados:
- Assistants inline hardcoded keys. You ask for a fix to an auth error and get a pasted Supabase example that assumes a service-role client. The key ends up at the top of a page component. Both the anon client and the service client coexist; both ship.
- Generated servers default to permissive CORS. Generated Express / Fastify handlers often ship with
cors({ origin: '*' })because that's the fastest way to get a working preview. The middleware never gets a second pass. - Rules files get skipped. Firestore-backed projects generate the data model but rarely touch
firestore.rules. Test-mode rules let anyone read and overwrite data until someone replaces them. - RLS never enters the migration. A generated Supabase schema and CRUD surface use the anon key, but
ENABLE ROW LEVEL SECURITYnever enters the migration. Anonymous users can read or write any row. - Handlers trust IDs. A generated
GET /api/items/[id]reads the param and queries Postgres without verifying ownership. Active scans on a verified domain test for this (IDOR / BOLA).
A auditoria imediata: verifique sua base de código em busca de padrões de risco
Antes de endurecer qualquer coisa, encontre o que já está quebrado. Cada um desses greps leva menos de um minuto:
Segredos e chaves do provedor
grep -RIn 'NEXT_PUBLIC_SUPABASE_SERVICE' src/
grep -RIn 'sk_live_\|pk_live_\|STRIPE_SECRET' src/
grep -RIn 'sk-ant-\|^sk-' src/ # Anthropic / OpenAI
grep -RIn 'AIza\|AKIA' src/ # Google / AWS
grep -RIn 'eyJh[A-Za-z0-9_-]\{20,\}' src/ # JWT-shaped stringsQualquer hit precisa ser excluído e rotação de chave. Provider painéis: Supabase → Configurações → API, Stripe → Desenvolvedores → API teclas, console Anthropic / OpenAI.
Controles de acesso ao banco de dados
# Supabase migrations
grep -RIn 'CREATE TABLE public\.' supabase/migrations/
grep -RIn 'ENABLE ROW LEVEL SECURITY\|FORCE ROW LEVEL SECURITY' supabase/migrations/
# Firebase / Firestore
cat firestore.rules # confirm no `if true;` matchesCada CREATE TABLE public.* precisa de um ENABLE ROW LEVEL SECURITY correspondente e de pelo menos uma política. As regras do Firestore devem definir o escopo das leituras para request.auth.uid.
Tratamento de autenticação e sessão
grep -RIn 'getSession()' src/ # should be getUser() server-side
grep -RIn 'localStorage\.\(set\|get\)Item.*token' src/
grep -RIn 'jwt.verify.*\(noVerify\|skipVerify\)' src/As rotas renderizadas pelo servidor devem usar supabase.auth.getUser() — verifica com o backend. getSession() lê um cookie não verificado. Os tokens em localStorage são acessíveis a qualquer script executado na página.
Cabeçalhos e middleware
# Confirm middleware location for src/ layouts
ls src/middleware.ts middleware.ts 2>&1
# Look for CSP and security headers
grep -RIn 'Content-Security-Policy\|Strict-Transport-Security' src/Com o layout src/, apenas src/middleware.ts é selecionado. Se o seu arquivo de middleware estiver na raiz do projeto, Next.js o ignorará silenciosamente e sua lógica CSP / auth-refresh nunca será executada.
Endurecimento no momento da implantação
Assim que a fonte estiver limpa, bloqueie como o aplicativo chega à produção.
Etapa 1: ambientes separados
Vercel: três ambientes — Production (seu domínio de produção), Visualização (PR / implantações de teste), Desenvolvimento (local). Cada um recebe seu próprio conjunto env-var. As teclas Live Stripe / Anthropic / Supabase nunca alcançam a Pré-visualização; As chaves de visualização nunca alcançam Production. As ramificações são enviadas para visualização automaticamente; mesclar para main implanta em Production.
Etapa 2: CSP estrito via middleware
Gere um nonce por solicitação e injete-o em Content-Security-Policy. Next.js aplica automaticamente o nonce às suas próprias tags de script quando você define o cabeçalho da solicitação x-nonce.
// src/middleware.ts
import { NextResponse, type NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const nonce = crypto.randomUUID().replace(/-/g, '');
const csp = [
`script-src 'nonce-${nonce}' 'strict-dynamic'`,
`style-src 'self' 'unsafe-inline'`,
`img-src 'self' data: https:`,
`connect-src 'self' https://*.supabase.co`,
`object-src 'none'`,
`base-uri 'self'`,
`frame-ancestors 'none'`,
].join('; ');
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-nonce', nonce);
const response = NextResponse.next({ request: { headers: requestHeaders } });
response.headers.set('Content-Security-Policy', csp);
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
return response;
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};Etapa 3: Forçar RLS em todas as tabelas públicas
RLS isn't enabled by default on tables you create in SQL or migrations. Enable it on every exposed table and pair each one with explicit policies per role — that is what stops the anon and authenticated roles. FORCE only makes the table owner obey RLS too.
-- supabase/migrations/XXXX_rls.sql
alter table public.profiles enable row level security;
alter table public.profiles force row level security;
create policy "profiles: read own"
on public.profiles for select
using (auth.uid() = id);
create policy "profiles: update own"
on public.profiles for update
using (auth.uid() = id)
with check (auth.uid() = id);Etapa 4: verificação de autenticação somente do servidor em cada rota API
Cada rota API de mudança de estado verifica o lado do servidor do chamador com supabase.auth.getUser(). O objeto do usuário se torna a fonte da verdade para user_id — nunca confie no corpo da solicitação para defini-lo.
// src/app/api/items/route.ts
import { NextResponse, type NextRequest } from 'next/server';
import { createClient } from '@/lib/supabase/server';
export async function POST(request: NextRequest) {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) return NextResponse.json({ error: 'unauthorized' }, { status: 401 });
const body = await request.json();
const { data, error } = await supabase
.from('items')
.insert({ ...body, user_id: user.id }) // server-supplied, not from body
.select()
.single();
if (error) return NextResponse.json({ error: error.message }, { status: 400 });
return NextResponse.json(data);
}Etapa 5: faça proxy reverso de suas análises
Proxying análises por meio de seu próprio domínio evita bloqueadores de anúncios e permite que seu CSP connect-src 'self' permaneça restrito. O mesmo padrão funciona para coletores de eventos personalizados PostHog, Plausíveis, Umami.
// src/app/api/posthog/[...path]/route.ts
import { type NextRequest } from 'next/server';
const UPSTREAM = 'https://us.i.posthog.com';
export async function POST(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
const { path } = await params;
const url = `${UPSTREAM}/${path.join('/')}`;
return fetch(url, {
method: 'POST',
headers: { 'content-type': req.headers.get('content-type') ?? 'application/json' },
body: await req.text(),
});
}Etapa 6: proteção de redirecionamento aberto na rejeição pós-autenticação
Os fluxos de entrada/inscrição geralmente aceitam um parâmetro de consulta next. Rejeite qualquer coisa que não seja um caminho do mesmo site - comece com / e nunca // (relativo ao protocolo, envia os usuários para fora do site).
function safeNext(raw: string | null): string {
if (!raw) return '/dashboard';
if (!raw.startsWith('/') || raw.startsWith('//')) return '/dashboard';
return raw;
}Em andamento: monitoramento e nova verificação
O desvio acontece em cada implantação. Trate a segurança como um loop, não como uma lista de verificação concluída.
Verifique seu domínio de produção
Dashboard → Domains → add your production domain → DNS TXT or HTTP-file verification. Active scans require Hobby or above; scheduled re-scans require Pro or Unlimited.
Agende novas verificações passivas
Scheduled re-scans are available on Pro and Unlimited for verified domains. Free and Hobby scans are manual. Scheduled scans use your plan allowance. Configure completion email preferences and a scan.completed webhook if needed.
# Or from CI, via the REST API:
curl -X POST https://fixvibe.app/api/v1/scans \
-H "authorization: Bearer $FIXVIBE_TOKEN" \
-H "content-type: application/json" \
-d '{"target":"https://your-app.com"}'Ativar verificações API-ativas (opcional)
Se você deseja sondagem ativa automatizada (SQLi / XSS / IDOR caminhada / etc.), ative-a por domínio em Painel → Domínios → API ativo. A autorização é durável, expira em 90 dias e é instantaneamente revogável. Combine com o webhook scan.active_api.first_used para que a primeira verificação ativa automatizada após a ativação chegue ao seu alerta.
Conecte as descobertas ao seu fluxo de trabalho AI
On Hobby or above, create an API token at Account → API tokens and configure the MCP server (/docs/mcp) in your coding tool. Ask your agent to run an authorized scan and inspect the highest-severity findings. Code fixes can use remediation prompts; provider and DNS fixes may need manual operator steps.
Detecção de ameaças ao vivo (Unlimited)
Periodic certificate-transparency, DNS, JS-bundle, and threat-intelligence checks report observed changes on supported signals. Alerts depend on successful polling and source availability; they do not establish continuous or complete security coverage.
Padrões reais de falhas e suas soluções
Five common patterns in AI-generated apps, each with the actual fix:
- Chave de função de serviço em um componente cliente
Symptom: FixVibe reports an exposed Supabase service-role key on the production URL. Cause: an autocomplete pasted
createClient(URL, SERVICE_ROLE_KEY)into a React component. Fix: move the service client tosrc/lib/supabase/service.tswithimport 'server-only'at the top; create a parallelsrc/lib/supabase/client.tsusing the anon key for client-side use; rotate the service-role key via Supabase Studio. - Regras do Firestore deixadas no modo de teste
Symptom: a high-severity open Firebase rules finding. Cause: generated rules read
allow read, write: if request.time < timestamp.date(2026, 6, 1);— a time-bounded "allow all". Fix: scope each rule to the authenticated user —match /users/{userId}/posts/{postId} { allow read, write: if request.auth.uid == userId; }— and re-deployfirebase deploy --only firestore:rules. - Permissivo CORS sobrevivendo em produção
Symptom: a high-severity CORS misconfiguration finding. Cause: generated Express middleware:
app.use(cors({ origin: '*' })). Fix: allowlist your frontend origin:app.use(cors({ origin: ['https://your-app.com'], credentials: true })). For Next.js API routes, setAccess-Control-Allow-Originexplicitly in the response. - RLS ativado, mas não forçado
Symptom: FixVibe reports that anonymous visitors can read a public table even though RLS looks enabled in the dashboard. Cause: RLS is on, but a policy such as
USING (true)lets theanonrole through, or the migration that tightened it never ran in production. Fix: replace the permissive policy with one scoped toauth.uid(), apply the migration, and re-scan. - IDOR-IDs transitáveis não assinados
Symptom: an active scan on your verified domain reports that one user can read another user's records at
/api/items/1,/api/items/2, ... Cause: the API handler trusts the path param and queries Postgres without an ownership predicate. Fix: add.eq('user_id', user.id)on every read query, or move to signed URLs / UUIDs scoped under/api/users/[uid]/items/[id].
O ciclo de segurança do código vibe
O objetivo não é a segurança perfeita; está eliminando os frutos mais fáceis de alcançar AI que as ferramentas perdem consistentemente para que você possa continuar enviando rapidamente.
- Generate fast — use Cursor, Claude Code, Lovable, Bolt. Esse é o ponto.
- Audit immediately — execute o conjunto grep acima, verifique RLS, verifique CSP, revise o limite de autenticação.
- Harden at deploy — middleware, separação de ambiente, CSP nonce, HSTS, verificação de autenticação somente de servidor.
- Monitor — FixVibe passivo diariamente, ativo semanalmente em um domínio verificado, webhooks para Slack, detecção de ameaças em Unlimited.
- Fix fast — use FixVibe coding-agent prompts for code/config findings and operator steps for DNS, provider, secret-rotation, or manual-review findings. Re-deploy, re-scan, close the loop.
Próximas etapas
Para obter o pano de fundo conceitual sobre DAST vs SAST e por que os aplicativos gerados por AI- precisam de sua própria verificação, leia AI-generated code security scanning. Para uma auditoria pré-envio de referência rápida, consulte vibe coding security checklist.
