Securing DOM Sinks with Trusted Types in Chromium

This article explores the purpose and functionality of Trusted Types in the Chromium web browser. It covers how this security API prevents DOM-based Cross-Site Scripting (DOM XSS) by locking down dangerous Document Object Model (DOM) manipulation sinks, how developers use security policies to sanitize data, and why this browser-level enforcement significantly reduces the web application attack surface.

The Problem with DOM Manipulation Sinks

For years, DOM-based Cross-Site Scripting (DOM XSS) has been one of the most persistent security vulnerabilities on the web. It occurs when an application takes untrusted data—such as user input from a URL parameter—and passes it directly into a DOM manipulation sink that executes code.

DOM sinks are browser APIs that allow JavaScript to modify the structure, style, or content of a webpage. Common risky sinks include:

  • Element.innerHTML
  • document.write()
  • eval()
  • HTMLScriptElement.src

By default, these APIs accept raw text strings. Because the browser cannot inherently tell the difference between a safe string created by a developer and a malicious string injected by an attacker, passing unvalidated strings into these sinks often results in unintended script execution.

The Purpose of Trusted Types

The primary purpose of Trusted Types in Chromium is to eliminate DOM XSS vulnerabilities by fundamentally changing how data is passed to DOM manipulation sinks. Instead of trying to catch every malicious string before it hits a sink, Trusted Types secures the sinks themselves.

When Trusted Types are enabled in Chromium via a Content Security Policy (CSP) header, the browser locks down all risky DOM sinks. They will no longer accept raw strings. If a developer attempts to pass a standard string into a sink like innerHTML, Chromium will block the action and throw a JavaScript TypeError.

To successfully manipulate the DOM, the application must instead pass a specific, strongly-typed object that indicates the data has been explicitly vetted as safe.

The Three Trusted Type Objects

Chromium enforces security by requiring one of three specific objects, depending on the sink being used:

  1. TrustedHTML: Required for sinks that parse and render HTML, such as Element.innerHTML or document.write().
  2. TrustedScript: Required for sinks that execute JavaScript code, such as eval() or HTMLScriptElement.text.
  3. TrustedScriptURL: Required for sinks that load external code for execution, such as HTMLScriptElement.src or Worker().

How Chromium Enforces Security Through Policies

Developers cannot simply instantiate a Trusted Type object directly; they must create it using a Trusted Type Policy. A policy is a user-defined JavaScript object that acts as a gatekeeper, enforcing specific security rules—such as running an HTML sanitizer like DOMPurify—before converting a raw string into a Trusted Type.

This mechanism serves two main purposes:

  • Centralized Sanitization: All potentially dangerous data must pass through a centralized policy to become a Trusted Type. If data does not go through the policy, the browser rejects it at the sink.
  • Simplified Security Audits: Instead of reviewing thousands of lines of application code to find every instance of innerHTML, security teams only need to audit the few lines of code that define the Trusted Type Policies. If the policy is secure, the DOM sinks are secure.

Shifting Security to the Browser Engine

Before Trusted Types, preventing DOM XSS relied entirely on developer discipline. A single forgotten sanitization check anywhere in the codebase could result in a vulnerability.

By integrating Trusted Types directly into Blink (Chromium’s rendering engine), the responsibility of enforcement shifts from the developer to the browser. Chromium monitors the sinks at the engine level, ensuring that insecure strings are rejected by default and making DOM XSS virtually impossible when the policies are properly configured.