import { ... } from Object

I touched on this in my other post, but I think this idea deserves its own post. The idea is just to assist code that wishes to harden its behavior by creating a mechanism by which module code can gain trusted access to builtin functionality

import { hasOwn } from Object;

This specifically rectifies one of the more unusual and unexpected problems that arose as a result of the design of ES modules: they allow any module loaded before yours to tinker with your copy of the globals. The end result is that any parent or elder sibling module must be considered to be a de-facto trusted owner of your environment...!!!

This is clearly ridiculous, and if there was no workaround a great deal of the internet would grind to a halt. The workaround is to have a trusted module be the very very very very first thing that gets imported and that module can capture references to the original engine-supplied versions of all the most important builtin functions like hasOwn. This is currently the responsibility of CoreJS.

Because the import system cannot be tampered with by parent or peer modules (only by the actual owner of the runtime environment) it is already doing the heavy lifting of providing the safe mechanism to get access to these supercritical native functions even from modules deep inside an application. You would import it from get-intrinsic or perhaps from core-js-pure/es/object/has-own. Importing from a package isn't really enough though -- whether or not this gets you unadulterated functions will depend entirely on whether the imported capturing module code really ran first. If the person who configured the environment did not load corejs as the very very first module, then the steps you've taken to protect yourself might not be creating any protection at all. Not to mention that CoreJS is a lot of page weight if all you need is access to a known-good version of Object.hasOwn.

Having a language-approved way of doing this then resolves the problem: it allows module code at any depth to interact directly with the core low-level features of the language. As long as the environment supports this kind of import, the module knows that the functionality it's getting is coming from the environment, not from a malicious module which happened to sneak in earlier in the module load order.

Finding further discussion here: 'use initial' directive, for globalThis mutation protection

And @ljharb's work with the get-intrinsic npm package is a much more focused and lightweight way to accomplish this in the current environment than using CoreJS.

Other discussions note that basically sometimes you want this behavior, and sometimes you don't.

Polyfill code wants to add intrinsic behavior, and something like SES wants to remove access to some builtins like Math.random. These are both legitimate use cases for the ability to modify the environment, and to work consistently they would sometimes need to add or remove imports available through this mechanism. It seems to me though that the SES proposal already provides the mechanism: they call it a Compartment.

A Compartment (new Compartment()) is a sandbox with its own global object and evaluators (eval, Function, AsyncFunction, Compartment, and import).

So if you want your application to run in an environment with polyfills, you'd just run it in a compartment with those polyfills defined. Compartment already provides a means to hook imports so extending that mechanism to also support hooking these new intrinsic imports should be a relatively minor change for SES.

It seems to me that such a design is the best-case scenario: if you really control the module environment, you're free to tinker with the experience for code inside it. If you don't really control the module environment, you can't break your peers' ability to access language-level intrinsics.

I looked at Jordan's Reflect.getIntrinsic proposal and while it takes some steps in the right direction, I don't think it solves the problem.

Here's a minimal example of the problem the proposal is trying to solve:

let { hasOwn } = Object;

