FixVibe

// docs / security guides / hardening

Come mettere in sicurezza un'app costruita con tool di coding IA

Una guida passo passo per il rafforzamento delle app create con Cursor, Claude Code, Lovable, Bolt, v0, Replit o Windsurf. Quattro fasi: capire perché le AI-app generate falliscono in modo diverso, eseguire un controllo immediato della base di codice, rafforzarle al momento della distribuzione, quindi continuare a monitorare. Supponente, narrativo, con frammenti reali che puoi copiare.

Perché AI-le app generate falliscono in modo diverso

Le app codificate da Vibe possono essere sicure. Hanno bisogno di un ulteriore passaggio di audit perché le modalità di fallimento sono strutturali, non trascurate:

  • Cursor inlines hardcoded keys. Chiedi a Cursor di "correggere l'errore di autenticazione" e incolla un esempio Supabase che presuppone un client con ruolo di servizio. La chiave finisce nella parte superiore di un componente della pagina. Sia il client anon che il client di servizio coesistono; entrambe le navi.
  • Claude Code defaults to permissive CORS. I gestori Express/Fastify generati vengono spediti con cors({ origin: '*' }) perché è il modo più veloce per ottenere un'anteprima funzionante. Il middleware non riceve mai un secondo passaggio.
  • Lovable and v0 skip the rules file. I progetti supportati da Firestore generano il modello dati ma raramente toccano firestore.rules. Le regole della modalità test scadono silenziosamente e bloccano il database senza alcun avviso per l'utente.
  • Bolt skips RLS migrations. Bolt genera uno schema Supabase e una superficie CRUD che utilizza la chiave anon. ENABLE ROW LEVEL SECURITY non entra mai nella migrazione. Gli utenti anonimi possono leggere o scrivere qualsiasi riga.
  • Windsurf trusts unsigned IDs. Generato GET /api/items/[id] legge il parametro e interroga Postgres senza verificare la proprietà. Il modello è sufficientemente comune da essere rilevato dalla sonda attiva active.idor-walking in una singola scansione.

L'audit immediato: grep la tua base di codice per i modelli di rischio

Prima di indurire qualcosa, trova ciò che è già rotto. Questi greps richiedono ciascuno meno di un minuto:

Segreti e chiavi del provider

bash
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 strings

Qualsiasi colpo richiede la cancellazione e la rotazione della chiave. Propannello di controllo vider: Supabase → Impostazioni → API, Stripe → Sviluppatori → Tasti API, console Anthropic / OpenAI.

Controlli di accesso al database

bash
# 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;` matches

Ogni CREATE TABLE public.* necessita di un ENABLE ROW LEVEL SECURITY corrispondente e di almeno una policy. Le regole di Firestore devono avere come ambito le letture su request.auth.uid.

Gestione dell'autenticazione e della sessione

bash
grep -RIn 'getSession()' src/   # should be getUser() server-side
grep -RIn 'localStorage\.\(set\|get\)Item.*token' src/
grep -RIn 'jwt.verify.*\(noVerify\|skipVerify\)' src/

Le rotte renderizzate dal server devono utilizzare supabase.auth.getUser(): verifica con il backend. getSession() legge un cookie non verificato. I token in localStorage sono accessibili a qualsiasi script eseguito sulla pagina.

Intestazioni e middleware

bash
# 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/

Con il layout src/ viene prelevato solo src/middleware.ts. Se il file middleware si trova nella radice del progetto, Next.js lo ignora silenziosamente e la logica CSP / auth-refresh non viene mai eseguita.

Rafforzamento al momento della distribuzione

Una volta pulita l'origine, blocca il modo in cui l'app raggiunge la produzione.

Passaggio 1: ambienti separati

Vercel: tre ambienti: Production (il tuo dominio di produzione), Anteprima (PR / staging deploys), Sviluppo (locale). Ognuno ottiene il proprio set env-var. I tasti Live Stripe / Anthropic / Supabase non raggiungono mai l'anteprima; Le chiavi di anteprima non raggiungono mai Production. I rami vengono inviati automaticamente all'anteprima; unisci in main distribuisci in Production.

