Files
qiming/qimingclaw/docs/sandbox-whitelist-blacklist-plan.md

23 KiB
Raw Blame History

沙箱白名单/黑名单矩阵与验证计划(跨平台 + 多模式)

更新日期2026-04-09 上游审查依据:sandbox-plan.md两轮审查12 条问题已确认)


1. 范围边界

1.1 In Scope

  • 平台macOS / Linux / Windows
  • 后端:macos-seatbelt / linux-bwrap / windows-sandbox / docker(当前未实现,显式标注 unsupported
  • 沙箱模式(modestrict / compat / permissive(全平台通用,含 Windows
  • Windows 子模式(windowsMode,独立维度):read-only / workspace-write,与 mode 正交组合
  • 双层覆盖:
    1. 沙箱层:文件系统、网络、进程执行、设备访问的 allow/block/conditional 行为
    2. 命令权限层PermissionManager 的 safeCommands 白名单与危险命令黑名单行为

1.2 Out of Scope

  • GUI Agent 沙箱策略与测试
  • 非沙箱业务功能

2. 核心设计决策

决策 1矩阵是"规范文档"normative而非从实现反向生成

若矩阵从实现代码自动提取,则实现的 bug 会被误记为"正确行为",规范价值丧失。

确定方向:矩阵由人工审查后手动维护 → 测试验证实现是否符合矩阵 → CI 检查矩阵与生成文档是否同步。

手工维护 sandbox-matrix.spec.json  ←  专家审查 + PR review
        ↓
测试驱动(从 spec 生成表驱动用例)
        ↓
验证实现是否符合矩阵
        ↓
CI: spec → 生成 Markdowngit diff 检查一致性

生成脚本(crates/agent-electron-client/scripts/generate-sandbox-matrix-doc.js)的职责是:spec.json → .md,方向单向,不反向提取。

决策 2两层矩阵严格分离

Spec 文件 内容
沙箱层 docs/sandbox/sandbox-matrix.spec.json 平台/后端/模式下各操作的 verdict
命令权限层 docs/sandbox/permission-matrix.spec.json safeCommands 白名单 + 危险命令黑名单行为

两层不混入同一文件,避免审查与测试范围混淆。


3. 产物定义

3.1 规范文件(手工维护,单一真源)

docs/sandbox/sandbox-matrix.spec.json — 沙箱层:

{
  "cases": [
    {
      "id": "MAC-SEATBELT-COMPAT-workspace_write",
      "platform": "darwin",
      "backend": "macos-seatbelt",
      "mode": "compat",
      "operationId": "workspace_write",
      "verdict": "allow",
      "evidence": "SandboxInvoker.ts buildSeatbelt: writablePaths → allow file-write*"
    },
    {
      "id": "WIN-SANDBOX-COMPAT-WW-network_external",
      "platform": "win32",
      "backend": "windows-sandbox",
      "mode": "compat",
      "windowsMode": "workspace-write",
      "operationId": "network_external",
      "verdict": "conditional",
      "condition": "best-effort suppression via env vars + denybin stubs (ssh/scp); does not block raw socket connections",
      "evidence": "windows-sandbox-helper/src/env.rs apply_no_network_to_env()"
    },
    {
      "id": "ALL-DOCKER-ANY-any",
      "platform": "*",
      "backend": "docker",
      "mode": "*",
      "operationId": "*",
      "verdict": "unsupported",
      "reason": "process-level docker wrapping not implemented; SandboxInvoker returns unwrapped command with warning log"
    }
  ]
}

操作枚举operationId

ID 说明
workspace_write 工作区内写文件
outside_workspace_write 工作区外写文件
system_path_write 系统路径写(/etc, /usr, C:\Windows
file_read 任意路径读
network_external 外网 TCP/UDP
network_loopback loopback127.0.0.1)访问
process_exec_whitelist 白名单路径可执行文件
process_exec_arbitrary 任意路径可执行文件
privilege_escalation sudo/su/提权命令
device_write /dev 设备节点写

Verdict 语义

含义 必填字段
allow 操作被允许
block 操作被阻断
conditional 取决于前置条件或调用方约束 condition
unsupported 该组合当前未实现 reason

所有条目必须有 evidence 字段,引用具体源码位置(如 SandboxInvoker.ts:L123)。

通配符语义"*" 表示"适用于该维度的所有值"(如 "platform": "*" 表示全平台)。测试代码需显式处理:c.platform === process.platform || c.platform === "*"

docs/sandbox/permission-matrix.spec.json — 命令权限层:

{
  "safeCommands": {
    "commands": ["node", "npm", "npx", "git", "ls", "cat", "..."],
    "expectedBehavior": "auto-approve for command:execute without user confirmation",
    "evidence": "PermissionManager.ts DEFAULT_PERMISSION_POLICY.safeCommands"
  },
  "dangerousCommands": {
    "cases": [
      {
        "id": "PERM-SUDO",
        "input": "sudo rm -rf /",
        "matchedPattern": "sudo",
        "expectedResult": "allowed: false",
        "evidence": "PermissionManager.ts DANGEROUS_COMMANDS + checkDangerousOperation word-boundary regex"
      },
      {
        "id": "PERM-CHMOD-777",
        "input": "chmod 777 /etc",
        "matchedPattern": "chmod 777",
        "expectedResult": "allowed: false"
      },
      {
        "id": "PERM-CHMOD-755-FP",
        "input": "chmod 755 file",
        "matchedPattern": null,
        "expectedResult": "allowed: true",
        "note": "word-boundary regression: must NOT match 'chmod 777' pattern"
      }
    ]
  },
  "sensitivePaths": {
    "cases": [
      {
        "id": "PERM-SSH",
        "type": "file:write",
        "target": "/home/user/.ssh/id_rsa",
        "expectedResult": "allowed: false"
      },
      {
        "id": "PERM-ETC-PASSWD",
        "type": "file:write",
        "target": "/etc/passwd",
        "expectedResult": "allowed: false",
        "note": "sensitiveEtcPaths 精确匹配;/etc/hosts 当前不在阻止列表中"
      }
    ]
  },
  "denyList": {
    "cases": [
      {
        "id": "PERM-SYS-PKG",
        "type": "package:install:system",
        "target": "curl",
        "expectedResult": "allowed: false, no confirmation prompt"
      }
    ]
  }
}

3.2 已知 conditional 条目(需在 spec 中显式标注)

组合 operationId condition
Windows / any / workspace_write strict outside_workspace_write helper 始终放行 command_cwd调用方必须保证 cwd 在 workspace 内
Windows / windows-sandbox / any network_external best-effort env/stub 抑制,不是内核级隔离
All / autoFallback=session 全部 当前与 startup-only 行为一致预留枚举TBD
macOS seatbelt / sudo exec process_exec_whitelist /usr/bin/sudo 在 exec 白名单(^/usr/bin/),可被执行;但权限提升被 seatbelt deny结果为 conditional
Windows / sandboxed-bash-mcp 全部 MCP 路径固定传 --no-write-restricted不使用 WRITE_RESTRICTED token依赖 DACL ACE 控制写权限
Linux / linux-bwrap / permissive network_external, network_loopback permissive 走独立代码分支SandboxInvoker.ts:228-239不执行 --unshare-net即使 networkEnabled=false 也不隔离网络

3.3 生成产物CI 检查)

  • docs/sandbox/sandbox-matrix.generated.md — 由 crates/agent-electron-client/scripts/generate-sandbox-matrix-doc.js 从 spec 生成,提交至仓库
  • sandbox-report.json — 测试执行结果汇总,上传 CI artifact

CI 一致性检查:每次 spec 变更后重新生成 .md若生成结果与仓库已提交版本不一致PR 门禁失败。

3.4 CI 报告 schema

{
  "platform": "darwin|linux|win32",
  "backend": "macos-seatbelt|linux-bwrap|windows-sandbox",
  "mode": "strict|compat|permissive",
  "windowsMode": "read-only|workspace-write|null",
  "case_id": "MAC-SEATBELT-COMPAT-workspace_write",
  "status": "pass|fail|skip",
  "duration_ms": 123,
  "exit_code": 0,
  "notes": ""
}

关键指标:pass_rate(按平台/后端/模式分组)、degrade_to_none_countenv_leak_countpolicy_violation_count


4. 测试方案

4.1 沙箱层核心测试清单

Windows 列的行为取决于 mode × windowsMode 组合,下表以 compat + workspace-write(默认配置)为基准。

操作 macOS seatbelt Linux bwrap Windows sandboxcompat + workspace-write
workspace 内写 allow allow allow
workspace 外写 block block conditionalhelper 放行 command_cwd,需调用方约束)
系统路径写/删 block block block
外网访问 networkEnabledtrue → (allow network*),适用所有模式) strict/compatnetworkEnabledfalse → --unshare-netpermissive不隔离网络(走独立分支,--unshare-net 不执行) conditionalbest-effort env/stub非内核级
loopback 访问 同外网,受 networkEnabled 控制(与 mode 无关) 同外网permissive 例外:始终可访问) 需实测
sudo/usr/bin/sudo conditionalexec 被放行(路径在白名单 ^/usr/bin/),但权限提升被 seatbelt deny block/usr/bin/ 不在 strict 挂载面 / bwrap 命名空间隔离) blockPermissionManager 层拦截)
reboot/shutdown/sbin/ block/sbin/ 不在 exec 白名单) block block
workspace 内写(read-only 模式时) N/A N/A blockReadOnly 策略无 writable_roots

