diff --git a/codex-rs/cli/src/debug_sandbox.rs b/codex-rs/cli/src/debug_sandbox.rs index 6359e09279..4f77a4b822 100644 --- a/codex-rs/cli/src/debug_sandbox.rs +++ b/codex-rs/cli/src/debug_sandbox.rs @@ -473,7 +473,6 @@ async fn run_command_under_windows_session( ) -> ! { use codex_core::windows_sandbox::WindowsSandboxLevelExt; use codex_protocol::config_types::WindowsSandboxLevel; - use codex_windows_sandbox::WindowsProcessLaunch; use codex_windows_sandbox::WindowsSandboxProxySettingsMode; use codex_windows_sandbox::WindowsSandboxSessionRequest; use codex_windows_sandbox::spawn_windows_sandbox_session_for_level; @@ -483,10 +482,7 @@ async fn run_command_under_windows_session( permission_profile, workspace_roots: workspace_roots.as_slice(), codex_home: config.codex_home.as_path(), - launch: WindowsProcessLaunch { - application_path: None, - command, - }, + command, cwd: cwd.as_path(), env_map: env, windows_sandbox_level: WindowsSandboxLevel::from_config(config), diff --git a/codex-rs/core/src/unified_exec/process_manager.rs b/codex-rs/core/src/unified_exec/process_manager.rs index d0883ed5ab..957dcd997e 100644 --- a/codex-rs/core/src/unified_exec/process_manager.rs +++ b/codex-rs/core/src/unified_exec/process_manager.rs @@ -995,10 +995,7 @@ impl UnifiedExecProcessManager { permission_profile: &request.permission_profile, workspace_roots: request.windows_sandbox_workspace_roots.as_slice(), codex_home: codex_home.as_ref(), - launch: codex_windows_sandbox::WindowsProcessLaunch { - application_path: None, - command: request.command.clone(), - }, + command: request.command.clone(), cwd: native_cwd.as_path(), env_map: request.env.clone(), windows_sandbox_level: request.windows_sandbox_level, diff --git a/codex-rs/windows-sandbox-rs/src/command_resolution.rs b/codex-rs/windows-sandbox-rs/src/command_resolution.rs index 29cdd7dd33..8ec1e233aa 100644 --- a/codex-rs/windows-sandbox-rs/src/command_resolution.rs +++ b/codex-rs/windows-sandbox-rs/src/command_resolution.rs @@ -1,4 +1,5 @@ -use crate::process::WindowsProcessLaunch; +use crate::env::windows_env_value; +use crate::process::ResolvedWindowsProcessLaunch; use anyhow::Context; use anyhow::Result; use anyhow::anyhow; @@ -88,14 +89,15 @@ pub fn resolve_windows_executable( } pub(crate) fn resolve_windows_launch( - mut launch: WindowsProcessLaunch, + command: Vec, cwd: &Path, env_map: &HashMap, -) -> Result { - if launch.application_path.is_none() { - launch.application_path = Some(resolve_windows_executable(&launch.command, cwd, env_map)?); - } - Ok(launch) +) -> Result { + let application_path = resolve_windows_executable(&command, cwd, env_map)?; + Ok(ResolvedWindowsProcessLaunch { + application_path, + command, + }) } fn windows_search_dirs(cwd: &Path, env_map: &HashMap) -> Vec { @@ -161,18 +163,6 @@ fn is_drive_relative(path: &Path) -> bool { ) } -fn windows_env_value<'a>(env_map: &'a HashMap, key: &str) -> Option<&'a str> { - env_map - .get(key) - .or_else(|| { - env_map - .iter() - .find(|(existing, _)| existing.eq_ignore_ascii_case(key)) - .map(|(_, value)| value) - }) - .map(String::as_str) -} - #[cfg(test)] #[path = "command_resolution_tests.rs"] mod tests; diff --git a/codex-rs/windows-sandbox-rs/src/command_resolution_tests.rs b/codex-rs/windows-sandbox-rs/src/command_resolution_tests.rs index 43f8d1a421..8b7d7f19e0 100644 --- a/codex-rs/windows-sandbox-rs/src/command_resolution_tests.rs +++ b/codex-rs/windows-sandbox-rs/src/command_resolution_tests.rs @@ -1,19 +1,12 @@ use super::resolve_windows_executable; use super::resolve_windows_launch; -use crate::process::WindowsProcessLaunch; +use crate::process::ResolvedWindowsProcessLaunch; use pretty_assertions::assert_eq; use std::collections::HashMap; use std::fs; use std::path::PathBuf; use tempfile::TempDir; -fn unresolved(program: &str) -> WindowsProcessLaunch { - WindowsProcessLaunch { - application_path: None, - command: vec![program.to_string(), "argument".to_string()], - } -} - #[test] fn resolves_with_case_insensitive_child_path_and_pathext() { let temp = TempDir::new().expect("tempdir"); @@ -28,13 +21,17 @@ fn resolves_with_case_insensitive_child_path_and_pathext() { ("PathExt".to_string(), ".BIN".to_string()), ]); - let launch = resolve_windows_launch(unresolved("child-only"), &cwd, &env) - .expect("resolve from child environment"); + let launch = resolve_windows_launch( + vec!["child-only".to_string(), "argument".to_string()], + &cwd, + &env, + ) + .expect("resolve from child environment"); assert_eq!( launch, - WindowsProcessLaunch { - application_path: Some(executable), + ResolvedWindowsProcessLaunch { + application_path: executable, command: vec!["child-only".to_string(), "argument".to_string()], } ); diff --git a/codex-rs/windows-sandbox-rs/src/conpty/mod.rs b/codex-rs/windows-sandbox-rs/src/conpty/mod.rs index 48f3827468..b1b10dc1a7 100644 --- a/codex-rs/windows-sandbox-rs/src/conpty/mod.rs +++ b/codex-rs/windows-sandbox-rs/src/conpty/mod.rs @@ -8,7 +8,7 @@ use crate::desktop::LaunchDesktop; use crate::proc_thread_attr::ProcThreadAttributeList; -use crate::process::WindowsProcessLaunch; +use crate::process::ResolvedWindowsProcessLaunch; use crate::winutil::format_last_error; use crate::winutil::quote_windows_arg; use crate::winutil::to_wide; @@ -92,7 +92,7 @@ pub fn create_conpty(cols: i16, rows: i16) -> Result { #[doc(hidden)] pub fn spawn_conpty_process_as_user( h_token: HANDLE, - launch: &WindowsProcessLaunch, + launch: &ResolvedWindowsProcessLaunch, cwd: &Path, env_map: &HashMap, use_private_desktop: bool, @@ -105,10 +105,7 @@ pub fn spawn_conpty_process_as_user( .collect::>() .join(" "); let mut cmdline: Vec = to_wide(&cmdline_str); - let application_name = launch.application_path.as_deref().map(to_wide); - let application_name_ptr = application_name - .as_ref() - .map_or(std::ptr::null(), Vec::as_ptr); + let application_name = to_wide(&launch.application_path); let env_block = make_env_block(env_map); let mut si: STARTUPINFOEXW = unsafe { std::mem::zeroed() }; si.StartupInfo.cb = std::mem::size_of::() as u32; @@ -136,7 +133,7 @@ pub fn spawn_conpty_process_as_user( let ok = unsafe { CreateProcessAsUserW( h_token, - application_name_ptr, + application_name.as_ptr(), cmdline.as_mut_ptr(), std::ptr::null_mut(), std::ptr::null_mut(), diff --git a/codex-rs/windows-sandbox-rs/src/elevated/ipc_framed.rs b/codex-rs/windows-sandbox-rs/src/elevated/ipc_framed.rs index c9031f7ca3..571f50135c 100644 --- a/codex-rs/windows-sandbox-rs/src/elevated/ipc_framed.rs +++ b/codex-rs/windows-sandbox-rs/src/elevated/ipc_framed.rs @@ -7,7 +7,7 @@ //! not use this protocol, and non‑unified exec capture uses it only when running //! through the elevated runner. -use crate::process::WindowsProcessLaunch; +use crate::process::ResolvedWindowsProcessLaunch; use anyhow::Result; use base64::Engine as _; use base64::engine::general_purpose::STANDARD; @@ -58,7 +58,7 @@ pub enum Message { /// Spawn parameters sent from parent to runner. #[derive(Debug, Serialize, Deserialize, Clone)] pub struct SpawnRequest { - pub launch: WindowsProcessLaunch, + pub launch: ResolvedWindowsProcessLaunch, pub cwd: PathBuf, pub env: HashMap, pub permission_profile: PermissionProfile, @@ -213,8 +213,8 @@ mod tests { AbsolutePathBuf::from_absolute_path(PathBuf::from(r"C:\workspace")) .expect("absolute workspace root"), ]; - let launch = WindowsProcessLaunch { - application_path: Some(PathBuf::from(r"C:\Windows\System32\cmd.exe")), + let launch = ResolvedWindowsProcessLaunch { + application_path: PathBuf::from(r"C:\Windows\System32\cmd.exe"), command: vec!["cmd.exe".to_string(), "/c".to_string(), "ver".to_string()], }; let msg = FramedMessage { diff --git a/codex-rs/windows-sandbox-rs/src/elevated/runner_client.rs b/codex-rs/windows-sandbox-rs/src/elevated/runner_client.rs index 40ea5e9e12..d6181eb134 100644 --- a/codex-rs/windows-sandbox-rs/src/elevated/runner_client.rs +++ b/codex-rs/windows-sandbox-rs/src/elevated/runner_client.rs @@ -7,7 +7,7 @@ use crate::ipc_framed::Message; use crate::ipc_framed::SpawnRequest; use crate::ipc_framed::read_frame; use crate::ipc_framed::write_frame; -use crate::process::WindowsProcessLaunch; +use crate::process::ResolvedWindowsProcessLaunch; use crate::runner_pipe::PIPE_ACCESS_INBOUND; use crate::runner_pipe::PIPE_ACCESS_OUTBOUND; use crate::runner_pipe::connect_pipe; @@ -103,24 +103,18 @@ fn is_refreshable_windows_error(code: u32) -> bool { matches!(code, ERROR_LOGON_FAILURE | ERROR_NO_SUCH_LOGON_SESSION) } -fn command_targets_windows_apps(launch: &WindowsProcessLaunch) -> bool { - launch - .application_path - .as_deref() - .or_else(|| launch.command.first().map(Path::new)) - .is_some_and(|program| { - program.components().any(|component| { - component - .as_os_str() - .to_string_lossy() - .eq_ignore_ascii_case("WindowsApps") - }) - }) +fn command_targets_windows_apps(launch: &ResolvedWindowsProcessLaunch) -> bool { + launch.application_path.components().any(|component| { + component + .as_os_str() + .to_string_lossy() + .eq_ignore_ascii_case("WindowsApps") + }) } pub(crate) fn is_refreshable_sandbox_creds_error( err: &anyhow::Error, - launch: &WindowsProcessLaunch, + launch: &ResolvedWindowsProcessLaunch, ) -> bool { if err .downcast_ref::() @@ -143,7 +137,7 @@ pub(crate) fn is_refreshable_sandbox_creds_error( pub(crate) fn retry_runner_spawn_once( sandbox_creds: SandboxCreds, - launch: &WindowsProcessLaunch, + launch: &ResolvedWindowsProcessLaunch, mut spawn: impl FnMut(SandboxCreds) -> Result, refresh: impl FnOnce() -> Result, ) -> Result { @@ -501,7 +495,7 @@ mod tests { use super::is_refreshable_sandbox_creds_error; use crate::ipc_framed::ErrorPayload; use crate::ipc_framed::ErrorStage; - use crate::process::WindowsProcessLaunch; + use crate::process::ResolvedWindowsProcessLaunch; use pretty_assertions::assert_eq; use std::path::PathBuf; use windows_sys::Win32::Foundation::ERROR_LOGON_FAILURE; @@ -510,8 +504,8 @@ mod tests { #[test] fn refreshable_sandbox_creds_error_recognizes_credential_and_child_start_failures() { - let launch = WindowsProcessLaunch { - application_path: None, + let launch = ResolvedWindowsProcessLaunch { + application_path: PathBuf::from(r"C:\Windows\System32\cmd.exe"), command: vec!["cmd.exe".to_string()], }; assert_eq!( @@ -546,16 +540,16 @@ mod tests { ); let windows_apps_launches = [ - WindowsProcessLaunch { - application_path: Some(PathBuf::from( + ResolvedWindowsProcessLaunch { + application_path: PathBuf::from( r"C:\Users\user\AppData\Local\Microsoft\WindowsApps\pwsh.exe", - )), + ), command: vec!["pwsh.exe".to_string()], }, - WindowsProcessLaunch { - application_path: Some(PathBuf::from( + ResolvedWindowsProcessLaunch { + application_path: PathBuf::from( r"C:\Program Files\WindowsApps\Microsoft.PowerShell_7.6.3.0_x64__8wekyb3d8bbwe\pwsh.exe", - )), + ), command: vec!["pwsh.exe".to_string()], }, ]; diff --git a/codex-rs/windows-sandbox-rs/src/elevated_impl.rs b/codex-rs/windows-sandbox-rs/src/elevated_impl.rs index 6fe8c7678b..f2e55438bc 100644 --- a/codex-rs/windows-sandbox-rs/src/elevated_impl.rs +++ b/codex-rs/windows-sandbox-rs/src/elevated_impl.rs @@ -24,6 +24,7 @@ pub struct ElevatedSandboxProfileCaptureRequest<'a> { mod windows_impl { use super::ElevatedSandboxProfileCaptureRequest; + use crate::env::inherit_path_env; use crate::identity::refresh_logon_sandbox_creds; use crate::ipc_framed::EmptyPayload; use crate::ipc_framed::FramedMessage; @@ -35,7 +36,6 @@ mod windows_impl { use crate::ipc_framed::write_frame; use crate::logging::log_failure; use crate::logging::log_success; - use crate::process::WindowsProcessLaunch; use crate::resolved_permissions::ResolvedWindowsSandboxPermissions; use crate::runner_client::retry_runner_spawn_once; use crate::runner_client::spawn_runner_transport; @@ -118,15 +118,13 @@ mod windows_impl { .iter() .map(AbsolutePathBuf::to_path_buf) .collect::>(); + inherit_path_env(&mut env_map); let elevated = prepare_elevated_spawn_context_for_permissions( permissions.clone(), codex_home, cwd, &mut env_map, - WindowsProcessLaunch { - application_path: None, - command: command.clone(), - }, + command.clone(), read_roots_override, read_roots_include_platform_defaults, write_roots_override, diff --git a/codex-rs/windows-sandbox-rs/src/env.rs b/codex-rs/windows-sandbox-rs/src/env.rs index 9bb317a309..751591364a 100644 --- a/codex-rs/windows-sandbox-rs/src/env.rs +++ b/codex-rs/windows-sandbox-rs/src/env.rs @@ -31,7 +31,7 @@ pub fn ensure_non_interactive_pager(env_map: &mut HashMap) { env_map.entry("LESS".into()).or_insert_with(|| "".into()); } -// Keep PATH and PATHEXT stable for callers that rely on inheriting the parent process env. +// Capture APIs accept partial environments, so keep PATH and PATHEXT stable for those callers. pub fn inherit_path_env(env_map: &mut HashMap) { if windows_env_value(env_map, "PATH").is_none() && let Ok(path) = env::var("PATH") @@ -47,7 +47,7 @@ pub fn inherit_path_env(env_map: &mut HashMap) { fn prepend_path(env_map: &mut HashMap, prefix: &str) { let existing = windows_env_value(env_map, "PATH") - .cloned() + .map(ToOwned::to_owned) .unwrap_or_default(); let parts: Vec = existing.split(';').map(ToString::to_string).collect(); if parts @@ -68,7 +68,7 @@ fn prepend_path(env_map: &mut HashMap, prefix: &str) { fn reorder_pathext_for_stubs(env_map: &mut HashMap) { let default = windows_env_value(env_map, "PATHEXT") - .cloned() + .map(ToOwned::to_owned) .unwrap_or(".COM;.EXE;.BAT;.CMD".to_string()); let exts: Vec = default .split(';') @@ -98,13 +98,19 @@ fn reorder_pathext_for_stubs(env_map: &mut HashMap) { set_windows_env_value(env_map, "PATHEXT", combined.join(";")); } -fn windows_env_value<'a>(env_map: &'a HashMap, key: &str) -> Option<&'a String> { - env_map.get(key).or_else(|| { - env_map - .iter() - .find(|(existing, _)| existing.eq_ignore_ascii_case(key)) - .map(|(_, value)| value) - }) +pub(crate) fn windows_env_value<'a>( + env_map: &'a HashMap, + key: &str, +) -> Option<&'a str> { + env_map + .get(key) + .or_else(|| { + env_map + .iter() + .find(|(existing, _)| existing.eq_ignore_ascii_case(key)) + .map(|(_, value)| value) + }) + .map(String::as_str) } fn set_windows_env_value(env_map: &mut HashMap, key: &str, value: String) { diff --git a/codex-rs/windows-sandbox-rs/src/lib.rs b/codex-rs/windows-sandbox-rs/src/lib.rs index 61db3fe00e..a28af85c7c 100644 --- a/codex-rs/windows-sandbox-rs/src/lib.rs +++ b/codex-rs/windows-sandbox-rs/src/lib.rs @@ -241,13 +241,13 @@ pub use path_normalization::canonicalize_path; #[cfg(target_os = "windows")] pub use process::PipeSpawnHandles; #[cfg(target_os = "windows")] +#[doc(hidden)] +pub use process::ResolvedWindowsProcessLaunch; +#[cfg(target_os = "windows")] pub use process::StderrMode; #[cfg(target_os = "windows")] pub use process::StdinMode; #[cfg(target_os = "windows")] -#[doc(hidden)] -pub use process::WindowsProcessLaunch; -#[cfg(target_os = "windows")] pub use process::read_handle_loop; #[cfg(target_os = "windows")] pub use process::spawn_process_with_pipes; @@ -351,10 +351,10 @@ pub use stub::run_windows_sandbox_legacy_preflight; #[cfg(target_os = "windows")] mod windows_impl { use super::WindowsSandboxCancellationToken; + use super::command_resolution::resolve_windows_launch; use super::logging::log_failure; use super::logging::log_success; use super::process::CreateProcessAsUserRequest; - use super::process::WindowsProcessLaunch; use super::process::create_process_as_user; use super::sandbox_utils::ensure_codex_home_exists; use super::spawn_prep::LegacyAclSids; @@ -564,10 +564,7 @@ mod windows_impl { )?; let (stdin_pair, stdout_pair, stderr_pair) = unsafe { setup_stdio_pipes()? }; let ((in_r, in_w), (out_r, out_w), (err_r, err_w)) = (stdin_pair, stdout_pair, stderr_pair); - let launch = WindowsProcessLaunch { - application_path: None, - command: command.clone(), - }; + let launch = resolve_windows_launch(command.clone(), cwd, &env_map)?; let spawn_res = unsafe { create_process_as_user( security.h_token, diff --git a/codex-rs/windows-sandbox-rs/src/process.rs b/codex-rs/windows-sandbox-rs/src/process.rs index 9d9566cb5b..39f83b41ae 100644 --- a/codex-rs/windows-sandbox-rs/src/process.rs +++ b/codex-rs/windows-sandbox-rs/src/process.rs @@ -39,14 +39,11 @@ pub struct CreatedProcess { _desktop: LaunchDesktop, } -/// The command line and optional executable path supplied to a Windows process launch. -/// -/// Callers that have already resolved an executable can set `application_path` so it is passed as -/// `lpApplicationName`. A value of `None` preserves Windows' command-line lookup behavior. +/// A Windows process launch whose executable was resolved from the final child environment. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[doc(hidden)] -pub struct WindowsProcessLaunch { - pub application_path: Option, +pub struct ResolvedWindowsProcessLaunch { + pub application_path: PathBuf, pub command: Vec, } @@ -87,7 +84,7 @@ unsafe fn ensure_inheritable_stdio(si: &mut STARTUPINFOW) -> Result<()> { } pub(crate) struct CreateProcessAsUserRequest<'a> { - pub(crate) launch: &'a WindowsProcessLaunch, + pub(crate) launch: &'a ResolvedWindowsProcessLaunch, pub(crate) cwd: &'a Path, pub(crate) env_map: &'a HashMap, pub(crate) logs_base_dir: Option<&'a Path>, @@ -112,8 +109,7 @@ pub(crate) unsafe fn create_process_as_user( } = request; let cmdline_str = argv_to_command_line(&launch.command); let mut cmdline: Vec = to_wide(&cmdline_str); - let application_name = launch.application_path.as_deref().map(to_wide); - let application_name_ptr = application_name.as_ref().map_or(ptr::null(), Vec::as_ptr); + let application_name = to_wide(&launch.application_path); 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(); @@ -150,7 +146,7 @@ pub(crate) unsafe fn create_process_as_user( let creation_flags = CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT; let ok = CreateProcessAsUserW( h_token, - application_name_ptr, + application_name.as_ptr(), cmdline.as_mut_ptr(), std::ptr::null_mut(), std::ptr::null_mut(), @@ -190,7 +186,7 @@ pub(crate) unsafe fn create_process_as_user( let creation_flags = CREATE_UNICODE_ENVIRONMENT; let ok = CreateProcessAsUserW( h_token, - application_name_ptr, + application_name.as_ptr(), cmdline.as_mut_ptr(), std::ptr::null_mut(), std::ptr::null_mut(), @@ -253,7 +249,7 @@ pub struct PipeSpawnHandles { #[allow(clippy::too_many_arguments)] pub fn spawn_process_with_pipes( h_token: HANDLE, - launch: &WindowsProcessLaunch, + launch: &ResolvedWindowsProcessLaunch, cwd: &Path, env_map: &HashMap, stdin_mode: StdinMode, diff --git a/codex-rs/windows-sandbox-rs/src/spawn_prep.rs b/codex-rs/windows-sandbox-rs/src/spawn_prep.rs index d11e816420..14377020a8 100644 --- a/codex-rs/windows-sandbox-rs/src/spawn_prep.rs +++ b/codex-rs/windows-sandbox-rs/src/spawn_prep.rs @@ -12,13 +12,12 @@ use crate::command_resolution::resolve_windows_launch; use crate::deny_read_state::sync_persistent_deny_read_acls; use crate::env::apply_no_network_to_env; use crate::env::ensure_non_interactive_pager; -use crate::env::inherit_path_env; use crate::env::normalize_null_device_env; use crate::identity::SandboxCreds; use crate::identity::require_logon_sandbox_creds; use crate::logging::log_start; use crate::path_normalization::canonicalize_path; -use crate::process::WindowsProcessLaunch; +use crate::process::ResolvedWindowsProcessLaunch; use crate::resolved_permissions::ResolvedWindowsSandboxPermissions; use crate::sandbox_utils::ensure_codex_home_exists; use crate::sandbox_utils::inject_git_safe_directory; @@ -55,7 +54,7 @@ pub(crate) struct ElevatedSpawnContext { pub(crate) logs_base_dir: Option, pub(crate) sandbox_creds: SandboxCreds, pub(crate) cap_sids: Vec, - pub(crate) launch: WindowsProcessLaunch, + pub(crate) launch: ResolvedWindowsProcessLaunch, pub(crate) read_roots_override: Vec, } @@ -355,7 +354,7 @@ pub(crate) fn prepare_elevated_spawn_context_for_permissions( codex_home: &Path, cwd: &Path, env_map: &mut HashMap, - launch: WindowsProcessLaunch, + command: Vec, read_roots_override: Option<&[PathBuf]>, read_roots_include_platform_defaults: bool, write_roots_override: Option<&[PathBuf]>, @@ -366,9 +365,8 @@ pub(crate) fn prepare_elevated_spawn_context_for_permissions( ) -> Result { normalize_null_device_env(env_map); ensure_non_interactive_pager(env_map); - inherit_path_env(env_map); inject_git_safe_directory(env_map, cwd); - let launch = resolve_windows_launch(launch, cwd, env_map)?; + let launch = resolve_windows_launch(command, cwd, env_map)?; // Use a temp-based log dir that the sandbox user can write. let sandbox_base = codex_home.join(".sandbox"); @@ -407,10 +405,7 @@ pub(crate) fn prepare_elevated_spawn_context_for_permissions( let mut resolved_read_roots = read_roots_override .map(<[PathBuf]>::to_vec) .unwrap_or_else(|| gather_read_roots(cwd, &permissions, env_map, codex_home)); - let application_path = launch - .application_path - .as_ref() - .ok_or_else(|| anyhow::anyhow!("resolved Windows launch is missing an application path"))?; + let application_path = &launch.application_path; if !resolved_read_roots.contains(application_path) { resolved_read_roots.push(application_path.clone()); } diff --git a/codex-rs/windows-sandbox-rs/src/unified_exec/backends/elevated.rs b/codex-rs/windows-sandbox-rs/src/unified_exec/backends/elevated.rs index 707a8d649f..ec5eda9a8e 100644 --- a/codex-rs/windows-sandbox-rs/src/unified_exec/backends/elevated.rs +++ b/codex-rs/windows-sandbox-rs/src/unified_exec/backends/elevated.rs @@ -10,7 +10,6 @@ use crate::ipc_framed::FramedMessage; use crate::ipc_framed::IPC_PROTOCOL_VERSION; use crate::ipc_framed::Message; use crate::ipc_framed::SpawnRequest; -use crate::process::WindowsProcessLaunch; use crate::resolved_permissions::ResolvedWindowsSandboxPermissions; use crate::runner_client::RunnerTransport; use crate::runner_client::retry_runner_spawn_once; @@ -113,7 +112,7 @@ pub(crate) async fn spawn_windows_sandbox_session_elevated_for_permission_profil permission_profile: &PermissionProfile, workspace_roots: &[AbsolutePathBuf], codex_home: &Path, - launch: WindowsProcessLaunch, + command: Vec, cwd: &Path, mut env_map: HashMap, proxy_enforced: bool, @@ -146,7 +145,7 @@ pub(crate) async fn spawn_windows_sandbox_session_elevated_for_permission_profil codex_home, cwd, &mut env_map, - launch, + command, read_roots_override, read_roots_include_platform_defaults, write_roots_override, diff --git a/codex-rs/windows-sandbox-rs/src/unified_exec/backends/elevated_tests.rs b/codex-rs/windows-sandbox-rs/src/unified_exec/backends/elevated_tests.rs index a710a6378d..1c10807982 100644 --- a/codex-rs/windows-sandbox-rs/src/unified_exec/backends/elevated_tests.rs +++ b/codex-rs/windows-sandbox-rs/src/unified_exec/backends/elevated_tests.rs @@ -5,7 +5,7 @@ use crate::identity::SandboxCreds; use crate::ipc_framed::ErrorPayload; use crate::ipc_framed::ErrorStage; use crate::ipc_framed::SpawnRequest; -use crate::process::WindowsProcessLaunch; +use crate::process::ResolvedWindowsProcessLaunch; use crate::resolved_permissions::ResolvedWindowsSandboxPermissions; use crate::runner_client::RunnerStartupError; use codex_protocol::models::PermissionProfile; @@ -69,8 +69,8 @@ fn retry_uses_original_unified_exec_request_and_stops_after_second_failure() { env_map: env_map.clone(), logs_base_dir: Some(PathBuf::from(r"C:\Users\codex\.sandbox")), spawn_request: SpawnRequest { - launch: WindowsProcessLaunch { - application_path: Some(PathBuf::from(r"C:\Program Files\PowerShell\7\pwsh.exe")), + launch: ResolvedWindowsProcessLaunch { + application_path: PathBuf::from(r"C:\Program Files\PowerShell\7\pwsh.exe"), command: vec!["pwsh.exe".to_string(), "-NoProfile".to_string()], }, cwd: PathBuf::from(r"C:\workspace"), diff --git a/codex-rs/windows-sandbox-rs/src/unified_exec/backends/legacy.rs b/codex-rs/windows-sandbox-rs/src/unified_exec/backends/legacy.rs index ea543ff752..95755bb863 100644 --- a/codex-rs/windows-sandbox-rs/src/unified_exec/backends/legacy.rs +++ b/codex-rs/windows-sandbox-rs/src/unified_exec/backends/legacy.rs @@ -5,9 +5,9 @@ use crate::conpty::spawn_conpty_process_as_user; use crate::desktop::LaunchDesktop; use crate::logging::log_failure; use crate::logging::log_success; +use crate::process::ResolvedWindowsProcessLaunch; use crate::process::StderrMode; use crate::process::StdinMode; -use crate::process::WindowsProcessLaunch; use crate::process::read_handle_loop; use crate::process::spawn_process_with_pipes; use crate::spawn_prep::LegacyAclSids; @@ -60,7 +60,7 @@ struct LegacyProcessHandles { #[allow(clippy::too_many_arguments)] fn spawn_legacy_process( h_token: HANDLE, - launch: &WindowsProcessLaunch, + launch: &ResolvedWindowsProcessLaunch, cwd: &Path, env_map: &HashMap, use_private_desktop: bool, @@ -275,7 +275,7 @@ pub(crate) async fn spawn_windows_sandbox_session_legacy( permission_profile: &PermissionProfile, workspace_roots: &[AbsolutePathBuf], codex_home: &Path, - launch: WindowsProcessLaunch, + command: Vec, cwd: &Path, mut env_map: HashMap, timeout_ms: Option, @@ -291,13 +291,13 @@ pub(crate) async fn spawn_windows_sandbox_session_legacy( codex_home, cwd, &mut env_map, - &launch.command, + &command, SpawnPrepOptions { inherit_path: false, add_git_safe_directory: false, }, )?; - let launch = resolve_windows_launch(launch, cwd, &env_map)?; + let launch = resolve_windows_launch(command, cwd, &env_map)?; if !common.permissions.has_full_disk_read_access() { anyhow::bail!("Restricted read-only access requires the elevated Windows sandbox backend"); } diff --git a/codex-rs/windows-sandbox-rs/src/unified_exec/mod.rs b/codex-rs/windows-sandbox-rs/src/unified_exec/mod.rs index fa2129710e..021bb61814 100644 --- a/codex-rs/windows-sandbox-rs/src/unified_exec/mod.rs +++ b/codex-rs/windows-sandbox-rs/src/unified_exec/mod.rs @@ -9,7 +9,6 @@ mod backends; -use crate::process::WindowsProcessLaunch; use anyhow::Result; use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::PermissionProfile; @@ -19,7 +18,7 @@ use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; -/// Fully resolved Windows sandbox session launch request. +/// Windows sandbox session launch request. /// /// Callers should parse their own input shape first, then use this request to /// share the elevated-vs-legacy backend selection and session launch path. @@ -27,7 +26,7 @@ pub struct WindowsSandboxSessionRequest<'a> { pub permission_profile: &'a PermissionProfile, pub workspace_roots: &'a [AbsolutePathBuf], pub codex_home: &'a Path, - pub launch: WindowsProcessLaunch, + pub command: Vec, pub cwd: &'a Path, pub env_map: HashMap, pub windows_sandbox_level: WindowsSandboxLevel, @@ -54,7 +53,7 @@ pub async fn spawn_windows_sandbox_session_for_level( request.permission_profile, request.workspace_roots, request.codex_home, - request.launch, + request.command, request.cwd, request.env_map, request.proxy_enforced, @@ -75,7 +74,7 @@ pub async fn spawn_windows_sandbox_session_for_level( request.permission_profile, request.workspace_roots, request.codex_home, - request.launch, + request.command, request.cwd, request.env_map, request.timeout_ms, diff --git a/codex-rs/windows-sandbox-rs/src/unified_exec/tests.rs b/codex-rs/windows-sandbox-rs/src/unified_exec/tests.rs index 3d286effa2..2f9633f24c 100644 --- a/codex-rs/windows-sandbox-rs/src/unified_exec/tests.rs +++ b/codex-rs/windows-sandbox-rs/src/unified_exec/tests.rs @@ -1,15 +1,11 @@ #![cfg(target_os = "windows")] -use super::WindowsSandboxSessionRequest; use super::backends::legacy::spawn_windows_sandbox_session_legacy; -use super::spawn_windows_sandbox_session_for_level; -use crate::WindowsProcessLaunch; use crate::WindowsSandboxCancellationToken; use crate::ipc_framed::Message; use crate::ipc_framed::decode_bytes; use crate::ipc_framed::read_frame; use crate::run_windows_sandbox_capture; -use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::PermissionProfile; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_pty::ProcessDriver; @@ -87,13 +83,6 @@ fn workspace_roots_for(root: &Path) -> Vec { vec![AbsolutePathBuf::from_absolute_path(root).expect("absolute workspace root")] } -fn unresolved_launch(command: Vec) -> WindowsProcessLaunch { - WindowsProcessLaunch { - application_path: None, - command, - } -} - fn wait_for_frame_count(frames_path: &Path, expected_frames: usize) -> Vec { let deadline = Instant::now() + Duration::from_secs(2); loop { @@ -173,11 +162,11 @@ fn legacy_non_tty_cmd_emits_output() { &permission_profile, workspace_roots_for(cwd.as_path()).as_slice(), codex_home.path(), - unresolved_launch(vec![ + vec![ "C:\\Windows\\System32\\cmd.exe".to_string(), "/c".to_string(), "echo LEGACY-NONTTY-CMD".to_string(), - ]), + ], cwd.as_path(), HashMap::new(), Some(5_000), @@ -199,56 +188,6 @@ fn legacy_non_tty_cmd_emits_output() { }); } -#[test] -fn legacy_non_tty_uses_explicit_application_path() { - let _guard = legacy_process_test_guard(); - let runtime = current_thread_runtime(); - runtime.block_on(async move { - let cwd = sandbox_cwd(); - let codex_home = sandbox_home("legacy-explicit-application-path"); - let permission_profile = PermissionProfile::workspace_write(); - let workspace_roots = workspace_roots_for(cwd.as_path()); - let spawned = spawn_windows_sandbox_session_for_level(WindowsSandboxSessionRequest { - permission_profile: &permission_profile, - workspace_roots: workspace_roots.as_slice(), - codex_home: codex_home.path(), - launch: WindowsProcessLaunch { - application_path: Some(PathBuf::from(r"C:\Windows\System32\cmd.exe")), - command: vec![ - "not-the-real-executable.exe".to_string(), - "/c".to_string(), - "echo EXPLICIT-APPLICATION-PATH".to_string(), - ], - }, - cwd: cwd.as_path(), - env_map: HashMap::new(), - windows_sandbox_level: WindowsSandboxLevel::RestrictedToken, - proxy_enforced: false, - proxy_settings_mode: crate::WindowsSandboxProxySettingsMode::Reconcile, - timeout_ms: Some(5_000), - read_roots_override: None, - read_roots_include_platform_defaults: false, - write_roots_override: None, - deny_read_paths_override: &[], - deny_write_paths_override: &[], - tty: false, - stdin_open: false, - use_private_desktop: true, - }) - .await - .expect("spawn with explicit application path"); - - let (stdout, exit_code) = - collect_stdout_and_exit(spawned, codex_home.path(), Duration::from_secs(10)).await; - let stdout = String::from_utf8_lossy(&stdout); - assert_eq!(exit_code, 0, "stdout={stdout:?}"); - assert!( - stdout.contains("EXPLICIT-APPLICATION-PATH"), - "stdout={stdout:?}" - ); - }); -} - #[test] fn legacy_resolves_request_pathext() { let _guard = legacy_process_test_guard(); @@ -312,11 +251,11 @@ fn legacy_non_tty_cmd_rejects_deny_read_overrides() { &permission_profile, workspace_roots_for(cwd.as_path()).as_slice(), codex_home.path(), - unresolved_launch(vec![ + vec![ "C:\\Windows\\System32\\cmd.exe".to_string(), "/c".to_string(), "echo deny-read".to_string(), - ]), + ], cwd.as_path(), HashMap::new(), Some(5_000), @@ -352,12 +291,12 @@ fn legacy_non_tty_powershell_emits_output() { &permission_profile, workspace_roots_for(cwd.as_path()).as_slice(), codex_home.path(), - unresolved_launch(vec![ + vec![ pwsh.display().to_string(), "-NoProfile".to_string(), "-Command".to_string(), "Write-Output LEGACY-NONTTY-DIRECT".to_string(), - ]), + ], cwd.as_path(), HashMap::new(), Some(5_000), @@ -630,14 +569,14 @@ fn legacy_tty_powershell_emits_output_and_accepts_input() { &permission_profile, workspace_roots_for(cwd.as_path()).as_slice(), codex_home.path(), - unresolved_launch(vec![ + vec![ pwsh.display().to_string(), "-NoLogo".to_string(), "-NoProfile".to_string(), "-NoExit".to_string(), "-Command".to_string(), "$PID; Write-Output ready".to_string(), - ]), + ], cwd.as_path(), HashMap::new(), Some(10_000), @@ -684,11 +623,11 @@ fn legacy_tty_cmd_emits_output_and_accepts_input() { &permission_profile, workspace_roots_for(cwd.as_path()).as_slice(), codex_home.path(), - unresolved_launch(vec![ + vec![ "C:\\Windows\\System32\\cmd.exe".to_string(), "/K".to_string(), "echo ready".to_string(), - ]), + ], cwd.as_path(), HashMap::new(), Some(10_000), @@ -738,11 +677,11 @@ fn legacy_tty_cmd_default_desktop_emits_output_and_accepts_input() { &permission_profile, workspace_roots_for(cwd.as_path()).as_slice(), codex_home.path(), - unresolved_launch(vec![ + vec![ "C:\\Windows\\System32\\cmd.exe".to_string(), "/K".to_string(), "echo ready".to_string(), - ]), + ], cwd.as_path(), HashMap::new(), Some(10_000), diff --git a/codex-rs/windows-sandbox-rs/src/wrapper.rs b/codex-rs/windows-sandbox-rs/src/wrapper.rs index 7ee1a30635..f02af26133 100644 --- a/codex-rs/windows-sandbox-rs/src/wrapper.rs +++ b/codex-rs/windows-sandbox-rs/src/wrapper.rs @@ -177,10 +177,7 @@ async fn run_windows_sandbox_wrapper_request(request: WindowsSandboxWrapperReque permission_profile: &request.permission_profile, workspace_roots: request.workspace_roots.as_slice(), codex_home: request.codex_home.as_path(), - launch: crate::WindowsProcessLaunch { - application_path: None, - command: request.command, - }, + command: request.command, cwd: request.command_cwd.as_path(), env_map: request.env_map, windows_sandbox_level: request.windows_sandbox_level,