# How to enable Trusted Types with CSP to stop DOM XSS (/en/blog/enable-trusted-types)



Trusted Types is the part of a Content Security Policy (CSP) that closes DOM-based cross-site scripting (XSS). A CSP is an HTTP response header that tells the browser which scripts may run, and Trusted Types extends that to the DOM sinks that turn a string into live markup or code: `innerHTML`, `document.write`, `eval`, the `Function` constructor, and a few dozen others. With Trusted Types enforced, those sinks reject a plain string. The only values they accept are typed objects your own policies produced, so an injected string never reaches a sink.

The short version: set [`require-trusted-types-for`](/en/docs/web-security/policies/content-security-policy/directives/require-trusted-types-for) to `'script'` to turn enforcement on, use the [`trusted-types`](/en/docs/web-security/policies/content-security-policy/directives/trusted-types) directive to allowlist the policy names that may create those typed values, and roll it out in Report-Only first so you see every sink before you block one. The rest of this post is the how-to, including the framework patterns for Angular, React, and DOMPurify.

This builds on [trusted-types-eval, a safer way to allow eval in CSP](/en/blog/trusted-types-eval-csp), which covers the eval sink specifically. For the inline-script and string-to-code problems Trusted Types sits next to, see [why you should never use unsafe-inline in CSP](/en/blog/unsafe-inline-csp) and [unsafe-eval and how to remove it](/en/blog/unsafe-eval-csp).

## What Trusted Types protects against [#what-trusted-types-protects-against]

DOM XSS happens when client-side code passes attacker-controlled text into a sink that parses it as HTML or executes it as script. A classic case is `element.innerHTML = location.hash`. Server-side defenses never see it, because the dangerous assignment happens in the browser, after the page loads, from data the server may never have touched.

Trusted Types changes the rule at the sink. When enforcement is on, an injection sink no longer accepts a string at all. It accepts a `TrustedHTML`, `TrustedScript`, or `TrustedScriptURL` object, and the only way to make one is to run a string through a registered policy function you wrote. That gives you one auditable place where every value crossing into a sink is checked, and it makes the unsafe pattern fail loudly instead of executing.

## Turn enforcement on with require-trusted-types-for [#turn-enforcement-on-with-require-trusted-types-for]

One directive switches Trusted Types on. The `require-trusted-types-for` directive takes the single token value `'script'`, which tells the browser to enforce Trusted Types at the script-related DOM sinks:

```http
Content-Security-Policy: require-trusted-types-for 'script'
```

With that header present, assigning a plain string to `innerHTML` (or any covered sink) throws a `TypeError` and emits a violation report. Nothing else runs differently. This directive has no fallback to `default-src`, so it only applies when you set it explicitly.

## Allowlist your policies with the trusted-types directive [#allowlist-your-policies-with-the-trusted-types-directive]

Enforcement on its own would block your own code too, because your code also writes to those sinks. The `trusted-types` directive names which Trusted Types policies the page is allowed to create. A policy is a small object with `createHTML`, `createScript`, or `createScriptURL` functions that vet a string and return the typed value.

List the policy names you intend to register:

```http
Content-Security-Policy: trusted-types myPolicy
```

A few tokens are worth knowing:

* `'none'` forbids creating any policy at all, the strictest setting, useful once you have removed every direct sink write.
* `*` allows any unique policy name (less strict, convenient during rollout).
* `'allow-duplicates'` permits registering the same policy name more than once, which some bundlers and micro-frontends need.
* `default` is the reserved name for the default policy. If you register a policy called `"default"`, the browser runs its `create*` function automatically on any plain string passed to a sink, which is how you retrofit Trusted Types onto code you cannot edit. Use it deliberately, because a weak default policy widens the surface back out.

Combine both directives in one policy, one directive per line for readability:

```http
Content-Security-Policy:
  require-trusted-types-for 'script';
  trusted-types myPolicy
```

## Register a policy in your code [#register-a-policy-in-your-code]

