Closed mikesamuel closed 3 months ago
These are interesting questions! On the one hand it would be nice to have a way to allow "trusted" eval via trusted types as it would simplify the rollout of a CSP that blocks eval (currently we have an all or nothing approach). On the other hand it seems to make things more complicated if you'd suddenly start allowing the use of eval() although it's explicitly blocked by CSP.
I guess it would be less of a problem if trusted-types would be capable of guarding eval independently of CSP.
Yeah. I think the core ambiguity is, with TT there's no difference between
unsafe-eval
means eval(x)
allowed for all xunsafe-eval
means eval(x)
allowed for no xIt seems clear to me that the presence of TT eval guards unambiguously means
eval(x)
allowed for x in TrustedScript.Adding Trusted-Types to the Content-Security-Policy header with a default policy that blesses no script values and with no explicit policy.createScript(...)
calls should not make eval
more permissive.
A Content-Security-Policy header with Trusted-Types and without unsafe-eval should allow eval(policy.createScript(x))
.
If those two litmus tests make sense, that leads to the following flow for %eval%(x)
before proceeding with the permissions check.
Then we'd have to define HostEnsureCanCompileStrings(calleeRealm, x) to do the following:
That leads to the following interpretation of unsafe-eval:
The absence of unsafe-eval means eval(x)
is denied when the post-processed value of x is a string.
To dumb this down a bit: are you saying that a document with a CSP which bans eval()
and enforces the use of TrustedScript for script-like sinks would ban eval(String)
but allow eval(TrustedScript)
?
@arturjanc, Sorry for the overlong writeup.
are you saying that a document with a CSP which bans
eval()
and enforces the use of TrustedScript for script-like sinks would baneval(string)
but alloweval(TrustedScript)
?
Yes, but with one wrinkle.
When eval(string)
is banned, but there's a default TT policy, the default policy is first given the chance to coerce string
to TrustedScript
.
This would allow:
//// Header content: Trusted-Types "default"
//// Sensitive setup code
TrustedTypes.createPolicy('default', {
createScript(x) {
if (x === 'return this;') { return x; }
throw new Error(x);
}
});
//// Non-sensitive legacy application code
const globalObject = new Function('return this;')();
One major problem is that this won't work with the polyfill - in browsers that don't support TT but do support CSP (90% of current browsers), the eval can't be run, and no amount of polyfilling will fix that.
Therefore, devs won't be able to use eval with TT unless they start sending 'unsafe-inline' - which depending on how far back browsers they need to support, could mean they send that flag for the next ten years, their systems less safe instead of more.
@Sora2455 It's true that direct eval
can't be polyfilled, but indirect eval
can be as can direct uses of new Function
:
function functionPolyfill(...args) {
// Load code via DOM manipulation with <script>
}
functionPolyfill.prototype = Function.prototype;
Function = functionPolyfill;
// Do the same thing with Function.prototype.constructor that the realms polyfill does.
Previously discussed at https://github.com/WICG/trusted-types/issues/120
@lweichselbaum
I guess it would be less of a problem if trusted-types would be capable of guarding eval independently of CSP.
I'm not sure I understand this. TT have that capability. The only interaction between TT and CSP for now is the same header used for the delivery of the enforcement policies.
Edit: clarified my mistake
Initially, I think we should respect the existing restrictions of unsafe-eval
(i.e. no eval and friends would be allowed, even with TrustedScript
in the ~presence~ absence of unsafe-eval
~alone~) for backwards compatibility. In that model, apps would have to decide:
Content-Security-Policy: script-src 'unsafe-eval'; trusted-types myPolicy
which only allows TrustedScript
in TT-supporting browsers, and strings in legacy ones. The JS code still passes through a policy modulo non-polyfillable cases that would cause functional breakage the application in non-supporting browsers, so this should not be cause security regressions in general.
Alternatively, we may provide a separate script-src
keyword like eval-allow-trustedscript
that makes it more explicit. This keyword is technically redundant, but makes it clear what's happening, even if one looks only at the script-src
directive.
or
Content-Security-Policy: script-src https://no-unsafe-eval; trusted-types myPolicy
which would prohibit eval in all CSP-supporting browsers.@koto
I think we should respect the existing restrictions of unsafe-eval (i.e. no eval and friends would be allowed, even with TrustedScript in the presence of unsafe-eval alone) for backwards compatibility.
I don't think that's required for backwards compatibility since pre-TT programs do not create TrustedScript values.
That was the idea behind:
Litmus test: backwards compatibility
Adding Trusted-Types to the Content-Security-Policy header with a default policy that blesses no script values and with no explicit
policy.createScript(...)
calls should not make eval more permissive.
What I meant was that we should not relax the CSP restrictions. The issue is you can create policies (and types through them) even without a TT header, so that would allow the authors to call eval
where they previously could not.
@koto, It sounds like you're agreeing with:
- Absence of unsafe-eval means eval(x) allowed for no x
What about if I want to write a CSP policy so that:
'return this'
and safeAPI(
wellFormedJSON)
.Given that the polyfill will not support direct eval, it sounds like this case requires serving different headers to different browsers depending on their level of TT support so that unsafe-eval
is present only when trusted-types
is used.
The issue is you can create policies (and types through them) even without a TT header
I thought in the absence of any grants, createPolicy is closed. I can see how leaving it open would be better for library code -- in the common case, they can create values, so code paths that expect TT APIs becomes the most field-tested.
I wonder whether there's a happy middle-ground:
That's not pithy and it means that code paths that use eval with TrustedScript are not the most field tested.
- the absence of unsafe-eval means eval(x) fails for all x unless trusted-types is specified and Type(x) is TrustedScript.
If we can get it into CSP spec - sure, but I'm not sure CSP editors would like this extra complexity (given that this behavior is defined in a context of two separate directives).
Actually, @arturjanc reminds that 'unsafe-eval' only kicks in for strings (at least in Chrome):
https://jsbin.com/qehejuvido/edit?html,output
which would elegantly allow us to enable eval(TrustedScript) without needing unsafe-eval
.
However, Chrome's behavior does not conform to spec: https://tc39.github.io/ecma262/#sec-eval-x and indeed, Firefox blocks that eval :/
Heh, I just found https://github.com/tc39/ecma262/issues/1495 which describes this neatly :)
I should also note that the new Function("return this")
use case is covered by globalThis
, and the eval(JSON)
is well and truly covered by JSON.parse
. Do modern script authors actually need eval
for anything?
eval
is still used quite often actually. Some frameworks use it to back a expression parser in their template systems e.g. VueJS. Allowing eval selectively (e.g. only in trusted components of those frameworks) with TT would unblock CSP adoption for those frameworks (FWIW, VueJS is not the best example, as there is a way to avoid eval
usage there).
@koto But those frameworks currently can't use CSP and eval together - and won't until TT is ubiquitous. That means that they have no find 'no-eval' solutions for the CSP-supporting non-TT supporting browsers, and once that's done there's no point re-enabling eval.
Not quite. Developing a non-eval based solution is sometimes quite complex, and the only real gain is being "CSP with no unsafe-eval" compliance. What we're seeing is that this gain is not big enough to trigger the change.
Adding TT-support is, ot the other hand, trivial, and backwards-compatible - adopting that gives one a TT compliance and "CSP with no-unsafe-eval in TT-supporting browsers" compliance, with is already a lot.
The application could then ship CSP script-src 'unsafe-eval'; trusted-types framework-eval-policy
- this would be secure in TT-supporting browser, and secure modulo eval-that-was-never-called-in-a-TT-browser in other browsers.
Actually, @arturjanc reminds that 'unsafe-eval' only kicks in for strings (at least in Chrome): ... However, Chrome's behavior does not conform to spec: https://tc39.github.io/ecma262/#sec-eval-x and indeed, Firefox blocks that eval :/
Heh, I just found tc39/ecma262#1495 which describes this neatly :)
Yeah, the consensus is that even though Firefox is correct, that difference is due to a spec bug. I am prepping https://mikesamuel.github.io/eval-in-order/ as a needs_consensus fix which would require Firefox to become consistent with Chrome in this regard.
allenwb commented 9 days ago
This is a specification bug introduced post ES6.
In the ES6 spec there are no observable actions that happen before the the type check on the argument. This behavior dates all the way back to the ES1 spec: ... It seems like the current spec. has an (unintentional??) breaking change.
@Sora2455 said
I should also note that the
new Function("return this")
use case is covered byglobalThis
, and the eval(JSON) is well and truly covered by JSON.parse. Do modern script authors actually need eval for anything?@koto But those frameworks currently can't use CSP and eval together - and won't until TT is ubiquitous. That means that they have no find 'no-eval' solutions for the CSP-supporting non-TT supporting browsers, and once that's done there's no point re-enabling eval.
Yes, there are better alternatives to these patterns but they appear deep in widely used libraries.
I worry that upgrading dependencies to newer versions of these libraries would require the kind of multi-level coordination that might prevent any one party from adopting TT+CSP in large systems.
@koto
- the absence of unsafe-eval means eval(x) fails for all x unless trusted-types is specified and Type(x) is TrustedScript.
If we can get it into CSP spec - sure, but I'm not sure CSP editors would like this extra complexity (given that this behavior is defined in a context of two separate directives).
I'd like to explore this as an option, and it's probably better to start sooner than later. Do we know whom to talk to? Would that conversation happen on public-webapps or another forum?
That would be https://github.com/w3c/webappsec-csp/. @mikewest is the editor.
Ok. I think he's on vacation. I'll try to wrangle 10 minutes FTF.
I've reopened this and https://github.com/w3c/trusted-types/issues/221 to continue this discussion around 'unsafe-eval', trusted types and the potential for a new script-src value.
@lweichselbaum @koto
Per https://twitter.com/we1x/status/1113340867409076224 since TT with
eval
/Function
guards provides similar protection to CSP without unsafe eval, maybe it's worth clarifying how an application might provide degraded service when TT is not available but take advantage of TT where it is.Should a CSP policy without
unsafe-eval
preventeval(myTrustedScriptValue)
?I'm leaning no since
unsafe-eval
in a CSP header can't be contingent on trusted-types being supported.If
unsafe-eval
is present, does trusted-types guardeval(x)
/Function(x)
/import(...)
at all?Similarly for
wasm-unsafe-eval
?