Support Unix socket allowlists in macOS sandbox (#17654)

## Changes

Allows sandboxes to restrict overall network access while granting
access to specific unix sockets on mac.

## Details

- `codex sandbox macos`: adds a repeatable `--allow-unix-socket` option.
- `codex-sandboxing`: threads explicit Unix socket roots into the macOS
Seatbelt profile generation.
- Preserves restricted network behavior when only Unix socket IPC is
requested, and preserves full network behavior when full network is
already enabled.

## Verification

- `cargo test -p codex-cli -p codex-sandboxing`
- `cargo build -p codex-cli --bin codex`
- verified that `codex sandbox macos --allow-unix-socket /tmp/test.sock
-- test-client` grants access as expected
This commit is contained in:
aaronl-openai
2026-04-15 00:53:24 -07:00
committed by GitHub
parent 42528a905d
commit 2e1003728c
8 changed files with 328 additions and 96 deletions

View File

@@ -18,7 +18,10 @@ use codex_protocol::config_types::SandboxMode;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_sandboxing::landlock::create_linux_sandbox_command_args_for_policies;
#[cfg(target_os = "macos")]
use codex_sandboxing::seatbelt::create_seatbelt_command_args_for_policies;
use codex_sandboxing::seatbelt::CreateSeatbeltCommandArgsParams;
#[cfg(target_os = "macos")]
use codex_sandboxing::seatbelt::create_seatbelt_command_args;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_cli::CliConfigOverrides;
use tokio::process::Child;
use tokio::process::Command as TokioCommand;
@@ -39,6 +42,7 @@ pub async fn run_command_under_seatbelt(
) -> anyhow::Result<()> {
let SeatbeltCommand {
full_auto,
allow_unix_sockets,
log_denials,
config_overrides,
command,
@@ -50,6 +54,7 @@ pub async fn run_command_under_seatbelt(
codex_linux_sandbox_exe,
SandboxType::Seatbelt,
log_denials,
&allow_unix_sockets,
)
.await
}
@@ -78,6 +83,7 @@ pub async fn run_command_under_landlock(
codex_linux_sandbox_exe,
SandboxType::Landlock,
/*log_denials*/ false,
&[],
)
.await
}
@@ -98,6 +104,7 @@ pub async fn run_command_under_windows(
codex_linux_sandbox_exe,
SandboxType::Windows,
/*log_denials*/ false,
&[],
)
.await
}
@@ -116,6 +123,8 @@ async fn run_command_under_sandbox(
codex_linux_sandbox_exe: Option<PathBuf>,
sandbox_type: SandboxType,
log_denials: bool,
#[cfg_attr(not(target_os = "macos"), allow(unused_variables))]
allow_unix_sockets: &[AbsolutePathBuf],
) -> anyhow::Result<()> {
let config = load_debug_sandbox_config(
config_overrides
@@ -252,14 +261,15 @@ async fn run_command_under_sandbox(
let mut child = match sandbox_type {
#[cfg(target_os = "macos")]
SandboxType::Seatbelt => {
let args = create_seatbelt_command_args_for_policies(
let args = create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
command,
&config.permissions.file_system_sandbox_policy,
config.permissions.network_sandbox_policy,
sandbox_policy_cwd.as_path(),
/*enforce_managed_network*/ false,
network.as_ref(),
);
file_system_sandbox_policy: &config.permissions.file_system_sandbox_policy,
network_sandbox_policy: config.permissions.network_sandbox_policy,
sandbox_policy_cwd: sandbox_policy_cwd.as_path(),
enforce_managed_network: false,
network: network.as_ref(),
extra_allow_unix_sockets: allow_unix_sockets,
});
let network_policy = config.permissions.network_sandbox_policy;
spawn_debug_sandbox_child(
PathBuf::from("/usr/bin/sandbox-exec"),

View File

@@ -3,6 +3,7 @@ mod exit_status;
pub(crate) mod login;
use clap::Parser;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_cli::CliConfigOverrides;
pub use debug_sandbox::run_command_under_landlock;
@@ -22,6 +23,10 @@ pub struct SeatbeltCommand {
#[arg(long = "full-auto", default_value_t = false)]
pub full_auto: bool,
/// Allow the sandboxed command to bind/connect AF_UNIX sockets rooted at this path. Relative paths are resolved against the current directory. Repeat to allow multiple paths.
#[arg(long = "allow-unix-socket", value_parser = parse_allow_unix_socket_path)]
pub allow_unix_sockets: Vec<AbsolutePathBuf>,
/// While the command runs, capture macOS sandbox denials via `log stream` and print them after exit
#[arg(long = "log-denials", default_value_t = false)]
pub log_denials: bool,
@@ -34,6 +39,11 @@ pub struct SeatbeltCommand {
pub command: Vec<String>,
}
fn parse_allow_unix_socket_path(raw: &str) -> Result<AbsolutePathBuf, String> {
AbsolutePathBuf::relative_to_current_dir(raw)
.map_err(|err| format!("invalid path {raw}: {err}"))
}
#[derive(Debug, Parser)]
pub struct LandlockCommand {
/// Convenience alias for low-friction sandboxed automatic execution (network-disabled sandbox that can write to cwd and TMPDIR)