vm2 是 Node.js 生态中最强大、安全的沙箱库,基于 Proxy 代理 与上下文隔离技术,在原生 vm 模块基础上构建了严密的安全边界,可安全执行不可信代码、防止沙箱逃逸,广泛用于插件系统、在线代码编辑器、模板引擎等场景。
本文从安装、基础使用、核心 API、高级配置、安全防护到实战案例,提供完整教程与可直接运行的详细代码。
一、环境准备与安装
1.1 版本说明
- 最新稳定版:3.10.2(修复 CVE-2026-22709 Promise 逃逸高危漏洞)
- 兼容:Node.js 12+
1.2 安装
bash
# 项目内安装
npm install vm2 --save
# 全局安装(含 CLI)
npm install -g vm2
1.3 核心模块导入
vm2 提供 3 个核心类,按需导入:
javascript
// CommonJS
const { VM, NodeVM, VMScript } = require('vm2');
// ES Module
import { VM, NodeVM, VMScript } from 'vm2';
二、基础沙箱:VM 类(轻量同步)
VM 是基础沙箱,无模块加载能力,适合执行简单同步代码,安全性最高、性能最好。
2.1 基础配置与运行代码
配置项(常用)
timeout:代码执行超时(ms),防死循环allowAsync:是否允许异步代码(setTimeout/Promise),默认falsesandbox:注入沙箱的全局变量(白名单)console:是否允许console,默认falseeval:是否允许eval/Function构造器,默认true
示例 1:简单计算与结果返回
javascript
const { VM } = require('vm2');
// 1. 创建沙箱实例
const vm = new VM({
timeout: 1000, // 1秒超时
allowAsync: false,
sandbox: {
// 注入安全全局变量
PI: 3.14159,
add: (a, b) => a + b
}
});
// 2. 运行代码(同步)
try {
// 基础计算
const res1 = vm.run('1 + 2 * 3');
console.log('计算结果:', res1); // 输出:7
// 使用注入变量
const res2 = vm.run('add(PI, 1)');
console.log('注入变量计算:', res2); // 输出:4.14159
// 访问被禁止的全局对象(报错)
vm.run('process.exit()');
} catch (err) {
console.error('错误:', err.message);
// 输出:ReferenceError: process is not defined
}
示例 2:自定义控制台输出
javascript
const { VM } = require('vm2');
const vm = new VM({
console: 'redirect', // 重定向控制台
sandbox: {
// 自定义console,仅允许log
console: {
log: (...args) => console.log('[沙箱日志]', ...args)
}
}
});
// 沙箱内console会被重定向
vm.run('console.log("Hello vm2!")');
// 输出:[沙箱日志] Hello vm2!
2.2 安全测试:原生 vm 对比 vm2
原生 vm 存在严重逃逸漏洞,vm2 可完美防护:
javascript
// 原生 vm(危险)
const vm = require('vm');
const ctx = {};
vm.createContext(ctx);
// 逃逸代码:获取主进程process并终止
vm.runInContext('this.constructor.constructor("return process")().exit()', ctx);
// 进程直接被杀死!
// vm2(安全)
const { VM } = require('vm2');
const safeVM = new VM();
// 同样代码会被拦截
safeVM.run('this.constructor.constructor("return process")().exit()');
// 抛出:TypeError: constructor is not a function
三、高级沙箱:NodeVM 类(支持模块)
NodeVM 是增强版沙箱,支持 require 加载内置 / 外部模块,适合复杂业务场景(如插件)。
3.1 核心配置(require 控制)
require 配置是安全核心,遵循最小权限原则:
javascript
const { NodeVM } = require('vm2');
const nodeVM = new NodeVM({
// 控制台继承主进程
console: 'inherit',
// 模块加载配置
require: {
// 允许的内置模块(白名单)
builtin: ['fs', 'path', 'crypto'],
// 允许加载外部模块(true/false/数组)
external: true,
// 外部模块根目录(限制访问范围)
root: './sandbox_modules',
// 禁止加载的模块
deny: ['child_process', 'net'],
// 模块映射(自定义别名)
mapping: {
'lodash': 'lodash-es'
}
},
// 沙箱全局变量
sandbox: {
appConfig: { env: 'production' }
}
});
3.2 示例 1:加载内置模块
javascript
const { NodeVM } = require('vm2');
const vm = new NodeVM({
console: 'inherit',
require: {
builtin: ['fs', 'path'] // 仅允许fs、path
}
});
// 沙箱内读取文件(安全)
const code = `
const fs = require('fs');
const path = require('path');
const filePath = path.join(__dirname, 'test.txt');
fs.writeFileSync(filePath, 'vm2 write test');
const content = fs.readFileSync(filePath, 'utf8');
module.exports = content;
`;
const result = vm.run(code);
console.log('文件内容:', result); // 输出:vm2 write test
// 尝试加载禁止模块(报错)
vm.run('require("child_process")');
// 抛出:Error: Access denied to module child_process
3.3 示例 2:导出与调用沙箱函数
javascript
const { NodeVM } = require('vm2');
const vm = new NodeVM({
console: 'inherit',
require: { external: false }
});
// 沙箱代码导出函数
const pluginCode = `
module.exports = {
greet: (name) => {
console.log('Greet from sandbox');
return \`Hello, \${name}\`;
},
calculate: (a, b) => a * b
};
`;
// 运行并获取导出对象
const plugin = vm.run(pluginCode);
// 调用沙箱函数(主进程 ↔ 沙箱通信)
console.log(plugin.greet('Node.js')); // 输出:Hello, Node.js
console.log(plugin.calculate(5, 10)); // 输出:50
四、性能优化:VMScript 类(预编译)
VMScript 可预编译代码,重复执行时大幅提升性能(避免重复解析)。
4.1 基础用法
javascript
const { VM, VMScript } = require('vm2');
// 1. 预编译脚本(编译一次)
const script = new VMScript(`
let sum = 0;
for(let i=0; i<10000; i++) sum += i;
sum;
`, {
filename: 'loop-script.js' // 调试文件名
});
// 2. 创建沙箱
const vm = new VM({ timeout: 500 });
// 3. 多次运行预编译脚本(高效)
console.time('执行10次');
for(let i=0; i<10; i++) {
const res = vm.run(script);
console.log(`第${i+1}次结果:`, res);
}
console.timeEnd('执行10次');
4.2 结合 NodeVM 预编译模块
javascript
const { NodeVM, VMScript } = require('vm2');
// 预编译模块代码
const moduleScript = new VMScript(`
const crypto = require('crypto');
module.exports = (str) => crypto.createHash('md5').update(str).digest('hex');
`);
const vm = new NodeVM({
require: { builtin: ['crypto'] }
});
// 运行预编译模块
const md5 = vm.run(moduleScript);
console.log('MD5:', md5('vm2-secure'));
五、安全机制与防护(核心)
5.1 vm2 三层安全架构
- 上下文隔离 :基于
vm.createContext创建独立内存空间 - Proxy 代理防护:拦截所有对象访问、属性操作、函数调用
- 运行时监控:超时控制、错误捕获、危险 API 拦截
5.2 高危漏洞与修复
- CVE-2026-22709 (<=3.10.1):Promise 回调净化缺陷 → 升级至 3.10.2+
- 历史漏洞:原型链污染、错误对象逃逸 → 始终用最新版
5.3 安全最佳实践(必看)
- 最小权限 :只开放必要模块 / API,禁止
child_process/process - 超时限制 :同步代码必设
timeout,异步代码额外监控 - 禁止危险函数 :
eval: false、wasm: false - 范围限制 :外部模块设
root,文件操作限制目录 - 版本锁定:固定 vm2 版本,定期更新安全补丁
- 深度防御:结合进程隔离(如 worker_threads)、权限控制
5.4 安全配置模板(生产可用)
javascript
const { NodeVM } = require('vm2');
const secureVM = new NodeVM({
// 基础安全
timeout: 3000,
eval: false,
wasm: false,
allowAsync: true,
// 控制台
console: 'redirect',
sandbox: {
console: {
log: (...args) => console.log('[SANDBOX]', ...args),
warn: (...args) => console.warn('[SANDBOX]', ...args)
}
},
// 严格模块控制
require: {
builtin: ['path', 'crypto', 'buffer'], // 最小内置模块
external: false, // 禁止外部模块
deny: ['fs', 'os', 'process', 'child_process'], // 黑名单
root: './' // 限制根目录
},
// 错误捕获
onCancel: () => console.error('沙箱代码超时被终止')
});
六、实战案例:安全插件系统
6.1 需求
- 主程序加载第三方插件
- 插件仅能访问指定 API
- 防止插件逃逸、破坏主进程
6.2 完整代码
javascript
const { NodeVM } = require('vm2');
const fs = require('fs');
const path = require('path');
// 1. 安全沙箱工厂
function createPluginSandbox(pluginName) {
return new NodeVM({
console: 'inherit',
timeout: 2000,
eval: false,
require: {
builtin: ['path'],
external: false
},
// 注入插件可用API(白名单)
sandbox: {
pluginName,
// 安全工具函数
utils: {
formatDate: (date) => new Date(date).toLocaleString(),
random: (min, max) => Math.floor(Math.random() * (max - min + 1)) + min
},
// 主程序提供的服务
pluginService: {
log: (msg) => console.log(`[插件${pluginName}]`, msg),
getData: () => ({ user: 'admin', role: 'guest' })
}
}
});
}
// 2. 加载并执行插件
function loadPlugin(pluginPath) {
try {
const pluginCode = fs.readFileSync(pluginPath, 'utf8');
const pluginName = path.basename(pluginPath, '.js');
const vm = createPluginSandbox(pluginName);
const plugin = vm.run(pluginCode);
// 初始化插件
if (typeof plugin.init === 'function') {
plugin.init();
}
console.log(`插件${pluginName}加载成功`);
return plugin;
} catch (err) {
console.error('插件加载失败:', err.message);
return null;
}
}
// 3. 插件代码(sandbox_plugins/demo-plugin.js)
/*
module.exports = {
init() {
pluginService.log('插件初始化');
const data = pluginService.getData();
pluginService.log(`用户数据: ${JSON.stringify(data)}`);
const date = utils.formatDate(Date.now());
pluginService.log(`当前时间: ${date}`);
const num = utils.random(1, 100);
pluginService.log(`随机数: ${num}`);
},
execute: (params) => {
pluginService.log(`执行任务: ${params}`);
return `插件处理结果: ${params.toUpperCase()}`;
}
};
*/
// 4. 运行
const plugin = loadPlugin(path.join(__dirname, 'sandbox_plugins/demo-plugin.js'));
if (plugin) {
const result = plugin.execute('vm2-plugin-test');
console.log('主程序接收:', result);
}
七、常见问题与解决方案
7.1 沙箱内无法访问主进程变量
- 原因:默认完全隔离,需通过
sandbox显式注入 - 解决:配置
sandbox白名单,仅传递必要对象
7.2 异步代码超时不生效
- 原因:
timeout仅对同步代码有效 - 解决:异步代码结合
Promise.race手动超时:
javascript
const vm = new VM({ allowAsync: true });
const code = 'new Promise(resolve => setTimeout(() => resolve("done"), 5000))';
// 手动超时控制
Promise.race([
vm.run(code),
new Promise((_, reject) => setTimeout(() => reject(new Error('异步超时')), 2000))
]);
7.3 沙箱逃逸风险
- 解决:
- 升级至最新版 vm2
- 严格限制模块与 API
- 结合进程隔离(如
worker_threads) - 生产环境禁用高危功能
八、总结
vm2 是 Node.js 安全执行不可信代码的首选方案,VM 适合轻量同步场景,NodeVM 支持模块加载满足复杂需求,VMScript 优化重复执行性能。
核心原则 :最小权限 + 最新版本 + 深度防御,即可构建高安全的沙箱环境。