import { ... } from Object

As above, it cuts both ways.

It is actually beneficial for security that the built-in APIs are made available from a mutable global. This allows consumers to libraries to deny access to APIs, removing them or replacing them from the global before evaluating 3rd party code within that realm.

If the language was changed so that a library could guarantee access to all global APIs this would also give more power to compromised libraries.

Also as above, it's a joint responsibility. A JS library can do as much as it can to maintain the level of robustness that it was first evaluated in. But to achieve 100% confidence the consumer of the library needs to also ensure they are validating their dependencies and sitting up correct sandboxes for untrusted code.

I think many of us would like to see more adoption of the security tooling that exists in this space and there are proposals such as "Compartments" that will make it easier to create sandboxes around modules - scoping their access to global APIs. Though, similar to proxies, these will be more advanced APIs which will only work correctly when used correctly. So applications will need to ensure they use good libraries and tooling that make composing these libraries together more ergonomic.

I might remind you that availability and integrity are core aspects of security.

The idea that any unprivileged thing can break the world and no defense is possible: that is not security. Left-pad having complete operational control over React's evaluation is not secure.

It seems to me that my proposal has exactly the same disposition towards deniability as Jordan's proposal. I see no daylight between them here. My proposal and Jordan's proposal both create a new route to access intrinsics. Both proposals make the new mechanism deniable. BOTH proposals would create a window wherein new mechanisms exist to access sensitive functions, but older SES shells won't yet be configured to deny this new method of access.

Defensive is possible. When an application is developed you can sandbox libraries from one another.

I'm talking about libraries. I'm talking about not being able to write libraries with known behavior.

If you are the author of an application you control the environment so none of this is a problem.

I understand. My point is that this is not ridiculous. The language does not offer a guaranteed way to access APIs because there are advantages to being able to deny access to them.

For freeze I personally think a strong argument can be made for allowing a syntactic, undeniable form for libraries to use. I was the champion of Records and Tuples that would have offered something similar to this.

The only point being made is that this approach wouldn't then expand to all APIs. It is intentional that apis such as Date.now, Math.random and web APIs such as fetch can be modified before the library tries to access them.

It is ridiculous! You're defending a gaping security hole on the basis that sometimes it is used for a legitimate purpose.

If that argument held water, we should get rid of all kernel mode vs user mode protections at the OS level because after all, sometimes the user wants to configure the system. Therefore allowing any code to reconfigure the system at any time is justified because SOMETIMES the usage is legitimate. Have I misrepresented your argument somehow? Feel free to add nuance...

It's not a security issue, it's a reliability issue.

The distinction you're describing already exists at the application level - first-run code is privileged, and can lock down, alter, polyfill, etc whatever it likes; later-run code is not privileged. It is unfortunate that the default has to be that if you don't opt in to freezing intrinsics, they remain mutable, but that's just the way the language works.

It's not that we're defending it, exactly, it's that it can never change, it's the reality of the language, and so any discussion that does not accept that as table stakes isn't going to be productive.

A reliability issue is (in this case) an integrity issue and an integrity issue is a security issue. A system without integrity cannot be secure. Again please, please correct me if I am wrong about this.

You're saying that for the sake of the very, very, very few people using nonstandard SES, everyone else must use the most insecure system possible, forever.

You say you can personally, forever, prevent libraries from being able to know the runtime's definition of functions like Object.hasOwn. The amount that I believe you is 0. It doesn't matter if you would like to, that's not how the process works and that's not how the world works. No individual is empowered to block all progress forever.

Famously, browsers do not consider integrity to automatically be a security issue, especially when it's about builtin modification. While I don't agree with them, your stance simply isn't a universal one.

and yes, I'm saying that it is not within our power to change that default, whether we want to or not, and thus it's not worth being upset about.

(Also yes, the way TC39's process works, any single delegate can block progress forever if it's a defensible stance, and in particular, browsers would and do block progress on anything that would break websites)

Compared to some other languages JS has high integrity. There are no pointers.

I'm really trying to choose the stupidest possible example with Object.hasOwn. It's a pretty security-critical function after all, as it is used to defend against prototype pollution attacks and is the only tool available for such defense.

If someone can tinker with your definition of hasOwn, they can make otherwise-securely-written library code blind to a prototype pollution attack that it thought it had defended against.

Are you saying that if you want to have a defensible security posture you must never use Object.hasOwn in a library?

Or create objects that don't inherit from Object.prototype which can be done robustly:

let obj = { __proto__: null };

I badly, badly wanted to be able to do that as well! But no, if you do that (null-proto objects) then you can't have arrays.

