mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-10-16 15:34:08 +08:00
fix(sandbox): preserve nested Windows write roots
This commit is contained in:
@@ -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`,说明 private desktop 不是 workspace write、offline network、allowlisted stdin 与 terminate 四个失败的单一根因。当前下一刀是先让四个真机断言稳定输出实时/终态 snapshot,再按 ACL 写入、stdio handle 和 descendant spawn 三条边界分别对照 Codex;在七项通过前保持 `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`,增强 snapshot 已将四项失败统一定位为 cwd `UnauthorizedAccessException`。根因是 Lime 把 cwd 与 `%TEMP%` write roots 按祖先折叠,导致 Temp 下的已有 cwd 丢失显式 ACE;Codex 只做 exact dedup。当前修复改为先捕获所有 exact roots 的原始 DACL,再分别授权,同时保留 rollback;在七项通过前保持 `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/*` 兼容主链。
|
||||
|
||||
@@ -398,6 +398,7 @@ Renderer typed gateway
|
||||
- 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`。
|
||||
- 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 验证。
|
||||
|
||||
## 7. 阻塞与避让
|
||||
|
||||
|
||||
@@ -74,11 +74,12 @@ pub(super) fn build_acl_plan(
|
||||
}
|
||||
read_grants.sort();
|
||||
read_grants.dedup();
|
||||
write_grants = minimal_roots(write_grants);
|
||||
write_grants.sort();
|
||||
write_grants.dedup();
|
||||
access_denies.sort();
|
||||
access_denies.dedup();
|
||||
write_grants.retain(|path| access_denies.binary_search(path).is_err());
|
||||
read_grants.retain(|path| !write_grants.iter().any(|root| path.starts_with(root)));
|
||||
read_grants.retain(|path| write_grants.binary_search(path).is_err());
|
||||
read_grants.retain(|path| access_denies.binary_search(path).is_err());
|
||||
write_denies.sort();
|
||||
write_denies.dedup();
|
||||
@@ -105,26 +106,6 @@ fn windows_temp_write_roots(environment: &HashMap<String, String>) -> io::Result
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn minimal_roots(mut roots: Vec<PathBuf>) -> Vec<PathBuf> {
|
||||
roots.sort_by(|left, right| {
|
||||
left.components()
|
||||
.count()
|
||||
.cmp(&right.components().count())
|
||||
.then_with(|| left.cmp(right))
|
||||
});
|
||||
let mut minimal = Vec::new();
|
||||
for root in roots {
|
||||
if !minimal
|
||||
.iter()
|
||||
.any(|parent: &PathBuf| root.starts_with(parent))
|
||||
{
|
||||
minimal.push(root);
|
||||
}
|
||||
}
|
||||
minimal.sort();
|
||||
minimal
|
||||
}
|
||||
|
||||
fn resolve_permission_path(path: &FileSystemPath, cwd: &Path) -> io::Result<Option<PathBuf>> {
|
||||
match path {
|
||||
FileSystemPath::Path { path } => canonical_absolute(Path::new(path)).map(Some),
|
||||
@@ -240,43 +221,44 @@ impl AclLease {
|
||||
let mut lease = Self {
|
||||
snapshots: Vec::new(),
|
||||
};
|
||||
for path in plan
|
||||
.read_grants
|
||||
.iter()
|
||||
.chain(&plan.write_grants)
|
||||
.chain(&plan.write_denies)
|
||||
.chain(&plan.access_denies)
|
||||
{
|
||||
if let Err(error) = lease.capture(path) {
|
||||
return Err(lease.rollback_after(error));
|
||||
}
|
||||
}
|
||||
for path in plan.read_grants {
|
||||
if let Err(error) = lease.capture(&path).and_then(|()| {
|
||||
grant_access(
|
||||
&path,
|
||||
sandbox_group_sid,
|
||||
capability_sid,
|
||||
FILE_GENERIC_READ | FILE_GENERIC_EXECUTE,
|
||||
)
|
||||
}) {
|
||||
if let Err(error) = grant_access(
|
||||
&path,
|
||||
sandbox_group_sid,
|
||||
capability_sid,
|
||||
FILE_GENERIC_READ | FILE_GENERIC_EXECUTE,
|
||||
) {
|
||||
return Err(lease.rollback_after(error));
|
||||
}
|
||||
}
|
||||
for path in plan.write_grants {
|
||||
if let Err(error) = lease.capture(&path).and_then(|()| {
|
||||
grant_access(
|
||||
&path,
|
||||
sandbox_group_sid,
|
||||
capability_sid,
|
||||
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE,
|
||||
)
|
||||
}) {
|
||||
if let Err(error) = grant_access(
|
||||
&path,
|
||||
sandbox_group_sid,
|
||||
capability_sid,
|
||||
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE,
|
||||
) {
|
||||
return Err(lease.rollback_after(error));
|
||||
}
|
||||
}
|
||||
for path in plan.write_denies {
|
||||
if let Err(error) = lease
|
||||
.capture(&path)
|
||||
.and_then(|()| deny_write_access(&path, capability_sid))
|
||||
{
|
||||
if let Err(error) = deny_write_access(&path, capability_sid) {
|
||||
return Err(lease.rollback_after(error));
|
||||
}
|
||||
}
|
||||
for path in plan.access_denies {
|
||||
if let Err(error) = lease
|
||||
.capture(&path)
|
||||
.and_then(|()| deny_access(&path, sandbox_group_sid, capability_sid))
|
||||
{
|
||||
if let Err(error) = deny_access(&path, sandbox_group_sid, capability_sid) {
|
||||
return Err(lease.rollback_after(error));
|
||||
}
|
||||
}
|
||||
@@ -620,7 +602,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_acl_plan_grants_windows_temp_roots() {
|
||||
fn workspace_acl_plan_keeps_nested_workspace_and_windows_temp_roots() {
|
||||
let root = tempfile::tempdir().expect("fixture root");
|
||||
let workspace = root.path().join("workspace");
|
||||
std::fs::create_dir(&workspace).expect("workspace directory");
|
||||
@@ -645,7 +627,10 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
plan.write_grants,
|
||||
vec![std::fs::canonicalize(root.path()).unwrap()]
|
||||
vec![
|
||||
std::fs::canonicalize(root.path()).unwrap(),
|
||||
std::fs::canonicalize(&workspace).unwrap(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user