Question

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?
Was it helpful?

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top