trusted-types-eval, a safer way to allow eval in CSP
CentralCSP Team ·
Last update:
If you need eval() or the Function() constructor to keep working under a Content Security Policy (CSP), the usual answer has been to add 'unsafe-eval'. That keyword re-enables string-to-code compilation for any string, which is exactly the behavior most cross-site scripting (XSS) attacks abuse. The 'trusted-types-eval' keyword is the safer replacement: it permits eval() and Function() only when Trusted Types are enforced and only when you pass a TrustedScript object instead of a raw string.
The short version: if you are deploying Trusted Types and you still have an eval sink you cannot remove yet, use 'trusted-types-eval' in script-src instead of 'unsafe-eval'. On browsers that enforce Trusted Types, code compilation has to go through a policy first. On browsers that do not support Trusted Types, eval stays blocked rather than fully open. The rest of this post explains where the keyword lives, how it behaves, and how to wire it up.
New to this part of CSP? How to build a strong Content Security Policy covers the foundations, and Why you should never use unsafe-inline in CSP explains the related inline-script problem.
What CSP blocks by default
A Content Security Policy is an HTTP response header that tells the browser which scripts it is allowed to run. When the policy sets script-src (or falls back to default-src), the browser blocks the functions that turn strings into running code:
eval()new Function()and theFunctionconstructor- the string form of
setTimeout()andsetInterval()
That is intentional. These string-compilation sinks are a direct path from injected text to executed JavaScript, so the security header switches them off unless you opt back in.
The two ways to opt back in are very different in risk. One of them, 'unsafe-eval', re-enables string compilation for every string with no checks; see unsafe-eval and how to remove it. The other, 'trusted-types-eval', is the subject of this post.
What trusted-types-eval does
'trusted-types-eval' is a script-src source keyword. It is not a value of require-trusted-types-for and it is not a value of trusted-types. It belongs in the script-src list next to keywords like 'unsafe-eval' and 'wasm-unsafe-eval'.
Here is how the two compare:
'unsafe-eval'turnseval()andFunction()back on for any string. High risk, and it works the same whether or not Trusted Types exist.'trusted-types-eval'turns them back on only when Trusted Types are enforced for scripts, and only when you pass aTrustedScriptproduced by one of your policies, not a plain string.
The practical difference shows up on browsers that do not support Trusted Types. With 'unsafe-eval', those browsers run any string you hand to eval(), so a missing Trusted Types feature means no protection at all. With 'trusted-types-eval', those browsers have nothing that lets eval through, so the sink stays blocked. You get the eval you need where Trusted Types enforce it, and a safe default everywhere else.
One thing to keep clear: 'trusted-types-eval' on its own does not enable Trusted Types. It only governs the eval and Function sinks inside an already-enforced context. Enforcement still comes from require-trusted-types-for.
How to enforce Trusted Types for eval
Two directives turn Trusted Types on, and the full walkthrough of how to enable Trusted Types covers the rollout. The require-trusted-types-for directive takes the single value 'script' and makes the browser enforce Trusted Types on DOM injection sinks and on the string-compilation sinks (eval, Function):
Content-Security-Policy: require-trusted-types-for 'script'The trusted-types directive controls which policy names you are allowed to create. It accepts one or more policy names, plus optional keywords like 'allow-duplicates' (and 'none' to forbid all policies, or * to allow any unique name):
Content-Security-Policy: trusted-types myPolicy 'allow-duplicates'To allow eval() only when Trusted Types are enforced, combine all three in one policy. One directive per line for readability:
Content-Security-Policy:
- script-src 'self' 'unsafe-eval';
+ script-src 'self' 'trusted-types-eval';
require-trusted-types-for 'script';
trusted-types myPolicyWith that policy in place, calling eval() with a plain string throws an EvalError, even though 'trusted-types-eval' is present. You have to compile a TrustedScript through one of your registered policies first.
A real example
Register a Trusted Types policy that defines createScript, then pass its output into eval():
// Register a policy that vets the string before it becomes code.
const policy = window.trustedTypes.createPolicy('myPolicy', {
createScript: (input) => {
// Validate or transform input here. Returning it as-is is not safe;
// a policy is only as good as the checks you put in it.
return input;
},
});
// A plain string is rejected under enforcement.
eval('a = "hello"'); // throws EvalError
// A TrustedScript from the policy is accepted.
const trusted = policy.createScript('a = "hello"');
eval(trusted); // runsThe string still has to pass through createScript, which gives you one place to validate or sanitize it. That is the whole value of the approach, and also its limit: the policy can still be written incorrectly, so Trusted Types do not make the code safe on their own. They make sure every compiled string went through a checkpoint you control.
There is one shortcut worth knowing. A policy registered with the reserved name "default" runs its createScript on any plain string handed to an eval sink, which re-permits plain-string eval() across the page. Use it deliberately, because it widens the surface back out toward 'unsafe-eval' behavior.
When violations are reported
Trusted Types violations do not introduce a new report type. They arrive as standard CSP violation reports, the same payload you already receive for any blocked script, delivered to whatever reporting endpoint your policy points at. The exact effectiveDirective and sample values emitted for an eval-specific Trusted Types block are best confirmed with a live capture; the general report shape (documentURL, blockedURL, effectiveDirective, sample) is the standard CSP violation report body.
Send those reports somewhere you can read them. Add a reporting endpoint to your policy:
Content-Security-Policy:
script-src 'self' 'trusted-types-eval';
require-trusted-types-for 'script';
trusted-types myPolicy;
report-to csp-endpointReporting-Endpoints: csp-endpoint="https://<Endpoint-ID>.report.centralcsp.com"Deploying Trusted Types on a real site usually means turning enforcement on in Report-Only first, watching which sinks fire, and fixing them before you enforce. CentralCSP ingests those reports and groups them so you can see which pages still rely on eval and which policies are being created. You can check a policy for eval keywords and other risks with the free CSP evaluator before you ship it.
Should you use it at all
'trusted-types-eval' is a safer keyword than 'unsafe-eval', but the safest policy has neither. Running code from strings stays risky, and a single mistake in a policy can still let an injection through.
A reasonable order of preference:
- Remove the eval. Replace dynamic string compilation with
JSON.parse, a lookup table, or ordinary code wherever you can. - Allow specific known scripts. Use a nonce or a hash in
script-srcto allow the scripts you trust, rather than opening eval. - If you genuinely need eval for now, use
'trusted-types-eval'withrequire-trusted-types-for 'script'and a strict policy, and treat it as a temporary step while you remove the dependency.
Browser support today
The require-trusted-types-for and trusted-types directives recently became available across current Chrome, Firefox, and Safari. Chromium has shipped Trusted Types for years, and Firefox added support more recently.
The 'trusted-types-eval' keyword is newer, and it recently became available across current Chrome, Firefox, and Safari as a standard-track addition to the CSP Level 3 script-src grammar. Because the keyword degrades safely, shipping it on a browser that does not yet recognize it leaves eval blocked, which is the behavior you want.
If you are introducing Trusted Types to an existing app, start in Report-Only, route the reports to a place you can read, and tighten from there. Get started with CSP reporting walks through the reporting setup, and you can start free to see your Trusted Types and eval reports grouped on real traffic.