Dein Analytics-Skript ist das Loch in deiner Content-Security-Policy
Eine strikte CSP stopft XSS. Ein Drittanbieter-Analytics-Tag reißt es wieder auf. Warum Host-Allowlists und fehlende SRI deine Policy aushöhlen — und was ein First-Party-Tracker behebt.
Eine Content-Security-Policy ist die wirksamste einzelne Verteidigung gegen Cross-Site-Scripting. In dem Moment, in dem du ein Drittanbieter-Analytics-Tag hinzufügst, musst du normalerweise ein Loch hineinschlagen. Genau dieses Loch ist der Ort, an dem die meisten realen CSP-Umgehungen stattfinden.
Der Grund ist strukturell, nicht zufällig. Überwachungslastige Analytics-Skripte sind so konzipiert, dass sie zur Laufzeit weiteren Code von Domains nachladen, die du nicht kontrollierst. Eine strikte CSP existiert, um genau das zu verbieten.
Host-Allowlists sind die schwache Art, einem Skript zu vertrauen
Der alte Weg, einen Analytics-Anbieter zuzulassen, war eine Host-Allowlist:
Content-Security-Policy: script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com;
OWASP empfiehlt dieses Muster nicht mehr. Allowlists lassen sich leicht umgehen: Jeder zugelassene Origin, der einen JSONP-Endpunkt, ein offenes Redirect oder von Nutzern hochgeladene Skripte hostet, wird zu einem XSS-Vektor. Tag-Manager verschlimmern das, weil sie genau dazu existieren, weitere Skripte von beliebigen Origins zu injizieren.
Die aktuell führende Praxis ist eine strikte CSP, die auf einer Nonce pro Antwort plus strict-dynamic aufbaut:
Content-Security-Policy: script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none';
Mit strict-dynamic vertraut der Browser Skripten, die von einem bereits vertrauten Skript geladen werden — also genau dem Verhalten, von dem ein Tag-Manager abhängt. Das ist bequem für den Anbieter und gefährlich für dich: Ein einziges kompromittiertes Tag hat jetzt implizit die Erlaubnis, alles nachzuladen.
SRI funktioniert nicht bei den Skripten, die es am dringendsten brauchen
Subresource Integrity erlaubt es dem Browser, eine geladene Datei vor der Ausführung gegen einen kryptografischen Hash zu prüfen:
<script
src="https://cdn.example.com/tracker.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"></script>
SRI unterstützt sha256, sha384 und sha512, und der Browser verweigert die Ressource bei jeder Abweichung. Es ist eine saubere Verteidigung gegen ein kompromittiertes CDN — für statische, versionsfixierte Dateien.
Bei Analytics bricht es zusammen. Anbieter-Tags sind absichtlich veränderlich: Die Datei unter derselben URL ändert sich, sobald der Anbieter Features ausliefert, sodass ein fixierter Hash die Erfassung beim nächsten Deploy zerstören würde. In der Praxis lassen Teams SRI genau bei den Skripten weg, die am weitesten in die Seite hineinreichen. Die Neubewertung des Supply-Chain-Risikos vom Oktober 2025 stufte unsichere SRI-Implementierung aus genau diesem Grund als hohe Schwere ein.
Trusted Types heben das Niveau im Februar 2026 an
Die DOM-XSS-Hälfte des Problems hat jetzt eine Antwort auf Browser-Ebene. Seit Baseline Februar 2026 sind Trusted Types in allen aktuellen Browsern verfügbar. Du aktivierst die Durchsetzung mit:
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types default;
Danach lehnen DOM-Sinks wie Element.innerHTML rohe Strings ab und akzeptieren nur Werte, die von einer registrierten Policy erzeugt wurden:
const policy = trustedTypes.createPolicy("default", {
createHTML: (input) => DOMPurify.sanitize(input),
});
el.innerHTML = policy.createHTML(userInput); // ok
el.innerHTML = userInput; // throws TypeError
Das ist wirklich stark. Es ist auch die Art von Regel, die Legacy-Analytics- und Tag-Manager-Code routinemäßig verletzt, weil diese Skripte in innerHTML schreiben und <script>-Knoten als reine Strings injizieren. Trusted Types einzuschalten bedeutet oft, zuerst deinen Analytics-Anbieter abzuschalten.
Ein First-Party-Tracker passt zu einer strikten Policy, statt sie zu bekämpfen
Der Konflikt verschwindet, wenn das Analytics-Skript klein, in sich abgeschlossen und vom eigenen Origin ausgeliefert ist. Ein Tracker, der keinen weiteren Code nachlädt, braucht weder strict-dynamic noch einen Anbieter-Host auf deiner Allowlist:
Content-Security-Policy: script-src 'self'; object-src 'none'; base-uri 'none'; require-trusted-types-for 'script';
Das war's. Kein Drittanbieter-Origin, keine Nonce-Eskalation, keine für einen Tag-Manager herausgeschnittene Ausnahme.
Der Tracker von Monoid ist etwa 2 KB groß, hat keine Abhängigkeiten und ruft niemals einen DOM-XSS-Sink auf. Er schreibt nicht in innerHTML, injiziert keine Skripte und liest oder setzt keine Cookies oder Storage. Er sendet pro Seitenaufruf eine keepalive-Anfrage an /collect und bleibt ansonsten untätig:
fetch('/collect', {
method: 'POST',
body: JSON.stringify({ site_id, path, referrer, screen_w }),
keepalive: true,
});
Weil die Datei statisch ist, kannst du SRI darauf anwenden, ohne dass es jemals bricht — ein fixierter Hash bleibt gültig, bis du dich entscheidest, das Skript zu aktualisieren. Und weil die Identität ein einseitiger Tageshash ist, SHA-256(IP | UA | SALT_SECRET | YYYY-MM-DD), gibt es kein seitenübergreifendes Profiling, das eine gelockerte CSP überhaupt durchsickern lassen könnte.
Das Muster verallgemeinert sich über Analytics hinaus: Jedes Skript, das du von deiner Allowlist entfernst, ist eine Klasse von XSS-Umgehungen, die du damit entfernst. Das sicherste Drittanbieter-Skript ist das, das du nicht lädst.
Comments
Loading comments…