TLDR
- La Content Security Policy limita quali risorse una pagina può caricare ed eseguire, ma non sostituisce la prevenzione delle vulnerabilità XSS.
- Content-Security-Policy-Report-Only segnala le violazioni senza bloccare: è la fase giusta per osservare i flussi reali e correggere la policy.
- Per gli script inline, preferisci hash del contenuto nei siti statici o nonce casuali generati per ogni risposta nei siti dinamici.
- Dopo i test, passa all'header Content-Security-Policy attivo: una policy lasciata solo in Report-Only non protegge la pagina.
Perché conta ora
Una Content Security Policy, o CSP, è una regola che il server comunica al browser. Può dire, per esempio, da dove arrivano gli script che una pagina è autorizzata a eseguire e se sono ammessi gli script scritti direttamente nell'HTML.
Il vantaggio è concreto: se un contenuto non fidato finisce per inserire JavaScript nella pagina, una policy ben costruita può impedirne l'esecuzione. Ma CSP è una difesa aggiuntiva, non una cura per il codice vulnerabile. L'output va comunque codificato nel contesto giusto e gli input non fidati vanno gestiti correttamente.
Il problema pratico è che molti siti hanno anni di script inline, widget esterni e dipendenze non inventariate. Aggiungere una policy severa da un giorno all'altro può rompere login, moduli, pagamenti o analytics. Rinunciare alla CSP per questo, però, significa confondere un problema di rollout con un limite della tecnologia.
La strada utile è progressiva: osservare, correggere, testare, bloccare.
Prima di scrivere la policy: fai l'inventario
Apri le pagine principali con gli strumenti di sviluppo del browser e annota:
- script caricati dal tuo dominio e da servizi terzi;
- codice JavaScript dentro l'HTML, inclusi attributi come
onclick; - fogli di stile e stili inline;
- richieste di rete avviate da
fetch, WebSocket e SDK; - iframe, font, immagini e form che inviano dati a domini esterni.
Non è necessario autorizzare tutto ciò che trovi. L'inventario serve anche a scoprire dipendenze che non dovrebbero più esserci. Un widget dismesso può essere rimosso invece di ottenere un'eccezione permanente.
Ricorda una distinzione importante: default-src è un fallback per molte direttive di caricamento, ma non per base-uri, form-action e frame-ancestors. Se vuoi quelle restrizioni, dichiarale esplicitamente.
Primo passo: Report-Only
Inizia inviando un header HTTP sulla risposta HTML. Questo esempio è una base di osservazione, non una policy universale da copiare alla cieca:
Reporting-Endpoints: csp="https://example.com/csp-reports"
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none'; report-to csp; report-uri /csp-report
In questa fase il browser non blocca gli script contrari alla policy. Registra le violazioni nella console e, dove supportato, le invia all'endpoint configurato. report-to usa Reporting API; report-uri resta un fallback per compatibilità. Gli endpoint dell'esempio sono illustrativi: devono esistere, accettare il formato dei report e avere limiti di volume e conservazione adeguati.
Prova le pagine davvero usate: homepage, ricerca, login, form, checkout, pannello admin, modalità mobile e percorsi che caricano script solo dopo un'interazione. Controlla la console e i report raccolti. Una violazione può essere un problema reale, un'estensione del browser o una risorsa non più necessaria: va interpretata prima di aggiungere un dominio alla lista consentita.
Per un test innocuo, inserisci temporaneamente in una pagina di prova uno script inline:
<script>console.log("csp probe");</script>
Con script-src 'self' in Report-Only, il messaggio appare e la console segnala la violazione. Con la stessa regola nell'header attivo, quello script viene bloccato. Rimuovi la sonda dopo il test.
Correggere gli script inline
Il modo più semplice è spostare il JavaScript in file esterni del sito e sostituire gli handler HTML come onclick con addEventListener. Se uno script deve restare inline, ci sono due strumenti principali.
Hash. Il browser autorizza uno script solo se il contenuto corrisponde esattamente all'hash indicato nella policy. È adatto a HTML statico con piccoli script stabili. Anche uno spazio o un carattere cambiato richiede un nuovo hash.
Nonce. Il server genera un valore imprevedibile per ogni risposta, lo inserisce nell'header CSP e nell'attributo nonce degli script autorizzati. È utile nelle pagine generate dinamicamente. Scrivere un nonce fisso nel template o generarlo una volta durante la build non è equivalente.
Le policy più robuste per gli script possono usare nonce o hash insieme a strict-dynamic. Non aggiungerlo senza capire il modello di caricamento: nei browser che lo supportano cambia il ruolo delle allowlist di host per gli script e può autorizzare script caricati dinamicamente da uno script già fidato. Per questa ragione va provato sui flussi reali e con i browser supportati dal progetto.
Quando attivare il blocco
Quando le violazioni legittime sono state risolte, sposta la policy verificata nell'header Content-Security-Policy. Mantieni il monitoraggio durante il rilascio e verifica subito i percorsi critici. Un deploy graduale per una parte del traffico, se l'infrastruttura lo consente, rende più facile individuare regressioni.
Una policy iniziale può essere più limitata ma reale: bloccare plugin obsoleti con object-src 'none', impedire la riscrittura dell'URL base con base-uri 'none' e introdurre una disciplina verificata sugli script. Poi si estende a connect-src, img-src, style-src, font-src, frame-src e alle altre direttive necessarie.
Se il sito non deve essere incorporato in iframe altrui, valuta frame-ancestors 'none'. Se deve esserlo solo da domini specifici, elencali. Questa direttiva non funziona in un tag meta: va inviata come header HTTP. Anche Report-Only richiede un header, non un tag meta.
Non lasciare la policy per sempre in Report-Only: i report sono utili, ma non fermano l'esecuzione. E non risolvere ogni errore aggiungendo 'unsafe-inline' o 'unsafe-eval': il sito può tornare a funzionare mentre la protezione che volevi ottenere si svuota.
Cosa CSP non promette
CSP riduce alcune possibilità di esecuzione indesiderata nella pagina. Non sostituisce la correzione di XSS, il controllo delle dipendenze, la protezione degli account o gli aggiornamenti. Un dominio autorizzato ma compromesso resta un rischio; anche uno script terzo fidato può fare più di quanto immagini.
Non è nemmeno una soluzione generale contro le estensioni browser con permessi invasivi: quelle operano con privilegi e meccanismi diversi dalla normale pagina web.
La domanda giusta non è "ho aggiunto CSP?". È: la policy attiva permette ciò che il sito deve fare e blocca ciò che non deve eseguire? Report-Only serve a raccogliere le prove. I test servono a ridurre le eccezioni. L'header attivo è il momento in cui la difesa comincia davvero.
FAQ
Che cos'è la Content Security Policy?
È un insieme di direttive inviate dal server, di solito in un header HTTP, che indica al browser quali sorgenti di contenuti sono ammesse. Una policy ben progettata può ridurre l'impatto di alcune forme di cross-site scripting.
CSP Report-Only blocca gli script non consentiti?
No. Il browser applica la policy come verifica, mostra le violazioni nella console e può inviare report, ma non blocca le risorse. Il blocco inizia quando la policy viene inviata nell'header Content-Security-Policy.
Meglio nonce o hash per gli script inline?
Su pagine statiche, un hash del contenuto esatto dello script è spesso pratico. Su pagine generate dal server, un nonce imprevedibile e diverso per ogni risposta permette di autorizzare script specifici. Un nonce fisso nel template non offre la stessa protezione.
Basta aggiungere unsafe-inline per far funzionare il sito?
Può far sparire molte violazioni, ma indebolisce proprio la protezione dagli script inline iniettati. È meglio spostare il codice in file esterni, eliminare gli handler HTML inline o adottare hash e nonce dove servono.
Posso configurare tutta la CSP in un tag meta?
No. Il tag meta non supporta Report-Only e direttive come frame-ancestors. Per una distribuzione completa usa gli header HTTP della risposta HTML.