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:

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:

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 executed

Enforcing 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;

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