Trusted Types: DOM XSS Protection at Scale

Jen Ozmen (Software Engineer · Google), Youssef Attia (Software Engineer · Google)

BSides NYC 2024 · Day 1 · Tech - Blue

Overview

This talk, presented by Jen Ozmen and Youssef Attia, Software Engineers at Google, introduces Trusted Types, an innovative browser feature designed to mitigate DOM-based Cross-Site Scripting (XSS) vulnerabilities at an unprecedented scale. The speakers, both working on Google's web security team, highlight the pervasive nature of XSS, citing Google's own vulnerability rewards program data where XSS consistently represents a significant portion of reported web bugs. Their work focuses on deploying web security mitigations across Google's vast ecosystem of web services, where the challenge lies in securing applications developed by hundreds of engineers.

Watch on YouTube

Visual summary for Trusted Types: DOM XSS Protection at Scale by Jen Ozmen, Youssef Attia
Visual summary for Trusted Types: DOM XSS Protection at Scale by Jen Ozmen, Youssef Attia

Key moments

  1. 0:00 Introduction to Trusted Types and talk overview
  2. 0:45 Understanding Cross-Site Scripting (XSS) and its impact
  3. 2:20 Focusing on DOM XSS: Client-side variant explained
  4. 3:20 Challenges: Why DOM XSS persists despite frameworks
  5. 4:10 Introducing Trusted Types: A solution for unsolved XSS
  6. 4:40 Core concept: Trusted Types restrict dangerous DOM APIs
  7. 5:20 Enabling Trusted Types via CSP HTTP header
  8. 6:00 Security guarantees come from Trusted Type policies

Trusted Types: DOM XSS Protection at Scale

Speakers: Jen Ozmen, Software Engineer, Google; Youssef Attia, Software Engineer, Google

Conference: BSides NYC

YouTube: https://www.youtube.com/watch?v=rM_CqA1BQzY

Overview

This talk, presented by Jen Ozmen and Youssef Attia, Software Engineers at Google, introduces Trusted Types, an innovative browser feature designed to mitigate DOM-based Cross-Site Scripting (XSS) vulnerabilities at an unprecedented scale. The speakers, both working on Google's web security team, highlight the pervasive nature of XSS, citing Google's own vulnerability rewards program data where XSS consistently represents a significant portion of reported web bugs. Their work focuses on deploying web security mitigations across Google's vast ecosystem of web services, where the challenge lies in securing applications developed by hundreds of engineers.

The core problem addressed is the ease with which user-controlled input can be inadvertently executed as code by vulnerable DOM APIs, leading to severe security breaches such as account takeover. Trusted Types offers a robust, client-side solution by restricting access to these dangerous APIs and enforcing a type-checking mechanism at runtime. This requires developers to explicitly define trusted sources for dynamic content, making security decisions auditable and preventing malicious strings from ever reaching the DOM as executable code. The talk delves into the technical mechanisms of Trusted Types, practical strategies for adoption in both new and existing applications, and Google's impressive success in eliminating DOM XSS on hundreds of its flagship products through its implementation.

Background

▶ Watch: Introduction to Trusted Types and talk overview (0:00)

Cross-Site Scripting (XSS) remains one of the most prevalent and dangerous web vulnerabilities. As highlighted by Google's vulnerability rewards program (VRP) data, XSS bugs constitute a substantial portion of reported web security issues, underscoring its persistence despite ongoing efforts. The fundamental cause of XSS is the improper handling of user-supplied data, allowing it to be interpreted as executable code within a trusted web page. This can manifest in various forms, but the focus of this talk is DOM XSS, a client-side variant where the vulnerability arises from unsafe manipulation of the Document Object Model (DOM) by client-side JavaScript.

The root of DOM XSS lies in the fact that many standard DOM APIs are not secure by default. Functions like innerHTML, script.innerText, document.write, and over 60 other DOM APIs can directly convert strings into executable code or markup. When these APIs are fed user-controlled input without proper sanitization or escaping, an attacker can inject malicious scripts. Exploiting DOM XSS can grant an attacker significant control over a victim's session, allowing them to steal session tokens, cookies, or perform actions on behalf of the user, akin to a client-side remote code execution.

The challenge of mitigating DOM XSS is compounded by several factors:

  1. Insecure-by-default APIs: The sheer number of potentially dangerous DOM APIs makes it difficult for developers to remember and correctly use secure alternatives.
  2. Dynamic and Complex JavaScript: Modern web applications are highly dynamic, with complex client-side logic that often involves manipulating the DOM. This complexity makes static analysis difficult and allows vulnerabilities to hide within layers of abstraction, including third-party libraries or legacy code.
  3. Organizational Overhead: In large organizations like Google, with potentially hundreds of engineers working on a single application, ensuring consistent secure coding practices is a monumental task. Dependencies, external libraries, and the rapid pace of development can introduce vulnerabilities without direct developer action.