4.2 命令权限层测试PermissionManager

新增测试文件src/main/services/sandbox/PermissionManager.test.ts

关键断言:

注意:checkPermission 返回 Promise<PermissionResult>{ allowed: boolean; reason: string }),不抛出异常。危险操作返回 allowed: false,不进入用户确认队列。

// safeCommands 自动放行
it("should auto-approve safeCommands", async () => {
  for (const cmd of DEFAULT_PERMISSION_POLICY.safeCommands) {
    const r = await pm.checkPermission(sid, "command:execute", cmd);
    expect(r.allowed).toBe(true);
  }
});

// 危险命令拦截(直接返回 allowed:false不抛出
it("should block dangerous commands", async () => {
  const sudo = await pm.checkPermission(sid, "command:execute", "sudo rm -rf /");
  expect(sudo.allowed).toBe(false);

  const nc = await pm.checkPermission(sid, "command:execute", "nc -l 8080");
  expect(nc.allowed).toBe(false);
});

// word-boundary 回归DANGEROUS_COMMANDS 包含 "chmod 777" 字面串
// 应 block "chmod 777 file",不应误杀 "chmod 755 file"
it("should not false-positive on chmod 755", async () => {
  const block = await pm.checkPermission(sid, "command:execute", "chmod 777 /etc");
  expect(block.allowed).toBe(false);

  const safe = await pm.checkPermission(sid, "command:execute", "chmod 755 file");
  expect(safe.allowed).toBe(true); // 755 不在危险命令黑名单
});

