ESEMPI DI CSP E ROLLOUT

Esempi di Content-Security-Policy: come costruire una CSP rigorosa per un sito moderno

Una Content-Security-Policy dice al browser quali script possono girare sulle tue pagine. Tre policy di esempio funzionanti, gli elementi che rendono rigorosa una policy (nonce, hash e strict-dynamic), le direttive che default-src non copre, la segnalazione delle violazioni e un rollout a fasi che parte in modalità Report-Only.

Illustrazione concettuale: un corpo di guardia in ceramica avorio con una saracinesca di vetro; sfere di vetro con un sigillo verde menta passano e si illuminano, mentre una sfera grigia senza sigillo resta fuori accanto a un timbro di rame.
Illustrazione concettuale

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: o data:.
  • 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-requests

Blocca 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 policyQuando è adattaLavoro continuoAttenzione a
A: allowlist di hostSiti statici senza codice di terze partiAggiornare l’elenco degli host quando aggiungi un fornitoreSi può caricare qualunque script da un host consentito, comprese vecchie versioni di librerie su una CDN condivisa
B: nonce + strict-dynamicPagine generate a ogni richiesta dalla tua applicazioneAggiungere il nonce a ogni tag script nei templateCache di pagina che riproducono lo stesso nonce a ogni visitatore
C: hash + strict-dynamicHTML statico, pre-renderizzato o in cache all’edgeRicalcolare gli hash ogni volta che il codice inline cambiaMinificatori 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 base64

Che 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 con document.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-ancestors decide 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 come https://app.example.com. Funziona solo nell’header HTTP. X-Frame-Options (DENY o SAMEORIGIN) 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 senza default-src li 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-requests fa scaricare al browser le sottorisorse http:// 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 csp

I 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:// o moz-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:

SintomoDirettiva nel reportCorrezione tipica
Gli analytics smettono di registrare le visualizzazioni di paginascript-src-elem, connect-src, img-srcCarica 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 sistemafont-src, style-src-elemAutorizza 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 funzionarescript-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 nullascript-src-attrRiscrivi il gestore con addEventListener in un file script
Le variabili personalizzate del tag manager non restituiscono nullascript-src (eval)Sostituiscile con variabili integrate, oppure accetta 'unsafe-eval' come eccezione documentata
Un video, una mappa o un frame di pagamento incorporato resta vuotoframe-srcAggiungi l’origine esatta del fornitore
Gli stili inline vengono ignorati e il layout si spostastyle-src-elem, style-src-attrSposta 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 connettonoconnect-srcAggiungi gli host https:// e wss:// del fornitore
La tua app o un partner non riesce più a incorporare la paginaframe-ancestorsElenca 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

  1. 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.
  2. Report-Only. Invia la bozza come Content-Security-Policy-Report-Only con 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ù.
  3. 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.
  4. 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.
  5. 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-policy

La 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-Policy applicato. 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-ancestors nella policy applicata conta come protezione dall’incorniciamento, esattamente come X-Frame-Options.
  • Nel voto di hardening separato (da A a F), una CSP ottiene meno punti quando default-src o una direttiva script-src o style-src (comprese le varianti -elem e -attr) contiene 'unsafe-inline', o quando default-src o 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-uri né 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

  1. W3C: Content Security Policy Level 3www.w3.org
  2. MDN: guida alla Content Security Policy (CSP)developer.mozilla.org
  3. MDN: header Content-Security-Policydeveloper.mozilla.org
  4. MDN: direttiva CSP script-srcdeveloper.mozilla.org
  5. MDN: direttiva CSP frame-ancestorsdeveloper.mozilla.org
  6. MDN: direttiva CSP report-todeveloper.mozilla.org
  7. MDN: header Reporting-Endpointsdeveloper.mozilla.org
  8. W3C: Reporting APIwww.w3.org
  9. web.dev: Mitigare il cross-site scripting (XSS) con una Content Security Policy (CSP) rigorosaweb.dev
  10. Google Tag Platform: usare Tag Manager con una Content Security Policydevelopers.google.com
  11. OWASP Content Security Policy Cheat Sheetcheatsheetseries.owasp.org
Team Sitelemetry

A cura della redazione di Sitelemetry. Le fonti collegate permettono di approfondire gli argomenti.

SITELEMETRY

Metti in pratica ciò che hai imparato.

Esplora struttura, impostazioni e segnali del tuo sito con Sitelemetry.