Trustedhtml: Secure HTML Injection with Trusted Types

Prevent DOM-based XSS attacks with browser-native security. Learn to implement TrustedHTML, create policies, and integrate with CSP for robust HTML injection protection.

Cross-site scripting (XSS) attacks remain one of the most critical security vulnerabilities in modern web applications. The Trusted Types API provides a browser-native mechanism to prevent DOM-based XSS by requiring that potentially dangerous HTML inputs pass through developer-defined policies before being inserted into the DOM. This comprehensive guide covers everything web developers need to know about implementing TrustedHTML and the broader Trusted Types framework.

Understanding Trusted Types is essential for any development team building secure web applications. By shifting from manual sanitization everywhere to policy-based injection control, you create a more maintainable and defense-in-depth security architecture that scales with your application.

What Is TrustedHTML?

TrustedHTML is an interface within the Trusted Types API that represents a string that has been processed through a developer-defined policy and can safely be inserted into HTML injection sinks. Unlike regular strings, TrustedHTML objects cannot be modified once created and carry an implicit guarantee that the content has passed through appropriate sanitization or transformation, as documented in the MDN TrustedHTML documentation.

Key Characteristics

Immutability: Once a TrustedHTML object is created, its value cannot be changed. This prevents any tampering after the content has been processed through your security policy.

Type Safety: Browser APIs that accept TrustedHTML will reject plain string assignments when Trusted Types are enforced via Content Security Policy, catching security issues at runtime rather than relying solely on code review.

Policy-Based Creation: TrustedHTML objects are created exclusively through TrustedTypePolicy.createHTML() methods, ensuring every piece of trusted HTML passes through your defined security logic.

No Public Constructor: There is no way to directly instantiate TrustedHTML--it must be created via a policy, preventing unauthorized creation of trusted values.

The Three Trusted Types

The Trusted Types API distinguishes between three specialized types of trusted values, each targeting different injection contexts:

  • TrustedHTML: For HTML injection sinks like innerHTML, outerHTML, and document.write()
  • TrustedScript: For JavaScript execution sinks like eval() and the Function constructor
  • TrustedScriptURL: For script URL sinks like HTMLScriptElement.src

Each type serves a specific purpose and can only be used with its corresponding injection sinks, creating clear boundaries in your security architecture.

Trusted Types and Their Corresponding Injection Sinks
Trusted TypePurposeCommon Sinks
TrustedHTMLHTML injection protectioninnerHTML, outerHTML, document.write(), insertAdjacentHTML, setHTML
TrustedScriptJavaScript execution protectioneval(), Function(), setTimeout/setInterval (string arg)
TrustedScriptURLScript URL protectionscript.src, script.href (inline event handlers)

How Trusted Types Work

Understanding how the Trusted Types API operates at a fundamental level is essential for implementing it correctly in your applications. The system revolves around the concept of policies--named configurations that define how untrusted input transforms into safe, trusted output.

The XSS Problem

DOM-based XSS attacks occur when untrusted data flows through browser APIs that execute HTML or JavaScript. Traditional defense requires developers to manually sanitize every input, which is error-prone and easy to miss in large codebases. Trusted Types shifts the security model from "sanitize everything everywhere" to "only allow sanitized content through specific, named policies," as defined in the W3C Trusted Types specification.

Policies: The Foundation of Trusted Types

A TrustedTypePolicy is a configuration object that defines how untrusted input should be transformed into safe, trusted output. Each policy has a unique name and provides callback functions for creating TrustedHTML, TrustedScript, and TrustedScriptURL values. Policies serve as security boundaries--only code that has access to a policy can create trusted values for that policy's sinks.

Creating a policy establishes a named security contract: any code using that policy name agrees to process data through the defined transformation functions. This makes it easy to audit and review which data flows through which security checks. Understanding these concepts pairs well with learning about advanced JavaScript objects and how browser APIs handle different data types.

Creating a Trusted Types Policy
1const myPolicy = trustedTypes.createPolicy('myPolicy', {2 createHTML: (input) => sanitizedHTML,3 createScript: (input) => safeScript,4 createScriptURL: (input) => safeURL5});

Creating TrustedHTML Objects

Creating TrustedHTML requires defining a policy with a createHTML function, then using that policy to transform untrusted input. When Trusted Types are enforced via CSP, attempting to assign a plain string to innerHTML would throw a TypeError, ensuring only policy-created TrustedHTML values can be used at injection sinks.

