DOM XSS auf Typebene ausschließen
DOM basiertes Cross Site Scripting entsteht meist nicht durch fehlende serverseitige Filterung, sondern durch unsichere Zuweisungen an innerHTML, document.write oder eval mitten im Frontend Code. Die Trusted Types API schließt diese Lücke strukturell, indem der Browser gefährliche Zuweisungen an DOM Sinks nur noch akzeptiert, wenn der Wert vorher durch eine definierte Policy in einen speziellen Typ umgewandelt wurde.
Inhaltsverzeichnis
- 1. Warum DOM XSS trotz CSP weiter möglich ist
- 2. Trusted Types aktivieren und require-trusted-types-for
- 3. Policies erstellen: createHTML, createScript, createScriptURL
- 4. Die default Policy für Legacy Code und Drittbibliotheken
- 5. Geschützte DOM Sinks im Überblick
- 6. Violation Reporting und schrittweise Migration
- 7. Trusted Types in Frameworks und Bibliotheken
- 8. Typische Fehler und Fallstricke
- 9. Trusted Types im Vergleich zu anderen Schutzmaßnahmen
- 10. Zusammenfassung
- 11. FAQ
1. Warum DOM XSS trotz CSP weiter möglich ist
Eine Content Security Policy schützt zuverlässig davor, dass fremde Skripte von falschen Quellen ausgeführt werden, verhindert aber nicht, dass eigener, an sich vertrauenswürdiger Code eine unsichere Zeichenkette an innerHTML oder document.write übergibt. Genau hier setzt die Trusted Types API an: Sie behandelt nicht die Herkunft eines Skripts, sondern den Datenfluss innerhalb der Anwendung selbst, an der Stelle, wo eine Zeichenkette zu HTML, Skript oder URL wird.
Ein typisches Beispiel: Eine Suchfunktion baut das Ergebnis Markup per Template Literal zusammen und weist es direkt element.innerHTML zu. Enthält der Suchbegriff ein Skript Tag, das nicht korrekt escaped wurde, führt der Browser es aus, ganz ohne fremde Domain, ganz ohne Content Security Policy Verletzung im klassischen Sinn. Die Trusted Types API verhindert genau diesen Fall, indem innerHTML nach Aktivierung nur noch spezielle TrustedHTML Objekte akzeptiert, keine rohen Strings mehr.
Statistisch gesehen entstehen DOM basierte Cross Site Scripting Schwachstellen in der Praxis häufiger durch harmlos wirkenden, eigenen Anwendungscode als durch klassische Server seitige Injection Lücken. Genau diese Beobachtung war die ursprüngliche Motivation von Google, die Trusted Types API zu entwickeln und in Chrome zu implementieren.
2. Trusted Types aktivieren und require-trusted-types-for
Aktiviert wird die Trusted Types API über die Content Security Policy Direktive require-trusted-types-for 'script'. Ab diesem Moment wirft der Browser eine TypeError Exception, sobald ein roher String an einen der geschützten DOM Sinks übergeben wird, statt ihn stillschweigend zu akzeptieren. Diese harte Fehlermeldung ist gewollt: Sie zwingt Entwickler, jede Zuweisung explizit über eine Policy laufen zu lassen, statt implizite Annahmen über die Sicherheit eines Strings zu treffen.
Zusätzlich kann trusted-types als eigene Direktive festlegen, welche Policy Namen überhaupt erlaubt sind, etwa trusted-types default myapp-sanitizer 'allow-duplicates'. Ohne diese Einschränkung könnte theoretisch jeder Code im Dokument beliebig viele Policies mit beliebigen Namen registrieren, was die Kontrolle über den Datenfluss wieder aufweichen würde. Die Kombination aus require-trusted-types-for und trusted-types bildet damit ein zweistufiges Sicherheitsnetz.
Content-Security-Policy:
require-trusted-types-for 'script';
trusted-types default myapp-sanitizer 'allow-duplicates';
3. Policies erstellen: createHTML, createScript, createScriptURL
Eine Trusted Types Policy wird über trustedTypes.createPolicy(name, rules) registriert und definiert drei mögliche Transformationsfunktionen: createHTML für TrustedHTML, createScript für TrustedScript und createScriptURL für TrustedScriptURL. Jede dieser Funktionen nimmt einen rohen String entgegen und muss eine bereinigte Version zurückgeben, bevor der Browser sie an einem geschützten Sink akzeptiert.
Der entscheidende Vorteil gegenüber einer manuellen Sanitizer Funktion ist die Erzwingbarkeit: Sobald require-trusted-types-for 'script' aktiv ist, kann kein Code mehr an der Policy vorbei direkt Strings zuweisen, weil der Browser selbst die Typprüfung durchsetzt. Ein vergessener Aufruf der Sanitizer Funktion an einer einzelnen Codestelle führt nicht mehr zu einer stillen Sicherheitslücke, sondern zu einer sofortigen Exception, die in Tests oder im Betrieb sichtbar wird.
// Register a Trusted Types policy with DOMPurify as sanitizer
const sanitizerPolicy = trustedTypes.createPolicy('myapp-sanitizer', {
createHTML: (input) => DOMPurify.sanitize(input, { RETURN_TRUSTED_TYPE: false }),
createScriptURL: (input) => {
const allowed = ['https://cdn.mironsoft.de/'];
if (!allowed.some((prefix) => input.startsWith(prefix))) {
throw new TypeError('Script URL not allowed: ' + input);
}
return input;
},
});
// Now innerHTML only accepts TrustedHTML produced by the policy
function renderSearchResults(container, rawMarkup) {
container.innerHTML = sanitizerPolicy.createHTML(rawMarkup);
}
4. Die default Policy für Legacy Code und Drittbibliotheken
Wird eine Policy mit dem reservierten Namen default registriert, greift sie automatisch für jeden String, der ohne explizite Policy an einen geschützten Sink übergeben wird. Das ist besonders wertvoll für große Codebasen, in denen nicht jede einzelne Zuweisung sofort auf eine explizite Policy umgestellt werden kann. Die default Policy fungiert dann als Sicherheitsnetz, das jede unbehandelte Zuweisung durch dieselbe Sanitizer Logik schickt, bevor sie überhaupt fehlschlägt.
Wichtig ist, die default Policy nicht als dauerhafte Lösung zu missverstehen, sondern als Migrationswerkzeug. Wer sich langfristig nur auf die default Policy verlässt, verliert die Möglichkeit, unterschiedliche Sicherheitsanforderungen für unterschiedliche Datenflüsse abzubilden, etwa strengere Regeln für Nutzereingaben gegenüber serverseitig generiertem Markup. Der empfohlene Weg ist, kritische Codepfade schrittweise auf benannte, spezifische Policies umzustellen und die default Policy nur für Alt Code und Drittbibliotheken zu behalten.
// Fallback policy for legacy code paths not yet migrated
if (window.trustedTypes && trustedTypes.createPolicy) {
trustedTypes.createPolicy('default', {
createHTML: (input) => {
console.warn('Legacy innerHTML assignment sanitized via default policy');
return DOMPurify.sanitize(input);
},
});
}
Zusätzlich zur eigentlichen Sanitizer Logik lohnt es sich, jede Policy mit Logging zu versehen, das protokolliert, wie oft und an welcher Stelle im Code die Policy tatsächlich durchlaufen wird. Diese Telemetrie erleichtert später die Entscheidung, welche Codepfade von der default Policy auf eine spezifischere, benannte Policy migriert werden sollten.
5. Geschützte DOM Sinks im Überblick
Die Trusted Types API schützt eine feste Liste bekannter DOM Sinks, die historisch für die meisten DOM XSS Schwachstellen verantwortlich sind. Dazu gehören Element.innerHTML, Element.outerHTML, document.write, document.writeln, das src Attribut von script Elementen sowie Funktionen wie eval und setTimeout mit String Argument, sofern require-trusted-types-for 'script' aktiv ist.
Nicht abgedeckt sind DOM APIs, die von Natur aus sicher sind, etwa textContent oder createElement gefolgt von setAttribute für nicht kritische Attribute. Diese Unterscheidung ist wichtig: Die Trusted Types API ersetzt nicht generelles sicheres Coding, sondern erzwingt es an genau den Stellen, an denen ein String faktisch als HTML, Skript oder URL interpretiert wird. Für alle anderen DOM Operationen bleibt normales JavaScript ohne zusätzliche Policy nutzbar.
6. Violation Reporting und schrittweise Migration
Analog zur Content Security Policy unterstützt auch die Trusted Types API einen Report Only Modus über Content-Security-Policy-Report-Only: require-trusted-types-for 'script'. In diesem Modus wirft der Browser keine Exception, meldet aber jede Verletzung an den konfigurierten Reporting Endpunkt. Das erlaubt, den gesamten Codepfad einer Anwendung über einen längeren Zeitraum zu beobachten, bevor der harte Enforcement Modus aktiviert wird.
In der Praxis empfiehlt sich eine Migration in drei Phasen: Erst Report Only mit umfassendem Logging, dann eine default Policy als breites Sicherheitsnetz, und zuletzt die schrittweise Ablösung durch benannte, spezifische Policies für jeden kritischen Datenfluss. Diese Reihenfolge verhindert, dass eine große Legacy Codebasis beim ersten Enforcement sofort mit hunderten Fehlern ausfällt.
7. Trusted Types in Frameworks und Bibliotheken
Moderne Frameworks wie Angular unterstützen Trusted Types bereits nativ und erzeugen intern TrustedHTML Objekte für Direktiven wie [innerHTML]. React arbeitet standardmäßig ohnehin mit escaped Strings über JSX, benötigt Trusted Types also seltener direkt, außer bei explizitem dangerouslySetInnerHTML. Für diesen Fall lässt sich eine dedizierte Policy registrieren, die React Markup vor der Zuweisung durch DOMPurify schickt.
Bibliotheken ohne native Unterstützung, etwa ältere jQuery Plugins, die direkt mit .html() arbeiten, benötigen entweder eine default Policy oder ein gezieltes Wrapping der betroffenen Aufrufe. Wichtig ist, vor der Migration zu prüfen, ob die verwendete Bibliothek überhaupt Trusted Types kompatibel ist, da einige ältere Pakete den Einsatz von eval in einer Weise nutzen, die auch mit Sanitizer Policy nicht sauber lösbar ist und einen Bibliothekswechsel erfordert.
8. Typische Fehler und Fallstricke
Der häufigste Fehler ist, die default Policy dauerhaft als einzige Lösung zu betreiben, ohne kritische Datenflüsse jemals auf spezifische Policies umzustellen. Das führt dazu, dass Nutzereingaben und serverseitig generiertes Markup mit derselben, oft zu freizügigen Sanitizer Logik behandelt werden. Ein zweiter Fehler ist, die Sanitizer Funktion einer Policy selbst unsicher zu implementieren, etwa mit einer eigenen Regex basierten Bereinigung statt einer geprüften Bibliothek wie DOMPurify.
Ein dritter Fehler betrifft Browser Kompatibilität: Firefox und Safari unterstützen die Trusted Types API nur eingeschränkt oder gar nicht, weshalb die Anwendung ohne Trusted Types Unterstützung trotzdem funktionieren muss. Ein Feature Detection Check über window.trustedTypes vor der Policy Registrierung verhindert, dass die Anwendung in nicht unterstützten Browsern mit einer Exception abstürzt.
// Feature detection before registering a policy — required for Firefox/Safari
if (window.trustedTypes && trustedTypes.createPolicy) {
const policy = trustedTypes.createPolicy('myapp-sanitizer', {
createHTML: (input) => DOMPurify.sanitize(input),
});
window.__cspPolicy = policy;
} else {
// Fallback: manual sanitization without native enforcement
window.__cspPolicy = { createHTML: (input) => DOMPurify.sanitize(input) };
}
// Usage stays identical regardless of native support
function safeRender(container, rawMarkup) {
container.innerHTML = window.__cspPolicy.createHTML(rawMarkup);
}
Diese Art der Feature Detection sollte zentral an einem einzigen Ort im Bootstrap Code der Anwendung erfolgen, nicht verstreut an jeder einzelnen Stelle, an der eine Policy benötigt wird. So bleibt die Kompatibilitätslogik wartbar, auch wenn sich der Browser Support für die Trusted Types API in Zukunft ändert.
9. Trusted Types im Vergleich zu anderen Schutzmaßnahmen
Trusted Types ergänzt, ersetzt aber nicht andere Sicherheitsmaßnahmen gegen Cross Site Scripting. Die folgende Tabelle ordnet die Trusted Types API im Vergleich zu klassischer Sanitisierung und einer Content Security Policy ein.
| Maßnahme | Schützt vor | Durchsetzung | Grenzen |
|---|---|---|---|
| Manuelle Sanitisierung | Bekannte HTML Injection Muster | Konvention, keine Erzwingung | Vergessene Aufrufe bleiben unsichtbar |
| Content Security Policy | Fremde Skript Quellen, Inline Skripte | Browser Enforcement | Schützt nicht vor eigenem unsicherem DOM Code |
| Trusted Types API | DOM basiertes XSS über innerHTML, eval, Script Src | Browser Enforcement auf Typebene | Eingeschränkte Browser Unterstützung |
In der Praxis ergänzen sich alle drei Maßnahmen: Eine Content Security Policy blockiert fremde Quellen, die Trusted Types API erzwingt sichere Umwandlung an geschützten Sinks, und manuelle Sanitisierung mit einer geprüften Bibliothek liefert die eigentliche Bereinigungslogik innerhalb der Policy. Keine der drei Maßnahmen ersetzt die anderen vollständig.
Mironsoft
DOM Sicherheit, Trusted Types Migration und Sanitizer Audits
DOM basiertes Cross Site Scripting strukturell ausschließen?
Wir identifizieren gefährliche DOM Sinks in eurer Codebasis, entwerfen passende Trusted Types Policies und begleiten die schrittweise Migration von Report Only bis zum harten Enforcement Modus.
Sink Audit
Identifikation aller innerHTML, eval und Script Src Zuweisungen
Policy Design
Spezifische Policies pro Datenfluss statt pauschaler default Policy
Framework Integration
Angular, React und Legacy Bibliotheken Trusted Types kompatibel machen
Wer die Trusted Types API in einem bestehenden Magento oder Hyvä Frontend einführen möchte, sollte zunächst die Layout XML generierten Skripte und die Alpine.js Komponenten auf direkte innerHTML Zuweisungen prüfen, bevor die Content Security Policy Direktive aktiviert wird. Das verhindert, dass zentrale UI Komponenten nach der Aktivierung unerwartet leer bleiben.
10. Zusammenfassung
Die Trusted Types API schließt eine Lücke, die eine Content Security Policy allein nicht abdecken kann: unsichere Zuweisungen an DOM Sinks durch den eigenen, vertrauenswürdigen Anwendungscode. Über require-trusted-types-for 'script' aktiviert, akzeptiert der Browser an geschützten Sinks nur noch Werte, die zuvor durch eine registrierte Policy gelaufen sind. Named Policies für kritische Datenflüsse und eine default Policy als Migrationsnetz ergänzen sich, wobei die default Policy nie die einzige dauerhafte Lösung sein sollte.
Report Only Modus, Feature Detection für nicht unterstützte Browser und die Integration bestehender Sanitizer Bibliotheken wie DOMPurify machen die Einführung der Trusted Types API praktisch handhabbar, auch in großen, gewachsenen Codebasen. Wer DOM basiertes Cross Site Scripting nicht nur durch Konvention, sondern durch erzwungene Typprüfung im Browser ausschließen will, kommt an der Trusted Types API als zusätzlicher Sicherheitsschicht nicht vorbei.
Trusted Types API im Detail — Das Wichtigste auf einen Blick
Vier Bausteine, die zusammen die Trusted Types API im Alltag praktisch handhabbar machen.
Aktivierung
require-trusted-types-for 'script' in der Content Security Policy erzwingt Typprüfung an geschützten DOM Sinks.
Policies
createHTML, createScript und createScriptURL transformieren rohe Strings in vertrauenswürdige Typen.
default Policy
Sicherheitsnetz für Legacy Code, kein Ersatz für spezifische Policies pro Datenfluss.
Browser Support
Chrome und Edge nativ, Firefox und Safari eingeschränkt. Feature Detection ist Pflicht.
Zusammen ergeben diese vier Bausteine eine praxistaugliche Migrationsstrategie für gewachsene Frontend Codebasen.