From 0618ba0d69ef4359ff190844278ad15e9e3460db Mon Sep 17 00:00:00 2001 From: David Wiesen Date: Sun, 14 Jun 2026 10:15:33 -0700 Subject: [PATCH] Fix Windows sandbox process application resolution --- codex-rs/windows-sandbox-rs/Cargo.toml | 3 ++ codex-rs/windows-sandbox-rs/src/process.rs | 21 ++++++++--- codex-rs/windows-sandbox-rs/src/winutil.rs | 42 ++++++++++++++++++++++ 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/codex-rs/windows-sandbox-rs/Cargo.toml b/codex-rs/windows-sandbox-rs/Cargo.toml index 31df9ad390..417e0a9e8f 100644 --- a/codex-rs/windows-sandbox-rs/Cargo.toml +++ b/codex-rs/windows-sandbox-rs/Cargo.toml @@ -92,5 +92,8 @@ features = [ ] version = "0.52" +[target.'cfg(windows)'.dependencies.which] +workspace = true + [dev-dependencies] pretty_assertions = { workspace = true } diff --git a/codex-rs/windows-sandbox-rs/src/process.rs b/codex-rs/windows-sandbox-rs/src/process.rs index 0899d34c2f..3144154628 100644 --- a/codex-rs/windows-sandbox-rs/src/process.rs +++ b/codex-rs/windows-sandbox-rs/src/process.rs @@ -3,6 +3,7 @@ use crate::logging; use crate::proc_thread_attr::ProcThreadAttributeList; use crate::winutil::argv_to_command_line; use crate::winutil::format_last_error; +use crate::winutil::resolve_application_path; use crate::winutil::to_wide; use anyhow::Result; use anyhow::anyhow; @@ -86,6 +87,8 @@ pub unsafe fn create_process_as_user( ) -> Result { let cmdline_str = argv_to_command_line(argv); let mut cmdline: Vec = to_wide(&cmdline_str); + let application_path = resolve_application_path(argv, env_map, cwd); + let application_name = application_path.as_deref().map(to_wide); let env_block = make_env_block(env_map); let desktop = LaunchDesktop::prepare(use_private_desktop, logs_base_dir)?; let mut pi: PROCESS_INFORMATION = std::mem::zeroed(); @@ -122,7 +125,9 @@ pub unsafe fn create_process_as_user( let creation_flags = CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT; let ok = CreateProcessAsUserW( h_token, - std::ptr::null(), + application_name + .as_ref() + .map_or(std::ptr::null(), |wide| wide.as_ptr()), cmdline.as_mut_ptr(), std::ptr::null_mut(), std::ptr::null_mut(), @@ -136,9 +141,12 @@ pub unsafe fn create_process_as_user( if ok == 0 { let err = GetLastError() as i32; let msg = format!( - "CreateProcessAsUserW failed: {} ({}) | cwd={} | cmd={} | env_u16_len={} | si_flags={} | creation_flags={}", + "CreateProcessAsUserW failed: {} ({}) | app={} | cwd={} | cmd={} | env_u16_len={} | si_flags={} | creation_flags={}", err, format_last_error(err), + application_path + .as_deref() + .map_or("", |path| path.to_string_lossy().as_ref()), cwd.display(), cmdline_str, env_block_len, @@ -163,7 +171,9 @@ pub unsafe fn create_process_as_user( let creation_flags = CREATE_UNICODE_ENVIRONMENT; let ok = CreateProcessAsUserW( h_token, - std::ptr::null(), + application_name + .as_ref() + .map_or(std::ptr::null(), |wide| wide.as_ptr()), cmdline.as_mut_ptr(), std::ptr::null_mut(), std::ptr::null_mut(), @@ -177,9 +187,12 @@ pub unsafe fn create_process_as_user( if ok == 0 { let err = GetLastError() as i32; let msg = format!( - "CreateProcessAsUserW failed: {} ({}) | cwd={} | cmd={} | env_u16_len={} | si_flags={} | creation_flags={}", + "CreateProcessAsUserW failed: {} ({}) | app={} | cwd={} | cmd={} | env_u16_len={} | si_flags={} | creation_flags={}", err, format_last_error(err), + application_path + .as_deref() + .map_or("", |path| path.to_string_lossy().as_ref()), cwd.display(), cmdline_str, env_block_len, diff --git a/codex-rs/windows-sandbox-rs/src/winutil.rs b/codex-rs/windows-sandbox-rs/src/winutil.rs index 45378b4bb5..76e7634eee 100644 --- a/codex-rs/windows-sandbox-rs/src/winutil.rs +++ b/codex-rs/windows-sandbox-rs/src/winutil.rs @@ -1,6 +1,10 @@ use anyhow::Result; +use std::collections::HashMap; use std::ffi::OsStr; +use std::ffi::OsString; use std::os::windows::ffi::OsStrExt; +use std::path::Path; +use std::path::PathBuf; use windows_sys::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER; use windows_sys::Win32::Foundation::GetLastError; use windows_sys::Win32::Foundation::HLOCAL; @@ -73,6 +77,25 @@ pub fn argv_to_command_line(argv: &[String]) -> String { .join(" ") } +#[cfg(target_os = "windows")] +pub fn resolve_application_path( + argv: &[String], + env_map: &HashMap, + cwd: &Path, +) -> Option { + let program = argv.first().filter(|program| !program.is_empty())?; + let candidate = Path::new(program); + if candidate.components().count() > 1 { + return Some(candidate.to_path_buf()); + } + + let search_path = env_map + .iter() + .find(|(key, _)| key.eq_ignore_ascii_case("PATH")) + .map(|(_, value)| OsString::from(value)); + which::which_in(program, search_path, cwd).ok() +} + // Produce a readable description for a Win32 error code. pub fn format_last_error(err: i32) -> String { unsafe { @@ -208,7 +231,11 @@ fn sid_bytes_from_string(sid_str: &str) -> Result> { #[cfg(test)] mod tests { use super::argv_to_command_line; + use super::resolve_application_path; use pretty_assertions::assert_eq; + use std::collections::HashMap; + use std::path::Path; + use std::path::PathBuf; #[test] fn argv_to_command_line_quotes_each_argument_independently() { @@ -238,4 +265,19 @@ mod tests { "pwsh.exe -Command \"Write-Output \\\"hello world\\\"\"" ); } + + #[cfg(target_os = "windows")] + #[test] + fn resolve_application_path_preserves_explicit_paths() { + let resolved = resolve_application_path( + &[r"C:\Program Files\Git\cmd\git.exe".to_string()], + &HashMap::new(), + Path::new(r"C:\"), + ); + + assert_eq!( + resolved, + Some(PathBuf::from(r"C:\Program Files\Git\cmd\git.exe")) + ); + } }