A policy is where you put the actual sanitization. Create it once, then route every sink write through it. Here is a minimal policy that sanitizes HTML before it becomes a `TrustedHTML`:

```javascript
// Register a named policy. The name must be in the trusted-types directive.
const policy = window.trustedTypes.createPolicy("myPolicy", {
  createHTML: (input) => {
    // Do the real sanitization here. Returning input untouched is not safe;
    // the policy is only as strong as the check inside it.
    return sanitize(input);
  },
});

// A plain string is now rejected at the sink.
element.innerHTML = userInput; // throws TypeError under enforcement

// A TrustedHTML from your policy is accepted.
element.innerHTML = policy.createHTML(userInput); // runs
```

The value is the chokepoint, not the assignment. Every string that becomes markup passes through `createHTML`, so you have one function to review, test, and harden, instead of auditing every `innerHTML` in the codebase.

## Roll it out in Report-Only first [#roll-it-out-in-report-only-first]

Switching enforcement on for a live site will break anything that writes a raw string to a covered sink, which on most apps is a lot of places you do not know about yet. Do it in stages and watch reports before you enforce, the same Report-Only-first approach as any CSP change covered in [how to build a strong CSP, step by step](/en/blog/how-to-build-a-strong-csp).

```mermaid
flowchart LR
  A["Report-Only"] --> B["See violations"]
  B --> C["Add policies"]
  C --> D["Enforce"]
```

Ship the directives on the `Content-Security-Policy-Report-Only` header. The browser blocks nothing; it sends a violation report for every sink write that enforcement would have rejected.

```http
Content-Security-Policy-Report-Only:
  require-trusted-types-for 'script';
  trusted-types myPolicy;
  report-to csp-endpoint
```

Wire the endpoint with the `Reporting-Endpoints` header:

```http
Reporting-Endpoints: csp-endpoint="https://<Endpoint-ID>.report.centralcsp.com"
```

Trusted Types violations do not introduce a new report type. They arrive as standard CSP violation reports, so they land at the same endpoint and read with the same `effectiveDirective`, `sample`, and `sourceFile` fields as any other blocked script. Collect them, fix each sink (route it through a policy or remove it), and only flip to the enforcing `Content-Security-Policy` header once Report-Only is quiet.

CentralCSP ingests those Report-Only reports and groups them by page and sink, so you can see which parts of the app still write raw strings before you enforce. You can also check a draft policy for weak spots with the free [CSP evaluator](/tools/csp-evaluator) before you ship it.

## Framework patterns [#framework-patterns]

Most apps do not call sinks directly; a framework does it for them. The Trusted Types work is then mostly about how that framework gets its `create*` function.

### Angular [#angular]

