The vm module provides APIs for compiling and running code within V8 virtual machine contexts. Here's how to use it.
Basic Script Execution#
1import vm from 'node:vm';
2
3// Run code in current context
4const code = 'x + y';
5const result = vm.runInThisContext(code);
6// Error: x is not defined (no access to local variables)
7
8// Run with context
9const context = { x: 10, y: 20 };
10vm.createContext(context);
11const result = vm.runInContext('x + y', context);
12console.log(result); // 30
13
14// Run in new context
15const result = vm.runInNewContext('x + y', { x: 10, y: 20 });
16console.log(result); // 30Creating Contexts#
1import vm from 'node:vm';
2
3// Create a reusable context
4const sandbox = {
5 console: console,
6 Math: Math,
7 result: null,
8};
9
10vm.createContext(sandbox);
11
12// Run multiple scripts in same context
13vm.runInContext('result = Math.sqrt(16)', sandbox);
14console.log(sandbox.result); // 4
15
16vm.runInContext('result = result * 2', sandbox);
17console.log(sandbox.result); // 8
18
19// Check if object is a context
20console.log(vm.isContext(sandbox)); // true
21console.log(vm.isContext({})); // falseScript Compilation#
1import vm from 'node:vm';
2
3// Compile script for reuse
4const script = new vm.Script('x + y');
5
6// Run in different contexts
7const context1 = vm.createContext({ x: 1, y: 2 });
8const context2 = vm.createContext({ x: 10, y: 20 });
9
10console.log(script.runInContext(context1)); // 3
11console.log(script.runInContext(context2)); // 30
12
13// Script with options
14const timedScript = new vm.Script('while(true) {}', {
15 filename: 'infinite-loop.js',
16 lineOffset: 0,
17 columnOffset: 0,
18});
19
20// Run with timeout
21try {
22 timedScript.runInNewContext({}, { timeout: 100 });
23} catch (err) {
24 console.log('Script timed out');
25}Execution Options#
1import vm from 'node:vm';
2
3const code = `
4 function calculate() {
5 return a * b;
6 }
7 calculate();
8`;
9
10const options = {
11 // Timeout in milliseconds
12 timeout: 1000,
13
14 // Display filename in stack traces
15 filename: 'user-code.js',
16
17 // Adjust line numbers in stack traces
18 lineOffset: 10,
19 columnOffset: 5,
20
21 // Break on first statement (debugging)
22 breakOnSigint: true,
23};
24
25const context = vm.createContext({ a: 5, b: 10 });
26const result = vm.runInContext(code, context, options);
27console.log(result); // 50Sandboxed Environment#
1import vm from 'node:vm';
2
3// Create minimal sandbox
4function createSandbox() {
5 const sandbox = {
6 // Safe built-ins
7 console: {
8 log: (...args) => console.log('[Sandbox]', ...args),
9 },
10 Math: Math,
11 Date: Date,
12 JSON: JSON,
13 parseInt: parseInt,
14 parseFloat: parseFloat,
15
16 // Custom APIs
17 result: undefined,
18 };
19
20 return vm.createContext(sandbox);
21}
22
23const sandbox = createSandbox();
24
25// Code can't access Node.js APIs
26vm.runInContext(`
27 console.log('Hello from sandbox');
28 result = Math.PI * 2;
29`, sandbox);
30
31console.log(sandbox.result); // 6.283...
32
33// Dangerous code is blocked
34try {
35 vm.runInContext(`
36 require('fs').readFileSync('/etc/passwd');
37 `, sandbox);
38} catch (err) {
39 console.log('Blocked:', err.message);
40 // require is not defined
41}Module Support#
1import vm from 'node:vm';
2
3// Create module
4const context = vm.createContext({
5 console: console,
6});
7
8// Synthetic module
9const syntheticModule = new vm.SyntheticModule(
10 ['default', 'named'],
11 function() {
12 this.setExport('default', 'default export');
13 this.setExport('named', 'named export');
14 },
15 { context }
16);
17
18// Link and evaluate
19await syntheticModule.link(() => {});
20await syntheticModule.evaluate();
21
22console.log(syntheticModule.namespace);
23// { default: 'default export', named: 'named export' }Safe Evaluation#
1import vm from 'node:vm';
2
3// Safe eval function
4function safeEval(code, context = {}, options = {}) {
5 const sandbox = {
6 ...context,
7 // Prevent access to dangerous globals
8 process: undefined,
9 require: undefined,
10 module: undefined,
11 exports: undefined,
12 __dirname: undefined,
13 __filename: undefined,
14 };
15
16 vm.createContext(sandbox);
17
18 const defaultOptions = {
19 timeout: 1000,
20 filename: 'eval',
21 };
22
23 return vm.runInContext(code, sandbox, {
24 ...defaultOptions,
25 ...options,
26 });
27}
28
29// Usage
30const result = safeEval('2 + 2');
31console.log(result); // 4
32
33const result2 = safeEval('items.filter(x => x > 5)', {
34 items: [1, 3, 5, 7, 9],
35});
36console.log(result2); // [7, 9]Expression Evaluation#
1import vm from 'node:vm';
2
3// Evaluate user expressions
4class ExpressionEvaluator {
5 constructor() {
6 this.context = vm.createContext({
7 Math: Math,
8 Date: Date,
9 });
10 this.variables = {};
11 }
12
13 setVariable(name, value) {
14 this.variables[name] = value;
15 this.context[name] = value;
16 }
17
18 evaluate(expression) {
19 try {
20 return vm.runInContext(expression, this.context, {
21 timeout: 100,
22 });
23 } catch (err) {
24 throw new Error(`Evaluation error: ${err.message}`);
25 }
26 }
27}
28
29const evaluator = new ExpressionEvaluator();
30evaluator.setVariable('price', 100);
31evaluator.setVariable('quantity', 5);
32
33console.log(evaluator.evaluate('price * quantity')); // 500
34console.log(evaluator.evaluate('Math.round(price * 1.1)')); // 110Template Engine#
1import vm from 'node:vm';
2
3// Simple template engine using vm
4function renderTemplate(template, data) {
5 const sandbox = {
6 ...data,
7 __output: '',
8 __append: function(str) {
9 this.__output += str;
10 },
11 };
12
13 vm.createContext(sandbox);
14
15 // Convert template to JavaScript
16 let code = '__append(`';
17 code += template
18 .replace(/`/g, '\\`')
19 .replace(/\${/g, '`); __append(')
20 .replace(/}/g, '); __append(`');
21 code += '`);';
22
23 vm.runInContext(code, sandbox, { timeout: 1000 });
24
25 return sandbox.__output;
26}
27
28// Usage
29const template = 'Hello, ${name}! You have ${count} messages.';
30const result = renderTemplate(template, { name: 'John', count: 5 });
31console.log(result); // 'Hello, John! You have 5 messages.'Code Analysis#
1import vm from 'node:vm';
2
3// Analyze code without executing
4function analyzeCode(code) {
5 try {
6 new vm.Script(code, { filename: 'analysis.js' });
7 return { valid: true, error: null };
8 } catch (err) {
9 return {
10 valid: false,
11 error: {
12 message: err.message,
13 line: err.lineNumber,
14 column: err.columnNumber,
15 },
16 };
17 }
18}
19
20console.log(analyzeCode('const x = 1;'));
21// { valid: true, error: null }
22
23console.log(analyzeCode('const x = ;'));
24// { valid: false, error: { message: 'Unexpected token ;', ... } }Worker-like Isolation#
1import vm from 'node:vm';
2
3// Create isolated execution environment
4class IsolatedRunner {
5 constructor() {
6 this.context = vm.createContext({
7 console: {
8 log: (...args) => this.output.push(['log', args]),
9 error: (...args) => this.output.push(['error', args]),
10 },
11 setTimeout: (fn, ms) => {
12 if (ms > 5000) throw new Error('Timeout too long');
13 return setTimeout(fn, ms);
14 },
15 });
16 this.output = [];
17 }
18
19 async run(code, timeout = 5000) {
20 this.output = [];
21
22 const script = new vm.Script(`
23 (async () => {
24 ${code}
25 })();
26 `);
27
28 try {
29 await script.runInContext(this.context, {
30 timeout,
31 breakOnSigint: true,
32 });
33 } catch (err) {
34 this.output.push(['error', [err.message]]);
35 }
36
37 return this.output;
38 }
39}
40
41// Usage
42const runner = new IsolatedRunner();
43const output = await runner.run(`
44 console.log('Starting...');
45 const result = 1 + 2;
46 console.log('Result:', result);
47`);
48
49console.log(output);
50// [['log', ['Starting...']], ['log', ['Result:', 3]]]Security Considerations#
1import vm from 'node:vm';
2
3// IMPORTANT: vm is NOT a security sandbox!
4// Malicious code can escape the context
5
6// Example of context escape (DO NOT rely on vm for security)
7const malicious = `
8 const ForeignFunction = this.constructor.constructor;
9 const process = ForeignFunction('return process')();
10 process.exit(1);
11`;
12
13// For true isolation, use:
14// 1. Worker threads with limited permissions
15// 2. Child processes
16// 3. Containers/VMs
17// 4. Third-party sandboxing libraries (vm2, isolated-vm)
18
19// Safe pattern: validate and whitelist operations
20function safeExecute(code) {
21 // Only allow specific patterns
22 const allowedPattern = /^[\d\s+\-*/().]+$/;
23 if (!allowedPattern.test(code)) {
24 throw new Error('Invalid expression');
25 }
26
27 return vm.runInNewContext(code, {});
28}
29
30console.log(safeExecute('2 + 2')); // 4
31// safeExecute('require("fs")'); // Error: Invalid expressionBest Practices#
Use Cases:
✓ Simple expression evaluation
✓ Template rendering
✓ Configuration evaluation
✓ Code analysis/validation
Context Setup:
✓ Minimal sandbox
✓ Whitelist allowed APIs
✓ Set timeouts
✓ Validate input
Security:
✓ vm is NOT a security sandbox
✓ Validate/sanitize input
✓ Use for trusted code only
✓ Consider alternatives for untrusted code
Avoid:
✗ Running untrusted code
✗ No timeout limits
✗ Exposing Node.js APIs
✗ Assuming isolation
Conclusion#
The Node.js vm module enables code execution in isolated V8 contexts, useful for template engines, expression evaluation, and code analysis. However, it is NOT a security sandbox - malicious code can escape. Use it for semi-trusted code with input validation and timeouts. For truly untrusted code, consider worker threads, child processes, or purpose-built sandboxing solutions like isolated-vm.