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

22 KiB
Raw Permalink Blame History

沙箱多平台测试方案(源码校准版)

更新日期2026-04-09 审查范围:SandboxInvoker.tspolicy.ts@shared/types/sandbox.tswindows-sandbox-helper/*.rs 审查目标:准确性、完整性、一致性、深度、可操作性、安全性


1. 以源码为准的当前实现

1.1 模式与后端(事实)

  • 沙箱模式:strict | compat | permissive(默认 compat)。
  • 后端:auto | docker | macos-seatbelt | linux-bwrap | windows-sandbox
  • auto 映射:darwin -> macos-seatbeltlinux -> linux-bwrapwin32 -> windows-sandbox
  • dockerSandboxInvoker.buildInvocation() 中仅返回未包装命令并记录 warning进程级未实现

1.2 macOSseatbelt

  • 基线固定是 (deny default)
  • permissive
    • 允许 file-read*
    • 允许 process-exec 的 regex^/usr/bin/^/bin/^/usr/lib/
    • 允许 command 本身literal及其 realpath
    • compat 才会额外并入 startupExecAllowlist
  • permissive(allow file-write*) + (allow process-exec) + (allow signal)
  • permissive 的写权限仅来自 writablePaths(会同时尝试加入 realpath)。
  • 额外固定写白名单:/dev/null/dev/dtracehelper/dev/urandom

1.3 Linuxbwrap

  • strict/compat 都启用:
    • --unshare-user-try --unshare-pid --unshare-uts --unshare-cgroup-try
    • networkEnabled=false 时加 --unshare-net
    • --tmpfs /tmp
    • 仅绑定 /dev/null/dev/urandom/dev/zero
  • strict:只读挂载最小面 + 命令相关目录:
    • 固定目录:/usr /bin /sbin /lib /lib64 /etc /opt /usr/local
    • 额外加入 dirname(command),以及绝对参数对应目录(或目录本身)
  • compat--ro-bind / /
  • permissive--bind / / --dev-bind /dev /dev --proc /proc,且不做 namespace 隔离;networkEnabled 在此模式下不生效。

1.4 Windowsqiming-sandbox-helper

  • 调用格式:qiming-sandbox-helper run|serve --mode --cwd --policy-json -- <cmd>
  • run 默认 write_restricted=truepermissive + run 时会加 --no-write-restricted
  • serve 模式固定 write_restricted=false(为允许孙进程)。
  • workspace-write 且有 writablePaths
    • strict 仅传第一个 root
    • compat/permissive 传全部 roots。
  • helper 内部策略关键点:
    • WorkspaceWrite 才有 writable_rootsnetwork_access 字段。
    • ReadOnly 固定 has_full_network_access=false
    • 网络“禁用”通过环境变量与 denybin stubssh/scp)实现,不是内核级网络隔离。
    • compute_allow_paths() 会在 WorkspaceWrite 下始终放行 command_cwdTEMP/TMP
    • .git deny 仅在被加入的 writable root 下且目录存在时生效。
    • workspace-write 下 ACE 默认持久化(persist_aces=true),不在进程退出时回滚。

1.5 回退策略policy.ts

  • enabled=false => 直接 type=none
  • 后端不可用:
    • autoFallback=manualSANDBOX_UNAVAILABLE
    • 否则统一降级 none,并 app.emit("sandbox:unavailable", ...)
  • 当前实现里 startup-onlysession 没有行为差异(都走统一降级逻辑)。

2. 发现的问题清单(含修改建议)

2.1 critical

  1. Windows 网络隔离被文档描述为“全阻断”,与实现不符
  • 问题helper 仅做环境变量与命令桩劫持,无法强制阻断所有二进制直连网络。
  • 位置原文网络章节Windows
  • 建议修改明确标注为“best-effort 网络抑制”,并新增绕过测试(自带 socket 客户端二进制)。
  1. Windows strict 写范围描述过度乐观,存在 cwd 放行面
  • 问题helper 在 WorkspaceWrite 下总是放行 command_cwd;若上游未约束 cwd,可扩大写面。
  • 位置原文“strict 仅第一个 writablePath 可写”。
  • 建议修改:文档加入前置条件“调用方必须保证 cwd 在工作区内”;新增负例测试。

2.2 major

  1. autoFallback=session 被描述为会话级差异,但源码无差异实现
  • 问题:startup-onlysession 当前都降级为 none
  • 建议修改:文档改为“预留枚举,当前行为一致”;补充后续实现 TODO。
  1. compat 的 startup allowlist 价值被夸大
  • 问题:当前两个调用点仅传入 command 本身,通常不增加额外可执行路径。
  • 建议修改:文档改为“机制已支持,但当前调用链默认未传额外启动链”。
  1. Linux strict 挂载面文档遗漏 /usr/local
  • 问题:源码已包含 /usr/local
  • 建议修改:更正目录清单,并统一所有章节。
  1. Linux permissive 网络行为描述前后冲突
  • 问题:原文一处写“由 networkEnabled 控制”,另一处写“无隔离”;实现是不会 unshare-net
  • 建议修改统一为“permissive 下不做 net namespace 隔离”。
  1. Windows read-only 与 network_access 字段关系未说明
  • 问题:ReadOnly 变体不消费 network_access,实际固定无 full network。
  • 建议修改在文档中写清“read-only 下网络默认受限,不依赖 policy_json 的 network_access 字段”。
  1. Windows ACL 持久化副作用未纳入测试计划
  • 问题:workspace-write 下 ACE 持久化可能造成长期 ACL 污染。
  • 建议修改:新增“重复运行/卸载后 ACL 回收”测试与清理策略。
  1. 现有 Linux 集成测试样例不可直接执行风险高
  • 问题:测试文件内 await exec(...) 用法与 Node API 不匹配,计划中的“可直接执行”不成立。
  • 建议修改:文档明确当前仅 macOS/Linux 部分集成用例存在,且需先修复测试脚本后纳入门禁。

2.3 minor

  1. 术语不一致strict/compact 拼写混用)
  • 建议:统一为 strict/compat/permissive
  1. macOS 设备写白名单漏写 /dev/dtracehelper
  • 建议:补全为 /dev/null/dev/dtracehelper/dev/urandom
  1. 部分“绝对结论”缺少前置条件
  • 建议:在表格增加“前置条件/调用方约束”列(如 cwd 约束、路径存在性)。

3. 需要补充的内容

  1. Windows 真实集成测试(当前仓库缺失)
  • run / serve 两路径;strict/compat/permissive 三模式;read-only/workspace-write 两子模式。
  1. Windows 安全回归专项
  • cwd 越界写入、TEMP/TMP 放行面、.git deny 仅在存在时生效、world-writable 扫描上限200 子目录 + 1 秒)。
  1. 回退可观测性测试
  • 校验 app.emit("sandbox:unavailable") 是否被 UI/日志消费;避免“以为开了沙箱,实际 none”。
  1. 模式一致性断言
  • startup-onlysession 当前等价,应有显式测试防止文档与实现再次漂移。
  1. 调用方约束测试
  • ACP terminal/create 的 cwd 是否被限制在工作区(当前未在 AcpTerminalManager 内做路径校验)。

4. 修订后的测试矩阵(可执行)

4.1 已有且可运行(先行)

  • 单元测试:
    • src/main/services/sandbox/SandboxInvoker.test.ts
    • src/main/services/sandbox/sandboxProcessWrapper.test.ts
    • src/main/services/sandbox/policy.test.ts
  • 集成测试(现有文件):
    • tests/sandbox-integration/macos-seatbelt.integration.test.ts
    • tests/sandbox-integration/linux-bwrap.integration.test.ts

执行命令:

cd crates/agent-electron-client
npm run test:run -- src/main/services/sandbox/SandboxInvoker.test.ts src/main/services/sandbox/sandboxProcessWrapper.test.ts src/main/services/sandbox/policy.test.ts
npm run test:run -- tests/sandbox-integration/macos-seatbelt.integration.test.ts
npm run test:run -- tests/sandbox-integration/linux-bwrap.integration.test.ts

4.2 必补Windows

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 直连外网 应失败(若成功,标记为当前设计限制)
WN-07 .git denyroot 存在 .git 时写入 .git/index.lock 失败
WN-08 world-writable 目录审计超 200 子目录 仅扫描前 200日志可见
WN-09 workspace-write 退出后 ACL 残留检查 残留可观测并有清理方案

5. 安全逃逸路径与当前状态

  1. Windows 网络旁路critical
  • 状态:未彻底解决。
  • 原因:仅 env/proxy/stub 抑制,无内核网络隔离。
  1. Windows cwd 扩写面critical
  • 状态:依赖调用方约束。
  • 原因:compute_allow_paths() 默认允许 command_cwd
  1. Windows ACL 持久化污染major
  • 状态:已存在设计行为。
  • 原因:workspace-write 设置 persist_aces=true
  1. Linux permissive 全盘可写known risk
  • 状态:设计如此,仅用于排障。
  • 控制:禁止作为默认策略,需审计日志。
  1. 回退到 none 的误感知风险major
  • 状态:已有 sandbox:unavailable 事件,但需 UI 与监控闭环。

6. CI 门禁建议(修订)

  1. PR required:三份单元测试必须通过。
  2. platform requiredmacOS + Linux 集成测试通过。
  3. release requiredWindows 集成测试通过后再允许发布。
  4. security required
    • 回退到 none 必须有告警事件;
    • permissive 运行必须记录 warning
    • Windows 网络“best-effort”限制必须在报告中明确声明。

7. 里程碑(更新)

周次 目标
第1周 修正文档与现有测试脚本可执行性(尤其 Linux 集成脚本)
第2周 补齐 Windows helper 集成测试WN-01~WN-05
第3周 完成 Windows 安全回归WN-06~WN-09
第4周 打通 CI 门禁与降级告警可视化

8. 第二轮复审:第一轮修正核验结果(逐项对源码)

结论:第一轮 12 条修正中10 条“准确”2 条“部分准确(需补充限定)”。

ID 第一轮修正 复审结论 源码依据
R1 Windows 网络并非“全阻断” 准确 windows-sandbox-helper/src/env.rsapply_no_network_to_env() 仅设置代理/离线变量与 denybinssh/scp非内核级隔离
R2 strict 写范围受 cwd 影响 准确 acpTerminalManager.tscwd 加入 writablePathsallow.rscompute_allow_paths() 总是放行 command_cwd
R3 autoFallback=sessionstartup-only 当前无行为差异 准确 policy.ts 中 backend 不可用时,非 manual 统一返回 type=none,degraded=true
R4 compat 启动链 allowlist 被高估 ⚠️ 部分准确 机制有效;sandboxProcessWrapper.ts 当前仅传 [originalCommand],但未来可扩展,文档应写“当前调用链价值有限”
R5 Linux strict 漏写 /usr/local 准确 SandboxInvoker.ts strict ro-bind 列表包含 /usr/local
R6 Linux permissive 网络描述冲突 准确 SandboxInvoker.ts permissive 分支不做 --unshare-netnetworkEnabled 在此分支不生效
R7 Windows read-only 与 network_access 关系未说明 准确 policy.rs ReadOnly => has_full_network_access=false,不消费 network_access 字段
R8 ACL 持久化副作用未纳入测试 准确 main.rs persist_aces = is_workspace_writecleanup 仅在 !persist_aces 回滚 ACE
R9 Linux 集成测试可执行性描述偏乐观 准确 linux-bwrap.integration.test.ts 存在 await exec("which bwrap") 等 callback API 误用
R10 strict/compact 术语混用 准确 当前实现统一为 strict/compat/permissive@shared/types/sandbox.ts
R11 macOS 设备白名单漏写 /dev/dtracehelper 准确 SandboxInvoker.ts 固定写白名单含 /dev/dtracehelper
R12 缺少前置条件声明 ⚠️ 部分准确 实现已隐含多项调用方前置约束(如 cwd、路径存在、MCP 进程 env文档需显式化

9. 第二轮新增发现(第一轮遗漏)

9.1 环境变量链路存在“分层过滤不一致”

  • AcpTerminalManagerWindows terminal/create 路径)确实使用 safeKeys 最小白名单构造 env可降低敏感变量泄露面
  • windows-sandbox-helperSandboxContext::setup()std::env::vars() 作为基底,env.rs 内没有独立的 safeKeys 白名单逻辑。
  • sandboxed-bash-mcp.mjs 执行 helper 时使用 env = { ...process.env },仅删除 ELECTRON_RUN_AS_NODE,因此若 MCP 进程继承到敏感变量,可能继续传入 helper/子命令。

结论需要把“safeKeys 白名单”在文档中明确为“仅 terminal/create 路径有效,不是 helper 全局机制”,并新增泄露测试(见第 12 节)。

9.2 sandboxed-bash-mcp 强制 --no-write-restricted

  • MCP 脚本构造 helper 参数时固定包含 run --no-write-restricted
  • 这意味着该路径始终依赖 DACL ACE 控制写权限,不使用 WRITE_RESTRICTED token 的二次约束。

结论:文档应把该行为从“可选/模式相关”改为“当前实现固定开启”。

9.3 world-writable 审计是“尽力而为”,且不会 fail-closed

  • audit.rs 只扫描 cwd 直接子目录,最多 200 个,最长 1 秒。
  • 发现风险目录后仅尝试加 deny ACE失败仅记日志不阻断运行。
  • 对工作区 root 下目录(starts_with(workspace_roots))会跳过 deny。

结论:文档需明确这是补偿性审计,不是强制安全边界。


10. 深度审查补充(指定模块)

10.1 AcpTerminalManagerWindows terminal/create 路径)

  • Windows + helper 可用时:terminal/create 通过 SandboxInvoker.buildInvocation(...subcommand=run)qiming-sandbox-helper runparseJson=true
  • writablePaths 传参为 [workspaceRoot, cwd]strict 下 helper policy 仅保留首个 root但 helper 内仍会允许 command_cwd)。
  • env 构建:
    • 沙箱路径:仅取 safeKeys
    • 非沙箱路径:透传宿主 env
    • params.env 直接覆盖(无二次过滤)。
  • 直接执行分支macOS/Linux 或未启用 helper在 Windows 下会 shell: truehelper 分支 shell: false

10.2 sandboxed-bash-mcpWindows 专属 MCP 注入)

  • 仅在 engine=claude-codewindows-sandbox 启用时注入,且通过 _meta 禁用内置 Bash。
  • MCP 工具名仍为 Bash,实际命令固定走 helper
    • run --no-write-restricted --mode <...> --cwd <process.cwd()> --policy-json <...> -- <bash/powershell> -c/-Command <user command>
  • shell 优先级:
    • 优先 Git Bash支持 bash 语法);
    • 否则回退 PowerShell。
  • 该路径的 env 继承策略与 terminal/create 不同(见 9.1)。

10.3 env.rs环境变量处理

  • 当前职责是网络抑制、分页器设置、/dev/null 归一化,不负责敏感键白名单过滤。
  • apply_no_network_to_env() 会注入代理与离线相关变量,并 prepend denybinssh/scp stub
  • 实际安全语义是“网络访问抑制 + 常见命令劫持”,不是“环境最小化”。

10.4 audit.rsworld-writable 扫描)

  • 扫描范围:仅 cwd 下一级目录,忽略 symlink/非目录。
  • 资源上限:MAX_CWD_CHILDREN=200AUDIT_TIME_LIMIT_SECS=1
  • 判定:检查 DACL 中是否存在 world SID 的 write allow ACE。
  • 处置:尝试对 capability SID 添加 deny write ACE失败仅日志流程继续。

10.5 acl.rsDACL/ACE 细节)

  • add_allow_ace:为 capability SID 授予 FILE_GENERIC_READ|WRITE|EXECUTE,继承到子对象。
  • add_deny_write_acedeny mask 包含 FILE_GENERIC_WRITEFILE_WRITE_* 细项。
  • revoke_acecleanup 阶段用 REVOKE_ACCESS 回收(仅 persist_aces=false 时执行)。
  • allow_null_device:对 \\.\NUL 追加 allow ACE避免 null 设备访问异常。

10.6 token.rsRestricted Token

  • 基础标志:DISABLE_MAX_PRIVILEGE | LUA_TOKEN
  • write_restricted=true 时额外加 WRITE_RESTRICTED,并附加 3 个 restricting SIDs
    • capability SID
    • 当前 logon SID
    • everyone SID
  • write_restricted=falseserve 或 run+--no-write-restricted)时不加 restricting SIDs主要依赖 DACL。
  • 创建后仅重新启用 SeChangeNotifyPrivilege

11. 与既有文档发现的纳入情况

11.1 sandbox/TEST-PLAN.md 纳入状态

状态 备注
Gap-1~Gap-4signal/exec/dev-bind/降级告警) 已纳入 第 1/2/6 节已覆盖
macOS/Linux 集成思路 部分纳入 已引用测试文件,但需补“当前脚本可执行性风险”
Windows 集成缺口 已纳入 第 4 节 WN-01~WN-09
并发/长稳/泄漏 未充分纳入 本轮第 12 节补全

11.2 sandbox/CODE-REVIEW.md 纳入状态

  • 该文档多数问题属于 DockerSandbox/PermissionManager/WorkspaceManager 通用模块,不全是“多平台 sandbox-plan”范围。
  • 与本计划直接相关且应跟踪的项:
    • 降级可观测性(已纳入)
    • 测试覆盖不足(已纳入)
    • 安全检测可绕过类问题(应在独立 PermissionManager 安全计划跟进,不并入本计划的门禁结论)

结论:CODE-REVIEW 需在文档中标注“范围交集清单”,避免误解为“全部已在本计划闭环”。


12. 新增测试用例(第二轮补充)

12.1 环境变量泄露测试

ID 路径 步骤 预期
ENV-01 terminal/create 预置宿主 ANTHROPIC_API_KEY=leak_test,在沙箱内执行 env 输出中不应出现该键(除非通过 params.env 显式注入)
ENV-02 sandboxed-bash-mcp 同样预置敏感键,经 MCP Bash 执行 set/env 若出现敏感键,标记高危并推动 MCP 路径加白名单过滤
ENV-03 helper run 直调 传入最小 env 与全量 env 对比 证明 helper 无内置 safeKeys 过滤(文档与实现一致)

12.2 并发沙箱实例测试

ID 场景 预期
CONC-01 50 个并发 terminal/create(上限) 全部可创建并可回收
CONC-02 第 51 个 terminal/create 返回“Terminal limit reached”错误
CONC-03 并发 run + serve 混合 无死锁;退出码与输出互不串扰

12.3 长时间运行稳定性

ID 场景 指标/阈值
SOAK-01 单实例 serve 连续 24h 无异常退出;内存增长斜率可控(例如 < 5%/h
SOAK-02 周期性 run(每分钟一次,持续 12h 成功率 >= 99.9%,无句柄持续上涨

12.4 资源泄漏测试

ID 场景 预期
LEAK-01 批量创建/释放 terminal 1000 次 无孤儿进程、无句柄持续累积
LEAK-02 workspace-write 退出后 ACL 检查 记录持久 ACE 残留;清理脚本可回收
LEAK-03 seatbelt profile 清理 sandboxProcessWrapper cleanup 后临时 .sb 文件不存在

13. CI/CD 细化GitHub Actions 可落地模板)

13.1 Workflow 建议

  1. sandbox-unit.yml
    • 触发:pull_requestpush 到主干
    • 矩阵:ubuntu-latest, macos-latest, windows-latest
    • 运行:SandboxInvoker/policy/sandboxProcessWrapper 单元测试
  2. sandbox-integration-unix.yml
    • 触发:pull_request
    • 矩阵:ubuntu-latest, macos-latest
    • 运行:tests/sandbox-integration/*(先修复 Linux 脚本 API 误用)
  3. sandbox-integration-windows.yml
    • 触发:workflow_dispatch + release/* 分支必跑
    • 运行WN/ENV/CONC/SOAK/LEAK 中可自动化子集
  4. sandbox-security-report.yml
    • 汇总各 job 产物,生成单一 sandbox-report.json + Markdown 摘要并上传 artifact。

13.2 报告格式与指标

  • 建议统一 JSON schema
    • platform, backend, mode, case_id, status, duration_ms, exit_code, notes
  • 关键指标:
    • pass_rate(按平台/后端/模式分组)
    • degrade_to_none_count
    • policy_violation_count(如 strict 下越权写成功)
    • env_leak_count
    • resource_leak_signalshandle/process/tempfile/acl
  • PR 门禁阈值建议:
    • critical 用例 100% 通过
    • major 用例 >= 99%
    • env_leak_count == 0
    • degrade_to_none_count 必须附带告警记录与原因

14. 迁移与兼容性补充

14.1 旧版本策略迁移

  • 已有兼容:policy.ts 支持 windows.sandbox.mode -> windowsMode 映射。
  • 建议新增迁移规则:
    1. 缺失 autoFallback 时默认补 startup-only
    2. 缺失 mode 时默认补 compat
    3. 旧值非法时回退到 DEFAULT_SANDBOX_POLICY 并记录一次迁移日志。

14.2 跨版本兼容要求

  1. 新增字段必须向后兼容unknown 字段忽略,不影响旧客户端读取)。
  2. 禁止静默改变默认安全语义:
    • 例如 windowsMode 默认值变更必须伴随版本门禁与迁移提示。
  3. SandboxAutoFallback 新枚举落地前,需先保证旧版本按“最安全可运行”路径降级(当前即 none + degraded=true + 事件告警)。
  4. helper 与主程序版本协商:
    • 建议在 helper 启动时输出 version/capabilities,主程序按能力选择参数,避免跨版本参数不兼容。

14.3 升级回滚策略

  • 升级前备份 sandbox_policySQLite
  • 新版本启动首轮执行策略 normalize + migrate并写入 policy_migration_audit 日志。
  • 回滚时保留可识别字段,删除新版本专有字段(或降级到默认策略),确保旧版本不崩溃。