vm2 是一个沙箱,可以使用白名单中的 Node 内置模块运行不受信任的代码。
npm install vm2
## 快速示例```js
import { VM } from 'vm2';
const vm = new VM();
vm.run(`process.exit()`); // TypeError: process.exit is not a function
I need the actual content of chunk 5 to translate it. Please provide the Markdown text you'd like translated.```js import { NodeVM } from 'vm2';
const vm = new NodeVM({ require: { external: true, root: './', }, });
vm.run(
var request = require('request'); request('http://www.google.com', function (error, response, body) { console.error(error); if (!error && response.statusCode == 200) { console.log(body); // Show the HTML for the Google homepage. } });,
'vm.js',
);
## 重要安全免责声明
**在使用 vm2 之前,您应当了解其工作原理及其局限性。**
vm2 尝试在**与您的应用程序相同的 Node.js 进程内**对不受信任的 JavaScript 代码进行沙箱隔离。它通过一个复杂的 [Proxies](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy) 网络来实现这一点,这些代理会拦截并调解沙箱与宿主环境之间的每一次交互。
### 根本性挑战
JavaScript 是一种极其动态的语言。对象可以通过原型链访问,构造函数可以通过错误对象触达,符号提供了协议钩子,异步执行则创造了时序窗口。在 JavaScript 中,从一个对象遍历到另一个对象的方式数不胜数,这使得构建一个严丝合缝的进程内沙箱变得极其困难。
**我们对这一现实保持坦诚:** 尽管我们尽了最大努力,研究人员和安全专业人员仍在不断发现逃逸 vm2 沙箱的新方法。我们会在这些漏洞被报告后积极修补,但进程内沙箱的猫鼠游戏性质意味着:
1. **未来很可能会发现新的绕过方法。** 请查看我们的[安全公告](https://github.com/patriksimek/vm2/security/advisories)以了解已知漏洞。
2. **您必须保持 vm2 更新**,以便受益于最新的安全修复。请订阅安全公告并及时更新。
3. **vm2 不应成为您唯一的防线。** 在运行不受信任的代码时,纵深防御至关重要。
### 更稳健的替代方案
如果您需要更强的隔离保证,请考虑以下提供**真正的进程级或硬件级隔离**的替代方案:
| 解决方案 | 方法 | 性能 | 权衡 |
|----------|----------|-------------|------------|
| **[isolated-vm](https://github.com/laverdet/isolated-vm)** | 独立的 V8 隔离(不同的 V8 堆) | 快 | 处于维护模式;需要手动更新 V8 |
| **独立进程 / Worker** | `child_process` 或具有受限权限的 Worker 线程 | 中等 | IPC 开销更高;数据必须序列化 |
| **容器 / 虚拟机** | Docker、gVisor、Firecracker | 慢 | 启动开销;资源占用高 |
| **托管服务** | 基于云的代码执行(例如 AWS Lambda、Cloudflare Workers) | 可变 | 网络延迟;外部依赖 |
### vm2 可能仍然适用的场景
vm2 在以下情况下可能适用:
- 您需要与宿主对象紧密集成以及快速的同步通信
- 不受信任的代码来自相对可信的来源(例如内部工具、经过审核作者的插件系统)
- 您将 vm2 与其他安全层结合使用(网络隔离、文件系统限制、资源限制)
- 您接受风险并积极监控安全更新
**如果您运行的是来自完全不受信任来源的代码(例如任意用户提交),我们强烈建议使用具有更强隔离保证的解决方案。**
## 运行时
| 运行时 | 状态 |
|---------|--------|
| Node.js | 受支持。沙箱是一个安全边界。 |
| Bun | **实验性。** 部分功能兼容 — **不是**安全边界。 |
Bun 存在两个独立的限制,二者互不关联。
**它不是安全边界。** vm2 的威胁模型、[`docs/ATTACKS.md`](https://github.com/patriksimek/vm2/blob/main/docs/ATTACKS.md) 中的攻击目录以及 `test/ghsa/` 中的每一个回归测试都源自 V8 内部机制。Bun 所使用的 JavaScriptCore 有其自身的对应机制,且均未针对 vm2 的桥接层进行审计。测试套件在 Bun 下通过仅表明兼容性,并不代表沙箱在该环境下能够保持安全。**请勿在 Bun 上使用 vm2 来隔离不受信任的代码。**
**兼容性是部分的,而非对等的。** Bun 下运行通过仅覆盖了实际在该环境中执行的测试。`test/bun-skips.js` 列出了被排除的测试及其原因,已知的行为差异包括:
- `Buffer.from(arrayLike)` 返回一个零长度的缓冲区
- `VMScript` 的 `filename` / `lineOffset` / `columnOffset` 元数据不可观察,因为 JSC 的 CallSite 对象不携带任何方法
- 对带有不可配置访问器的冻结宿主对象执行 `Object.freeze` 会抛出代理不变式 `TypeError`,而 V8 不会
- 某些跨沙箱边界的 `Buffer` 操作速度大幅下降 — 在 Node 上 64 MB 的 `allocUnsafe` 仅需 1.7 秒,而在 Bun 上需要超过 400 秒,慢到足以被视为挂起
请将 Bun 支持视为针对可信代码的尽力而为的兼容性,并在依赖任何特定行为之前查看跳过列表。
## 功能特性
- 在单个进程中与您的代码并排安全地运行不受信任的代码
- 完全控制沙箱的控制台输出
- 沙箱对进程的方法具有受限访问权限
- 可以从沙箱中 require 模块(内置和外部)
- 您可以限制对某些(或全部)内置模块的访问
- 您可以在沙箱之间安全地调用方法并交换数据和回调
- 积极维护并修补已知的逃逸方法(请参阅[安全免责声明](#important-security-disclaimer))
- 支持转译器
## 工作原理
- 它使用内部的 VM 模块来创建安全上下文。
- 它使用 [Proxies](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy) 来防止逃逸沙箱。
- 它覆盖内置的 require 以控制对模块的访问。
如需深入了解 vm2 的内部机制,请参阅 [docs/ATTACKS.md](https://github.com/patriksimek/vm2/blob/main/docs/ATTACKS.md)。
## Node 的 vm 与 vm2 有什么区别?
您可以亲自尝试:```js
import { runInNewContext } from "node:vm";
runInNewContext('this.constructor.constructor("return process")().exit()');
console.log('Never gets executed.');
请提供需要翻译的 Markdown 内容。```js import { VM } from 'vm2';
new VM().run('this.constructor.constructor("return process")().exit()'); // Throws ReferenceError: process is not defined
## 文档
- [VM](#vm)
- [NodeVM](#nodevm)
- [VMScript](#vmscript)
- [错误处理](#error-handling)
- [调试沙箱代码](#debugging-a-sandboxed-code)
- [只读对象](#read-only-objects-experimental)
- [受保护对象](#protected-objects-experimental)
- [跨沙箱关系](#cross-sandbox-relationships)
- [CLI](#cli)
- [2.x 到 3.x 的变更](https://github.com/patriksimek/vm2/wiki/2.x-to-3.x-changes)
- [1.x 和 2.x 文档](https://github.com/patriksimek/vm2/wiki/1.x-and-2.x-docs)
- [贡献指南](https://github.com/patriksimek/vm2/wiki/Contributing)
## VM
VM 是一个简单的沙箱,用于同步运行不受信任的代码,但不具备 `require` 功能。只有 JavaScript 内置对象和 Node 的 `Buffer` 可用。调度函数(`setInterval`、`setTimeout` 和 `setImmediate`)默认不可用。
**选项:**
- `timeout` - 脚本超时时间(毫秒)。**警告**:你可能希望将此选项与 `allowAsync=false` 一起使用。此外,对沙箱返回的对象进行操作可能会执行任意代码并绕过超时限制。应使用 `typeof` 测试返回对象是否为原始值,并在其他情况下完全丢弃该对象(使用此类对象进行日志记录或生成错误消息也可能再次执行任意代码)。
- `sandbox` - VM 的全局对象。
- `compiler` - `javascript`(默认)、`typescript`、`coffeescript` 或自定义编译器函数。如果该值设置为 `typescript` 或 `coffeescript`,库期望你已预先安装相应的编译器。**`typescript` 需要 `typescript@6` 或更早版本** — 请参阅 [编译器](#compilers)。
- `eval` - 如果设置为 `false`,任何对 `eval` 或函数构造函数(`Function`、`GeneratorFunction` 等)的调用都将抛出 `EvalError`(默认值:`true`)。
- `wasm` - 如果设置为 `false`,任何编译 WebAssembly 模块的尝试都将抛出 `WebAssembly.CompileError`(默认值:`true`)。注意:出于安全原因,`WebAssembly.JSTag` 已在沙箱内被移除,因此 wasm 代码无法捕获 JavaScript 异常。
- `allowAsync` - 如果设置为 `false`,任何使用 `async` 运行代码的尝试都将抛出 `VMError`(默认值:`true`)。
- `bufferAllocLimit` - 沙箱内部发起的单个 `Buffer.alloc` / `Buffer.allocUnsafe` / `Buffer.allocUnsafeSlow` / `Buffer(N)` / `new Buffer(N)` 请求的最大字节数。超过此上限的请求将同步抛出 `RangeError`,且不会执行宿主分配。默认值:`Infinity`(无上限,完全向后兼容)。在内存受限环境(Docker / Kubernetes / Lambda / serverless)中运行不受信任代码的嵌入者应选择有限上限(例如 `32 * 1024 * 1024`),作为分层 DoS 防御的一部分,就像选择 `timeout` 一样。请参阅下面的 [加固建议](#hardening-recommendations)。
**重要提示**:超时仅对通过 `run` 运行的同步代码有效。超时**不**适用于 VM 返回的任何方法。在某些情况下超时不起作用 - 请参阅 [#244](https://github.com/patriksimek/vm2/pull/244)。```js
import { VM } from 'vm2';
const vm = new VM({
timeout: 1000,
allowAsync: false,
sandbox: {},
});
vm.run('process.exit()'); // throws ReferenceError: process is not defined
你也可以从虚拟机中检索值。```js let number = vm.run('1337'); // returns 1337
**提示**:更多用法示例请参阅测试。
## NodeVM
与 `VM` 不同,`NodeVM` 允许你以与常规 Node 上下文中相同的方式 `require` 模块。
**选项:**