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.innerHTMLdocument.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:
- TrustedHTML: Required for sinks that parse and
render HTML, such as
Element.innerHTMLordocument.write(). - TrustedScript: Required for sinks that execute
JavaScript code, such as
eval()orHTMLScriptElement.text. - TrustedScriptURL: Required for sinks that load
external code for execution, such as
HTMLScriptElement.srcorWorker().
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.