From 2628c0becfa5b6949064bec9ab485ba2b2d88c43 Mon Sep 17 00:00:00 2001 From: Javier Soto Date: Thu, 28 May 2026 12:33:47 -0700 Subject: [PATCH] Fix macOS fs helper startup runtime policy --- codex-rs/exec-server/src/fs_sandbox.rs | 37 ++++++++---- codex-rs/protocol/src/permissions.rs | 14 +++-- codex-rs/protocol/src/protocol.rs | 23 ++++++++ codex-rs/sandboxing/BUILD.bazel | 1 + .../restricted_platform_runtime_defaults.sbpl | 56 ++++++++++++++++++ ...estricted_read_only_platform_defaults.sbpl | 58 +----------------- codex-rs/sandboxing/src/seatbelt.rs | 7 +++ codex-rs/sandboxing/src/seatbelt_tests.rs | 59 +++++++++++++++++++ 8 files changed, 185 insertions(+), 70 deletions(-) create mode 100644 codex-rs/sandboxing/src/restricted_platform_runtime_defaults.sbpl diff --git a/codex-rs/exec-server/src/fs_sandbox.rs b/codex-rs/exec-server/src/fs_sandbox.rs index 3356791d44..9f05e1852e 100644 --- a/codex-rs/exec-server/src/fs_sandbox.rs +++ b/codex-rs/exec-server/src/fs_sandbox.rs @@ -154,16 +154,15 @@ fn add_helper_runtime_permissions( helper_read_roots: &[AbsolutePathBuf], cwd: &std::path::Path, ) { - if !file_system_policy.has_full_disk_read_access() { - let minimal_read_entry = FileSystemSandboxEntry { - path: FileSystemPath::Special { - value: FileSystemSpecialPath::Minimal, - }, - access: FileSystemAccessMode::Read, - }; - if !file_system_policy.entries.contains(&minimal_read_entry) { - file_system_policy.entries.push(minimal_read_entry); - } + // `:minimal` includes native runtime permissions needed before the helper reaches Rust. + let minimal_read_entry = FileSystemSandboxEntry { + path: FileSystemPath::Special { + value: FileSystemSpecialPath::Minimal, + }, + access: FileSystemAccessMode::Read, + }; + if !file_system_policy.entries.contains(&minimal_read_entry) { + file_system_policy.entries.push(minimal_read_entry); } for helper_read_root in helper_read_roots { @@ -361,6 +360,24 @@ mod tests { assert!(policy.include_platform_defaults()); } + #[test] + fn helper_permissions_enable_minimal_runtime_for_full_disk_read_profile() { + let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir().as_path()) + .expect("absolute cwd"); + let minimal_read_entry = + special_entry(FileSystemSpecialPath::Minimal, FileSystemAccessMode::Read); + let mut policy = restricted_policy(vec![special_entry( + FileSystemSpecialPath::Root, + FileSystemAccessMode::Read, + )]); + + add_helper_runtime_permissions(&mut policy, /*helper_read_roots*/ &[], cwd.as_path()); + + assert!(policy.entries.contains(&minimal_read_entry)); + assert!(policy.include_platform_runtime_defaults()); + assert!(!policy.include_platform_defaults()); + } + #[test] fn helper_permissions_preserve_existing_writes() { let codex_self_exe = std::env::current_exe().expect("current exe"); diff --git a/codex-rs/protocol/src/permissions.rs b/codex-rs/protocol/src/permissions.rs index f90859a92b..79b52489db 100644 --- a/codex-rs/protocol/src/permissions.rs +++ b/codex-rs/protocol/src/permissions.rs @@ -213,6 +213,7 @@ struct ResolvedFileSystemEntry { struct FileSystemSemanticSignature { has_full_disk_read_access: bool, has_full_disk_write_access: bool, + include_platform_runtime_defaults: bool, include_platform_defaults: bool, readable_roots: Vec, writable_roots: Vec, @@ -633,10 +634,9 @@ impl FileSystemSandboxPolicy { } } - /// Returns true when platform-default readable roots should be included. - pub fn include_platform_defaults(&self) -> bool { - !self.has_full_disk_read_access() - && matches!(self.kind, FileSystemSandboxKind::Restricted) + /// Returns true when minimum native runtime permissions should be included. + pub fn include_platform_runtime_defaults(&self) -> bool { + matches!(self.kind, FileSystemSandboxKind::Restricted) && self.entries.iter().any(|entry| { matches!( &entry.path, @@ -647,6 +647,11 @@ impl FileSystemSandboxPolicy { }) } + /// Returns true when platform-default readable roots should be included. + pub fn include_platform_defaults(&self) -> bool { + !self.has_full_disk_read_access() && self.include_platform_runtime_defaults() + } + pub fn resolve_access_with_cwd(&self, path: &Path, cwd: &Path) -> FileSystemAccessMode { match self.kind { FileSystemSandboxKind::Unrestricted | FileSystemSandboxKind::ExternalSandbox => { @@ -1233,6 +1238,7 @@ impl FileSystemSandboxPolicy { FileSystemSemanticSignature { has_full_disk_read_access: self.has_full_disk_read_access(), has_full_disk_write_access: self.has_full_disk_write_access(), + include_platform_runtime_defaults: self.include_platform_runtime_defaults(), include_platform_defaults: self.include_platform_defaults(), readable_roots: sorted_absolute_paths(self.get_readable_roots_with_cwd(cwd)), writable_roots: sorted_writable_roots(self.get_writable_roots_with_cwd(cwd)), diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index a675362b62..388e4ae482 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -4218,8 +4218,31 @@ mod tests { }]); assert!(read_only.has_full_disk_read_access()); assert!(!read_only.has_full_disk_write_access()); + assert!(!read_only.include_platform_runtime_defaults()); assert!(!read_only.include_platform_defaults()); + let read_only_with_minimal_runtime = FileSystemSandboxPolicy::restricted(vec![ + FileSystemSandboxEntry { + path: FileSystemPath::Special { + value: FileSystemSpecialPath::Root, + }, + access: FileSystemAccessMode::Read, + }, + FileSystemSandboxEntry { + path: FileSystemPath::Special { + value: FileSystemSpecialPath::Minimal, + }, + access: FileSystemAccessMode::Read, + }, + ]); + assert!(read_only_with_minimal_runtime.has_full_disk_read_access()); + assert!(read_only_with_minimal_runtime.include_platform_runtime_defaults()); + assert!(!read_only_with_minimal_runtime.include_platform_defaults()); + assert!(!read_only.is_semantically_equivalent_to( + &read_only_with_minimal_runtime, + std::path::Path::new("/") + )); + let writable = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry { path: FileSystemPath::Special { value: FileSystemSpecialPath::Root, diff --git a/codex-rs/sandboxing/BUILD.bazel b/codex-rs/sandboxing/BUILD.bazel index 57837fa809..2d83df7857 100644 --- a/codex-rs/sandboxing/BUILD.bazel +++ b/codex-rs/sandboxing/BUILD.bazel @@ -4,6 +4,7 @@ codex_rust_crate( name = "sandboxing", crate_name = "codex_sandboxing", compile_data = [ + "src/restricted_platform_runtime_defaults.sbpl", "src/restricted_read_only_platform_defaults.sbpl", "src/seatbelt_base_policy.sbpl", "src/seatbelt_network_policy.sbpl", diff --git a/codex-rs/sandboxing/src/restricted_platform_runtime_defaults.sbpl b/codex-rs/sandboxing/src/restricted_platform_runtime_defaults.sbpl new file mode 100644 index 0000000000..efc58b6f66 --- /dev/null +++ b/codex-rs/sandboxing/src/restricted_platform_runtime_defaults.sbpl @@ -0,0 +1,56 @@ +; macOS non-file runtime defaults included when a split filesystem policy requests `:minimal`. + +; Allow guarded vnodes. +(allow system-mac-syscall (mac-policy-name "vnguard")) + +; Determine whether a container is expected. +(allow system-mac-syscall + (require-all + (mac-policy-name "Sandbox") + (mac-syscall-number 67))) + +; Allow FSIOC_CAS_BSDFLAGS as alternate chflags. +(allow system-fsctl (fsctl-command FSIOC_CAS_BSDFLAGS)) + +; IOKit access for root domain services. +(allow iokit-open + (iokit-registry-entry-class "RootDomainUserClient")) + +; macOS standard library queries opendirectoryd at startup. +(allow mach-lookup (global-name "com.apple.system.opendirectoryd.libinfo")) + +; Allow IPC to analytics, logging, trust, and other system agents. +(allow mach-lookup + (global-name "com.apple.analyticsd") + (global-name "com.apple.analyticsd.messagetracer") + (global-name "com.apple.appsleep") + (global-name "com.apple.bsd.dirhelper") + (global-name "com.apple.cfprefsd.agent") + (global-name "com.apple.cfprefsd.daemon") + (global-name "com.apple.diagnosticd") + (global-name "com.apple.dt.automationmode.reader") + (global-name "com.apple.espd") + (global-name "com.apple.logd") + (global-name "com.apple.logd.events") + (global-name "com.apple.runningboard") + (global-name "com.apple.secinitd") + (global-name "com.apple.system.DirectoryService.libinfo_v1") + (global-name "com.apple.system.logger") + (global-name "com.apple.system.notification_center") + (global-name "com.apple.system.opendirectoryd.membership") + (global-name "com.apple.trustd") + (global-name "com.apple.trustd.agent") + (global-name "com.apple.xpc.activity.unmanaged") + (local-name "com.apple.cfprefsd.agent")) + +; Allow IPC to the syslog socket for logging. +(allow network-outbound (literal "/private/var/run/syslog")) + +; macOS notifications. +(allow ipc-posix-shm-read* + (ipc-posix-name "apple.shm.notification_center")) + +; Audio and power management services. +(allow mach-lookup (global-name "com.apple.audio.audiohald")) +(allow mach-lookup (global-name "com.apple.audio.AudioComponentRegistrar")) +(allow mach-lookup (global-name "com.apple.PowerManagement.control")) diff --git a/codex-rs/sandboxing/src/restricted_read_only_platform_defaults.sbpl b/codex-rs/sandboxing/src/restricted_read_only_platform_defaults.sbpl index d3015bc58a..d2bb3ea40c 100644 --- a/codex-rs/sandboxing/src/restricted_read_only_platform_defaults.sbpl +++ b/codex-rs/sandboxing/src/restricted_read_only_platform_defaults.sbpl @@ -1,4 +1,5 @@ -; macOS platform defaults included when a split filesystem policy requests `:minimal`. +; macOS platform file defaults included when a split filesystem policy requests `:minimal` +; without already granting full disk read access. ; Read access to standard system paths (allow file-read* file-test-existence @@ -40,15 +41,6 @@ (subpath "/System/iOSSupport/System/Library/SubFrameworks") (subpath "/usr/lib")) -; Allow guarded vnodes. -(allow system-mac-syscall (mac-policy-name "vnguard")) - -; Determine whether a container is expected. -(allow system-mac-syscall - (require-all - (mac-policy-name "Sandbox") - (mac-syscall-number 67))) - ; Allow resolution of standard system symlinks. (allow file-read-metadata file-test-existence (literal "/etc") @@ -64,9 +56,6 @@ (allow file-read* file-test-existence (literal "/")) -; Allow FSIOC_CAS_BSDFLAGS as alternate chflags. -(allow system-fsctl (fsctl-command FSIOC_CAS_BSDFLAGS)) - ; Allow access to standard special files. (allow file-read* file-test-existence (literal "/dev/autofs_nowait") @@ -109,53 +98,10 @@ (allow file-read-metadata (subpath "/var")) (allow file-read-metadata (subpath "/private/var")) -; IOKit access for root domain services. -(allow iokit-open - (iokit-registry-entry-class "RootDomainUserClient")) - -; macOS Standard library queries opendirectoryd at startup -(allow mach-lookup (global-name "com.apple.system.opendirectoryd.libinfo")) - -; Allow IPC to analytics, logging, trust, and other system agents. -(allow mach-lookup - (global-name "com.apple.analyticsd") - (global-name "com.apple.analyticsd.messagetracer") - (global-name "com.apple.appsleep") - (global-name "com.apple.bsd.dirhelper") - (global-name "com.apple.cfprefsd.agent") - (global-name "com.apple.cfprefsd.daemon") - (global-name "com.apple.diagnosticd") - (global-name "com.apple.dt.automationmode.reader") - (global-name "com.apple.espd") - (global-name "com.apple.logd") - (global-name "com.apple.logd.events") - (global-name "com.apple.runningboard") - (global-name "com.apple.secinitd") - (global-name "com.apple.system.DirectoryService.libinfo_v1") - (global-name "com.apple.system.logger") - (global-name "com.apple.system.notification_center") - (global-name "com.apple.system.opendirectoryd.membership") - (global-name "com.apple.trustd") - (global-name "com.apple.trustd.agent") - (global-name "com.apple.xpc.activity.unmanaged") - (local-name "com.apple.cfprefsd.agent")) - -; Allow IPC to the syslog socket for logging. -(allow network-outbound (literal "/private/var/run/syslog")) - -; macOS Notifications -(allow ipc-posix-shm-read* - (ipc-posix-name "apple.shm.notification_center")) - ; Regulatory domain support. (allow file-read* (literal "/private/var/db/eligibilityd/eligibility.plist")) -; Audio and power management services. -(allow mach-lookup (global-name "com.apple.audio.audiohald")) -(allow mach-lookup (global-name "com.apple.audio.AudioComponentRegistrar")) -(allow mach-lookup (global-name "com.apple.PowerManagement.control")) - ; Allow reading the minimum system runtime so exec works. (allow file-read-data (subpath "/bin")) (allow file-read-metadata (subpath "/bin")) diff --git a/codex-rs/sandboxing/src/seatbelt.rs b/codex-rs/sandboxing/src/seatbelt.rs index 7057ec46b9..36a9999058 100644 --- a/codex-rs/sandboxing/src/seatbelt.rs +++ b/codex-rs/sandboxing/src/seatbelt.rs @@ -19,6 +19,8 @@ use url::Url; const MACOS_SEATBELT_BASE_POLICY: &str = include_str!("seatbelt_base_policy.sbpl"); const MACOS_SEATBELT_NETWORK_POLICY: &str = include_str!("seatbelt_network_policy.sbpl"); +const MACOS_RESTRICTED_PLATFORM_RUNTIME_DEFAULTS: &str = + include_str!("restricted_platform_runtime_defaults.sbpl"); const MACOS_RESTRICTED_READ_ONLY_PLATFORM_DEFAULTS: &str = include_str!("restricted_read_only_platform_defaults.sbpl"); @@ -705,6 +707,8 @@ pub fn create_seatbelt_command_args(args: CreateSeatbeltCommandArgsParams<'_>) - let network_policy = dynamic_network_policy_for_network(network_sandbox_policy, enforce_managed_network, &proxy); + let include_platform_runtime_defaults = + file_system_sandbox_policy.include_platform_runtime_defaults(); let include_platform_defaults = file_system_sandbox_policy.include_platform_defaults(); let deny_read_policy = build_seatbelt_unreadable_glob_policy(file_system_sandbox_policy, sandbox_policy_cwd); @@ -715,6 +719,9 @@ pub fn create_seatbelt_command_args(args: CreateSeatbeltCommandArgsParams<'_>) - deny_read_policy, network_policy, ]; + if include_platform_runtime_defaults { + policy_sections.push(MACOS_RESTRICTED_PLATFORM_RUNTIME_DEFAULTS.to_string()); + } if include_platform_defaults { policy_sections.push(MACOS_RESTRICTED_READ_ONLY_PLATFORM_DEFAULTS.to_string()); } diff --git a/codex-rs/sandboxing/src/seatbelt_tests.rs b/codex-rs/sandboxing/src/seatbelt_tests.rs index d905d0adcf..ececde2fbd 100644 --- a/codex-rs/sandboxing/src/seatbelt_tests.rs +++ b/codex-rs/sandboxing/src/seatbelt_tests.rs @@ -108,6 +108,65 @@ fn base_policy_allows_node_cpu_sysctls() { ); } +#[test] +fn minimal_runtime_allows_core_foundation_user_lookup_ipc_without_expanding_base_policy() { + assert!( + !MACOS_SEATBELT_BASE_POLICY.contains("(global-name \"com.apple.logd\")"), + "CoreFoundation startup logging should be scoped to minimal runtime policies:\n{MACOS_SEATBELT_BASE_POLICY}" + ); + assert!( + !MACOS_SEATBELT_BASE_POLICY + .contains("(global-name \"com.apple.system.notification_center\")"), + "CoreFoundation user lookup notifications should be scoped to minimal runtime policies:\n{MACOS_SEATBELT_BASE_POLICY}" + ); + assert!( + !MACOS_SEATBELT_BASE_POLICY.contains("(ipc-posix-name \"apple.shm.notification_center\")"), + "CoreFoundation notification shared memory should be scoped to minimal runtime policies:\n{MACOS_SEATBELT_BASE_POLICY}" + ); + + let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ + FileSystemSandboxEntry { + path: FileSystemPath::Special { + value: FileSystemSpecialPath::Root, + }, + access: FileSystemAccessMode::Read, + }, + FileSystemSandboxEntry { + path: FileSystemPath::Special { + value: FileSystemSpecialPath::Minimal, + }, + access: FileSystemAccessMode::Read, + }, + ]); + 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); + + assert!( + policy.contains("(global-name \"com.apple.logd\")"), + "minimal runtime must allow CoreFoundation startup logging:\n{policy}" + ); + assert!( + policy.contains("(global-name \"com.apple.system.notification_center\")"), + "minimal runtime must allow CoreFoundation user lookup notifications:\n{policy}" + ); + assert!( + policy.contains("(ipc-posix-name \"apple.shm.notification_center\")"), + "minimal runtime must allow CoreFoundation notification shared memory:\n{policy}" + ); + assert!( + !policy.contains("(subpath \"/private/tmp\")"), + "full disk read must not acquire minimal runtime temporary writes:\n{policy}" + ); +} + #[test] fn base_policy_allows_kmp_registration_shm_read_create_and_unlink() { let expected = r##"(allow ipc-posix-shm-read-data