Header di sicurezza spiegati: CSP, HSTS e gli altri
Guida pratica a CSP con nonce e strict-dynamic, HSTS e preload, frame-ancestors, Referrer-Policy, Permissions-Policy e COOP, con una config Next.js.
· 7 min di lettura · Lina Source LLC
Gli header di sicurezza sono istruzioni che il tuo server dà al browser: esegui solo questi script, parlami solo tramite HTTPS, non lasciare che altri siti mettano questa pagina in un frame. Non correggono i bug nel tuo codice, ma limitano ciò che un attaccante può farne. Una falla di cross-site scripting dietro una CSP rigorosa è un problema molto più piccolo della stessa falla senza CSP.
La maggior parte di questi header è una riga di configurazione. L'eccezione è la CSP, che richiede pianificazione. Questa guida spiega che cosa fa ciascun header, quali valori sono sensati per una tipica applicazione web e come adottarli senza rompere la produzione.
Content-Security-Policy
La CSP indica al browser quali origini di script, stili, immagini, frame e connessioni sono consentite. Il suo compito principale è impedire l'esecuzione degli script iniettati. Una allowlist di domini sembra l'approccio ovvio, ma ha una lunga storia di aggiramenti: qualsiasi CDN consentita che ospiti contenuti caricati dagli utenti o vecchie versioni di librerie può essere usata per caricare script controllati dall'attaccante.
Nonce e strict-dynamic
L'approccio che regge è una policy basata su nonce. Il server genera un valore casuale per ogni risposta, lo inserisce nell'header CSP e lo aggiunge come attributo nonce a ciascun tag script che produce. I tag script iniettati non conoscono il nonce e vengono bloccati. Aggiungere 'strict-dynamic' consente agli script caricati da uno script fidato di caricarne altri, il che permette a bundler e tag manager di continuare a funzionare senza elencare ogni dominio.
Content-Security-Policy:
default-src 'self';
script-src 'nonce-4AEemGb0xJptoIGFP3Nd' 'strict-dynamic';
style-src 'self' 'nonce-4AEemGb0xJptoIGFP3Nd';
img-src 'self' data: https:;
connect-src 'self';
object-src 'none';
base-uri 'none';
frame-ancestors 'none';
form-action 'self';
upgrade-insecure-requestsL'header viene inviato su una sola riga; qui è spezzato per leggibilità. Alcune direttive di quella policy fanno più di quanto sembri. object-src 'none' blocca i contenuti dei plugin legacy. base-uri 'none' impedisce a un tag base iniettato di dirottare gli URL relativi degli script. form-action 'self' impedisce ai form iniettati di inviare credenziali altrove. Il nonce deve essere imprevedibile e nuovo a ogni risposta, il che significa che le pagine che lo usano non possono essere servite da una cache statica.
Adottala con Report-Only
Una CSP rigorosa distribuita alla cieca romperà qualcosa: uno snippet di analytics, un gestore di eventi inline, un widget di terze parti. Pubblica prima la policy come Content-Security-Policy-Report-Only. Il browser non applica nulla ma segnala ogni violazione all'endpoint indicato nella direttiva report-to o report-uri. Raccogli i report per un po', correggi o consenti ciò che è legittimo, poi cambia il nome dell'header per passare all'applicazione effettiva. Mantieni attive le segnalazioni anche dopo, perché le nuove violazioni sono o regressioni o attacchi. Aspettati rumore nei report dovuto alle estensioni del browser, che iniettano i propri script nelle pagine; filtrale in base all'origine prima di decidere che cosa consentire.
Strict-Transport-Security
L'HSTS dice al browser di usare HTTPS per il tuo dominio per un periodo stabilito, anche se un utente digita http:// o clicca su un vecchio link. Questo chiude la finestra in cui un attaccante di rete potrebbe intercettare la prima richiesta HTTP in chiaro e rimuovere il redirect verso HTTPS. I browser onorano l'header solo quando arriva tramite HTTPS, e inoltre non permettono agli utenti di ignorare gli errori di certificato su un dominio con HSTS: è proprio questo il punto, ma è anche il rischio se un certificato scade.
Parti da un max-age breve, per esempio un giorno, verifica che non si rompa nulla, poi portalo a uno o due anni. includeSubDomains estende la regola a ogni sottodominio, quindi verifica prima che tutti servano HTTPS valido, compresi vecchi siti di marketing e strumenti interni sullo stesso dominio.
La direttiva preload, insieme all'invio alla lista di preload dei browser, inserisce il tuo dominio nei browser in modo che anche la primissima visita usi HTTPS. Richiede un max-age di almeno un anno e includeSubDomains. Trattala come una porta a senso unico: la rimozione dalla lista è possibile ma impiega molto tempo per arrivare agli utenti, e nel frattempo qualsiasi sottodominio che non sappia fare HTTPS diventa irraggiungibile.
Gli header da una riga
- X-Content-Type-Options: nosniff. Impedisce ai browser di indovinare un content type diverso da quello che hai dichiarato, il che evita che un file caricato e servito come testo venga eseguito come script.
- frame-ancestors (nella CSP) e X-Frame-Options: DENY. Controllano chi può incorporare le tue pagine in un frame, ed è la difesa contro il clickjacking. I browser moderni usano frame-ancestors e ignorano X-Frame-Options quando sono impostati entrambi; mantieni X-Frame-Options per i client più vecchi. Usa 'self' o SAMEORIGIN se incorpori le tue stesse pagine.
- Referrer-Policy: strict-origin-when-cross-origin. Invia agli altri siti solo la tua origin, non l'intero percorso e la query string. Così i token e gli ID presenti negli URL non trapelano verso terze parti. Usa no-referrer per le pagine particolarmente sensibili.
- Permissions-Policy. Disattiva le funzionalità del browser che non usi, come camera=(), microphone=(), geolocation=() e payment=(), così il codice iniettato o incorporato non può richiederle.
- Cross-Origin-Opener-Policy: same-origin. Colloca la tua pagina in un proprio gruppo di contesti di navigazione, così una finestra aperta da un altro sito non può conservarne un riferimento. Usa same-origin-allow-popups se dipendi da popup di OAuth o di pagamento.
- Cross-Origin-Resource-Policy: same-origin oppure same-site. Dice ai browser di non permettere ad altre origin di caricare le tue risposte come immagini, script o altre sottorisorse. Usa same-site se servi asset da un sottodominio fratello, e cross-origin per gli asset pensati per essere incorporati altrove.
Puoi abbandonare X-XSS-Protection. Il filtro che controllava è stato rimosso dai browser moderni, e la CSP è il suo sostituto. Se uno scanner insiste, impostalo a 0. Allo stesso modo, evita gli header che aggiungono soltanto informazioni utili a un attaccante: X-Powered-By e i banner Server dettagliati rivelano gratuitamente il tuo framework e la sua versione. In Next.js, imposta poweredByHeader: false nella configurazione per rimuovere il primo.
Una configurazione Next.js
Gli header statici stanno in next.config.ts, dove la funzione headers() li applica a ogni route. I valori qui sotto sono un'impostazione predefinita sensata per un'applicazione che non si incorpora in frame, non usa la fotocamera né la posizione e serve i propri asset. Adatta ciascuno a ciò che la tua applicazione fa davvero, anziché copiarli alla cieca.
// next.config.ts
import type { NextConfig } from "next";
const securityHeaders = [
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains",
},
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "X-Frame-Options", value: "DENY" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{
key: "Permissions-Policy",
value: "camera=(), microphone=(), geolocation=(), payment=()",
},
{ key: "Cross-Origin-Opener-Policy", value: "same-origin" },
{ key: "Cross-Origin-Resource-Policy", value: "same-origin" },
];
const nextConfig: NextConfig = {
async headers() {
return [{ source: "/(.*)", headers: securityHeaders }];
},
};
export default nextConfig;La CSP ha bisogno di un nonce nuovo a ogni richiesta, quindi viene impostata nel middleware. Next.js legge il nonce dall'header Content-Security-Policy della richiesta e lo applica agli script del framework durante il rendering. Puoi leggerlo in un server component con headers().get("x-nonce") per i tuoi tag script.
// middleware.ts (in Next.js 16 il file è proxy.ts e la funzione è proxy)
import { NextResponse, type NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
const csp = [
"default-src 'self'",
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
`style-src 'self' 'nonce-${nonce}'`,
"img-src 'self' blob: data:",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
"frame-ancestors 'none'",
"upgrade-insecure-requests",
].join("; ");
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-nonce", nonce);
requestHeaders.set("Content-Security-Policy", csp);
const response = NextResponse.next({ request: { headers: requestHeaders } });
response.headers.set("Content-Security-Policy", csp);
return response;
}
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};Le pagine renderizzate con un nonce devono essere renderizzate dinamicamente, perché una pagina statica riutilizzerebbe un unico nonce per tutti i visitatori. Durante l'adozione, cambia il nome dell'header in entrambi i punti in Content-Security-Policy-Report-Only e aggiungi un endpoint di reporting. In sviluppo, React può richiedere 'unsafe-eval' in script-src per le sue funzionalità di debug; aggiungilo solo quando NODE_ENV è development.
Come verificare
- Esegui curl -sI https://your-domain.example/ sulla home page, su una pagina dinamica, su una route API e su un asset statico, e leggi gli header di ciascuna. Header impostati solo sulle pagine HTML sono una lacuna comune.
- Apri gli strumenti per sviluppatori del browser. Le violazioni della CSP compaiono nella console con la direttiva e la risorsa bloccata, e la scheda Network mostra gli header effettivamente serviti.
- Controlla sia dietro la tua CDN o il reverse proxy sia all'origine. I proxy a volte rimuovono, duplicano o sovrascrivono gli header, e due header CSP in conflitto vengono applicati entrambi, quindi la policy effettiva è più restrittiva di ciascuno dei due.
- Usa un servizio di verifica pubblico come Mozilla HTTP Observatory per una rapida seconda opinione, e CSP Evaluator di Google per individuare le direttive CSP deboli.
- Aggiungi un test in CI che richieda le route principali e verifichi la presenza degli header, così un refactoring della configurazione non può eliminarli in silenzio.
Gli header sono configurazione, ed è esattamente per questo che tendono a divergere: un nuovo route handler che costruisce la propria risposta, una regola di proxy che sovrascrive i valori predefiniti, una CSP allentata con 'unsafe-inline' per sbloccare un rilascio. Revisionali come faresti con il codice. CodeAuditAgent legge la configurazione di un repository pubblico o di uno snippet incollato insieme al resto del codice, e segnala header mancanti o indeboliti con la gravità, la configurazione citata e una correzione suggerita.
Un ordine di lavoro ragionevole: gli header da una riga oggi, l'HSTS con un max-age breve questa settimana e una CSP basata su nonce in modalità Report-Only non appena riesci a raccogliere i report.