Codex Handbook
windows-sandbox-rs/src/elevated_impl.rs 312 lines
use codex_protocol::models::PermissionProfile;use codex_utils_absolute_path::AbsolutePathBuf;use std::collections::HashMap;use std::path::Path;use std::path::PathBuf;pub struct ElevatedSandboxProfileCaptureRequest<'a> {    pub permission_profile: &'a PermissionProfile,    pub workspace_roots: &'a [AbsolutePathBuf],    pub codex_home: &'a Path,    pub command: Vec<String>,    pub cwd: &'a Path,    pub env_map: HashMap<String, String>,    pub timeout_ms: Option<u64>,    pub cancellation: Option<crate::WindowsSandboxCancellationToken>,    pub use_private_desktop: bool,    pub proxy_enforced: bool,    pub read_roots_override: Option<&'a [PathBuf]>,    pub read_roots_include_platform_defaults: bool,    pub write_roots_override: Option<&'a [PathBuf]>,    pub deny_read_paths_override: &'a [AbsolutePathBuf],    pub deny_write_paths_override: &'a [AbsolutePathBuf],}mod windows_impl {    use super::ElevatedSandboxProfileCaptureRequest;    use crate::acl::allow_null_device;    use crate::cap::load_or_create_cap_sids;    use crate::cap::workspace_write_cap_sid_for_root;    use crate::env::ensure_non_interactive_pager;    use crate::env::inherit_path_env;    use crate::env::normalize_null_device_env;    use crate::identity::refresh_logon_sandbox_creds;    use crate::identity::require_logon_sandbox_creds;    use crate::ipc_framed::EmptyPayload;    use crate::ipc_framed::FramedMessage;    use crate::ipc_framed::Message;    use crate::ipc_framed::OutputStream;    use crate::ipc_framed::SpawnRequest;    use crate::ipc_framed::decode_bytes;    use crate::ipc_framed::read_frame;    use crate::ipc_framed::write_frame;    use crate::logging::log_failure;    use crate::logging::log_start;    use crate::logging::log_success;    use crate::resolved_permissions::ResolvedWindowsSandboxPermissions;    use crate::runner_client::is_stale_sandbox_creds_error;    use crate::runner_client::spawn_runner_transport;    use crate::sandbox_utils::ensure_codex_home_exists;    use crate::sandbox_utils::inject_git_safe_directory;    use crate::setup::effective_write_roots_for_permissions;    use crate::token::LocalSid;    use anyhow::Result;    use codex_utils_absolute_path::AbsolutePathBuf;    use std::fs::File;    use std::path::Path;    use std::sync::Arc;    use std::sync::atomic::AtomicBool;    use std::sync::atomic::Ordering;    use std::time::Duration;    pub use crate::windows_impl::CaptureResult;    /// Polls for cancellation and sends the runner's terminate IPC frame when requested.    ///    /// The 50 ms park bounds cancellation latency without busy-waiting.    fn spawn_cancel_writer(        pipe_write: &File,        cancellation: Option<crate::WindowsSandboxCancellationToken>,    ) -> Result<Option<(std::thread::JoinHandle<()>, Arc<AtomicBool>)>> {        let Some(cancellation) = cancellation else {            return Ok(None);        };        let mut pipe_write = pipe_write.try_clone()?;        let done = Arc::new(AtomicBool::new(false));        let done_for_thread = Arc::clone(&done);        let handle = std::thread::spawn(move || {            while !done_for_thread.load(Ordering::SeqCst) {                if cancellation.is_cancelled() {                    let _ = write_frame(                        &mut pipe_write,                        &FramedMessage {                            version: 1,                            message: Message::Terminate {                                payload: EmptyPayload::default(),                            },                        },                    );                    break;                }                std::thread::park_timeout(Duration::from_millis(50));            }        });        Ok(Some((handle, done)))    }    /// Launches the command runner under the sandbox user and captures its output.    #[allow(clippy::too_many_arguments)]    pub fn run_windows_sandbox_capture_for_permission_profile(        request: ElevatedSandboxProfileCaptureRequest<'_>,    ) -> Result<CaptureResult> {        let ElevatedSandboxProfileCaptureRequest {            permission_profile,            workspace_roots,            codex_home,            command,            cwd,            mut env_map,            timeout_ms,            cancellation,            use_private_desktop,            proxy_enforced,            read_roots_override,            read_roots_include_platform_defaults,            write_roots_override,            deny_read_paths_override,            deny_write_paths_override,        } = request;        let permissions =            ResolvedWindowsSandboxPermissions::try_from_permission_profile_for_workspace_roots(                permission_profile,                workspace_roots,            )?;        let deny_read_paths_override = deny_read_paths_override            .iter()            .map(AbsolutePathBuf::to_path_buf)            .collect::<Vec<_>>();        let deny_write_paths_override = deny_write_paths_override            .iter()            .map(AbsolutePathBuf::to_path_buf)            .collect::<Vec<_>>();        normalize_null_device_env(&mut env_map);        ensure_non_interactive_pager(&mut env_map);        inherit_path_env(&mut env_map);        inject_git_safe_directory(&mut env_map, cwd);        // Use a temp-based log dir that the sandbox user can write.        let sandbox_base = codex_home.join(".sandbox");        ensure_codex_home_exists(&sandbox_base)?;        let logs_base_dir: Option<&Path> = Some(sandbox_base.as_path());        log_start(&command, logs_base_dir);        let mut sandbox_creds = require_logon_sandbox_creds(            &permissions,            cwd,            &env_map,            codex_home,            read_roots_override,            read_roots_include_platform_defaults,            write_roots_override,            &deny_read_paths_override,            &deny_write_paths_override,            proxy_enforced,        )?;        // Build capability SID for ACL grants.        let caps = load_or_create_cap_sids(codex_home)?;        let (sid_for_null, cap_sids) = if permissions.uses_write_capabilities_for_cwd(cwd, &env_map)        {            let write_roots = effective_write_roots_for_permissions(                &permissions,                cwd,                &env_map,                codex_home,                write_roots_override,            );            let cap_sids = write_roots                .iter()                .map(|root| workspace_write_cap_sid_for_root(codex_home, cwd, root))                .collect::<Result<Vec<_>>>()?;            if cap_sids.is_empty() {                anyhow::bail!("workspace-write sandbox has no writable root capability SIDs");            }            (LocalSid::from_string(&cap_sids[0])?, cap_sids)        } else {            let sid = LocalSid::from_string(&caps.readonly)?;            (sid, vec![caps.readonly])        };        unsafe {            allow_null_device(sid_for_null.as_ptr());        }        (|| -> Result<CaptureResult> {            let spawn_request = SpawnRequest {                command: command.clone(),                cwd: cwd.to_path_buf(),                env: env_map.clone(),                permission_profile: permission_profile.clone(),                workspace_roots: workspace_roots.to_vec(),                codex_home: sandbox_base.clone(),                real_codex_home: codex_home.to_path_buf(),                cap_sids,                timeout_ms,                tty: false,                stdin_open: false,                use_private_desktop,            };            let transport = match spawn_runner_transport(                codex_home,                cwd,                &sandbox_creds,                logs_base_dir,                spawn_request.clone(),            ) {                Ok(transport) => transport,                Err(err) if is_stale_sandbox_creds_error(&err) => {                    sandbox_creds = refresh_logon_sandbox_creds(                        &permissions,                        cwd,                        &env_map,                        codex_home,                        read_roots_override,                        read_roots_include_platform_defaults,                        write_roots_override,                        &deny_read_paths_override,                        &deny_write_paths_override,                        proxy_enforced,                    )?;                    spawn_runner_transport(                        codex_home,                        cwd,                        &sandbox_creds,                        logs_base_dir,                        spawn_request,                    )?                }                Err(err) => return Err(err),            };            let (pipe_write, mut pipe_read) = transport.into_files();            let cancel_writer = spawn_cancel_writer(&pipe_write, cancellation)?;            let mut stdout = Vec::new();            let mut stderr = Vec::new();            let result = loop {                let msg = match read_frame(&mut pipe_read) {                    Ok(Some(msg)) => msg,                    Ok(None) => break Err(anyhow::anyhow!("runner pipe closed before exit")),                    Err(err) => break Err(err),                };                match msg.message {                    Message::SpawnReady { .. } => {}                    Message::Output { payload } => match decode_bytes(&payload.data_b64) {                        Ok(bytes) => match payload.stream {                            OutputStream::Stdout => stdout.extend_from_slice(&bytes),                            OutputStream::Stderr => stderr.extend_from_slice(&bytes),                        },                        Err(err) => {                            break Err(err);                        }                    },                    Message::Exit { payload } => break Ok((payload.exit_code, payload.timed_out)),                    Message::Error { payload } => {                        break Err(anyhow::anyhow!("runner error: {}", payload.message));                    }                    other => {                        break Err(anyhow::anyhow!(                            "unexpected runner message during capture: {other:?}"                        ));                    }                }            };            if let Some((cancel_handle, done)) = cancel_writer {                done.store(true, Ordering::SeqCst);                cancel_handle.thread().unpark();                let _ = cancel_handle.join();            }            drop(pipe_write);            let (exit_code, timed_out) = result?;            if exit_code == 0 {                log_success(&command, logs_base_dir);            } else {                log_failure(&command, &format!("exit code {exit_code}"), logs_base_dir);            }            Ok(CaptureResult {                exit_code,                stdout,                stderr,                timed_out,            })        })()    }}#[cfg(target_os = "windows")]pub use windows_impl::run_windows_sandbox_capture_for_permission_profile;#[cfg(not(target_os = "windows"))]mod stub {    use super::ElevatedSandboxProfileCaptureRequest;    use anyhow::Result;    use anyhow::bail;    #[derive(Debug, Default)]    pub struct CaptureResult {        pub exit_code: i32,        pub stdout: Vec<u8>,        pub stderr: Vec<u8>,        pub timed_out: bool,    }    /// Stub implementation for non-Windows targets; sandboxing only works on Windows.    #[allow(clippy::too_many_arguments)]    pub fn run_windows_sandbox_capture_for_permission_profile(        _request: ElevatedSandboxProfileCaptureRequest<'_>,    ) -> Result<CaptureResult> {        bail!("Windows sandbox is only available on Windows")    }}#[cfg(not(target_os = "windows"))]pub use stub::run_windows_sandbox_capture_for_permission_profile;