An idea about creating sandboxes.

4 views
Skip to first unread message

Kris Kowal

unread,
Jan 17, 2009, 12:26:05 AM1/17/09
to ihab...@gmail.com, chir...@googlegroups.com
Ihab,

I think we've agreed that the "require" object is a sandbox, which
closes over and contains a "load" function, which can safely pass from
parent to child sandbox without leaking capabilities since it only
renders module constructor functions, and a params object which
contains capability objects that the sandbox creator must explicitly
create or copy to grant capabilities.

To be clear, if you want to leave this matter to rest, you need not
read the rest of this email! I'm content that we've reached a good
solution.

That being said, I still think it's still worth avoiding this @params
syntax or anything like it. For that reason alone, I present the
following idea. At this point, we have a sandbox that closes over a
loader and capabilities and provides module instances. We also insist
that every sandbox also provide its loader so that sub-sandboxes can
be created. Adapting the "require" function would be unacceptable for
a sub-sandbox because it closes over capabilities that might be
communicated through cached module instances. However, capabilities
cannot leak through the loader.

So, lets consider this notation for creating a sandbox:

var sandbox = Sandbox({load: require.load, modules: {}, capabilities: {}});
var sandboxedModule = sandbox(moduleIdentifier);

Lets consider this trivialized implementation:

function Sandbox({load, modules, capabilities}) {
if (!load) load = require.load; // defaults to inheriting
current sandbox
if (!capabilities) capabilities = {};
modules = Map(modules); // assuming an any-type to any-type
Map implementation with a "has" method.
var sandbox = function (id) {
if (modules.has(id))
return modules[id];
var constructor = load(id);
var module = constructor(sandbox).freeze();
modules[id] = module;
return module;
};
sandbox.capabilities = capabilities;
sandbox.load = load;
return sandbox;
}

I contend that with this implementation, the capabilities separation
is not necessary to prevent the container from being vulnerable to the
sub-sandbox by way of capability leakage. That is, injecting
capabilities with the "capabilities" object is behaviorally equivalent
to injecting capabilities in the "modules" instance memo. There is no
chance of implicit leakage because the sub-module uses the loader
(which closes on constructors), not the importer (which closes on
module instances).

Here's our current way of grabbing the "window" capability:

@param window

And this is how we would inject it in the sandbox:

var sandbox = Sandbox({load: require.load, capabilities: {window:
aWindow}});

But consider an alternative:

import "window" as window; // or import window as a convenience
assuming that it's equivalent for a variable name identifier
special-case

And this is how we could inject the capability into the sandbox:

var sandbox = Sandbox({load: require.load, modules = {window: aWindow}});

I think that these examples are behaviorally equivalent so our
implementation could be simplified:

function Sandbox({load, modules}) {
if (!load) load = require.load; // defaults to inheriting
current sandbox
modules = Map(modules); // since it has to be a proper mapping
of any type to any type
var sandbox = function (id) {
if (modules.hasOwnProperty(id))
return modules[id];
var constructor = load(id);
var module = constructor(sandbox).freeze();
modules[id] = module;
return module;
};
sandbox.load = load;
return sandbox;
}

So, as far as i can see, the only remaining argument for creating
parallel syntax and functionality for capabilities alongside modules
is to partition their identifier domains, which would be necessary
only if the responsibility of managing capability and module
identifiers were split between separate entities. I don't consider
this probable since the creator of the sandbox is likely to be
responsible for determining the semantics of both name spaces, and
thus could manage with just one.

As usual, I'm cool with either way.

Kris

ihab...@gmail.com

unread,
Jan 19, 2009, 10:25:37 PM1/19/09
to Kris Kowal, chir...@googlegroups.com
On Fri, Jan 16, 2009 at 9:26 PM, Kris Kowal <kris....@cixar.com> wrote:
> I think we've agreed that the "require" object is a sandbox, which
> closes over and contains a "load" function, which can safely pass from
> parent to child sandbox without leaking capabilities since it only
> renders module constructor functions, and a params object which
> contains capability objects that the sandbox creator must explicitly
> create or copy to grant capabilities.

Agreed.

> We also insist
> that every sandbox also provide its loader so that sub-sandboxes can

> be created. ... capabilities cannot leak through the loader.

Agreed.

> So, lets consider this notation for creating a sandbox:
> var sandbox = Sandbox({load: require.load, modules: {}, capabilities: {}});
> var sandboxedModule = sandbox(moduleIdentifier);

