What is DOM-Based Cross-Site Scripting?
DOM-based cross-site scripting represents a particularly insidious category of XSS vulnerability because it occurs entirely on the client-side, exploiting the dynamic nature of modern web applications. Unlike reflected or stored XSS attacks that involve server-side processing, DOM-based XSS happens when JavaScript code modifies the Document Object Model in an unsafe manner.
Modern web applications frequently use APIs like innerHTML, document.write, and eval to manipulate the DOM dynamically. While powerful, these injection sinks can become attack vectors when untrusted user input flows into them without proper sanitization.
The Trusted Types API, enforced through CSP, fundamentally changes how browsers handle these dangerous operations by requiring typed values instead of strings for DOM injection sinks.
The Challenge with Traditional XSS Prevention
Traditional approaches to XSS prevention have focused on input validation and output encoding. However, these approaches suffer from limitations: input validation cannot catch all malicious payloads, and output encoding requires developers to consistently apply the correct method for each context. A single oversight can introduce a vulnerability.
Trusted Types provides a structural requirement--dangerous APIs can only accept typed values, not arbitrary strings, creating a fail-secure default.
For organizations building modern web applications with AI-powered features, implementing robust security measures like Trusted Types is essential. Our web development services include comprehensive security audits and implementation of industry-best practices for client-side security.
The API introduces three primary typed objects for secure DOM manipulation
TrustedHTML
Protects against DOM injection vulnerabilities by ensuring HTML content assigned to innerHTML, outerHTML, and insertAdjacentHTML has been explicitly created through a policy.
TrustedScript
Provides protection for contexts where JavaScript code needs to be dynamically created and executed, used with eval(), new Function(), and setTimeout().
TrustedScriptURL
Addresses the risk of dynamically setting script sources on script elements, ensuring URLs have been validated before loading external scripts.
Fail-Secure Default
Types can only be created through explicitly defined policies. Without a policy, the browser rejects attempts to use strings with protected APIs.
The require-trusted-types-for Directive
The require-trusted-types-for directive is a Content Security Policy header that instructs the browser to enforce Trusted Types for specific injection sinks. When this directive is present, the browser switches to a mode where certain DOM APIs will reject string assignments and require typed Trusted Type values instead.
Basic Syntax
Content-Security-Policy: require-trusted-types-for 'script';
The 'script' value specifies that the directive applies to DOM XSS injection sinks. When this header is present, attempting to use a plain string with protected APIs will immediately throw a TypeError.
Error Example
// Without Trusted Types enabled, this works:
element.innerHTML = userInput;
// With require-trusted-types-for 'script' enabled, this throws:
element.innerHTML = userInput;
// TypeError: Failed to set the 'innerHTML' property on 'Element':
// This document requires 'TrustedHTML' assignment.
The directive can also be specified via a meta tag for applications that cannot modify HTTP headers:
<meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'">
Protected Injection Sinks
When require-trusted-types-for 'script' is active, the browser enforces type checking on:
HTML Injection Sinks: Element.innerHTML, Element.outerHTML, Document.write(), insertAdjacentHTML(), DOMParser parseFromString
Script Execution Sinks: eval(), new Function(), setTimeout() with string arguments
Script URL Sinks: script element src attributes, applet and embed elements
Understanding these injection sinks is crucial for securing modern web applications. Our security experts can help identify and remediate these vulnerabilities in your codebase, and ensure your SEO foundations remain protected against malicious attacks.
1// VULNERABLE CODE - Do not use in production2 3const urlParams = new URLSearchParams(window.location.search);4const username = urlParams.get('username') || 'no user';5 6// This is vulnerable to DOM-based XSS!7const display = document.getElementById('display');8display.innerHTML = '<h1>Welcome, ' + username + '</h1>';9 10// Attack vector: ?username=<img src=x onerror="alert('XSS')">11// When the image fails to load, onerror executes the JavaScriptImplementation Strategies
Implementing Trusted Types requires a systematic approach. Several strategies exist, ranging from the safest (eliminating dangerous APIs) to practical migration paths.
Strategy One: Eliminating Dangerous APIs
The most secure approach is to eliminate use of problematic APIs entirely:
// Instead of using innerHTML:
const username = '<b>Alice</b>';
element.innerHTML = '<h1>Hello, ' + username + '</h1>';
// Use DOM manipulation methods that are inherently safe:
const h1 = document.createElement('h1');
h1.textContent = 'Hello, ' + username;
element.textContent = '';
element.appendChild(h1);
Strategy Two: Using DOMPurify
For cases where HTML must be dynamically inserted:
// With require-trusted-types-for enabled:
if (window.trustedTypes && trustedTypes.createPolicy && DOMPurify) {
const clean = DOMPurify.sanitize(userInput, {
RETURN_TRUSTED_TYPE: true
});
element.innerHTML = clean; // Works! clean is TrustedHTML
}
Strategy Three: Custom Policies
For complete control over trusted value creation:
if (window.trustedTypes && trustedTypes.createPolicy) {
const myPolicy = trustedTypes.createPolicy('my-policy', {
createHTML: (input) => {
return input.replace(/</g, '<').replace(/>/g, '>');
}
});
const trustedHTML = myPolicy.createHTML(someString);
element.innerHTML = trustedHTML;
}
Our team can help you implement these strategies as part of a comprehensive application security program. This is especially important for AI-integrated web applications that process user-generated content at scale.
Managing Policy Allowances with trusted-types
The require-trusted-types-for directive alone would prevent all use of dangerous APIs. The trusted-types directive provides control over which named policies are permitted.
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types my-policy dompurify default;
This CSP header specifies three allowed policies: "my-policy", "dompurify", and "default". Any attempt to use a policy not in this list will be blocked.
Default Policy for Migration
The default policy is a special built-in policy for gradual migration:
trustedTypes.createPolicy('default', {
createHTML: (input) => {
console.warn('Default policy used - consider proper sanitization');
return input; // No sanitization!
}
});
This is useful for initial migration but should be replaced with proper sanitization policies.
Why Policy Control Matters
Policy control prevents malicious code from creating its own policies to bypass protection. An attacker who manages to inject JavaScript cannot simply create a policy that accepts strings--they must use a policy explicitly allowed by the application's CSP.
Implementing proper Content Security Policy requires expertise. Our security consultation services can help you design and deploy comprehensive CSP configurations that protect both your website performance and user data.
Browser Support and Compatibility
Browser support for Trusted Types has been consistent in Chromium-based browsers since 2020.
Supported Browsers
- Chrome 83+ (released May 2020)
- Edge 83+ (released May 2020)
- Opera 69+
These browsers fully implement the Trusted Types API and will enforce the directive as specified.
Browsers Without Support
- Firefox (experimental support behind flags)
- Safari (no public support as of 2025)
In unsupported browsers, the directive is ignored and the application continues with legacy behavior. This means:
- The XSS protection is not active in unsupported browsers
- Code written for Trusted Types continues to work with proper feature detection
Feature Detection Pattern
if (window.trustedTypes && trustedTypes.createPolicy) {
// Use Trusted Types
const policy = trustedTypes.createPolicy('my-policy', {
createHTML: (input) => DOMPurify.sanitize(input)
});
element.innerHTML = policy.createHTML(userInput);
} else {
// Fallback for legacy browsers
element.innerHTML = DOMPurify.sanitize(userInput);
}
When planning your security implementation, consider browser reach and user demographics. Our team can help you design a progressive enhancement strategy that maximizes security for supporting browsers while maintaining functionality for all users.
Trusted Types Impact on Web Security
2020
Year Trusted Types Shipped in Chrome
3
Primary Trusted Type Objects
3
Key Implementation Strategies
Migration Strategies for Existing Applications
Migrating a large application to Trusted Types requires careful planning.
Phase One: Audit and Inventory
Before making changes, identify all uses of protected APIs:
- Searches for innerHTML, outerHTML, document.write, insertAdjacentHTML
- Uses of eval(), new Function(), setTimeout() with string arguments
- Dynamic script element creation
Phase Two: Deploy in Report-Only Mode
Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; trusted-types default; report-uri /csp-violation-report
The report-only mode collects violations without breaking functionality.
Phase Three: Implement Default Policy
Create a default policy that uses sanitization for gradual migration.
Phase Four: Progressive Refactoring
- Replace unnecessary innerHTML uses with textContent
- Eliminate eval() and new Function() where possible
- Create named policies for remaining uses
- Update CSP allowlist as each area is updated
Phase Five: Full Enforcement
Remove the default policy from the trusted-types directive once migration is complete.
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types my-policy dompurify;
Need help with your migration? Our web development team has extensive experience implementing security improvements like Trusted Types in production applications, including those built with modern AI automation features.
Best Practices and Key Recommendations
Start by Eliminating Unnecessary API Usage
The safest code is code that doesn't use injection sinks at all. Use textContent for displaying user-provided text, and reserve innerHTML for cases where HTML markup must be preserved.
Use Well-Audited Sanitization Libraries
DOMPurify has undergone extensive security review and is trusted by major organizations. Custom sanitization logic is rarely as robust as existing solutions.
Implement Proper Policy Namespacing
Use descriptive policy names like "cms-content", "user-generated-content", or "rich-text-editor" to make auditing easier.
Key Takeaways
- Trusted Types provide structural protection against DOM-based XSS
- The require-trusted-types-for directive enforces type checking on dangerous APIs
- Start by eliminating unnecessary uses of injection sinks
- Use DOMPurify or similar libraries for HTML sanitization
- Implement a migration strategy with report-only mode first
- Remember that Trusted Types complement, not replace, other security measures
Moving Forward
The investment in implementing Trusted Types pays dividends in reduced vulnerability surface area. As web applications continue to handle increasingly complex user-generated content, the structured approach to DOM security that Trusted Types provides becomes essential.
For organizations looking to strengthen their application security posture, our team offers comprehensive security assessments and implementation services. We also help ensure your security investments support your overall digital marketing goals.