// 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:
- 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).
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
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 stringsQualsiasi 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
# 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;` matchesOgni 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
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
# 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.
// 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 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);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.
// 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.
// 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).
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 → add your production domain → DNS TXT or HTTP-file verification. Active scans require Hobby or above; scheduled re-scans require Pro or Unlimited.
Pianifica nuove scansioni passive
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"}'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
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.
Rilevamento delle minacce in tempo reale (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.
Modelli di fallimento reali e loro soluzioni
Five common patterns in AI-generated apps, each with the actual fix:
- Chiave del ruolo del servizio in un componente client
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. - Regole Firestore lasciate in modalità test
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 sopravvissuto alla produzione
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 abilitato ma non forzato
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-ID pedonabili non firmati
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].
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.
- Generate fast: usa Cursor, Claude Code, Lovable, Bolt. Questo è il punto.
- Audit immediately: esegui il set grep sopra, controlla RLS, verifica CSP, rivedi il limite di autenticazione.
- Harden at deploy: middleware, separazione degli ambienti, CSP nonce, HSTS, verifica dell'autenticazione solo server.
- Monitor — FixVibe passivo giornaliero, attivo settimanalmente su un dominio verificato, webhook per Slack, rilevamento delle minacce su 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.
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.
