Prevent DOM XSS with Trusted Types
Enable Trusted Types, route unsafe sinks through a policy that sanitizes input, and keep string assignment from injecting scripts.
Task
Add Trusted Types enforcement so assigning strings to dangerous DOM sinks cannot inject scripts.
When to use this
User data reaches innerHTML somewhere in the codebase, so a stored value executes as script even with a reasonable CSP.
Prerequisites
- A Chromium-based browser for enforcement
- A sanitizer for a default policy
Steps
1. Send the Trusted Types header so unsafe string assignments become type errors.
Expected output: Dangerous sinks reject plain strings.
2. Create a default policy that sanitizes or escapes input before returning trusted HTML.
Expected output: Only sanitized values reach sinks.
3. Replace direct innerHTML assignment with the policy output.
Expected output: The codebase no longer bypasses the policy.
4. Test with a known injection payload to confirm it is neutralized.
curl -sI https://app.example.com | grep -i content-security-policy | grep trusted-typesExpected output: Injection attempts are rendered inert.
Troubleshooting
Problem
Library uses innerHTML internally
Solution
Wrap or patch the library sink, or move to a version that supports Trusted Types.
Problem
Untrusted HTML needed
Solution
Sanitize with a reviewed library inside the default policy; do not relax the header globally.
Limitations
- Trusted Types is not enforced in all browsers, so it is defense in depth rather than a universal guarantee.
- A misconfigured default policy that returns unsanitized HTML reintroduces the vulnerability behind a false sense of safety.
Context
Add Trusted Types enforcement so assigning strings to dangerous DOM sinks cannot inject scripts. User data reaches innerHTML somewhere in the codebase, so a stored value executes as script even with a reasonable CSP.
Steps
- Send the Trusted Types header so unsafe string assignments become type errors. Expected: Dangerous sinks reject plain strings.
- Create a default policy that sanitizes or escapes input before returning trusted HTML. Expected: Only sanitized values reach sinks.
- Replace direct innerHTML assignment with the policy output. Expected: The codebase no longer bypasses the policy.
- Test with a known injection payload to confirm it is neutralized. Expected: Injection attempts are rendered inert.
// header
Content-Security-Policy: require-trusted-types-for 'script'
// policy
const policy = trustedTypes.createPolicy('default', {
createHTML: (s) => DOMPurify.sanitize(s),
});
el.innerHTML = policy.createHTML(userInput);
Verify
- Dangerous sinks reject plain strings.
- Only sanitized values reach sinks.
- The codebase no longer bypasses the policy.
- Injection attempts are rendered inert.
Limits
- Trusted Types is not enforced in all browsers, so it is defense in depth rather than a universal guarantee.
- A misconfigured default policy that returns unsanitized HTML reintroduces the vulnerability behind a false sense of safety.