Share a secret across ES6 specific modules, so that other modules cannot access the secret?
Nope. This is exactly why it seems that "protected" couldn't have any way to work that has "hard" enforcement.
The only true privacy in JS is via closure (including WeakMaps), or via the upcoming "private fields" proposal, assuming it stays as "hard private". Anything shared is publicly accessible.
I'd be very interested to hear any idea that would allow modules A and B to privately share something, without module C being able to; the only thing I could think of would be some sort of private export that explicitly included a list of module specifiers that were allowed to import it - which would still not be secure whenever loader hooks exist.
If the module that holds the secrets knows how to import the modules that it wants to share the secret with, it can import a certain function from them to share the secret by calling that function and pass the secret.
To an extent, yes. You can use hoisting of function declarations and circular dependencies to create a "gateway". During circular dependencies, you have a time where function declarations are available but evaluation has not occured. During that time you can setup your private state. Then, immediately on evaluation, remove your gateway.
// file: ./secrets
// import all friendly modules (note: all dependencies of these modules
could access GATEWAY)
import './a'
GATEWAY = void 0;
// storage for shared secrets
let SECRET;
// gateway
function GATEWAY() {
if (!SECRET) SECRET = {};
return SECRET;
}
export {GATEWAY};
// file: ./a
import {GATEWAY} from './secrets';
if (typeof GATEWAY !== 'function') {
throw Error('import secrets *first*.');
}
const SECRET = GATEWAY();
With Realms (tc39/proposal-realms) you may be able to use the completion value of Module Evaluation to also achieve a way to share private state in a more sane way. VM implementations allow this in some ways as well : v8/v8/blob/a71c338d9e24e55b47125108a0fce754076751d0/include/v8.h#L1109
On Apr 24, 2017, at 12:34 PM, Bradley Meck <bradley.meck at gmail.com> wrote:
To an extent, yes. You can use hoisting of function declarations and circular dependencies to create a "gateway". During circular dependencies, you have a time where function declarations are available but evaluation has not occured. During that time you can setup your private state. Then, immediately on evaluation, remove your gateway.
// file: ./secrets // import all friendly modules (note: all dependencies of these modules could access GATEWAY) import './a' GATEWAY = void 0; // storage for shared secrets let SECRET;
Really clever!
Doesn’t this need to be a var
declaration. Otherwise, the initial test of SECRET will be in its TDZ the first time GATEWAY is called.
Ah yes, my bad. var
!
If you are interested to look at it, I’ve implemented a module loader with a secret shared across the Doodad framework (1)(2) and not available outside that framework. A Doodad module consists of only one export (an “add” function) that injects the module payload into an object. That payload has some metadata and a “create” function that receives the root namespace (public) and shared stuff (protected, like the secret).
(I’m French, so please excuse me if I don’t elaborate more and/or if my English is not accurate)
From: Jordan Harband [mailto:ljharb at gmail.com] Sent: Sunday, April 23, 2017 7:03 PM To: /#!/JoePea <joe at trusktr.io>
Cc: es-discuss <es-discuss at mozilla.org>
Subject: Re: Share a secret across ES6 specific modules, so that other modules cannot access the secret?
Nope. This is exactly why it seems that "protected" couldn't have any way to work that has "hard" enforcement.
The only true privacy in JS is via closure (including WeakMaps), or via the upcoming "private fields" proposal, assuming it stays as "hard private". Anything shared is publicly accessible.
I'd be very interested to hear any idea that would allow modules A and B to privately share something, without module C being able to; the only thing I could think of would be some sort of private export that explicitly included a list of module specifiers that were allowed to import it - which would still not be secure whenever loader hooks exist.
On Sun, Apr 23, 2017 at 1:42 PM, /#!/JoePea <joe at trusktr.io <mailto:joe at trusktr.io> > wrote:
Is there a way to share some secret value across a few modules, and prevent other modules? f.e. prevent modules of an app dev who is importing modules of a library where the library wants to share private stuff across its modules. Is this possible to implement somehow?
WeakMaps can be encapsulated inside a module to implement "private" properties for a class defined inside that module and then exported. But "protected" can't be implemented with a WeakMap shared with modules because then end-user app code can import the WeakMap and read all the stuff. Is there some way to share a WeakMap private with classes defined across modules?
/#!/JoePea
GATEWAY
That's an interesting idea, Bradley, thanks! I originally wanted this for implementing "protected" class members (i.e. sharing a WeakMap across subclasses in different files), but I've since done this another way that is even better: trusktr/lowclass.
With lowclass we can also make what I'm calling "module protected" access (similar to "friends" in C or "package protected" in Java) by simply leaking access helpers to outside scope inside a module. Here's the example in the README trusktr/lowclass#friends-like-in-c-or-package-protected-like-in-java .
But now that I've revisited this thread, I realize that with a GATEWAY it is possible to share a "leaked" access helper across files too, thus making something that is more like Java's "package protected"! Cool!
note: all dependencies of these modules could access GATEWAY
This can be prevented by having the GATEWAY
function count how many times
it is called. If we hard code the number of modules that will call GATEWAY
in a project, we can throw an error if the number of accesses is higher
than expected and blatantly breaking an app to force people not to do it.
Automated tests can ensure that the number is hard coded properly. It could
also be possible to add a build step to automatically detect and add the
hard-coded number.
With Realms
That's also interesting, but I think it would be too heavy-weight to create
new JS contexts just for sharing some private references. The GATEWAY idea
I think is much slimmer. Plus sharing across Realms can introduce all sorts
of other problems like instanceof
checks failing due to one global not
being the same reference to the equivalent global in the other realm
(console.assert(window.Object !== realm.Object)
). Here's is the same
problem with Jest facebook/jest#5946 (with
Node's vm
rather than Realms, but essentially its the same issue).
It'd be better if there were some sort of spec added to Modules that allowed some set of modules to import private exports from each other with simple syntax, or similar. Maybe it would be based on folder structure, or maybe module identifiers referenced in each other so that there's no physical circular dependency, only harmless circular mentioning of each other at worst, so that runtime circular dependency issues esdiscuss.org/topic/how-to-solve-this-basic-es6-module-circular-dependency-problem
don't become a problem.
If you are interested to look at it, I’ve implemented a module loader with a secret shared across the Doodad framework
Thanks for sharing that Claude. It's interesting to see how others are implementing public/protected/private for their classes. :)
The box function defined in github.com/mikesamuel/tc39-module-keys enables things like this.
Ah, interesting! That would be useful!
I think it'd be nice if there was something also easier, based only on file names or directory structure.
// log-foo.js
import Foo from './Foo'
console.log(Foo)
// Foo.js
class Foo {}
export Foo for './log-foo'
// or with expressions, all files in this folder
export Foo for './*'
I know, someone can stick a file in there then, but at least it gives a "don't mess with this unless you know what you're doing" message like many other things in JavaScript (f.e. redefining a configurable property even if writable is false).
We'll, I suppose even with public keys, a use can just replace your module in the file system with their own, so the same "be careful" principal still applies there.
Of course, network-modules won't have the filesystem-module problem, so probably both filename approach and box approach would be secure with network modules (just needs infrastructure for passing that info around).
Related relevant proposal/strawman/whatever:
And in particular in the first linked issue, I did propose an export { ... } to "./foo.js"
here:
Isiah Meadows me at isiahmeadows.com
Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com
Is there a way to share some secret value across a few modules, and prevent other modules? f.e. prevent modules of an app dev who is importing modules of a library where the library wants to share private stuff across its modules. Is this possible to implement somehow?
WeakMaps can be encapsulated inside a module to implement "private" properties for a class defined inside that module and then exported. But "protected" can't be implemented with a WeakMap shared with modules because then end-user app code can import the WeakMap and read all the stuff. Is there some way to share a WeakMap private with classes defined across modules?