Passaggio 2: rigoroso CSP tramite middleware

Genera un nonce per richiesta, quindi inseriscilo in Content-Security-Policy. Next.js applica automaticamente il nonce ai propri tag di script quando imposti l'intestazione della richiesta x-nonce.

ts
// 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).*)'],
};

Passaggio 3: forza RLS su ogni tabella pubblica

RLS non è abilitato per impostazione predefinita e non viene applicato ai proprietari delle tabelle a meno che tu non lo FORCE. Associa ciascuna tabella a policy esplicite per ruolo.

sql
-- 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);

Passaggio 4: verifica dell'autenticazione solo del server su ogni percorso API

Ogni route API che cambia stato verifica il lato server del chiamante con supabase.auth.getUser(). L'oggetto utente diventa la fonte della verità per user_id: non fidarti mai del corpo della richiesta per impostarlo.

ts
// 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);
}

Passaggio 5: proxy inverso per le tue analisi

Proxying Analytics tramite il tuo dominio evita i blocchi pubblicitari e lascia che il tuo CSP connect-src 'self' rimanga ristretto. Lo stesso modello funziona per PostHog, Plausible, Umami, sink di eventi personalizzati.

ts
// 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(),
  });
}

Passaggio 6: aprire la protezione del reindirizzamento sul mancato recapito post-autenticazione

I flussi di accesso/iscrizione accettano comunemente un parametro di query next. Rifiuta tutto ciò che non è un percorso dello stesso sito: inizia con / e mai // (relativo al protocollo, invia gli utenti fuori sito).

ts
function safeNext(raw: string | null): string {
  if (!raw) return '/dashboard';
  if (!raw.startsWith('/') || raw.startsWith('//')) return '/dashboard';
  return raw;
}

In corso: monitoraggio e nuova scansione

La deriva avviene ad ogni distribuzione. Tratta la sicurezza come un ciclo, non come una lista di controllo da completare.

Verifica il tuo dominio di produzione

Dashboard → Domains → aggiungi il tuo dominio di produzione → DNS TXT o HTTP-verifica del file (passaggio singolo). Una volta verificate, le scansioni attive diventano disponibili ed è possibile abilitare le nuove scansioni pianificate.

Pianifica nuove scansioni passive

Tutti i giorni su Hobby, ogni 3 ore su Pro, ogni ora su Unlimited. Ogni esecuzione ti invia un'e-mail se viene visualizzata una nuova scoperta e attiva un webhook scan.completed se sei iscritto.

bash
# 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"}'

Abilita API-scansioni attive (opzionale)

Se desideri il sondaggio attivo automatizzato (SQLi / XSS / IDOR camminando / ecc.), attivalo per dominio in Dashboard → Domini → API attivo. L'autorizzazione è durevole, scadenza 90 giorni, immediatamente revocabile. Associalo al webhook scan.active_api.first_used in modo che la prima scansione attiva automatizzata dopo l'abilitazione raggiunga i tuoi avvisi.

Collega i risultati al tuo flusso di lavoro AI

Mint an API token at Account → API tokens, then configure the MCP server (/docs/mcp) in Claude Desktop / Cursor / Continue. Ask your agent: "Run a scan on staging and show me the highest-severity findings." The agent calls FixVibe, fetches the report, and renders categorized remediation guidance so code/config fixes become prompts and DNS/provider/manual fixes become operator steps.

Rilevamento delle minacce in tempo reale (Unlimited)

Le differenze nel log di trasparenza dei certificati mostrano i nuovi certificati TLS emessi per il tuo dominio. DNS-record diff rileva modifiche non autorizzate. JS-Il monitoraggio segreto del pacchetto si attiva nel momento in cui una nuova chiave raggiunge un pacchetto spedito. I feed di informazioni sulle minacce (Spamhaus, URLhaus) segnalano il tuo dominio se è elencato.

