Trusted Types API: Stopping DOM XSS at the Sink
The Trusted Types API blocks DOM-based XSS by forcing risky DOM sinks like innerHTML to accept only vetted objects instead of raw strings.
The Trusted Types API is a browser mechanism that prevents DOM-based cross-site scripting by refusing to let dangerous DOM sinks — innerHTML, document.write, eval, and similar — accept plain strings. Instead, those sinks only accept specially typed objects created by a policy your application defines, so an attacker who manages to inject a string into your page has no path left to turn it into executable markup or script.
The problem it solves
Most XSS defenses focus on the input side: escaping or sanitizing data before it reaches the page. That works until a developer somewhere in a large codebase writes element.innerHTML = userControlledString without thinking about it. Browsers don’t distinguish between a trusted template string and untrusted user data — both are just strings, and both get parsed as HTML the same way.
This is the class of bug usually called DOM-based XSS: the vulnerability lives entirely in client-side code, often far from where the tainted data was read. Static analysis catches some of it, but large applications with many contributors reliably miss cases. Trusted Types moves the enforcement into the runtime itself, so a missed case fails loudly instead of shipping quietly.
How it works
Trusted Types changes the type signature of dangerous DOM sinks. Once enforcement is turned on, sinks like Element.innerHTML, Element.outerHTML, document.write, HTMLScriptElement.src, and the Function/eval family stop accepting string values. They only accept one of three special object types:
TrustedHTMLTrustedScriptTrustedScriptURL
You can’t construct these objects directly. You create a policy — a named object with functions that transform a string into a trusted object — and every string has to pass through that policy first:
const policy = trustedTypes.createPolicy("app-html", {
createHTML: (input) => sanitize(input),
});
element.innerHTML = policy.createHTML(userInput);
If code anywhere in the page tries to assign a raw string to innerHTML instead of going through a policy, the browser throws a TypeError and blocks the assignment. That’s the core guarantee: every DOM injection point is either backed by a reviewed transformation function or it doesn’t run at all.
Turning it on
Trusted Types is opt-in, enforced via a Content Security Policy directive:
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types app-html default;
require-trusted-types-for 'script' enables enforcement for script-related sinks. The trusted-types directive lists which policy names are allowed to be created — this matters because it caps how many distinct transformation functions exist in your app, each of which becomes a security review target. A default policy, if you define one, catches any string assignment that doesn’t go through a named policy, which is useful for third-party code you don’t control but still want covered.
You can also run it in report-only mode first, which logs violations without blocking anything — the practical way to find every unguarded sink in an existing codebase before flipping enforcement on.
What it doesn’t cover
Trusted Types only governs the DOM injection sinks it lists — it has no opinion on CSRF, server-side template injection, or XSS that happens to enter through a channel outside its sink list, like inline event handler attributes set at parse time via a response body rather than a DOM assignment. It’s a mitigation for one specific, common bug class, not a general application firewall. And because a policy’s createHTML function is where you plug in sanitization or trust logic, a poorly written policy — one that returns input unchanged — provides no protection at all. The policy is the security boundary; Trusted Types just guarantees that boundary can’t be bypassed. Getting it right still means validating and sanitizing input properly inside the policy function.
Existing string-based code also needs a migration pass. Any library or first-party code that assigns to innerHTML, calls document.write, or evaluates strings dynamically will break under enforcement until it’s rewritten to use a policy or an inherently safer API, like building nodes with document.createElement and setting textContent instead of touching the DOM via markup strings at all.
Trusted Types vs. sanitization libraries
| Sanitization library (e.g. DOMPurify) | Trusted Types | |
|---|---|---|
| Enforcement point | Wherever a developer remembers to call it | Every listed DOM sink, browser-enforced |
| Coverage guarantee | Only as complete as manual usage | Structural — unguarded sinks throw |
| Runtime cost | Per-call sanitization | Near-zero after policy creation |
| Browser support | Universal (pure JS) | Chromium-based browsers; others ignore the header harmlessly |
| Typical role | Does the actual cleaning | Forces the cleaning to happen |
In practice these two work together: a Trusted Types policy commonly calls a sanitizer like DOMPurify inside createHTML. Trusted Types doesn’t replace sanitization — it guarantees the sanitizer can’t be skipped.
The takeaway
Trusted Types closes off DOM-based XSS by making raw strings unusable at the sinks that would otherwise render them as markup or code. It doesn’t fix bad sanitization logic, and it’s currently a Chromium-specific enforcement mechanism — but combined with a real CSP and a properly written policy, it turns “we hope every innerHTML call was reviewed” into a guarantee the browser enforces for you.
Tagged
Keep reading
Chisato · · 3 min read What Is XSS? Cross-Site Scripting Explained
Cross-site scripting (XSS) injects malicious scripts into pages other users view. How stored, reflected, and DOM-based XSS work, and how to prevent them.
Chisato · · 4 min read What Is a Content Security Policy (CSP)?
A Content Security Policy is an HTTP header that restricts what scripts and resources a page can load, blocking most XSS attacks by default.
Takina · · 4 min read What Is CORS? Cross-Origin Requests, Explained
CORS lets a server opt in to cross-origin browser requests, relaxing the same-origin policy in a controlled way. Why it exists and how to fix CORS errors.