In which we provide a fairly trivial way to bypass "hard private" protections
on third party libraries, in practice.
Is this really "hard private"?
It seems like a lot of decisions in this proposal concerning private properties have been made specifically to support "hard private" properties. There was a long discussion here about whether this was a good idea or not, which didn't really seem to come to a consensus. TC39 is never the less proceeding with "hard private", and I see a decisions being justified using the argument "the alternative wouldn't really be hard private." This is stange to me, because the current proposal isn't hard private (at least in practice), and I suspect creating hard private properties is impossible in JavaScript.
What is "hard-private" anyways?
To quote @ljharb:
If it's accessible, it's public. "soft-private" is no different than underscores, because both are a convention (albeit, the former would be a convention baked into the language).
If a user can do it, they will, and if you break them, you broke them, even if they did something unwise.
(We can have a whole argument here between the "programming by contract" people, and the "language purist" people, about whether that second statement is correct or not - in fact we did in tc39/proposal-private-fields#33, so let's not have it again. 😉)
Obviously, nothing is really hard-private. I mean, the contents of that private variable are somewhere in memory, and I'm sure I can find some interesting clever way to get at them. I can write my own javascript engine, or write a node.js native module that peeks at memory, or I can use some esoteric attack like rowhammer to exfiltrate that data. What we really mean by "hard private" is that, in a "normal" javascript environment (not a debugger, and maybe in a browser), you can't get access to the contents of a private member without doing something extraordinarily.
The motivation for hard private seems mainly (?) to be for library authors; so let's say if a user can access private members in your library "easily", it's not hard private.
So, how "hard private" is this proposal?
PoC||GTFO
Bob writes an npm module:
export default class MyClass {
#secret = "You can't see me!";
}
Alice is using Bob's npm module, and realizes she wants access to that #secret variable. I mean, it's named with a hashtag, and hashtags are for sharing, right? (And, let's be honest, this is a pretty boring NPM module. What else is Alice going to do with it?)
const inst = new MyClass();
console.log(inst.#secret); // No worky worky. Sad tombone.
In order to get around this, Alice adds the following to her .babelrc.js:
module.exports = {
...
overrides: [{
test: ["./node_modules/bobs-package"],
plugins: ["private-class-fields-to-public"]
}],
}
babel-plugin-private-class-fields-to-public is a Babel plugin which transforms Bob's package to look like this:
export default class MyClass {
#secret = "You can't see me!";
get _secret() { return this.#secret; }
set _secret(secret) { this.#secret = secret; }
}
Now Alice can write:
const inst = new MyClass();
console.log(inst._secret);
Mischief managed!
Wait wait. Isn't this cheating? Babel isn't a "normal javascript environment!"
Between @babel/core and babel-core, babel was downloaded around 11 million times in the past week. Not too many javascript programs these days don't go through babel. You could even run babel in a browser, and transpile third party modules on the fly. babel-plugin-private-class-fields-to-public plugin was written in typescript, which for a while was a big babel competitor, but even that plugin was compiled with babel. Seems like a normal javascript environment to me.
Even if you don't buy that argument, look at this from a practical standpoint:
In Python, private members are about as soft-private as you can get. Private members are members that start with an _. To get around this, you need to type the name of the variable, and maybe feel bad for a few moments.
Java, on the other hand, is still soft-private by the "if it's accessible" definition above, but you have to do some work to access a private member. You have to go look up the refletion package and figure out how it works again (since the last time you used it was probably when you last had to get around a pesky visability problem). Then, when it doesn't work, you need to go to stack overflow to find out you forgot to call setAccessible().
With this proposal, as it stands, accessing private members in JavaScript falls somewhere between these two; you need to install a babel plugin, but after that it's basically "type _ and feel slightly guilty". Let's put it somewhere between Python and Java; maybe not as "hard private" as all that.
In which we provide a fairly trivial way to bypass "hard private" protections
on third party libraries, in practice.
Is this really "hard private"?
It seems like a lot of decisions in this proposal concerning private properties have been made specifically to support "hard private" properties. There was a long discussion here about whether this was a good idea or not, which didn't really seem to come to a consensus. TC39 is never the less proceeding with "hard private", and I see a decisions being justified using the argument "the alternative wouldn't really be hard private." This is stange to me, because the current proposal isn't hard private (at least in practice), and I suspect creating hard private properties is impossible in JavaScript.
What is "hard-private" anyways?
To quote @ljharb:
(We can have a whole argument here between the "programming by contract" people, and the "language purist" people, about whether that second statement is correct or not - in fact we did in tc39/proposal-private-fields#33, so let's not have it again. 😉)
Obviously, nothing is really hard-private. I mean, the contents of that private variable are somewhere in memory, and I'm sure I can find some interesting clever way to get at them. I can write my own javascript engine, or write a node.js native module that peeks at memory, or I can use some esoteric attack like rowhammer to exfiltrate that data. What we really mean by "hard private" is that, in a "normal" javascript environment (not a debugger, and maybe in a browser), you can't get access to the contents of a private member without doing something extraordinarily.
The motivation for hard private seems mainly (?) to be for library authors; so let's say if a user can access private members in your library "easily", it's not hard private.
So, how "hard private" is this proposal?
PoC||GTFO
Bob writes an npm module:
Alice is using Bob's npm module, and realizes she wants access to that
#secretvariable. I mean, it's named with a hashtag, and hashtags are for sharing, right? (And, let's be honest, this is a pretty boring NPM module. What else is Alice going to do with it?)In order to get around this, Alice adds the following to her .babelrc.js:
babel-plugin-private-class-fields-to-public is a Babel plugin which transforms Bob's package to look like this:
Now Alice can write:
Mischief managed!
Wait wait. Isn't this cheating? Babel isn't a "normal javascript environment!"
Between
@babel/coreandbabel-core, babel was downloaded around 11 million times in the past week. Not too many javascript programs these days don't go through babel. You could even run babel in a browser, and transpile third party modules on the fly.babel-plugin-private-class-fields-to-publicplugin was written in typescript, which for a while was a big babel competitor, but even that plugin was compiled with babel. Seems like a normal javascript environment to me.Even if you don't buy that argument, look at this from a practical standpoint:
In Python, private members are about as soft-private as you can get. Private members are members that start with an
_. To get around this, you need to type the name of the variable, and maybe feel bad for a few moments.Java, on the other hand, is still soft-private by the "if it's accessible" definition above, but you have to do some work to access a private member. You have to go look up the refletion package and figure out how it works again (since the last time you used it was probably when you last had to get around a pesky visability problem). Then, when it doesn't work, you need to go to stack overflow to find out you forgot to call
setAccessible().With this proposal, as it stands, accessing private members in JavaScript falls somewhere between these two; you need to install a babel plugin, but after that it's basically "type _ and feel slightly guilty". Let's put it somewhere between Python and Java; maybe not as "hard private" as all that.