WindowsSandbox测试与限制
Windows sandbox 的测试重点不是“命令返回 0”。当前测试覆盖 backend 选择、managed proxy 拒绝、restricted-token 进程、pipe/TTY、interrupt、cancellation、descendant 生命周期、setup roots 和 wrapper 参数。不同终止原因的断言也不同:正常 capture 结束允许 descendant 继续,取消或显式 terminate 则要求 Job Object 回收 descendants。
本文承接WindowsSandbox架构和Windows可读路径授权,面向理解平台集成测试、Job Object、ConPTY 和依赖探测的读者。范围是测试输入、关键断言、skip 条件和未证明边界;不重复 backend 实现细节。源码测试只能证明在满足 Windows、PowerShell、权限和 helper 条件的环境中观察到的行为。
1. Backend矩阵
restricted-token request 携带 proxy_enforced=true 时测试断言返回错误,因为 managed networking 只允许 elevated backend。该测试证明选择器 fail closed,不证明 elevated backend 已成功配置 firewall。
源码位置:codex-rs/windows-sandbox-rs/src/unified_exec/tests.rs :: proxy enforced restricted-token test
let error = spawn_windows_sandbox_session_for_level(WindowsSandboxSessionRequest {
windows_sandbox_level: WindowsSandboxLevel::RestrictedToken,
proxy_enforced: true,
network_proxy_restricting_sid: None,
..request
}).await.expect_err("restricted token should reject managed proxy");2. 正常退出
legacy_capture_emits_output_and_preserves_descendant_after_normal_exit 启动 parent 与 descendant,等待 parent capture 结束后再释放 descendant,并断言 descendant 仍存活。正常 top-level 退出不会总是 kill job 中所有 descendants。
源码位置:codex-rs/windows-sandbox-rs/src/unified_exec/tests.rs :: legacy_capture_emits_output_and_preserves_descendant_after_normal_exit
assert_eq!(result.exit_code, 0, "stdout={stdout:?} stderr={stderr:?}");
assert!(stdout.contains("LEGACY-CAPTURE-DIRECT"));
assert!(
wait_for_path(&survival_marker, Duration::from_secs(10)),
"sandbox descendant did not survive normal capture exit"
);
wait_for_process_exit(&descendant_process, Duration::from_secs(10))
.expect("sandbox descendant did not exit after release");源码位置:codex-rs/windows-sandbox-rs/src/unified_exec/tests.rs :: legacy_capture_emits_output_and_preserves_descendant_after_normal_exit
3. 取消回收
legacy_capture_cancellation_terminates_descendants_without_timeout 在 descendant 启动后触发 cancellation token,捕获 descendant handle,并等待其退出;survival marker 必须不存在。这里证明 cancellation 路径终止进程树,而不是只关闭 stdout reader。
源码位置:codex-rs/windows-sandbox-rs/src/unified_exec/tests.rs :: legacy_capture_cancellation_terminates_descendants_without_timeout
assert!(started_at.elapsed() < Duration::from_secs(10));
assert!(!result.timed_out, "cancellation should not be reported as a timeout");
assert_ne!(result.exit_code, 0);
wait_for_process_exit(&descendant_process, Duration::from_secs(10))
.expect("sandbox descendant did not exit after cancellation");
assert!(!descendant_marker.exists(), "sandbox descendant survived cancellation");源码位置:codex-rs/windows-sandbox-rs/src/unified_exec/tests.rs :: legacy_capture_cancellation_terminates_descendants_without_timeout
4. TTY差异
TTY 测试使用 PowerShell 7 和 ConPTY,分别验证 terminate 与 preserve descendant 两种生命周期。若 pwsh 未安装,测试打印 skip 并返回;因此测试通过数量不能被解释成所有 TTY 场景已执行。
源码位置:codex-rs/windows-sandbox-rs/src/unified_exec/tests.rs :: legacy_tty_job_terminates_and_preserves_descendants
let Some(pwsh) = find_pwsh() else {
eprintln!("skipping sandbox ConPTY lifecycle test: PowerShell 7 is not installed");
return;
};等待辅助函数把退出、超时、Win32 API 失败和意外返回值分开,避免把“没有观察到退出”直接归因于 sandbox。
源码位置:codex-rs/windows-sandbox-rs/src/unified_exec/tests.rs :: wait_for_process_exit
match WaitForSingleObject(process.as_raw_handle() as _, timeout_ms) {
WAIT_OBJECT_0 => Ok(()),
WAIT_TIMEOUT => Err(std::io::Error::new(
std::io::ErrorKind::TimedOut,
"timed out waiting for process to exit",
)),
WAIT_FAILED => Err(std::io::Error::last_os_error()),
result => Err(std::io::Error::other(format!("unexpected process wait result: {result}"))),
}5. Setup与路径
setup tests 验证 helper bin dir 总在 read roots、platform defaults 的开关、write root 从 read roots 移除、deny-read lexical/canonical 路径保留,以及 Codex sandbox/control directories 不被授予宽泛读写权限。
源码位置:codex-rs/windows-sandbox-rs/src/setup.rs :: tests
6. 环境依赖
部分测试依赖 Windows API、PowerShell 7、ConPTY、可创建用户/token、ACL 权限和 helper binaries。非 Windows 平台无法动态运行;Windows CI 若缺少 pwsh 也会 skip。源码分支仍可静态复核,但不能写成跨平台实测通过。
7. 限制矩阵
| 范围 | 已证明 | 未证明 |
|---|---|---|
| Backend选择 | proxy enforced 拒绝 legacy | elevated firewall 实际生效 |
| Capture | stdout/stderr 与正常退出 | 任意程序输出行为 |
| Cancellation | Job 终止 descendants | 外部脱离 Job 的进程 |
| TTY | ConPTY 特定生命周期 | 无 pwsh 时的动态行为 |
| Paths | roots/deny aliases 组装 | ACL 与第三方 ACE 最终合并 |
| Private desktop | 参数进入 backend | 桌面隔离的完整攻击面 |
8. 可执行验证
输入一:restricted token + managed proxy,断言错误。输入二:normal capture root exit,断言 descendant 保留。输入三:cancellation,断言 descendant 在 10 秒内退出且 survival marker 不存在。输入四:TTY terminate/preserve,断言 Job 行为符合调用模式;若 pwsh 缺失则明确 skip。
这些测试能够证明实现中的进程与参数生命周期;不能证明所有 Windows 版本、企业安全软件、UAC 策略或第三方 ACL 下行为一致,也不能把 skip 当成 pass。
源码位置:
codex-rs/windows-sandbox-rs/src/unified_exec/tests.rscodex-rs/windows-sandbox-rs/src/setup.rs :: testscodex-rs/windows-sandbox-rs/src/wrapper_tests.rs
cd codex-rs
cargo test -p codex-windows-sandbox -- --test-threads=19. 阅读闭环
建议按 backend 拒绝 → normal exit → cancellation → TTY → setup/wrapper tests 阅读。读完后应能解释:为什么正常退出与取消对 descendants 的要求不同;为什么 skip 不等于通过;哪些测试只验证 payload;以及 Windows sandbox 的哪些系统效果仍需要真实平台集成环境。
下一篇将进入 NetworkPolicy 决策模型。
