diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index 8e87528df0..756a65edd8 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -385,7 +385,7 @@ pub fn build_exec_request( expiration, capture_policy, }; - let mut exec_req = manager + let exec_req = manager .transform(SandboxTransformRequest { command, permissions: permission_profile, @@ -414,6 +414,12 @@ pub fn build_exec_request( ) }) .map_err(CodexErr::from)?; + apply_windows_sandbox_filesystem_overrides(exec_req) +} + +pub(crate) fn apply_windows_sandbox_filesystem_overrides( + mut exec_req: ExecRequest, +) -> Result { let use_windows_elevated_backend = windows_sandbox_uses_elevated_backend( exec_req.windows_sandbox_level, exec_req.network.is_some(), @@ -422,14 +428,14 @@ pub fn build_exec_request( resolve_windows_elevated_filesystem_overrides( exec_req.sandbox, &exec_req.permission_profile, - sandbox_cwd, + &exec_req.windows_sandbox_policy_cwd, use_windows_elevated_backend, ) } else { resolve_windows_restricted_token_filesystem_overrides( exec_req.sandbox, &exec_req.permission_profile, - sandbox_cwd, + &exec_req.windows_sandbox_policy_cwd, exec_req.windows_sandbox_level, ) } diff --git a/codex-rs/core/src/tools/runtimes/shell.rs b/codex-rs/core/src/tools/runtimes/shell.rs index 13b65f0443..612ab8c0fc 100644 --- a/codex-rs/core/src/tools/runtimes/shell.rs +++ b/codex-rs/core/src/tools/runtimes/shell.rs @@ -310,7 +310,7 @@ impl ToolRuntime for ShellRuntime { }; let env = attempt .env_for(command, options, managed_network) - .map_err(|err| ToolError::Codex(err.into()))?; + .map_err(ToolError::Codex)?; let out = execute_env(env, Self::stdout_stream(ctx)) .await .map_err(ToolError::Codex)?; diff --git a/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs b/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs index 71fa89f980..848598b0ef 100644 --- a/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs +++ b/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs @@ -141,7 +141,7 @@ pub(super) async fn try_run_zsh_fork( options, managed_network_for_sandbox_permissions(req.network.as_ref(), req.sandbox_permissions), ) - .map_err(|err| ToolError::Codex(err.into()))?; + .map_err(ToolError::Codex)?; let crate::sandboxing::ExecRequest { command, cwd: sandbox_cwd, diff --git a/codex-rs/core/src/tools/runtimes/unified_exec.rs b/codex-rs/core/src/tools/runtimes/unified_exec.rs index 250965b67c..474df24e34 100644 --- a/codex-rs/core/src/tools/runtimes/unified_exec.rs +++ b/codex-rs/core/src/tools/runtimes/unified_exec.rs @@ -330,7 +330,7 @@ impl<'a> ToolRuntime for UnifiedExecRunt let options = unified_exec_options(attempt.network_denial_cancellation_token.clone()); let mut exec_env = attempt .env_for(command, options, managed_network) - .map_err(|err| ToolError::Codex(err.into()))?; + .map_err(ToolError::Codex)?; exec_env.exec_server_env_config = req.exec_server_env_config.clone(); match zsh_fork_backend::maybe_prepare_unified_exec( req, @@ -381,7 +381,7 @@ impl<'a> ToolRuntime for UnifiedExecRunt let options = unified_exec_options(attempt.network_denial_cancellation_token.clone()); let mut exec_env = attempt .env_for(command, options, managed_network) - .map_err(|err| ToolError::Codex(err.into()))?; + .map_err(ToolError::Codex)?; exec_env.exec_server_env_config = req.exec_server_env_config.clone(); self.manager .open_session_with_exec_env( diff --git a/codex-rs/core/src/tools/sandboxing.rs b/codex-rs/core/src/tools/sandboxing.rs index 598d8855af..2d3329b9cd 100644 --- a/codex-rs/core/src/tools/sandboxing.rs +++ b/codex-rs/core/src/tools/sandboxing.rs @@ -21,7 +21,6 @@ use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::ReviewDecision; use codex_sandboxing::SandboxCommand; use codex_sandboxing::SandboxManager; -use codex_sandboxing::SandboxTransformError; use codex_sandboxing::SandboxTransformRequest; use codex_sandboxing::SandboxType; use codex_sandboxing::SandboxablePreference; @@ -426,8 +425,9 @@ impl<'a> SandboxAttempt<'a> { command: SandboxCommand, options: ExecOptions, network: Option<&NetworkProxy>, - ) -> Result { - self.manager + ) -> codex_protocol::error::Result { + let exec_request = self + .manager .transform(SandboxTransformRequest { command, permissions: self.permissions, @@ -455,6 +455,8 @@ impl<'a> SandboxAttempt<'a> { self.workspace_roots.to_vec(), ) }) + .map_err(CodexErr::from)?; + crate::exec::apply_windows_sandbox_filesystem_overrides(exec_request) } } diff --git a/codex-rs/core/src/tools/sandboxing_tests.rs b/codex-rs/core/src/tools/sandboxing_tests.rs index 21719e281f..ac22985a10 100644 --- a/codex-rs/core/src/tools/sandboxing_tests.rs +++ b/codex-rs/core/src/tools/sandboxing_tests.rs @@ -1,12 +1,101 @@ use super::*; +use crate::exec::ExecCapturePolicy; +use crate::exec::ExecExpiration; +use crate::exec::WindowsSandboxFilesystemOverrides; +use crate::sandboxing::ExecOptions; use crate::sandboxing::SandboxPermissions; use crate::tools::hook_names::HookToolName; +use codex_protocol::config_types::WindowsSandboxLevel; +use codex_protocol::models::PermissionProfile; use codex_protocol::permissions::FileSystemAccessMode; use codex_protocol::permissions::FileSystemPath; use codex_protocol::permissions::FileSystemSandboxEntry; +use codex_protocol::permissions::FileSystemSpecialPath; +use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::protocol::GranularApprovalConfig; +use codex_sandboxing::SandboxCommand; +use codex_sandboxing::SandboxManager; +use codex_sandboxing::SandboxType; +use codex_utils_absolute_path::test_support::PathBufExt; +use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use serde_json::json; +use std::collections::HashMap; + +#[test] +fn shell_sandbox_attempt_applies_windows_deny_read_overrides() { + let temp_dir = tempfile::TempDir::new().expect("tempdir"); + let blocked = temp_dir.path().join(".env"); + std::fs::write(&blocked, "secret").expect("write blocked file"); + let expected_blocked = dunce::canonicalize(&blocked) + .expect("canonical blocked file") + .abs(); + let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ + FileSystemSandboxEntry { + path: FileSystemPath::Special { + value: FileSystemSpecialPath::Root, + }, + access: FileSystemAccessMode::Read, + }, + FileSystemSandboxEntry { + path: FileSystemPath::Special { + value: FileSystemSpecialPath::project_roots(/*subpath*/ None), + }, + access: FileSystemAccessMode::Write, + }, + FileSystemSandboxEntry { + path: FileSystemPath::Path { + path: blocked.abs(), + }, + access: FileSystemAccessMode::Deny, + }, + ]); + let permissions = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); + let sandbox_cwd = temp_dir.path().abs(); + let manager = SandboxManager::new(); + let attempt = SandboxAttempt { + sandbox: SandboxType::WindowsRestrictedToken, + permissions: &permissions, + enforce_managed_network: false, + manager: &manager, + sandbox_cwd: &sandbox_cwd, + workspace_roots: std::slice::from_ref(&sandbox_cwd), + codex_linux_sandbox_exe: None, + use_legacy_landlock: false, + windows_sandbox_level: WindowsSandboxLevel::Elevated, + windows_sandbox_private_desktop: false, + network_denial_cancellation_token: None, + }; + let command = SandboxCommand { + program: "echo".into(), + args: vec!["ok".to_string()], + cwd: sandbox_cwd.clone(), + env: HashMap::new(), + additional_permissions: None, + }; + let options = ExecOptions { + expiration: ExecExpiration::DefaultTimeout, + capture_policy: ExecCapturePolicy::ShellTool, + }; + + let exec_request = attempt + .env_for(command, options, /*network*/ None) + .expect("prepare shell exec request"); + + assert_eq!( + exec_request.windows_sandbox_filesystem_overrides, + Some(WindowsSandboxFilesystemOverrides { + read_roots_override: None, + read_roots_include_platform_defaults: false, + write_roots_override: None, + additional_deny_read_paths: vec![expected_blocked.clone()], + additional_deny_write_paths: vec![expected_blocked], + }) + ); +} #[test] fn bash_permission_request_payload_omits_missing_description() {