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
Unverified
parent 42528a905d
commit 2e1003728c
8 changed files with 328 additions and 96 deletions
+4
View File
@@ -1694,6 +1694,7 @@ dependencies = [
"codex-stdio-to-uds",
"codex-terminal-detection",
"codex-tui",
"codex-utils-absolute-path",
"codex-utils-cargo-bin",
"codex-utils-cli",
"codex-utils-path",
@@ -2694,6 +2695,8 @@ dependencies = [
name = "codex-sandboxing"
version = "0.0.0"
dependencies = [
"anyhow",
"async-trait",
"codex-network-proxy",
"codex-protocol",
"codex-utils-absolute-path",
@@ -2702,6 +2705,7 @@ dependencies = [
"pretty_assertions",
"serde_json",
"tempfile",
"tokio",
"tracing",
"url",
"which 8.0.0",
+1
View File
@@ -45,6 +45,7 @@ codex-state = { workspace = true }
codex-stdio-to-uds = { workspace = true }
codex-terminal-detection = { workspace = true }
codex-tui = { workspace = true }
codex-utils-absolute-path = { workspace = true }
codex-utils-path = { workspace = true }
libc = { workspace = true }
owo-colors = { workspace = true }
+18 -8
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"),
+10
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)
+3
View File
@@ -23,5 +23,8 @@ url = { workspace = true }
which = { workspace = true }
[dev-dependencies]
anyhow = { workspace = true }
async-trait = { workspace = true }
pretty_assertions = { workspace = true }
tempfile = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt"] }
+10 -9
View File
@@ -9,10 +9,6 @@ use crate::policy_transforms::EffectiveSandboxPermissions;
use crate::policy_transforms::effective_file_system_sandbox_policy;
use crate::policy_transforms::effective_network_sandbox_policy;
use crate::policy_transforms::should_require_platform_sandbox;
#[cfg(target_os = "macos")]
use crate::seatbelt::MACOS_PATH_TO_SEATBELT_EXECUTABLE;
#[cfg(target_os = "macos")]
use crate::seatbelt::create_seatbelt_command_args_for_policies;
use codex_network_proxy::NetworkProxy;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfile;
@@ -208,14 +204,19 @@ impl SandboxManager {
SandboxType::None => (os_argv_to_strings(argv), None),
#[cfg(target_os = "macos")]
SandboxType::MacosSeatbelt => {
let mut args = create_seatbelt_command_args_for_policies(
os_argv_to_strings(argv),
&effective_file_system_policy,
effective_network_policy,
use crate::seatbelt::CreateSeatbeltCommandArgsParams;
use crate::seatbelt::MACOS_PATH_TO_SEATBELT_EXECUTABLE;
use crate::seatbelt::create_seatbelt_command_args;
let mut args = create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
command: os_argv_to_strings(argv),
file_system_sandbox_policy: &effective_file_system_policy,
network_sandbox_policy: effective_network_policy,
sandbox_policy_cwd,
enforce_managed_network,
network,
);
extra_allow_unix_sockets: &[],
});
let mut full_command = Vec::with_capacity(1 + args.len());
full_command.push(MACOS_PATH_TO_SEATBELT_EXECUTABLE.to_string());
full_command.append(&mut args);
+93 -53
View File
@@ -100,41 +100,54 @@ struct UnixSocketPathParam {
path: AbsolutePathBuf,
}
fn proxy_policy_inputs(network: Option<&NetworkProxy>) -> ProxyPolicyInputs {
if let Some(network) = network {
let mut env = HashMap::new();
network.apply_to_env(&mut env);
let unix_domain_socket_policy = if network.dangerously_allow_all_unix_sockets() {
UnixDomainSocketPolicy::AllowAll
} else {
let allowed = network
.allow_unix_sockets()
.iter()
.filter_map(
|socket_path| match normalize_path_for_sandbox(Path::new(socket_path)) {
Some(path) => Some((path.to_string_lossy().to_string(), path)),
None => {
warn!(
"ignoring network.allow_unix_sockets entry because it could not be normalized: {socket_path}"
);
None
}
},
)
.collect::<BTreeMap<_, _>>()
.into_values()
.collect();
UnixDomainSocketPolicy::Restricted { allowed }
};
return ProxyPolicyInputs {
ports: proxy_loopback_ports_from_env(&env),
has_proxy_config: has_proxy_url_env_vars(&env),
allow_local_binding: network.allow_local_binding(),
unix_domain_socket_policy,
};
}
fn proxy_policy_inputs(
network: Option<&NetworkProxy>,
extra_allow_unix_sockets: &[AbsolutePathBuf],
) -> ProxyPolicyInputs {
let extra_allowed = extra_allow_unix_sockets
.iter()
.filter_map(|socket_path| normalize_path_for_sandbox(socket_path.as_path()))
.collect::<Vec<_>>();
ProxyPolicyInputs::default()
match network {
Some(network) => {
let mut env = HashMap::new();
network.apply_to_env(&mut env);
let unix_domain_socket_policy = if network.dangerously_allow_all_unix_sockets() {
UnixDomainSocketPolicy::AllowAll
} else {
let mut allowed = network
.allow_unix_sockets()
.iter()
.filter_map(|socket_path| {
match normalize_path_for_sandbox(Path::new(socket_path)) {
Some(path) => Some(path),
None => {
warn!(
"ignoring network.allow_unix_sockets entry because it could not be normalized: {socket_path}"
);
None
}
}
})
.collect::<Vec<_>>();
allowed.extend(extra_allowed);
UnixDomainSocketPolicy::Restricted { allowed }
};
ProxyPolicyInputs {
ports: proxy_loopback_ports_from_env(&env),
has_proxy_config: has_proxy_url_env_vars(&env),
allow_local_binding: network.allow_local_binding(),
unix_domain_socket_policy,
}
}
None => ProxyPolicyInputs {
unix_domain_socket_policy: UnixDomainSocketPolicy::Restricted {
allowed: extra_allowed,
},
..Default::default()
},
}
}
fn normalize_path_for_sandbox(path: &Path) -> Option<AbsolutePathBuf> {
@@ -244,8 +257,14 @@ fn dynamic_network_policy_for_network(
enforce_managed_network: bool,
proxy: &ProxyPolicyInputs,
) -> String {
let should_use_restricted_network_policy =
!proxy.ports.is_empty() || proxy.has_proxy_config || enforce_managed_network;
let has_some_unix_socket_access = match &proxy.unix_domain_socket_policy {
UnixDomainSocketPolicy::AllowAll => true,
UnixDomainSocketPolicy::Restricted { allowed } => !allowed.is_empty(),
};
let should_use_restricted_network_policy = !proxy.ports.is_empty()
|| proxy.has_proxy_config
|| enforce_managed_network
|| (!network_policy.is_enabled() && has_some_unix_socket_access);
if should_use_restricted_network_policy {
let mut policy = String::new();
if proxy.allow_local_binding {
@@ -285,9 +304,13 @@ fn dynamic_network_policy_for_network(
if network_policy.is_enabled() {
// No proxy env is configured: retain the existing full-network behavior.
format!(
"(allow network-outbound)\n(allow network-inbound)\n{MACOS_SEATBELT_NETWORK_POLICY}"
)
let mut policy = String::from("(allow network-outbound)\n(allow network-inbound)\n");
let unix_socket_policy = unix_socket_policy(proxy);
if !unix_socket_policy.is_empty() {
policy.push_str("; allow unix domain sockets for local IPC\n");
policy.push_str(&unix_socket_policy);
}
format!("{policy}{MACOS_SEATBELT_NETWORK_POLICY}")
} else {
String::new()
}
@@ -357,31 +380,48 @@ fn build_seatbelt_access_policy(
}
#[cfg_attr(not(test), allow(dead_code))]
fn create_seatbelt_command_args(
fn create_seatbelt_command_args_for_legacy_policy(
command: Vec<String>,
sandbox_policy: &SandboxPolicy,
sandbox_policy_cwd: &Path,
enforce_managed_network: bool,
network: Option<&NetworkProxy>,
) -> Vec<String> {
create_seatbelt_command_args_for_policies(
let file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy(sandbox_policy, sandbox_policy_cwd);
create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
command,
&FileSystemSandboxPolicy::from_legacy_sandbox_policy(sandbox_policy, sandbox_policy_cwd),
NetworkSandboxPolicy::from(sandbox_policy),
file_system_sandbox_policy: &file_system_sandbox_policy,
network_sandbox_policy: NetworkSandboxPolicy::from(sandbox_policy),
sandbox_policy_cwd,
enforce_managed_network,
network,
)
extra_allow_unix_sockets: &[],
})
}
pub fn create_seatbelt_command_args_for_policies(
command: Vec<String>,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
sandbox_policy_cwd: &Path,
enforce_managed_network: bool,
network: Option<&NetworkProxy>,
) -> Vec<String> {
#[derive(Debug)]
pub struct CreateSeatbeltCommandArgsParams<'a> {
pub command: Vec<String>,
pub file_system_sandbox_policy: &'a FileSystemSandboxPolicy,
pub network_sandbox_policy: NetworkSandboxPolicy,
pub sandbox_policy_cwd: &'a Path,
pub enforce_managed_network: bool,
pub network: Option<&'a NetworkProxy>,
pub extra_allow_unix_sockets: &'a [AbsolutePathBuf],
}
pub fn create_seatbelt_command_args(args: CreateSeatbeltCommandArgsParams<'_>) -> Vec<String> {
let CreateSeatbeltCommandArgsParams {
command,
file_system_sandbox_policy,
network_sandbox_policy,
sandbox_policy_cwd,
enforce_managed_network,
network,
extra_allow_unix_sockets,
} = args;
let unreadable_roots =
file_system_sandbox_policy.get_unreadable_roots_with_cwd(sandbox_policy_cwd);
let (file_write_policy, file_write_dir_params) =
@@ -465,7 +505,7 @@ pub fn create_seatbelt_command_args_for_policies(
}
};
let proxy = proxy_policy_inputs(network);
let proxy = proxy_policy_inputs(network, extra_allow_unix_sockets);
let network_policy =
dynamic_network_policy_for_network(network_sandbox_policy, enforce_managed_network, &proxy);
+189 -26
View File
@@ -1,14 +1,23 @@
use super::CreateSeatbeltCommandArgsParams;
use super::MACOS_PATH_TO_SEATBELT_EXECUTABLE;
use super::MACOS_SEATBELT_BASE_POLICY;
use super::ProxyPolicyInputs;
use super::UnixDomainSocketPolicy;
use super::create_seatbelt_command_args;
use super::create_seatbelt_command_args_for_policies;
use super::create_seatbelt_command_args_for_legacy_policy;
use super::dynamic_network_policy;
use super::macos_dir_params;
use super::normalize_path_for_sandbox;
use super::unix_socket_dir_params;
use super::unix_socket_policy;
use codex_network_proxy::ConfigReloader;
use codex_network_proxy::ConfigState;
use codex_network_proxy::NetworkMode;
use codex_network_proxy::NetworkProxy;
use codex_network_proxy::NetworkProxyConfig;
use codex_network_proxy::NetworkProxyConstraints;
use codex_network_proxy::NetworkProxyState;
use codex_network_proxy::build_config_state;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
@@ -23,6 +32,7 @@ use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::sync::Arc;
use tempfile::TempDir;
fn assert_seatbelt_denied(stderr: &[u8], path: &Path) {
@@ -48,6 +58,23 @@ fn seatbelt_policy_arg(args: &[String]) -> &str {
.expect("seatbelt args should include policy text")
}
struct TestConfigReloader;
#[async_trait::async_trait]
impl ConfigReloader for TestConfigReloader {
fn source_label(&self) -> String {
"seatbelt test config".to_string()
}
async fn maybe_reload(&self) -> anyhow::Result<Option<ConfigState>> {
Ok(None)
}
async fn reload_now(&self) -> anyhow::Result<ConfigState> {
Err(anyhow::anyhow!("seatbelt test config cannot reload"))
}
}
#[test]
fn base_policy_allows_node_cpu_sysctls() {
assert!(
@@ -128,14 +155,15 @@ fn explicit_unreadable_paths_are_excluded_from_full_disk_read_and_write_access()
},
]);
let args = create_seatbelt_command_args_for_policies(
vec!["/bin/true".to_string()],
&file_system_policy,
NetworkSandboxPolicy::Restricted,
Path::new("/"),
/*enforce_managed_network*/ false,
/*network*/ None,
);
let args = create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
command: vec!["/bin/true".to_string()],
file_system_sandbox_policy: &file_system_policy,
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
sandbox_policy_cwd: Path::new("/"),
enforce_managed_network: false,
network: None,
extra_allow_unix_sockets: &[],
});
let policy = seatbelt_policy_arg(&args);
let unreadable_roots = file_system_policy.get_unreadable_roots_with_cwd(Path::new("/"));
@@ -193,14 +221,15 @@ fn explicit_unreadable_paths_are_excluded_from_readable_roots() {
},
]);
let args = create_seatbelt_command_args_for_policies(
vec!["/bin/true".to_string()],
&file_system_policy,
NetworkSandboxPolicy::Restricted,
Path::new("/"),
/*enforce_managed_network*/ false,
/*network*/ None,
);
let args = create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
command: vec!["/bin/true".to_string()],
file_system_sandbox_policy: &file_system_policy,
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
sandbox_policy_cwd: Path::new("/"),
enforce_managed_network: false,
network: None,
extra_allow_unix_sockets: &[],
});
let policy = seatbelt_policy_arg(&args);
let readable_roots = file_system_policy.get_readable_roots_with_cwd(Path::new("/"));
@@ -231,7 +260,7 @@ fn explicit_unreadable_paths_are_excluded_from_readable_roots() {
#[test]
fn seatbelt_args_without_extension_profile_keep_legacy_preferences_read_access() {
let cwd = std::env::temp_dir();
let args = create_seatbelt_command_args(
let args = create_seatbelt_command_args_for_legacy_policy(
vec!["echo".to_string(), "ok".to_string()],
&SandboxPolicy::new_read_only_policy(),
cwd.as_path(),
@@ -249,7 +278,7 @@ fn seatbelt_legacy_workspace_write_nested_readable_root_stays_writable() {
let cwd = tmp.path().join("workspace");
fs::create_dir_all(cwd.join("docs")).expect("create docs");
let docs = AbsolutePathBuf::from_absolute_path(cwd.join("docs")).expect("absolute docs");
let args = create_seatbelt_command_args(
let args = create_seatbelt_command_args_for_legacy_policy(
vec!["/bin/true".to_string()],
&SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
@@ -451,6 +480,140 @@ fn create_seatbelt_args_allowlists_unix_socket_paths() {
);
}
#[test]
fn create_seatbelt_args_allowlists_explicit_unix_socket_paths_without_proxy() {
let cwd = TempDir::new().expect("temp cwd");
let file_system_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy(
&SandboxPolicy::new_read_only_policy(),
cwd.path(),
);
let extra_allow_unix_sockets = vec![absolute_path("/tmp/codex-browser-use")];
let args = create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
command: vec!["/usr/bin/true".to_string()],
file_system_sandbox_policy: &file_system_policy,
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
sandbox_policy_cwd: cwd.path(),
enforce_managed_network: false,
network: None,
extra_allow_unix_sockets: &extra_allow_unix_sockets,
});
let policy = seatbelt_policy_arg(&args);
assert!(
policy.contains("(allow system-socket (socket-domain AF_UNIX))"),
"policy should allow AF_UNIX when explicit socket paths are requested:\n{policy}"
);
assert!(
policy.contains(
"(allow network-outbound (remote unix-socket (subpath (param \"UNIX_SOCKET_PATH_0\"))))"
),
"policy should allow outbound AF_UNIX traffic for explicit socket paths:\n{policy}"
);
let expected_socket_root = normalize_path_for_sandbox(Path::new("/tmp/codex-browser-use"))
.expect("socket root should normalize")
.to_string_lossy()
.into_owned();
assert!(
args.iter()
.any(|arg| arg == &format!("-DUNIX_SOCKET_PATH_0={expected_socket_root}")),
"seatbelt args should pass the configured socket root as a sandbox param: {args:?}"
);
}
#[tokio::test]
async fn create_seatbelt_args_merges_proxy_and_explicit_unix_socket_paths() -> anyhow::Result<()> {
let cwd = TempDir::new().expect("temp cwd");
let file_system_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy(
&SandboxPolicy::new_read_only_policy(),
cwd.path(),
);
let network_socket = "/tmp/codex-proxy-use";
let explicit_socket = "/tmp/codex-browser-use";
let mut network_config = NetworkProxyConfig::default();
network_config.network.enabled = true;
network_config.network.mode = NetworkMode::Full;
network_config
.network
.set_allow_unix_sockets(vec![network_socket.to_string()]);
let state = build_config_state(network_config, NetworkProxyConstraints::default())?;
let network_proxy = NetworkProxy::builder()
.state(Arc::new(NetworkProxyState::with_reloader(
state,
Arc::new(TestConfigReloader),
)))
.managed_by_codex(/*managed_by_codex*/ false)
.build()
.await?;
let extra_allow_unix_sockets = vec![absolute_path(explicit_socket)];
let args = create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
command: vec!["/usr/bin/true".to_string()],
file_system_sandbox_policy: &file_system_policy,
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
sandbox_policy_cwd: cwd.path(),
enforce_managed_network: false,
network: Some(&network_proxy),
extra_allow_unix_sockets: &extra_allow_unix_sockets,
});
let expected_explicit_socket = normalize_path_for_sandbox(Path::new(explicit_socket))
.expect("explicit socket root should normalize");
let expected_network_socket = normalize_path_for_sandbox(Path::new(network_socket))
.expect("network socket root should normalize");
let unix_socket_definitions = args
.iter()
.filter(|arg| arg.starts_with("-DUNIX_SOCKET_PATH_"))
.cloned()
.collect::<Vec<_>>();
assert_eq!(
unix_socket_definitions,
vec![
format!(
"-DUNIX_SOCKET_PATH_0={}",
expected_explicit_socket.display()
),
format!("-DUNIX_SOCKET_PATH_1={}", expected_network_socket.display()),
],
"seatbelt args should include both explicit and network proxy socket roots: {args:?}"
);
Ok(())
}
#[test]
fn create_seatbelt_args_preserves_full_network_with_explicit_unix_socket_paths() {
let cwd = TempDir::new().expect("temp cwd");
let file_system_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy(
&SandboxPolicy::new_read_only_policy(),
cwd.path(),
);
let extra_allow_unix_sockets = vec![absolute_path("/tmp/codex-browser-use")];
let args = create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
command: vec!["/usr/bin/true".to_string()],
file_system_sandbox_policy: &file_system_policy,
network_sandbox_policy: NetworkSandboxPolicy::Enabled,
sandbox_policy_cwd: cwd.path(),
enforce_managed_network: false,
network: None,
extra_allow_unix_sockets: &extra_allow_unix_sockets,
});
let policy = seatbelt_policy_arg(&args);
assert!(
policy.contains("(allow network-outbound)\n"),
"policy should preserve full outbound network access:\n{policy}"
);
assert!(
policy.contains("(allow network-inbound)\n"),
"policy should preserve full inbound network access:\n{policy}"
);
assert!(
policy.contains(
"(allow network-outbound (remote unix-socket (subpath (param \"UNIX_SOCKET_PATH_0\"))))"
),
"policy should still allow outbound AF_UNIX traffic for explicit socket paths:\n{policy}"
);
}
#[test]
fn unix_socket_policy_non_empty_output_is_newline_terminated() {
let allowlist_policy = unix_socket_policy(&ProxyPolicyInputs {
@@ -615,7 +778,7 @@ fn create_seatbelt_args_with_read_only_git_and_codex_subpaths() {
.iter()
.map(std::string::ToString::to_string)
.collect();
let args = create_seatbelt_command_args(
let args = create_seatbelt_command_args_for_legacy_policy(
shell_command.clone(),
&policy,
&cwd,
@@ -729,7 +892,7 @@ fn create_seatbelt_args_with_read_only_git_and_codex_subpaths() {
.iter()
.map(std::string::ToString::to_string)
.collect();
let write_hooks_file_args = create_seatbelt_command_args(
let write_hooks_file_args = create_seatbelt_command_args_for_legacy_policy(
shell_command_git,
&policy,
&cwd,
@@ -765,7 +928,7 @@ fn create_seatbelt_args_with_read_only_git_and_codex_subpaths() {
.iter()
.map(std::string::ToString::to_string)
.collect();
let write_allowed_file_args = create_seatbelt_command_args(
let write_allowed_file_args = create_seatbelt_command_args_for_legacy_policy(
shell_command_allowed,
&policy,
&cwd,
@@ -830,7 +993,7 @@ fn create_seatbelt_args_block_first_time_dot_codex_creation_with_exact_and_desce
.iter()
.map(std::string::ToString::to_string)
.collect();
let args = create_seatbelt_command_args(
let args = create_seatbelt_command_args_for_legacy_policy(
shell_command,
&policy,
repo_root.as_path(),
@@ -885,7 +1048,7 @@ fn create_seatbelt_args_with_read_only_git_pointer_file() {
.iter()
.map(std::string::ToString::to_string)
.collect();
let args = create_seatbelt_command_args(
let args = create_seatbelt_command_args_for_legacy_policy(
shell_command,
&policy,
&cwd,
@@ -921,7 +1084,7 @@ fn create_seatbelt_args_with_read_only_git_pointer_file() {
.iter()
.map(std::string::ToString::to_string)
.collect();
let gitdir_args = create_seatbelt_command_args(
let gitdir_args = create_seatbelt_command_args_for_legacy_policy(
shell_command_gitdir,
&policy,
&cwd,
@@ -984,7 +1147,7 @@ fn create_seatbelt_args_for_cwd_as_git_repo() {
.iter()
.map(std::string::ToString::to_string)
.collect();
let args = create_seatbelt_command_args(
let args = create_seatbelt_command_args_for_legacy_policy(
shell_command.clone(),
&policy,
vulnerable_root.as_path(),