This enforcement mechanism provides runtime protection against accidental or malicious misuse of injection APIs, catching security issues before they can cause harm.

Creating and Using TrustedHTML
1// Define a policy that sanitizes HTML2const sanitizerPolicy = trustedTypes.createPolicy('sanitizer', {3 createHTML: (input) => {4 return DOMPurify.sanitize(input);5 }6});7 8// Create TrustedHTML from untrusted input9const untrustedInput = '<img src=x onerror=alert(1)>';10const trustedHTML = sanitizerPolicy.createHTML(untrustedInput);11 12// Use TrustedHTML with injection sink13element.innerHTML = trustedHTML; // Safe - content was sanitized

Injection Sinks and Trusted Values

TrustedHTML is accepted by multiple HTML injection APIs across the browser platform. Understanding which sinks require trusted values helps you identify where to apply policies in your codebase.

HTML Injection Sinks (TrustedHTML):

  • Element.innerHTML and Element.outerHTML
  • document.write() and document.writeln()
  • Element.insertAdjacentHTML()
  • Range.createContextualFragment()
  • DOMParser.parseFromString()
  • Element.setHTML()

JavaScript Execution Sinks (TrustedScript):

  • eval() function
  • Function constructor
  • setTimeout and setInterval with string arguments
  • script.textContent

Script URL Sinks (TrustedScriptURL):

  • HTMLScriptElement.src
  • HTMLScriptElement.href for inline event handlers

These concepts align with AJAX and API calls in modern web applications, where secure data handling is paramount.

Browser Support and Compatibility

As of 2025, Trusted Types have achieved broad support in Chromium-based browsers and Safari, while Firefox maintains limited availability behind feature flags. Understanding the current landscape helps you make informed decisions about adoption timelines and fallback strategies.

Chrome has supported Trusted Types since version 83, and this support extends to Edge (Chromium-based) and Opera. Safari added full support in version 16.4, making the feature available on iOS devices as well. Firefox currently requires the security.tls.insecure_fallback_hosts preference to be set and offers limited functionality behind a feature flag.

The TrustedHTML interface is not yet part of Baseline due to Firefox's limited support, which means applications requiring broad cross-browser compatibility should implement feature detection and fallback strategies. This approach ensures graceful degradation for users on unsupported browsers while still providing enhanced security where supported.

Chrome 83+

Full support for all Trusted Types features

Safari 16.4+

Full support across iOS and macOS

Edge 83+

Full support (Chromium-based)

Firefox

Limited support behind feature flag

Opera 69+

Full support

Baseline

Not yet included due to Firefox gap

