From 7c0c0574bd2a33bbe2edbe60725c99568f331709 Mon Sep 17 00:00:00 2001 From: coso Date: Wed, 26 Aug 2026 21:02:40 +0800 Subject: [PATCH] test(sandbox): align Windows evidence workspace --- .../codex-desktop-complete-alignment-plan.md | 3 +- .../tests/windows_restricted_execution.rs | 35 ++++++++++++------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/internal/exec-plans/codex-desktop-complete-alignment-plan.md b/internal/exec-plans/codex-desktop-complete-alignment-plan.md index 7d12db253..8d16d46f7 100644 --- a/internal/exec-plans/codex-desktop-complete-alignment-plan.md +++ b/internal/exec-plans/codex-desktop-complete-alignment-plan.md @@ -5,7 +5,7 @@ > upstream: `/Users/coso/Documents/dev/rust/codex`(当前观测 HEAD:`99660ab3c7b861c916e467581fa9b8723504d66b`) > checklist progress: `44/46 (95.7%)`;这是未加权任务数,不等于交付完成度,剩余项包含 Windows 平台安全、live/verifier 与正式分发硬门槛。 -> 当前主目标(2026-08-26):关闭 C-3 Windows restricted execution 的真机证据。Codex current 的 per-user Windows Firewall 非 loopback/loopback 出站阻断、offline/online sandbox identity、`LimeSandboxUsers` 普通 token ACL、短生命周期 capability SID restricted ACL、clean-runner setup/read-back、原始 DACL rollback、sandbox-account command runner 与七项 evidence collector 已进入唯一 current owner。真机已证明 runner transport/MSVC 编译、内置 `Users` setup/read-back、默认平台/profile RX ACL setup/read-back、`%TEMP%/%TMP%` workspace-write ACL lease 和 private desktop 路径可编译执行;最新矩阵仍为 `3/7`,增强 snapshot 已将四项失败统一定位为 cwd `UnauthorizedAccessException`。exact-root 修复未改变结果后,当前根因收敛为逻辑终态先于 runner reaper/ACL rollback:上一命令的旧 DACL snapshot 可覆盖下一命令刚安装的 ACE。当前修复为重叠 exact path 共享原始 snapshot 与 lease 引用计数,只在最后一个使用者退出时恢复;在七项通过前保持 `Planned/enforced=false`。 +> 当前主目标(2026-08-26):关闭 C-3 Windows restricted execution 的真机证据。Codex current 的 per-user Windows Firewall 非 loopback/loopback 出站阻断、offline/online sandbox identity、`LimeSandboxUsers` 普通 token ACL、短生命周期 capability SID restricted ACL、clean-runner setup/read-back、原始 DACL rollback、sandbox-account command runner 与七项 evidence collector 已进入唯一 current owner。真机已证明 runner transport/MSVC 编译、内置 `Users` setup/read-back、默认平台/profile RX ACL setup/read-back、`%TEMP%/%TMP%` workspace-write ACL lease 和 private desktop 路径可编译执行;最新矩阵仍为 `3/7`。重叠 ACL lease registry 已证明原生 `cmd.exe` 可连续写 exact workspace,但没有关闭 PowerShell 对私有 runner `%LOCALAPPDATA%\\Temp` cwd 的 provider 初始化拒绝。当前证据 fixture 改为与 Codex 一致的仓库工作树内隔离 cwd,系统 Temp 只作为 child `%TEMP%/%TMP%` 特殊写根;在七项通过前保持 `Planned/enforced=false`。 > 参考原则:`/Users/coso/Documents/dev/rust/codex` 是协议、Thread/Turn/Item、runtime lifecycle、tool/approval 和 GUI 语义的直接实现参考源;Lime 只在产品范围、provider 和桌面宿主边界做显式适配,不以旧矩阵或同名 legacy API 代替对齐。 > 目标:把 Lime 的产品主链、协议字段、GUI 工作区、执行安全和真实 Desktop 证据推进到当前 Codex Desktop 语义;不恢复已退役 runtime、v0 或 `agentSession/*` 兼容主链。 @@ -400,6 +400,7 @@ Renderer typed gateway - 2026-08-26:Windows Quality run `32965197227`(SHA `07b04bfcb`,artifact `9605628837`)已通过 elevated setup 和 MSVC 编译,schema v3 七项矩阵完整执行仍为 `3/7`:ConPTY stdin/resize/combined output、bounded large output 和 Everyone-write audit 通过,workspace write、offline account/network block、allowlisted stdin 与 terminate/job cleanup 失败。该结果排除 private desktop 是四项失败的单一根因,但不证明该安全对齐无需保留。已收紧四个失败断言:workspace 在 child 提前终态时立即打印 snapshot,offline/stdin/large-output 打印终态 `retained_output`,terminate 在发送控制前记录早退状态;不新增矩阵项、不放宽 token/ACL/firewall。本机 `npm run test:rust:related -- lime-rs/crates/tool-runtime/tests/windows_restricted_execution.rs` 退出码 `0`,反向依赖链与 `tool-runtime 373/373` 通过;Windows-only 断言编译/执行待新的 MSVC run。完成度继续 `44/46 (95.7%)`,readiness 继续 `Planned/enforced=false`。 - 2026-08-26:Windows Quality run `32966584306`(SHA `63e4de484`)的增强 schema v3 证据仍为 `3/7`,但已把四个失败的首个共同错误定位为 PowerShell 访问 cwd 时抛出 `UnauthorizedAccessException`;stdin 与 terminate 并未进入各自控制边界。对照 Codex `effective_write_roots_for_permissions` 只做 exact dedup 后确认,Lime 在 `9af4c627a` 引入的祖先折叠会在 cwd 位于 `%TEMP%` 时删掉 cwd 本身;向 Temp 祖先新增可继承 ACE 不能代替对已存在/受保护子目录的显式授权。当前修复保留 cwd 和 Temp 两个 exact root,并在任何 ACL 变更前捕获全部原始 DACL,以避免嵌套 root snapshot 被早先授权污染。`npm run test:rust:related -- lime-rs/crates/tool-runtime/src/execution_process/windows_acl.rs lime-rs/crates/tool-runtime/tests/windows_restricted_execution.rs` 退出码 `0`,反向依赖链与 `tool-runtime 373/373` 通过;rustfmt 与 `git diff --check` 通过。该修复不改变 token、firewall、deny carveout 或 readiness,Windows-only 单测与七项矩阵仍待 MSVC 验证。 - 2026-08-26:Windows Quality run `32967946457`(SHA `e6c658aaa`,artifact `9606571669`)通过 elevated setup 和 MSVC 编译,schema v3 仍为 `3/7`;四个失败继续由 PowerShell cmdlet 报 cwd `UnauthorizedAccessException`,证明 exact-root 保留不是充分修复。继续检查 current supervisor 后确认,`RunnerMessage::Exit` 会先完成用户可见终态,持有 ACL lease 的 runner reaper 随后才等待进程并 rollback;下一命令可在同一路径安装新 ACE 后,被上一命令的旧 snapshot 恢复覆盖,矩阵从首个 ConPTY/纯输出通过到后续 cwd 操作连续失败的顺序与该竞态一致。当前修复以进程内 registry 为 exact path 共享首个原始 DACL snapshot 和活跃 lease 引用计数,重叠执行只在最后一个 lease 退出时恢复;workspace 必需项新增连续 restricted `cmd.exe` native write -> PowerShell write 回归,并提前记录 active SDDL。`npm run test:rust:related -- lime-rs/crates/tool-runtime/src/execution_process/windows_acl.rs lime-rs/crates/tool-runtime/tests/windows_restricted_execution.rs` 退出码 `0`,反向依赖链与 `tool-runtime 373/373` 通过;Windows-only registry 与连续执行回归仍待 MSVC。readiness 继续保持 `Planned/enforced=false`。 +- 2026-08-26:Windows Quality run `32970595099`(SHA `844a2316a`,job `98183292205`,artifact `9607584715`)setup/MSVC 通过,schema v3 仍为 `3/7`。workspace 新增的连续 restricted `cmd.exe` 原生写入已成功,active SDDL 同时包含 `LimeSandboxUsers` 与短期 capability SID,说明 exact workspace write 与共享 lease 已实际生效;该 case 随后被“group SID 必须相对 baseline 新增”的诊断断言提前截断。stdin/offline/terminate 仍在 PowerShell cmdlet 初始化 cwd provider 时对 `C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\.tmp*` 抛 `UnauthorizedAccessException`。对照 Codex current `unified_exec` tests 后确认,其 sandbox command cwd 固定为仓库工作树,系统 Temp 仅承接 sandbox home;Lime evidence 把另一用户的私有 `%LOCALAPPDATA%\\Temp` 子目录同时当成产品 workspace,混入了宿主 profile ACL 边界。当前回归改为仓库工作树内隔离 fixture,并把普通组断言改为 active DACL 绝对包含、仅 capability 要求相对 baseline 新增;不放宽生产 ACL、token、firewall 或 readiness。`npm run test:rust:related -- lime-rs/crates/tool-runtime/tests/windows_restricted_execution.rs` 退出码 `0`,反向依赖链与 `tool-runtime 373/373` 通过,rustfmt 与 `git diff --check` 通过;Windows-only fixture 仍待 MSVC。 ## 7. 阻塞与避让 diff --git a/lime-rs/crates/tool-runtime/tests/windows_restricted_execution.rs b/lime-rs/crates/tool-runtime/tests/windows_restricted_execution.rs index 706d4d732..ebd7be705 100644 --- a/lime-rs/crates/tool-runtime/tests/windows_restricted_execution.rs +++ b/lime-rs/crates/tool-runtime/tests/windows_restricted_execution.rs @@ -30,6 +30,14 @@ fn powershell_script(script: &str) -> Vec { ] } +fn workspace_fixture() -> tempfile::TempDir { + let repository = std::env::current_dir().expect("repository working directory"); + tempfile::Builder::new() + .prefix(".windows-restricted-") + .tempdir_in(repository) + .expect("repository-local fixture root") +} + fn restricted_request(root: &Path, command: Vec) -> LocalExecutionRequest { let mut request = LocalExecutionRequest::new( format!("windows-restricted-{}", uuid::Uuid::new_v4()), @@ -173,7 +181,7 @@ async fn wait_for_acl_restore(path: &Path, expected: &str) { #[tokio::test] async fn workspace_write_allows_workspace_and_denies_metadata_and_external_paths() { - let fixture = tempfile::tempdir().expect("fixture root"); + let fixture = workspace_fixture(); let workspace = fixture.path().join("workspace"); let outside = fixture.path().join("outside.txt"); std::fs::create_dir(&workspace).expect("workspace directory"); @@ -225,18 +233,19 @@ async fn workspace_write_allows_workspace_and_denies_metadata_and_external_paths ); let mut request = restricted_request(&workspace, powershell_script(&script)); request.stdin = true; - let mut handle = + let handle = start_local_execution_process(request).expect("restricted workspace process should start"); let baseline_workspace_sddl = &acl_baselines[0].1; let active_workspace_sddl = acl_sddl(&workspace); eprintln!("active restricted workspace SDDL: {active_workspace_sddl}"); let group_sid = account_sid("LimeSandboxUsers"); - let added_sids = sddl_sids(&active_workspace_sddl) + let active_sids = sddl_sids(&active_workspace_sddl); + let added_sids = active_sids .difference(&sddl_sids(baseline_workspace_sddl)) .cloned() .collect::>(); assert!( - added_sids.contains(&group_sid), + active_sids.contains(&group_sid), "active workspace ACL must grant the ordinary token through {group_sid}: {active_workspace_sddl}" ); assert!( @@ -298,7 +307,7 @@ async fn workspace_write_allows_workspace_and_denies_metadata_and_external_paths #[tokio::test] async fn restricted_execution_uses_offline_account_and_blocks_network() { - let fixture = tempfile::tempdir().expect("fixture root"); + let fixture = workspace_fixture(); let marker = fixture.path().join("offline-process-ran.txt"); let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("loopback listener"); let port = listener.local_addr().expect("listener address").port(); @@ -350,7 +359,7 @@ async fn restricted_execution_uses_offline_account_and_blocks_network() { #[tokio::test] async fn restricted_execution_bounds_large_output() { - let fixture = tempfile::tempdir().expect("fixture root"); + let fixture = workspace_fixture(); let script = "[Console]::Out.Write(('x' * 400000))"; let snapshot = run_to_terminal( start_local_execution_process(restricted_request( @@ -377,8 +386,8 @@ async fn restricted_execution_bounds_large_output() { #[tokio::test] async fn restricted_execution_preserves_allowlisted_stdin_handle() { - let fixture = tempfile::tempdir().expect("fixture root"); - let mut handle = start_local_execution_process(restricted_request( + let fixture = workspace_fixture(); + let handle = start_local_execution_process(restricted_request( fixture.path(), powershell_script("$line = [Console]::In.ReadLine(); Write-Output ('received:' + $line)"), )) @@ -405,7 +414,7 @@ async fn restricted_execution_preserves_allowlisted_stdin_handle() { #[tokio::test] async fn restricted_conpty_supports_stdin_resize_and_combined_output() { - let fixture = tempfile::tempdir().expect("fixture root"); + let fixture = workspace_fixture(); let mut request = restricted_request( fixture.path(), powershell_script( @@ -415,7 +424,7 @@ async fn restricted_conpty_supports_stdin_resize_and_combined_output() { request.tty = true; request.stdin = true; request.pty_size = Some((24, 80)); - let mut handle = + let handle = start_local_execution_process(request).expect("restricted ConPTY process should start"); handle @@ -443,7 +452,7 @@ async fn restricted_conpty_supports_stdin_resize_and_combined_output() { #[tokio::test] async fn world_writable_audit_reports_everyone_write_acl() { - let fixture = tempfile::tempdir().expect("fixture root"); + let fixture = workspace_fixture(); let world_writable = fixture.path().join("world-writable"); std::fs::create_dir(&world_writable).expect("world-writable directory"); let world_writable_text = world_writable.to_string_lossy().to_string(); @@ -475,13 +484,13 @@ async fn world_writable_audit_reports_everyone_write_acl() { #[tokio::test] async fn terminate_ends_restricted_process_and_its_job() { - let fixture = tempfile::tempdir().expect("fixture root"); + let fixture = workspace_fixture(); let marker = fixture.path().join("descendant-marker.txt"); let script = format!( "$child = Start-Process -FilePath powershell.exe -ArgumentList '-NoProfile','-Command','Start-Sleep -Seconds 10; Set-Content -LiteralPath ''{}'' -Value leaked' -PassThru; Start-Sleep -Seconds 10", ps_literal(&marker) ); - let mut handle = start_local_execution_process(restricted_request( + let handle = start_local_execution_process(restricted_request( fixture.path(), powershell_script(&script), ))