TL;DR / Trusted Types is a browser API that prevents DOM XSS by requiring developers to pass specially typed objects (instead of raw strings) to dangerous DOM sinks like
innerHTML,eval, anddocument.write.
How It Works
┌────────────┐ ┌────────────┐ ┌─────────────┐
│ Untrusted │ │ Policy │ │ TrustedHTML │
│ string │───────→│ function │───────→│ or reject │
└────────────┘ └────────────┘ └─────────────┘
│
raw string --X--> innerHTML │
(blocked) ↓
┌─────────────┐
│ innerHTML │
└─────────────┘
DOM-based XSS remains one of the hardest vulnerability classes to eliminate at scale. Traditional mitigations focus on sanitizing input or encoding output, but they require developers to remember the correct escaping for every context, every time. Trusted Types flips this model: instead of hoping developers escape correctly, the browser refuses to accept raw strings in dangerous sinks unless they pass through a developer-defined policy.
The Problem Trusted Types Solves
DOM XSS occurs when user-controlled data flows into a dangerous sink -- element.innerHTML, document.write(), eval(), setTimeout(string), location.href (for javascript: URLs), and <script> element creation. Static analysis tools struggle to trace all data flows through complex applications. Code reviews miss edge cases. Trusted Types provides runtime enforcement at the browser level.
Enabling Trusted Types
Trusted Types are enabled via CSP: Content-Security-Policy: require-trusted-types-for 'script'. Once enabled, the browser throws a TypeError whenever a raw string is assigned to a DOM XSS sink. This is a hard enforcement -- the assignment does not happen, and the violation is surfaced as a JavaScript error.
Creating Policies
A Trusted Types policy is a named object with callback functions that transform strings into typed wrappers:
A policy named sanitize-html defines a createHTML callback. The callback receives the raw string, runs it through a sanitizer like DOMPurify, and returns the sanitized result. The browser wraps this return value in a TrustedHTML object. When that TrustedHTML object is assigned to innerHTML, the browser accepts it. Raw strings are still rejected.
Policies also define createScript (for TrustedScript objects used in eval-like sinks) and createScriptURL (for TrustedScriptURL objects used in script src attributes and dynamic imports).
The Default Policy
Trusted Types supports a special policy named default. When present, the browser automatically passes raw strings through this policy instead of throwing. This serves as a migration tool for large codebases -- you create a default policy that sanitizes inputs globally while gradually migrating individual call sites to explicit policies. The default policy should log violations (or even throw on known-bad patterns) so you can track and fix them over time.
Restricting Policy Creation
The CSP directive trusted-types policy-name-1 policy-name-2 restricts which policy names can be created. Any attempt to create a policy with a name not in this list throws an error. This prevents third-party libraries from creating permissive policies that bypass your security model. A strict configuration might allow only a single policy, ensuring all DOM mutations pass through one auditable code path.
Practical Adoption
Adopting Trusted Types in an existing application is non-trivial. Any library that writes to innerHTML, any ad script that uses document.write, any module that dynamically creates script elements -- all of these break under enforcement. The recommended approach: deploy with Content-Security-Policy-Report-Only: require-trusted-types-for 'script' first, use the reports to identify violations, create a default policy that wraps the most common patterns, then migrate to enforcement mode once violations are handled.
Frameworks like Angular have built-in Trusted Types support, automatically wrapping their template rendering through a policy. React largely avoids the problem because JSX does not use innerHTML (except dangerouslySetInnerHTML, which can be wrapped in a policy). Libraries must be evaluated individually for compatibility.
Browser Support
Trusted Types are supported in Chromium-based browsers (Chrome, Edge). Firefox and Safari have not yet implemented the API. For cross-browser applications, Trusted Types function as a progressive enhancement -- they strengthen security in supporting browsers without breaking functionality elsewhere.
Gotchas
- The default policy is a migration tool, not a permanent solution. Relying on it long-term means all string-to-sink assignments pass through one function, making it hard to audit and easy to make overly permissive.
- Third-party scripts will break under Trusted Types enforcement if they write raw strings to DOM sinks. You need to either wrap them in a policy, load them in a sandboxed iframe, or contact the vendor.
createHTMLreturning the input unchanged provides zero security. The policy must actually sanitize or validate. A no-op policy gives a false sense of security.- Trusted Types do not protect against server-side XSS. They only cover DOM XSS sinks in the browser. Server-rendered HTML that includes unsanitized input is not covered.
- Browser support is Chromium-only. Firefox and Safari users get no protection. Always combine Trusted Types with output encoding and CSP as layered defenses.