mirror of
https://github.com/openai/codex.git
synced 2026-10-26 18:38:47 +00:00
Tighten Windows launch resolution boundary
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<String>,
|
||||
cwd: &Path,
|
||||
env_map: &HashMap<String, String>,
|
||||
) -> Result<WindowsProcessLaunch> {
|
||||
if launch.application_path.is_none() {
|
||||
launch.application_path = Some(resolve_windows_executable(&launch.command, cwd, env_map)?);
|
||||
}
|
||||
Ok(launch)
|
||||
) -> Result<ResolvedWindowsProcessLaunch> {
|
||||
let application_path = resolve_windows_executable(&command, cwd, env_map)?;
|
||||
Ok(ResolvedWindowsProcessLaunch {
|
||||
application_path,
|
||||
command,
|
||||
})
|
||||
}
|
||||
|
||||
fn windows_search_dirs(cwd: &Path, env_map: &HashMap<String, String>) -> Vec<PathBuf> {
|
||||
@@ -161,18 +163,6 @@ fn is_drive_relative(path: &Path) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
fn windows_env_value<'a>(env_map: &'a HashMap<String, String>, 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;
|
||||
|
||||
@@ -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()],
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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<ConptyInstance> {
|
||||
#[doc(hidden)]
|
||||
pub fn spawn_conpty_process_as_user(
|
||||
h_token: HANDLE,
|
||||
launch: &WindowsProcessLaunch,
|
||||
launch: &ResolvedWindowsProcessLaunch,
|
||||
cwd: &Path,
|
||||
env_map: &HashMap<String, String>,
|
||||
use_private_desktop: bool,
|
||||
@@ -105,10 +105,7 @@ pub fn spawn_conpty_process_as_user(
|
||||
.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 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::<STARTUPINFOEXW>() 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(),
|
||||
|
||||
@@ -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<String, String>,
|
||||
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 {
|
||||
|
||||
@@ -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::<RunnerLogonError>()
|
||||
@@ -143,7 +137,7 @@ pub(crate) fn is_refreshable_sandbox_creds_error(
|
||||
|
||||
pub(crate) fn retry_runner_spawn_once<T>(
|
||||
sandbox_creds: SandboxCreds,
|
||||
launch: &WindowsProcessLaunch,
|
||||
launch: &ResolvedWindowsProcessLaunch,
|
||||
mut spawn: impl FnMut(SandboxCreds) -> Result<T>,
|
||||
refresh: impl FnOnce() -> Result<SandboxCreds>,
|
||||
) -> Result<T> {
|
||||
@@ -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()],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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::<Vec<_>>();
|
||||
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,
|
||||
|
||||
@@ -31,7 +31,7 @@ pub fn ensure_non_interactive_pager(env_map: &mut HashMap<String, String>) {
|
||||
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<String, String>) {
|
||||
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<String, String>) {
|
||||
|
||||
fn prepend_path(env_map: &mut HashMap<String, String>, prefix: &str) {
|
||||
let existing = windows_env_value(env_map, "PATH")
|
||||
.cloned()
|
||||
.map(ToOwned::to_owned)
|
||||
.unwrap_or_default();
|
||||
let parts: Vec<String> = existing.split(';').map(ToString::to_string).collect();
|
||||
if parts
|
||||
@@ -68,7 +68,7 @@ fn prepend_path(env_map: &mut HashMap<String, String>, prefix: &str) {
|
||||
|
||||
fn reorder_pathext_for_stubs(env_map: &mut HashMap<String, String>) {
|
||||
let default = windows_env_value(env_map, "PATHEXT")
|
||||
.cloned()
|
||||
.map(ToOwned::to_owned)
|
||||
.unwrap_or(".COM;.EXE;.BAT;.CMD".to_string());
|
||||
let exts: Vec<String> = default
|
||||
.split(';')
|
||||
@@ -98,13 +98,19 @@ fn reorder_pathext_for_stubs(env_map: &mut HashMap<String, String>) {
|
||||
set_windows_env_value(env_map, "PATHEXT", combined.join(";"));
|
||||
}
|
||||
|
||||
fn windows_env_value<'a>(env_map: &'a HashMap<String, String>, 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<String, String>,
|
||||
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<String, String>, key: &str, value: String) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<PathBuf>,
|
||||
pub struct ResolvedWindowsProcessLaunch {
|
||||
pub application_path: PathBuf,
|
||||
pub command: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -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<String, String>,
|
||||
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<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 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<String, String>,
|
||||
stdin_mode: StdinMode,
|
||||
|
||||
@@ -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<PathBuf>,
|
||||
pub(crate) sandbox_creds: SandboxCreds,
|
||||
pub(crate) cap_sids: Vec<String>,
|
||||
pub(crate) launch: WindowsProcessLaunch,
|
||||
pub(crate) launch: ResolvedWindowsProcessLaunch,
|
||||
pub(crate) read_roots_override: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
@@ -355,7 +354,7 @@ pub(crate) fn prepare_elevated_spawn_context_for_permissions(
|
||||
codex_home: &Path,
|
||||
cwd: &Path,
|
||||
env_map: &mut HashMap<String, String>,
|
||||
launch: WindowsProcessLaunch,
|
||||
command: Vec<String>,
|
||||
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<ElevatedSpawnContext> {
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -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<String>,
|
||||
cwd: &Path,
|
||||
mut env_map: HashMap<String, String>,
|
||||
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,
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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<String, String>,
|
||||
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<String>,
|
||||
cwd: &Path,
|
||||
mut env_map: HashMap<String, String>,
|
||||
timeout_ms: Option<u64>,
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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<String>,
|
||||
pub cwd: &'a Path,
|
||||
pub env_map: HashMap<String, String>,
|
||||
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,
|
||||
|
||||
@@ -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<AbsolutePathBuf> {
|
||||
vec![AbsolutePathBuf::from_absolute_path(root).expect("absolute workspace root")]
|
||||
}
|
||||
|
||||
fn unresolved_launch(command: Vec<String>) -> WindowsProcessLaunch {
|
||||
WindowsProcessLaunch {
|
||||
application_path: None,
|
||||
command,
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_for_frame_count(frames_path: &Path, expected_frames: usize) -> Vec<Message> {
|
||||
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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user