Codex Handbook
sandboxing/src/seatbelt_tests.rs 1395 lines
use super::CreateSeatbeltCommandArgsParams;use super::MACOS_PATH_TO_SEATBELT_EXECUTABLE;use super::MACOS_SEATBELT_BASE_POLICY;use super::ProxyPolicyInputs;use super::UnixDomainSocketPolicy;use super::build_seatbelt_unreadable_glob_policy;use super::create_seatbelt_command_args;use super::create_seatbelt_command_args_for_legacy_policy;use super::dynamic_network_policy;use super::normalize_path_for_sandbox;use super::seatbelt_regex_for_unreadable_glob;use super::unix_socket_dir_params;use super::unix_socket_policy;use codex_network_proxy::ConfigReloader;use codex_network_proxy::ConfigReloaderFuture;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;use codex_protocol::permissions::FileSystemSandboxPolicy;use codex_protocol::permissions::FileSystemSpecialPath;use codex_protocol::permissions::NetworkSandboxPolicy;use codex_protocol::permissions::PROTECTED_METADATA_PATH_NAMES;use codex_protocol::protocol::SandboxPolicy;use codex_utils_absolute_path::AbsolutePathBuf;use pretty_assertions::assert_eq;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) {    let stderr = String::from_utf8_lossy(stderr);    let expected = format!("bash: {}: Operation not permitted\n", path.display());    assert!(        stderr == expected            || stderr.contains("sandbox-exec: sandbox_apply: Operation not permitted"),        "unexpected stderr: {stderr}"    );}fn absolute_path(path: &str) -> AbsolutePathBuf {    AbsolutePathBuf::from_absolute_path(Path::new(path)).expect("absolute path")}fn seatbelt_policy_arg(args: &[String]) -> &str {    let policy_index = args        .iter()        .position(|arg| arg == "-p")        .expect("seatbelt args should include -p");    args.get(policy_index + 1)        .expect("seatbelt args should include policy text")}fn seatbelt_protected_metadata_name_requirements(root: &Path) -> String {    let mut root = root.to_string_lossy().to_string();    while root.len() > 1 && root.ends_with('/') {        root.pop();    }    let root = regex_lite::escape(&root);    PROTECTED_METADATA_PATH_NAMES        .iter()        .map(|name| {            let name = regex_lite::escape(name);            if root == "/" {                format!(r#"(require-not (regex #"^/{name}(/.*)?$"))"#)            } else {                format!(r#"(require-not (regex #"^{root}/{name}(/.*)?$"))"#)            }        })        .collect::<Vec<_>>()        .join(" ")}struct TestConfigReloader;impl ConfigReloader for TestConfigReloader {    fn source_label(&self) -> String {        "seatbelt test config".to_string()    }    fn maybe_reload(&self) -> ConfigReloaderFuture<'_, Option<ConfigState>> {        Box::pin(async { Ok(None) })    }    fn reload_now(&self) -> ConfigReloaderFuture<'_, ConfigState> {        Box::pin(async { Err(anyhow::anyhow!("seatbelt test config cannot reload")) })    }}#[test]fn base_policy_allows_node_cpu_sysctls() {    assert!(        MACOS_SEATBELT_BASE_POLICY.contains("(sysctl-name \"machdep.cpu.brand_string\")"),        "base policy must allow CPU brand lookup for os.cpus()"    );    assert!(        MACOS_SEATBELT_BASE_POLICY.contains("(sysctl-name \"hw.model\")"),        "base policy must allow hardware model lookup for os.cpus()"    );}#[test]fn base_policy_allows_kmp_registration_shm_read_create_and_unlink() {    let expected = r##"(allow ipc-posix-shm-read-data  ipc-posix-shm-write-create  ipc-posix-shm-write-unlink  (ipc-posix-name-regex #"^/__KMP_REGISTERED_LIB_[0-9]+$"))"##;    assert!(        MACOS_SEATBELT_BASE_POLICY.contains(expected),        "base policy must allow only KMP registration shm read/create/unlink:\n{MACOS_SEATBELT_BASE_POLICY}"    );}#[test]fn create_seatbelt_args_routes_network_through_proxy_ports() {    let policy = dynamic_network_policy(        &SandboxPolicy::new_read_only_policy(),        /*enforce_managed_network*/ false,        &ProxyPolicyInputs {            ports: vec![43128, 48081],            has_proxy_config: true,            allow_local_binding: false,            ..ProxyPolicyInputs::default()        },    );    assert!(        policy.contains("(allow network-outbound (remote ip \"localhost:43128\"))"),        "expected HTTP proxy port allow rule in policy:\n{policy}"    );    assert!(        policy.contains("(allow network-outbound (remote ip \"localhost:48081\"))"),        "expected SOCKS proxy port allow rule in policy:\n{policy}"    );    assert!(        !policy.contains("\n(allow network-outbound)\n"),        "policy should not include blanket outbound allowance when proxy ports are present:\n{policy}"    );    assert!(        !policy.contains("(allow network-bind (local ip \"*:*\"))"),        "policy should not allow local binding unless explicitly enabled:\n{policy}"    );    assert!(        !policy.contains("(allow network-inbound (local ip \"localhost:*\"))"),        "policy should not allow loopback inbound unless explicitly enabled:\n{policy}"    );    assert!(        !policy.contains("(allow network-outbound (remote ip \"*:53\"))"),        "policy should not allow raw DNS unless local binding is explicitly enabled:\n{policy}"    );}#[test]fn dynamic_network_policy_allows_tls_without_darwin_user_cache_write() {    let policy = dynamic_network_policy(        &SandboxPolicy::WorkspaceWrite {            writable_roots: vec![],            network_access: true,            exclude_tmpdir_env_var: true,            exclude_slash_tmp: true,        },        /*enforce_managed_network*/ false,        &ProxyPolicyInputs::default(),    );    assert!(        policy.contains("(global-name \"com.apple.trustd.agent\")"),        "policy should keep trustd agent access for TLS certificate verification:\n{policy}"    );    assert!(        !policy.contains("DARWIN_USER_CACHE_DIR"),        "network policy should not grant broad user cache writes:\n{policy}"    );}#[test]fn explicit_unreadable_paths_are_excluded_from_full_disk_read_and_write_access() {    let unreadable = absolute_path("/tmp/codex-unreadable");    let file_system_policy = FileSystemSandboxPolicy::restricted(vec![        FileSystemSandboxEntry {            path: FileSystemPath::Special {                value: FileSystemSpecialPath::Root,            },            access: FileSystemAccessMode::Write,        },        FileSystemSandboxEntry {            path: FileSystemPath::Path { path: unreadable },            access: FileSystemAccessMode::Deny,        },    ]);    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("/"));    let unreadable_root = unreadable_roots.first().expect("expected unreadable root");    assert!(        policy.contains("(require-not (literal (param \"READABLE_ROOT_0_EXCLUDED_0\")))"),        "expected exact read carveout in policy:\n{policy}"    );    assert!(        policy.contains("(require-not (subpath (param \"READABLE_ROOT_0_EXCLUDED_0\")))"),        "expected read carveout in policy:\n{policy}"    );    assert!(        policy.contains("(require-not (literal (param \"WRITABLE_ROOT_0_EXCLUDED_0\")))"),        "expected exact write carveout in policy:\n{policy}"    );    assert!(        policy.contains("(require-not (subpath (param \"WRITABLE_ROOT_0_EXCLUDED_0\")))"),        "expected write carveout in policy:\n{policy}"    );    assert!(        policy.contains(&seatbelt_protected_metadata_name_requirements(Path::new(            "/"        ))),        "expected metadata protection regex deny requirements in policy:\n{policy}"    );    assert!(        args.iter().any(            |arg| arg == &format!("-DREADABLE_ROOT_0_EXCLUDED_0={}", unreadable_root.display())        ),        "expected read carveout parameter in args: {args:#?}"    );    let writable_definitions: Vec<String> = args        .iter()        .filter(|arg| arg.starts_with("-DWRITABLE_ROOT_"))        .cloned()        .collect();    assert_eq!(        writable_definitions,        vec![            "-DWRITABLE_ROOT_0=/".to_string(),            "-DWRITABLE_ROOT_0_EXCLUDED_0=/.codex".to_string(),            format!("-DWRITABLE_ROOT_0_EXCLUDED_1={}", unreadable_root.display()),        ],        "unexpected write carveout parameters in args: {args:#?}"    );}#[test]fn explicit_unreadable_paths_are_excluded_from_readable_roots() {    let root = absolute_path("/tmp/codex-readable");    let unreadable = absolute_path("/tmp/codex-readable/private");    let file_system_policy = FileSystemSandboxPolicy::restricted(vec![        FileSystemSandboxEntry {            path: FileSystemPath::Path { path: root },            access: FileSystemAccessMode::Read,        },        FileSystemSandboxEntry {            path: FileSystemPath::Path { path: unreadable },            access: FileSystemAccessMode::Deny,        },    ]);    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("/"));    let readable_root = readable_roots.first().expect("expected readable root");    let unreadable_roots = file_system_policy.get_unreadable_roots_with_cwd(Path::new("/"));    let unreadable_root = unreadable_roots.first().expect("expected unreadable root");    assert!(        policy.contains("(require-not (literal (param \"READABLE_ROOT_0_EXCLUDED_0\")))"),        "expected exact read carveout in policy:\n{policy}"    );    assert!(        policy.contains("(require-not (subpath (param \"READABLE_ROOT_0_EXCLUDED_0\")))"),        "expected read carveout in policy:\n{policy}"    );    assert!(        args.iter()            .any(|arg| arg == &format!("-DREADABLE_ROOT_0={}", readable_root.display())),        "expected readable root parameter in args: {args:#?}"    );    assert!(        args.iter().any(            |arg| arg == &format!("-DREADABLE_ROOT_0_EXCLUDED_0={}", unreadable_root.display())        ),        "expected read carveout parameter in args: {args:#?}"    );}#[test]fn unreadable_globstar_slash_matches_zero_or_more_directories() {    let regex = seatbelt_regex_for_unreadable_glob("/tmp/repo/**/*.env");    assert_eq!(regex.as_deref(), Some(r"^/tmp/repo/(.*/)?[^/]*\.env$"));    let regex = regex_lite::Regex::new(regex.as_deref().expect("glob should compile"))        .expect("regex should compile");    assert!(regex.is_match("/tmp/repo/.env"));    assert!(regex.is_match("/tmp/repo/app/.env"));    assert!(regex.is_match("/tmp/repo/app/config.env"));    assert!(!regex.is_match("/tmp/repo/app/config.toml"));}#[test]fn unreadable_globs_use_git_style_component_matching() {    let regex = seatbelt_regex_for_unreadable_glob("/tmp/repo/*/file[0-9]?.txt");    assert_eq!(        regex.as_deref(),        Some(r"^/tmp/repo/[^/]*/file[0-9][^/]\.txt$")    );    let regex = regex_lite::Regex::new(regex.as_deref().expect("glob should compile"))        .expect("regex should compile");    assert!(regex.is_match("/tmp/repo/app/file42.txt"));    assert!(!regex.is_match("/tmp/repo/app/nested/file42.txt"));    assert!(!regex.is_match("/tmp/repo/app/file4.txt"));    assert!(!regex.is_match("/tmp/repo/app/fileab.txt"));}#[test]fn unreadable_globs_treat_unclosed_character_classes_as_literals() {    let regex = seatbelt_regex_for_unreadable_glob("/tmp/repo/[*.env");    assert_eq!(regex.as_deref(), Some(r"^/tmp/repo/\[[^/]*\.env$"));    let regex = regex_lite::Regex::new(regex.as_deref().expect("glob should compile"))        .expect("regex should compile");    assert!(regex.is_match("/tmp/repo/[local.env"));    assert!(regex.is_match("/tmp/repo/[.env"));    assert!(!regex.is_match("/tmp/repo/local.env"));}#[cfg(unix)]#[test]fn unreadable_glob_policy_includes_canonicalized_static_prefix() {    use std::os::unix::fs::symlink;    let temp_dir = TempDir::new().expect("temp dir");    let real_root = temp_dir.path().join("real-root");    let link_root = temp_dir.path().join("link-root");    fs::create_dir(&real_root).expect("create real root");    symlink(&real_root, &link_root).expect("create symlinked root");    let pattern = format!("{}/**/*.env", link_root.display());    let canonical_pattern = format!(        "{}/**/*.env",        real_root            .canonicalize()            .expect("canonicalize real root")            .display()    );    let expected_regex = seatbelt_regex_for_unreadable_glob(&canonical_pattern)        .expect("canonical glob should compile");    let mut policy = FileSystemSandboxPolicy::default();    policy.entries.push(FileSystemSandboxEntry {        path: FileSystemPath::GlobPattern { pattern },        access: FileSystemAccessMode::Deny,    });    let seatbelt_policy = build_seatbelt_unreadable_glob_policy(&policy, temp_dir.path());    assert!(        seatbelt_policy.contains(&format!(r#"(deny file-read* (regex #"{expected_regex}"))"#)),        "expected canonicalized glob regex in policy:\n{seatbelt_policy}"    );}#[test]fn seatbelt_args_without_extension_profile_keep_legacy_preferences_read_access() {    let cwd = std::env::temp_dir();    let args = create_seatbelt_command_args_for_legacy_policy(        vec!["echo".to_string(), "ok".to_string()],        &SandboxPolicy::new_read_only_policy(),        cwd.as_path(),        /*enforce_managed_network*/ false,        /*network*/ None,    );    let policy = &args[1];    assert!(policy.contains("(allow user-preference-read)"));    assert!(!policy.contains("(allow user-preference-write)"));}#[test]fn create_seatbelt_args_allows_local_binding_when_explicitly_enabled() {    let policy = dynamic_network_policy(        &SandboxPolicy::new_read_only_policy(),        /*enforce_managed_network*/ false,        &ProxyPolicyInputs {            ports: vec![43128],            has_proxy_config: true,            allow_local_binding: true,            ..ProxyPolicyInputs::default()        },    );    assert!(        policy.contains("(allow network-bind (local ip \"*:*\"))"),        "policy should allow loopback local binding when explicitly enabled:\n{policy}"    );    assert!(        policy.contains("(allow network-inbound (local ip \"localhost:*\"))"),        "policy should allow loopback inbound when explicitly enabled:\n{policy}"    );    assert!(        policy.contains("(allow network-outbound (remote ip \"localhost:*\"))"),        "policy should allow loopback outbound when explicitly enabled:\n{policy}"    );    assert!(        policy.contains("(allow network-outbound (remote ip \"*:53\"))"),        "policy should allow DNS egress when local binding is explicitly enabled:\n{policy}"    );    assert!(        !policy.contains("\n(allow network-outbound)\n"),        "policy should keep proxy-routed behavior without blanket outbound allowance:\n{policy}"    );}#[test]fn dynamic_network_policy_preserves_restricted_policy_when_proxy_config_without_ports() {    let policy = dynamic_network_policy(        &SandboxPolicy::WorkspaceWrite {            writable_roots: vec![],            network_access: true,            exclude_tmpdir_env_var: true,            exclude_slash_tmp: true,        },        /*enforce_managed_network*/ false,        &ProxyPolicyInputs {            ports: vec![],            has_proxy_config: true,            allow_local_binding: false,            ..ProxyPolicyInputs::default()        },    );    assert!(        policy.contains("(socket-domain AF_SYSTEM)"),        "policy should keep the restricted network profile when proxy config is present without ports:\n{policy}"    );    assert!(        !policy.contains("\n(allow network-outbound)\n"),        "policy should not include blanket outbound allowance when proxy config is present without ports:\n{policy}"    );    assert!(        !policy.contains("(allow network-outbound (remote ip \"localhost:"),        "policy should not include proxy port allowance when proxy config is present without ports:\n{policy}"    );    assert!(        !policy.contains("(allow network-outbound (remote ip \"*:53\"))"),        "policy should stay fail-closed for DNS when no proxy ports are available:\n{policy}"    );}#[test]fn dynamic_network_policy_blocks_dns_when_local_binding_has_no_proxy_ports() {    let policy = dynamic_network_policy(        &SandboxPolicy::WorkspaceWrite {            writable_roots: vec![],            network_access: true,            exclude_tmpdir_env_var: true,            exclude_slash_tmp: true,        },        /*enforce_managed_network*/ false,        &ProxyPolicyInputs {            ports: vec![],            has_proxy_config: true,            allow_local_binding: true,            ..ProxyPolicyInputs::default()        },    );    assert!(        policy.contains("(allow network-bind (local ip \"*:*\"))"),        "policy should still allow explicitly configured local binding:\n{policy}"    );    assert!(        !policy.contains("(allow network-outbound (remote ip \"*:53\"))"),        "policy should not allow DNS egress when no proxy ports are available:\n{policy}"    );}#[test]fn dynamic_network_policy_preserves_restricted_policy_for_managed_network_without_proxy_config() {    let policy = dynamic_network_policy(        &SandboxPolicy::WorkspaceWrite {            writable_roots: vec![],            network_access: true,            exclude_tmpdir_env_var: true,            exclude_slash_tmp: true,        },        /*enforce_managed_network*/ true,        &ProxyPolicyInputs {            ports: vec![],            has_proxy_config: false,            allow_local_binding: false,            ..ProxyPolicyInputs::default()        },    );    assert!(        policy.contains("(socket-domain AF_SYSTEM)"),        "policy should keep the restricted network profile when managed network is active without proxy endpoints:\n{policy}"    );    assert!(        !policy.contains("\n(allow network-outbound)\n"),        "policy should not include blanket outbound allowance when managed network is active without proxy endpoints:\n{policy}"    );    assert!(        !policy.contains("(allow network-outbound (remote ip \"*:53\"))"),        "policy should stay fail-closed for DNS when no proxy endpoints are available:\n{policy}"    );}#[test]fn create_seatbelt_args_allowlists_unix_socket_paths() {    let policy = dynamic_network_policy(        &SandboxPolicy::new_read_only_policy(),        /*enforce_managed_network*/ false,        &ProxyPolicyInputs {            ports: vec![43128],            has_proxy_config: true,            allow_local_binding: false,            unix_domain_socket_policy: UnixDomainSocketPolicy::Restricted {                allowed: vec![absolute_path("/tmp/example.sock")],            },        },    );    assert!(        policy.contains("(allow system-socket (socket-domain AF_UNIX))"),        "policy should allow AF_UNIX socket creation for configured unix sockets:\n{policy}"    );    assert!(        policy.contains(            "(allow network-bind (local unix-socket (subpath (param \"UNIX_SOCKET_PATH_0\"))))"        ),        "policy should allow binding explicitly configured unix sockets:\n{policy}"    );    assert!(        policy.contains(            "(allow network-outbound (remote unix-socket (subpath (param \"UNIX_SOCKET_PATH_0\"))))"        ),        "policy should allow connecting to explicitly configured unix sockets:\n{policy}"    );    assert!(        !policy.contains("(allow network* (subpath"),        "policy should no longer use the generic subpath unix-socket rules:\n{policy}"    );}#[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_for_cwd(        &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_for_cwd(        &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_for_cwd(        &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 {        unix_domain_socket_policy: UnixDomainSocketPolicy::Restricted {            allowed: vec![absolute_path("/tmp/example.sock")],        },        ..ProxyPolicyInputs::default()    });    assert!(        allowlist_policy.ends_with('\n'),        "allowlist unix socket policy should end with a newline:\n{allowlist_policy}"    );    let allow_all_policy = unix_socket_policy(&ProxyPolicyInputs {        unix_domain_socket_policy: UnixDomainSocketPolicy::AllowAll,        ..ProxyPolicyInputs::default()    });    assert!(        allow_all_policy.ends_with('\n'),        "allow-all unix socket policy should end with a newline:\n{allow_all_policy}"    );}#[test]fn unix_socket_dir_params_use_stable_param_names() {    let params = unix_socket_dir_params(&ProxyPolicyInputs {        unix_domain_socket_policy: UnixDomainSocketPolicy::Restricted {            allowed: vec![                absolute_path("/tmp/b.sock"),                absolute_path("/tmp/a.sock"),                absolute_path("/tmp/a.sock"),            ],        },        ..ProxyPolicyInputs::default()    });    assert_eq!(        params,        vec![            (                "UNIX_SOCKET_PATH_0".to_string(),                PathBuf::from("/tmp/a.sock")            ),            (                "UNIX_SOCKET_PATH_1".to_string(),                PathBuf::from("/tmp/b.sock")            ),        ]    );}#[test]fn normalize_path_for_sandbox_rejects_relative_paths() {    assert_eq!(normalize_path_for_sandbox(Path::new("relative.sock")), None);}#[test]fn create_seatbelt_args_allows_all_unix_sockets_when_enabled() {    let policy = dynamic_network_policy(        &SandboxPolicy::new_read_only_policy(),        /*enforce_managed_network*/ false,        &ProxyPolicyInputs {            ports: vec![43128],            has_proxy_config: true,            allow_local_binding: false,            unix_domain_socket_policy: UnixDomainSocketPolicy::AllowAll,        },    );    assert!(        policy.contains("(allow system-socket (socket-domain AF_UNIX))"),        "policy should allow AF_UNIX socket creation when unix sockets are enabled:\n{policy}"    );    assert!(        policy.contains("(allow network-bind (local unix-socket))"),        "policy should allow binding unix sockets when enabled:\n{policy}"    );    assert!(        policy.contains("(allow network-outbound (remote unix-socket))"),        "policy should allow connecting to unix sockets when enabled:\n{policy}"    );    assert!(        !policy.contains("(allow network* (subpath"),        "policy should no longer use the generic subpath unix-socket rules:\n{policy}"    );}#[test]fn create_seatbelt_args_full_network_with_proxy_is_still_proxy_only() {    let policy = dynamic_network_policy(        &SandboxPolicy::WorkspaceWrite {            writable_roots: vec![],            network_access: true,            exclude_tmpdir_env_var: true,            exclude_slash_tmp: true,        },        /*enforce_managed_network*/ false,        &ProxyPolicyInputs {            ports: vec![43128],            has_proxy_config: true,            allow_local_binding: false,            ..ProxyPolicyInputs::default()        },    );    assert!(        policy.contains("(allow network-outbound (remote ip \"localhost:43128\"))"),        "expected proxy endpoint allow rule in policy:\n{policy}"    );    assert!(        !policy.contains("\n(allow network-outbound)\n"),        "policy should not include blanket outbound allowance when proxy is configured:\n{policy}"    );    assert!(        !policy.contains("\n(allow network-inbound)\n"),        "policy should not include blanket inbound allowance when proxy is configured:\n{policy}"    );}#[test]fn create_seatbelt_args_with_read_only_git_and_codex_subpaths() {    // Create a temporary workspace with two writable roots: one containing    // top-level workspace metadata paths and one without them.    let tmp = TempDir::new().expect("tempdir");    let PopulatedTmp {        vulnerable_root,        vulnerable_root_canonical,        dot_git_canonical,        dot_agents_canonical: _,        dot_codex_canonical,        empty_root,        empty_root_canonical,    } = populate_tmpdir(tmp.path());    let cwd = tmp.path().join("cwd");    fs::create_dir_all(&cwd).expect("create cwd");    // Build a policy that only includes the two test roots as writable and    // does not automatically include defaults TMPDIR or /tmp.    let policy = SandboxPolicy::WorkspaceWrite {        writable_roots: vec![vulnerable_root, empty_root]            .into_iter()            .map(|p| p.try_into().unwrap())            .collect(),        network_access: false,        exclude_tmpdir_env_var: true,        exclude_slash_tmp: true,    };    // Create the Seatbelt command to wrap a shell command that tries to    // write to .codex/config.toml in the vulnerable root.    let shell_command: Vec<String> = [        "bash",        "-c",        "echo 'sandbox_mode = \"danger-full-access\"' > \"$1\"",        "bash",        dot_codex_canonical            .join("config.toml")            .to_string_lossy()            .as_ref(),    ]    .iter()    .map(std::string::ToString::to_string)    .collect();    let args = create_seatbelt_command_args_for_legacy_policy(        shell_command.clone(),        &policy,        &cwd,        /*enforce_managed_network*/ false,        /*network*/ None,    );    let policy_text = seatbelt_policy_arg(&args);    assert!(        policy_text.contains("(require-all (subpath (param \"WRITABLE_ROOT_0\"))"),        "expected cwd writable root to carry protected carveouts:\n{policy_text}",    );    assert!(        policy_text.contains("WRITABLE_ROOT_0_EXCLUDED_0"),        "expected cwd metadata carveouts in policy:\n{policy_text}",    );    assert!(        policy_text.contains("WRITABLE_ROOT_0_EXCLUDED_1")            && policy_text.contains("WRITABLE_ROOT_0_EXCLUDED_2"),        "expected symbolic cwd .git/.agents carveouts in policy:\n{policy_text}",    );    assert!(        policy_text.contains("WRITABLE_ROOT_1_EXCLUDED_0")            && policy_text.contains("WRITABLE_ROOT_1_EXCLUDED_1"),        "expected explicit writable root .git/.codex carveouts in policy:\n{policy_text}",    );    assert!(        policy_text.contains(&seatbelt_protected_metadata_name_requirements(            &cwd.canonicalize().expect("canonicalize cwd")        )),        "expected cwd metadata protection regex requirements in policy:\n{policy_text}",    );    assert!(        policy_text.contains(&seatbelt_protected_metadata_name_requirements(            &vulnerable_root_canonical        )),        "expected populated root metadata protection regex requirements in policy:\n{policy_text}",    );    assert!(        policy_text.contains(&seatbelt_protected_metadata_name_requirements(            &empty_root_canonical        )),        "expected empty root metadata protection regex requirements in policy:\n{policy_text}",    );    let expected_definitions = [        format!(            "-DWRITABLE_ROOT_0={}",            cwd.canonicalize()                .expect("canonicalize cwd")                .to_string_lossy()        ),        format!(            "-DWRITABLE_ROOT_0_EXCLUDED_0={}",            cwd.canonicalize()                .expect("canonicalize cwd")                .join(".codex")                .display()        ),        format!(            "-DWRITABLE_ROOT_0_EXCLUDED_1={}",            cwd.canonicalize()                .expect("canonicalize cwd")                .join(".git")                .display()        ),        format!(            "-DWRITABLE_ROOT_0_EXCLUDED_2={}",            cwd.canonicalize()                .expect("canonicalize cwd")                .join(".agents")                .display()        ),        format!(            "-DWRITABLE_ROOT_1={}",            vulnerable_root_canonical.to_string_lossy()        ),        format!(            "-DWRITABLE_ROOT_1_EXCLUDED_0={}",            dot_git_canonical.to_string_lossy()        ),        format!(            "-DWRITABLE_ROOT_1_EXCLUDED_1={}",            dot_codex_canonical.to_string_lossy()        ),        format!(            "-DWRITABLE_ROOT_2={}",            empty_root_canonical.to_string_lossy()        ),    ];    let writable_definitions: Vec<String> = args        .iter()        .filter(|arg| arg.starts_with("-DWRITABLE_ROOT_"))        .cloned()        .collect();    assert_eq!(        writable_definitions, expected_definitions,        "unexpected writable-root parameter definitions in {args:#?}"    );    let command_index = args        .iter()        .position(|arg| arg == "--")        .expect("seatbelt args should include command separator");    assert_eq!(args[command_index + 1..], shell_command);    // Verify that .codex/config.toml cannot be modified under the generated    // Seatbelt policy.    let config_toml = dot_codex_canonical.join("config.toml");    let output = Command::new(MACOS_PATH_TO_SEATBELT_EXECUTABLE)        .args(&args)        .current_dir(&cwd)        .output()        .expect("execute seatbelt command");    assert_eq!(        "sandbox_mode = \"read-only\"\n",        String::from_utf8_lossy(&fs::read(&config_toml).expect("read config.toml")),        "config.toml should contain its original contents because it should not have been modified"    );    assert!(        !output.status.success(),        "command to write {} should fail under seatbelt",        &config_toml.display()    );    assert_seatbelt_denied(&output.stderr, &config_toml);    // Create a similar Seatbelt command that tries to write to a file in    // the .git folder, which should also be blocked.    let pre_commit_hook = dot_git_canonical.join("hooks").join("pre-commit");    let shell_command_git: Vec<String> = [        "bash",        "-c",        "echo 'pwned!' > \"$1\"",        "bash",        pre_commit_hook.to_string_lossy().as_ref(),    ]    .iter()    .map(std::string::ToString::to_string)    .collect();    let write_hooks_file_args = create_seatbelt_command_args_for_legacy_policy(        shell_command_git,        &policy,        &cwd,        /*enforce_managed_network*/ false,        /*network*/ None,    );    let output = Command::new(MACOS_PATH_TO_SEATBELT_EXECUTABLE)        .args(&write_hooks_file_args)        .current_dir(&cwd)        .output()        .expect("execute seatbelt command");    assert!(        !fs::exists(&pre_commit_hook).expect("exists pre-commit hook"),        "{} should not exist because it should not have been created",        pre_commit_hook.display()    );    assert!(        !output.status.success(),        "command to write {} should fail under seatbelt",        &pre_commit_hook.display()    );    assert_seatbelt_denied(&output.stderr, &pre_commit_hook);    // Verify that writing a file to the folder containing .git and .codex is allowed.    let allowed_file = vulnerable_root_canonical.join("allowed.txt");    let shell_command_allowed: Vec<String> = [        "bash",        "-c",        "echo 'this is allowed' > \"$1\"",        "bash",        allowed_file.to_string_lossy().as_ref(),    ]    .iter()    .map(std::string::ToString::to_string)    .collect();    let write_allowed_file_args = create_seatbelt_command_args_for_legacy_policy(        shell_command_allowed,        &policy,        &cwd,        /*enforce_managed_network*/ false,        /*network*/ None,    );    let output = Command::new(MACOS_PATH_TO_SEATBELT_EXECUTABLE)        .args(&write_allowed_file_args)        .current_dir(&cwd)        .output()        .expect("execute seatbelt command");    let stderr = String::from_utf8_lossy(&output.stderr);    if !output.status.success()        && stderr.contains("sandbox-exec: sandbox_apply: Operation not permitted")    {        return;    }    assert!(        output.status.success(),        "command to write {} should succeed under seatbelt",        &allowed_file.display()    );    assert_eq!(        "this is allowed\n",        String::from_utf8_lossy(&fs::read(&allowed_file).expect("read allowed.txt")),        "{} should contain the written text",        allowed_file.display()    );}#[test]fn create_seatbelt_args_block_first_time_dot_codex_creation_with_metadata_name_regex() {    let tmp = TempDir::new().expect("tempdir");    let repo_root = tmp.path().join("repo");    fs::create_dir_all(&repo_root).expect("create repo root");    Command::new("git")        .arg("init")        .arg(".")        .current_dir(&repo_root)        .output()        .expect("git init .");    let dot_codex = repo_root.join(".codex");    let config_toml = dot_codex.join("config.toml");    let policy = SandboxPolicy::WorkspaceWrite {        writable_roots: vec![repo_root.as_path().try_into().expect("absolute repo root")],        network_access: false,        exclude_tmpdir_env_var: true,        exclude_slash_tmp: true,    };    let shell_command: Vec<String> = [        "bash",        "-c",        "mkdir -p \"$1\" && echo 'sandbox_mode = \"danger-full-access\"' > \"$2\"",        "bash",        dot_codex.to_string_lossy().as_ref(),        config_toml.to_string_lossy().as_ref(),    ]    .iter()    .map(std::string::ToString::to_string)    .collect();    let args = create_seatbelt_command_args_for_legacy_policy(        shell_command,        &policy,        repo_root.as_path(),        /*enforce_managed_network*/ false,        /*network*/ None,    );    let policy_text = seatbelt_policy_arg(&args);    assert!(        policy_text.contains(&seatbelt_protected_metadata_name_requirements(            &repo_root.canonicalize().expect("canonicalize repo root")        )),        "expected metadata protection regex requirements in policy:\n{policy_text}"    );}#[test]fn create_seatbelt_args_with_read_only_git_pointer_file() {    let tmp = TempDir::new().expect("tempdir");    let worktree_root = tmp.path().join("worktree_root");    fs::create_dir_all(&worktree_root).expect("create worktree_root");    let gitdir = worktree_root.join("actual-gitdir");    fs::create_dir_all(&gitdir).expect("create gitdir");    let gitdir_config = gitdir.join("config");    let gitdir_config_contents = "[core]\n";    fs::write(&gitdir_config, gitdir_config_contents).expect("write gitdir config");    let dot_git = worktree_root.join(".git");    let dot_git_contents = format!("gitdir: {}\n", gitdir.to_string_lossy());    fs::write(&dot_git, &dot_git_contents).expect("write .git pointer");    let cwd = tmp.path().join("cwd");    fs::create_dir_all(&cwd).expect("create cwd");    let policy = SandboxPolicy::WorkspaceWrite {        writable_roots: vec![worktree_root.try_into().expect("worktree_root is absolute")],        network_access: false,        exclude_tmpdir_env_var: true,        exclude_slash_tmp: true,    };    let shell_command: Vec<String> = [        "bash",        "-c",        "echo 'pwned!' > \"$1\"",        "bash",        dot_git.to_string_lossy().as_ref(),    ]    .iter()    .map(std::string::ToString::to_string)    .collect();    let args = create_seatbelt_command_args_for_legacy_policy(        shell_command,        &policy,        &cwd,        /*enforce_managed_network*/ false,        /*network*/ None,    );    let output = Command::new(MACOS_PATH_TO_SEATBELT_EXECUTABLE)        .args(&args)        .current_dir(&cwd)        .output()        .expect("execute seatbelt command");    assert_eq!(        dot_git_contents,        String::from_utf8_lossy(&fs::read(&dot_git).expect("read .git pointer")),        ".git pointer file should not be modified under seatbelt"    );    assert!(        !output.status.success(),        "command to write {} should fail under seatbelt",        dot_git.display()    );    assert_seatbelt_denied(&output.stderr, &dot_git);    let shell_command_gitdir: Vec<String> = [        "bash",        "-c",        "echo 'pwned!' > \"$1\"",        "bash",        gitdir_config.to_string_lossy().as_ref(),    ]    .iter()    .map(std::string::ToString::to_string)    .collect();    let gitdir_args = create_seatbelt_command_args_for_legacy_policy(        shell_command_gitdir,        &policy,        &cwd,        /*enforce_managed_network*/ false,        /*network*/ None,    );    let output = Command::new(MACOS_PATH_TO_SEATBELT_EXECUTABLE)        .args(&gitdir_args)        .current_dir(&cwd)        .output()        .expect("execute seatbelt command");    assert_eq!(        gitdir_config_contents,        String::from_utf8_lossy(&fs::read(&gitdir_config).expect("read gitdir config")),        "gitdir config should contain its original contents because it should not have been modified"    );    assert!(        !output.status.success(),        "command to write {} should fail under seatbelt",        gitdir_config.display()    );    assert_seatbelt_denied(&output.stderr, &gitdir_config);}#[test]fn create_seatbelt_args_for_cwd_as_git_repo() {    // Create a temporary workspace with two writable roots: one containing    // top-level workspace metadata paths and one without them.    let tmp = TempDir::new().expect("tempdir");    let PopulatedTmp {        vulnerable_root,        vulnerable_root_canonical,        dot_git_canonical,        dot_agents_canonical,        dot_codex_canonical,        ..    } = populate_tmpdir(tmp.path());    // Build a policy that does not specify any writable_roots, but does    // use the default ones (cwd and TMPDIR) and verifies the protected    // metadata checks are done properly for cwd.    let policy = SandboxPolicy::WorkspaceWrite {        writable_roots: vec![],        network_access: false,        exclude_tmpdir_env_var: false,        exclude_slash_tmp: false,    };    let shell_command: Vec<String> = [        "bash",        "-c",        "echo 'sandbox_mode = \"danger-full-access\"' > \"$1\"",        "bash",        dot_codex_canonical            .join("config.toml")            .to_string_lossy()            .as_ref(),    ]    .iter()    .map(std::string::ToString::to_string)    .collect();    let args = create_seatbelt_command_args_for_legacy_policy(        shell_command.clone(),        &policy,        vulnerable_root.as_path(),        /*enforce_managed_network*/ false,        /*network*/ None,    );    let slash_tmp = PathBuf::from("/tmp")        .canonicalize()        .expect("canonicalize /tmp");    let policy_text = seatbelt_policy_arg(&args);    assert!(        policy_text.contains(&seatbelt_protected_metadata_name_requirements(            &vulnerable_root_canonical        )),        "expected cwd metadata protection regex requirements in policy:\n{policy_text}",    );    assert!(        policy_text.contains(&seatbelt_protected_metadata_name_requirements(&slash_tmp)),        "expected /tmp metadata protection regex requirements in policy:\n{policy_text}",    );    if let Some(tmpdir_env_var) = std::env::var("TMPDIR")        .ok()        .map(PathBuf::from)        .and_then(|p| p.canonicalize().ok())    {        assert!(            policy_text.contains(&seatbelt_protected_metadata_name_requirements(                &tmpdir_env_var            )),            "expected TMPDIR metadata protection regex requirements in policy:\n{policy_text}",        );    }    let expected_root = format!(        "-DWRITABLE_ROOT_0={}",        vulnerable_root_canonical.to_string_lossy()    );    assert!(        args.contains(&expected_root),        "missing {expected_root}: {args:#?}"    );    let expected_dot_git = format!(        "-DWRITABLE_ROOT_0_EXCLUDED_0={}",        dot_git_canonical.to_string_lossy()    );    assert!(        args.contains(&expected_dot_git),        "missing {expected_dot_git}: {args:#?}"    );    let expected_dot_codex = format!(        "-DWRITABLE_ROOT_0_EXCLUDED_1={}",        dot_codex_canonical.to_string_lossy()    );    assert!(        args.contains(&expected_dot_codex),        "missing {expected_dot_codex}: {args:#?}"    );    let unexpected_dot_agents = format!(        "-DWRITABLE_ROOT_0_EXCLUDED_1={}",        dot_agents_canonical.to_string_lossy()    );    assert!(        !args.contains(&unexpected_dot_agents),        "missing .agents should be handled by regex rather than materialized as a path param: {args:#?}"    );    let expected_slash_tmp = format!("-DWRITABLE_ROOT_1={}", slash_tmp.to_string_lossy());    assert!(        args.contains(&expected_slash_tmp),        "missing {expected_slash_tmp}: {args:#?}"    );    let command_index = args        .iter()        .position(|arg| arg == "--")        .expect("seatbelt args should include command separator");    assert_eq!(args[command_index + 1..], shell_command);}struct PopulatedTmp {    /// Path containing protected metadata subfolders.    /// For the purposes of this test, we consider this a "vulnerable" root    /// because a bad actor could write to .git/hooks/pre-commit so an    /// unsuspecting user would run code as privileged the next time they    /// ran `git commit` themselves, or modified .codex/config.toml to    /// contain `sandbox_mode = "danger-full-access"` so the agent would    /// have full privileges the next time it ran in that repo.    vulnerable_root: PathBuf,    vulnerable_root_canonical: PathBuf,    dot_git_canonical: PathBuf,    dot_agents_canonical: PathBuf,    dot_codex_canonical: PathBuf,    /// Path without protected metadata subfolders.    empty_root: PathBuf,    /// Canonicalized version of `empty_root`.    empty_root_canonical: PathBuf,}fn populate_tmpdir(tmp: &Path) -> PopulatedTmp {    let vulnerable_root = tmp.join("vulnerable_root");    fs::create_dir_all(&vulnerable_root).expect("create vulnerable_root");    // TODO(mbolin): Should also support the case where `.git` is a file    // with a gitdir: ... line.    Command::new("git")        .arg("init")        .arg(".")        .current_dir(&vulnerable_root)        .output()        .expect("git init .");    fs::create_dir_all(vulnerable_root.join(".codex")).expect("create .codex");    fs::write(        vulnerable_root.join(".codex").join("config.toml"),        "sandbox_mode = \"read-only\"\n",    )    .expect("write .codex/config.toml");    let empty_root = tmp.join("empty_root");    fs::create_dir_all(&empty_root).expect("create empty_root");    // Ensure we have canonical paths for -D parameter matching.    let vulnerable_root_canonical = vulnerable_root        .canonicalize()        .expect("canonicalize vulnerable_root");    let dot_git_canonical = vulnerable_root_canonical.join(".git");    let dot_agents_canonical = vulnerable_root_canonical.join(".agents");    let dot_codex_canonical = vulnerable_root_canonical.join(".codex");    let empty_root_canonical = empty_root.canonicalize().expect("canonicalize empty_root");    PopulatedTmp {        vulnerable_root,        vulnerable_root_canonical,        dot_git_canonical,        dot_agents_canonical,        dot_codex_canonical,        empty_root,        empty_root_canonical,    }}