6.3 KiB
6.3 KiB
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,供调用方清理
- profile 以
- 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_UNAVAILABLEreject - networkEnabled=false 时 policy JSON network_access=false(JSON 解析验证)
- writable_roots 含 workspace(JSON 解析验证)
- helper 不存在时抛
- 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 allows,workspace writable,no 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 now(exec 允许但 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.txt(tmpfs) |
ALLOWED,但宿主 /tmp 无此文件 |
| 文件删除 | /etc/hostname, /bin/sh |
BLOCKED |
| 网络 | curl(--unshare-net) | BLOCKED |
| 网络 | loopback ping(--unshare-net 下仍有 lo) | ALLOWED(localhost 可用) |
| 提权 | 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 1:allow signal 过于宽泛(macOS)
- 位置:
SandboxInvoker.tsbuildSeatbeltProfile() - 风险:允许向任意可 reach 进程发信号(UNIX 权限仍阻止 PID 1,但若以 root 运行则有风险)
- 修复:改为
(allow signal (target self))
Gap 2:allow process-exec 无限制(macOS)
- 位置:
SandboxInvoker.tsbuildSeatbeltProfile() - 风险:可 exec
/sbin/shutdown、/sbin/reboot等系统命令,依赖权限不足阻止而非沙箱 - 修复:用路径白名单限制可 exec 的目录
Gap 3:--dev-bind /dev /dev 可读写(Linux)
- 位置:
SandboxInvoker.tsbuildBwrap() - 风险:若
--unshare-user-tryfallback 失败(内核不支持),/dev 可写 - 修复:改为最小 device allowlist(
--dev-bind /dev/null、/dev/urandom、/dev/zero、/dev/random)
Gap 4:降级到 none 无用户提示
- 位置:
policy.tsresolveSandboxType() - 风险:用户以为有沙箱保护,实际在无沙箱运行
- 修复:降级时 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 在对应平台运行。