Despite Google's world-class tooling for static analysis and rigorous security reviews, DOM XSS remained an unsolved problem. Traditional approaches, such as manual code reviews, developer training, and static analysis, proved insufficient to catch all instances of these subtle yet dangerous flaws, especially in legacy codebases or complex frameworks. This persistent challenge necessitated a more fundamental, browser-enforced solution, leading to the development of Trusted Types.

Key Findings

▶ Watch: Focusing on DOM XSS: Client-side variant explained (2:20)

The central and most compelling finding presented by the speakers is the elimination of DOM XSS on applications that enforce Trusted Types. Specifically, Google has seen zero reported DOM XSS vulnerabilities across over 400 applications that have adopted this mitigation. This includes many of Google's flagship products such as Gemini, Photos, Docs, and Gmail, demonstrating the feature's effectiveness at an enterprise scale.

The core mechanism enabling this success is Trusted Types' ability to make security decisions explicit and auditable by introducing a runtime type-checking mechanism in the browser. Instead of allowing arbitrary strings to be passed into dangerous DOM APIs, Trusted Types restricts access to these APIs, requiring typed objects (e.g., TrustedHTML, TrustedScriptURL) that are guaranteed to have originated from a trusted source or undergone a secure transformation. This fundamentally shifts the security paradigm from "try to find all bad inputs" to "only allow explicitly good inputs."

Key contributions and findings include:

  • Browser-enforced security: Trusted Types is a browser-native feature (currently supported on Chromium, with upcoming support from other vendors) that provides a robust, client-side defense against DOM XSS. It's not merely a linter or a static analysis tool but an enforcement mechanism that blocks unsafe operations at runtime.
  • Comprehensive API coverage: Trusted Types restricts over 60 DOM APIs known to be susceptible to XSS, significantly reducing the attack surface.
  • Scalable adoption strategies: The talk outlines two primary approaches for adoption:
  1. Greenfield Development: Using frameworks (e.g., Angular, Lit, React with specific configurations) that are already hardened for Trusted Types compatibility.
  2. Refactoring Existing Applications: A detailed, multi-stage process involving reporting modes, violation triage, and systematic refactoring patterns, including the use of tools like SafetyWeb (an ESLint-based static analysis tool) and SafeValues (a library for type-safe assignments).
  • Default policy as a pragmatic fallback: For complex scenarios, especially with third-party libraries, the concept of a default policy allows for a blanket sanitization or specific pattern whitelisting, providing a pragmatic path to adoption while acknowledging its limitations.
  • Ecosystem contribution: Google's investment in Trusted Types extends beyond internal use, with contributions to open-source frameworks and tools, aiming to build a more secure web development ecosystem for all.

The overall finding is that by shifting from implicit trust in strings to explicit trust in typed objects, enforced by the browser, DOM XSS can be effectively and comprehensively mitigated, even in large, complex, and evolving web applications.

Technical Deep Dive

▶ Watch: Introducing Trusted Types: A solution for unsolved XSS (4:10)

Trusted Types operates by introducing a runtime type-checking mechanism directly into the browser. Its fundamental principle is to prevent strings from being passed directly into sensitive DOM manipulation APIs that can lead to code execution. Instead, these APIs must receive Trusted Type objects, which are guarantees that the content has been processed by a trusted policy.

Enabling Trusted Types

Trusted Types is enabled via an HTTP response header, specifically within the Content Security Policy (CSP). The TrustedTypes directive is added to the CSP, specifying which "policies" are allowed to create trusted objects. For example:

Content-Security-Policy: trusted-types <policy-name> <another-policy>; require-trusted-types-for 'script'; report-uri /csp-report-endpoint

The require-trusted-types-for 'script' directive is crucial; it instructs the browser to enforce Trusted Types for all APIs that accept script, HTML, or URL values. A report-uri can also be specified to collect violation reports.

The Problem: Unsafe String Assignments

Consider a common DOM XSS vector:

Without Trusted Types, this assigns the malicious string directly to innerHTML, executing the onerror handler. With Trusted Types enforced, the browser will block this assignment and throw an error, stating that a TrustedHTML object is required, not a plain string.

Trusted Type Objects and Policies

Trusted Types defines several specific object types:

  • TrustedHTML: For HTML strings.
  • TrustedScript: For JavaScript code strings.
  • TrustedScriptURL: For script URLs.
  • TrustedURL: For general URLs.

