Files
qiming/qimingclaw/docs/sandbox/TEST-PLAN.md

6.3 KiB
Raw Blame History

Sandbox 测试验证方案

创建时间2026/04/08 状态:已计划,待实施

Context

目前三平台沙箱macOS seatbelt / Linux bwrap / Windows helper没有任何测试文件覆盖无法验证危险操作是否被正确拦截。需要新增

  • 单元测试(无需真实沙箱二进制)
  • 跨平台集成测试
  • 并修复已发现的 4 个安全 Gap

关键文件

文件 作用
src/main/services/sandbox/SandboxInvoker.ts seatbelt profile / bwrap args 生成
src/main/services/sandbox/sandboxProcessWrapper.ts ACP 进程包装
src/main/services/sandbox/policy.ts resolveSandboxType fallback
vitest.config.ts 测试配置(已包含 tests/**

实现计划

1. 单元测试(src/main/services/sandbox/

SandboxInvoker.test.ts

  • mock fs/promises.writeFile,捕获 profile 内容
  • macOS
    • profile 以 (version 1) (deny default) 开头
    • (allow network*) 当 networkEnabled=true
    • seatbeltProfilePath 返回值非 undefined供调用方清理
  • Linux
    • --ro-bind / / 存在于 bwrap args
    • --tmpfs /tmp 存在
    • --dev-bind /dev /dev 存在(记录 rw gap
    • networkEnabled=false → --unshare-net 存在
    • networkEnabled=true → --unshare-net 不存在
    • 每个 writablePath 生成 --bind <p> <p>,精确索引
  • Windows
    • helper 不存在时抛 SANDBOX_UNAVAILABLE reject
    • networkEnabled=false 时 policy JSON network_access=falseJSON 解析验证)
    • writable_roots 含 workspaceJSON 解析验证)
  • none:原样返回 command/args
  • docker:返回原始命令并 log.warn

sandboxProcessWrapper.test.ts

  • 测试 buildSandboxedSpawnArgs() 对各平台的包装结果
  • enabled=false → 原样返回
  • docker backend → 透传 + warn

policy.test.ts

  • mock fs.existsSync 全返回 false + mock SQLite
  • enabled=false → type=none, degraded=false
  • 后端不可用 → degraded=true, reason 非空
  • fallback 结果永远是 none不会是其他可用类型

2. 集成测试(tests/sandbox-integration/

shared-integration-utils.ts

runInSeatbelt(profileContent: string, shellCommand: string): Promise<{exitCode, stdout, stderr, timedOut}>
runInBwrap(bwrapArgs: string[]): Promise<{exitCode, stdout, stderr, timedOut}>
expectBlocked(result): void
expectAllowed(result): void

macos-seatbelt.integration.test.ts

describe.skipIf(process.platform !== 'darwin' || !existsSync('/usr/bin/sandbox-exec'))

使用与生产完全一致的 profile 格式((deny default) + explicit allowsworkspace writableno network

类别 命令 期望
文件写入 echo ... >> /etc/hosts BLOCKED
文件写入 echo ... >> /etc/passwd BLOCKED
文件写入 touch /usr/bin/evil-binary BLOCKED
文件写入 touch ~/Documents/exfil.txt BLOCKED
文件写入 touch /tmp/attacker-file.txt/tmp 不在 writablePaths BLOCKED
文件写入 workspace 内写入 ALLOWED
文件删除 rm /etc/resolv.conf BLOCKED
文件删除 rm /usr/bin/ls BLOCKED
提权 sudo id BLOCKED
系统命令 /sbin/shutdown -h nowexec 允许但 root 权限不足) 输出 Permission 字样
网络 curl http://example.com(无 allow network* BLOCKED

(allow network*) 的 profile 下 curl 应 ALLOWED。

linux-bwrap.integration.test.ts

describe.skipIf(process.platform !== 'linux')

测试场景(含 --unshare-net--tmpfs /tmp、workspace bind

类别 命令 期望
文件写入 /etc/hosts, /etc/passwd, /usr/bin/evil, /home/exfil.txt BLOCKED
文件写入 workspace 内 ALLOWED
文件写入 /tmp/sandbox-tmp.txttmpfs ALLOWED但宿主 /tmp 无此文件
文件删除 /etc/hostname, /bin/sh BLOCKED
网络 curl--unshare-net BLOCKED
网络 loopback ping--unshare-net 下仍有 lo ALLOWEDlocalhost 可用)
提权 sudo id BLOCKED
系统命令 reboot, halt BLOCKED
/dev gap mknod /dev/test-device BLOCKED
/dev gap echo evil > /dev/sda BLOCKED
PID 命名空间 ps aux | wc -l < 10 行(与宿主隔离)

3. 修复 4 个安全 Gap

Gap 1allow signal 过于宽泛macOS

  • 位置SandboxInvoker.ts buildSeatbeltProfile()
  • 风险:允许向任意可 reach 进程发信号UNIX 权限仍阻止 PID 1但若以 root 运行则有风险)
  • 修复:改为 (allow signal (target self))

Gap 2allow process-exec 无限制macOS

  • 位置SandboxInvoker.ts buildSeatbeltProfile()
  • 风险:可 exec /sbin/shutdown/sbin/reboot 等系统命令,依赖权限不足阻止而非沙箱
  • 修复:用路径白名单限制可 exec 的目录

Gap 3--dev-bind /dev /dev 可读写Linux

  • 位置SandboxInvoker.ts buildBwrap()
  • 风险:若 --unshare-user-try fallback 失败(内核不支持),/dev 可写
  • 修复:改为最小 device allowlist--dev-bind /dev/null/dev/urandom/dev/zero/dev/random

Gap 4降级到 none 无用户提示

  • 位置policy.ts resolveSandboxType()
  • 风险:用户以为有沙箱保护,实际在无沙箱运行
  • 修复:降级时 emit sandbox:unavailable 事件,日志级别从 debug 改为 warn

文件清单(新增)

src/main/services/sandbox/
  SandboxInvoker.test.ts          # 新增
  sandboxProcessWrapper.test.ts   # 新增
  policy.test.ts                  # 新增

tests/sandbox-integration/
  shared-integration-utils.ts               # 新增
  macos-seatbelt.integration.test.ts         # 新增
  linux-bwrap.integration.test.ts            # 新增

运行方式

cd crates/agent-electron-client

# 单元测试(全平台)
npm run test:run -- src/main/services/sandbox/

# macOS 集成(在 macOS 机器上)
npm run test:run -- tests/sandbox-integration/macos-seatbelt.integration.test.ts

# Linux 集成(在 Linux 机器上)
npm run test:run -- tests/sandbox-integration/linux-bwrap.integration.test.ts

CI 建议

package.json 增加集成测试的 platform-specific 配置,确保 CI 在对应平台运行。