From 1946a4c48b91f9d0505c2bc2ddf7a27c4078ba1b Mon Sep 17 00:00:00 2001 From: Leo Shimonaka Date: Tue, 17 Feb 2026 23:56:35 -0800 Subject: [PATCH] =?UTF-8?q?fix:=20Restricted=20Read:=20/System=20is=20too?= =?UTF-8?q?=20permissive=20for=20macOS=20platform=20de=E2=80=A6=20(#11798)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …fault Update the list of platform defaults included for `ReadOnlyAccess`. When `ReadOnlyAccess::Restricted::include_platform_defaults` is `true`, the policy defined in `codex-rs/core/src/seatbelt_platform_defaults.sbpl` is appended to enable macOS programs to function properly. --- codex-rs/core/src/seatbelt.rs | 6 + .../core/src/seatbelt_platform_defaults.sbpl | 181 ++++++++++++++++++ codex-rs/protocol/src/protocol.rs | 76 +++----- 3 files changed, 217 insertions(+), 46 deletions(-) create mode 100644 codex-rs/core/src/seatbelt_platform_defaults.sbpl diff --git a/codex-rs/core/src/seatbelt.rs b/codex-rs/core/src/seatbelt.rs index 6ae1b061a..03cb45d91 100644 --- a/codex-rs/core/src/seatbelt.rs +++ b/codex-rs/core/src/seatbelt.rs @@ -23,6 +23,7 @@ use crate::spawn::spawn_child_async; 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_SEATBELT_PLATFORM_DEFAULTS: &str = include_str!("seatbelt_platform_defaults.sbpl"); /// When working with `sandbox-exec`, only consider `sandbox-exec` in `/usr/bin` /// to defend against an attacker trying to inject a malicious version on the @@ -314,18 +315,23 @@ pub(crate) fn create_seatbelt_command_args_with_extensions( build_seatbelt_extensions, ); + let include_platform_defaults = sandbox_policy.include_platform_defaults(); let mut policy_sections = vec![ MACOS_SEATBELT_BASE_POLICY.to_string(), file_read_policy, file_write_policy, network_policy, ]; + if include_platform_defaults { + policy_sections.push(MACOS_SEATBELT_PLATFORM_DEFAULTS.to_string()); + } if !unix_socket_policy.is_empty() { policy_sections.push(unix_socket_policy); } if !seatbelt_extensions.policy.is_empty() { policy_sections.push(seatbelt_extensions.policy.clone()); } + let full_policy = policy_sections.join("\n"); let dir_params = [ diff --git a/codex-rs/core/src/seatbelt_platform_defaults.sbpl b/codex-rs/core/src/seatbelt_platform_defaults.sbpl new file mode 100644 index 000000000..ec2c59aca --- /dev/null +++ b/codex-rs/core/src/seatbelt_platform_defaults.sbpl @@ -0,0 +1,181 @@ +; macOS platform defaults included via `ReadOnlyAccess::Restricted::include_platform_defaults` + +; Read access to standard system paths +(allow file-read* file-test-existence + (subpath "/Library/Apple") + (subpath "/Library/Filesystems/NetFSPlugins") + (subpath "/Library/Preferences/Logging") + (subpath "/private/var/db/DarwinDirectory/local/recordStore.data") + (subpath "/private/var/db/timezone") + (subpath "/usr/lib") + (subpath "/usr/share") + (subpath "/Library/Preferences") + (subpath "/var/db") + (subpath "/private/var/db")) + +; Map system frameworks + dylibs for loader. +(allow file-map-executable + (subpath "/Library/Apple/System/Library/Frameworks") + (subpath "/Library/Apple/System/Library/PrivateFrameworks") + (subpath "/Library/Apple/usr/lib") + (subpath "/System/Library/Extensions") + (subpath "/System/Library/Frameworks") + (subpath "/System/Library/PrivateFrameworks") + (subpath "/System/Library/SubFrameworks") + (subpath "/System/iOSSupport/System/Library/Frameworks") + (subpath "/System/iOSSupport/System/Library/PrivateFrameworks") + (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") + (literal "/tmp") + (literal "/var") + (literal "/private/etc/localtime")) + +; Allow stat'ing of firmlink parent path components. +(allow file-read-metadata file-test-existence + (path-ancestors "/System/Volumes/Data/private")) + +; Allow processes to get their current working directory. +(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") + (literal "/dev/random") + (literal "/dev/urandom") + (literal "/private/etc/master.passwd") + (literal "/private/etc/passwd") + (literal "/private/etc/protocols") + (literal "/private/etc/services")) + +; Allow null/zero read/write. +(allow file-read* file-test-existence file-write-data + (literal "/dev/null") + (literal "/dev/zero")) + +; Allow read/write access to the file descriptors. +(allow file-read-data file-test-existence file-write-data + (subpath "/dev/fd")) + +; Provide access to debugger helpers. +(allow file-read* file-test-existence file-write-data file-ioctl + (literal "/dev/dtracehelper")) + +; Scratch space so tools can create temp files. +(allow file-read* file-test-existence file-write* (subpath "/tmp")) +(allow file-read* file-write* (subpath "/private/tmp")) +(allow file-read* file-write* (subpath "/var/tmp")) +(allow file-read* file-write* (subpath "/private/var/tmp")) + +; Allow reading standard config directories. +(allow file-read* (subpath "/etc")) +(allow file-read* (subpath "/private/etc")) + +; Some processes read /var metadata during startup. +(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")) +(allow file-read-data (subpath "/sbin")) +(allow file-read-metadata (subpath "/sbin")) +(allow file-read-data (subpath "/usr/bin")) +(allow file-read-metadata (subpath "/usr/bin")) +(allow file-read-data (subpath "/usr/sbin")) +(allow file-read-metadata (subpath "/usr/sbin")) +(allow file-read-data (subpath "/usr/libexec")) +(allow file-read-metadata (subpath "/usr/libexec")) + +(allow file-read* (subpath "/Library/Preferences")) +(allow file-read* (subpath "/opt/homebrew/lib")) +(allow file-read* (subpath "/usr/local/lib")) +(allow file-read* (subpath "/Applications")) + +; Terminal basics and device handles. +(allow file-read* (regex "^/dev/fd/(0|1|2)$")) +(allow file-write* (regex "^/dev/fd/(1|2)$")) +(allow file-read* file-write* (literal "/dev/null")) +(allow file-read* file-write* (literal "/dev/tty")) +(allow file-read-metadata (literal "/dev")) +(allow file-read-metadata (regex "^/dev/.*$")) +(allow file-read-metadata (literal "/dev/stdin")) +(allow file-read-metadata (literal "/dev/stdout")) +(allow file-read-metadata (literal "/dev/stderr")) +(allow file-read-metadata (regex "^/dev/tty[^/]*$")) +(allow file-read-metadata (regex "^/dev/pty[^/]*$")) +(allow file-read* file-write* (regex "^/dev/ttys[0-9]+$")) +(allow file-read* file-write* (literal "/dev/ptmx")) +(allow file-ioctl (regex "^/dev/ttys[0-9]+$")) + +; Allow metadata traversal for firmlink parents. +(allow file-read-metadata (literal "/System/Volumes") (vnode-type DIRECTORY)) +(allow file-read-metadata (literal "/System/Volumes/Data") (vnode-type DIRECTORY)) +(allow file-read-metadata (literal "/System/Volumes/Data/Users") (vnode-type DIRECTORY)) + +; App sandbox extensions +(allow file-read* (extension "com.apple.app-sandbox.read")) +(allow file-read* file-write* (extension "com.apple.app-sandbox.read-write")) \ No newline at end of file diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index f9c56c5bc..5676dc3e2 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -435,6 +435,17 @@ impl ReadOnlyAccess { matches!(self, ReadOnlyAccess::FullAccess) } + /// Returns true if platform defaults should be included for restricted read access. + pub fn include_platform_defaults(&self) -> bool { + matches!( + self, + ReadOnlyAccess::Restricted { + include_platform_defaults: true, + .. + } + ) + } + /// Returns the readable roots for restricted read access. /// /// For [`ReadOnlyAccess::FullAccess`], returns an empty list because @@ -442,53 +453,12 @@ impl ReadOnlyAccess { pub fn get_readable_roots_with_cwd(&self, cwd: &Path) -> Vec { let mut roots: Vec = match self { ReadOnlyAccess::FullAccess => return Vec::new(), - ReadOnlyAccess::Restricted { - include_platform_defaults, - readable_roots, - } => { + ReadOnlyAccess::Restricted { readable_roots, .. } => { let mut roots = readable_roots.clone(); - if *include_platform_defaults { - #[cfg(target_os = "macos")] - for platform_path in [ - "/bin", "/dev", "/etc", "/Library", "/private", "/sbin", "/System", "/tmp", - "/usr", - ] { - #[allow(clippy::expect_used)] - roots.push( - AbsolutePathBuf::from_absolute_path(platform_path) - .expect("platform defaults should be absolute"), - ); - } - - #[cfg(target_os = "linux")] - for platform_path in ["/bin", "/dev", "/etc", "/lib", "/lib64", "/tmp", "/usr"] - { - #[allow(clippy::expect_used)] - roots.push( - AbsolutePathBuf::from_absolute_path(platform_path) - .expect("platform defaults should be absolute"), - ); - } - - #[cfg(target_os = "windows")] - for platform_path in [ - r"C:\Windows", - r"C:\Program Files", - r"C:\Program Files (x86)", - r"C:\ProgramData", - ] { - #[allow(clippy::expect_used)] - roots.push( - AbsolutePathBuf::from_absolute_path(platform_path) - .expect("platform defaults should be absolute"), - ); - } - - match AbsolutePathBuf::from_absolute_path(cwd) { - Ok(cwd_root) => roots.push(cwd_root), - Err(err) => { - error!("Ignoring invalid cwd {cwd:?} for sandbox readable root: {err}"); - } + match AbsolutePathBuf::from_absolute_path(cwd) { + Ok(cwd_root) => roots.push(cwd_root), + Err(err) => { + error!("Ignoring invalid cwd {cwd:?} for sandbox readable root: {err}"); } } roots @@ -653,6 +623,20 @@ impl SandboxPolicy { } } + /// Returns true if platform defaults should be included for restricted read access. + pub fn include_platform_defaults(&self) -> bool { + if self.has_full_disk_read_access() { + return false; + } + match self { + SandboxPolicy::ReadOnly { access } => access.include_platform_defaults(), + SandboxPolicy::WorkspaceWrite { + read_only_access, .. + } => read_only_access.include_platform_defaults(), + SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => false, + } + } + /// Returns the list of readable roots (tailored to the current working /// directory) when read access is restricted. ///