tc39 / proposal-get-intrinsic

EcmaScript language proposal for a way to get intrinsics.
https://tc39.github.io/proposal-get-intrinsic/
MIT License
32 stars 4 forks source link

How does this proposal prevent tampering with `globalThis.getIntrinsic`? #7

Closed mathiasbynens closed 3 years ago

mathiasbynens commented 3 years ago

How does this proposal prevent overwriting globalThis.getIntrinsic itself? The current draft spec text doesn’t seem to address this. Should it?

mhofman commented 3 years ago

As far as I understand it doesn't and shouldn't, and any code running before you grab globalThis.getIntrinsic can replace it, which would actually be necessary for any trusted shim that wants to be complete. For example the SES shim would replace getIntrinsic to prevent access to the untamed Function constructor or eval.

theScottyJam commented 3 years ago

So then, what's the point?

You want to call Array.prototype.map(), but you realize that the map function may have been tampered with on Array.prototype. So you call globalThis.getIntrinsic to get the original map function, but that too may have been tampered with. What has been achieved?

ljharb commented 3 years ago

The point is that you only have to cache one function instead of N.

The goal is not to protect against first-run code; that’s impossible and must remain so. It’s to protect against later-run code.

theScottyJam commented 3 years ago

Ah, it clicked.

I wasn't thinking about locally caching the globalThis.getIntrinsic. So, the point of this proposal is to easily provide a way to locally store a snapshot of the current state of things. And the primary benefits of doing this is:

mathiasbynens commented 3 years ago

The point is that you only have to cache one function instead of N.

The goal is not to protect against first-run code; that’s impossible and must remain so. It’s to protect against later-run code.

I see. This sounds worth documenting in an FAQ or so :)

ljharb commented 3 years ago

The intention was that this was apparent from the narrative in the readme:

The typical approach for package authors to author their code in a way that is robust against later modification is to "cache" - to store a copy in a closed-over lexical variable - the functions they'll need in their package. Note that borrowing a prototype method in this way requires use of .call to set the receiver (the "this" value), so to be robust against that as well it will need to "call-bind" the methods:

Due to code splitting and lazy loading, often these packages will not all be evaluated at the same time, which breaks the "first-run" expectation established above. To minimize the chance of this being a problem, they will often use a common "shared" package to hold all the intrinsics.

This means that whichever shim is evaluated first will also cause get-intrinsic to evaluate first, providing safe, robust access to intrinsics for all other shims, even ones loaded after the environment has been changed.

Shims/polyfills that added new methods would of course need to also wrap/replace the getIntrinsic function so that it also provided the new methods; "lockdown" libraries like SES would similarly need to wrap/replace the getIntrinsic function so it provided whatever forms of these methods it desired.

Understanding the problem space is a bit of a precondition, and my hope was that the full readme text covered that - happy to review a PR that clarifies further.

ljharb commented 3 years ago

I'm going to close this as answered, but would love a PR that clarifies the readme - perhaps due to the curse of knowledge, it's as clear as I can personally make it already.