Feature Detection for Trusted Types
1if (window.trustedTypes && trustedTypes.createPolicy) {2 // Trusted Types are supported3 const policy = trustedTypes.createPolicy('default', {4 createHTML: (input) => input5 });6} else {7 // Fallback to traditional sanitization8 element.innerHTML = sanitize(userInput);9}

Content Security Policy Integration

Trusted Types enforcement is controlled through Content Security Policy directives, integrating seamlessly with your existing CSP infrastructure. Understanding these directives is crucial for proper configuration and enforcement.

When properly configured, CSP with Trusted Types provides defense-in-depth security that prevents entire categories of DOM-based XSS vulnerabilities from being exploitable, even if other security measures fail.

require-trusted-types-for Directive

The require-trusted-types-for directive specifies which types of sinks require trusted values. When enforced, this directive causes browsers to throw TypeErrors when plain strings are passed to protected sinks, blocking potential XSS attacks at runtime.

Common values include 'script' for JavaScript execution sinks and 'dom' for DOM injection sinks. Both can be specified together to enforce protection across all trusted types simultaneously, as outlined in the W3C Trusted Types CSP directives specification.

CSP Directive for Trusted Types Enforcement
1Content-Security-Policy: require-trusted-types-for 'script' 'dom'

trusted-types Directive

The trusted-types directive controls which policies can be created in your application. This is a critical security control--it specifies an allowlist of approved policy names and prevents unapproved policies from being created.

Without this directive, any script in your application could create policies, potentially bypassing your security controls. The special keyword 'allow-duplicates' permits multiple policies with the same name, which is normally prohibited (only one policy per name is allowed by default).

CSP Directive for Policy Allowlist
1Content-Security-Policy: trusted-types myPolicy sanitizerPolicy 'allow-duplicates'

Using TrustedHTML with Sanitization Libraries

While you can implement custom sanitization logic in your policies, using established sanitization libraries provides robust protection against known XSS vectors. The most widely used option integrates seamlessly with Trusted Types.

DOMPurify Integration

DOMPurify is the most widely used XSS sanitization library and works seamlessly with Trusted Types. When configured with the RETURN_TRUSTED_TYPE option, DOMPurify returns TrustedHTML objects directly, simplifying your policy implementation.

The library provides comprehensive protection against XSS attacks while maintaining good performance, making it suitable for high-traffic applications. Its extensive test suite and active maintenance provide confidence in its security guarantees, as evidenced by its use in production environments across the web.

For more details, visit the DOMPurify GitHub repository which maintains extensive documentation and security advisories.

DOMPurify with Trusted Types
1// Create a policy that uses DOMPurify2const securePolicy = trustedTypes.createPolicy('secure-html', {3 createHTML: (input) => DOMPurify.sanitize(input, {4 RETURN_TRUSTED_TYPE: true5 })6});7 8// DOMPurify with RETURN_TRUSTED_TYPE returns TrustedHTML directly9const trustedHTML = securePolicy.createHTML(userInput);10element.innerHTML = trustedHTML;

Alternative Sanitization Approaches

While DOMPurify is the most feature-complete option, other sanitization libraries may better fit specific use cases:

  • sanitize-html: A Node.js-based library with a similar API to DOMPurify, ideal for server-side sanitization
  • isomorphic-dompurify: Works in both browser and Node.js environments, providing consistent behavior across contexts

For simple use cases where only basic HTML escaping is needed, a custom policy may suffice. This approach provides fine-grained control but requires careful implementation to avoid security gaps. Similar to how reading and writing JSON files in Node.js requires careful handling of data formats, HTML sanitization demands attention to edge cases and encoding issues.

Custom HTML Escaping Policy
1const escapePolicy = trustedTypes.createPolicy('escape-html', {2 createHTML: (input) => String(input)3 .replace(/&/g, '&amp;')4 .replace(/</g, '&lt;')5 .replace(/>/g, '&gt;')6 .replace(/"/g, '&quot;')7 .replace(/'/g, '&#039;')8});

Implementation Best Practices

Implementing Trusted Types effectively requires thoughtful policy design and a systematic migration approach. These best practices help you build secure, maintainable policies that scale with your application.

Policy Design Principles

Effective policies follow security-conscious design patterns that minimize risk while maintaining usability:

  1. Minimal Policy Names: Use specific, descriptive policy names rather than generic ones. A policy named 'user-content' is clearer than 'policy1' and helps during security audits.

  2. Single Responsibility: Each policy should have one clear purpose. Separate policies for markdown rendering, user content display, and template rendering allow independent evolution and testing.

  3. Deny by Default: Policies should reject potentially dangerous content rather than trying to allowlist everything. This approach prevents new attack vectors from slipping through.

  4. Logging Hooks: Include logging in policies during development to track what content is being processed. This visibility helps identify potential issues early.

Policy with Logging and Strict Allowlist
1const policy = trustedTypes.createPolicy('user-content', {2 createHTML: (input, sink) => {3 console.log(`Sanitizing for ${sink}:`, input.substring(0, 100));4 return DOMPurify.sanitize(input, {5 ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],6 ALLOWED_ATTR: ['href']7 });8 }9});

The Default Policy

The Trusted Types API supports a special policy named 'default' that is automatically applied to any sink that would otherwise throw an error. This provides a safety net during migration but requires careful consideration.

The default policy is useful during gradual migration--it catches legacy code still using strings and applies sanitization. However, it should be temporary. The goal is to use explicit policies for all trusted content, making security boundaries clear and auditable.

Using the default policy long-term defeats some of the security benefits of Trusted Types, since it allows code to bypass explicit policy requirements. Plan to migrate away from it as part of your policy cleanup phase.

For comprehensive implementation guidance, refer to the MDN Trusted Types API documentation.

Default Policy Example
1// Default policy catches legacy code still using strings2trustedTypes.createPolicy('default', {3 createHTML: (input) => {4 console.warn('Using default policy - consider refactoring');5 return DOMPurify.sanitize(input);6 }7});

Migration Strategy

Migrating an existing application to Trusted Types requires a systematic approach to avoid breaking production functionality. A phased migration allows you to identify and address issues incrementally.

Phase 1: Assessment -- Identify all DOM injection sinks in the application, catalog all sources of untrusted input, and audit current sanitization practices. This inventory guides policy creation and helps prioritize high-risk areas.

Phase 2: Policy Creation -- Define policies for each distinct use case, implement sanitization within policies, and test policies with existing content to ensure compatibility.

Phase 3: Enforcement -- Enable require-trusted-types-for in report-only mode to identify violations without blocking functionality. Address violations systematically, then switch to enforced mode once clean.

Phase 4: Cleanup -- Remove the default policy, refactor code to use explicit policies, and document approved policies for future reference and team knowledge sharing.

Migration Phases

4

Migration Phases

Assessment

Start: Inventory Sinks & Inputs

Enforcement

Key: Use Report-Only First

Cleanup

Goal: Explicit Policies Only

Security Considerations

While Trusted Types provide powerful protection against DOM-based XSS, understanding potential bypass vectors helps you build more robust defenses. Awareness of these considerations ensures your implementation provides genuine security value.

Security implementation like Trusted Types directly impacts your site's search engine rankings. Search engines favor secure websites, and implementing proper security measures can improve your SEO performance while protecting your users from malicious attacks.

Script Gadget Attacks

Script gadgets are sophisticated attack patterns where seemingly benign HTML contains elements that are later interpreted as scripts through JavaScript framework behavior. A classic example involves data attributes or custom elements that frameworks transform into executable code.

Trusted Types policies should consider not just direct XSS but also indirect execution paths. This means analyzing how your JavaScript frameworks process content and ensuring policies account for potential gadget vectors. The W3C Trusted Types specification discusses these considerations in detail.

When designing policies for frameworks like React, Vue, or Angular, understand how each framework interprets different HTML constructs and ensure your sanitization accounts for framework-specific behavior.

Cross-Document Vectors

When content from different origins is mixed, CSP policies from each origin apply simultaneously. This creates potential interaction vectors where policies from one origin might unexpectedly affect content from another.

Ensure your policies account for potential cross-document interaction vectors, particularly when embedding third-party content via iframes or cross-origin APIs. Understanding these interactions helps prevent subtle security bypasses that could emerge from policy interactions.

For applications that load or integrate content from external sources, consider implementing additional validation layers beyond Trusted Types to maintain defense in depth.

Policy Bypass Prevention

Understanding common bypass methods helps you design policies that resist circumvention:

  • Never expose policy creation to untrusted code: Policy creation should happen in controlled, reviewed code paths
  • Use strict allowlists rather than denylists: Allowlists catch new attack vectors automatically; denylists require constant updates
  • Validate output of sanitization functions: Test that your sanitization behaves as expected with various inputs
  • Monitor for new sink APIs: As new web APIs emerge, evaluate whether they require policy updates

Regular security reviews of your Trusted Types implementation ensure policies remain effective as your application evolves.

Common Patterns and Examples

These practical patterns demonstrate how to apply Trusted Types to common web development scenarios. Adapt these examples to fit your specific use cases and framework context.

Pattern 1: Rich Text Editor Integration

WYSIWYG editors are common sources of DOM-based XSS because they accept HTML input from users. This pattern shows how to sanitize editor output before DOM injection.

The policy uses strict allowlists for allowed tags and attributes, preventing editors from injecting dangerous elements while preserving intended formatting capabilities.

Rich Text Editor Integration
1const editorPolicy = trustedTypes.createPolicy('rich-editor', {2 createHTML: (input) => {3 return DOMPurify.sanitize(input, {4 ALLOWED_TAGS: ['p', 'br', 'b', 'i', 'u', 'ul', 'ol', 'li', 'a'],5 ALLOWED_ATTR: ['href', 'target']6 });7 }8});9 10// When user submits editor content11const editorContent = editorPolicy.createHTML(editorInstance.getHTML());12displayArea.innerHTML = editorContent;

Pattern 2: Dynamic Template Rendering

Template systems often combine static templates with dynamic data, making them common injection points. This pattern shows how to wrap template rendering in a Trusted Types policy.

The policy accepts both template and data, allowing sanitization to occur at the point of rendering rather than requiring separate sanitization steps.

Dynamic Template Rendering
1const templatePolicy = trustedTypes.createPolicy('template-renderer', {2 createHTML: (template, data) => {3 return renderTemplate(template, data, { sanitize: true });4 }5});6 7const html = templatePolicy.createHTML(template, userData);8document.getElementById('app').innerHTML = html;

Pattern 3: Markdown Processing

Markdown is commonly used for user-generated content but requires conversion to HTML before display. This pattern shows how to integrate markdown parsing with Trusted Types.

The policy wraps the entire conversion pipeline, ensuring that any markdown input is first parsed then sanitized before becoming trusted HTML. This approach is similar to using gradients in CSS--layering techniques that build on each other for the desired result.

Markdown Processing Pipeline
1const markdownPolicy = trustedTypes.createPolicy('markdown', {2 createHTML: (markdown) => {3 const html = marked.parse(markdown);4 return DOMPurify.sanitize(html);5 }6});7 8const safeHTML = markdownPolicy.createHTML(userMarkdown);9document.body.innerHTML = safeHTML;

Troubleshooting Common Issues

Implementing Trusted Types may surface unexpected issues. Understanding common error patterns and their solutions helps you resolve problems quickly and maintain security enforcement.

"This document requires 'TrustedHTML'" Error

This error occurs when Trusted Types are enforced but a plain string is assigned to an HTML sink. Common causes and solutions include:

  1. Missing policy usage: Ensure you're creating TrustedHTML via a policy before assignment
  2. CSP accidentally enabled: Check if Trusted Types were enabled in CSP without proper policy configuration
  3. Library incompatibility: Some third-party libraries may not support Trusted Types--consider compatibility wrappers

Resolving this error typically involves either updating the code to use policies or adjusting CSP configuration to match your application's needs.

Policy Creation Failures

If the trusted-types CSP directive specifies an allowlist of approved policy names, attempting to create a policy with an unapproved name will throw an error. The solution is straightforward: update your CSP to include the new policy name before creating it.

This is actually a security feature--it prevents unauthorized policy creation. Keep your CSP configuration synchronized with your policy definitions to avoid unexpected failures.

Third-Party Library Compatibility

Some libraries may not yet support Trusted Types, causing errors when their output is assigned to protected sinks. Options for addressing this include:

  1. Compatibility wrapper: Create a wrapper that takes library output and wraps it in a TrustedHTML policy
  2. Request support: File issues or pull requests with library maintainers requesting Trusted Types support
  3. Proxy wrapper: Use a proxy to intercept library output and apply sanitization before injection

As Trusted Types adoption grows, library compatibility continues to improve. Checking library documentation and issue trackers helps identify current support status and planned updates.

The Future of Trusted Types

The Trusted Types specification continues to evolve with several anticipated developments that will enhance its utility for web developers:

Expanded Sink Coverage: As new web APIs emerge, the specification will extend trusted type requirements to additional sinks, providing broader protection against emerging XSS vectors.

Framework Integration: Major JavaScript frameworks are increasing their Trusted Types support, making it easier to use the API with React, Vue, Angular, and other popular frameworks without manual wrapper code.

Improved Tooling: Developer tooling for policy management continues to improve, including better browser DevTools integration for policy debugging and violation reporting.

Baseline Inclusion: As Firefox support improves and stabilizes, Trusted Types will likely achieve Baseline status, signaling broad cross-browser availability and encouraging wider adoption.

As web security awareness grows across the industry, Trusted Types adoption is expected to increase significantly. Learning this API now positions developers for future security requirements and helps build more secure applications today.

Common Questions About TrustedHTML

Need Help Implementing Secure HTML Handling?

Our web development team specializes in building secure applications with modern security patterns. Contact us to discuss your project's security requirements.

Sources

  1. MDN Web Docs - TrustedHTML - Core API documentation and examples
  2. MDN Web Docs - Trusted Types API - Complete framework documentation
  3. W3C Trusted Types Specification - Official specification and security considerations
  4. DOMPurify GitHub Repository - Leading XSS sanitizer library
  5. Web.dev - Prevent DOM-based XSS with Trusted Types - Practical implementation guidance