Quando lanci un'app con Cursor, Lovable o v0, il server risponde a ogni richiesta con una serie di header HTTP: istruzioni invisibili che il browser segue prima di disegnare qualsiasi cosa. Una manciata di essi decide buona parte della sicurezza del tuo sito, e quasi nessun template li porta attivati. Questa guida spiega cosa fa ciascuno, quale valore impostare, gli errori che rompono le cose e come configurarli su Vercel, Next.js, Nuxt, Netlify, Cloudflare o nginx.
Cos'è un header e come vedere i tuoi
Un header è una riga di testo che accompagna la risposta, del tipo Nome: valore. Il browser la legge e cambia comportamento: caricare o no uno script, forzare HTTPS, permettere o no che la tua pagina finisca in un iframe. Puoi vederli negli strumenti per sviluppatori (F12, scheda Network, clic sul documento principale, sezione Response Headers) o dal terminale:
curl -I https://tuodominio.com
Se in quell'elenco non compaiono quelli qui sotto, non sono configurati. Non ci sono valori di default che ti salvino.
I sette che contano
Content-Security-Policy (CSP)
È la regina. Dice al browser da quali origini può caricare script, stili, immagini, font e connessioni. Se un attaccante riesce a iniettare uno <script> nella tua pagina, una CSP fatta bene gli impedisce di eseguirsi. Un punto di partenza onesto per un'app moderna:
Content-Security-Policy: default-src 'self'; script-src 'self' https://js.stripe.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://*.supabase.co; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
L'errore tipico è mettere 'unsafe-inline' in script-src perché "sennò non funziona": questo butta via la protezione principale. I framework moderni risolvono gli script inline con un nonce (un valore casuale per richiesta) o con hash. E il modo sicuro per iniziare è la modalità report: l'header Content-Security-Policy-Report-Only non blocca nulla, ti racconta solo cosa bloccherebbe. Lascialo così qualche giorno, esamina i report e passalo all'header reale quando non rompe più nulla.
Strict-Transport-Security (HSTS)
Obbliga il browser a usare HTTPS per il tempo indicato, anche se l'utente scrive http:// o clicca un vecchio link. Evita che qualcuno sulla stessa rete declassi la connessione. Il valore abituale:
Strict-Transport-Security: max-age=31536000; includeSubDomains
Attivalo solo quando tutto il tuo dominio e i suoi sottodomini vanno in HTTPS: se lo imposti con un sottodominio ancora in HTTP, lo rendi inaccessibile per un anno nei browser che l'hanno visto. Inizia con un max-age breve se hai dubbi.
X-Frame-Options e frame-ancestors
Impediscono che il tuo sito venga caricato dentro un <iframe> di un'altra pagina. Senza, qualcuno può incorporare la tua app in un sito falso e ingannare gli utenti perché clicchino dove non dovrebbero: è il clickjacking. X-Frame-Options: DENY o SAMEORIGIN lo risolve nei browser vecchi; la versione moderna è la direttiva frame-ancestors dentro la CSP. Metti entrambe.
X-Content-Type-Options
Un solo valore: nosniff. Impedisce al browser di indovinare il tipo di un file ed eseguire come script qualcosa che hai servito come testo o immagine. È una riga; non ha scuse per mancare.
Referrer-Policy
Controlla quanta informazione dell'URL di origine viene inviata navigando verso un altro sito o caricando una risorsa esterna. Senza controllo, puoi far trapelare percorsi privati o token che viaggiano nell'URL. strict-origin-when-cross-origin è l'equilibrio sensato: il tuo dominio sì, il percorso completo no.
Permissions-Policy
Dichiara quali capacità del browser possono usare la tua pagina e i suoi iframe: fotocamera, microfono, geolocalizzazione, pagamenti. Se non le usi, disattivale; così nemmeno uno script iniettato o un iframe di terzi può farlo. Per esempio: camera=(), microphone=(), geolocation=().
Cross-Origin-Opener-Policy
Meno conosciuto: isola la tua finestra da quelle che l'hanno aperta o che hai aperto con window.open, il che taglia una famiglia di attacchi tra schede. same-origin è il valore abituale, a meno che tu non dipenda da popup di terze parti (alcuni login OAuth), dove conviene same-origin-allow-popups.
Quelli che conviene togliere
X-Powered-By e un header Server con versione annunciano il tuo stack esatto a chi cerca falle note. In Next.js si toglie con poweredByHeader: false; in Express, con app.disable('x-powered-by'). E X-XSS-Protection è obsoleto: i browser moderni lo ignorano e in quelli vecchi ha causato bug. Non metterlo, o impostalo a 0.
Come impostarli a seconda dell'hosting
Vercel, per qualsiasi framework, in vercel.json:
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "Strict-Transport-Security", "value": "max-age=31536000; includeSubDomains" },
{ "key": "X-Frame-Options", "value": "SAMEORIGIN" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
{ "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }
]
}
]
}
Next.js: la funzione headers() di next.config.js, con la stessa lista; la CSP con nonce si genera nel middleware. Nuxt: routeRules in nuxt.config.ts con un oggetto headers, o un plugin server se ti serve un nonce. Netlify: un file _headers nella cartella pubblicata:
/*
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Cloudflare: regole di trasformazione degli header di risposta nel pannello, senza toccare codice. nginx su un VPS:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
Lascio la CSP fuori da questi blocchi apposta: è l'unica che dipende da quali script e servizi usa la tua app, e conviene costruirla in modalità report prima di fissarla.
Errori che rompono le cose
- HSTS prima di avere tutto in HTTPS. Lascia fuori chi ha un sottodominio in HTTP. Verifica prima, attiva dopo.
- CSP senza nonce in Next.js o Nuxt. Entrambi iniettano script inline per idratare la pagina; senza nonce, la CSP rigorosa li blocca e l'app resta bianca. Usa il meccanismo di nonce del framework.
- Header solo sull'HTML. Anche le route API e i file statici ne hanno bisogno, soprattutto
nosniff. Applica la regola a/(.*). - Header duplicati. Se il CDN e l'app aggiungono lo stesso, il browser può tenere il più restrittivo o ignorarli entrambi. Decidi un posto solo.
- Dimenticare i sottodomini (app., api., docs.): ognuno risponde con i propri header.
Verificali dall'esterno
Questi header sono informazione pubblica: chiunque può leggerli, te compreso. Il verificatore gratuito di header ti elenca quelli mancanti e quelli sbagliati in pochi secondi. E Scanaris fa la verifica completa in sola lettura, con una quarantina di altre cose, e per ogni header assente ti dà un fix prompt pronto da incollare nella tua IA con la riga esatta per il tuo hosting. Non sfrutta nulla e non tocca il tuo server: legge e ti dice cosa attivare. Per il contesto completo, c'è la guida alla sicurezza per app create con l'IA e la checklist di lancio.
Domande frequenti
Gli header rallentano il sito?
No. Sono qualche centinaio di byte per risposta e il browser li elabora in microsecondi. HSTS lo accelera perfino, perché evita il redirect da HTTP a HTTPS nelle visite successive.
Cosa succede se un header rompe qualcosa in produzione?
Toglilo o allentalo e ridistribuisci: l'effetto è immediato, tranne per HSTS, che i browser ricordano per il max-age. Per questo la CSP si prova in modalità report e HSTS si attiva per ultimo.
Mi serve la CSP se la mia app è piccola?
Se la tua app ha un form o mostra contenuti degli utenti, sì: è la difesa che trasforma un XSS in un incidente minore. Inizia dagli altri header, che sono banali, e aggiungi la CSP in modalità report quando puoi dedicarle un pomeriggio.