Plumb explicit application paths through Windows launchers

This commit is contained in:
jif-oai
2026-06-25 11:16:54 +01:00
parent bb05c1f30f
commit 7a52e551db
13 changed files with 282 additions and 63 deletions
+5 -1
View File
@@ -473,6 +473,7 @@ 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;
@@ -482,7 +483,10 @@ async fn run_command_under_windows_session(
permission_profile,
workspace_roots: workspace_roots.as_slice(),
codex_home: config.codex_home.as_path(),
command,
launch: WindowsProcessLaunch {
application_path: None,
command,
},
cwd: cwd.as_path(),
env_map: env,
windows_sandbox_level: WindowsSandboxLevel::from_config(config),
@@ -39,7 +39,7 @@ use codex_windows_sandbox::hide_current_user_profile_dir;
use codex_windows_sandbox::log_note;
use codex_windows_sandbox::read_frame;
use codex_windows_sandbox::read_handle_loop;
use codex_windows_sandbox::spawn_process_with_pipes;
use codex_windows_sandbox::spawn_process_with_pipes_with_launch;
use codex_windows_sandbox::to_wide;
use codex_windows_sandbox::token_mode_for_permission_profile;
use codex_windows_sandbox::write_frame;
@@ -300,9 +300,9 @@ fn spawn_ipc_process(req: &SpawnRequest) -> Result<IpcSpawnedProcess> {
let mut hpc_handle: Option<HANDLE> = None;
let mut pipe_handles = None;
let (pi, stdout_handle, stderr_handle, stdin_handle) = if req.tty {
let (pi, mut conpty) = codex_windows_sandbox::spawn_conpty_process_as_user(
let (pi, mut conpty) = codex_windows_sandbox::spawn_conpty_process_as_user_with_launch(
h_token.raw(),
&req.command,
&req.launch,
&effective_cwd,
&req.env,
req.use_private_desktop,
@@ -332,9 +332,9 @@ fn spawn_ipc_process(req: &SpawnRequest) -> Result<IpcSpawnedProcess> {
} else {
StdinMode::Closed
};
let spawned_pipes: PipeSpawnHandles = spawn_process_with_pipes(
let spawned_pipes: PipeSpawnHandles = spawn_process_with_pipes_with_launch(
h_token.raw(),
&req.command,
&req.launch,
&effective_cwd,
&req.env,
stdin_mode,
+32 -2
View File
@@ -8,6 +8,7 @@
use crate::desktop::LaunchDesktop;
use crate::proc_thread_attr::ProcThreadAttributeList;
use crate::process::WindowsProcessLaunch;
use crate::winutil::format_last_error;
use crate::winutil::quote_windows_arg;
use crate::winutil::to_wide;
@@ -99,12 +100,41 @@ pub fn spawn_conpty_process_as_user(
use_private_desktop: bool,
logs_base_dir: Option<&Path>,
) -> Result<(PROCESS_INFORMATION, ConptyInstance)> {
let cmdline_str = argv
let launch = WindowsProcessLaunch {
application_path: None,
command: argv.to_vec(),
};
spawn_conpty_process_as_user_with_launch(
h_token,
&launch,
cwd,
env_map,
use_private_desktop,
logs_base_dir,
)
}
/// Spawns a ConPTY process with an optional explicit `lpApplicationName`.
#[doc(hidden)]
pub fn spawn_conpty_process_as_user_with_launch(
h_token: HANDLE,
launch: &WindowsProcessLaunch,
cwd: &Path,
env_map: &HashMap<String, String>,
use_private_desktop: bool,
logs_base_dir: Option<&Path>,
) -> Result<(PROCESS_INFORMATION, ConptyInstance)> {
let cmdline_str = launch
.command
.iter()
.map(|arg| quote_windows_arg(arg))
.collect::<Vec<_>>()
.join(" ");
let mut cmdline: Vec<u16> = 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 env_block = make_env_block(env_map);
let mut si: STARTUPINFOEXW = unsafe { std::mem::zeroed() };
si.StartupInfo.cb = std::mem::size_of::<STARTUPINFOEXW>() as u32;
@@ -132,7 +162,7 @@ pub fn spawn_conpty_process_as_user(
let ok = unsafe {
CreateProcessAsUserW(
h_token,
std::ptr::null(),
application_name_ptr,
cmdline.as_mut_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
@@ -7,6 +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 anyhow::Result;
use base64::Engine as _;
use base64::engine::general_purpose::STANDARD;
@@ -26,7 +27,7 @@ use std::path::PathBuf;
const MAX_FRAME_LEN: usize = 8 * 1024 * 1024;
/// Protocol version shared by the parent process and elevated command runner.
pub const IPC_PROTOCOL_VERSION: u8 = 4;
pub const IPC_PROTOCOL_VERSION: u8 = 5;
/// Length-prefixed, JSON-encoded frame.
#[derive(Debug, Serialize, Deserialize, Clone)]
@@ -57,7 +58,7 @@ pub enum Message {
/// Spawn parameters sent from parent to runner.
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SpawnRequest {
pub command: Vec<String>,
pub launch: WindowsProcessLaunch,
pub cwd: PathBuf,
pub env: HashMap<String, String>,
pub permission_profile: PermissionProfile,
@@ -212,11 +213,15 @@ 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")),
command: vec!["cmd.exe".to_string(), "/c".to_string(), "ver".to_string()],
};
let msg = FramedMessage {
version: IPC_PROTOCOL_VERSION,
message: Message::SpawnRequest {
payload: Box::new(SpawnRequest {
command: vec!["cmd.exe".to_string(), "/c".to_string(), "ver".to_string()],
launch: launch.clone(),
cwd: PathBuf::from(r"C:\workspace"),
env: HashMap::new(),
permission_profile: PermissionProfile::read_only(),
@@ -245,6 +250,7 @@ mod tests {
};
assert_eq!(PermissionProfile::read_only(), payload.permission_profile);
assert_eq!(workspace_roots, payload.workspace_roots);
assert_eq!(launch, payload.launch);
}
#[test]
@@ -43,6 +43,7 @@ mod windows_impl {
use crate::logging::log_failure;
use crate::logging::log_start;
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;
@@ -182,7 +183,10 @@ mod windows_impl {
(|| -> Result<CaptureResult> {
let spawn_request = SpawnRequest {
command: command.clone(),
launch: WindowsProcessLaunch {
application_path: None,
command: command.clone(),
},
cwd: cwd.to_path_buf(),
env: env_map.clone(),
permission_profile: permission_profile.clone(),
@@ -197,7 +201,7 @@ mod windows_impl {
};
let transport = retry_runner_spawn_once(
sandbox_creds,
&spawn_request.command,
&spawn_request.launch.command,
|sandbox_creds| {
spawn_runner_transport(
codex_home,
+23 -8
View File
@@ -164,6 +164,8 @@ pub use conpty::ConptyInstance;
#[cfg(target_os = "windows")]
pub use conpty::spawn_conpty_process_as_user;
#[cfg(target_os = "windows")]
pub use conpty::spawn_conpty_process_as_user_with_launch;
#[cfg(target_os = "windows")]
pub use deny_read_acl::apply_deny_read_acls;
#[cfg(target_os = "windows")]
pub use deny_read_acl::plan_deny_read_acl_paths;
@@ -241,12 +243,17 @@ 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::create_process_as_user;
#[cfg(target_os = "windows")]
pub use process::read_handle_loop;
#[cfg(target_os = "windows")]
pub use process::spawn_process_with_pipes;
#[cfg(target_os = "windows")]
pub use process::spawn_process_with_pipes_with_launch;
#[cfg(target_os = "windows")]
pub use resolved_permissions::ResolvedWindowsSandboxPermissions;
#[cfg(target_os = "windows")]
pub use resolved_permissions::WindowsSandboxTokenMode;
@@ -352,7 +359,9 @@ mod windows_impl {
use super::WindowsSandboxCancellationToken;
use super::logging::log_failure;
use super::logging::log_success;
use super::process::create_process_as_user;
use super::process::CreateProcessAsUserRequest;
use super::process::WindowsProcessLaunch;
use super::process::create_process_as_user_with_launch;
use super::sandbox_utils::ensure_codex_home_exists;
use super::spawn_prep::LegacyAclSids;
use super::spawn_prep::SpawnPrepOptions;
@@ -561,15 +570,21 @@ 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 spawn_res = unsafe {
create_process_as_user(
create_process_as_user_with_launch(
security.h_token,
&command,
cwd,
&env_map,
logs_base_dir,
Some((in_r, out_w, err_w)),
use_private_desktop,
CreateProcessAsUserRequest {
launch: &launch,
cwd,
env_map: &env_map,
logs_base_dir,
stdio: Some((in_r, out_w, err_w)),
use_private_desktop,
},
)
};
let created = match spawn_res {
+108 -20
View File
@@ -7,9 +7,12 @@ use crate::winutil::to_wide;
use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::ffi::c_void;
use std::path::Path;
use std::path::PathBuf;
use std::ptr;
use windows_sys::Win32::Foundation::CloseHandle;
use windows_sys::Win32::Foundation::GetLastError;
@@ -37,6 +40,17 @@ 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.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[doc(hidden)]
pub struct WindowsProcessLaunch {
pub application_path: Option<PathBuf>,
pub command: Vec<String>,
}
pub fn make_env_block(env: &HashMap<String, String>) -> Vec<u16> {
let mut items: Vec<(String, String)> =
env.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
@@ -73,20 +87,34 @@ unsafe fn ensure_inheritable_stdio(si: &mut STARTUPINFOW) -> Result<()> {
Ok(())
}
pub(crate) struct CreateProcessAsUserRequest<'a> {
pub(crate) launch: &'a WindowsProcessLaunch,
pub(crate) cwd: &'a Path,
pub(crate) env_map: &'a HashMap<String, String>,
pub(crate) logs_base_dir: Option<&'a Path>,
pub(crate) stdio: Option<(HANDLE, HANDLE, HANDLE)>,
pub(crate) use_private_desktop: bool,
}
/// # Safety
/// Caller must provide a valid primary token handle (`h_token`) with appropriate access,
/// and the `argv`, `cwd`, and `env_map` must remain valid for the duration of the call.
pub unsafe fn create_process_as_user(
/// Caller must provide a valid primary token handle (`h_token`) with appropriate access, and all
/// borrowed request fields must remain valid for the duration of the call.
pub(crate) unsafe fn create_process_as_user_with_launch(
h_token: HANDLE,
argv: &[String],
cwd: &Path,
env_map: &HashMap<String, String>,
logs_base_dir: Option<&Path>,
stdio: Option<(HANDLE, HANDLE, HANDLE)>,
use_private_desktop: bool,
request: CreateProcessAsUserRequest<'_>,
) -> Result<CreatedProcess> {
let cmdline_str = argv_to_command_line(argv);
let CreateProcessAsUserRequest {
launch,
cwd,
env_map,
logs_base_dir,
stdio,
use_private_desktop,
} = request;
let cmdline_str = argv_to_command_line(&launch.command);
let mut cmdline: Vec<u16> = 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 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();
@@ -123,7 +151,7 @@ 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_ptr,
cmdline.as_mut_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
@@ -164,7 +192,7 @@ pub unsafe fn create_process_as_user(
let creation_flags = CREATE_UNICODE_ENVIRONMENT;
let ok = CreateProcessAsUserW(
h_token,
std::ptr::null(),
application_name_ptr,
cmdline.as_mut_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
@@ -199,6 +227,36 @@ pub unsafe fn create_process_as_user(
}
}
/// # Safety
/// Caller must provide a valid primary token handle (`h_token`) with appropriate access, and the
/// borrowed command inputs must remain valid for the duration of the call.
#[allow(clippy::too_many_arguments)]
pub unsafe fn create_process_as_user(
h_token: HANDLE,
argv: &[String],
cwd: &Path,
env_map: &HashMap<String, String>,
logs_base_dir: Option<&Path>,
stdio: Option<(HANDLE, HANDLE, HANDLE)>,
use_private_desktop: bool,
) -> Result<CreatedProcess> {
let launch = WindowsProcessLaunch {
application_path: None,
command: argv.to_vec(),
};
create_process_as_user_with_launch(
h_token,
CreateProcessAsUserRequest {
launch: &launch,
cwd,
env_map,
logs_base_dir,
stdio,
use_private_desktop,
},
)
}
/// Controls whether the child's stdin handle is kept open for writing.
#[allow(dead_code)]
pub enum StdinMode {
@@ -234,6 +292,35 @@ pub fn spawn_process_with_pipes(
stderr_mode: StderrMode,
use_private_desktop: bool,
logs_base_dir: Option<&Path>,
) -> Result<PipeSpawnHandles> {
let launch = WindowsProcessLaunch {
application_path: None,
command: argv.to_vec(),
};
spawn_process_with_pipes_with_launch(
h_token,
&launch,
cwd,
env_map,
stdin_mode,
stderr_mode,
use_private_desktop,
logs_base_dir,
)
}
/// Spawns a process with an optional explicit `lpApplicationName`.
#[doc(hidden)]
#[allow(clippy::too_many_arguments)]
pub fn spawn_process_with_pipes_with_launch(
h_token: HANDLE,
launch: &WindowsProcessLaunch,
cwd: &Path,
env_map: &HashMap<String, String>,
stdin_mode: StdinMode,
stderr_mode: StderrMode,
use_private_desktop: bool,
logs_base_dir: Option<&Path>,
) -> Result<PipeSpawnHandles> {
let mut in_r: HANDLE = 0;
let mut in_w: HANDLE = 0;
@@ -266,16 +353,17 @@ pub fn spawn_process_with_pipes(
StderrMode::Separate => err_w,
};
let stdio = Some((in_r, out_w, stderr_handle));
let spawn_result = unsafe {
create_process_as_user(
create_process_as_user_with_launch(
h_token,
argv,
cwd,
env_map,
logs_base_dir,
stdio,
use_private_desktop,
CreateProcessAsUserRequest {
launch,
cwd,
env_map,
logs_base_dir,
stdio: Some((in_r, out_w, stderr_handle)),
use_private_desktop,
},
)
};
let created = match spawn_result {
@@ -10,6 +10,7 @@ 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;
@@ -63,7 +64,7 @@ fn spawn_runner_transport_with_retry<T>(
) -> Result<T> {
retry_runner_spawn_once(
sandbox_creds,
&request.spawn_request.command,
&request.spawn_request.launch.command,
|sandbox_creds| {
spawn(
&request.codex_home,
@@ -112,7 +113,7 @@ pub(crate) async fn spawn_windows_sandbox_session_elevated_for_permission_profil
permission_profile: &PermissionProfile,
workspace_roots: &[AbsolutePathBuf],
codex_home: &Path,
command: Vec<String>,
launch: WindowsProcessLaunch,
cwd: &Path,
mut env_map: HashMap<String, String>,
proxy_enforced: bool,
@@ -145,7 +146,7 @@ pub(crate) async fn spawn_windows_sandbox_session_elevated_for_permission_profil
codex_home,
cwd,
&mut env_map,
&command,
&launch.command,
read_roots_override,
read_roots_include_platform_defaults,
write_roots_override,
@@ -163,7 +164,7 @@ pub(crate) async fn spawn_windows_sandbox_session_elevated_for_permission_profil
env_map: env_map.clone(),
logs_base_dir: elevated.logs_base_dir,
spawn_request: SpawnRequest {
command,
launch,
cwd: cwd.to_path_buf(),
env: env_map,
permission_profile: permission_profile.clone(),
@@ -5,6 +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::resolved_permissions::ResolvedWindowsSandboxPermissions;
use crate::runner_client::RunnerStartupError;
use codex_protocol::models::PermissionProfile;
@@ -68,7 +69,10 @@ 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 {
command: vec!["pwsh.exe".to_string(), "-NoProfile".to_string()],
launch: WindowsProcessLaunch {
application_path: Some(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"),
env: env_map.clone(),
permission_profile,
@@ -1,13 +1,14 @@
use super::windows_common::finish_driver_spawn;
use crate::conpty::ConptyInstance;
use crate::conpty::spawn_conpty_process_as_user;
use crate::conpty::spawn_conpty_process_as_user_with_launch;
use crate::desktop::LaunchDesktop;
use crate::logging::log_failure;
use crate::logging::log_success;
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::process::spawn_process_with_pipes_with_launch;
use crate::spawn_prep::LegacyAclSids;
use crate::spawn_prep::SpawnPrepOptions;
use crate::spawn_prep::allow_null_device_for_workspace_write;
@@ -58,7 +59,7 @@ struct LegacyProcessHandles {
#[allow(clippy::too_many_arguments)]
fn spawn_legacy_process(
h_token: HANDLE,
command: &[String],
launch: &WindowsProcessLaunch,
cwd: &Path,
env_map: &HashMap<String, String>,
use_private_desktop: bool,
@@ -70,9 +71,9 @@ fn spawn_legacy_process(
logs_base_dir: Option<&Path>,
) -> Result<LegacyProcessHandles> {
let (pi, output_join, writer_handle, hpc, conpty_owner, desktop) = if tty {
let (pi, mut conpty) = spawn_conpty_process_as_user(
let (pi, mut conpty) = spawn_conpty_process_as_user_with_launch(
h_token,
command,
launch,
cwd,
env_map,
use_private_desktop,
@@ -87,9 +88,9 @@ fn spawn_legacy_process(
);
(pi, output_join, writer_handle, hpc, Some(conpty), None)
} else {
let pipe_handles = spawn_process_with_pipes(
let pipe_handles = spawn_process_with_pipes_with_launch(
h_token,
command,
launch,
cwd,
env_map,
if stdin_open {
@@ -273,7 +274,7 @@ pub(crate) async fn spawn_windows_sandbox_session_legacy(
permission_profile: &PermissionProfile,
workspace_roots: &[AbsolutePathBuf],
codex_home: &Path,
command: Vec<String>,
launch: WindowsProcessLaunch,
cwd: &Path,
mut env_map: HashMap<String, String>,
timeout_ms: Option<u64>,
@@ -289,7 +290,7 @@ pub(crate) async fn spawn_windows_sandbox_session_legacy(
codex_home,
cwd,
&mut env_map,
&command,
&launch.command,
SpawnPrepOptions {
inherit_path: false,
add_git_safe_directory: false,
@@ -354,7 +355,7 @@ pub(crate) async fn spawn_windows_sandbox_session_legacy(
desktop,
} = match spawn_legacy_process(
security.h_token,
&command,
&launch,
cwd,
&env_map,
use_private_desktop,
@@ -377,7 +378,7 @@ pub(crate) async fn spawn_windows_sandbox_session_legacy(
let process_handle = Arc::new(StdMutex::new(Some(pi.hProcess)));
let wait_handle = Arc::clone(&process_handle);
let command_for_wait = command.clone();
let command_for_wait = launch.command.clone();
let hpc_for_wait = hpc_handle.clone();
std::thread::spawn(move || {
let _desktop = desktop;
@@ -9,6 +9,7 @@
mod backends;
use crate::process::WindowsProcessLaunch;
use anyhow::Result;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfile;
@@ -26,7 +27,7 @@ pub struct WindowsSandboxSessionRequest<'a> {
pub permission_profile: &'a PermissionProfile,
pub workspace_roots: &'a [AbsolutePathBuf],
pub codex_home: &'a Path,
pub command: Vec<String>,
pub launch: WindowsProcessLaunch,
pub cwd: &'a Path,
pub env_map: HashMap<String, String>,
pub windows_sandbox_level: WindowsSandboxLevel,
@@ -53,7 +54,7 @@ pub async fn spawn_windows_sandbox_session_for_level(
request.permission_profile,
request.workspace_roots,
request.codex_home,
request.command,
request.launch,
request.cwd,
request.env_map,
request.proxy_enforced,
@@ -74,7 +75,7 @@ pub async fn spawn_windows_sandbox_session_for_level(
request.permission_profile,
request.workspace_roots,
request.codex_home,
request.command,
request.launch,
request.cwd,
request.env_map,
request.timeout_ms,
@@ -103,11 +104,15 @@ pub async fn spawn_windows_sandbox_session_legacy(
stdin_open: bool,
use_private_desktop: bool,
) -> Result<SpawnedProcess> {
let launch = WindowsProcessLaunch {
application_path: None,
command,
};
backends::legacy::spawn_windows_sandbox_session_legacy(
permission_profile,
workspace_roots,
codex_home,
command,
launch,
cwd,
env_map,
timeout_ms,
@@ -139,11 +144,15 @@ pub async fn spawn_windows_sandbox_session_elevated_for_permission_profile(
stdin_open: bool,
use_private_desktop: bool,
) -> Result<SpawnedProcess> {
let launch = WindowsProcessLaunch {
application_path: None,
command,
};
backends::elevated::spawn_windows_sandbox_session_elevated_for_permission_profile(
permission_profile,
workspace_roots,
codex_home,
command,
launch,
cwd,
env_map,
proxy_enforced,
@@ -1,11 +1,15 @@
#![cfg(target_os = "windows")]
use super::WindowsSandboxSessionRequest;
use super::spawn_windows_sandbox_session_for_level;
use super::spawn_windows_sandbox_session_legacy;
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;
@@ -188,6 +192,56 @@ 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_non_tty_cmd_rejects_deny_read_overrides() {
let _guard = legacy_process_test_guard();
+4 -1
View File
@@ -177,7 +177,10 @@ 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(),
command: request.command,
launch: crate::WindowsProcessLaunch {
application_path: None,
command: request.command,
},
cwd: request.command_cwd.as_path(),
env_map: request.env_map,
windows_sandbox_level: request.windows_sandbox_level,