// 敏感路径拦截
// 注意sensitiveEtcPaths 仅含 /etc/passwd, /etc/shadow, /etc/sudoers, /etc/group
// /etc/hosts 当前 **不在** 阻止列表中(待评估是否需要补入)
it("should block file:write to sensitive paths", async () => {
  const passwd = await pm.checkPermission(sid, "file:write", "/etc/passwd");
  expect(passwd.allowed).toBe(false);

  const shadow = await pm.checkPermission(sid, "file:write", "/etc/shadow");
  expect(shadow.allowed).toBe(false);

  // .ssh 检测使用 .includes(".ssh"),存在子串误判风险
  // "not.sshrc" 也会被拦截(已知限制,需评估是否改为路径段匹配)
  const ssh = await pm.checkPermission(sid, "file:write", "/home/user/.ssh/id_rsa");
  expect(ssh.allowed).toBe(false);
});

// workspaceOnly 注意PermissionManager 中 workspaceOnly 检查为占位注释,
// 实际路径边界由 WorkspaceManager 在执行时校验。
// 此处仅测试 PermissionManager 不会越权放行 denyList/dangerousOp。

// system packages 在 denyList 中 → checkDangerousOperation 直接返回 allowed:false
it("should deny package:install:system without user confirmation", async () => {
  const r = await pm.checkPermission(sid, "package:install:system", "curl");
  expect(r.allowed).toBe(false);
  // 验证:不应出现在待审批队列中(即未调用 requestPermission
});