Modelli di fallimento reali e loro soluzioni

Cinque modelli provenienti da scansioni di produzione su migliaia di app AI-generate, ciascuno con la correzione effettiva:

  1. Chiave del ruolo del servizio in un componente client

    Symptom: baas.supabase-service-key ricerca sulla produzione URL. Cause: un Cursor completamento automatico incollato createClient(URL, SERVICE_ROLE_KEY) in un componente React. Fix: sposta il client del servizio su src/lib/supabase/service.ts con import 'server-only' in alto; creare un src/lib/supabase/client.ts parallelo utilizzando la chiave anon per l'utilizzo lato client; ruotare la chiave del ruolo di servizio tramite Supabase Studio.

  2. Regole Firestore lasciate in modalità test

    Symptom: baas.firebase-rules risultato di gravità elevata. Cause: regole generate leggi allow read, write: if request.time < timestamp.date(2026, 6, 1); — un "consenti tutto" limitato nel tempo. Fix: applica ogni regola all'utente autenticato — match /users/{userId}/posts/{postId} { allow read, write: if request.auth.uid == userId; } — e ridistribuisci firebase deploy --only firestore:rules.

  3. Permissivo CORS sopravvissuto alla produzione

    Symptom: active.cors gravità elevata. Cause: ha generato il middleware Express: app.use(cors({ origin: '*' })). Fix: inserisci nella lista consentita l'origine del frontend: app.use(cors({ origin: ['https://your-app.com'], credentials: true })). Per le rotte Next.js API, impostare esplicitamente Access-Control-Allow-Origin nella risposta.

  4. RLS abilitato ma non forzato

    Symptom: attivo baas.supabase-rls segnala che il ruolo anon può scrivere su una tabella pubblica anche se RLS è abilitato nel dashboard. Cause: ENABLE senza FORCE lascia il proprietario della tabella esente e le migrazioni vengono eseguite come proprietario. Fix: aggiungi alter table public.items force row level security; alla migrazione. Ridistribuire.

  5. IDOR-ID pedonabili non firmati

    Symptom: active.idor-walking segnala che l'utente anon può leggere /api/items/1, /api/items/2, ... tra tenant. Cause: il gestore API si fida del parametro del percorso e interroga Postgres senza un predicato di proprietà. Fix: aggiungi .eq('user_id', user.id) su ogni query di lettura o passa a URL/UUID firmati con ambito /api/users/[uid]/items/[id].

Il circuito di sicurezza del codice vibe

L'obiettivo non è la sicurezza perfetta; sta eliminando i frutti a portata di mano che gli strumenti AI perdono costantemente in modo da poter continuare a spedire velocemente.

  1. Generate fast: usa Cursor, Claude Code, Lovable, Bolt. Questo è il punto.
  2. Audit immediately: esegui il set grep sopra, controlla RLS, verifica CSP, rivedi il limite di autenticazione.
  3. Harden at deploy: middleware, separazione degli ambienti, CSP nonce, HSTS, verifica dell'autenticazione solo server.
  4. Monitor — FixVibe passivo giornaliero, attivo settimanalmente su un dominio verificato, webhook per Slack, rilevamento delle minacce su Unlimited.
  5. 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.

Prossimi passi

Per il contesto concettuale su DAST vs SAST e perché le app generate da AI- necessitano della propria scansione, leggi AI-generated code security scanning. Per un audit pre-nave di riferimento rapido, consultare vibe coding security checklist.

// scansiona la tua app

Smetti di leggere. Inizia a trovare le falle nella tua.

Incolla una URL — FixVibe esegue ogni controllo passivo di questa guida più 200 altri in meno di un minuto. Gratis, senza installazione, senza carta.

  • Free tier — 3 scansioni / mese, senza carta.
  • Scansioni passive contro qualsiasi URL — nessuna verifica di dominio.
  • Ottimizzato per Cursor, Claude Code, Lovable, Bolt, v0, Replit.
  • Coding-agent prompts for code/config findings, plus operator steps for DNS/provider fixes.