Trusted Types and DOM XSS Bypass Hunting
Trusted Types changes DOM XSS hunting. With enforcement enabled, assigning a plain string to innerHTML, outerHTML, or script.src can throw instead of executing. That is useful protection, but it does not make every DOM data flow safe. The application still decides which policies exist and what those policies trust.
Start in DevTools. Read the Content-Security-Policy header and look for require-trusted-types-for 'script' plus a trusted-types allowlist. Report-only policy is telemetry, not enforcement. A meta tag may cover less than a response header, and older webviews may ignore the feature.
Inventory policy factories
Search loaded bundles and source maps for trustedTypes.createPolicy. A strong policy sends input through a maintained sanitizer and returns only the needed type. A dangerous policy looks like this:
trustedTypes.createPolicy("default", {
createHTML: value => value
});
That default policy silently converts arbitrary strings for protected HTML sinks. It can turn enforcement into a compatibility shim. Other warning signs include policy callbacks that remove only <script>, trust URLs based on startsWith, or skip sanitization for a “safe” flag controlled by query data.
Trace a concrete source into the policy: location.hash, postMessage, stored profile text, API JSON, or a DOM attribute. Then trace the returned TrustedHTML, TrustedScript, or TrustedScriptURL into its sink.
Look outside the protected list
Trusted Types protects dangerous injection sinks, not every path to script execution. Event handler attributes set through unexpected APIs, navigation to a javascript: URL in an unprotected context, script gadgets in third-party libraries, and framework-specific template compilers still deserve review. Browser support also matters. Test the supported browser named by the program, not a legacy browser chosen only to defeat the control.
DOM clobbering can redirect a supposedly safe data source. If code reads window.config.callback and the page permits attacker HTML, named elements may replace the expected object. Trusted Types could block the final assignment, yet a permissive policy may bless the clobbered value.
Prove a policy gadget
Use a short, non-destructive payload such as setting a unique element attribute. Capture the browser console with enforcement active. A solid proof shows:
- Untrusted input reaches the application from an attacker-controlled source.
- Direct string assignment is rejected by Trusted Types.
- The application's named policy converts that input.
- The resulting trusted object reaches an executable sink.
- Script runs in the target origin.
This sequence separates a genuine bypass from DOM XSS that appears only after you disable CSP locally.
Pay attention to policy name restrictions. If CSP allows only sanitize-html, attacker code cannot normally create anythingGoes. The 'allow-duplicates' token and policy creation order can create odd behavior, but you need a reachable way to run policy-creation code first. Do not claim that console access is an exploit.
Fix and severity
Severity follows the reachable victim and data, not the phrase “Trusted Types bypass.” Stored input viewed by administrators is sharper than self-XSS in a hash fragment. Recommend removing pass-through policies, using a proven sanitizer, narrowing allowed policy names, and fixing the source-to-sink flow even when the browser blocks it.
PortSwigger's DOM XSS material provides the source-and-sink model. Trusted Types adds one more question: who minted the trusted value, and did they actually inspect it?