Chrome Ships Trusted Types: An Early Win Against DOM-Based XSS

DOM-based cross-site scripting has been one of the most persistent categories of web vulnerability for as long as JavaScript has been able to write directly into the page. The pattern is almost always the same: untrusted data — a URL parameter, a piece of user input, content pulled from an API response — flows into a “sink” API like innerHTML, document.write, or eval, and the browser dutifully executes whatever it’s handed. Sanitizer libraries have existed for years to clean data before it reaches these sinks, but a library can always be forgotten, misapplied, or bypassed at exactly one call site in a codebase with thousands of them. Trusted Types, which Chrome shipped support for starting with Chrome 83 in 2020 and which continued gaining adoption and refinement through 2021 and into 2022, takes a different approach: instead of trusting developers to sanitize correctly at every call site, it makes the browser itself refuse to execute unsanitized data at the API level.

The Problem Trusted Types Actually Solves

Most approaches to XSS prevention are advisory. A linter can flag a dangerous pattern; a code review can catch a missing sanitization step; a security training program can teach developers what not to do. All of these depend on humans consistently doing the right thing across every code path in a large, evolving application — including code paths written by developers who haven’t had the training, code inherited from a third-party library, and code changed six months after the original security review happened.

Content Security Policy, the more established browser-level defense, restricts where scripts can load from and what they’re allowed to do, but historically had a large gap: it did relatively little to stop an already-trusted, already-loaded script from misusing a dangerous DOM API with attacker-controlled data. A page could have an airtight CSP and still be vulnerable to DOM XSS if any script running on that page — first-party or third-party — passed unsanitized data into innerHTML.

Trusted Types closes that specific gap. It’s a browser-enforced policy that, once enabled via a CSP directive, prevents dangerous DOM sink APIs from accepting plain strings at all. Instead, those APIs will only accept a TrustedHTML, TrustedScript, or TrustedScriptURL object — special types that can only be created through a policy function the page has explicitly registered. If any code anywhere on the page tries to assign a raw string directly to innerHTML with Trusted Types enforcement active, the browser throws a TypeError and refuses, full stop, regardless of whether that code was written carefully or not.

How Enforcement Actually Works

The mechanism has three pieces that fit together:

A CSP directive turns enforcement on. Adding require-trusted-types-for 'script' to a page’s Content Security Policy tells the browser to start rejecting plain-string assignments to sink APIs on that page.

Policies are the only way to create a trusted value. A page registers one or more named policies via trustedTypes.createPolicy(), each with functions that transform an input string into a sanitized TrustedHTML (or the script or script-URL equivalent). Critically, the policy function is where the actual sanitization logic lives — Trusted Types itself doesn’t sanitize anything; it enforces that sanitization happened through a registered, auditable code path rather than happening (or not happening) inconsistently at every call site.

A trusted-types CSP directive can restrict which policy names are allowed to exist at all, meaning a page can lock down not just “raw strings are rejected” but also “only these specific, reviewed policy functions are permitted to produce trusted values in the first place” — closing off the possibility of an injected malicious script simply registering its own permissive policy to bypass the protection.

The practical effect is architectural rather than advisory: a codebase with Trusted Types correctly enforced cannot ship a DOM XSS vulnerability through the sink APIs it covers, because the vulnerable pattern — an unsanitized string reaching innerHTML — is not merely discouraged, it’s a runtime error that breaks the page during development and testing, long before it would reach production as a live vulnerability.

Why Google Prioritized This

Trusted Types emerged from Google’s own internal security engineering work — the company had been retrofitting the API onto large portions of its own web properties for years before formalizing it as a W3C proposal and shipping browser support. That internal experience mattered for the design: Google’s security team had accumulated real data on how much DOM XSS in its own large codebases traced back to exactly the sink-API pattern Trusted Types targets, and the API’s design reflects lessons learned from actually retrofitting large, pre-existing applications rather than a clean-slate theoretical exercise.

