DevUA

Alex Rudenko is a software developer who enjoys building applications using NodeJS and JavaScript. He likes to explore new ways of building applications and is looking forward to new JavaScript features. Interested in Rust. Author at https://60devs.com, co-creator of https://supporterhq.com and curator of http://open-source-in-action.net/

Meta


Executing JavaScript Code in a Sandbox Using Node’s VM Module

Alex RudenkoAlex Rudenko

The API of Node exposes a module called VM that allows for a more safe execution of arbitrary JS code. The module operates in terms of scripts, sandboxes and contexts.

How to create a script?

const vm = require('vm');
const script = new vm.Script('throw new Error("Problem");', {
filename: 'my-index.js', // filename for stack traces
lineOffset: 1, // line number offset to be used for stack traces
columnOffset: 1, // column number offset to be used for stack traces
displayErrors: true,
timeout: 1000 // ms
});

How to run a script?

There are severalImmediately-Invoked Function Expression options how to run a script and they depend on which context the running script should have:

Also it’s possible to omit creation of the script object and use the same methods but on the vm module to create and run scripts in a single step:

const vm = require('vm');
vm.runInThisContext(code, opts);
vm.runInNewContext(code, sandbox, opts);
vm.runInContext(code, context, opts);

Performance

The code in the sandbox can run slower than normally if you create a new context and use global lookups on that context. For example, the following test code will produce the following results (node v5.4.0, default params):

'use strict';
var code = `
// global script scope
function nop() {
};
var i = 1000000; // global script scope
while (i--) {
nop(); // access global scope
}
`;

console.time('eval');
eval(code);
console.timeEnd('eval');

var vm = require('vm');
var context = vm.createContext();
var script = new vm.Script(code);

console.time('vm');
script.runInContext(context);
console.timeEnd('vm');

Results:

eval: 11.191ms
vm: 648.014ms

To avoid this, wrap scripts that you run using runInContext or runInNewContext in a immediately-invoked function expression (IIFE).

Safety

Using vm’s module is more safe than relying on eval because scripts run by the vm module do not have access to the outer scope (or do have access only to the global scope as with runInThisContext). Still the scripts run in the same process so for the best security it’s advised to run unsafe scripts in a separate process.

Also by default the scripts do not have access to things like require and console. If you want to allow them, you have to provide it explicitly:

const vm = require('vm');
vm.runInNewContext(`
var util = require('util');
console.log(util.isBoolean(true));
`, {
require: require,
console: console
});

Thanks for reading.

Alex Rudenko is a software developer who enjoys building applications using NodeJS and JavaScript. He likes to explore new ways of building applications and is looking forward to new JavaScript features. Interested in Rust. Author at https://60devs.com, co-creator of https://supporterhq.com and curator of http://open-source-in-action.net/