如何在Node.js中使用vm模块安全运行用户代码?
安全使用Node.js vm模块运行用户代码的方案
直接给你不用第三方包的硬限制方案,核心就是彻底切断用户代码对全局危险对象的访问路径,具体步骤和代码示例如下:
完全自定义沙箱上下文,绝不直接传入
global/globalThis
只把你允许用户使用的API放进上下文,比如仅提供Math的部分方法、自定义的安全工具函数,初始上下文为空对象,避免继承任何默认原型链。用
Proxy拦截危险属性访问
拦截用户代码对__proto__、constructor、prototype这类能逃逸沙箱的属性访问,一旦检测到就返回undefined或抛出错误,彻底阻断原型链攻击路径。冻结上下文对象
用Object.freeze()冻结最终的上下文,防止用户代码修改或扩展上下文里的属性。
完整代码示例
const vm = require('vm'); // 定义允许用户使用的安全API const safeGlobals = { Math: { abs: Math.abs, sqrt: Math.sqrt, // 只暴露你确认安全的Math方法 }, // 可以添加自定义的安全函数 log: (msg) => console.log(`[User Code Log]: ${msg}`) }; // 创建拦截危险属性的Proxy const sandboxProxy = new Proxy(safeGlobals, { get(target, prop) { // 拦截危险属性 const dangerousProps = ['__proto__', 'constructor', 'prototype', '__lookupGetter__', '__lookupSetter__']; if (dangerousProps.includes(prop)) { return undefined; } // 拦截对Object、Function等全局构造函数的访问 if (['Object', 'Function', 'Array', 'String'].includes(prop)) { return undefined; } return Reflect.get(target, prop); }, set(target, prop) { // 禁止修改上下文属性 throw new Error('Cannot modify sandbox context'); } }); // 冻结代理后的上下文,进一步强化限制 const frozenSandbox = Object.freeze(sandboxProxy); // 运行用户代码 try { const userCode = ` // 测试:尝试访问constructor console.log(this.constructor); // 输出undefined // 尝试逃逸沙箱 ({}).__proto__.constructor('return process')(); // 会失败,因为__proto__被拦截 // 只能使用允许的API log(Math.sqrt(25)); // 输出[User Code Log]: 5 `; vm.runInNewContext(userCode, frozenSandbox, { timeout: 1000 }); // 添加超时防止死循环 } catch (err) { console.error('User code execution error:', err); }
额外注意事项
- 一定要用
vm.runInNewContext或vm.createContext创建独立上下文,绝对不能用vm.runInThisContext(会共享当前进程的全局上下文,等于没隔离)。 - 添加超时时间(
timeout选项),防止用户代码执行死循环或长时间阻塞。 - 即使做了这些限制,也不能保证100%安全——Node.js的vm模块本身不是为完全不可信代码设计的。如果你的场景是处理高风险不可信代码,最好还是用进程隔离(比如
child_process创建独立进程,限制进程权限),这也属于原生Node.js API,不需要第三方包。
内容的提问来源于stack exchange,提问作者xzfses dawqdq
相关产品推荐
相关产品推荐

