What Are Trusted Types and Why They Matter
DOM-based XSS remains one of the most common and dangerous vulnerabilities in modern web applications. The Trusted Types API provides browser-level enforcement to eliminate entire classes of injection vulnerabilities by requiring developers to explicitly mark data as trusted before passing it to dangerous DOM APIs.
Unlike traditional sanitization approaches that rely on developer discipline, Trusted Types create a structural security boundary where the browser itself enforces the rules--any attempt to bypass the policy results in an immediate runtime error.
The Core Problem
Web browsers provide numerous APIs that accept strings and interpret them as HTML, JavaScript, or URLs:
element.innerHTML = userInput; // HTML sink - dangerous!
document.write(htmlContent); // HTML sink - dangerous!
eval(codeString); // JavaScript sink - dangerous!
script.src = dynamicUrl; // Script URL sink - dangerous!
Traditional defense relies on sanitization libraries like DOMPurify, but this approach has critical weaknesses:
- Developers must remember to sanitize everywhere
- Third-party code may bypass sanitization
- New dangerous APIs can be introduced without review
- A single missed location creates a vulnerability
Trusted Types solve this by making sanitization mandatory at the browser level. This shift from developer discipline to structural enforcement is fundamental to building secure web applications with web development services that prioritize security from the ground up, and can be enhanced through AI automation workflows that intelligently validate and sanitize content at scale.
How Trusted Types Prevent XSS Attacks
The Policy-Based Security Model
Trusted Types work through a policy-based architecture where developers define policies--named factories that transform untrusted input into trusted objects:
// Create a policy that sanitizes HTML
const sanitizePolicy = trustedTypes.createPolicy('sanitize', {
createHTML: (input) => DOMPurify.sanitize(input)
});
// Later, use the policy to create trusted HTML
const trusted = sanitizePolicy.createHTML(userContent);
element.innerHTML = trusted; // ✓ This works!
element.innerHTML = userContent; // ✗ This throws an error!
The Three Trusted Types
The API provides three specialized types, each protecting different categories of injection sinks:
| Type | Purpose | Protected Sinks |
|---|---|---|
| TrustedHTML | Safe HTML content | innerHTML, outerHTML, insertAdjacentHTML, document.write |
| TrustedScript | Safe JavaScript code | eval(), new Function(), setTimeout/setInterval (string) |
| TrustedScriptURL | Safe script URLs | script.src, iframe.srcdoc, Worker constructor |
Why Type Safety Matters
Trusted type objects are opaque and non-convertible back to strings--this prevents circumvention:
const trusted = policy.createHTML('<b>Hello</b>');
trusted + '<script>evil()</script>'; // Cannot concatenate!
String(trusted); // Cannot convert!
The browser enforces that only these typed objects can reach protected sinks, creating a complete security boundary that integrates seamlessly with modern JavaScript development practices and complements comprehensive SEO services by ensuring your security implementations don't inadvertently harm search visibility.
Understanding the key elements that make up the Trusted Types security model
Policies
Named factories that define how untrusted input is transformed into trusted output. Each policy specifies createHTML, createScript, and/or createScriptURL methods.
TrustedHTML
Represents sanitized HTML that can safely be assigned to DOM properties like innerHTML without risking XSS injection.
TrustedScript
Represents approved JavaScript code that can be executed through eval() or similar sinks under strict control.
TrustedScriptURL
Represents verified script URLs for dynamic script loading, preventing malicious URL injection in script.src.
CSP Integration for Enforcement
The require-trusted-types-for Directive
The require-trusted-types-for CSP directive enables browser enforcement of Trusted Types. When enabled, the browser rejects any string assignment to protected DOM sinks:
Content-Security-Policy: require-trusted-types-for 'script';
With this header set, the following code throws a TypeError:
element.innerHTML = '<p>Hello</p>';
// Uncaught TypeError: Failed to set the 'innerHTML' property
// on 'Element': This document requires 'TrustedHTML' assignment.
The trusted-types Directive
The trusted-types directive whitelists which policies are allowed, preventing malicious code from creating its own policies:
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types sanitize dompurify default;
This configuration:
- Enables Trusted Types enforcement
- Allows only named policies: 'sanitize', 'dompurify', and 'default'
- Blocks any attempt to create unauthorized policies
The Default Policy
A special policy named 'default' can catch string assignments during migration:
trustedTypes.createPolicy('default', {
createHTML: (input) => DOMPurify.sanitize(input)
});
When the default policy exists, string assignments are automatically passed through it rather than throwing an error--useful for gradual migration while identifying all violation locations. This approach aligns with our security-first development methodology that prioritizes protecting existing codebases while systematically improving security posture through intelligent AI-powered validation.
1# Full enforcement - strict mode2Content-Security-Policy: 3 require-trusted-types-for 'script';4 trusted-types sanitize-policy user-content-policy;5 6# Report-only mode for testing7Content-Security-Policy-Report-Only:8 require-trusted-types-for 'script';9 trusted-types * 'allow-duplicates';10 11# Development mode - allows any policy with duplicates12Content-Security-Policy:13 require-trusted-types-for 'script';14 trusted-types * 'allow-duplicates';Implementing Trusted Types in React
The Challenge: dangerouslySetInnerHTML
React's dangerouslySetInnerHTML property is a common source of XSS vulnerabilities. When Trusted Types are enforced, passing a raw string to __html will fail:
// Before Trusted Types - works but is dangerous
<div dangerouslySetInnerHTML={{ __html: userContent }} />
// After Trusted Types - this throws an error!
<div dangerouslySetInnerHTML={{ __html: userContent }} />
Creating a Trusted Types Policy
Integrate DOMPurify with a Trusted Types policy:
// In your initialization code (before React mounts)
if (window.trustedTypes && trustedTypes.createPolicy && window.DOMPurify) {
const ttPolicy = trustedTypes.createPolicy('react-sanitize', {
createHTML: (input) => DOMPurify.sanitize(input, {
RETURN_TRUSTED_TYPE: true
})
});
}
Updating Components for Trusted Types
import { useMemo } from 'react';
function SafeHTMLContent({ content }) {
const trustedContent = useMemo(() => {
if (window.trustedTypes?.createPolicy) {
return window.trustedTypes.createPolicy('react-sanitize').createHTML(content);
}
// Fallback for browsers without Trusted Types
return content;
}, [content]);
return (
<div dangerouslySetInnerHTML={{ __html: trustedContent }} />
);
}
Handling Third-Party Components
For libraries that use innerHTML internally, create specific policies:
// Policy for rich text editor output
const editorPolicy = trustedTypes.createPolicy('editor-output', {
createHTML: (input) => DOMPurify.sanitize(input, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p', 'ul', 'li'],
RETURN_TRUSTED_TYPE: true
})
});
For React development projects, implementing Trusted Types requires careful coordination between frontend architecture and security requirements to ensure comprehensive protection against DOM-based attacks.
Common Violation Patterns and Solutions
Pattern 1: Direct innerHTML Assignment
Problem: The most common violation pattern.
element.innerHTML = '<div>' + userInput + '</div>'; // Violation!
Solution: Use a policy with sanitization.
const policy = trustedTypes.createPolicy('sanitize', {
createHTML: (input) => DOMPurify.sanitize(input)
});
element.innerHTML = policy.createHTML('<div>' + userInput + '</div>');
Pattern 2: Template Literals with User Input
Problem: Dynamic HTML construction with template strings.
element.innerHTML = `<div class="${userClass}">${userContent}</div>`;
Solution: Refactor to separate static and dynamic content.
const safeClass = DOMPurify.sanitize(userClass);
element.innerHTML = policy.createHTML(
`<div class="${safeClass}">${userContent}</div>`
);
Pattern 3: Dynamic Script Loading
Problem: Loading scripts from dynamic URLs.
const script = document.createElement('script');
script.src = analyticsUrl; // Violation if not trusted!
Solution: Validate against an allowlist or use TrustedScriptURL.
const policy = trustedTypes.createPolicy('script-urls', {
createScriptURL: (input) => {
const allowed = ['https://analytics.example.com'];
if (allowed.includes(input)) return input;
throw new Error('Disallowed script URL');
}
});
script.src = policy.createScriptURL(analyticsUrl);
Pattern 4: Browser Extensions
Problem: Extensions that modify page DOM directly.
Solution: Extension developers must update their code to use Trusted Types. The integration of Trusted Types often reveals hidden dependencies in third-party code, which is why comprehensive security audits are essential before deployment.
- Override CSP header locally to test:
Content-Security-Policy: require-trusted-types-for 'script' - Enable automatic breakpoints on Trusted Type violations in DevTools
- Use libraries like safevalues or DOMPurify
- Test extension workflows to identify violations
By addressing these common patterns systematically, organizations can achieve robust security across their web application infrastructure while maintaining developer productivity and user experience.
1// 1. Policy initialization (run once at app startup)2const ttPolicy = trustedTypes?.createPolicy('default-sanitizer', {3 createHTML: (input) => {4 // Sanitize with DOMPurify5 const sanitized = DOMPurify.sanitize(input, {6 RETURN_TRUSTED_TYPE: true7 });8 return sanitized;9 },10 createScriptURL: (input) => {11 // Validate against allowlist12 const allowedDomains = ['cdn.example.com', 'analytics.google.com'];13 try {14 const url = new URL(input);15 if (allowedDomains.includes(url.hostname)) {16 return input;17 }18 } catch (e) {19 // Invalid URL, return a safe default20 return '';21 }22 throw new Error('Disallowed script URL');23 }24});25 26// 2. Usage in application code27function renderUserContent(htmlContent) {28 const container = document.getElementById('user-content');29 if (ttPolicy) {30 container.innerHTML = ttPolicy.createHTML(htmlContent);31 } else {32 // Fallback for browsers without Trusted Types33 container.innerHTML = DOMPurify.sanitize(htmlContent);34 }35}36 37// 3. Dynamic script loading38function loadAnalytics(url) {39 const script = document.createElement('script');40 script.type = 'text/javascript';41 42 if (ttPolicy) {43 script.src = ttPolicy.createScriptURL(url);44 } else {45 script.src = url; // Fallback46 }47 48 document.head.appendChild(script);49}Best Practices and Recommendations
Policy Design
- Start restrictive, expand as needed: Begin with minimal policies and add more as you discover requirements
- Use descriptive names: Policy names should indicate purpose (e.g., 'cms-content', 'user-comments', 'analytics')
- Implement least-privilege: Different content sources should have different policies based on trust level
- Document policies: Each policy should have clear documentation of its purpose and allowed content
Gradual Rollout Strategy
- Start with report-only mode: Use
Content-Security-Policy-Report-Onlyto discover violations without breaking functionality - Monitor violations: Collect reports from report-only mode to identify all locations needing updates
- Fix high-priority issues first: Focus on user-generated content areas that pose the highest risk
- Enable full enforcement: After thorough testing, switch to enforcement mode
- Maintain rollback plan: Have a process to quickly disable enforcement if issues arise
Testing Strategy
- Add to CI/CD: Include Trusted Types checks in your continuous integration pipeline
- Use DevTools breakpoints: Enable automatic breakpoints on TT violations during development
- Integration tests: Create tests that verify TT-compliant code paths work correctly
- Real content testing: Test with actual user content to catch edge cases
Performance Considerations
- DOMPurify caching: Sanitize similar content once and cache the result
- Server-side sanitization: Consider sanitizing content on the server for static content
- Minimal policy lookups: Policy creation is cheap, but avoid creating policies in hot paths
- Monitor sanitization overhead: Use browser performance tools to measure impact
Implementing Trusted Types is part of a comprehensive application security strategy that protects your users and your reputation, while supporting your broader digital marketing initiatives with secure, performant web infrastructure.
Browser Support
2020
Year Chrome/Edge Added Support
83+
Minimum Chrome/Edge Version
3
Trusted Types Available (HTML, Script, ScriptURL)
Major
Browsers Supporting TT (Chrome, Edge, Opera)
Frequently Asked Questions
Sources
-
MDN Web Docs - Trusted Types API - Primary source for API definitions, injection sink interfaces, and browser compatibility.
-
Chrome for Developers - Adding Trusted Types to YouTube - Production deployment insights, extension compatibility requirements, and enforcement best practices.
-
DEV Community - Complete Guide to Trusted Types in React - React integration patterns, DOMPurify integration, and step-by-step implementation examples.
-
Content Security Policy Reference - require-trusted-types-for - CSP directive syntax, browser support matrix, and error message interpretation.
-
Google Bug Hunters - Deep Dive into JS Trusted Types Violations - Technical analysis of common Trusted Types violations patterns from Gmail and AppSheet rollouts.