[Angular](https://angular.dev/best-practices/security) has built-in Trusted Types support. Its `DomSanitizer` already runs through a Trusted Types policy, named `angular` for the framework and `angular#bundler` for the build output, so an Angular app can run under enforcement once you allowlist those policy names in the directive. List the ones your build uses:

```http
Content-Security-Policy:
  require-trusted-types-for 'script';
  trusted-types angular angular#bundler
```

Angular registers `angular` and `angular#bundler` as its base policy names. Depending on your build, it may also register `angular#unsafe-bypass`, `angular#unsafe-jit`, or `angular#unsafe-upgrade`, so watch your Report-Only reports and allowlist the ones your build actually uses. If you call sinks yourself outside Angular's APIs, register your own additional policy and add its name to the list.

### React with DOMPurify [#react-with-dompurify]

React escapes text by default, but `dangerouslySetInnerHTML` writes straight to the DOM, so it is the sink to guard. [DOMPurify](https://github.com/cure53/DOMPurify) sanitizes HTML and can return a Trusted Types value directly with the `RETURN_TRUSTED_TYPE` option, which means the sanitized output is already a `TrustedHTML` your policy produced:

```javascript
import DOMPurify from "dompurify";

// DOMPurify creates a Trusted Types policy named "dompurify" internally.
const clean = DOMPurify.sanitize(dirtyHtml, { RETURN_TRUSTED_TYPE: true });

// clean is a TrustedHTML, accepted by the sink under enforcement.
<div dangerouslySetInnerHTML={{ __html: clean }} />;
```

Allowlist the policy DOMPurify registers (`dompurify`) plus any of your own:

```http
Content-Security-Policy:
  require-trusted-types-for 'script';
  trusted-types dompurify myPolicy
```

This pattern works for any framework that exposes a raw-HTML sink: sanitize with DOMPurify in `RETURN_TRUSTED_TYPE` mode, pass the typed result to the sink, and allowlist `dompurify`.

## Browser support today [#browser-support-today]

Trusted Types is strongest in [Chromium](https://www.chromium.org/Home/), which has shipped `require-trusted-types-for` and `trusted-types` for years. [Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox) added support more recently, and the directives recently became available across current Chrome, Firefox, and Safari. Support outside Chromium was historically limited, so treat broad cross-browser enforcement as recent.

The behavior degrades safely. A browser that does not enforce Trusted Types ignores the directives and runs the page as before, so shipping them does not break older clients; you simply do not get the protection there. That makes it safe to enable now and gain the protection wherever the browser supports it.

## Frequently asked questions [#frequently-asked-questions]

### How do I enable Trusted Types? [#how-do-i-enable-trusted-types]

Add `require-trusted-types-for 'script'` to your CSP to turn enforcement on, and add a `trusted-types` directive listing the policy names your code creates. Route every DOM sink write through one of those policies, and roll it out on the `Content-Security-Policy-Report-Only` header first so you catch every sink before you block one.

### What does require-trusted-types-for do? [#what-does-require-trusted-types-for-do]

It tells the browser to enforce Trusted Types at script-related DOM sinks like `innerHTML`, `document.write`, and `eval`. Its only value is the token `'script'`. With it set, those sinks reject a plain string and accept only a typed object produced by one of your registered policies.

### What is the default Trusted Types policy? [#what-is-the-default-trusted-types-policy]

A policy registered with the reserved name `"default"`. The browser runs its `create*` function automatically on any plain string passed to a sink, which retrofits Trusted Types onto code you cannot change. It is convenient but widens the surface, so keep the default policy's checks strict.

### Can I use Trusted Types with React? [#can-i-use-trusted-types-with-react]

Yes. Guard `dangerouslySetInnerHTML` by sanitizing with DOMPurify in `RETURN_TRUSTED_TYPE` mode, which returns a `TrustedHTML` the sink accepts under enforcement, then allowlist the `dompurify` policy name in the `trusted-types` directive.

## The takeaway [#the-takeaway]

Enabling Trusted Types is two directives and one habit: `require-trusted-types-for 'script'` turns enforcement on, `trusted-types` allowlists the policies that may create typed values, and every sink write goes through a policy you can audit. Roll it out in Report-Only, fix the sinks the reports surface, then enforce. Frameworks do most of the work: Angular ships its own policies, and DOMPurify hands React a `TrustedHTML` directly.

If you want the rollout instrumented, [start free with CentralCSP](/register), point a Report-Only header at it, and watch which sinks still need a policy before you enforce.

## Sources [#sources]

* [W3C, Trusted Types specification](https://w3c.github.io/trusted-types/dist/spec/)
* [MDN, Trusted Types API](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API)
* [Angular, security and Trusted Types](https://angular.dev/best-practices/security)
* [DOMPurify, source and options](https://github.com/cure53/DOMPurify)

## Related [#related]

* [trusted-types-eval, a safer way to allow eval in CSP](/en/blog/trusted-types-eval-csp)
* [Why you should never use unsafe-inline in CSP](/en/blog/unsafe-inline-csp)
* [unsafe-eval and how to remove it](/en/blog/unsafe-eval-csp)