4.3 表驱动集成测试(复用 spec

新增文件tests/sandbox-integration/matrix-driven.test.ts

import spec from "../../docs/sandbox/sandbox-matrix.spec.json";

const runnable = spec.cases.filter(c =>
  (c.platform === process.platform || c.platform === "*") &&
  c.verdict !== "unsupported"
);

describe.each(runnable)("[$id] $platform/$backend/$mode $operationId", (tc) => {
  it(`verdict: ${tc.verdict}`, async () => {
    const result = await runSandboxedOperation(tc);
    if (tc.verdict === "block") {
      expect(result.exitCode).not.toBe(0);
    } else if (tc.verdict === "allow") {
      expect(result.exitCode).toBe(0);
    }
    // conditional: 由 tc.condition 描述约束,测试结果写入 notes 字段
  });
});

runSandboxedOperation 职责

  1. 根据 tc.backend 构造 SandboxInvoker 实例
  2. 根据 tc.operationId 映射到具体 shell 操作(如 workspace_writeecho test > <workspaceDir>/test.txtnetwork_externalcurl -s --max-time 2 https://example.com
  3. 调用 buildInvocation() 包装命令,spawn 执行,返回 { exitCode, stdout, stderr }
  4. 需维护一个 operationId → shell command 映射表,确保测试操作可复现

4.4 环境变量泄露测试

新增文件tests/sandbox-integration/env-leak.test.ts

ID 路径 步骤 预期
ENV-01 terminal/create 预置 ANTHROPIC_API_KEY=leak_test,沙箱内执行 env 输出不含该键
ENV-02 sandboxed-bash-mcp 同上,经 MCP Bash 路径 若出现则标记高危(当前已知风险:仅删 ELECTRON_RUN_AS_NODE
ENV-03 helper 直调 对比最小 env vs 全量 env 证明 helper 无内置 safeKeys 过滤(文档与实现一致)

4.5 降级可观测性测试

文件tests/sandbox-integration/fallback-observability.test.ts

ID 场景 预期
OBS-01 模拟 backend 不可用(autoFallback 非 manual检查 app.emit("sandbox:unavailable") 是否触发 事件必须触发,并携带 reason
OBS-02 降级后实际 sandbox type 为 none,日志中必须有明确记录 日志可查,不静默
OBS-03 permissive 模式启动时,必须有 warning 日志 warning 可查

4.6 Windows 集成测试WN 系列,来自 sandbox-plan.md §4.2

ID 场景 预期
WN-01 workspace-write + strictcwd 在 workspace 内写文件 成功
WN-02 workspace-write + strictcwd 指向 workspace 外 失败(逃逸判定)
WN-03 read-only 下写 workspace 失败
WN-04 run + permissive--no-write-restricted)创建子进程管道 成功
WN-05 serve 模式孙进程启动 成功
WN-06 network_access=false 下原生 socket 直连外网 应失败(若成功,标记为当前设计限制并写入矩阵 conditional
WN-07 .git denyroot 存在 .git 时写入 .git/index.lock 失败
WN-08 world-writable 目录审计超 200 子目录 仅扫描前 200日志可见
WN-09 workspace-write 退出后 ACL 残留检查 残留可观测并有清理方案

5. CI 门禁(分层)

新增 workflow.github/workflows/sandbox-gates.yml

5.1 PR 必跑

Job 说明
sandbox-unit matrix: ubuntu/macos/windows运行 src/main/services/sandbox/ 单元测试
sandbox-matrix-consistency generate-sandbox-matrix-doc.js 生成后 git diff --exit-code,不一致则失败
sandbox-integration-unix matrix: ubuntu/macos运行 tests/sandbox-integration/

5.2 Release 分支必跑

Job 说明
sandbox-integration-windows runs-on: windows-latest,运行 WN-01~WN-09 可自动化子集
sandbox-security-report 汇总所有 job 产物,生成 sandbox-report.json artifact

5.3 门禁阈值

  • system_path_write / outside_workspace_write critical 用例100% block
  • env_leak_count == 0ENV-01~ENV-03
  • degrade_to_none_count > 0 时必须有对应 sandbox:unavailable 事件日志

6. 交付物一览

文件 类型 说明
docs/sandbox/sandbox-matrix.spec.json 规范(手工维护) 沙箱层单一真源
docs/sandbox/permission-matrix.spec.json 规范(手工维护) 命令权限层单一真源
docs/sandbox/sandbox-matrix.generated.md 生成CI 检查) 从 spec 生成,人类可读
crates/agent-electron-client/scripts/generate-sandbox-matrix-doc.js 工具 spec.json → .md 单向转换
crates/agent-electron-client/scripts/generate-sandbox-report.js 工具 测试结果 → sandbox-report.json
src/main/services/sandbox/PermissionManager.test.ts 测试(新增) 命令权限层专项
tests/sandbox-integration/matrix-driven.test.ts 测试(新增) 从 spec 驱动的沙箱层集成测试
tests/sandbox-integration/env-leak.test.ts 测试(新增) 环境变量泄露测试
tests/sandbox-integration/fallback-observability.test.ts 测试(新增) 降级可观测性测试OBS-01~03
.github/workflows/sandbox-gates.yml CI新增 分层门禁 workflow

7. 执行顺序macOS → Windows → Linux

Phase 1macOSWeek 1~2

  1. Week 1:手工编写 sandbox-matrix.spec.jsonmacOS seatbelt 为第一批 verdictsudo conditional 条目)
  2. Week 1:新增 PermissionManager.test.tsallowed 字段断言、word-boundary 回归;平台无关,最早可并行)
  3. Week 1:新增 fallback-observability.test.tsOBS-01~03平台无关最早可并行
  4. Week 2:新增 env-leak.test.ts ENV-01~ENV-02terminal/create + sandboxed-bash-mcp
  5. Week 2:实现 matrix-driven.test.ts(先只运行 macOS 部分);打通 macOS seatbelt 集成门禁

Phase 2WindowsWeek 3

  1. 补写 sandbox-matrix.spec.json Windows 部分conditionalcwd 约束、网络 best-effort、sandboxed-bash-mcp --no-write-restricted
  2. 实现 WN-01~WN-05基础行为workspace write、read-only、run/serve
  3. ENV-03 + WN-06~WN-09安全回归网络旁路、ACL 持久化清理)
  4. 打通 sandbox-integration-windows.ymlrelease 分支门禁)

