diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index 6eb3c585c3..2d7f3a164e 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -431,6 +431,39 @@ fn record_windows_sandbox_spawn_failure( } } +#[cfg(target_os = "windows")] +fn is_powershell_executable(path: &str) -> bool { + matches!( + Path::new(path) + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or(path) + .to_ascii_lowercase() + .as_str(), + "powershell" | "powershell.exe" | "pwsh" | "pwsh.exe" + ) +} + +#[cfg(target_os = "windows")] +fn ensure_powershell_no_profile_for_sandbox(mut command: Vec) -> Vec { + let Some(program) = command.first() else { + return command; + }; + if !is_powershell_executable(program) { + return command; + } + if command + .iter() + .skip(1) + .any(|arg| arg.eq_ignore_ascii_case("-NoProfile") || arg.eq_ignore_ascii_case("/NoProfile")) + { + return command; + } + + command.insert(1, "-NoProfile".to_string()); + command +} + #[cfg(target_os = "windows")] async fn exec_windows_sandbox( params: ExecParams, @@ -455,6 +488,7 @@ async fn exec_windows_sandbox( if let Some(network) = network.as_ref() { network.apply_to_env(&mut env); } + let command = ensure_powershell_no_profile_for_sandbox(command); // TODO(iceweasel-oai): run_windows_sandbox_capture should support all // variants of ExecExpiration, not just timeout. diff --git a/codex-rs/core/src/exec_tests.rs b/codex-rs/core/src/exec_tests.rs index 3cde807c0f..30b6e0365a 100644 --- a/codex-rs/core/src/exec_tests.rs +++ b/codex-rs/core/src/exec_tests.rs @@ -77,6 +77,61 @@ fn sandbox_detection_uses_aggregated_output() { )); } +#[cfg(target_os = "windows")] +#[test] +fn windows_sandbox_powershell_inserts_no_profile() { + let command = ensure_powershell_no_profile_for_sandbox(vec![ + "powershell.exe".to_string(), + "-Command".to_string(), + "Write-Output ok".to_string(), + ]); + + assert_eq!( + command, + vec![ + "powershell.exe".to_string(), + "-NoProfile".to_string(), + "-Command".to_string(), + "Write-Output ok".to_string(), + ] + ); +} + +#[cfg(target_os = "windows")] +#[test] +fn windows_sandbox_powershell_preserves_existing_no_profile() { + let command = ensure_powershell_no_profile_for_sandbox(vec![ + "pwsh.exe".to_string(), + "-NoLogo".to_string(), + "-NoProfile".to_string(), + "-Command".to_string(), + "Write-Output ok".to_string(), + ]); + + assert_eq!( + command, + vec![ + "pwsh.exe".to_string(), + "-NoLogo".to_string(), + "-NoProfile".to_string(), + "-Command".to_string(), + "Write-Output ok".to_string(), + ] + ); +} + +#[cfg(target_os = "windows")] +#[test] +fn windows_sandbox_powershell_detects_full_path() { + let command = ensure_powershell_no_profile_for_sandbox(vec![ + r"C:\Program Files\PowerShell\7\pwsh.exe".to_string(), + "-Command".to_string(), + "Write-Output ok".to_string(), + ]); + + assert_eq!(command[1], "-NoProfile"); +} + #[test] fn sandbox_detection_ignores_network_policy_text_with_zero_exit_code() { let output = make_exec_output( diff --git a/codex-rs/core/src/tools/spec_tests.rs b/codex-rs/core/src/tools/spec_tests.rs index a635a0f9e3..cc1836515b 100644 --- a/codex-rs/core/src/tools/spec_tests.rs +++ b/codex-rs/core/src/tools/spec_tests.rs @@ -174,6 +174,7 @@ fn multi_agent_v2_tools_config() -> ToolsConfig { session_source: SessionSource::Cli, sandbox_policy: &SandboxPolicy::DangerFullAccess, windows_sandbox_level: WindowsSandboxLevel::Disabled, + image_generation_tool_auth_allowed: true, }) }