From 60cf6de0ba044a657fb96164a2f36258b738de08 Mon Sep 17 00:00:00 2001 From: Greg <288152503+gregyoung2@users.noreply.github.com> Date: Wed, 3 Jun 2026 10:12:40 -0700 Subject: [PATCH] tests: cover macOS capability propagation through turns --- codex-rs/core/tests/suite/mod.rs | 50 ++++++++++- .../core/tests/suite/request_permissions.rs | 86 +++++++++++++++++++ 2 files changed, 134 insertions(+), 2 deletions(-) diff --git a/codex-rs/core/tests/suite/mod.rs b/codex-rs/core/tests/suite/mod.rs index 1e4a5f9501..e64e012033 100644 --- a/codex-rs/core/tests/suite/mod.rs +++ b/codex-rs/core/tests/suite/mod.rs @@ -7,13 +7,56 @@ use codex_test_binary_support::TestBinaryDispatchMode; use codex_test_binary_support::configure_test_binary_dispatch; use ctor::ctor; +#[cfg(target_os = "macos")] +pub(crate) const MACOS_SANDBOX_CAPABILITY_PROBE_ARG: &str = + "--codex-test-macos-sandbox-capability-probe"; +#[cfg(target_os = "macos")] +pub(crate) const MACOS_SANDBOX_CAPABILITY_PROBE_MACH_SERVICE: &str = + "com.apple.coreservices.appleevents"; + +#[cfg(target_os = "macos")] +unsafe extern "C" { + static bootstrap_port: libc::c_uint; + fn bootstrap_look_up( + bootstrap_port: libc::c_uint, + service_name: *const libc::c_char, + service_port: *mut libc::c_uint, + ) -> libc::c_int; +} + +#[cfg(target_os = "macos")] +fn maybe_run_macos_sandbox_capability_probe() { + if std::env::args().nth(1).as_deref() != Some(MACOS_SANDBOX_CAPABILITY_PROBE_ARG) { + return; + } + + let mut service_port = 0; + // SAFETY: the service name and output port pointers remain valid for the duration of the call. + let result = unsafe { + bootstrap_look_up( + bootstrap_port, + c"com.apple.coreservices.appleevents".as_ptr(), + &mut service_port, + ) + }; + if result != 0 { + eprintln!("Mach lookup failed with result {result}"); + std::process::exit(1); + } + std::process::exit(0); +} + // This code runs before any other tests are run. // It allows the test binary to behave like codex and dispatch to apply_patch and codex-linux-sandbox // based on the arg0. // NOTE: this doesn't work on ARM #[ctor] pub static CODEX_ALIASES_TEMP_DIR: Option = { - configure_test_binary_dispatch("codex-core-tests", |exe_name, argv1| { + let guard = configure_test_binary_dispatch("codex-core-tests", |exe_name, argv1| { + #[cfg(target_os = "macos")] + if argv1 == Some(MACOS_SANDBOX_CAPABILITY_PROBE_ARG) { + return TestBinaryDispatchMode::Skip; + } if argv1 == Some(CODEX_CORE_APPLY_PATCH_ARG1) { return TestBinaryDispatchMode::DispatchArg0Only; } @@ -24,7 +67,10 @@ pub static CODEX_ALIASES_TEMP_DIR: Option = { return TestBinaryDispatchMode::DispatchArg0Only; } TestBinaryDispatchMode::InstallAliases - }) + }); + #[cfg(target_os = "macos")] + maybe_run_macos_sandbox_capability_probe(); + guard }; #[cfg(not(target_os = "windows"))] diff --git a/codex-rs/core/tests/suite/request_permissions.rs b/codex-rs/core/tests/suite/request_permissions.rs index a78e4a016e..09a0bdcfc6 100644 --- a/codex-rs/core/tests/suite/request_permissions.rs +++ b/codex-rs/core/tests/suite/request_permissions.rs @@ -7,6 +7,8 @@ use codex_features::Feature; use codex_protocol::config_types::ApprovalsReviewer; use codex_protocol::models::AdditionalPermissionProfile as PermissionProfile; use codex_protocol::models::FileSystemPermissions; +#[cfg(target_os = "macos")] +use codex_protocol::models::MacOsSandboxCapabilities; use codex_protocol::models::PermissionProfile as CorePermissionProfile; use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::protocol::AskForApproval; @@ -507,6 +509,90 @@ async fn request_permissions_tool_is_auto_denied_when_granular_request_permissio Ok(()) } +#[cfg(target_os = "macos")] +#[tokio::test(flavor = "current_thread")] +async fn managed_macos_capability_survives_additional_permissions_tool_execution() -> Result<()> { + skip_if_no_network!(Ok(())); + skip_if_sandbox!(Ok(())); + + let server = start_mock_server().await; + let approval_policy = AskForApproval::OnRequest; + let macos = MacOsSandboxCapabilities { + mach_lookup_services: vec![super::MACOS_SANDBOX_CAPABILITY_PROBE_MACH_SERVICE.to_string()], + apple_event_destinations: Vec::new(), + launch_services_open: false, + }; + let permission_profile = + CorePermissionProfile::read_only().with_macos_sandbox_capabilities(Some(macos)); + let permission_profile_for_config = permission_profile.clone(); + + let mut builder = test_codex().with_config(move |config| { + config.permissions.approval_policy = Constrained::allow_any(approval_policy); + config + .permissions + .set_permission_profile(permission_profile_for_config) + .expect("set permission profile"); + config + .features + .enable(Feature::ExecPermissionApprovals) + .expect("test config should allow feature update"); + }); + let test = builder.build(&server).await?; + + let requested_dir = test.workspace_path("macos-capability-probe-grant"); + fs::create_dir_all(&requested_dir)?; + let requested_permissions = requested_directory_write_permissions(&requested_dir); + let normalized_requested_permissions = normalized_directory_write_permissions(&requested_dir)?; + let test_exe = std::env::current_exe()?; + let probe_arg = super::MACOS_SANDBOX_CAPABILITY_PROBE_ARG; + let command = format!("{test_exe:?} {probe_arg}"); + let call_id = "managed-macos-capability-with-additional-permissions"; + let event = shell_event_with_request_permissions(call_id, &command, &requested_permissions)?; + + let _ = mount_sse_once( + &server, + sse(vec![ + ev_response_created("resp-macos-capability-1"), + event, + ev_completed("resp-macos-capability-1"), + ]), + ) + .await; + let results = mount_sse_once( + &server, + sse(vec![ + ev_assistant_message("msg-macos-capability-1", "done"), + ev_completed("resp-macos-capability-2"), + ]), + ) + .await; + + submit_turn(&test, call_id, approval_policy, permission_profile).await?; + let approval = expect_exec_approval(&test, &command).await; + assert_eq!( + approval.additional_permissions, + Some(normalized_requested_permissions.into()) + ); + test.codex + .submit(Op::ExecApproval { + id: approval.effective_approval_id(), + turn_id: None, + decision: ReviewDecision::Approved, + }) + .await?; + wait_for_completion(&test).await; + + let result = parse_result(&results.single_request().function_call_output(call_id)); + assert_eq!( + result.exit_code, + Some(0), + "capability probe should succeed after applying additional permissions: {}", + result.stdout + ); + + Ok(()) +} + #[tokio::test(flavor = "current_thread")] async fn relative_additional_permissions_resolve_against_tool_workdir() -> Result<()> { skip_if_no_network!(Ok(()));