Sometimes an application needs to run a piece of JavaScript that comes from somewhere untrusted — a user-submitted formula, a plugin script, code stored in a database. Running it with a plain eval() would let it touch anything your main program can touch, which is a serious risk. The vm module exists to give that code its own, restricted execution context instead.
Running code in a completely fresh, isolated context
const vm = require('vm');
const untrustedCode = 'result = 6 * 7';
const sandbox = { result: null };
vm.createContext(sandbox); // turns `sandbox` into a usable execution context
vm.runInContext(untrustedCode, sandbox);
console.log(sandbox.result); // 42
The code only ever sees the properties you explicitly put in sandbox — it has no access to your application's other variables, your file system, or your network, unless you deliberately expose them.
Reusing a compiled script across multiple runs
If the same code needs to run repeatedly (with different input data each time), compiling it once with vm.Script avoids re-parsing it every single time:
const vm = require('vm');
const script = new vm.Script('total = price * quantity');
function calculate(price, quantity) {
const context = { price, quantity, total: null };
vm.createContext(context);
script.runInContext(context);
return context.total;
}
console.log(calculate(50, 3)); // 150
console.log(calculate(20, 10)); // 200
A realistic example: a small formula-evaluation feature
function evaluateFormula(formula, variables) {
const context = { ...variables, result: undefined };
vm.createContext(context);
try {
vm.runInContext(`result = ${formula}`, context, { timeout: 1000 });
return context.result;
} catch (error) {
throw new Error('Invalid formula: ' + error.message);
}
}
console.log(evaluateFormula('basePrice * (1 + taxRate)', { basePrice: 100, taxRate: 0.1 }));
// 110
The timeout option is worth calling out — it stops runaway code (like an infinite loop a user might accidentally, or deliberately, submit) from hanging the whole process indefinitely.
An important honesty check: the vm module is not a full security boundary
This matters enough to state plainly: vm restricts what variables and objects the code can directly reach, but it is not a complete, bulletproof security sandbox on its own — cleverly crafted malicious code has historically found ways to escape a vm context and reach the surrounding Node.js process. For genuinely untrusted code (arbitrary user-submitted scripts, for instance), the safer real-world approach is running it in a fully separate, isolated process or container, not relying on vm alone as the only protection.
When it's the right tool
vm is well-suited for semi-trusted scenarios — evaluating a formula, running a plugin from a source you mostly trust, or building a simple scripting feature inside your own application — where you want isolation from your main program's variables, but aren't defending against a genuinely hostile, security-conscious attacker.