The W3C’s Web Application Security Working Group carried the specification forward as a standards-track proposal, and while Chrome was the first and, through this period, the primary browser with a shipping implementation, the standards process itself was explicitly framed as cross-vendor from the outset rather than a Chrome-only extension — a distinction that matters for whether a security feature like this is worth adopting broadly versus treating as a single-vendor curiosity.

The Adoption Reality

Retrofitting Trusted Types onto an existing, large application is genuinely hard work, and that difficulty is worth naming honestly rather than glossing over. Every existing call site that passes a string into innerHTML, document.write, or similar sinks needs to either be rewritten to use a registered policy, or the code path needs to be verified safe and explicitly excepted. For a mature codebase with years of accumulated string-to-DOM patterns, often written before anyone was thinking about this specific enforcement model, that audit is substantial — which is exactly why adoption, even among security-conscious engineering organizations, moved more slowly than the technical merits of the API would suggest on their own.

Google published tooling and migration guidance specifically aimed at reducing this friction, including a compatibility-analysis mode that reports what would break under enforcement without actually breaking it yet, letting teams audit the blast radius before flipping enforcement on. That staged-rollout pattern — report-only mode first, targeted policy authoring, then enforcement — became the standard playbook for organizations adopting Trusted Types on existing applications, distinct from greenfield projects that could design around sink-API discipline from day one.

What This Means for Extension and Web Developers

For developers building browser extensions, the relevance is direct: content scripts that inject UI into arbitrary third-party pages are exactly the kind of code that benefits from — and can be broken by — Trusted Types enforcement on the host page. An extension’s content script that blindly assigns HTML strings to innerHTML on a page it doesn’t control can find itself failing silently (or loudly, as a console error) on any site that has since adopted Trusted Types enforcement, which is a distinct failure mode from the traditional CSP-related breakage extension developers were already used to diagnosing.

For anyone building or maintaining a web application from the ground up, the practical recommendation is straightforward even this early in the API’s adoption curve: start in report-only mode, use the violation reports to understand which sink-API call sites exist across the application, and treat any newly-written code as an opportunity to route DOM construction through a small number of reviewed, trusted-type-producing utility functions rather than ad hoc string concatenation scattered across the codebase.

FAQ

Does Trusted Types replace the need for input sanitization? No, it enforces where sanitization happens, not whether it happens correctly. The policy functions a page registers still need to actually sanitize their input properly. What Trusted Types guarantees is that no code path can bypass the registered policy and assign an unsanitized string directly to a dangerous sink.

Which browsers support Trusted Types as of this writing? Chrome has shipped support since Chrome 83, and Chromium-based browsers inherit it accordingly. Firefox and Safari have not shipped implementations during this period, which means Trusted Types currently functions as a defense-in-depth measure for Chromium users specifically rather than a universally enforceable guarantee, though the W3C standards process remains cross-vendor in intent.

Is Trusted Types hard to retrofit onto an existing large application? Generally yes, and this is worth being honest about rather than minimizing. Every existing sink-API call site needs review, and report-only mode plus staged rollout is the realistic path for any non-trivial existing codebase, rather than flipping enforcement on directly.

Does Trusted Types stop all forms of XSS? No. It specifically targets DOM-based XSS through the sink APIs it covers. Reflected and stored XSS vectors that don’t route through a covered DOM sink, and vulnerabilities in server-side templating, are outside its scope and require separate defenses.

How does this relate to browser extension development specifically? Content scripts that inject markup into host pages need to be aware that pages enforcing Trusted Types will reject unsanitized innerHTML assignments from any script running on that page, including extension content scripts. That’s a distinct compatibility concern from network request interception, but both are part of the same broader shift toward platform-enforced security guarantees rather than developer-discipline-dependent ones.

For the request-blocking side of that same shift, see our look at the first Manifest V3 developer preview.