export function* objectKeys(object) {
  for (let key in object)
    if (hasOwn(object, key) yield key;
}

I notice that the proposal document doesn't actually spell out this simplest example, nor does it spell out what the example code would look like after the proposal. I think it would look like this:

let { getIntrinsic } = Reflect;
let hasOwn = getIntrinsic('%Object.hasOwn%');

export function* objectKeys(object) {
  for (let key in object)
    if (hasOwn(object, key) yield key;
}

Now we're just right back where we started. If each module were to have its own potentially-different implementation of getIntrinsic that would be chaos, so to stay buttoned up our code actually has to look like this:

import { getIntrinsic } from 'get-intrinsic';
let hasOwn = getIntrinsic('%Object.hasOwn%');

This leaves us two big problems:

  1. We do not and cannot know if the get-intrinsic module might have been invoked for the first time just now. A library author is left to blindly trust that whoever set up the environment imported get-intrinsic at the correct moment to capture the original pristine copy of Reflect.getIntrinsic (after polyfills are defined but before any untrusted code runs). Something as simple as a semver mismatch could cause chaos!
  2. Jordan's get-intrinsic npm package is still needed as a de-facto common definition of the intrinsics: the globally-immutable implementation of Reflect.getIntrinsic that everyone can agree upon. Thus Jordan's personal NPM credentials remain in the supply chain for my trusted access to the primitives of the language.

Any way we slice it we will still have to deal with the madness of what it means if the reflected intrinsics have differing implementations to the access-on-demand ones, but we already live in that world thanks to packages like get-intrinsic and it seems to work out OK.

The security model cuts both ways.

If code can still reach something via undeniable syntax then that also means that code can't deny it. For example, if I want to deny access to Math.random then I ensure my script is loaded first before I load any libraries and I delete or replace that API. If there was a syntactic way to still import the API then I can't deny it.

Maybe for some APIs such as freeze it is OK to be undeniable but the current approach is that all APIs are exposed as configurable properties so they can be removed or replaced before 3rd party code is evaluated.

To control the environment you need to be run first. A library itself can't decide that it must be run first, that is the responsibility of the consumer.

Your analysis doesn’t seem right to me - the entire point of the proposal is to remove the need for the get-intrinsics package.

As Ashley indicated, syntax is a non starter and an antigoal since deniability must be preserved.

The “prior code can change things” problem isn’t solved by the proposal - it’s just that you only need to hold onto one reference instead of N references, the original getIntrinsic function, which means you don’t have to optimistically save and call-bind a ton of potentially executed methods.

I understand that the proposal was intended to do that, but I'm looking at what it actually does. Without syntax how will you remove the need for the module? I'm all ears.

I just think this is ridiculous and wrong;

// a.js
import './b.js';
import './c.js';

Why should c.js be in a jail made by b.js? I would completely understand if a.js was able to put b.js and c.js into jails as it wished -- that wouldn't violate basic composability.

Well isn't that exactly my point though? The best a library can do is capture a reference at the top of its own module, and pretty much any other module could have interfered by that time.

If you want a safe, clean intrinsics in your libraries so long as you're in control of the environment you solve the security/correctness hazard by taking a combination of actions: first you import the get-intrinsic package for the first time right after you finish setting up your polyfills that replace the implementation of getIntrinsic, then in any library code you use get-intrinsic to go those original pristine functions: the ones that were given to the environment before any untrusted code could mess them up. But this only solves the problem if you control both the library and the environment, so for library authors it still is a bad deal. It means the correctness of their code is only guaranteed on some systems which happen to import a particular library at a particular time.

Because A might be using B to setup the environment.

import "harden-environment";
import lib from "some-library";

Sure it might be, but also within the same project it could be used to steal information and cause mayhem.

import leftPad from "left-pad";
import { React } from "react";

How many people are aware that when they write code like this that they're giving left-pad what amounts to root access to React?

Probably not very many, but the lack of awareness doesn’t change that it’s a critical capability/feature for the language.

With the proposal, you just make your own module that’s loaded first that does export default Reflect.getIntrinsic or similar, and the or similar, and then your code imports that when it needs to.

Right so my question is: I'm the author of a library, how would I "just make my own module that is loaded first".

It seems to me that you are suggesting that the library author is also in control of the runtime to be able to load things first...

As a library author, you’re right - you don’t have that kind of control (ever). The best you can hope for is using the builtin and hoping it’s the original one - and then applications can rewrite that to use their module as needed/desired.

If the best you can do is take what's on the global when your module loads and hope it's fine, why do you bother to build your own applications in a way that defends against tampering with intrinsics?

Are you saying that code written in libraries shouldn't ever want to have well-defined behavior? If there is not any need for get-intrinsic, are you ready to replace all uses of the get-intrinsic package with calls to a non-module-cached usages of Reflect.getIntrinsic across all the projects you maintain -- giving up your defensible security posture in trade for a "just hope for the best" attitude?

I'm saying that JS code can only ever have guaranteed well-defined behavior when no code that ran before it, changed any of its assumptions. It certainly can and should want to have it regardless of code execution order, but its desire is irrelevant - it can't have that.

Without get-intrinsic or similar, the most robust approach is to cache everything at module-level, call-bind it if receiver-sensitive, and hope that the specific methods you need haven't been tampered with before you need them.

With the concept of getIntrinsic - whether with my package, or a built-in - the first time ANY module gets ahold of it, every future module can use that getIntrinsic for all builtins. In other words, it's impossible to prevent someone from replacing getIntrinsic before that first time, but the window of time where it can happen gets much, much smaller, and an application developer can ensure that the getIntrinsic it captures and makes available is the proper one (or, it can freeze it so it can't be tampered with).

I'm saying that JS code can only ever have guaranteed well-defined behavior when no code that ran before it

So no libraries in JS can have well-defined behavior then, ever

I feel like we've crossed over into the realm of pure absurdity.

If I want to know if an object has its own property, and I need that in a library, you're telling me, "No, you can't have that, ever, and you should not want it and nobody should ever want it if they know what is good for them"

If I can't know if an object has its own property in library code AT ALL, EVER, what are we even doing here?

There are still things a library can do today that other libraries will not have access to.

For example: Module exports.

export let x = 1;
export const y = 2;
export function specialNumber() {
  return 3;
}

Another library can not replace the above exports. It could add properties to the specialNumber function as functions are mutable objects - but they can't change what happens when the function is called - which what matters the most.

Another example: private fields.

let isX;
let readX;
class X {
   #x;
   constructor(x) {  this.#x = x; }
    static {
        isX = (v) => v && #x in v;
        readX = (v) => return v.#x;
    }
}

export function makeData(v) {
     return new X(v);
}

export function readData(x) {
   return readX(x);
}

Other modules can't change the functionality of the above code.

Right, so this is something JS protects and allows right up until you need to use any builtin method. You're saying "just never use any builtins ever and you'll be fine, at that point your code will be truly (safely) composable and modular."