JavaScript sandboxing: hide global variables from a given scope

closures, isolation, javascript, scope

Solution

If it’s client-side and you can guarantee a modern browser, use web workers instead; they’re much safer, and you can also stop infinite loops from tying up the main thread, and implement timeouts by calling `Worker#terminate`.

Start up a new worker for each execution:

var worker = new Worker('path/to/evaluator.js');

Receive messages from the worker:

worker.onmessage = function (e) {
    console.log(e.data);
};

Send over the code to execute:

worker.postMessage(someCode);

In the worker, listen:

onmessage = function (e) {
    postMessage(eval(e.data));
};

And make sure to call `terminate` after receiving the message, too, because the worker can call `postMessage` itself. (You can prevent that, but there’s really no point.)

Web workers don’t have access to anything in the main execution context, they run on another thread, and they’re implemented by the browser, so they’re much safer than the typical delete-dangerous-things sandbox.

They do, however, have access to `XMLHttpRequest`; see Is It Possible to Sandbox JavaScript Running In the Browser? for other approaches if this is a problem.

Problem

I want to create an HTML+JS environment where user can enter and run arbitrary JavaScript code which will be executed in context of given jail object. I've set up a playground to illustrate what I have so far. This one does a somewhat decent job: - Basic evaluation works: - Input: `2 + 2` - Output: `4` - `this` returns jail object - Input: `this` - Output: `[object Object]` - `this.hello()` runs expected method - Input: `this.hello()` - Output: `hello world!` - User can set up their own functions and execute them later: - Input: `this.foo = function() { return 42; }` - Output: `function () { return 42; }` - Input: `this.foo()` - Ouput: `42` - Trying to access some object that I want to stay "hidden" from jail context fails: - Input: `hidden` - Output: `ReferenceError: hidden is not defined` However, it completely fails to hide globally accessible properties, such as `window` or `document` for user: - Input: `window` - Current output: `[object Window]` - Desired output: `ReferenceError: window is not defined` The best solution I've came up so far is to just fill up all the global variable I can think of with `undefined` or `null` right in the Jail object declaration, as illustrated in updated version. This way they seem to be lost forever inside the scope of jail and user won't be able to access them. My questions: - Am I right? Is this safe enough? - Is there any better way, i.e. to really undefine global stuff in certain scope instead of rewriting them with some placeholder values?

Original source

Related problems