Factually, that is precisely the case. Library code can rely upon behavior that is reachable purely through syntax and pretty much nothing else. There’s lots of useful functionality reachable via the built-in Object/Proxy/Reflect/etc., but no guarantee that you’ll be able to access it unless you control the entry point. Built-ins such as Object.fromEntries, Reflect.deleteProperty, Reflect.has, Reflect.set already overlap with pure-syntax behavior, and some proposals seek to extend that set (e.g., https://github.com/tc39/proposal-call-this would make undeniable behaviors that are currently only possible through Function.prototype.apply/Function.prototype.bind/Function.prototype.call/Reflect.apply), but at no point has it been considered reasonable to make all built-ins undeniable. The best we can hope for is encouraging application authors to ensure that libraries don’t make unintended environment modifications, ideally by as-early-as-possible use of functionality like SES lockdown().

I said earlier that I was making the argument from absurdity, and I think we've now we've fully and completely arrived at the absurdity: saying that no libraries can ever safely use anything from the global namespace is absurd.

OK, so SES depends on the base system being fundamentally completely insecure to be able to set up its secure environment. That makes me think that we either need to go up to 100% SES adoption right away, or that we need to ensure that SES can set up a secure environment without needing to insist that no other code may have a secure environment.

Absurdity notwithstanding, it’s a fundamental aspect of JavaScript that objects and functions of the primordial environment can be modified by early-run code such that later-run code is unavoidably subject to the effects of such modifications. Libraries are simply not in a position of privilege, and that won’t change.

I don’t understand the final clause here. SES doesn’t prevent other code from having a secure environment (pretty much exactly the opposite, in fact), and lots of non-SES libraries already perform similar modifications to the primordial environment with the intention of affecting later-run code.

I think your time would be better spent identifying specific capabilities that you wish to be made undeniable and exploring how that might be accomplished.

Nobody is even disagreeing with me that outside of SES, there is no possibility of writing a secure or correct library in Javascript.

Again I point you to this simple example:

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

If React is a bad puppy and used any global-static or prototype methods, any compromise of left-pad is a complete compromise of React. React does not have any possibility of adopting a defensive posture to mitigate the attack.

So far nobody is trying to argue to me that this is secure. You don't have security if compromise of the tiniest most unimportant poorly-secured thing in the system leads to complete collapse of the defensibility of the whole system including the parts of it that are important and otherwise highly secure. I have very high trust that the supply chain of the actual React package won't be compromised, but in an ordinary project there might be 30 single-author micropackages that happen to run before React is imported for the first time. Now each of those is in the supply chain to compromise React!

We're all terrified of the next micropackage to get malware injected into it, but its all the more terrifying because of the asymmetry. There are so many poorly-secured targets to attack all of which have, in essence, accidentally been given fully-elevated privileges to control the system. Normally they use their fully elevated control to export a single functional helper like isNumber, but the real weak point is the fact the package, though it only needs to export one function, could completely rewrite the environment.

Consider too that in a real file of code many programmers use automatic import sorting, so left-pad and is-number both come alphabetically before react, and thus are automatically given root privileges and complete control of React's runtime.

Hi!

Nobody is even disagreeing with me that outside of SES, there is no possibility of writing a secure or correct library in Javascript.

Nobody is disagreeing with you because there is no such thing as a
"secure library". A library is a code artifact that you give someone
else to run. (This is true especially in client-side JavaScript, where
the runtime is controlled by some random guy on the internet to whose
user agent you delivered the code as text, but is essentially true in
all languages).
As a library author, you do not control the runtime environment, so
there is nothing you can (or should need to) do to secure it. This does
not even have anything to do with mutable globals or deniable intrinsics.

Again I point you to this simple example:

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

If React is a bad puppy and used any global-static or prototype methods, any compromise of left-pad is a complete compromise of React.

No. Any compromise of left-pad is a compromise of the application.
Whether or not React is also loaded in that application does not
matter the slightest. It's not "React" that gets compromised.

React does not have any possibility of adopting a defensive posture to mitigate the attack.

Why should it? It's not the job of React to ensure the application
environment integrity.

We're all terrified of the next micropackage to get malware injected into it, [and] the real weak point is the fact the package, though it only needs to export one function, could completely rewrite the environment.

I do agree with this. And there are efforts to mitigate this problem,
from the already mentioned
compartments to deno's
security model
.
And it would certainly be a good idea to have an easy way, like within
the import syntax, to confine the loaded module (and its dependencies)
into an immutable environment.

But it's not the job of the library authors, neither of left-pad nor
of React, to make use of such functionality. It's the application
developer that controls the runtime, who is responsible for the security
(and who needs to trust the code that they run with certain privileges).
All a library author can and should do is write correct code that will
work as expected under the assumption of being run in an uncompromised
environment.

There is nothing absurd about this.

kind regards,
Bergi

2 Likes