diff --git a/codex-rs/cli/src/debug_sandbox.rs b/codex-rs/cli/src/debug_sandbox.rs index 0b325fbe6..df4c2e97c 100644 --- a/codex-rs/cli/src/debug_sandbox.rs +++ b/codex-rs/cli/src/debug_sandbox.rs @@ -155,11 +155,11 @@ async fn run_command_under_sandbox( run_windows_sandbox_capture( policy_str, &sandbox_cwd, + base_dir.as_path(), command_vec, &cwd_clone, env_map, None, - Some(base_dir.as_path()), ) }) .await; diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index 765086ef1..b44a40bdb 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -189,16 +189,20 @@ async fn exec_windows_sandbox( }; let sandbox_cwd = cwd.clone(); - let logs_base_dir = find_codex_home().ok(); + let codex_home = find_codex_home().map_err(|err| { + CodexErr::Io(io::Error::other(format!( + "windows sandbox: failed to resolve codex_home: {err}" + ))) + })?; let spawn_res = tokio::task::spawn_blocking(move || { run_windows_sandbox_capture( policy_str, &sandbox_cwd, + codex_home.as_ref(), command, &cwd, env, timeout_ms, - logs_base_dir.as_deref(), ) }) .await; diff --git a/codex-rs/windows-sandbox-rs/sandbox_smoketests.py b/codex-rs/windows-sandbox-rs/sandbox_smoketests.py index bc35a4103..63bf34909 100644 --- a/codex-rs/windows-sandbox-rs/sandbox_smoketests.py +++ b/codex-rs/windows-sandbox-rs/sandbox_smoketests.py @@ -340,7 +340,13 @@ def main() -> int: add("WS: protected path case-variation denied", rc != 0 and assert_not_exists(git_variation), f"rc={rc}") # 34. WS: policy tamper (.codex artifacts) denied - rc, out, err = run_sbx("workspace-write", ["cmd", "/c", "echo tamper > .codex\\cap_sid"], WS_ROOT) + codex_home = Path(os.environ["USERPROFILE"]) / ".codex" + cap_sid_target = codex_home / "cap_sid" + rc, out, err = run_sbx( + "workspace-write", + ["cmd", "/c", f"echo tamper > \"{cap_sid_target}\""], + WS_ROOT, + ) rc2, out2, err2 = run_sbx("workspace-write", ["cmd", "/c", "echo tamper > .codex\\policy.json"], WS_ROOT) add("WS: .codex cap_sid tamper denied", rc != 0, f"rc={rc}, err={err}") add("WS: .codex policy tamper denied", rc2 != 0, f"rc={rc2}, err={err2}") diff --git a/codex-rs/windows-sandbox-rs/src/cap.rs b/codex-rs/windows-sandbox-rs/src/cap.rs index 41273db1d..2f4c96237 100644 --- a/codex-rs/windows-sandbox-rs/src/cap.rs +++ b/codex-rs/windows-sandbox-rs/src/cap.rs @@ -13,8 +13,8 @@ pub struct CapSids { pub readonly: String, } -pub fn cap_sid_file(policy_cwd: &Path) -> PathBuf { - policy_cwd.join(".codex").join("cap_sid") +pub fn cap_sid_file(codex_home: &Path) -> PathBuf { + codex_home.join("cap_sid") } fn make_random_cap_sid_string() -> String { @@ -26,8 +26,8 @@ fn make_random_cap_sid_string() -> String { format!("S-1-5-21-{}-{}-{}-{}", a, b, c, d) } -pub fn load_or_create_cap_sids(policy_cwd: &Path) -> CapSids { - let path = cap_sid_file(policy_cwd); +pub fn load_or_create_cap_sids(codex_home: &Path) -> CapSids { + let path = cap_sid_file(codex_home); if path.exists() { if let Ok(txt) = fs::read_to_string(&path) { let t = txt.trim(); diff --git a/codex-rs/windows-sandbox-rs/src/lib.rs b/codex-rs/windows-sandbox-rs/src/lib.rs index 955f2ca3c..a2f63c8d4 100644 --- a/codex-rs/windows-sandbox-rs/src/lib.rs +++ b/codex-rs/windows-sandbox-rs/src/lib.rs @@ -73,6 +73,11 @@ mod windows_impl { Ok(()) } + fn ensure_codex_home_exists(p: &Path) -> Result<()> { + std::fs::create_dir_all(p)?; + Ok(()) + } + fn make_env_block(env: &HashMap) -> Vec { let mut items: Vec<(String, String)> = env.iter().map(|(k, v)| (k.clone(), v.clone())).collect(); @@ -179,40 +184,37 @@ mod windows_impl { pub fn run_windows_sandbox_capture( policy_json_or_preset: &str, sandbox_policy_cwd: &Path, + codex_home: &Path, command: Vec, cwd: &Path, mut env_map: HashMap, timeout_ms: Option, - logs_base_dir: Option<&Path>, ) -> Result { let policy = SandboxPolicy::parse(policy_json_or_preset)?; normalize_null_device_env(&mut env_map); ensure_non_interactive_pager(&mut env_map); apply_no_network_to_env(&mut env_map)?; + ensure_codex_home_exists(codex_home)?; let current_dir = cwd.to_path_buf(); // for now, don't fail if we detect world-writable directories // audit::audit_everyone_writable(¤t_dir, &env_map)?; + let logs_base_dir = Some(codex_home); log_start(&command, logs_base_dir); + let cap_sid_path = cap_sid_file(codex_home); let (h_token, psid_to_use): (HANDLE, *mut c_void) = unsafe { match &policy.0 { SandboxMode::ReadOnly => { - let caps = load_or_create_cap_sids(sandbox_policy_cwd); - ensure_dir(&cap_sid_file(sandbox_policy_cwd))?; - fs::write( - cap_sid_file(sandbox_policy_cwd), - serde_json::to_string(&caps)?, - )?; + let caps = load_or_create_cap_sids(codex_home); + ensure_dir(&cap_sid_path)?; + fs::write(&cap_sid_path, serde_json::to_string(&caps)?)?; let psid = convert_string_sid_to_sid(&caps.readonly).unwrap(); super::token::create_readonly_token_with_cap(psid)? } SandboxMode::WorkspaceWrite => { - let caps = load_or_create_cap_sids(sandbox_policy_cwd); - ensure_dir(&cap_sid_file(sandbox_policy_cwd))?; - fs::write( - cap_sid_file(sandbox_policy_cwd), - serde_json::to_string(&caps)?, - )?; + let caps = load_or_create_cap_sids(codex_home); + ensure_dir(&cap_sid_path)?; + fs::write(&cap_sid_path, serde_json::to_string(&caps)?)?; let psid = convert_string_sid_to_sid(&caps.workspace).unwrap(); super::token::create_workspace_write_token_with_cap(psid)? } @@ -445,11 +447,11 @@ mod stub { pub fn run_windows_sandbox_capture( _policy_json_or_preset: &str, _sandbox_policy_cwd: &Path, + _codex_home: &Path, _command: Vec, _cwd: &Path, _env_map: HashMap, _timeout_ms: Option, - _logs_base_dir: Option<&Path>, ) -> Result { bail!("Windows sandbox is only available on Windows") }