New built-in: Sandbox

We all know that JavaScripts eval is unsafe, yet alluring. Why not offer a safe alternative?


Currently, it is essentially impossible to (fully) restrict the values that eval can access.

This is one solution, but it can still be escaped with ease:


//"use strict"; You can't even use strict mode with this...

function safeishEval(code) {
  var scope = Object.create(null);
  var obscurer = {};
  for (const key in this)
    obscurer[key] = undefined;
  for (const key in self)
    obscurer[key] = undefined;
  for (const key in window)
    obscurer[key] = undefined;
  with (obscurer) {
    var isolated = function(code) {
      return eval(code);
    };
  }
  return isolated.call(scope, code);
}

safeishEval("1 + 2"); // => 3
// Somehow still logs "Hello world!"???
safeishEval("console.log('Hello world!')");
// Works, but doesn't have access to globals.
safeishEval("eval('console.log(\"Hello world!\")')");
// Function.prototype.constructor is accessible...
safeishEval("console.log(Function.prototype.constructor('console.log(\"Hello world\")')())");
// ... completely invalidating the safety of this "sandbox" 
safeishEval("Function.prototype.constructor('console.log(window)')()");


I would like to propose the following API:


let mySensitiveValue = "something sensitive";
let myTypicalValue = "something typical";

new Sandbox(console, myTypicalValue)
  .eval(`console.log("Inside of the sandbox!");
    myTypicalValue += ", something typical";`
    // This should fail.
    + `try { mySensitiveValue += "... PWND!!!" }
    catch(err) {}`);

assert(mySensitiveValue === "something sensitive");
assert(myTypicalValue === "something typical, something typical");

The constructor exposes values to the Sandbox. In theory, you wouldn't even need to pass a name for each value, as Sandbox would need to be implemented in the JavaScript runtime itself, which should know the name of variables. Prototypes on exposed values should be "linked" to the prototypes used by the sandbox, as a protective measure.

The eval method evaluates code inside of the Sandbox, returning a value, just like vanilla eval.


But why should this be part of JavaScript itself, rather than a part of Browsers and/or NodeJS?

By making something like this part of the ECMAScript standard, it ensures that there is one singular sandbox that is tried and tested throughout all JavaScript runtimes.

In other words, by making this part of the ECMAScript standard, you effectively ensure that any potential security oversights are caught before the final version makes it into browsers.


I'd be foolish to not acknowledge the possible downsides of this API, so I'll go over them here:

  • If a security oversight is discovered in the sandbox after its launched, you have a zero day in the ECMAScript standard itself.
  • If a security breach is discovered in the sandbox after its launched, you have a zero day in the affected browsers and/or runtimes.
  • Impossible to polyfill.

I'd love to hear feedback regarding this.

Cheers!

Seems you don’t know what eval can truly do:

You may want to look at https://github.com/tc39/proposal-realms - this does exactly what you're looking for and more.

Oh god, I forgot about that nightmare... Sometimes JS makes my head hurt.

Thank you! This is exactly what I'm looking for! I know its gonna take a few years, but this will be amazing once it lands! (Stage 2 means its most likely going to be included, right?)

Not necessarily - lots of things change shape at stage 2. It really depends on the feature.

This one in particular is likely to become a thing in some form or another simply due to the massive need for it in many different problem domains, though. (Google's SES needs practically the full power of that proposal, not just the execution side of it.)