These objects cannot be created directly by developers using new TrustedHTML(). Instead, they must be created by a Trusted Types Policy. A policy is a JavaScript object registered with the browser that defines how untrusted strings are transformed into trusted types.

Here’s an example of creating a policy:

The security guarantee comes from the policy's implementation. For createHTML, using a robust sanitizer like DOMPurify (which offers out-of-the-box Trusted Types compatibility with the RETURN_TRUSTED_TYPES setting) ensures that the output HTML is safe. The policy acts as a wrapper, taking a string and returning a Trusted Type object only after it has been deemed safe.

Enforcement and Reporting Modes

Trusted Types supports two modes of operation, crucial for progressive rollout:

  1. report-only: Enabled by Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri /report-endpoint. In this mode, violations are reported to the specified endpoint but are not blocked by the browser. This allows developers to collect violation data and identify problematic code without breaking functionality.
  2. enforce: Enabled by Content-Security-Policy: require-trusted-types-for 'script'; report-uri /report-endpoint. In this mode, violations are both reported and blocked by the browser, preventing the XSS payload from executing.

Violation reports are sent as JSON objects to the report-uri. These reports contain critical debugging information:

  • document-uri: The URL of the page where the violation occurred.
  • source-file: The JavaScript file, line, and column number where the dangerous API call was made. (Caveat: this might be empty for inline scripts or show browser extensions as the source).
  • script-sample: The specific DOM sink (e.g., innerHTML) and the truncated value (up to 40 characters) of the string that triggered the violation.

Refactoring Strategies

The talk outlines several common refactoring patterns for existing codebases:

  1. Escaping User Input: For user input meant to be displayed as text, not markup, escaping special characters (e.g., &, <, >) can prevent interpretation as code.

Example:

  1. Using Safer DOM APIs: Often, a more specific and safer API can replace a dangerous one. textContent is a prime example, as it treats all input as plain text, preventing HTML parsing.

Example:

  1. Constant Values and SafeValues: For hard-coded, constant strings (e.g., static HTML snippets), these can be directly converted into Trusted Type objects, as their safety is known. The SafeValues library, developed by Google, helps developers make safer choices by enforcing TypeScript type checking, ensuring values are trusted before assignment.

Example with SafeValues:

Alternatively, an inline policy can be used for simple constants.

  1. Legacy Conversions: For complex legacy logic that is difficult to refactor immediately, a temporary "legacy conversion" function can be used. This function takes a string and returns a Trusted Type object without full sanitization, effectively bypassing Trusted Types for that specific call site. This is not secure, but it allows for incremental rollout, marking areas for future, proper refactoring.

