diff --git a/internal/exec-plans/codex-desktop-complete-alignment-plan.md b/internal/exec-plans/codex-desktop-complete-alignment-plan.md index 2765491a8..15eca0e1b 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 成功;最新矩阵已达 `3/7`,普通 pipe 的 PowerShell、stdin、文件写入和 terminate 仍在标准 desktop 初始化前 exit `1`。当前下一刀是对齐 Codex `LaunchDesktop`,为 restricted child 创建并授权 sandbox logon SID 的短生命周期 desktop,保留到 child/descendants 完成后再关闭,再重跑真实 Windows/MSVC 矩阵。在证据通过前保持 `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`,说明 private desktop 不是 workspace write、offline network、allowlisted stdin 与 terminate 四个失败的单一根因。当前下一刀是先让四个真机断言稳定输出实时/终态 snapshot,再按 ACL 写入、stdio handle 和 descendant spawn 三条边界分别对照 Codex;在七项通过前保持 `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/*` 兼容主链。 @@ -397,6 +397,7 @@ Renderer typed gateway - 2026-08-26:Windows Quality run `32960057901`(SHA `f1bec2328`,artifact `9603664649`)再次通过 setup/MSVC 编译并完整执行 schema v3,但矩阵仍为 `1/7`;六项 restricted child 继续无有效输出 exit `1`,说明内置 `Users` 成员身份已正确写入/read-back,却不是唯一缺口。继续逐段对照 Codex `gather_read_roots -> run_setup_refresh -> apply_read_acls` 后确认 Lime 尚未刷新 `C:\Windows`、Program Files/ProgramData 与当前用户 profile 非敏感子目录的默认 RX ACL;这会让已创建的 restricted PowerShell 在加载 DLL、模块或 AppData 时静默失败。当前修复将该行为收归 Windows setup、补 read-back 并提升 setup marker version 强制旧安装重跑;完成度/readiness 不变。 - 2026-08-26:Windows Quality run `32961870012`(SHA `740d5b61b`,artifact `9604315141`)证明默认平台/profile RX ACL 已通过 setup/read-back 与 MSVC 编译,但 schema v3 仍为 `1/7`;六项 PowerShell child 全部在首条输出前 exit `1`,因此 read roots 不是最后一个缺口。继续对照 Codex `PermissionProfile::workspace_write -> FileSystemSpecialPath::Tmpdir -> windows_temp_env_roots` 后确认 Lime ACL plan 只给 cwd/显式 grant 可写,遗漏 child 继承的 `%TEMP%/%TMP%`;sandbox account 对宿主 Temp 只有 RX,符合 PowerShell 初始化阶段无输出失败。当前修复把已过滤 child environment 中的 Windows Temp 根纳入 workspace-write ACL lease,保持原始 DACL rollback、metadata deny、完成度 `44/46 (95.7%)` 与 `Planned/enforced=false` 不变。 - 2026-08-26:Windows Quality run `32963357155`(SHA `9af4c627a`,artifact `9604867800`)验证 `%TEMP%/%TMP%` workspace-write ACL lease 已生效,schema v3 从 `1/7` 提升至 `3/7`:`restricted_conpty_supports_stdin_resize_and_combined_output`、`restricted_execution_bounds_large_output`、`world_writable_audit_reports_everyone_write_acl` 通过;普通 pipe 的 workspace write、offline network、allowlisted stdin 与 terminate 仍失败,均表现为 PowerShell 在首条输出前 exit `1`,terminate 因 runner 已结束返回 `ControlClosed`。继续对照 Codex `windows-sandbox-rs::desktop::LaunchDesktop` 与 `process::spawn_process_with_pipes` 后确认 Lime 只填 `Winsta0\\Default` 字符串,未创建并授权 sandbox logon SID 的 desktop;当前修复新增 `windows_desktop::LaunchDesktop`,按 child token 的 logon SID 创建/授权短生命周期 desktop,并让 pipe/ConPTY 的 `STARTUPINFOEX` 复用同一 desktop,child/descendants 完成后再释放。新增 `windows-sys` 的 GDI/StationsAndDesktops features;本机 `test:rust:related` `373/373`、`cargo fmt --check`、`git diff --check` 通过,但无 Windows target,下一轮必须由 MSVC runner 验证,完成度继续 `44/46 (95.7%)`、readiness 继续 `Planned/enforced=false`。 +- 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`。 ## 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 22c8b1752..e2867dab9 100644 --- a/lime-rs/crates/tool-runtime/tests/windows_restricted_execution.rs +++ b/lime-rs/crates/tool-runtime/tests/windows_restricted_execution.rs @@ -137,9 +137,15 @@ fn sddl_sids(sddl: &str) -> HashSet { .collect() } -async fn wait_for_path(path: &Path) { +async fn wait_for_path(handle: &LocalExecutionProcessHandle, path: &Path) { tokio::time::timeout(Duration::from_secs(5), async { while !path.exists() { + let snapshot = handle.status(); + assert!( + !snapshot.status.is_terminal(), + "restricted process exited before creating {}: {snapshot:#?}", + path.display() + ); tokio::time::sleep(Duration::from_millis(25)).await; } }) @@ -194,7 +200,7 @@ async fn workspace_write_allows_workspace_and_denies_metadata_and_external_paths request.stdin = true; let mut handle = start_local_execution_process(request).expect("restricted workspace process should start"); - wait_for_path(&inside).await; + wait_for_path(&handle, &inside).await; let baseline_workspace_sddl = &acl_baselines[0].1; let active_workspace_sddl = acl_sddl(&workspace); @@ -291,7 +297,11 @@ async fn restricted_execution_uses_offline_account_and_blocks_network() { ) .await; assert_eq!(snapshot.status, ExecutionProcessStatus::Exited); - assert_eq!(snapshot.exit_code, Some(0)); + assert_eq!( + snapshot.exit_code, + Some(0), + "offline restricted process must exit successfully: {snapshot:#?}" + ); assert!( marker.is_file(), "offline process must execute inside the workspace" @@ -325,6 +335,11 @@ async fn restricted_execution_bounds_large_output() { .await; assert_eq!(snapshot.status, ExecutionProcessStatus::Exited); + assert_eq!( + snapshot.exit_code, + Some(0), + "large-output restricted process must exit successfully: {snapshot:#?}" + ); assert!(snapshot.output_truncated, "large output must be truncated"); assert!( snapshot.output_omitted_bytes > 0, @@ -349,7 +364,11 @@ async fn restricted_execution_preserves_allowlisted_stdin_handle() { let snapshot = run_to_terminal(handle).await; assert_eq!(snapshot.status, ExecutionProcessStatus::Exited); - assert_eq!(snapshot.exit_code, Some(0)); + assert_eq!( + snapshot.exit_code, + Some(0), + "stdin restricted process must exit successfully: {snapshot:#?}" + ); assert!( snapshot.retained_output.contains("received:sandbox-input"), "child output should contain the stdin payload: {:?}", @@ -442,6 +461,11 @@ async fn terminate_ends_restricted_process_and_its_job() { .expect("long-running process should start"); tokio::time::sleep(Duration::from_millis(250)).await; + let before_terminate = handle.status(); + assert!( + !before_terminate.status.is_terminal(), + "terminate target exited before control could be sent: {before_terminate:#?}" + ); handle .terminate() .expect("terminate should reach supervisor");