Il tuo script di analytics è il buco nella tua Content-Security-Policy
Una CSP rigorosa blocca l'XSS. Un tag di analytics di terze parti lo riapre. Ecco perché allowlist di host e SRI mancante minano la policy — e cosa risolve un tracker first-party.
Una Content-Security-Policy è la difesa singola più efficace contro il cross-site scripting. Nel momento in cui aggiungi un tag di analytics di terze parti, di solito devi praticarci un buco. È in quel buco che vivono la maggior parte dei bypass di CSP nel mondo reale.
Il motivo è strutturale, non accidentale. Gli script di analytics orientati alla sorveglianza sono progettati per caricare altro codice a runtime, da domini che non controlli. Una CSP rigorosa esiste proprio per vietare esattamente questo.
Le allowlist di host sono il modo debole di fidarsi di uno script
Il vecchio modo per consentire un fornitore di analytics era una allowlist di host:
Content-Security-Policy: script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com;
OWASP non raccomanda più questo pattern. Le allowlist sono facilmente aggirabili: qualsiasi origine consentita che ospiti un endpoint JSONP, un open redirect o script caricati dagli utenti diventa un vettore di XSS. I tag manager peggiorano la situazione, perché esistono proprio per iniettare ulteriori script da origini arbitrarie.
La pratica leader attuale è una CSP rigorosa costruita su un nonce per ogni risposta più strict-dynamic:
Content-Security-Policy: script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none';
Con strict-dynamic, il browser si fida degli script caricati da uno script già considerato affidabile — che è esattamente il comportamento da cui dipende un tag manager. Questo è comodo per il fornitore e pericoloso per te: un singolo tag compromesso ha ora il permesso implicito di caricare qualsiasi cosa.
La SRI non funziona proprio sugli script che ne hanno più bisogno
La Subresource Integrity consente al browser di verificare un file scaricato rispetto a un hash crittografico prima di eseguirlo:
<script
src="https://cdn.example.com/tracker.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"></script>
La SRI supporta sha256, sha384 e sha512, e il browser rifiuta la risorsa in caso di qualsiasi mismatch. È una difesa pulita contro una CDN compromessa — per file statici, con versione fissata.
Crolla per gli analytics. I tag dei fornitori sono volutamente mutabili: il file allo stesso URL cambia ogni volta che il fornitore rilascia funzionalità, quindi un hash fissato romperebbe la raccolta dati al deploy successivo. In pratica i team rinunciano alla SRI proprio sugli script con la portata più ampia sulla pagina. La ricalibrazione del rischio supply-chain di ottobre 2025 ha valutato un'implementazione SRI non sicura come di gravità alta proprio per questo motivo.
I Trusted Types alzano il livello minimo a febbraio 2026
La metà del problema relativa al DOM-XSS ha ora una risposta a livello di browser. A partire dalla Baseline di febbraio 2026, i Trusted Types sono disponibili nei browser attuali. Abiliti l'enforcement con:
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types default;
Dopodiché, i DOM sink come Element.innerHTML rifiutano le stringhe grezze e accettano solo valori generati da una policy registrata:
const policy = trustedTypes.createPolicy("default", {
createHTML: (input) => DOMPurify.sanitize(input),
});
el.innerHTML = policy.createHTML(userInput); // ok
el.innerHTML = userInput; // throws TypeError
Questo è davvero solido. È anche il tipo di regola che il codice di analytics legacy e dei tag manager viola regolarmente, perché quegli script scrivono su innerHTML e iniettano nodi <script> come stringhe semplici. Attivare i Trusted Types spesso significa prima disattivare il tuo fornitore di analytics.
Un tracker first-party si adatta a una policy rigorosa invece di combatterla
Il conflitto svanisce quando lo script di analytics è piccolo, autocontenuto e servito dalla tua stessa origine. Un tracker che non carica ulteriore codice non ha bisogno né di strict-dynamic né di un host del fornitore nella tua allowlist:
Content-Security-Policy: script-src 'self'; object-src 'none'; base-uri 'none'; require-trusted-types-for 'script';
Tutto qui. Nessuna origine di terze parti, nessuna escalation di nonce, nessuna eccezione ritagliata per un tag manager.
Il tracker di Monoid pesa circa 2 KB, non ha dipendenze e non chiama mai un DOM-XSS sink. Non scrive su innerHTML, non inietta script e non legge né imposta cookie o storage. Invia una richiesta keepalive per ogni pageview a /collect e per il resto resta inattivo:
fetch('/collect', {
method: 'POST',
body: JSON.stringify({ site_id, path, referrer, screen_w }),
keepalive: true,
});
Poiché il file è statico, puoi applicargli la SRI senza che si rompa mai — un hash fissato resta valido finché non scegli tu di aggiornare lo script. E poiché l'identità è un hash giornaliero a senso unico, SHA-256(IP | UA | SALT_SECRET | YYYY-MM-DD), non esiste alcuna profilazione cross-site che una CSP rilassata potrebbe anche solo far trapelare.
Il pattern si generalizza oltre gli analytics: ogni script che rimuovi dalla tua allowlist è una classe di bypass XSS che rimuovi insieme a esso. Lo script di terze parti più sicuro è quello che non carichi.
Comments
Loading comments…