Wunderlandmedia

Dein Browser blockiert XSS jetzt für dich. Fast niemand hat es eingeschaltet.

Trusted Types ist seit Februar 2026 Baseline und blockiert DOM-XSS auf API-Ebene. Die Verbreitung liegt unter 3%. Hier ist der Rollout, der funktioniert.

Kemal Esensoy·aktualisiert am October 31, 2026

Dein Browser blockiert XSS jetzt für dich. Fast niemand hat es eingeschaltet.
Einblicke & Ideen

Am 24. Februar 2026 hat Firefox 148 Unterstützung für die Trusted-Types-API ausgeliefert. Das war die letzte Browser-Engine, die noch fehlte, und damit ist das Feature nach Baseline Newly Available gerutscht. Ich habe das auf api.webstatus.dev geprüft statt einem Blogbeitrag zu glauben, denn Baseline-Behauptungen werden ständig falsch weitergegeben. Die Daten sind eindeutig: Chrome 83 im Mai 2020, Safari 26 im September 2025, Firefox 148 im Februar 2026, Baseline-Low-Datum 2026-02-24.

Jeder große Browser liefert also inzwischen einen Mechanismus aus, der DOM-basiertes XSS strukturell blockiert. Nicht filtert. Blockiert, auf API-Ebene.

Und jetzt der deprimierende Teil. Der Web Almanac 2025 hat require-trusted-types-for auf 2,67% der Desktop-Seiten gefunden, die überhaupt eine Content Security Policy schicken, und nur 21,9% der Seiten schicken überhaupt eine CSP. Die Mobilzahl sieht mit 12,10% besser aus, ist aber ein Artefakt: Der dritthäufigste CSP-Header-Wert im gesamten Crawl ist require-trusted-types-for 'script';report-uri /checkin/_/AndroidCheckinHttp/cspreport, also Googles eigene Android-Infrastruktur, die an Googles eigenen Endpunkt meldet. Zieh das ab, und die Verbreitung ist ein Rundungsfehler.

Ein Browser-Feature, das eine ganze Schwachstellenklasse beseitigt, ist seit sechs Jahren in Chrome verfügbar, und fast niemand hat es eingeschaltet.

Warum DOM-XSS jede andere Gegenmaßnahme überlebt hat

Cross-Site-Scripting gibt es in drei Geschmacksrichtungen, und Leute werfen sie ständig durcheinander. Reflektiertes und gespeichertes XSS passieren auf dem Server: Dein Backend nimmt Angreifereingaben und schreibt sie in die HTML-Antwort. Automatisches Escaping in Templates und Ausgabekodierung haben das weitgehend gelöst. Moderne Frameworks escapen standardmäßig, und du musst dich anstrengen, um das kaputtzumachen.

Ein Filter im Hauptrohr, während ein dünnes Nebenrohr ihn in den Tank umgeht

DOM-basiertes XSS fasst deinen Server nie an. Angreiferkontrollierte Daten gehen in eine clientseitige "Senke", und der Browser führt sie aus. Die Senkenliste ist kurz und langweilig: innerHTML, outerHTML, insertAdjacentHTML, document.write, iframe.srcdoc, DOMParser.parseFromString, <script src> und Skripttext, <embed src>, <object data>, dazu die Laufzeit-Compiler eval, new Function und die String-Formen von setTimeout und setInterval.

Content Security Policy erledigt vieles davon. Ein strenges script-src mit Nonces killt die meisten eingeschleusten <script>-Tags. Aber CSP arbeitet daran, woher Skripte kommen, nicht daran, was dein eigenes Skript mit einem String macht. Wenn dein eigenes Bundle, das unter deiner eigenen Nonce läuft, einen Wert aus location.hash liest und ihn element.innerHTML zuweist, sieht CSP ein freigegebenes Skript, das etwas Freigegebenes tut. Die Injektion segelt geradewegs durch den Filter, den du eingebaut hast.

Genau diese Lücke schließt Trusted Types, und deswegen existiert das Feature als eigene Sache und nicht als weiteres script-src-Schlüsselwort.

Hier sollte man präzise sein, denn das wird überverkauft: Trusted Types tut nichts gegen reflektiertes oder gespeichertes XSS in serverseitig gerendertem Output. Es bewacht DOM-Senken. Das ist eine große Klasse, es ist die Klasse, die alles andere überlebt hat, aber es ist eine Klasse.

Es ist eine Typprüfung, kein Sanitizer

Hier ist das Denkmodell, mit dem es bei mir klick gemacht hat. Trusted Types säubert deine Strings nicht. Es nimmt Strings überhaupt nicht mehr an.

