From b045c36afb84eb89c6e1030637b3ba00695749d6 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 8 Mar 2026 21:58:00 -0700 Subject: [PATCH] Tighten macOS Seatbelt sandbox permissions - Scope mach-lookup to a specific allowlist of Mach services needed for shell operation (DNS, directory services, security, logging, launch services, etc.) instead of granting unrestricted IPC access. Add troubleshooting comment for diagnosing missing services. - Remove blanket iokit-open permission. Terminal shells don't need IOKit user client access (no GPU, audio, USB, or HID). Random numbers use /dev/urandom or getentropy(), timing uses syscalls. - Remove blanket process-exec and rely on the existing subpath rules that grant process-exec only to designated executable directories. Add process-exec to the project directory rule so users can run scripts like ./build.sh or ./gradlew. --- crates/terminal/src/sandbox_macos.rs | 85 ++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 5 deletions(-) diff --git a/crates/terminal/src/sandbox_macos.rs b/crates/terminal/src/sandbox_macos.rs index 35ec29fffca07b840030d6cb208aa885d573421e..bbe490d80b64e930c7cfbd5392b7ec9c26c06e41 100644 --- a/crates/terminal/src/sandbox_macos.rs +++ b/crates/terminal/src/sandbox_macos.rs @@ -51,14 +51,85 @@ fn generate_sbpl_profile(config: &SandboxConfig) -> String { let mut p = String::from("(version 1)\n(deny default)\n"); // Process lifecycle - p.push_str("(allow process-exec)\n"); p.push_str("(allow process-fork)\n"); p.push_str("(allow signal)\n"); - // System services needed for basic operation - p.push_str("(allow mach-lookup)\n"); + // Mach service allowlist. + // + // TROUBLESHOOTING: If users report broken terminal behavior (e.g. DNS failures, + // keychain errors, or commands hanging), a missing Mach service here is a likely + // cause. To diagnose: + // 1. Open Console.app and filter for "sandbox" or "deny mach-lookup" to find + // the denied service name. + // 2. Or test interactively: + // sandbox-exec -p '(version 1)(deny default)(allow mach-lookup ...)' /bin/sh + // 3. Add the missing service to the appropriate group below. + + // Logging: unified logging (os_log) and legacy syslog. + p.push_str("(allow mach-lookup (global-name \"com.apple.logd\"))\n"); + p.push_str("(allow mach-lookup (global-name \"com.apple.logd.events\"))\n"); + p.push_str("(allow mach-lookup (global-name \"com.apple.system.logger\"))\n"); + + // User/group directory lookups (getpwuid, getgrnam, id, etc.). + p.push_str("(allow mach-lookup (global-name \"com.apple.system.opendirectoryd.libinfo\"))\n"); + p.push_str( + "(allow mach-lookup (global-name \"com.apple.system.opendirectoryd.membership\"))\n", + ); + + // Darwin notification center, used internally by many system frameworks. + p.push_str("(allow mach-lookup (global-name \"com.apple.system.notification_center\"))\n"); + + // CFPreferences: reading user and system preferences. + p.push_str("(allow mach-lookup (global-name \"com.apple.cfprefsd.agent\"))\n"); + p.push_str("(allow mach-lookup (global-name \"com.apple.cfprefsd.daemon\"))\n"); + + // Temp directory management (_CS_DARWIN_USER_CACHE_DIR, etc.). + p.push_str("(allow mach-lookup (global-name \"com.apple.bsd.dirhelper\"))\n"); + + // DNS and network configuration. + p.push_str("(allow mach-lookup (global-name \"com.apple.dnssd.service\"))\n"); + p.push_str( + "(allow mach-lookup (global-name \"com.apple.SystemConfiguration.DNSConfiguration\"))\n", + ); + p.push_str("(allow mach-lookup (global-name \"com.apple.SystemConfiguration.configd\"))\n"); + p.push_str( + "(allow mach-lookup (global-name \"com.apple.SystemConfiguration.NetworkInformation\"))\n", + ); + p.push_str("(allow mach-lookup (global-name \"com.apple.SystemConfiguration.SCNetworkReachability\"))\n"); + p.push_str("(allow mach-lookup (global-name \"com.apple.networkd\"))\n"); + p.push_str("(allow mach-lookup (global-name \"com.apple.nehelper\"))\n"); + + // Security, keychain, and TLS certificate verification. + p.push_str("(allow mach-lookup (global-name \"com.apple.SecurityServer\"))\n"); + p.push_str("(allow mach-lookup (global-name \"com.apple.trustd.agent\"))\n"); + p.push_str("(allow mach-lookup (global-name \"com.apple.ocspd\"))\n"); + p.push_str("(allow mach-lookup (global-name \"com.apple.security.authtrampoline\"))\n"); + + // Launch Services: needed for the `open` command, file-type associations, + // and anything that uses NSWorkspace or LaunchServices. + p.push_str("(allow mach-lookup (global-name \"com.apple.coreservices.launchservicesd\"))\n"); + p.push_str("(allow mach-lookup (global-name \"com.apple.CoreServices.coreservicesd\"))\n"); + p.push_str("(allow mach-lookup (global-name-regex #\"^com\\.apple\\.lsd\\.\" ))\n"); + + // Kerberos: needed in enterprise environments for authentication. + p.push_str("(allow mach-lookup (global-name \"com.apple.GSSCred\"))\n"); + p.push_str("(allow mach-lookup (global-name \"org.h5l.kcm\"))\n"); + + // Distributed notifications: some command-line tools using Foundation may need this. + p.push_str( + "(allow mach-lookup (global-name-regex #\"^com\\.apple\\.distributed_notifications\"))\n", + ); + p.push_str("(allow sysctl-read)\n"); - p.push_str("(allow iokit-open)\n"); + + // No iokit-open rules: a terminal shell does not need to open IOKit user + // clients (kernel driver interfaces). IOKit access is needed for GPU/ + // graphics (IOAccelerator, AGPMClient), audio (IOAudioEngine), USB, + // Bluetooth, and similar hardware — none of which a shell requires. Random + // numbers come from /dev/urandom or getentropy(), and timing uses syscalls, + // so no IOKit involvement is needed for basic process operation. Chromium's + // network process and Firefox's content process both operate without any + // iokit-open rules. // System executable paths (read + execute) for path in &config.system_paths.executable { @@ -76,7 +147,11 @@ fn generate_sbpl_profile(config: &SandboxConfig) -> String { } // Project directory: full access - write_subpath_rule(&mut p, &config.project_dir, "file-read* file-write*"); + write_subpath_rule( + &mut p, + &config.project_dir, + "file-read* file-write* process-exec", + ); // User-configured additional paths for path in &config.additional_executable_paths {