Una Content-Security-Policy (CSP) è un header di risposta che dice al browser quali script, stili, immagini, frame e connessioni una pagina può usare. Se un attaccante riesce a infilare uno script nel tuo HTML, una buona policy impedisce al browser di eseguirlo. Una policy copiata da un thread di un forum di solito sbaglia in due modi: rompe analytics, web font e widget di pagamento, oppure viene riempita di 'unsafe-inline' e wildcard fino a permettere quasi tutto.
Questa guida offre esempi funzionanti per tre tipi di sito, spiega che cosa rende rigorosa una policy (nonce, hash e 'strict-dynamic'), tratta le direttive che default-src non raggiunge, mostra come raccogliere i report delle violazioni e propone un rollout che parte in modalità Report-Only, così nulla si rompe senza preavviso. Tutti gli esempi usano example.com: sostituisci i valori segnaposto con i tuoi.
Ambito di questa guida
La verifica gratuita legge una sola risposta (la home page dell’origine, dopo al massimo cinque redirect) e solo la Content-Security-Policy applicata, non la Report-Only. Non giudica se la policy è adatta al sito e non è un pentest.
1. Che cosa fa una Content-Security-Policy e che cosa no
Una policy è un elenco di direttive separate da punto e virgola. Ogni direttiva indica un tipo di risorsa e le sorgenti ammesse: script-src per JavaScript, style-src per il CSS, img-src, font-src, connect-src per connessioni fetch, XHR e WebSocket, e frame-src per i frame incorporati nella pagina. default-src è il valore di ripiego per queste direttive di caricamento quando ne manca una specifica (W3C CSP Level 3).
Tre direttive importanti non ripiegano su default-src: frame-ancestors (quali siti possono incorniciare la tua pagina), base-uri (che cosa può impostare un elemento <base>) e form-action (dove possono inviare i dati i form). Una policy con il solo default-src 'self' lascia quindi che qualunque sito incorpori la pagina e che un tag <base> iniettato reindirizzi gli URL relativi.
I valori delle sorgenti sono di tre tipi:
- Parole chiave tra apici singoli:
'self'(l’origine della pagina),'none'(nulla),'unsafe-inline','unsafe-eval'e'strict-dynamic'. - Host e schemi:
https://cdn.example.com,https://*.example.com,https:odata:. - Nonce e hash:
'nonce-…'e'sha256-…', che autorizzano singoli elementi script o style invece di interi host.
Invia la policy come header HTTP. Un tag <meta http-equiv="Content-Security-Policy"> funziona per la maggior parte delle direttive, ma la specifica esclude lì frame-ancestors, report-uri e sandbox, e una policy Report-Only non può essere consegnata in un tag meta (guida MDN alla CSP). Mantieni aspettative realistiche: una CSP limita ciò che il codice iniettato può fare. Non sostituisce l’escaping dell’output e la validazione dell’input, che fermano l’iniezione alla radice.
2. Tre esempi di Content-Security-Policy da cui partire
Esempio A: un sito senza script di terze parti. Una policy con allowlist funziona quando ogni script e foglio di stile è un file della tua origine:
Content-Security-Policy: default-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requestsBlocca ogni <script> inline, ogni attributo style e tutto ciò che viene caricato da altri host: va bene per siti scritti a mano o generati in modo statico, senza contenuti incorporati.
Esempio B: un sito con rendering lato server e nonce. È la policy rigorosa che web.dev consiglia per le pagine costruite dal server a ogni richiesta, più la protezione dall’incorniciamento:
Content-Security-Policy: script-src 'nonce-R4nd0mBase64Value' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'self'Il nonce cambia a ogni risposta (sezione 3). Nota che cosa lascia fuori: senza default-src, stili, immagini, font e connessioni non sono limitati. È un compromesso voluto: si concentra sull’esecuzione degli script, dove avviene la maggior parte dei danni del cross-site scripting, ed evita elenchi di host che si rompono ogni volta che un fornitore cambia dominio.
Esempio C: pagine statiche o in cache con gli hash. Quando lo stesso HTML è servito a tutti, autorizza gli script inline tramite il loro hash:
Content-Security-Policy: script-src 'sha256-BASE64_HASH_OF_INLINE_LOADER' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'self'| Stile di policy | Quando è adatta | Lavoro continuo | Attenzione a |
|---|---|---|---|
| A: allowlist di host | Siti statici senza codice di terze parti | Aggiornare l’elenco degli host quando aggiungi un fornitore | Si può caricare qualunque script da un host consentito, comprese vecchie versioni di librerie su una CDN condivisa |
| B: nonce + strict-dynamic | Pagine generate a ogni richiesta dalla tua applicazione | Aggiungere il nonce a ogni tag script nei template | Cache di pagina che riproducono lo stesso nonce a ogni visitatore |
| C: hash + strict-dynamic | HTML statico, pre-renderizzato o in cache all’edge | Ricalcolare gli hash ogni volta che il codice inline cambia | Minificatori e template che cambiano gli spazi e quindi l’hash |
Qualunque scelta tu faccia, aggiungi le direttive di report della sezione 6 e inviala prima come Content-Security-Policy-Report-Only.
3. Nonce e hash al posto di 'unsafe-inline'
'unsafe-inline' autorizza ogni script inline, anche uno iniettato da un attaccante, e così elimina gran parte di ciò che la CSP offre contro il cross-site scripting. Nonce e hash autorizzano solo il codice inline che intendevi pubblicare. I browser ignorano 'unsafe-inline' in una direttiva che contiene anche un nonce o un hash (MDN): per questo può restare in una policy rigorosa come ripiego per browser molto vecchi.
Nonce
Un nonce è un valore casuale che il server genera per ogni risposta, inserisce nell’header e copia nell’attributo nonce di ogni script che vuole eseguire. Deve essere imprevedibile e nuovo a ogni risposta; web.dev consiglia almeno 128 bit da un generatore crittograficamente sicuro. In un’applicazione Node.js:
import crypto from 'node:crypto';
app.use((req, res, next) => {
const nonce = crypto.randomBytes(16).toString('base64');
res.locals.nonce = nonce;
res.setHeader('Content-Security-Policy',
`script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'self'`);
next();
});Il template scrive poi <script nonce="…" src="/app.js"></script> con lo stesso valore. Aggiungi il nonce dove i tuoi template scrivono i tuoi tag script, mai riscrivendo ogni <script> nell’HTML finito: una riscrittura indiscriminata approverebbe anche uno script iniettato da un attaccante. Due trappole sono comuni: una CDN o una cache di pagina che memorizza l’HTML riproduce lo stesso nonce a ogni visitatore, e un valore incorporato in un file statico in fase di build non è affatto un nonce.
Hash
Un hash autorizza uno specifico script inline: il digest SHA-256, SHA-384 o SHA-512 in base64 del testo tra i tag script. Conta ogni carattere, spazi e a capo compresi, quindi una modifica del minificatore o del template produce un nuovo hash. Per questo gli hash sono più adatti a pagine statiche e in cache, dove il contenuto è identico a ogni risposta. Chrome stampa nella console l’hash atteso quando blocca uno script inline, oppure puoi calcolarlo tu:
printf '%s' 'console.log("hello")' | openssl dgst -sha256 -binary | openssl base64Che cosa non autorizzano
Gestori di eventi inline come onclick="…" e URL javascript: sono bloccati da una policy basata su nonce o hash. Sposta quel codice in file script e collegalo con addEventListener. 'unsafe-hashes' può autorizzare un singolo gestore tramite il suo hash, ma consideralo un rimedio temporaneo. Il codice che esegue stringhe come codice, come eval(), new Function() o setTimeout con una stringa, richiede 'unsafe-eval': sostituiscilo dove puoi e usa il più circoscritto 'wasm-unsafe-eval' se serve solo compilare WebAssembly (MDN script-src).
4. strict-dynamic: lasciare che gli script fidati carichino ciò che serve
La maggior parte dei siti esegue script che ne caricano altri: un tag manager aggiunge gli analytics, un banner del consenso aggiunge i suoi fornitori, un widget di chat scarica il proprio bundle. Elencare ogni host che potrebbero usare è fragile. 'strict-dynamic' prende un’altra strada: uno script autorizzato da un nonce o da un hash può aggiungere altri script, e anche questi sono considerati fidati.
Nei browser che lo supportano, 'strict-dynamic' fa anche ignorare al browser le allowlist di host, 'self' e 'unsafe-inline' in script-src (MDN). Ne derivano tre conseguenze:
- Ogni tag
<script>nel tuo HTML richiede il nonce o un hash corrispondente, anche gli script della tua origine, perché'self'non li autorizza più. - Gli script che uno script fidato crea con
document.createElement('script')sono consentiti. Quelli scritti nella pagina condocument.write(script inseriti dal parser) no, quindi i vecchi snippet di embed che ne dipendono si rompono. - La fiducia si trasmette. Tutto ciò che il tuo tag manager carica verrà eseguito, quindi chiunque possa pubblicare nel tag manager può di fatto eseguire codice sul tuo sito. Tratta quell’accesso come un accesso al deploy.
Per i browser più vecchi, aggiungi ripieghi che i browser nuovi ignorano:
script-src 'nonce-R4nd0mBase64Value' 'strict-dynamic' https: 'unsafe-inline'; object-src 'none'; base-uri 'none'Un browser che supporta CSP Level 3 applica solo il nonce e 'strict-dynamic'. Un browser che conosce i nonce ma non 'strict-dynamic' usa il nonce e https:, e un browser molto vecchio ripiega su 'unsafe-inline' https:. Google documenta uno snippet di Tag Manager compatibile con i nonce, che passa il nonce agli script che aggiunge, e segnala che le variabili JavaScript personalizzate in Tag Manager richiedono ancora 'unsafe-eval' (guida CSP di Tag Manager).
5. frame-ancestors, object-src 'none' e base-uri: le direttive da aggiungere in modo esplicito
frame-ancestorsdecide quali siti possono incorporare la tua pagina in un frame, ed è la difesa contro il clickjacking. Usa'none'se nessuno deve incorniciare la pagina,'self'se solo le tue pagine, oppure elenca origini precise comehttps://app.example.com. Funziona solo nell’header HTTP.X-Frame-Options(DENYoSAMEORIGIN) resta un ripiego per i browser più vecchi; fai in modo che entrambi consentano lo stesso incorniciamento (MDN frame-ancestors).object-src 'none'blocca<object>ed<embed>. I plugin sono spariti dai browser moderni, ma questi elementi possono ancora caricare contenuti, e una policy rigorosa senzadefault-srcli lascia senza limiti se non lo dichiari.base-uri 'none', o'self'se le tue pagine usano un elemento<base>, impedisce a un<base href>iniettato di puntare ogni URL relativo di script a un altro host.form-action 'self'limita dove i form possono inviare i dati. Aggiungi l’origine del tuo provider di pagamento o di accesso se un form invia lì, e prova quei flussi dopo la modifica.upgrade-insecure-requestsfa scaricare al browser le sottorisorsehttp://tramite HTTPS. Aiuta contro il mixed content ma non sostituisce HSTS.
Scrivi esplicitamente queste direttive in ogni policy, compresi gli esempi B e C qui sopra. Richiedono quasi nessuna manutenzione e chiudono le lacune che le regole sugli script lasciano aperte.
6. Segnalare le violazioni con report-to e report-uri
I report delle violazioni ti dicono che cosa una policy ha bloccato, o avrebbe bloccato in modalità Report-Only, nei browser dei visitatori reali. CSP Level 3 definisce report-to, che indica un endpoint dichiarato nell’header Reporting-Endpoints, e depreca il più vecchio report-uri, che accetta direttamente un URL. MDN indica report-to come disponibile da poco nei browser attuali dal 2026, mentre le versioni più vecchie capiscono solo report-uri. I browser che supportano report-to ignorano report-uri, quindi per ora invia entrambi (MDN report-to):
Reporting-Endpoints: csp="https://example.com/csp-reports"
Content-Security-Policy-Report-Only: script-src 'nonce-R4nd0mBase64Value' 'strict-dynamic' 'report-sample'; object-src 'none'; base-uri 'none'; report-uri https://example.com/csp-reports; report-to cspI due meccanismi inviano formati diversi. report-uri invia un oggetto JSON con una chiave csp-report e il content type application/csp-report. report-to invia application/reports+json: un array di report il cui type è csp-violation e il cui corpo contiene campi come documentURL, blockedURL, effectiveDirective e disposition (enforce o report). Con 'report-sample', un report per codice inline bloccato include i primi 40 caratteri. Accetta entrambi i formati sullo stesso endpoint.
- Aspettati rumore. Le estensioni del browser iniettano script e generano violazioni con file sorgente come
chrome-extension://omoz-extension://. Raggruppa i report per direttiva e host bloccato e cerca schemi ricorrenti anziché singole voci. - Tratta i report come dati personali. Gli URL dei documenti e i referrer possono contenere query string con indirizzi email o token. Rimuovili o accorciali, conserva i report solo per un periodo breve e non trasmetterli senza motivo.
- Proteggi l’endpoint. Chiunque può inviare report falsi: applica limiti di dimensione e di frequenza e non mostrare mai il contenuto dei report senza escaping in una vista di amministrazione.
7. Che cosa si rompe di solito: analytics, font, script inline e incorporamenti
La maggior parte delle violazioni dei primi giorni arriva da poche fonti. Il campo effectiveDirective del report indica quale regola guardare:
| Sintomo | Direttiva nel report | Correzione tipica |
|---|---|---|
| Gli analytics smettono di registrare le visualizzazioni di pagina | script-src-elem, connect-src, img-src | Carica il tag con un nonce, oppure autorizza il suo host di script, e consenti gli host di raccolta dati documentati dal fornitore (Google li elenca per prodotto) |
| I web font ripiegano sui font di sistema | font-src, style-src-elem | Autorizza l’host del foglio di stile in style-src e quello dei file font in font-src (per Google Fonts, fonts.googleapis.com e fonts.gstatic.com), oppure ospita i file in proprio |
| Uno snippet di un tema o di un CMS smette di funzionare | script-src-elem con blockedURL “inline” | Aggiungi il nonce nel template, sposta il codice in un file o autorizza il suo hash |
I pulsanti con onclick non fanno nulla | script-src-attr | Riscrivi il gestore con addEventListener in un file script |
| Le variabili personalizzate del tag manager non restituiscono nulla | script-src (eval) | Sostituiscile con variabili integrate, oppure accetta 'unsafe-eval' come eccezione documentata |
| Un video, una mappa o un frame di pagamento incorporato resta vuoto | frame-src | Aggiungi l’origine esatta del fornitore |
| Gli stili inline vengono ignorati e il layout si sposta | style-src-elem, style-src-attr | Sposta gli stili nei fogli di stile, aggiungi il nonce agli elementi style, oppure mantieni 'unsafe-inline' solo in style-src, come eccezione documentata |
| Chat o aggiornamenti in tempo reale non si connettono | connect-src | Aggiungi gli host https:// e wss:// del fornitore |
| La tua app o un partner non riesce più a incorporare la pagina | frame-ancestors | Elenca le origini esatte che la incorporano |
Gli stili inline sono un rischio minore degli script inline, ma non innocuo: un CSS iniettato può cambiare ciò che vedono i visitatori e, con i selettori di attributo, far trapelare alcuni dati della pagina. Molti framework e librerie CSS-in-JS inseriscono elementi style a runtime, quindi verifica se il tuo supporta un nonce prima di togliere 'unsafe-inline' da style-src. Ospitare in proprio font e librerie elimina più righe di questa tabella in un colpo solo e mantiene la policy breve.
8. Introdurre una CSP a fasi
- Inventario. Elenca script, stili, font, frame e destinazioni di connessione delle pagine chiave: home, accesso, account, checkout e pagine con widget incorporati. Il pannello Network di DevTools e la configurazione del tag manager sono le fonti più rapide.
- Report-Only. Invia la bozza come
Content-Security-Policy-Report-Onlycon i report. Nulla viene bloccato; le violazioni compaiono nella console e sul tuo endpoint. Lasciala attiva abbastanza a lungo perché emergano le pagine meno visitate e le campagne, di solito una settimana o più. - Correggi le fonti, non la policy. Aggiungi i nonce, sposta i gestori inline nei file, ospita i font in proprio. Aggiungi un host o
'unsafe-eval'solo come eccezione documentata, con un responsabile e un motivo. - Applica. Cambia il nome dell’header in
Content-Security-Policy. Tieni in esecuzione accanto una policy Report-Only più rigorosa per provare il prossimo irrigidimento: quando arrivano entrambi gli header, il browser ne applica uno e si limita a segnalare l’altro. - Continua a osservare. Un nuovo tag di marketing, l’aggiornamento di un plugin o di un framework possono portare nuovo codice inline. Mantieni attivo l’endpoint dei report e ricontrolla l’header dopo ogni modifica a server, CDN o framework.
Due dettagli di infrastruttura causano molte sorprese. Se una CDN o un framework aggiunge un proprio header CSP, il browser applica tutte le policy che riceve e una risorsa deve superarle tutte: un header in più può solo rendere le cose più restrittive, mai più permissive (MDN). E se l’HTML è in cache all’edge, una policy basata su nonce richiede che il nonce sia generato dove la pagina viene assemblata, oppure il passaggio agli hash. Per vedere che cosa invia davvero una pagina:
curl -sS -D - -o /dev/null https://example.com/ | grep -i content-security-policyLa guida al controllo degli header di sicurezza tratta gli header che stanno accanto alla CSP, e la checklist di sicurezza per il lancio di un sito li inserisce in una revisione completa pre-lancio.
9. Controllare l’header in produzione con la verifica gratuita degli header di sicurezza
Una volta applicata la policy, la verifica gratuita degli header di sicurezza mostra che cosa riceve un browser dalla tua home page. Esegue lo snapshot gratuito di preparazione al lancio, otto controlli passivi su un dominio pubblico senza registrazione, e mette in evidenza il controllo degli header rispetto agli altri sette.
- Richiede la home page dell’origine che inserisci, segue fino a cinque redirect e legge la risposta finale. Altre pagine, come accesso o checkout, spesso hanno policy diverse: controllale con DevTools o curl.
- Legge solo l’header
Content-Security-Policyapplicato. Durante la fase Report-Only segnala comunque la CSP come assente, con gravità media, perché una policy solo in report non blocca nulla. È un risultato atteso finché non la applichi. - Una direttiva
frame-ancestorsnella policy applicata conta come protezione dall’incorniciamento, esattamente comeX-Frame-Options. - Nel voto di hardening separato (da A a F), una CSP ottiene meno punti quando
default-srco una direttivascript-srcostyle-src(comprese le varianti-eleme-attr) contiene'unsafe-inline', o quandodefault-srco una direttiva script contiene'unsafe-eval'. Il voto legge la policy così com’è scritta, quindi anche un ripiego'unsafe-inline'accanto a un nonce abbassa il punteggio, benché i browser attuali lo ignorino. - Non valuta nonce, hash,
'strict-dynamic',object-src,base-uriné i report. Per questi usa la console del browser e i tuoi report delle violazioni.
Il risultato mostra un punteggio e l’esito di ciascuno degli otto controlli, così il segnale della CSP sta accanto a HSTS, TLS e agli altri controlli pubblici.
Domande frequenti
Qual è un buon esempio di Content-Security-Policy da cui partire?
Per un sito con rendering lato server: script-src 'nonce-{random}' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'self', con un nonce nuovo a ogni risposta. Per pagine statiche o in cache usa gli hash degli script inline al posto del nonce. In entrambi i casi comincia con Content-Security-Policy-Report-Only.
Nella CSP conviene usare un nonce o un hash?
Usa un nonce quando la tua applicazione costruisce ogni pagina, perché può inserire un valore nuovo ogni volta. Usa gli hash quando l’HTML è statico o in cache, perché un nonce in cache verrebbe riutilizzato per ogni visitatore. Entrambi funzionano insieme a 'strict-dynamic'.
'unsafe-inline' in style-src è un problema?
È un rischio minore rispetto a 'unsafe-inline' in script-src, ma un CSS iniettato può comunque cambiare ciò che vedono i visitatori e far trapelare alcuni dati della pagina. Tienilo solo come eccezione documentata mentre sposti gli stili nei fogli di stile o aggiungi nonce agli elementi style.
Content-Security-Policy-Report-Only protegge il mio sito?
No. Non blocca nulla: segnala solo ciò che una policy applicata bloccherebbe. Usala per fare prove, poi passa all’header Content-Security-Policy. I due header possono essere inviati insieme, il che è utile per provare la versione successiva, più rigorosa.
Posso impostare una CSP in un tag meta?
Sì, per la maggior parte delle direttive. Ma frame-ancestors, report-uri e sandbox vengono ignorate in un tag meta, e una policy Report-Only non può essere consegnata lì. Usa l’header HTTP ovunque il tuo server o hosting ti permetta di impostarne uno.
Qual è la differenza tra report-uri e report-to?
report-uri accetta direttamente un URL; CSP Level 3 lo depreca, ma i browser più vecchi ne dipendono ancora. report-to indica un endpoint dell’header Reporting-Endpoints e usa il formato della Reporting API. I browser che supportano report-to ignorano report-uri, quindi inviarli entrambi va bene.
Fonti e approfondimenti
- W3C: Content Security Policy Level 3www.w3.org
- MDN: guida alla Content Security Policy (CSP)developer.mozilla.org
- MDN: header Content-Security-Policydeveloper.mozilla.org
- MDN: direttiva CSP script-srcdeveloper.mozilla.org
- MDN: direttiva CSP frame-ancestorsdeveloper.mozilla.org
- MDN: direttiva CSP report-todeveloper.mozilla.org
- MDN: header Reporting-Endpointsdeveloper.mozilla.org
- W3C: Reporting APIwww.w3.org
- web.dev: Mitigare il cross-site scripting (XSS) con una Content Security Policy (CSP) rigorosaweb.dev
- Google Tag Platform: usare Tag Manager con una Content Security Policydevelopers.google.com
- OWASP Content Security Policy Cheat Sheetcheatsheetseries.owasp.org
A cura della redazione di Sitelemetry. Le fonti collegate permettono di approfondire gli argomenti.