This approach should be tracked (e.g., using VS Code's "right-click" features to find usages) and revisited. The principle is to convert strings to safe types as close to the source as possible to maintain reasoning about safety.

  1. Reviewed Conversions: Similar to legacy conversions, but used when a developer has manually reviewed a specific call site and is confident the string is safe, even if it's dynamic. A justification comment should be included.

Again, this is a bypass and should be used sparingly after careful security review.

Third-Party Dependencies and the Default Policy

A significant challenge arises with third-party libraries that use dangerous DOM APIs internally. Google's ideal approach is to patch upstream repositories and upgrade libraries, improving the entire JavaScript ecosystem. However, this is often impractical due to maintainer approval delays or deep dependency chains.

When upstream patching isn't feasible, Google's workaround has been to fork repositories, apply patches, and use private copies – a tedious and unsustainable solution.

The Default Policy offers a more scalable alternative. A policy can be designated as default in the CSP:

Content-Security-Policy: trusted-types default; require-trusted-types-for 'script';

The default policy is invoked by the browser whenever a Trusted Types violation occurs, and no specific policy was used to create the object. This allows for blanket fallback behavior.

A common use case for the default policy is to handle libraries like jQuery, which may use innerHTML under the hood for seemingly safe operations (e.g., creating <link> or <textarea> elements). The default policy can inspect the input string and allow only known-safe patterns while sanitizing or blocking others. If the default policy returns null, the browser proceeds to generate a violation report.

While powerful for reducing refactoring workload, the default policy has a significant drawback: its benefits are only reaped by users on Chromium-based browsers that support Trusted Types. The underlying code remains unsafe for other browsers. Moreover, a blanket sanitizer in a default policy needs careful configuration to avoid bypasses, which is an active area of research. Thus, while pragmatic, it's not the most recommended long-term solution compared to refactoring the code itself.

Tooling for Adoption

Google has developed several tools to aid Trusted Types adoption:

  • SafetyWeb: An ESLint-based static analysis tool that integrates Google's internal static analysis checks for unsafe API usages. It alerts developers to risky code during development.
  • SafeValues: A TypeScript-aware library that steers developers towards safer choices by requiring Trusted Type objects for assignments, leveraging type-checking to prevent unsafe string usage.
  • TrustedTypesHelper (in active development): A Chrome extension that will provide a DevTools tab to demystify Trusted Types violations and suggest refactoring solutions, streamlining the debugging process.

These tools, combined with the comprehensive refactoring methodology, form Google's strategy for building a more secure web ecosystem.

Demo / Proof of Concept

▶ Watch: Core concept: Trusted Types restrict dangerous DOM APIs (4:40)

While the talk did not feature a live, interactive demo, the core functionality and protective capabilities of Trusted Types were demonstrated through illustrative code snippets and explanations of browser behavior. The primary proof of concept revolves around how Trusted Types fundamentally alters the execution flow when an unsafe string is passed to a vulnerable DOM API.

The speakers used a simple, yet potent, example:

If location.hash contained a malicious payload like "<img src=x onerror=alert(document.domain)>", this code would execute the alert function, proving an XSS vulnerability.

With Trusted Types enforced (via a Content-Security-Policy header including require-trusted-types-for 'script'), the browser's behavior changes dramatically. When the same assignment is attempted:

The browser would immediately throw an error in the console, explicitly stating that a TrustedHTML object is required for innerHTML, and a plain string was provided. This error signifies that the DOM XSS attempt has been unsuccessful, as the browser actively blocked the unsafe operation at runtime. This direct blocking mechanism is the most powerful demonstration of Trusted Types' efficacy.

Furthermore, the talk implicitly demonstrated the "fix" using a Trusted Types Policy combined with a sanitizer like DOMPurify:

In this scenario, if location.hash contained the malicious payload, policy.createHTML would pass it to DOMPurify.sanitize. DOMPurify would then strip out the unsafe onerror attribute, returning a safe TrustedHTML object. This trusted object would then be successfully assigned to innerHTML, but without any executable script, thus demonstrating how safe content can flow through the system while malicious content is neutralized.

The discussion of the report-only mode also serves as a "proof of concept" for the debugging process. By enabling report-only, developers can observe violations appearing in the browser's console and being sent to a reporting endpoint, without any visible breakage to the user interface. This demonstrates how the system can identify and flag unsafe code before full enforcement. The script-sample in violation reports, showing the DOM sink and the value passed, directly illustrates the problematic code in action.

Defensive Implications

▶ Watch: Security guarantees come from Trusted Type policies (6:00)

Trusted Types offers a profound shift in web security, moving from reactive patching to proactive, browser-enforced protection against DOM XSS. For defenders, the implications are significant, providing a robust framework to secure web applications at scale.

  1. Prioritize Trusted Types Adoption: The most critical defensive implication is to adopt Trusted Types in all web applications. Google's success of zero DOM XSS on over 400 applications is a compelling testament to its effectiveness. This should be a high-priority security initiative, especially for applications handling sensitive user data.
  1. Integrate with Modern Frameworks: When starting new projects, leverage web frameworks that are already compatible with Trusted Types, such as hardened versions of Angular, Lit, and React. These frameworks abstract away the complexities, providing a secure foundation by default. Defenders should advocate for the use of such frameworks and contribute to their hardening efforts.
  1. Enforce Early in Development: Integrate Trusted Types enforcement into CI/CD pipelines and developer environments from the outset. By sending the Content-Security-Policy header with require-trusted-types-for 'script' in development servers (e.g., Webpack configurations) or using meta tags, developers can catch violations immediately. This "shift-left" approach prevents insecure code from ever reaching production.
  1. Implement a Systematic Refactoring Process: For existing, complex applications, follow a phased rollout approach:
  • Start with report-only mode: Deploy Trusted Types in report-only mode to collect violation reports without causing user-observable breakages. This allows for comprehensive discovery of all problematic call sites, including those in third-party libraries.
  • Triage and Prioritize Reports: Be prepared for noisy reports, as browser extensions or malware can trigger violations. Focus on actionable reports originating from your first-party code. Leverage tools like the upcoming TrustedTypesHelper Chrome extension to demystify violations.
  • Apply Refactoring Patterns: Systematically address violations using the recommended patterns:
  • Use safer DOM APIs: Always prefer textContent over innerHTML if only text is needed.
  • Escape user input: Sanitize user-controlled strings that are expected to be text.
  • Convert constants: For hard-coded safe values, use SafeValues or inline policies to create Trusted Types.
  • Strategic bypasses (Legacy/Reviewed Conversions): For highly complex legacy code or thoroughly reviewed safe code, use temporary bypasses but ensure they are tracked for future, proper refactoring and include clear justifications. These should be a last resort.
  • "Close to the Source" Principle: When converting strings to Trusted Types, perform the conversion as close to the source of the string as possible. This makes it easier to reason about the string's safety throughout its lifecycle and prevents accidental modification by intermediate functions.
  • Iterate and Enforce: Continuously refactor and re-evaluate reports until confidence is high that all first-party code is compliant. Only then switch to full enforce mode to block violations.
  1. Strategically Use Default Policies: For third-party dependencies that cannot be patched upstream, the default policy can be a pragmatic solution. It allows for blanket sanitization or whitelisting of known-safe patterns (e.g., jQuery's internal innerHTML usage for specific elements) to unblock adoption. However, defenders must be aware of its limitations:
  • Browser-specific protection: Default policies only protect users on browsers supporting Trusted Types.
  • Sanitizer complexity: Blanket sanitizers require careful configuration to avoid bypasses, which is a complex area of security research.
  • Potential for silencing: A poorly configured default policy might unintentionally silence legitimate violations, masking underlying vulnerabilities. Use the ability to return null from a default policy to still generate reports for problematic inputs.
  1. Leverage Static Analysis Tools: Integrate tools like SafetyWeb (ESLint-based) and SafeValues into development workflows. These tools provide early warnings and guide developers toward secure coding patterns, reducing the burden on security teams for manual code reviews.
  1. Foster a Security-Conscious Ecosystem: Contribute to the open-source community by submitting patches to libraries that introduce Trusted Types compatibility. This collective effort improves the security posture of the entire web.

By implementing these defensive strategies, organizations can significantly reduce their exposure to DOM XSS, enhance the overall security posture of their web applications, and free up security engineers to focus on more complex threats.

Key Takeaways

  • DOM XSS is a persistent and critical vulnerability: Despite significant efforts, DOM XSS remains a major threat, allowing attackers to take control of user accounts. Traditional mitigations often fall short, especially in complex, dynamic web applications.
  • Trusted Types provides robust, browser-enforced protection: This browser-native feature (currently Chromium-only, but expanding) blocks unsafe assignments to over 60 dangerous DOM APIs, requiring explicitly "trusted" objects instead of plain strings.
  • Google has achieved zero DOM XSS on hundreds of applications: By implementing Trusted Types across over 400 of its products, Google has successfully eliminated reported DOM XSS vulnerabilities, demonstrating its effectiveness at scale.
  • Adoption requires a systematic refactoring approach: For existing codebases, a phased rollout starting with report-only mode, followed by iterative refactoring using specific patterns (safer APIs, escaping, SafeValues, controlled bypasses), is crucial.
  • Default policies offer pragmatic solutions for third-party code: While not ideal, a default policy can help unblock adoption by applying blanket sanitization or allowing specific, known-safe patterns in unmanageable third-party dependencies, though with caveats regarding browser support and sanitizer configuration.
  • Leverage tooling and frameworks for easier adoption: Tools like SafetyWeb and SafeValues, along with modern web frameworks hardened for Trusted Types, significantly streamline the development and refactoring process, promoting a more secure ecosystem.

About the Speaker(s)

Jen Ozmen is a Software Engineer at Google, where she has worked for over three years. She is a member of the web security team, focusing on deploying web security mitigations across Google Web Services. Her work involves collaborating with numerous teams to implement security solutions at scale, addressing challenges posed by the vast number of product engineers and diverse web applications within Google.

Youssef Attia is also a Software Engineer at Google, working on the same web security team as Jen Ozmen. Together, they contribute to enhancing the security posture of Google's web products by developing and implementing advanced security features like Trusted Types. Their shared expertise lies in understanding the intricacies of web vulnerabilities and engineering scalable solutions to protect users across Google's extensive online presence.

Reviews

Dr. Zero (Offensive Security Researcher) — SOLID

A competent, well-structured walkthrough of Trusted Types from the team that actually shipped it at scale across 400+ Google products. The zero-DOM-XSS claim is the headline and it's earned, but the talk is fundamentally a deployment guide, not a research contribution — the mechanism itself is years old at this point.

Heather Calloway (CISO) — WEAK

Technically solid presentation of a real browser security control with genuine deployment results at Google scale. But this is an engineering talk for web developers, and it never crosses the line into anything a security leader, program manager, or governance-minded operator can act on.

→ Top-rated talks at BSides NYC 2024

All talks from BSides NYC 2024