A well-built Content-Security-Policy answers one question: where is this page allowed to load scripts from?
DOM XSS does not load a script. It takes a string the application already trusts, and hands it to a browser API that turns strings into code. innerHTML. document.write. eval. The script never crosses the network, so there is no origin for CSP to allow or deny.
This is why teams with a nonce-based CSP and a clean security headers report still ship DOM XSS.
What Trusted Types changes
Trusted Types inverts the problem. Instead of trying to identify dangerous strings, it stops the dangerous APIs from accepting strings at all.
With Trusted Types enforced, element.innerHTML = userInput throws. The only way to write to that sink is to pass a TrustedHTML object, and the only way to obtain one is through a policy you declared explicitly:
const policy = trustedTypes.createPolicy('sanitizer', {
createHTML: (input) => DOMPurify.sanitize(input),
});
element.innerHTML = policy.createHTML(userInput);
The security property is structural rather than behavioural. Every path to a DOM injection sink now runs through code you named, which means the audit question changes from “is every one of our four hundred assignments safe?” to “are these three policies correct?”
That is a question a human can actually answer.
The two directives
Content-Security-Policy:
require-trusted-types-for 'script';
trusted-types sanitizer default;
require-trusted-types-for 'script' turns on enforcement at the sinks.
trusted-types declares the allowlist of policy names. This one is easy to omit and worth keeping. Without it, any code on the page, including a compromised third-party script, can call createPolicy() with any name and mint trusted values. With it, createPolicy() throws on an unlisted name, so the set of code paths that can produce trusted values is fixed and reviewable.
A CSP carrying require-trusted-types-for without trusted-types has the enforcement and loses the audit trail. That is a real but partial deployment.
Browser support
Trusted Types works across current browser versions as of February 2026. Older versions ignore the directives entirely, which means deployment is safe: unsupported browsers behave exactly as they do today, and supported browsers get the protection.
There is no compatibility argument for waiting.
How to deploy without breaking the app
Enforcement is strict, and a large existing codebase will have violations. Run report-only first.
- Ship
Content-Security-Policy-Report-Onlywith the two directives and areport-toendpoint. - Collect violations. Each one is a real DOM sink assignment, and the report tells you the file and line.
- Route each through a policy, or remove the assignment.
- Add a
defaultpolicy as a temporary shim if the volume is large. It catches everything not yet migrated, which lets you enforce earlier and shrink the shim over time. - Switch the header to enforcing.
The reporting step is where the value shows up even before enforcement, because the violation list is an inventory of DOM sinks that no static analysis produces as accurately.
Pairing it with the rest of the policy
Trusted Types is one layer. The other pieces of a modern CSP:
| Directive | Stops |
|---|---|
script-src with nonce plus strict-dynamic | Injected external scripts |
object-src 'none' | Plugin-based execution |
base-uri 'self' | <base> injection hijacking relative URLs |
frame-ancestors 'none' | Clickjacking, more precisely than X-Frame-Options |
require-trusted-types-for 'script' | DOM XSS at the sink |
Allowlist-based script-src policies are discouraged now: they grow unmanageable and are routinely bypassable through the allowed origins themselves. Nonce plus strict-dynamic is the current recommendation.
Worth knowing what CSP does not cover at all: server-side vulnerabilities, and a supply-chain compromise of a script source you already trust. For the second one you need Subresource Integrity and its enforcement header.
Where it maps for compliance
- NIS2 Article 21(2)(g), basic cyber hygiene, and 21(2)(e), network and system security.
- ISO/IEC 27001:2022 A.8.28 (secure coding) and A.8.26 (application security requirements).
- OWASP Top 10 A03:2021, injection.
How to check it
curl -sI https://yourdomain.com | grep -i content-security-policy
Look for both directives. A SaaSFort scan reports three distinct states, because they are genuinely different postures: both directives present, enforcement without an allowlist, and neither.
The short version
CSP controls script sources. Trusted Types controls script sinks. They address different halves of the same attack class, and most SaaS applications in 2026 have deployed only the first half.
Report-only costs nothing and produces an inventory of every DOM sink in your application. Start there.
Ready to put this into practice?
Two ways to start — pick what fits. Free Scan if you want to see your security grade in 60s with no commitment. Free 14-day Growth trial if you're ready to monitor multiple domains, export NIS2 reports, and download Deal Reports — no credit card required.
No credit card · Cancel anytime · GDPR-ready · EU-hosted