Trusted Types & HTML Sanitization
Prevent DOM-based XSS by enforcing Trusted Types policies for innerHTML and dangerous DOM sinks, and use the HTML Sanitizer API for safe HTML insertion.
Overview
Key Points
- •Enable via CSP: `require-trusted-types-for "script"` header enforces TrustedHTML on innerHTML and similar
- •`trustedTypes.createPolicy("name", { createHTML: (str) => sanitize(str) })` registers a policy
- •`policy.createHTML(str)` returns a `TrustedHTML` object safe to assign to `innerHTML`
- •`new Sanitizer()` HTML Sanitizer API, strips scripts/event handlers while keeping safe formatting
- •`element.setHTML(untrustedString, { sanitizer })` safely insert HTML using Sanitizer
- •`el.setHTMLUnsafe(str)` inserts HTML including declarative shadow DOM (still sanitized by browser)
- •Trusted Types prevents typosquatting attacks on your own code and third-party libraries
Examples
Trusted Types policy for safe HTML insertion
<!-- Enable Trusted Types via CSP header (set on server):
Content-Security-Policy: require-trusted-types-for 'script'
-->
<div id="output"></div>
<script>
// Register a Trusted Types policy
const policy = trustedTypes.createPolicy('sanitize', {
createHTML: (input) => {
// Only allow specific safe HTML
return input
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '') // strip scripts
.replace(/\son\w+\s*=/gi, '') // strip event handlers
.replace(/javascript:/gi, ''); // strip javascript: URLs
}
});
// Get user-controlled content
const userContent = '<h2>Title</h2><script>alert("XSS")</script><p>Safe text</p>';
// ❌ This would throw TypeError with Trusted Types enforced:
// document.getElementById('output').innerHTML = userContent;
// ✅ Must go through policy.createHTML()
const trusted = policy.createHTML(userContent);
document.getElementById('output').innerHTML = trusted;
// Result: <h2>Title</h2><p>Safe text</p> (script stripped)
</script>In production, use a hardened sanitizer like DOMPurify instead of a naive regex replacement.
The HTML Sanitizer API (browser-native)
<!-- HTML Sanitizer API available in Chrome/Edge -->
<div id="safe-output"></div>
<script>
const userHtml = `
<h2>Article Title</h2>
<p onclick="alert('xss')">Click me</p>
<p style="color:red"><em>Styled</em> paragraph</p>
<script>alert("xss")<\/script>
<a href="javascript:void(0)" onclick="steal()">Click</a>
<a href="https://safe.example.com">Safe link</a>
`;
// Default sanitizer: removes scripts and dangerous attributes
const sanitizer = new Sanitizer();
const output = document.getElementById('safe-output');
output.setHTML(userHtml, { sanitizer });
// Result: headings, paragraphs (without onclick), styled em, safe anchor
// Scripts, javascript: hrefs, and onclick are stripped
// Custom sanitizer: allow only specific elements and attributes
const strictSanitizer = new Sanitizer({
allowElements: ['p', 'strong', 'em', 'a'],
allowAttributes: { 'a': ['href'] },
blockElements: ['script', 'style'],
});
output.setHTML(userHtml, { sanitizer: strictSanitizer });
</script>setHTML() with Sanitizer is the modern alternative to elem.innerHTML = DOMPurify.sanitize(str).
Default Trusted Types policy for gradual migration
<script>
// During migration: log all unsafe innerHTML uses instead of throwing
// This helps identify all sinks in existing code
if (trustedTypes.defaultPolicy === null) {
trustedTypes.createPolicy('default', {
createHTML: (str) => {
// Log the call for audit
console.warn('Unsafe innerHTML at:', new Error().stack);
// In migration phase: allow but log
// In production: throw or sanitize
return str; // or DOMPurify.sanitize(str)
},
createScript: (str) => str,
createScriptURL: (str) => str,
});
}
</script>A "default" policy is used as fallback when no other policy wraps the value useful for auditing legacy code.
Common Mistakes
- Writing a Trusted Types policy that just returns the input unchanged this defeats the entire purpose; always sanitize inside the policy
- Using regex to sanitize HTML HTML is too complex for regex; use DOMPurify, the Sanitizer API, or a whitelist-based parser
- Forgetting that Trusted Types only protects DOM sinks it does not prevent server-side XSS or reflected XSS in HTML attributes
Note
DOMPurify is the gold-standard HTML sanitizer compatible with Trusted Types: `const clean = DOMPurify.sanitize(dirty, { RETURN_TRUSTED_TYPE: true })`. It returns a TrustedHTML value directly, integrating seamlessly with a Trusted Types policy.
Exercises
Write HTML to solve each exercise. Use Preview to see it render in a live iframe, then mark as done.
Completed: 0 / 5 exercises
Knowledge Check
Question 1 of 4