Trusted Types is a browser security API defined in the W3C Trusted Types specification that prevents DOM-based Cross-Site Scripting (DOM XSS) by locking down the dangerous DOM sinks that attackers exploit — innerHTML, document.write, eval, and others. When Trusted Types is enforced, the browser refuses to accept a plain string at these sinks; it only accepts a TrustedHTML, TrustedScript, or TrustedScriptURLobject created through a declared policy. An injected string — regardless of where it came from — cannot reach the sink without going through your policy's sanitisation logic first.
DOM XSS differs from reflected and stored XSS in a critical way: it happens entirely in the browser, with no round trip to the server. A malicious value from location.hash, document.referrer, or postMessage is read by JavaScript and written directly into the DOM. Traditional server-side output encoding does not help here — the server never sees the payload. The OWASP DOM-based XSS Prevention Cheat Sheet documents the canonical taxonomy of dangerous DOM sinks; Trusted Types is the browser-native enforcement mechanism that makes those sinks safe by default.
Content-Security-Policy: require-trusted-types-for 'script'TrustedHTML, TrustedScript, or TrustedScriptURL objects are accepted.Content-Security-Policy-Report-Only: require-trusted-types-for 'script' reports violations without breaking the page.Understanding why Trusted Types exists requires understanding what makes DOM XSS different from the other two XSS variants:
| Type | Where the payload lives | Server involved? | Primary defence |
|---|---|---|---|
| Reflected XSS | URL parameter reflected in the server response | Yes — server echoes the payload | Server-side output encoding |
| Stored XSS | Database — payload saved and later rendered to users | Yes — server stores and serves the payload | Input validation + output encoding |
| DOM XSS | Browser sources: URL hash, referrer, postMessage, localStorage | No — JavaScript reads and writes the payload directly | Trusted Types + safe DOM APIs |
Because DOM XSS bypasses the server entirely, a Content Security Policy with script-src rules alone does not stop it — the malicious script is already on your own origin. Trusted Types addresses exactly this gap.
A DOM sink is any API that takes a string and interprets it as HTML, JavaScript, or a URL that could execute code. These are the sinks Trusted Types locks down when enforced:
| Sink | Type required | Risk without Trusted Types |
|---|---|---|
element.innerHTML | TrustedHTML | Executes injected <script> tags and event handlers |
element.outerHTML | TrustedHTML | Replaces the element with attacker-controlled markup |
element.insertAdjacentHTML() | TrustedHTML | Inserts arbitrary HTML adjacent to the element |
document.write() | TrustedHTML | Overwrites the document with attacker HTML |
eval() | TrustedScript | Executes arbitrary JavaScript |
new Function() | TrustedScript | Creates and executes a function from a string |
setTimeout(string) / setInterval(string) | TrustedScript | Schedules arbitrary code execution |
script.src | TrustedScriptURL | Loads an attacker-controlled script file |
Worker / SharedWorker / ServiceWorkerContainer.register | TrustedScriptURL | Spawns a worker from an attacker-controlled URL |
WorkerGlobalScope.importScripts | TrustedScriptURL | Loads additional scripts into a worker context |
DOMParser.parseFromString / Range.createContextualFragment | TrustedHTML | Parses attacker-controlled HTML into DOM nodes |
The above is a representative subset. The W3C Trusted Types specification defines more than 60 guarded sinks across the DOM, including SVG's scripting sinks and modern unsafe-HTML APIs like Element.setHTMLUnsafe.
Trusted Types is activated via the Content-Security-Policy header. Add the require-trusted-types-for directive with the value 'script':
# Enable Trusted Types enforcement
Content-Security-Policy: require-trusted-types-for 'script'
# Also restrict which policy names are allowed (recommended)
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types default myapp-policyThe trusted-types directive lists the policy names your application is allowed to create. Any call to trustedTypes.createPolicy() with a name not in this list is blocked. This prevents attacker code from creating its own policy to bypass the enforcement.
A Trusted Types policy is a JavaScript object that wraps your sanitisation logic. You create it once and use it everywhere a dangerous sink needs to receive a value:
// Create a policy named "default" (matches trusted-types directive)
const policy = trustedTypes.createPolicy('default', {
// Called when code passes a string to innerHTML, outerHTML, etc.
createHTML: (input) => {
// Sanitise the input — e.g. with a sanitiser library
return input.replace(/<script[^>]*>.*?</script>/gi, '');
},
// Called when code passes a string to eval() or new Function()
createScript: (input) => {
throw new Error('Creating scripts from strings is not allowed');
},
// Called when code passes a string to script.src
createScriptURL: (input) => {
const url = new URL(input, location.origin);
if (url.origin !== location.origin) {
throw new Error('Only same-origin script URLs are allowed');
}
return input;
},
});
// Now use the policy wherever you need to write HTML
element.innerHTML = policy.createHTML(userInput); // safe
element.innerHTML = userInput; // TypeError — plain string rejectedThe key insight is that all sanitisation logic is centralised in the policy. Every dangerous sink in your codebase is forced to go through the same code path — you cannot accidentally forget to sanitise a value because the browser itself enforces the requirement.
When Trusted Types is enforced, any code that writes a plain string to a dangerous sink throws a TypeError — including third-party libraries that pre-date Trusted Types. The default policy is a special policy named "default" that the browser calls automatically when a plain string reaches a sink, instead of throwing immediately.
// The default policy intercepts plain strings at sinks
// so legacy third-party code does not immediately throw
trustedTypes.createPolicy('default', {
createHTML: (input) => {
console.warn('Unsafe innerHTML assignment intercepted:', input);
// Sanitise and return, or throw to block it
return sanitise(input);
},
});The default policy is useful during migration: it lets you intercept and log all unsafe sink assignments across your codebase and its dependencies before you start enforcing strict policies on a per-module basis.
Enforcing Trusted Types on an existing codebase will break any code that passes plain strings to dangerous sinks. Use report-only mode first to discover all violations without breaking the page:
# Observe violations without enforcement
Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri /tt-violationsViolations appear in the browser console as warnings, and are sent as JSON to your report-uri endpoint. Work through them one by one — replacing raw innerHTML assignments with either a Trusted Types policy or a safe DOM API like textContent (which does not parse HTML and is safe for plain text).
The best fix for a Trusted Types violation is often to avoid the dangerous sink entirely. Here are the safe alternatives for the most common patterns:
| Unsafe pattern | Safe alternative | When to use |
|---|---|---|
el.innerHTML = text | el.textContent = text | When inserting plain text with no HTML formatting needed |
el.innerHTML = '<b>' + text + '</b>' | Create elements with document.createElement | When building simple element structures |
el.insertAdjacentHTML('beforeend', html) | el.append() with a DocumentFragment | When inserting multiple nodes |
eval(code) | Refactor to avoid dynamic evaluation entirely | Almost never necessary in modern applications |
As of 2026, Trusted Types is supported across all major browsers. Chrome and Edge have shipped Trusted Types since version 83 (May 2020). Safari added support in version 26, and Firefox enabled it by default in version 148. MDN Web Docs classifies Trusted Types as Baseline — newly available since February 2026 — meaning it works across the latest versions of all major browser engines.
For older browsers that do not support Trusted Types, the require-trusted-types-for CSP directive is ignored gracefully: the page does not throw errors, it simply does not get the additional enforcement layer. This means enabling Trusted Types is safe to roll out everywhere — modern browsers get active DOM XSS protection, and older browsers fall back to whatever defences you already had in place.
Google has published a case study on adopting Trusted Types in production frameworks showing that migrated applications had effectively zero DOM XSS vulnerabilities after enforcement.
No — they are complementary. A CSP with script-src controls which script files the browser is allowed to load. Trusted Types controls how values are passed to dangerous DOM sinks within scripts that are already running. You need both for complete XSS defence: CSP blocks external script injection; Trusted Types prevents DOM-based injection from within your own code.
React and Vue avoid innerHTML by default — they use virtual DOM diffing that writes to safe DOM APIs. Direct use of dangerouslySetInnerHTML in React or v-html in Vue will trigger a Trusted Types violation. Angular has built-in Trusted Types support. For frameworks that do use unsafe sinks, you typically create a default policy that sanitises the input before it reaches the DOM.
They will throw a TypeError unless a default policy is in place. Use the default policy as a temporary interceptor to sanitise or log those assignments during migration. Long-term, replace or update the third-party code, or report the issue to the library maintainer as a Trusted Types compatibility bug.
Yes — this is the recommended pattern. Inside your createHTML policy function, pass the input through a sanitiser (such as a library built for HTML sanitisation) before returning it. The policy becomes the single place where all HTML sanitisation logic lives, making it easy to audit and update.
Trusted Types is defined in its own W3C specification, separate from the CSP specification — but it is activated via a CSP directive (require-trusted-types-for). Think of it as an extension to CSP that targets DOM sinks specifically, whereas the original CSP focuses on resource loading.
Yes. The W3C spec explicitly guards SVG's scripting sinks — including SVGAnimatedString.baseVal when it is set on a script-bearing attribute. Workers (new Worker(), SharedWorker(), ServiceWorkerContainer.register(), and importScripts()) are also covered by TrustedScriptURL. The full sink list in the W3C specification covers more than 60 DOM APIs.
require-trusted-types-for 'script' enables enforcement — sinks reject plain strings. trusted-types policyName1 policyName2 restricts which policy names can be created with trustedTypes.createPolicy(). Without trusted-types, any policy name is allowed — including names an attacker might use in injected code. Both directives should be used together.
Trusted Types closes the DOM XSS attack surface that server-side defences and standard CSP policies cannot reach. By requiring that dangerous sinks like innerHTML and eval only accept values created through a declared policy, it forces all HTML sanitisation into a single, auditable code path — and lets the browser enforce it at runtime. Enable it in report-only mode first, work through violations by replacing unsafe sink assignments with safe DOM APIs or policy-wrapped values, then switch to full enforcement. Run a Greadme deep scan to check whether your CSP includes the require-trusted-types-for directive.