Codex Handbook
windows-sandbox-rs/src/wrapper.rs 320 lines
//! Internal `codex.exe --run-as-windows-sandbox` wrapper.//!//! This gives direct-spawn callers an argv-shaped Windows sandbox launcher,//! analogous to the macOS seatbelt and Linux sandbox wrapper paths. The wrapper//! parses sandbox metadata from argv, launches the requested inner command in a//! Windows sandbox session, and forwards stdio to that inner command.use std::collections::HashMap;use std::path::Path;use std::path::PathBuf;use anyhow::Context;use anyhow::Result;use anyhow::anyhow;use anyhow::bail;use codex_protocol::config_types::WindowsSandboxLevel;use codex_protocol::models::PermissionProfile;use codex_utils_absolute_path::AbsolutePathBuf;pub const CODEX_WINDOWS_SANDBOX_ARG1: &str = "--run-as-windows-sandbox";const COMMAND_CWD_FLAG: &str = "--command-cwd";const CODEX_HOME_FLAG: &str = "--codex-home";const DENY_READ_PATHS_JSON_FLAG: &str = "--deny-read-paths-json";const DENY_WRITE_PATHS_JSON_FLAG: &str = "--deny-write-paths-json";const ENV_JSON_FLAG: &str = "--env-json";const PERMISSION_PROFILE_FLAG: &str = "--permission-profile";const PRIVATE_DESKTOP_FLAG: &str = "--windows-sandbox-private-desktop";const PROXY_ENFORCED_FLAG: &str = "--proxy-enforced";const READ_ROOTS_INCLUDE_PLATFORM_DEFAULTS_FLAG: &str = "--read-roots-include-platform-defaults";const READ_ROOTS_JSON_FLAG: &str = "--read-roots-json";const SANDBOX_LEVEL_FLAG: &str = "--windows-sandbox-level";const WRITE_ROOTS_JSON_FLAG: &str = "--write-roots-json";const WORKSPACE_ROOT_FLAG: &str = "--workspace-root";#[allow(clippy::too_many_arguments)]pub fn create_windows_sandbox_command_args_for_permission_profile(    command: Vec<String>,    command_cwd: &AbsolutePathBuf,    workspace_roots: &[AbsolutePathBuf],    env_map: &HashMap<String, String>,    permission_profile: &PermissionProfile,    windows_sandbox_level: WindowsSandboxLevel,    windows_sandbox_private_desktop: bool,    proxy_enforced: bool,    read_roots_override: Option<&[PathBuf]>,    read_roots_include_platform_defaults: bool,    write_roots_override: Option<&[PathBuf]>,    deny_read_paths_override: &[AbsolutePathBuf],    deny_write_paths_override: &[AbsolutePathBuf],    codex_home: &Path,) -> Vec<String> {    let permission_profile_json = serde_json::to_string(permission_profile)        .unwrap_or_else(|err| panic!("failed to serialize permission profile: {err}"));    let env_json = serde_json::to_string(env_map)        .unwrap_or_else(|err| panic!("failed to serialize env: {err}"));    let mut args = vec![        CODEX_WINDOWS_SANDBOX_ARG1.to_string(),        CODEX_HOME_FLAG.to_string(),        codex_home.to_string_lossy().into_owned(),        COMMAND_CWD_FLAG.to_string(),        command_cwd.as_path().to_string_lossy().into_owned(),        PERMISSION_PROFILE_FLAG.to_string(),        permission_profile_json,        ENV_JSON_FLAG.to_string(),        env_json,        SANDBOX_LEVEL_FLAG.to_string(),        windows_sandbox_level.to_string(),    ];    let workspace_roots = if workspace_roots.is_empty() {        std::slice::from_ref(command_cwd)    } else {        workspace_roots    };    for root in workspace_roots {        args.push(WORKSPACE_ROOT_FLAG.to_string());        args.push(root.as_path().to_string_lossy().into_owned());    }    if windows_sandbox_private_desktop {        args.push(PRIVATE_DESKTOP_FLAG.to_string());    }    if proxy_enforced {        args.push(PROXY_ENFORCED_FLAG.to_string());    }    if let Some(read_roots_override) = read_roots_override {        push_json_arg(&mut args, READ_ROOTS_JSON_FLAG, &read_roots_override);    }    if read_roots_include_platform_defaults {        args.push(READ_ROOTS_INCLUDE_PLATFORM_DEFAULTS_FLAG.to_string());    }    if let Some(write_roots_override) = write_roots_override {        push_json_arg(&mut args, WRITE_ROOTS_JSON_FLAG, &write_roots_override);    }    if !deny_read_paths_override.is_empty() {        push_json_arg(            &mut args,            DENY_READ_PATHS_JSON_FLAG,            &deny_read_paths_override,        );    }    if !deny_write_paths_override.is_empty() {        push_json_arg(            &mut args,            DENY_WRITE_PATHS_JSON_FLAG,            &deny_write_paths_override,        );    }    args.push("--".to_string());    args.extend(command);    args}fn push_json_arg<T: serde::Serialize>(args: &mut Vec<String>, flag: &str, value: &T) {    args.push(flag.to_string());    args.push(        serde_json::to_string(value)            .unwrap_or_else(|err| panic!("failed to serialize {flag}: {err}")),    );}pub fn run_windows_sandbox_wrapper_main() -> ! {    let args = std::env::args().skip(2).collect::<Vec<_>>();    let runtime = match tokio::runtime::Builder::new_current_thread()        .enable_all()        .build()    {        Ok(runtime) => runtime,        Err(err) => {            eprintln!("windows sandbox failed to build runtime: {err}");            std::process::exit(1);        }    };    let exit_code = match runtime.block_on(run_windows_sandbox_wrapper_args(args)) {        Ok(exit_code) => exit_code,        Err(err) => {            eprintln!("windows sandbox failed: {err:#}");            1        }    };    std::process::exit(exit_code);}async fn run_windows_sandbox_wrapper_args(args: Vec<String>) -> Result<i32> {    let request = parse_windows_sandbox_wrapper_args(args)?;    run_windows_sandbox_wrapper_request(request).await}struct WindowsSandboxWrapperRequest {    codex_home: PathBuf,    command_cwd: AbsolutePathBuf,    workspace_roots: Vec<AbsolutePathBuf>,    env_map: HashMap<String, String>,    permission_profile: PermissionProfile,    windows_sandbox_level: WindowsSandboxLevel,    windows_sandbox_private_desktop: bool,    proxy_enforced: bool,    read_roots_override: Option<Vec<PathBuf>>,    read_roots_include_platform_defaults: bool,    write_roots_override: Option<Vec<PathBuf>>,    deny_read_paths_override: Vec<AbsolutePathBuf>,    deny_write_paths_override: Vec<AbsolutePathBuf>,    command: Vec<String>,}async fn run_windows_sandbox_wrapper_request(request: WindowsSandboxWrapperRequest) -> Result<i32> {    if request.command.is_empty() {        bail!("missing sandboxed command in windows sandbox wrapper request");    }    let spawned =        crate::spawn_windows_sandbox_session_for_level(crate::WindowsSandboxSessionRequest {            permission_profile: &request.permission_profile,            workspace_roots: request.workspace_roots.as_slice(),            codex_home: request.codex_home.as_path(),            command: request.command,            cwd: request.command_cwd.as_path(),            env_map: request.env_map,            windows_sandbox_level: request.windows_sandbox_level,            proxy_enforced: request.proxy_enforced,            timeout_ms: None,            read_roots_override: request.read_roots_override.as_deref(),            read_roots_include_platform_defaults: request.read_roots_include_platform_defaults,            write_roots_override: request.write_roots_override.as_deref(),            deny_read_paths_override: request.deny_read_paths_override.as_slice(),            deny_write_paths_override: request.deny_write_paths_override.as_slice(),            tty: false,            stdin_open: true,            use_private_desktop: request.windows_sandbox_private_desktop,        })        .await?;    Ok(crate::forward_sandbox_session_stdio(spawned).await)}fn parse_windows_sandbox_wrapper_args(args: Vec<String>) -> Result<WindowsSandboxWrapperRequest> {    let mut args = args.into_iter();    let mut codex_home = None;    let mut command_cwd = None;    let mut workspace_roots = Vec::new();    let mut env_map = None;    let mut permission_profile = None;    let mut windows_sandbox_level = None;    let mut windows_sandbox_private_desktop = false;    let mut proxy_enforced = false;    let mut read_roots_override = None;    let mut read_roots_include_platform_defaults = false;    let mut write_roots_override = None;    let mut deny_read_paths_override = Vec::new();    let mut deny_write_paths_override = Vec::new();    let mut command = None;    while let Some(arg) = args.next() {        match arg.as_str() {            CODEX_HOME_FLAG => codex_home = Some(PathBuf::from(next_flag_value(&mut args, &arg)?)),            COMMAND_CWD_FLAG => {                command_cwd = Some(absolute_path_arg(next_flag_value(&mut args, &arg)?, &arg)?);            }            WORKSPACE_ROOT_FLAG => {                workspace_roots.push(absolute_path_arg(next_flag_value(&mut args, &arg)?, &arg)?);            }            ENV_JSON_FLAG => {                let value = next_flag_value(&mut args, &arg)?;                env_map = Some(serde_json::from_str(&value).context("failed to parse env json")?);            }            DENY_READ_PATHS_JSON_FLAG => {                deny_read_paths_override =                    json_flag_value(next_flag_value(&mut args, &arg)?, &arg)?;            }            DENY_WRITE_PATHS_JSON_FLAG => {                deny_write_paths_override =                    json_flag_value(next_flag_value(&mut args, &arg)?, &arg)?;            }            PERMISSION_PROFILE_FLAG => {                let value = next_flag_value(&mut args, &arg)?;                permission_profile = Some(                    serde_json::from_str(&value).context("failed to parse permission profile")?,                );            }            SANDBOX_LEVEL_FLAG => {                let value = next_flag_value(&mut args, &arg)?;                windows_sandbox_level = Some(parse_windows_sandbox_level(&value)?);            }            PRIVATE_DESKTOP_FLAG => windows_sandbox_private_desktop = true,            PROXY_ENFORCED_FLAG => proxy_enforced = true,            READ_ROOTS_INCLUDE_PLATFORM_DEFAULTS_FLAG => {                read_roots_include_platform_defaults = true;            }            READ_ROOTS_JSON_FLAG => {                read_roots_override =                    Some(json_flag_value(next_flag_value(&mut args, &arg)?, &arg)?);            }            WRITE_ROOTS_JSON_FLAG => {                write_roots_override =                    Some(json_flag_value(next_flag_value(&mut args, &arg)?, &arg)?);            }            "--" => {                command = Some(args.collect::<Vec<_>>());                break;            }            _ => bail!("unexpected windows sandbox wrapper argument: {arg}"),        }    }    let codex_home = codex_home.ok_or_else(|| anyhow!("missing required {CODEX_HOME_FLAG}"))?;    if !codex_home.is_absolute() {        bail!(            "{CODEX_HOME_FLAG} must be absolute: {}",            codex_home.display()        );    }    let command_cwd = command_cwd.ok_or_else(|| anyhow!("missing required {COMMAND_CWD_FLAG}"))?;    if workspace_roots.is_empty() {        workspace_roots.push(command_cwd.clone());    }    Ok(WindowsSandboxWrapperRequest {        codex_home,        command_cwd,        workspace_roots,        env_map: env_map.ok_or_else(|| anyhow!("missing required {ENV_JSON_FLAG}"))?,        permission_profile: permission_profile            .ok_or_else(|| anyhow!("missing required {PERMISSION_PROFILE_FLAG}"))?,        windows_sandbox_level: windows_sandbox_level            .ok_or_else(|| anyhow!("missing required {SANDBOX_LEVEL_FLAG}"))?,        windows_sandbox_private_desktop,        proxy_enforced,        read_roots_override,        read_roots_include_platform_defaults,        write_roots_override,        deny_read_paths_override,        deny_write_paths_override,        command: command.ok_or_else(|| anyhow!("missing sandboxed command separator --"))?,    })}fn next_flag_value(args: &mut impl Iterator<Item = String>, flag: &str) -> Result<String> {    args.next()        .ok_or_else(|| anyhow!("missing value for {flag}"))}fn absolute_path_arg(value: String, flag: &str) -> Result<AbsolutePathBuf> {    let path = PathBuf::from(value);    AbsolutePathBuf::from_absolute_path(path.as_path())        .with_context(|| format!("{flag} must be absolute: {}", path.display()))}fn json_flag_value<T: serde::de::DeserializeOwned>(value: String, flag: &str) -> Result<T> {    serde_json::from_str(&value).with_context(|| format!("failed to parse {flag}"))}fn parse_windows_sandbox_level(value: &str) -> Result<WindowsSandboxLevel> {    match value {        "disabled" => Ok(WindowsSandboxLevel::Disabled),        "restricted-token" => Ok(WindowsSandboxLevel::RestrictedToken),        "elevated" => Ok(WindowsSandboxLevel::Elevated),        _ => bail!("invalid windows sandbox level: {value}"),    }}#[cfg(test)]#[path = "wrapper_tests.rs"]mod tests;