# Code 节点沙箱方案

## 当前方案：Node.js `vm` 模块

```typescript
// activities/code-execute.ts
const vm = await import('vm');
const sandbox = { data, items, result: undefined };
const context = vm.createContext(sandbox);
new vm.Script(`result = (function() { ${script} })()`).runInContext(context, { timeout });
```

**优点：** 零依赖，零开销，简单直接
**缺点：** 隔离性有限，恶意代码可通过原型链逃逸访问 Node.js API
**适用：** 内部管理后台，脚本由可信管理员编写

---

## 备选方案：isolated-vm（强隔离）

当需要执行**不可信用户代码**时，切换到 `isolated-vm`。

### 什么是 isolated-vm

- V8 引擎 Isolate 接口的 Node.js 封装
- 在独立 V8 实例中执行代码，和主进程**零共享内存**
- 无法访问 Node.js API（fs、net、process 等全部不可用）
- 周下载量 100万+，生产级稳定
- `vm2` 已停止维护（不可修复的逃逸漏洞），`isolated-vm` 是社区推荐替代

### 层级结构

```
Isolate (V8 引擎实例)       ← 创建开销 ~5ms，可复用
  └── Context (执行上下文)    ← 创建开销 <1ms，每次新建保证隔离
        └── Script (编译后代码) ← 可缓存
```

### 推荐实现：复用 Isolate + 每次新 Context

```typescript
// activities/code-execute.ts（isolated-vm 版本）
import ivm from 'isolated-vm';

// Worker 启动时创建一个 Isolate（复用，不用每次新建）
const isolate = new ivm.Isolate({ memoryLimit: 128 }); // 128MB 内存上限

export async function codeExecute(input: CodeExecuteInput): Promise<CodeExecuteOutput> {
  const { script, mode = 'runOnceForAll', timeout = 5000, data, items } = input;

  if (mode === 'runOnceForEach' && Array.isArray(items)) {
    const results: any[] = [];
    for (const item of items) {
      const result = await runInSandbox(script, { item, data }, timeout);
      results.push(result);
    }
    return { result: results };
  }

  const result = await runInSandbox(script, { data, items: items || [] }, timeout);
  return { result };
}

async function runInSandbox(
  script: string,
  globals: Record<string, any>,
  timeout: number,
): Promise<any> {
  // 每次执行创建新 Context（<1ms），保证执行间完全隔离
  const context = await isolate.createContext();

  try {
    const jail = context.global;

    // 注入变量（深拷贝进 Isolate，主进程对象不可直接访问）
    for (const [key, value] of Object.entries(globals)) {
      await jail.set(key, new ivm.ExternalCopy(value).copyInto());
    }

    // 执行脚本
    const wrapped = `(function() { ${script} })()`;
    const result = await context.eval(wrapped, { timeout });

    return result;
  } finally {
    context.release(); // 释放 Context 内存，Isolate 继续复用
  }
}
```

### 性能对比

| 策略 | 每次执行开销 | 内存 |
|------|------------|------|
| 每次新建 Isolate | ~5ms | 每次分配独立堆 |
| **复用 Isolate + 每次新 Context** | **<1ms** | 共享 Isolate 堆 |
| 复用 Isolate + 复用 Context | ~0 | ⚠️ 执行间状态泄漏 |

推荐第二种：性能和隔离性的最佳平衡。

### 安全保障

| 防护 | 说明 |
|------|------|
| 内存限制 | `memoryLimit: 128` → 超限直接 kill |
| CPU 限制 | `timeout` 参数 → 超时抛异常 |
| 网络隔离 | 无 `require`/`import`，无法访问 `net`/`http` |
| 文件系统隔离 | 无 `fs` 模块 |
| 进程隔离 | 无 `process`/`child_process` |
| 原型链隔离 | 独立 V8 堆，原型链攻击无效 |

### 何时切换

- ✅ 当前 `vm` 方案：适用于内部可信用户（管理员自己写脚本）
- 🔄 切换到 `isolated-vm`：当开放给外部用户或非技术人员编写脚本时

### 切换步骤

1. 依赖已在 `package.json` 中（`"isolated-vm": "^5.x"`）
2. 替换 `activities/code-execute.ts` 的实现（如上）
3. 接口不变，调用方无需修改
4. 测试：验证 `runOnceForAll` 和 `runOnceForEach` 两种模式