So this pre-populates the sandbox with an existing (moduleId =>
moduleInstance) map. Without reference to the @params issue yet, let's
consider this in isolation. It is certainly a provocative idea.
Consider the case where the stuff within the sandbox is using 2
modules (call them Widget.js and Color.js) that expect capabilities --

(document, window)

By our design, the authors of Widget.js and Color.js may assume that
they are referring to the same 'document' and 'window' objects when
they coexist in the same sandbox. Specifically, if Widget.js wants to
set the background color of the entire document, it can make a call to
a function from Color.js like --

setBackground('#ff00ff')

without specifying the 'document' since that is commonly known.

Under your proposal, it will be possible for a system to construct a
sandbox where the Widget.js instance refers to one (document, window)
pair, while the Color.js instance refers to another (document, window)
pair. In that case, the behavior of the sandbox as a whole could be
wildly different from what the authors of the code expected.

[ Your implementation snipped but understood. ]

> ... injecting capabilities with the "capabilities" object is behaviorally equivalent


> to injecting capabilities in the "modules" instance memo.

I agree up to a point.

Define a "sandbox spec" as the specification of what capabilities a
particular kind of sandbox is expected to provide. E.g., one can say
the sandbox spec for Web pages provides 'document', obeying some W3C
specs; 'window', obeying some other specs; and 'navigator', obeying
yet more de facto specs. The sandbox spec for a database querying
application may provide 'connection', 'user' and 'pass'. Etc etc.

Injecting capabilities in the "modules" instance memo combines the
namespace of module IDs (understood by the loader) and capability
names (understood by the designers of a particular sandbox spec).

It would be nice if these namespaces were completely orthogonal. It
should be possible to use any module loader with any sandbox spec,
without worrying about trampling over people's names.

> So, as far as i can see, the only remaining argument for creating
> parallel syntax and functionality for capabilities alongside modules

> is to partition their identifier domains, ...

Agreed.

> ... which would be necessary


> only if the responsibility of managing capability and module
> identifiers were split between separate entities.

Right -- but I argue they *are* in some ways split, because most
sandbox creators do not create loaders, and most loader creators could
give a rat's rear end what sorts of capabilities their loaders are
used with.

And we *do* expose that split, by making a sandbox's loader available
to code running inside the sandbox, with which it can create
subordinate sandboxes.

> As usual, I'm cool with either way.

So I must admit I am leaning against combining the namespaces. At this
point, it's your call. If you agree with my arguments to your
satisfaction, then we can go that way. If not, then we are undecided
and maybe we should not try to resolve this prematurely but should
bring the whole discussion to the committee.

Ihab

--
Ihab A.B. Awad, Palo Alto, CA

Kris Kowal

unread,
Jan 19, 2009, 10:53:08 PM1/19/09
to ihab...@gmail.com, chir...@googlegroups.com
>
> So I must admit I am leaning against combining the namespaces. At this
> point, it's your call. If you agree with my arguments to your
> satisfaction, then we can go that way. If not, then we are undecided
> and maybe we should not try to resolve this prematurely but should
> bring the whole discussion to the committee.
>
> Ihab

Alright. Let's leave the specification alone in this regard, keeping
the capability namespace separate from the module namespace.

So, this leaves the following issues unresolved:
* we don't yet have acceptable syntactic sugar for bringing
capability objects into local scope. We do have a salty syntax (var
{...} = require.params). I think we could consider just using the
salty syntax in both salty and sugary modules. This would have the
disadvantage of leaving future ES implementations with no option to
deprecate the "require" and "exports" objects. In either case, I
think we should rename the variable to "require.capabilities",
"require.powers", or "require.environment".
* our current salty syntax requires repeating the name to bind it to
a name in local scope (let foo = exports.foo = ...). This can be
solved by using a "with" block in the desugarred syntax, or by using
it in place of the global object when executing code in a module, or a
stipulation that the "exports" object is special in future ES in that
assign members to this special object also binds them to local scope.

I think those are the only issues with the document that are plaguing
us. There are some "extension" sections that might be better rolled
into the main body, particularly the global script migration bridge,
so I'm looking forward to your remarks to the additions this weekend.

Apart from that, I look forward to your visit to hammer out the
presentation details, I'll start scraping up an archive of pro/con's
for various assumptions we converged on, and we still need slides.

Kris

Reply all
Reply to author
Forward
0 new messages