From 8be9296b05586114f04ffe848772934de3c50374 Mon Sep 17 00:00:00 2001 From: celia-oai Date: Wed, 18 Feb 2026 22:07:32 -0800 Subject: [PATCH] changes --- codex-rs/core/src/exec.rs | 1 + codex-rs/core/src/sandboxing/mod.rs | 83 ++++++++- codex-rs/core/src/seatbelt_permissions.rs | 171 ++++++++++++++++++ codex-rs/core/src/skills/command_policy.rs | 53 +++++- codex-rs/core/src/skills/mod.rs | 2 +- codex-rs/core/src/tools/handlers/shell.rs | 45 ++++- codex-rs/core/src/tools/js_repl/mod.rs | 1 + .../core/src/tools/runtimes/apply_patch.rs | 2 +- codex-rs/core/src/tools/runtimes/shell.rs | 17 +- .../core/src/tools/runtimes/unified_exec.rs | 2 +- codex-rs/core/src/tools/sandboxing.rs | 4 + 11 files changed, 358 insertions(+), 23 deletions(-) diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index 4e1220b87b..640b0de787 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -225,6 +225,7 @@ pub async fn process_exec_tool_call( codex_linux_sandbox_exe: codex_linux_sandbox_exe.as_ref(), use_linux_sandbox_bwrap, windows_sandbox_level, + macos_seatbelt_profile_extensions: None, }) .map_err(CodexErr::from)?; diff --git a/codex-rs/core/src/sandboxing/mod.rs b/codex-rs/core/src/sandboxing/mod.rs index f5e171bfe9..75d6643de5 100644 --- a/codex-rs/core/src/sandboxing/mod.rs +++ b/codex-rs/core/src/sandboxing/mod.rs @@ -19,7 +19,9 @@ use crate::protocol::SandboxPolicy; #[cfg(target_os = "macos")] use crate::seatbelt::MACOS_PATH_TO_SEATBELT_EXECUTABLE; #[cfg(target_os = "macos")] -use crate::seatbelt::create_seatbelt_command_args; +use crate::seatbelt::create_seatbelt_command_args_with_extensions; +#[cfg(target_os = "macos")] +pub(crate) use crate::seatbelt_permissions::MacOsSeatbeltProfileExtensions; #[cfg(target_os = "macos")] use crate::spawn::CODEX_SANDBOX_ENV_VAR; use crate::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR; @@ -27,6 +29,8 @@ use crate::tools::sandboxing::SandboxablePreference; use codex_network_proxy::NetworkProxy; use codex_protocol::config_types::WindowsSandboxLevel; pub use codex_protocol::models::SandboxPermissions; +#[cfg(not(target_os = "macos"))] +pub(crate) type MacOsSeatbeltProfileExtensions = (); use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; @@ -72,6 +76,7 @@ pub(crate) struct SandboxTransformRequest<'a> { pub codex_linux_sandbox_exe: Option<&'a PathBuf>, pub use_linux_sandbox_bwrap: bool, pub windows_sandbox_level: WindowsSandboxLevel, + pub macos_seatbelt_profile_extensions: Option<&'a MacOsSeatbeltProfileExtensions>, } pub enum SandboxPreference { @@ -147,6 +152,7 @@ impl SandboxManager { codex_linux_sandbox_exe, use_linux_sandbox_bwrap, windows_sandbox_level, + macos_seatbelt_profile_extensions, } = request; let mut env = spec.env; if !policy.has_full_network_access() { @@ -172,12 +178,13 @@ impl SandboxManager { let zsh_exec_bridge_allowed_unix_sockets = zsh_exec_bridge_wrapper_socket .as_ref() .map_or_else(Vec::new, |path| vec![path.clone()]); - let mut args = create_seatbelt_command_args( + let mut args = create_seatbelt_command_args_with_extensions( command.clone(), policy, sandbox_policy_cwd, enforce_managed_network, network, + macos_seatbelt_profile_extensions, &zsh_exec_bridge_allowed_unix_sockets, ); let mut full_command = Vec::with_capacity(1 + args.len()); @@ -249,12 +256,22 @@ pub async fn execute_env( #[cfg(test)] mod tests { + #[cfg(target_os = "macos")] + use super::CommandSpec; use super::SandboxManager; + #[cfg(target_os = "macos")] + use super::SandboxTransformRequest; + #[cfg(target_os = "macos")] + use crate::exec::ExecExpiration; use crate::exec::SandboxType; use crate::protocol::SandboxPolicy; use crate::tools::sandboxing::SandboxablePreference; use codex_protocol::config_types::WindowsSandboxLevel; + #[cfg(target_os = "macos")] + use codex_protocol::models::SandboxPermissions; use pretty_assertions::assert_eq; + #[cfg(target_os = "macos")] + use std::collections::HashMap; #[test] fn danger_full_access_defaults_to_no_sandbox_without_network_requirements() { @@ -280,4 +297,66 @@ mod tests { ); assert_eq!(sandbox, expected); } + + #[cfg(target_os = "macos")] + #[test] + fn transform_applies_macos_seatbelt_profile_extensions_when_present() { + use crate::seatbelt::MACOS_PATH_TO_SEATBELT_EXECUTABLE; + use crate::seatbelt_permissions::MacOsAutomationPermission; + use crate::seatbelt_permissions::MacOsPreferencesPermission; + use crate::seatbelt_permissions::MacOsSeatbeltProfileExtensions; + + let manager = SandboxManager::new(); + let tempdir = tempfile::tempdir().expect("tempdir"); + let cwd = tempdir.path().to_path_buf(); + let spec = CommandSpec { + program: "/bin/echo".to_string(), + args: vec!["ok".to_string()], + cwd: cwd.clone(), + env: HashMap::new(), + expiration: ExecExpiration::DefaultTimeout, + sandbox_permissions: SandboxPermissions::UseDefault, + justification: None, + }; + let extensions = MacOsSeatbeltProfileExtensions { + macos_preferences: MacOsPreferencesPermission::ReadWrite, + macos_automation: MacOsAutomationPermission::BundleIds(vec![ + "com.apple.Notes".to_string(), + ]), + macos_accessibility: true, + macos_calendar: false, + }; + + let transformed = manager + .transform(SandboxTransformRequest { + spec, + policy: &SandboxPolicy::new_read_only_policy(), + sandbox: SandboxType::MacosSeatbelt, + enforce_managed_network: false, + network: None, + sandbox_policy_cwd: &cwd, + codex_linux_sandbox_exe: None, + use_linux_sandbox_bwrap: false, + windows_sandbox_level: WindowsSandboxLevel::Disabled, + macos_seatbelt_profile_extensions: Some(&extensions), + }) + .expect("transform"); + + assert_eq!( + transformed.command.first(), + Some(&MACOS_PATH_TO_SEATBELT_EXECUTABLE.to_string()) + ); + let policy_arg_idx = transformed + .command + .iter() + .position(|arg| arg == "-p") + .expect("contains -p policy"); + let policy = transformed + .command + .get(policy_arg_idx + 1) + .expect("policy after -p"); + assert!(policy.contains("(allow user-preference-write)")); + assert!(policy.contains("com.apple.Notes")); + assert!(policy.contains("com.apple.axserver")); + } } diff --git a/codex-rs/core/src/seatbelt_permissions.rs b/codex-rs/core/src/seatbelt_permissions.rs index aacc3a7784..d090b0389b 100644 --- a/codex-rs/core/src/seatbelt_permissions.rs +++ b/codex-rs/core/src/seatbelt_permissions.rs @@ -53,6 +53,85 @@ impl MacOsSeatbeltProfileExtensions { } } +pub(crate) fn merge_macos_seatbelt_profile_extensions( + base: Option<&MacOsSeatbeltProfileExtensions>, + extension: Option<&MacOsSeatbeltProfileExtensions>, +) -> Option { + match (base, extension) { + (None, None) => None, + (Some(base), None) => Some(base.clone().normalized()), + (None, Some(extension)) => Some(extension.clone().normalized()), + (Some(base), Some(extension)) => { + let base = base.normalized(); + let extension = extension.normalized(); + Some( + MacOsSeatbeltProfileExtensions { + macos_preferences: merge_macos_preferences_permission( + &base.macos_preferences, + &extension.macos_preferences, + ), + macos_automation: merge_macos_automation_permission( + &base.macos_automation, + &extension.macos_automation, + ), + macos_accessibility: base.macos_accessibility && extension.macos_accessibility, + macos_calendar: base.macos_calendar && extension.macos_calendar, + } + .normalized(), + ) + } + } +} + +fn merge_macos_preferences_permission( + base: &MacOsPreferencesPermission, + extension: &MacOsPreferencesPermission, +) -> MacOsPreferencesPermission { + fn rank(permission: &MacOsPreferencesPermission) -> u8 { + match permission { + MacOsPreferencesPermission::None => 0, + MacOsPreferencesPermission::ReadOnly => 1, + MacOsPreferencesPermission::ReadWrite => 2, + } + } + + if rank(extension) < rank(base) { + extension.clone() + } else { + base.clone() + } +} + +fn merge_macos_automation_permission( + base: &MacOsAutomationPermission, + extension: &MacOsAutomationPermission, +) -> MacOsAutomationPermission { + match (base, extension) { + (MacOsAutomationPermission::None, _) | (_, MacOsAutomationPermission::None) => { + MacOsAutomationPermission::None + } + (MacOsAutomationPermission::All, other) | (other, MacOsAutomationPermission::All) => { + other.clone() + } + ( + MacOsAutomationPermission::BundleIds(base_ids), + MacOsAutomationPermission::BundleIds(extension_ids), + ) => { + let base_ids = base_ids.iter().cloned().collect::>(); + let extension_ids = extension_ids.iter().cloned().collect::>(); + let intersection = base_ids + .intersection(&extension_ids) + .cloned() + .collect::>(); + if intersection.is_empty() { + MacOsAutomationPermission::None + } else { + MacOsAutomationPermission::BundleIds(intersection) + } + } + } +} + pub(crate) fn build_seatbelt_extensions( extensions: &MacOsSeatbeltProfileExtensions, ) -> SeatbeltExtensionPolicy { @@ -166,6 +245,7 @@ mod tests { use super::MacOsPreferencesPermission; use super::MacOsSeatbeltProfileExtensions; use super::build_seatbelt_extensions; + use super::merge_macos_seatbelt_profile_extensions; #[test] fn preferences_read_only_emits_read_clauses_only() { @@ -245,4 +325,95 @@ mod tests { assert!(policy.policy.contains("(allow user-preference-read)")); assert!(!policy.policy.contains("(allow user-preference-write)")); } + + #[test] + fn merge_extensions_intersects_permissions() { + let base = MacOsSeatbeltProfileExtensions { + macos_preferences: MacOsPreferencesPermission::ReadOnly, + macos_automation: MacOsAutomationPermission::BundleIds(vec![ + "com.apple.Notes".to_string(), + "com.apple.Calendar".to_string(), + ]), + macos_accessibility: false, + macos_calendar: true, + }; + let extension = MacOsSeatbeltProfileExtensions { + macos_preferences: MacOsPreferencesPermission::ReadWrite, + macos_automation: MacOsAutomationPermission::BundleIds(vec![ + "com.apple.Reminders".to_string(), + "com.apple.Calendar".to_string(), + ]), + macos_accessibility: true, + macos_calendar: false, + }; + + let merged = + merge_macos_seatbelt_profile_extensions(Some(&base), Some(&extension)).expect("merged"); + + assert_eq!( + merged.macos_preferences, + MacOsPreferencesPermission::ReadOnly + ); + assert_eq!( + merged.macos_automation, + MacOsAutomationPermission::BundleIds(vec!["com.apple.Calendar".to_string(),]) + ); + assert!(!merged.macos_accessibility); + assert!(!merged.macos_calendar); + } + + #[test] + fn merge_extensions_all_intersects_to_other_side() { + let base = MacOsSeatbeltProfileExtensions { + macos_automation: MacOsAutomationPermission::All, + ..Default::default() + }; + let extension = MacOsSeatbeltProfileExtensions { + macos_automation: MacOsAutomationPermission::BundleIds(vec![ + "com.apple.Notes".to_string(), + ]), + ..Default::default() + }; + + let merged = + merge_macos_seatbelt_profile_extensions(Some(&base), Some(&extension)).expect("merged"); + assert_eq!( + merged.macos_automation, + MacOsAutomationPermission::BundleIds(vec!["com.apple.Notes".to_string(),]) + ); + } + + #[test] + fn merge_extensions_none_intersects_to_none() { + let base = MacOsSeatbeltProfileExtensions { + macos_automation: MacOsAutomationPermission::All, + ..Default::default() + }; + let extension = MacOsSeatbeltProfileExtensions { + macos_automation: MacOsAutomationPermission::None, + ..Default::default() + }; + + let merged = + merge_macos_seatbelt_profile_extensions(Some(&base), Some(&extension)).expect("merged"); + assert_eq!(merged.macos_automation, MacOsAutomationPermission::None); + } + + #[test] + fn merge_extensions_normalizes_single_source() { + let extension = MacOsSeatbeltProfileExtensions { + macos_automation: MacOsAutomationPermission::BundleIds(vec![ + " com.apple.Notes ".to_string(), + "com.apple.Notes".to_string(), + ]), + ..Default::default() + }; + + let merged = + merge_macos_seatbelt_profile_extensions(None, Some(&extension)).expect("merged"); + assert_eq!( + merged.macos_automation, + MacOsAutomationPermission::BundleIds(vec!["com.apple.Notes".to_string()]) + ); + } } diff --git a/codex-rs/core/src/skills/command_policy.rs b/codex-rs/core/src/skills/command_policy.rs index cc71d35764..d3115d50e7 100644 --- a/codex-rs/core/src/skills/command_policy.rs +++ b/codex-rs/core/src/skills/command_policy.rs @@ -3,14 +3,13 @@ use std::path::Path; use std::path::PathBuf; use codex_protocol::parse_command::ParsedCommand; -use codex_protocol::protocol::SandboxPolicy; use dunce::canonicalize as canonicalize_path; use crate::config::Permissions; use crate::path_utils::normalize_for_path_comparison; use crate::skills::SkillLoadOutcome; -/// Resolves the sandbox policy extension contributed by the first matching +/// Resolves the full permissions extension contributed by the first matching /// skill for a command invocation. /// /// Assumptions: @@ -23,13 +22,30 @@ use crate::skills::SkillLoadOutcome; /// Returns `None` when `shell_zsh_fork` is disabled, command shell is not /// `zsh`, or when no enabled skill with permissions matches an eligible /// command action executable. -pub(crate) fn resolve_skill_sandbox_extension_for_command( +pub(crate) fn resolve_skill_permissions_for_command( skills_outcome: &SkillLoadOutcome, shell_zsh_fork_enabled: bool, command: &[String], command_cwd: &Path, command_actions: &[ParsedCommand], -) -> Option { +) -> Option { + resolve_skill_permissions_ref( + skills_outcome, + shell_zsh_fork_enabled, + command, + command_cwd, + command_actions, + ) + .cloned() +} + +fn resolve_skill_permissions_ref<'a>( + skills_outcome: &'a SkillLoadOutcome, + shell_zsh_fork_enabled: bool, + command: &[String], + command_cwd: &Path, + command_actions: &[ParsedCommand], +) -> Option<&'a Permissions> { if !shell_zsh_fork_enabled { return None; } @@ -48,7 +64,7 @@ pub(crate) fn resolve_skill_sandbox_extension_for_command( continue; }; if let Some(permissions) = match_skill_for_candidate(skills_outcome, &action_candidate) { - return Some(permissions.sandbox_policy.get().clone()); + return Some(permissions); } } @@ -74,7 +90,7 @@ fn command_action_candidate_path( | ParsedCommand::Search { .. } => None, }?; let action_path = if action_path.is_absolute() { - action_path.to_path_buf() + action_path } else { command_cwd.join(action_path) }; @@ -143,7 +159,7 @@ fn normalize_lexically(path: &Path) -> PathBuf { #[cfg(test)] mod tests { - use super::resolve_skill_sandbox_extension_for_command; + use super::resolve_skill_permissions_for_command; use crate::config::Constrained; use crate::config::Permissions; use crate::config::types::ShellEnvironmentPolicy; @@ -193,6 +209,23 @@ mod tests { dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()) } + fn resolve_skill_sandbox_extension_for_command( + skills_outcome: &SkillLoadOutcome, + shell_zsh_fork_enabled: bool, + command: &[String], + command_cwd: &Path, + command_actions: &[ParsedCommand], + ) -> Option { + resolve_skill_permissions_for_command( + skills_outcome, + shell_zsh_fork_enabled, + command, + command_cwd, + command_actions, + ) + .map(|permissions| permissions.sandbox_policy.get().clone()) + } + #[test] fn resolves_policy_for_zsh_executable_inside_skill_scripts_directory() { let tempdir = tempfile::tempdir().expect("tempdir"); @@ -256,7 +289,7 @@ mod tests { }; let outcome = outcome_with_skills(vec![skill_with_policy( canonical(&skill_path), - skill_policy.clone(), + skill_policy, )]); let command = vec![ "/bin/bash".to_string(), @@ -297,7 +330,7 @@ mod tests { }; let outcome = outcome_with_skills(vec![skill_with_policy( canonical(&skill_path), - skill_policy.clone(), + skill_policy, )]); let command = vec![ "/bin/zsh".to_string(), @@ -396,7 +429,7 @@ mod tests { exclude_slash_tmp: false, }; let outcome = outcome_with_skills(vec![ - skill_with_policy(canonical(&parent_skill_path), parent_policy.clone()), + skill_with_policy(canonical(&parent_skill_path), parent_policy), skill_with_policy(canonical(&nested_skill_path), nested_policy.clone()), ]); let command = vec![ diff --git a/codex-rs/core/src/skills/mod.rs b/codex-rs/core/src/skills/mod.rs index f9d07b7edf..d602dc52bd 100644 --- a/codex-rs/core/src/skills/mod.rs +++ b/codex-rs/core/src/skills/mod.rs @@ -9,7 +9,7 @@ pub mod remote; pub mod render; pub mod system; -pub(crate) use command_policy::resolve_skill_sandbox_extension_for_command; +pub(crate) use command_policy::resolve_skill_permissions_for_command; pub(crate) use env_var_dependencies::collect_env_var_dependencies; pub(crate) use env_var_dependencies::resolve_skill_dependencies_for_turn; pub(crate) use injection::SkillInjections; diff --git a/codex-rs/core/src/tools/handlers/shell.rs b/codex-rs/core/src/tools/handlers/shell.rs index 0e2f507d74..c5f6b4a1ff 100644 --- a/codex-rs/core/src/tools/handlers/shell.rs +++ b/codex-rs/core/src/tools/handlers/shell.rs @@ -15,8 +15,12 @@ use crate::is_safe_command::is_known_safe_command; use crate::parse_command::parse_command; use crate::protocol::ExecCommandSource; use crate::sandboxing::extend_sandbox_policy; +#[cfg(target_os = "macos")] +use crate::seatbelt_permissions::MacOsSeatbeltProfileExtensions; +#[cfg(target_os = "macos")] +use crate::seatbelt_permissions::merge_macos_seatbelt_profile_extensions; use crate::shell::Shell; -use crate::skills::resolve_skill_sandbox_extension_for_command; +use crate::skills::resolve_skill_permissions_for_command; use crate::tools::context::ToolInvocation; use crate::tools::context::ToolOutput; use crate::tools::context::ToolPayload; @@ -30,6 +34,8 @@ use crate::tools::registry::ToolKind; use crate::tools::runtimes::shell::ShellRequest; use crate::tools::runtimes::shell::ShellRuntime; use crate::tools::sandboxing::ToolCtx; +#[cfg(not(target_os = "macos"))] +type MacOsSeatbeltProfileExtensions = (); pub struct ShellHandler; @@ -308,19 +314,29 @@ impl ShellHandler { .await; let shell_zsh_fork_enabled = session.features().enabled(Feature::ShellZshFork); let command_actions = parse_command(&exec_params.command); - let effective_sandbox_policy = resolve_skill_sandbox_extension_for_command( + let skill_permissions = resolve_skill_permissions_for_command( &skills_outcome, shell_zsh_fork_enabled, &exec_params.command, &exec_params.cwd, &command_actions, - ) - .map_or_else( + ); + let effective_sandbox_policy = skill_permissions.as_ref().map_or_else( || turn.sandbox_policy.clone(), - |skill_sandbox_policy| { - extend_sandbox_policy(&turn.sandbox_policy, &skill_sandbox_policy) + |skill_permissions| { + extend_sandbox_policy(&turn.sandbox_policy, skill_permissions.sandbox_policy.get()) }, ); + let effective_macos_seatbelt_profile_extensions = + merge_turn_and_skill_macos_seatbelt_profile_extensions( + turn.config + .permissions + .macos_seatbelt_profile_extensions + .as_ref(), + skill_permissions + .as_ref() + .and_then(|permissions| permissions.macos_seatbelt_profile_extensions.as_ref()), + ); let exec_approval_requirement = session .services @@ -343,6 +359,7 @@ impl ShellHandler { network: exec_params.network.clone(), sandbox_permissions: exec_params.sandbox_permissions, justification: exec_params.justification.clone(), + macos_seatbelt_profile_extensions: effective_macos_seatbelt_profile_extensions, exec_approval_requirement, }; let mut orchestrator = ToolOrchestrator::new(); @@ -374,6 +391,22 @@ impl ShellHandler { } } +#[cfg(target_os = "macos")] +fn merge_turn_and_skill_macos_seatbelt_profile_extensions( + turn_extensions: Option<&MacOsSeatbeltProfileExtensions>, + skill_extensions: Option<&MacOsSeatbeltProfileExtensions>, +) -> Option { + merge_macos_seatbelt_profile_extensions(turn_extensions, skill_extensions) +} + +#[cfg(not(target_os = "macos"))] +fn merge_turn_and_skill_macos_seatbelt_profile_extensions( + _turn_extensions: Option<&MacOsSeatbeltProfileExtensions>, + _skill_extensions: Option<&MacOsSeatbeltProfileExtensions>, +) -> Option { + None +} + #[cfg(test)] mod tests { use std::path::PathBuf; diff --git a/codex-rs/core/src/tools/js_repl/mod.rs b/codex-rs/core/src/tools/js_repl/mod.rs index 5b7b539bb3..98356e1d2d 100644 --- a/codex-rs/core/src/tools/js_repl/mod.rs +++ b/codex-rs/core/src/tools/js_repl/mod.rs @@ -617,6 +617,7 @@ impl JsReplManager { .features .enabled(crate::features::Feature::UseLinuxSandboxBwrap), windows_sandbox_level: turn.windows_sandbox_level, + macos_seatbelt_profile_extensions: None, }) .map_err(|err| format!("failed to configure sandbox for js_repl: {err}"))?; diff --git a/codex-rs/core/src/tools/runtimes/apply_patch.rs b/codex-rs/core/src/tools/runtimes/apply_patch.rs index d16f9b836b..3f1a175447 100644 --- a/codex-rs/core/src/tools/runtimes/apply_patch.rs +++ b/codex-rs/core/src/tools/runtimes/apply_patch.rs @@ -151,7 +151,7 @@ impl ToolRuntime for ApplyPatchRuntime { ) -> Result { let spec = Self::build_command_spec(req)?; let env = attempt - .env_for(spec, None) + .env_for(spec, None, None) .map_err(|err| ToolError::Codex(err.into()))?; let out = execute_env(env, attempt.policy, Self::stdout_stream(ctx)) .await diff --git a/codex-rs/core/src/tools/runtimes/shell.rs b/codex-rs/core/src/tools/runtimes/shell.rs index 6f1e4b44af..b53d32411e 100644 --- a/codex-rs/core/src/tools/runtimes/shell.rs +++ b/codex-rs/core/src/tools/runtimes/shell.rs @@ -10,6 +10,8 @@ use crate::features::Feature; use crate::powershell::prefix_powershell_script_with_utf8; use crate::sandboxing::SandboxPermissions; use crate::sandboxing::execute_env; +#[cfg(target_os = "macos")] +use crate::seatbelt_permissions::MacOsSeatbeltProfileExtensions; use crate::shell::ShellType; use crate::tools::network_approval::NetworkApprovalMode; use crate::tools::network_approval::NetworkApprovalSpec; @@ -31,6 +33,8 @@ use codex_network_proxy::NetworkProxy; use codex_protocol::protocol::ReviewDecision; use futures::future::BoxFuture; use std::path::PathBuf; +#[cfg(not(target_os = "macos"))] +type MacOsSeatbeltProfileExtensions = (); #[derive(Clone, Debug)] pub struct ShellRequest { @@ -42,6 +46,7 @@ pub struct ShellRequest { pub network: Option, pub sandbox_permissions: SandboxPermissions, pub justification: Option, + pub macos_seatbelt_profile_extensions: Option, pub exec_approval_requirement: ExecApprovalRequirement, } @@ -203,7 +208,11 @@ impl ToolRuntime for ShellRuntime { req.justification.clone(), )?; let env = attempt - .env_for(spec, req.network.as_ref()) + .env_for( + spec, + req.network.as_ref(), + req.macos_seatbelt_profile_extensions.as_ref(), + ) .map_err(|err| ToolError::Codex(err.into()))?; return ctx .session @@ -222,7 +231,11 @@ impl ToolRuntime for ShellRuntime { req.justification.clone(), )?; let mut env = attempt - .env_for(spec, req.network.as_ref()) + .env_for( + spec, + req.network.as_ref(), + req.macos_seatbelt_profile_extensions.as_ref(), + ) .map_err(|err| ToolError::Codex(err.into()))?; env.network_attempt_id = ctx.network_attempt_id.clone(); let out = execute_env(env, attempt.policy, Self::stdout_stream(ctx)) diff --git a/codex-rs/core/src/tools/runtimes/unified_exec.rs b/codex-rs/core/src/tools/runtimes/unified_exec.rs index be049d0934..e493242077 100644 --- a/codex-rs/core/src/tools/runtimes/unified_exec.rs +++ b/codex-rs/core/src/tools/runtimes/unified_exec.rs @@ -200,7 +200,7 @@ impl<'a> ToolRuntime for UnifiedExecRunt ) .map_err(|_| ToolError::Rejected("missing command line for PTY".to_string()))?; let exec_env = attempt - .env_for(spec, req.network.as_ref()) + .env_for(spec, req.network.as_ref(), None) .map_err(|err| ToolError::Codex(err.into()))?; self.manager .open_session_with_exec_env(&exec_env, req.tty) diff --git a/codex-rs/core/src/tools/sandboxing.rs b/codex-rs/core/src/tools/sandboxing.rs index 04dc10c3e1..2e6a17c524 100644 --- a/codex-rs/core/src/tools/sandboxing.rs +++ b/codex-rs/core/src/tools/sandboxing.rs @@ -293,6 +293,9 @@ impl<'a> SandboxAttempt<'a> { &self, spec: CommandSpec, network: Option<&NetworkProxy>, + macos_seatbelt_profile_extensions: Option< + &crate::sandboxing::MacOsSeatbeltProfileExtensions, + >, ) -> Result { self.manager .transform(crate::sandboxing::SandboxTransformRequest { @@ -305,6 +308,7 @@ impl<'a> SandboxAttempt<'a> { codex_linux_sandbox_exe: self.codex_linux_sandbox_exe, use_linux_sandbox_bwrap: self.use_linux_sandbox_bwrap, windows_sandbox_level: self.windows_sandbox_level, + macos_seatbelt_profile_extensions, }) } }