Ein Papierstreifen läuft in eine Stempelmaschine, die einen versiegelten Umschlag für einen beschrifteten Schlitz ausgibt

Sobald die Richtlinie durchgesetzt wird, wirft element.innerHTML = irgendeinString einen TypeError. Punkt. Das Einzige, was der Browser an dieser Senke akzeptiert, ist ein TrustedHTML-Objekt, und der einzige Weg, eines zu bekommen, ist, deinen String durch eine Richtlinie zu schicken, die du namentlich registriert hast. Der Wert trägt den Beweis, dass er durch deinen Code gelaufen ist. Er kann nicht gefälscht und nicht versehentlich erzeugt werden.

Du schaltest es mit zwei CSP-Direktiven ein:

Content-Security-Policy: require-trusted-types-for 'script'; trusted-types my-sanitizer;

require-trusted-types-for 'script' ist das, was die Senken tatsächlich verriegelt. trusted-types ist die Erlaubnisliste der Richtliniennamen, die auf der Seite überhaupt erzeugt werden dürfen. Namen akzeptieren Alphanumerisches plus -#=_/@.%. Du kannst * schreiben, um jeden Namen zu erlauben, 'none', um keinen zu erlauben, und 'allow-duplicates', wenn eine Bibliothek darauf besteht, denselben Richtliniennamen zweimal zu registrieren. Wenn du die trusted-types-Direktive ganz weglässt, kann keine Richtlinie erzeugt werden, und das ist ein sehr wirksamer Weg, deine eigene Seite kaputtzumachen.

Die Richtlinie selbst ist klein:

if (window.trustedTypes && trustedTypes.createPolicy) {
  const policy = trustedTypes.createPolicy('my-sanitizer', {
    createHTML: (input) => DOMPurify.sanitize(input, { RETURN_TRUSTED_TYPE: true }),
  });

  el.innerHTML = policy.createHTML(userInput); // in Ordnung
  el.innerHTML = userInput;                    // TypeError
}

Es gibt drei Callbacks: createHTML, createScript und createScriptURL. Definier nur die, die du brauchst. Eine Richtlinie, die nur createHTML implementiert, wirft, wenn jemand versucht, eine Skript-URL durch sie zu leiten, und genau das willst du.

Die Feature-Detection-Absicherung zählt mehr, als sie aussieht. trustedTypes ist in älteren Browsern undefiniert, und ungeschützte Aufrufe von trustedTypes.createPolicy sind ein zuverlässiger Weg, eine Seite für alle mit altem Browser weiß zu machen. Baseline Newly Available heißt aktuelle Versionen von allem, nicht die tatsächlichen Browser aller Leute.

Bei der Standardrichtlinie wird geschummelt

Es gibt eine Hintertür, und sie ist der Grund, warum manche Umsetzungen null Sicherheit liefern und dabei ein grünes Häkchen zeigen.

Wenn du eine Richtlinie registrierst, die buchstäblich default heißt, ruft der Browser sie automatisch auf, sobald ein nackter String eine bewachte Senke trifft, statt zu werfen. Das sieht nach einer Migrationsabkürzung aus. Alter Code läuft weiter, keine Fehler, ausliefern.

Und dann schreibt jemand das hier:

trustedTypes.createPolicy('default', {
  createHTML: (s) => s,  // das tut nichts
});

Das erfüllt das Typsystem und säubert nichts. Du hast einen Header ergänzt, einen Scanner bestanden und jeden DOM-XSS-Fehler behalten, den du hattest, jetzt mit Dokumentation, die behauptet, du hättest es behoben. Wenn du eine Standardrichtlinie nutzt, muss sie tatsächlich säubern, idealerweise mit DOMPurify, und du solltest sie als vorübergehende Brücke mit Frist behandeln und nicht als Endzustand. Benannte Richtlinien an jeder Aufrufstelle sind das echte Ziel, denn sie lassen dich prüfen, welcher Code HTML erzeugen darf.

Googles eigene Zahlen sind das Argument, das ordentlich zu machen. Ihre Fallstudie zur Einführung der API über Produktions-Frameworks hinweg berichtet von einem Rollout über mehr als 130 Dienste mit null DOM-XSS-Vorfällen in diesen Anwendungen, und eine Auswertung ihrer Einreichungen aus dem Vulnerability Reward Program ergab, dass es mindestens 61% der DOM-XSS-Fehler verhindert hätte, die ihre statische Analyse übersehen hat. Die Angular-Integration hat vier Sicherheitsingenieure und einen Praktikanten rund sechs Wochen gekostet. Das sind echte Kosten, und es ist zugleich der Beweis, dass das endliche Arbeit ist und kein Endlosprogramm.