Phase 3LinuxWeek 4

  1. 修复 linux-bwrap.integration.test.ts callback API 误用(改为 promisify(exec)execa
  2. 补写 sandbox-matrix.spec.json Linux bwrap 部分;扩展 matrix-driven.test.ts
  3. 打通全平台 sandbox-gates.yml;生成 sandbox-report.json

8. 已锁定假设

  1. GUI Agent 沙箱不纳入本计划。
  2. 采用双层模型(沙箱层 + 命令权限层),两层矩阵严格分离,不混入同一 spec 文件。
  3. 交付形式固定为 JSON + Markdown + 自动化测试。
  4. CI 采用分层门禁PR 强制单元测试 + 一致性检查Windows 集成在 release 分支强制。
  5. 矩阵为规范normative文档人工维护生成脚本仅做 spec→md不从实现反向提取。
  6. docker 全平台显式标注 unsupported,不伪装为已支持。
  7. autoFallback=sessionstartup-only 当前等价,矩阵注释标明"预留枚举TBD"。
  8. Windows 的 modestrict/compat/permissivewindowsModeread-only/workspace-write是正交组合矩阵条目需覆盖两个维度。
  9. CONC并发、SOAK长稳、LEAK资源泄漏测试不纳入本计划作为后续独立专项。

9. 与现有文档的关系

  • sandbox-plan.md:保留为"问题发现与决策记录"文档,本计划不替换它。本计划将其 §12 中的 ENV 测试用例和 §13CI 建议)转化为可执行代码与 workflow。§12 中的 CONC并发、SOAK长稳、LEAK资源泄漏测试用例当前不纳入本计划作为后续独立专项跟进。
  • sandbox/TEST-PLAN.mdsandbox/CODE-REVIEW.md:已有内容仍有效,本计划以 spec + 表驱动测试为主干,与之互补。
  • sandbox-matrix.spec.json:作为"当前正确行为"的规范真源,与 sandbox-plan.md 中的问题列表形成问题→修复→验证闭环。

