How Trusted Types Prevent DOM XSS in JavaScript
DOM-based Cross-Site Scripting (DOM XSS) remains one of the most common and dangerous client-side web vulnerabilities. Trusted Types is a modern browser security mechanism that systematically eliminates DOM XSS by enforcing strict type checking on dangerous web APIs. Instead of allowing arbitrary strings to be inserted into the Document Object Model (DOM), Trusted Types requires developers to wrap and sanitize data into dedicated, immutable objects before passing them to vulnerable execution points known as injection sinks.
The Problem: DOM-based XSS and Injection Sinks
DOM XSS occurs when untrusted user input is passed directly into browser APIs capable of parsing and executing code. These entry points are known as “DOM sinks.” Common injection sinks include:
- HTML execution sinks:
element.innerHTML,element.outerHTML, anddocument.write(). - Script execution sinks:
eval(),setTimeout(string), andsetInterval(string). - URL/Navigation sinks:
<script src="...">,location.href, andiframe.src.
Traditionally, developers rely on manual sanitization or validation before passing strings to these sinks. However, in large codebases, it is easy to miss a sink, leading to security regressions where malicious payload strings execute within the user’s browser session.
What Are Trusted Types?
Trusted Types shifts the responsibility of security from individual
API calls to a centralized type system. When Trusted Types is enabled,
browser sinks refuse to accept raw JavaScript strings. If a developer
attempts to pass a plain string to an element like
element.innerHTML, the browser throws a runtime
TypeError.
To interact with these sinks, data must be converted into one of the specialized Trusted Type objects:
TrustedHTML: Safe HTML markup for DOM sinks.TrustedScript: Safe JavaScript code for script evaluation sinks.TrustedScriptURL: Safe URLs for loading external scripts.
Because these objects can only be instantiated through defined policies, vulnerabilities cannot be introduced by accidentally passing unverified strings.
How Trusted Types Work
To create a Trusted Type object, you define a policy using the
window.trustedTypes API. Policies contain custom logic to
sanitize or validate data, often integrating libraries such as
DOMPurify.
1. Creating a Policy
// Check for browser support
if (window.trustedTypes && window.trustedTypes.createPolicy) {
const sanitizePolicy = window.trustedTypes.createPolicy('mySanitizerPolicy', {
createHTML: (string) => {
// Use a sanitizer library to strip malicious code
return DOMPurify.sanitize(string, { RETURN_TRUSTED_TYPE: false });
},
createScriptURL: (url) => {
// Validate that script URLs originate only from trusted domains
const parsed = new URL(url, document.baseURI);
if (parsed.origin === 'https://trusted-cdn.example.com') {
return url;
}
throw new Error('Untrusted script URL source');
}
});
}2. Using Trusted Types with Sinks
Once a policy is registered, you pass the generated object to the DOM sink rather than the raw string:
// Raw strings will fail when Trusted Types are enforced:
// element.innerHTML = userInput; // Throws TypeError
// Pass data through the policy to create a TrustedHTML object:
const safeHTML = sanitizePolicy.createHTML(userInput);
element.innerHTML = safeHTML; // Successfully executedEnforcing Trusted Types via CSP
Trusted Types is enabled and enforced using the
Content-Security-Policy (CSP) HTTP header.
Enforcing Mode
To block all raw strings passed into sinks across the application, send the following HTTP header:
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types mySanitizerPolicy;
require-trusted-types-for 'script': Tells the browser to require Trusted Types for all DOM sinks that can execute scripts.trusted-types mySanitizerPolicy: Restricts policy creation to only explicitly allowlisted policy names, preventing unauthorized code or malicious dependencies from registering their own pass-through policies.
Report-Only Mode
For migration purposes, Trusted Types can be deployed in report-only
mode using the Content-Security-Policy-Report-Only header.
This logs violations to a reporting endpoint without breaking the
application, allowing teams to identify and refactor remaining
string-based sinks.
Benefits of Trusted Types
- Eliminates Code-Level Oversight: It removes reliance on developers remembering to sanitize data at every DOM write operation.
- Centralized Security Audits: Security reviews only need to examine policy definitions rather than searching through thousands of DOM manipulation calls.
- Compile-Time and Runtime Safety: By stopping string-to-DOM conversions, applications gain deterministic protection against DOM XSS at the browser level.