在 Node.js 中,vm 模块作为官方内置模块,提供了一种在独立上下文环境中执行 JavaScript 代码的机制。所谓独立上下文,指的是代码执行时的全局对象与宿主环境互相隔离,宿主进程中的大部分全局能力不会直接暴露给被执行的代码。通过这种方式,我们可以在一定程度上安全地运行来自用户输入、配置文件或第三方服务的不可信代码片段,同时降低其对主进程造成破坏的风险。

vm 模块核心 API 与基础执行流程
在使用 vm 模块之前,需要先理解它的几个关键方法。vm.createContext 负责将一个普通 JavaScript 对象转换为上下文对象,这个对象会成为后续代码执行时的全局对象。默认情况下,新创建的上下文不会包含 Node.js 的 process、require、Buffer 等常见全局变量,因此不可信代码无法直接通过这些接口访问文件系统或网络。
vm.runInContext 方法接收代码字符串、上下文对象以及可选配置,并返回代码最后的表达式结果。vm.runInNewContext 则整合了创建上下文和执行代码两个步骤,适合一次性执行场景。此外,vm.Script 类可以预编译代码,便于在多个上下文中重复执行,提高效率。
下面通过一个基础示例展示如何创建沙箱并执行不可信代码:
const vm = require('vm');
// 定义沙箱上下文,只暴露必要的变量
const sandbox = {
a: 10,
b: 20,
console: console
};
// 将普通对象转换为沙箱上下文
vm.createContext(sandbox);
// 不可信代码字符串
const untrustedCode = `
const total = a + b;
console.log('沙箱中的计算结果:', total);
total;
`;
// 在沙箱中执行代码
const result = vm.runInContext(untrustedCode, sandbox);
console.log('宿主进程获取返回值:', result);
构建安全的沙箱上下文
仅仅创建一个空上下文并不足以保证安全,开发者还需要对上下文内容和执行选项进行严格控制。沙箱的安全性很大程度上取决于挂载到上下文中的对象是否包含危险能力。如果错误地将 process、require 或 Buffer 等核心对象放入沙箱,不可信代码就可能通过它们读取文件、创建子进程甚至退出宿主进程。
最小权限原则是设计安全沙箱的核心思路:只向不可信代码提供完成其任务所必需的 API,不提供任何多余的接口。例如,如果沙箱代码只需要进行数学运算,就不应该提供任何与输入输出相关的能力。同时,对于传入沙箱的数据必须进行严格的类型检查和范围校验,避免不可信代码通过构造特殊值触发原型链污染等问题。
限制全局对象访问
在创建上下文时,不要将宿主环境的全局对象直接复制进去。比较安全的做法是显式列出允许访问的对象,例如只提供 Math、JSON 等纯计算或数据转换能力。任何涉及文件、网络、进程、模块加载的对象都不应出现在沙箱中。
const vm = require('vm');
// 仅提供纯计算所需的对象
const safeSandbox = {
Math: Math,
parseInt: parseInt
};
vm.createContext(safeSandbox);
const code = `
const num = parseInt('128', 10);
const squared = Math.pow(num, 2);
squared;
`;
const output = vm.runInContext(code, safeSandbox);
console.log('安全沙箱计算结果:', output);
按需提供有限 API
如果不可信代码确有输出日志的需求,也不应该直接把宿主 console 对象原样传入,而应提供经过包装的、只包含特定方法的对象。这样既能满足调试需求,又能防止不可信代码调用 console 的其他方法或通过 console 访问宿主环境。
const vm = require('vm');
// 包装 console,仅暴露 log 和 error 方法
const safeConsole = {
log: (...args) => console.log('[沙箱输出]', ...args),
error: (...args) => console.error('[沙箱错误]', ...args)
};
const sandbox = {
console: safeConsole
};
vm.createContext(sandbox);
const code = `
console.log('这是一条来自沙箱的消息');
const result = 6 * 7;
result;
`;
const result = vm.runInContext(code, sandbox);
console.log('获取到的结果:', result);
设置超时时间
不可信代码可能包含死循环或长时间运行的计算,如果不加限制,宿主进程会被阻塞。vm 模块的 runInContext 方法支持通过 options 参数中的 timeout 字段设置执行超时时间,单位为毫秒。当代码执行超过设定时间后,Node.js 会抛出错误并中断执行。
const vm = require('vm');
const sandbox = {};
vm.createContext(sandbox);
const infiniteLoopCode = `
while (true) {
// 模拟死循环
}
`;
try {
// 设置 1000 毫秒超时
vm.runInContext(infiniteLoopCode, sandbox, { timeout: 1000 });
} catch (err) {
console.log('代码执行超时:', err.message);
}
使用 vm.Script 预编译与完整安全执行
当同一段不可信代码
当同一段不可信代码需要被反复执行时,可以先用 vm.Script 将源码编译成脚本对象,再通过 runInContext 在指定上下文中运行。预编译能避免重复解析,同时可以为每次执行单独设置超时时间,适合需要批量或周期性执行沙箱代码的场景。
const vm = require('vm');
const sandbox = {};
vm.createContext(sandbox);
const script = new vm.Script(`
let sum = 0;
for (let i = 1; i <= 100; i++) {
sum += i;
}
sum;
`);
const result = script.runInContext(sandbox, { timeout: 500 });
console.log('第一次执行结果:', result);
const result2 = script.runInContext(sandbox, { timeout: 500 });
console.log('第二次执行结果:', result2);
由于 runInContext 会复用传入的上下文对象,如果脚本中使用了全局变量,多次执行之间会互相影响。例如,把计数器放在 globalThis 上时,第二次执行会看到上一次修改后的值。若需要每次执行都从干净状态开始,就应为每次调用创建新的上下文。
const vm = require('vm');
const script = new vm.Script(`
globalThis.count = (globalThis.count || 0) + 1;
globalThis.count;
`);
function runInFreshContext() {
const sandbox = {};
vm.createContext(sandbox);
return script.runInContext(sandbox);
}
console.log(runInFreshContext()); // 1
console.log(runInFreshContext()); // 1
除超时与上下文隔离外,还可以对沙箱中的内建对象做加固。例如冻结 Object.prototype、Array.prototype 等原型对象,可以在一定程度上阻止恶意代码篡改基础方法。但这类加固并不构成绝对安全边界,原型链逃逸和异步资源消耗仍可能发生,因此只能作为辅助手段。
如果要在生产环境中执行真正不受信任的代码,仅靠 vm 模块是不够的。vm 在同一进程内运行代码,无法彻底隔离 CPU 时间和内存占用,timeout 也只能中断同步执行。更稳妥的做法是使用 worker_threads 将不可信代码放入独立线程,或通过子进程、容器等方式实现操作系统级隔离。这样可以在任务超时或崩溃时直接终止执行单元,不影响主进程。
总体而言,Node.js 的 vm 模块提供了一种轻量级的代码执行隔离方案,适合运行可信但动态生成的脚本、配置表达式或测试代码。通过 createContext、runInContext 和 timeout 参数,可以控制沙箱全局环境与执行时长;预编译脚本则能提升重复执行效率。但若代码来源完全不可信,务必结合 worker_threads、子进程或容器等额外隔离层级,才能真正实现安全沙箱。
Node.jsvm_modulesandboxuntrusted_code修改时间:2026-07-19 15:03:30