Erst Report-only, sonst zerlegst du die Produktion

Setz es nicht am ersten Tag durch. Der Rollout, der funktioniert, hat vier Schritte, und der erste ist kostenlos.

Eine Person hakt Punkte auf einem langen gedruckten Bericht ab, daneben ein Wandschalter mit Report und Enforce

Fang damit an, den Report-only-Header neben deiner bestehenden CSP auszuliefern:

Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri /csp-report

Nichts geht kaputt. Der Browser schickt dir jedes Mal einen Verstoßbericht, wenn Code eine bewachte Senke berührt. Du kannst sie beim Entwickeln auch live in der Konsole beobachten:

document.addEventListener('securitypolicyviolation', console.error.bind(console));

Dann liest du die Berichte, und das ist der Teil, der Zeit kostet. Jeder Verstoß nennt eine Datei und eine Zeile. Manche gehören dir und werden behoben, indem du durch eine Richtlinie leitest oder, besser, indem du innerHTML durch textContent ersetzt, wo du ohnehin nie HTML erzeugt hast. Erstaunlich viele Senken in einer typischen Codebasis setzen einfach Text.

Drittens ergänzt du trusted-types mit deinen Richtliniennamen, während du noch im Report-only bist, sodass du alles erwischst, was eine Richtlinie erzeugen will, die du nicht erwartet hast.

Erst dann schaltest du den Header auf Durchsetzung. Behalte report-uri auch nach der Durchsetzung. Berichte hören nicht an dem Tag auf nützlich zu sein, an dem du den Schalter umlegst.

Plan für Schritt zwei echte Kalenderzeit ein. Das ist kein Header, den du an einem Freitagnachmittag ergänzt, anders als das meiste an Sicherheitshärtung, die tatsächlich so schnell geht.

Was tatsächlich kaputtgeht

Der Grund, warum die Verbreitung nahe null ist, ist nicht, dass die API schwer wäre. Es ist, dass deine Seite nicht nur aus deinem Code besteht.

Tag Manager sind der schlimmste Übeltäter. Google Tag Manager fügt beliebige Marketing-Snippets ein und wertet sie aus, per Entwurf, und genau dieses Verhalten verbietet das Feature. Werbe-Tags, Chat-Widgets, Session Recorder, A/B-Test-Skripte und Heatmap-Werkzeuge schreiben alle HTML aus Strings in deine Seite. Jedes davon wird zum Verstoß, und ihren Quellcode kontrollierst du nicht.

Älteres jQuery ist ein Klassiker. $(html), .html() und .append() mit einem Markup-String landen alle bei innerHTML. Jedes Plugin aus der 2015er-Zeit tut das ständig.

Auch dein eigenes Build-Werkzeug kann dich überraschen. Inline-SVG, Template-Compiler und alles, was Markup aus einem String hydriert, kann innerHTML-Zuweisungen erzeugen, die du nie geschrieben hast.

Die ehrliche Antwort für Drittanbieter lautet, dass du drei Optionen hast: den Anbieter dazu bringen, einen Trusted-Types-kompatiblen Build auszuliefern, seinen Richtliniennamen erlauben, wenn er einen ordentlich registriert, oder das Skript rauswerfen. Microsofts Clarity hat Unterstützung für eigene Richtlinien ergänzt, was zeigt, dass Anbieter sich bewegen, wenn man fragt, aber die meisten haben es nicht getan. Das ist dasselbe Abhängigkeitsproblem, über das ich im npm-Supply-Chain-Beitrag geschrieben habe: Deine Sicherheitslage ist durch das unsorgfältigste Skript auf deiner Seite gedeckelt.

Wo die Frameworks stehen

Angular ist mit Abstand am besten unterstützt, weil Google die Integration als Teil der Fallstudie oben gebaut hat. Es funktioniert, und es verweigert bewusst bypassSecurityTrustHtml, was das korrekte Verhalten ist und dich nerven wird.

React unterstützt Trusted-Types-Werte in dangerouslySetInnerHTML und bei der Attribut-Interpolation, aber die vollständige Integration saß hinter einem internen enableTrustedTypesIntegration-Flag statt standardmäßig an zu sein. In der Praxis funktionieren React-Apps, die dangerouslySetInnerHTML meiden, größtenteils, und die, die es nutzen, brauchen an jeder Aufrufstelle eine Richtlinie.

Vue hat recht früh integriert, und die kaputtgehende Fläche ist klein, denn der Compiler vermeidet String-HTML in den meisten Render-Pfaden. Der Laufzeit-Compiler und v-html sind deine Problemstellen.