10. Deep Research 发现(实施前需评估)

以下问题在源码审查中发现,需在 Phase 1 实施前决定处置方式(修复 or 标注为已知限制):

10.1 PermissionManager 层

# 问题 严重度 源码位置 建议
DR-1 /etc/hosts 不在 sensitiveEtcPaths 列表中,当前不会被拦截 PermissionManager.ts:651-657 评估是否补入;若不补入则在矩阵中标注为 allow
DR-2 .ssh 检测用 lowerTarget.includes(".ssh"),会误拦含 .ssh 子串的非 SSH 路径(如 not.sshrc PermissionManager.ts:642 改为路径段匹配(如 /.ssh/path.basename 检查)
DR-3 workspaceOnly 在 PermissionManager 中为注释占位,实际由 WorkspaceManager 执行路径边界检查 PermissionManager.ts:203-205 测试需跨模块验证PermissionManager + WorkspaceManager 联合),不能仅测 PermissionManager

10.2 沙箱层

# 问题 严重度 源码位置 建议
DR-4 Linux permissive 模式走独立代码分支,不执行 --unshare-net(即使 networkEnabled=false SandboxInvoker.ts:228-239 在矩阵中标注为 conditional若需修复则在 permissive 分支末尾补入 --unshare-net 判断
DR-5 macOS strict/compat 下 signal 规则为 (allow signal (target self))permissive 为无限制 (allow signal) SandboxInvoker.ts:428,464 在矩阵中记录差异即可
DR-6 Windows serve 模式固定 write_restricted=falsemain.rs:585仅靠 DACL ACE 保护写权限 windows-sandbox-helper/src/main.rs:585 已知设计决策,需在矩阵 conditional 中标注
DR-7 sandboxed-bash-mcp 使用 { ...process.env } 仅删除 ELECTRON_RUN_AS_NODE,所有其他环境变量透传至沙箱 resources/sandboxed-bash-mcp/sandboxed-bash-mcp.mjs:197-202 ENV-02 测试覆盖;长期应补入 safeKeys 白名单过滤
DR-8 Windows helper (env.rs) 无独立 safeKeys 白名单,以 std::env::vars() 全量继承再修改 windows-sandbox-helper/src/main.rs:256, env.rs:100-135 ENV-03 测试覆盖;与 DR-7 一起作为 env 安全专项跟进