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!