Svelte ist der unangenehme Fall. Der Compiler erzeugt innerHTML-Zuweisungen für statisches Markup und Inline-SVG, was die Durchsetzung auslöst, obwohl die Strings Konstanten zur Kompilierzeit sind und keine Nutzerdaten in der Nähe haben. Dazu gibt es offene Issues bei Svelte und bei SvelteKit. Prüf deinen kompilierten Output, bevor du irgendwem ein Datum versprichst.

Die allgemeine Regel: Wenn dein Framework zur Laufzeit HTML aus Strings erzeugt, rechne mit einem Shim. Wenn es DOM-Knoten direkt baut, bist du größtenteils fein raus.

Wer sich das wirklich antun sollte

Ich sage dir nicht, dass jede Seite das braucht.

Eine fünfseitige Broschürenseite mit Kontaktformular, ohne Nutzerkonten, ohne nutzergenerierte Inhalte und ohne dynamisches DOM-Schreiben bekommt von Trusted Types fast nichts. Es gibt keine DOM-XSS-Fläche zu schützen. Steck diese Zeit in Dinge, die in dieser Größe mehr zählen, etwa die Skripte zu löschen, die du nicht nutzt oder dein Framework gepatcht zu halten, was Leuten bei den Next.js-CVEs im August tatsächlich wehgetan hat.

Es verdient sein Geld, wenn du eines davon hast: nutzergenerierte Inhalte, die in die Seite gerendert werden, ein eingeloggtes Dashboard, ein CMS-Admin, in dem Redakteure HTML einfügen, einen Rich-Text-Editor, alles, was Markdown clientseitig rendert, oder eine Anwendung, in der ein URL-Parameter beeinflusst, was gezeichnet wird. Das sind die Formen, in denen DOM-XSS lebt, und in denen eine einzige übersehene Senke eine Kontoübernahme ist.

Es gibt einen zweiten Grund, sich zu kümmern, der nichts mit deinem eigenen Code zu tun hat. Wenn du Anwendungen ausliefertst, die schnell mit KI-Unterstützung zusammengesetzt wurden, taucht innerHTML ständig im generierten Code auf, denn die Trainingsdaten sind voll davon. Über diese Gewohnheit habe ich im Vibe-Coding-Sicherheitsbeitrag geschrieben. Eine Lint-Regel zur Build-Zeit fängt einen Teil davon. Ein Browser, der die Zuweisung verweigert, fängt alles davon, auch den Code, den du nie geprüft hast.

Was ich tatsächlich denke

Das ist ein gutes Feature mit einer schlechten Verbreitungsgeschichte, und die Verbreitungsgeschichte ist nicht die Schuld der Browser. Die Arbeit ist nicht, die Richtlinie zu schreiben, das dauert zwanzig Minuten. Die Arbeit ist, jede Senke in einer geerbten Codebasis zu prüfen und mit Anbietern zu verhandeln, die keinen Grund haben, sich zu kümmern. Das ist unspektakulär, es lässt sich schlecht vorführen, und niemand genehmigt dafür einen Sprint.

Was sich im Februar 2026 geändert hat, ist, dass die Ausrede weg ist. "Nur Chrome" war sechs Jahre lang ein legitimer Grund, das auszulassen. Jetzt ist es keiner mehr.

Wenn ich priorisieren würde, würde ich diesen Quartal den Report-only-Header auf jede Anwendung mit Login-Bildschirm setzen, allein um herauszufinden, wie schlimm es ist. Der Bericht kostet dich einen Header und sagt dir etwas Echtes über deine Codebasis. Ob du danach durchsetzt, ist eine Entscheidung, die du mit tatsächlichen Daten statt einer Vermutung treffen kannst.

Wenn du ein zweites Augenpaar auf deine CSP, deine Senken oder die allgemeine Frage willst, was sich auf deiner Seite zu härten lohnt und was Theater ist, ist das ein guter Teil dessen, was ich bei Wunderlandmedia mache.

Wenn dir diese Beiträge helfen: Markiere Wunderlandmedia bei Google als bevorzugte Quelle — dann tauchen meine Artikel häufiger in deinen Suchergebnissen, AI Overviews und im AI-Modus auf.

Als bevorzugte Quelle festlegen

Über den Autor

KE

Kemal Esensoy

Kemal Esensoy, Gründer von Wunderlandmedia, begann seine Karriere als freiberuflicher Webentwickler und Designer. Er führte Web-Design-Kurse mit über 3.000 Studenten durch. Heute leitet er eine preisgekrönte Full-Stack-Agentur, die sich auf Webentwicklung, SEO und digitales Marketing spezialisiert hat.

Trusted Types: DOM-XSS blockieren 2026 | Wunderlandmedia