From a48adb53fbaf9d90029900dd8eddbb3d00c0129b Mon Sep 17 00:00:00 2001 From: David Wiesen Date: Mon, 11 May 2026 09:12:20 -0700 Subject: [PATCH] windows sandbox: preserve delete rights on stamped paths --- .../windows-sandbox-rs/sandbox_smoketests.py | 37 +++++++++++++++++++ codex-rs/windows-sandbox-rs/src/acl.rs | 4 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/codex-rs/windows-sandbox-rs/sandbox_smoketests.py b/codex-rs/windows-sandbox-rs/sandbox_smoketests.py index f6689587f5..f3d44b437d 100644 --- a/codex-rs/windows-sandbox-rs/sandbox_smoketests.py +++ b/codex-rs/windows-sandbox-rs/sandbox_smoketests.py @@ -328,6 +328,43 @@ def main() -> int: rc, out, err = run_sbx("workspace-write", ["cmd", "/c", "del /q delme.txt"], WS_ROOT) add("WS: delete succeeds (expected on this host)", rc == 0 and not target.exists(), f"rc={rc}, err={err}") + # 12b. WS: atomic replace inside workspace succeeds + replace_target = WS_ROOT / "replace.txt" + write_file(replace_target, "old") + rc, out, err = run_sbx( + "workspace-write", + [ + "python", + "-c", + "from pathlib import Path; import os; p=Path('replace.txt'); t=Path('replace.txt.tmp'); t.write_text('new', encoding='utf-8'); os.replace(t, p); print(p.read_text(encoding='utf-8'))", + ], + WS_ROOT, + ) + add( + "WS: atomic replace in workspace allowed", + rc == 0 and replace_target.read_text(encoding='utf-8').strip() == "new", + f"rc={rc}, err={err}", + ) + + # 12c. WS: atomic replace in additional root succeeds + extra_replace_target = EXTRA_ROOT / "replace-extra.txt" + write_file(extra_replace_target, "old") + rc, out, err = run_sbx( + "workspace-write", + [ + "python", + "-c", + f"from pathlib import Path; import os; p=Path(r'{extra_replace_target}'); t=Path(r'{extra_replace_target}.tmp'); t.write_text('new', encoding='utf-8'); os.replace(t, p); print(p.read_text(encoding='utf-8'))", + ], + WS_ROOT, + additional_root=EXTRA_ROOT, + ) + add( + "WS: atomic replace in additional root allowed", + rc == 0 and extra_replace_target.read_text(encoding='utf-8').strip() == "new", + f"rc={rc}, err={err}", + ) + # 13. RO: python tries to write (denied) pyfile = WS_ROOT / "py_should_fail.txt"; remove_if_exists(pyfile) rc, out, err = run_sbx("read-only", ["python", "-c", "open('py_should_fail.txt','w').write('x')"], WS_ROOT) diff --git a/codex-rs/windows-sandbox-rs/src/acl.rs b/codex-rs/windows-sandbox-rs/src/acl.rs index 68a17c9799..b559c49fc9 100644 --- a/codex-rs/windows-sandbox-rs/src/acl.rs +++ b/codex-rs/windows-sandbox-rs/src/acl.rs @@ -411,7 +411,9 @@ pub unsafe fn add_allow_ace(path: &Path, psid: *mut c_void) -> Result { ptstrName: psid as *mut u16, }; let mut explicit: EXPLICIT_ACCESS_W = std::mem::zeroed(); - explicit.grfAccessPermissions = FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE; + // Keep legacy one-shot ACL grants aligned with the stronger write mask used by + // setup refresh so atomic replace/delete patterns work on stamped paths too. + explicit.grfAccessPermissions = WRITE_ALLOW_MASK; explicit.grfAccessMode = 2; // SET_ACCESS explicit.grfInheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE; explicit.Trustee = trustee;