diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 9f62e87ee..f494c8528 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -2505,7 +2505,6 @@ dependencies = [ "codex-network-proxy", "codex-protocol", "codex-utils-absolute-path", - "dirs", "dunce", "libc", "pretty_assertions", diff --git a/codex-rs/app-server-protocol/schema/json/CommandExecutionRequestApprovalParams.json b/codex-rs/app-server-protocol/schema/json/CommandExecutionRequestApprovalParams.json index 66dccf15e..617fa1f3c 100644 --- a/codex-rs/app-server-protocol/schema/json/CommandExecutionRequestApprovalParams.json +++ b/codex-rs/app-server-protocol/schema/json/CommandExecutionRequestApprovalParams.json @@ -28,41 +28,6 @@ }, "type": "object" }, - "AdditionalMacOsPermissions": { - "properties": { - "accessibility": { - "type": "boolean" - }, - "automations": { - "$ref": "#/definitions/MacOsAutomationPermission" - }, - "calendar": { - "type": "boolean" - }, - "contacts": { - "$ref": "#/definitions/MacOsContactsPermission" - }, - "launchServices": { - "type": "boolean" - }, - "preferences": { - "$ref": "#/definitions/MacOsPreferencesPermission" - }, - "reminders": { - "type": "boolean" - } - }, - "required": [ - "accessibility", - "automations", - "calendar", - "contacts", - "launchServices", - "preferences", - "reminders" - ], - "type": "object" - }, "AdditionalNetworkPermissions": { "properties": { "enabled": { @@ -86,16 +51,6 @@ } ] }, - "macos": { - "anyOf": [ - { - "$ref": "#/definitions/AdditionalMacOsPermissions" - }, - { - "type": "null" - } - ] - }, "network": { "anyOf": [ { @@ -298,49 +253,6 @@ } ] }, - "MacOsAutomationPermission": { - "oneOf": [ - { - "enum": [ - "none", - "all" - ], - "type": "string" - }, - { - "additionalProperties": false, - "properties": { - "bundle_ids": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "bundle_ids" - ], - "title": "BundleIdsMacOsAutomationPermission", - "type": "object" - } - ] - }, - "MacOsContactsPermission": { - "enum": [ - "none", - "read_only", - "read_write" - ], - "type": "string" - }, - "MacOsPreferencesPermission": { - "enum": [ - "none", - "read_only", - "read_write" - ], - "type": "string" - }, "NetworkApprovalContext": { "properties": { "host": { diff --git a/codex-rs/app-server-protocol/schema/json/ServerRequest.json b/codex-rs/app-server-protocol/schema/json/ServerRequest.json index 6c63d36a3..7c11a4c02 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ServerRequest.json @@ -28,41 +28,6 @@ }, "type": "object" }, - "AdditionalMacOsPermissions": { - "properties": { - "accessibility": { - "type": "boolean" - }, - "automations": { - "$ref": "#/definitions/MacOsAutomationPermission" - }, - "calendar": { - "type": "boolean" - }, - "contacts": { - "$ref": "#/definitions/MacOsContactsPermission" - }, - "launchServices": { - "type": "boolean" - }, - "preferences": { - "$ref": "#/definitions/MacOsPreferencesPermission" - }, - "reminders": { - "type": "boolean" - } - }, - "required": [ - "accessibility", - "automations", - "calendar", - "contacts", - "launchServices", - "preferences", - "reminders" - ], - "type": "object" - }, "AdditionalNetworkPermissions": { "properties": { "enabled": { @@ -86,16 +51,6 @@ } ] }, - "macos": { - "anyOf": [ - { - "$ref": "#/definitions/AdditionalMacOsPermissions" - }, - { - "type": "null" - } - ] - }, "network": { "anyOf": [ { @@ -627,49 +582,6 @@ ], "type": "object" }, - "MacOsAutomationPermission": { - "oneOf": [ - { - "enum": [ - "none", - "all" - ], - "type": "string" - }, - { - "additionalProperties": false, - "properties": { - "bundle_ids": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "bundle_ids" - ], - "title": "BundleIdsMacOsAutomationPermission", - "type": "object" - } - ] - }, - "MacOsContactsPermission": { - "enum": [ - "none", - "read_only", - "read_write" - ], - "type": "string" - }, - "MacOsPreferencesPermission": { - "enum": [ - "none", - "read_only", - "read_write" - ], - "type": "string" - }, "McpElicitationArrayType": { "enum": [ "array" diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 28be3e5b3..f52faf5b1 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -28,41 +28,6 @@ }, "type": "object" }, - "AdditionalMacOsPermissions": { - "properties": { - "accessibility": { - "type": "boolean" - }, - "automations": { - "$ref": "#/definitions/MacOsAutomationPermission" - }, - "calendar": { - "type": "boolean" - }, - "contacts": { - "$ref": "#/definitions/MacOsContactsPermission" - }, - "launchServices": { - "type": "boolean" - }, - "preferences": { - "$ref": "#/definitions/MacOsPreferencesPermission" - }, - "reminders": { - "type": "boolean" - } - }, - "required": [ - "accessibility", - "automations", - "calendar", - "contacts", - "launchServices", - "preferences", - "reminders" - ], - "type": "object" - }, "AdditionalNetworkPermissions": { "properties": { "enabled": { @@ -86,16 +51,6 @@ } ] }, - "macos": { - "anyOf": [ - { - "$ref": "#/definitions/AdditionalMacOsPermissions" - }, - { - "type": "null" - } - ] - }, "network": { "anyOf": [ { @@ -2468,49 +2423,6 @@ "title": "JSONRPCResponse", "type": "object" }, - "MacOsAutomationPermission": { - "oneOf": [ - { - "enum": [ - "none", - "all" - ], - "type": "string" - }, - { - "additionalProperties": false, - "properties": { - "bundle_ids": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "bundle_ids" - ], - "title": "BundleIdsMacOsAutomationPermission", - "type": "object" - } - ] - }, - "MacOsContactsPermission": { - "enum": [ - "none", - "read_only", - "read_write" - ], - "type": "string" - }, - "MacOsPreferencesPermission": { - "enum": [ - "none", - "read_only", - "read_write" - ], - "type": "string" - }, "McpElicitationArrayType": { "enum": [ "array" diff --git a/codex-rs/app-server-protocol/schema/typescript/MacOsAutomationPermission.ts b/codex-rs/app-server-protocol/schema/typescript/MacOsAutomationPermission.ts deleted file mode 100644 index 31036b23e..000000000 --- a/codex-rs/app-server-protocol/schema/typescript/MacOsAutomationPermission.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type MacOsAutomationPermission = "none" | "all" | { "bundle_ids": Array }; diff --git a/codex-rs/app-server-protocol/schema/typescript/MacOsContactsPermission.ts b/codex-rs/app-server-protocol/schema/typescript/MacOsContactsPermission.ts deleted file mode 100644 index dd6d7b59e..000000000 --- a/codex-rs/app-server-protocol/schema/typescript/MacOsContactsPermission.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type MacOsContactsPermission = "none" | "read_only" | "read_write"; diff --git a/codex-rs/app-server-protocol/schema/typescript/MacOsPreferencesPermission.ts b/codex-rs/app-server-protocol/schema/typescript/MacOsPreferencesPermission.ts deleted file mode 100644 index 2f5234a26..000000000 --- a/codex-rs/app-server-protocol/schema/typescript/MacOsPreferencesPermission.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type MacOsPreferencesPermission = "none" | "read_only" | "read_write"; diff --git a/codex-rs/app-server-protocol/schema/typescript/index.ts b/codex-rs/app-server-protocol/schema/typescript/index.ts index 777feaa56..09c388337 100644 --- a/codex-rs/app-server-protocol/schema/typescript/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/index.ts @@ -41,9 +41,6 @@ export type { InputModality } from "./InputModality"; export type { LocalShellAction } from "./LocalShellAction"; export type { LocalShellExecAction } from "./LocalShellExecAction"; export type { LocalShellStatus } from "./LocalShellStatus"; -export type { MacOsAutomationPermission } from "./MacOsAutomationPermission"; -export type { MacOsContactsPermission } from "./MacOsContactsPermission"; -export type { MacOsPreferencesPermission } from "./MacOsPreferencesPermission"; export type { MessagePhase } from "./MessagePhase"; export type { ModeKind } from "./ModeKind"; export type { NetworkPolicyAmendment } from "./NetworkPolicyAmendment"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/AdditionalMacOsPermissions.ts b/codex-rs/app-server-protocol/schema/typescript/v2/AdditionalMacOsPermissions.ts deleted file mode 100644 index 177661bb0..000000000 --- a/codex-rs/app-server-protocol/schema/typescript/v2/AdditionalMacOsPermissions.ts +++ /dev/null @@ -1,8 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { MacOsAutomationPermission } from "../MacOsAutomationPermission"; -import type { MacOsContactsPermission } from "../MacOsContactsPermission"; -import type { MacOsPreferencesPermission } from "../MacOsPreferencesPermission"; - -export type AdditionalMacOsPermissions = { preferences: MacOsPreferencesPermission, automations: MacOsAutomationPermission, launchServices: boolean, accessibility: boolean, calendar: boolean, reminders: boolean, contacts: MacOsContactsPermission, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/AdditionalPermissionProfile.ts b/codex-rs/app-server-protocol/schema/typescript/v2/AdditionalPermissionProfile.ts index 701ba5431..65836c119 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/AdditionalPermissionProfile.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/AdditionalPermissionProfile.ts @@ -2,7 +2,6 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { AdditionalFileSystemPermissions } from "./AdditionalFileSystemPermissions"; -import type { AdditionalMacOsPermissions } from "./AdditionalMacOsPermissions"; import type { AdditionalNetworkPermissions } from "./AdditionalNetworkPermissions"; -export type AdditionalPermissionProfile = { network: AdditionalNetworkPermissions | null, fileSystem: AdditionalFileSystemPermissions | null, macos: AdditionalMacOsPermissions | null, }; +export type AdditionalPermissionProfile = { network: AdditionalNetworkPermissions | null, fileSystem: AdditionalFileSystemPermissions | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index b545f7986..73fe05eae 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -5,7 +5,6 @@ export type { AccountLoginCompletedNotification } from "./AccountLoginCompletedN export type { AccountRateLimitsUpdatedNotification } from "./AccountRateLimitsUpdatedNotification"; export type { AccountUpdatedNotification } from "./AccountUpdatedNotification"; export type { AdditionalFileSystemPermissions } from "./AdditionalFileSystemPermissions"; -export type { AdditionalMacOsPermissions } from "./AdditionalMacOsPermissions"; export type { AdditionalNetworkPermissions } from "./AdditionalNetworkPermissions"; export type { AdditionalPermissionProfile } from "./AdditionalPermissionProfile"; export type { AgentMessageDeltaNotification } from "./AgentMessageDeltaNotification"; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index 4492a1494..f3e56f4ff 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -1703,7 +1703,6 @@ mod tests { read: Some(vec![absolute_path("/tmp/allowed")]), write: None, }), - macos: None, }), proposed_execpolicy_amendment: None, proposed_network_policy_amendments: None, diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 559d825aa..2211e7aa1 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -32,10 +32,6 @@ use codex_protocol::mcp::Tool as McpTool; use codex_protocol::memory_citation::MemoryCitation as CoreMemoryCitation; use codex_protocol::memory_citation::MemoryCitationEntry as CoreMemoryCitationEntry; use codex_protocol::models::FileSystemPermissions as CoreFileSystemPermissions; -use codex_protocol::models::MacOsAutomationPermission as CoreMacOsAutomationPermission; -use codex_protocol::models::MacOsContactsPermission as CoreMacOsContactsPermission; -use codex_protocol::models::MacOsPreferencesPermission as CoreMacOsPreferencesPermission; -use codex_protocol::models::MacOsSeatbeltProfileExtensions as CoreMacOsSeatbeltProfileExtensions; use codex_protocol::models::MessagePhase; use codex_protocol::models::NetworkPermissions as CoreNetworkPermissions; use codex_protocol::models::PermissionProfile as CorePermissionProfile; @@ -1085,47 +1081,6 @@ impl From for CoreFileSystemPermissions { } } -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export_to = "v2/")] -pub struct AdditionalMacOsPermissions { - pub preferences: CoreMacOsPreferencesPermission, - pub automations: CoreMacOsAutomationPermission, - pub launch_services: bool, - pub accessibility: bool, - pub calendar: bool, - pub reminders: bool, - pub contacts: CoreMacOsContactsPermission, -} - -impl From for AdditionalMacOsPermissions { - fn from(value: CoreMacOsSeatbeltProfileExtensions) -> Self { - Self { - preferences: value.macos_preferences, - automations: value.macos_automation, - launch_services: value.macos_launch_services, - accessibility: value.macos_accessibility, - calendar: value.macos_calendar, - reminders: value.macos_reminders, - contacts: value.macos_contacts, - } - } -} - -impl From for CoreMacOsSeatbeltProfileExtensions { - fn from(value: AdditionalMacOsPermissions) -> Self { - Self { - macos_preferences: value.preferences, - macos_automation: value.automations, - macos_launch_services: value.launch_services, - macos_accessibility: value.accessibility, - macos_calendar: value.calendar, - macos_reminders: value.reminders, - macos_contacts: value.contacts, - } - } -} - #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] @@ -1182,7 +1137,6 @@ impl From for CoreRequestPermissionProfile { pub struct AdditionalPermissionProfile { pub network: Option, pub file_system: Option, - pub macos: Option, } impl From for AdditionalPermissionProfile { @@ -1190,7 +1144,6 @@ impl From for AdditionalPermissionProfile { Self { network: value.network.map(AdditionalNetworkPermissions::from), file_system: value.file_system.map(AdditionalFileSystemPermissions::from), - macos: value.macos.map(AdditionalMacOsPermissions::from), } } } @@ -1200,7 +1153,6 @@ impl From for CorePermissionProfile { Self { network: value.network.map(CoreNetworkPermissions::from), file_system: value.file_system.map(CoreFileSystemPermissions::from), - macos: value.macos.map(CoreMacOsSeatbeltProfileExtensions::from), } } } @@ -1222,7 +1174,6 @@ impl From for CorePermissionProfile { Self { network: value.network.map(CoreNetworkPermissions::from), file_system: value.file_system.map(CoreFileSystemPermissions::from), - macos: None, } } } @@ -6075,8 +6026,7 @@ mod tests { "fileSystem": { "read": ["relative/path"], "write": null - }, - "macos": null + } }, "proposedExecpolicyAmendment": null, "proposedNetworkPolicyAmendments": null, @@ -6090,90 +6040,6 @@ mod tests { ); } - #[test] - fn command_execution_request_approval_accepts_macos_automation_bundle_ids_object() { - let params = serde_json::from_value::(json!({ - "threadId": "thr_123", - "turnId": "turn_123", - "itemId": "call_123", - "command": "cat file", - "cwd": "/tmp", - "commandActions": null, - "reason": null, - "networkApprovalContext": null, - "additionalPermissions": { - "network": null, - "fileSystem": null, - "macos": { - "preferences": "read_only", - "automations": { - "bundle_ids": ["com.apple.Notes"] - }, - "launchServices": false, - "accessibility": false, - "calendar": false, - "reminders": false, - "contacts": "read_only" - } - }, - "proposedExecpolicyAmendment": null, - "proposedNetworkPolicyAmendments": null, - "availableDecisions": null - })) - .expect("bundle_ids object should deserialize"); - - assert_eq!( - params - .additional_permissions - .and_then(|permissions| permissions.macos) - .map(|macos| (macos.automations, macos.launch_services, macos.contacts)), - Some(( - CoreMacOsAutomationPermission::BundleIds(vec!["com.apple.Notes".to_string(),]), - false, - CoreMacOsContactsPermission::ReadOnly, - )) - ); - } - - #[test] - fn command_execution_request_approval_accepts_macos_reminders_permission() { - let params = serde_json::from_value::(json!({ - "threadId": "thr_123", - "turnId": "turn_123", - "itemId": "call_123", - "command": "cat file", - "cwd": "/tmp", - "commandActions": null, - "reason": null, - "networkApprovalContext": null, - "additionalPermissions": { - "network": null, - "fileSystem": null, - "macos": { - "preferences": "read_only", - "automations": "none", - "launchServices": false, - "accessibility": false, - "calendar": false, - "reminders": true, - "contacts": "none" - } - }, - "proposedExecpolicyAmendment": null, - "proposedNetworkPolicyAmendments": null, - "availableDecisions": null - })) - .expect("reminders permission should deserialize"); - - assert_eq!( - params - .additional_permissions - .and_then(|permissions| permissions.macos) - .map(|macos| macos.reminders), - Some(true) - ); - } - #[test] fn permissions_request_approval_uses_request_permission_profile() { let read_only_path = if cfg!(windows) { @@ -6331,7 +6197,6 @@ mod tests { .expect("path must be absolute"), ]), }), - macos: None, } ); } diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 1ca468827..cabea022a 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -1019,7 +1019,7 @@ the client can offer session-scoped and/or persistent approval choices. ### Permission requests -The built-in `request_permissions` tool sends an `item/permissions/requestApproval` JSON-RPC request to the client with the requested permission profile. This v2 payload mirrors the standalone tool's narrower permission shape, so it can request network access and additional filesystem access but does not include the broader `macos` branch used by command-execution `additionalPermissions`. +The built-in `request_permissions` tool sends an `item/permissions/requestApproval` JSON-RPC request to the client with the requested permission profile. This v2 payload mirrors the command-execution `additionalPermissions` shape: it can request network access and additional filesystem access. ```json { @@ -1055,7 +1055,7 @@ The client responds with `result.permissions`, which should be the granted subse } ``` -Only the granted subset matters on the wire. Any permissions omitted from `result.permissions` are treated as denied, including omitted nested keys inside `result.permissions.macos`, so a sparse response like `{ "permissions": { "macos": { "accessibility": true } } }` grants only accessibility. Any permissions not present in the original request are ignored by the server. +Only the granted subset matters on the wire. Any permissions omitted from `result.permissions` are treated as denied. Any permissions not present in the original request are ignored by the server. Within the same turn, granted permissions are sticky: later shell-like tool calls can automatically reuse the granted subset without reissuing a separate permission request. diff --git a/codex-rs/app-server/src/transport/mod.rs b/codex-rs/app-server/src/transport/mod.rs index 9e02e7723..c0653b903 100644 --- a/codex-rs/app-server/src/transport/mod.rs +++ b/codex-rs/app-server/src/transport/mod.rs @@ -801,7 +801,6 @@ mod tests { write: None, }, ), - macos: None, }, ), proposed_execpolicy_amendment: None, @@ -864,7 +863,6 @@ mod tests { write: None, }, ), - macos: None, }, ), proposed_execpolicy_amendment: None, @@ -891,7 +889,6 @@ mod tests { "read": [allowed_path], "write": null, }, - "macos": null, }) ); } diff --git a/codex-rs/cli/src/debug_sandbox.rs b/codex-rs/cli/src/debug_sandbox.rs index d519bd5f6..e5cccfc53 100644 --- a/codex-rs/cli/src/debug_sandbox.rs +++ b/codex-rs/cli/src/debug_sandbox.rs @@ -18,7 +18,7 @@ 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_with_extensions; +use codex_sandboxing::seatbelt::create_seatbelt_command_args_for_policies; use codex_utils_cli::CliConfigOverrides; use tokio::process::Child; use tokio::process::Command as TokioCommand; @@ -246,14 +246,13 @@ 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_with_extensions( + let args = create_seatbelt_command_args_for_policies( command, &config.permissions.file_system_sandbox_policy, config.permissions.network_sandbox_policy, sandbox_policy_cwd.as_path(), /*enforce_managed_network*/ false, network.as_ref(), - /*extensions*/ None, ); let network_policy = config.permissions.network_sandbox_policy; spawn_debug_sandbox_child( diff --git a/codex-rs/core/README.md b/codex-rs/core/README.md index 1605d28a0..63cd3b6f3 100644 --- a/codex-rs/core/README.md +++ b/codex-rs/core/README.md @@ -17,32 +17,8 @@ pointer file), the resolved `gitdir:` target, and `.codex` read-only. Network access and filesystem read/write roots are controlled by `SandboxPolicy`. Seatbelt consumes the resolved policy and enforces it. -Seatbelt also supports macOS permission-profile extensions layered on top of -`SandboxPolicy`: - -- no extension profile provided: - keeps legacy default preferences read access (`user-preference-read`). -- extension profile provided with no `macos_preferences` grant: - does not add preferences access clauses. -- `macos_preferences = "readonly"`: - enables cfprefs read clauses and `user-preference-read`. -- `macos_preferences = "readwrite"`: - includes readonly clauses plus `user-preference-write` and cfprefs shm write - clauses. -- `macos_automation = true`: - enables broad Apple Events send permissions. -- `macos_automation = ["com.apple.Notes", ...]`: - enables Apple Events send only to listed bundle IDs. -- `macos_launch_services = true`: - enables LaunchServices lookups and open/launch operations. -- `macos_accessibility = true`: - enables `com.apple.axserver` mach lookup. -- `macos_calendar = true`: - enables `com.apple.CalendarAgent` mach lookup. -- `macos_contacts = "read_only"`: - enables Address Book read access and Contacts read services. -- `macos_contacts = "read_write"`: - includes the readonly Contacts clauses plus Address Book writes and keychain/temp helpers required for writes. +Seatbelt also keeps the legacy default preferences read access +(`user-preference-read`) needed for cfprefs-backed macOS behavior. ### Linux diff --git a/codex-rs/core/src/codex_tests_guardian.rs b/codex-rs/core/src/codex_tests_guardian.rs index 33b8d15d1..18eb3d177 100644 --- a/codex-rs/core/src/codex_tests_guardian.rs +++ b/codex-rs/core/src/codex_tests_guardian.rs @@ -160,7 +160,6 @@ async fn guardian_allows_shell_additional_permissions_requests_past_policy_valid enabled: Some(true), }), file_system: None, - macos: None, }, "justification": params.justification.clone(), }) @@ -234,7 +233,7 @@ async fn guardian_allows_unified_exec_additional_permissions_requests_past_polic assert_eq!( output, - "missing `additional_permissions`; provide at least one of `network`, `file_system`, or `macos` when using `with_additional_permissions`" + "missing `additional_permissions`; provide at least one of `network` or `file_system` when using `with_additional_permissions`" ); } diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index a62919a44..4d6527ab8 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -4379,7 +4379,6 @@ fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> { shell_environment_policy: ShellEnvironmentPolicy::default(), windows_sandbox_mode: None, windows_sandbox_private_desktop: true, - macos_seatbelt_profile_extensions: None, }, approvals_reviewer: ApprovalsReviewer::User, enforce_residency: Constrained::allow_any(None), @@ -4522,7 +4521,6 @@ fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> { shell_environment_policy: ShellEnvironmentPolicy::default(), windows_sandbox_mode: None, windows_sandbox_private_desktop: true, - macos_seatbelt_profile_extensions: None, }, approvals_reviewer: ApprovalsReviewer::User, enforce_residency: Constrained::allow_any(None), @@ -4663,7 +4661,6 @@ fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> { shell_environment_policy: ShellEnvironmentPolicy::default(), windows_sandbox_mode: None, windows_sandbox_private_desktop: true, - macos_seatbelt_profile_extensions: None, }, approvals_reviewer: ApprovalsReviewer::User, enforce_residency: Constrained::allow_any(None), @@ -4790,7 +4787,6 @@ fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> { shell_environment_policy: ShellEnvironmentPolicy::default(), windows_sandbox_mode: None, windows_sandbox_private_desktop: true, - macos_seatbelt_profile_extensions: None, }, approvals_reviewer: ApprovalsReviewer::User, enforce_residency: Constrained::allow_any(None), diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 8435e6132..fa21cbad8 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -78,7 +78,6 @@ use codex_protocol::config_types::WebSearchConfig; use codex_protocol::config_types::WebSearchMode; use codex_protocol::config_types::WebSearchToolConfig; use codex_protocol::config_types::WindowsSandboxLevel; -use codex_protocol::models::MacOsSeatbeltProfileExtensions; use codex_protocol::openai_models::ModelsResponse; use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::permissions::FileSystemSandboxPolicy; @@ -206,9 +205,6 @@ pub struct Permissions { pub windows_sandbox_mode: Option, /// Whether the final Windows sandboxed child should run on a private desktop. pub windows_sandbox_private_desktop: bool, - /// Optional macOS seatbelt extension profile used to extend default - /// seatbelt permissions when running under seatbelt. - pub macos_seatbelt_profile_extensions: Option, } /// Application configuration loaded from disk and merged with overrides. @@ -2556,7 +2552,6 @@ impl Config { shell_environment_policy, windows_sandbox_mode, windows_sandbox_private_desktop, - macos_seatbelt_profile_extensions: None, }, approvals_reviewer, enforce_residency: enforce_residency.value, diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index 4291b6cca..11d8600ba 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -292,8 +292,6 @@ pub fn build_exec_request( enforce_managed_network, network: network.as_ref(), sandbox_policy_cwd: sandbox_cwd, - #[cfg(target_os = "macos")] - macos_seatbelt_profile_extensions: None, codex_linux_sandbox_exe: codex_linux_sandbox_exe.as_ref(), use_legacy_landlock, windows_sandbox_level, diff --git a/codex-rs/core/src/exec_policy_tests.rs b/codex-rs/core/src/exec_policy_tests.rs index f062bcc74..200f302a2 100644 --- a/codex-rs/core/src/exec_policy_tests.rs +++ b/codex-rs/core/src/exec_policy_tests.rs @@ -828,6 +828,33 @@ fn unmatched_on_request_uses_split_filesystem_policy_for_escalation_prompts() { ); } +#[tokio::test] +async fn exec_approval_requirement_prompts_for_inline_additional_permissions_under_on_request() { + assert_exec_approval_requirement_for_command( + ExecApprovalRequirementScenario { + policy_src: None, + command: vec![ + "zsh".to_string(), + "-lc".to_string(), + "touch requested-dir/requested-but-unused.txt".to_string(), + ], + approval_policy: AskForApproval::OnRequest, + sandbox_policy: SandboxPolicy::new_read_only_policy(), + file_system_sandbox_policy: read_only_file_system_sandbox_policy(), + sandbox_permissions: SandboxPermissions::WithAdditionalPermissions, + prefix_rule: None, + }, + ExecApprovalRequirement::NeedsApproval { + reason: None, + proposed_execpolicy_amendment: Some(ExecPolicyAmendment::new(vec![ + "touch".to_string(), + "requested-dir/requested-but-unused.txt".to_string(), + ])), + }, + ) + .await; +} + #[tokio::test] async fn exec_approval_requirement_rejects_unmatched_sandbox_escalation_when_granular_sandbox_is_disabled() { diff --git a/codex-rs/core/src/seatbelt.rs b/codex-rs/core/src/seatbelt.rs index f81016a99..353c9708f 100644 --- a/codex-rs/core/src/seatbelt.rs +++ b/codex-rs/core/src/seatbelt.rs @@ -9,7 +9,7 @@ use codex_network_proxy::NetworkProxy; use codex_protocol::permissions::FileSystemSandboxPolicy; use codex_protocol::permissions::NetworkSandboxPolicy; use codex_sandboxing::seatbelt::MACOS_PATH_TO_SEATBELT_EXECUTABLE; -use codex_sandboxing::seatbelt::create_seatbelt_command_args_for_policies_with_extensions; +use codex_sandboxing::seatbelt::create_seatbelt_command_args_for_policies; use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; @@ -24,14 +24,13 @@ pub async fn spawn_command_under_seatbelt( network: Option<&NetworkProxy>, mut env: HashMap, ) -> std::io::Result { - let args = create_seatbelt_command_args_for_policies_with_extensions( + let args = create_seatbelt_command_args_for_policies( command, &FileSystemSandboxPolicy::from_legacy_sandbox_policy(sandbox_policy, sandbox_policy_cwd), NetworkSandboxPolicy::from(sandbox_policy), sandbox_policy_cwd, /*enforce_managed_network*/ false, network, - /*extensions*/ None, ); let arg0 = None; env.insert(CODEX_SANDBOX_ENV_VAR.to_string(), "seatbelt".to_string()); diff --git a/codex-rs/core/src/tools/handlers/mod.rs b/codex-rs/core/src/tools/handlers/mod.rs index 3241b323b..1afab676a 100644 --- a/codex-rs/core/src/tools/handlers/mod.rs +++ b/codex-rs/core/src/tools/handlers/mod.rs @@ -127,18 +127,14 @@ pub(crate) fn normalize_and_validate_additional_permissions( } let Some(additional_permissions) = additional_permissions else { return Err( - "missing `additional_permissions`; provide at least one of `network`, `file_system`, or `macos` when using `with_additional_permissions`" + "missing `additional_permissions`; provide at least one of `network` or `file_system` when using `with_additional_permissions`" .to_string(), ); }; - #[cfg(not(target_os = "macos"))] - if additional_permissions.macos.is_some() { - return Err("`additional_permissions.macos` is only supported on macOS".to_string()); - } let normalized = normalize_additional_permissions(additional_permissions)?; if normalized.is_empty() { return Err( - "`additional_permissions` must include at least one requested permission in `network`, `file_system`, or `macos`" + "`additional_permissions` must include at least one requested permission in `network` or `file_system`" .to_string(), ); } diff --git a/codex-rs/core/src/tools/js_repl/mod.rs b/codex-rs/core/src/tools/js_repl/mod.rs index a9e3aa0f2..beb2930b2 100644 --- a/codex-rs/core/src/tools/js_repl/mod.rs +++ b/codex-rs/core/src/tools/js_repl/mod.rs @@ -1068,8 +1068,6 @@ impl JsReplManager { enforce_managed_network: has_managed_network_requirements, network: None, sandbox_policy_cwd: &turn.cwd, - #[cfg(target_os = "macos")] - macos_seatbelt_profile_extensions: None, codex_linux_sandbox_exe: turn.codex_linux_sandbox_exe.as_ref(), use_legacy_landlock: turn.features.use_legacy_landlock(), windows_sandbox_level: turn.windows_sandbox_level, diff --git a/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs b/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs index 8cfb551a1..6ceef4c2c 100644 --- a/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs +++ b/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs @@ -23,7 +23,6 @@ use codex_execpolicy::Policy; use codex_execpolicy::RuleMatch; use codex_features::Feature; use codex_protocol::config_types::WindowsSandboxLevel; -use codex_protocol::models::MacOsSeatbeltProfileExtensions; use codex_protocol::models::PermissionProfile; use codex_protocol::permissions::FileSystemSandboxPolicy; use codex_protocol::permissions::NetworkSandboxPolicy; @@ -154,12 +153,6 @@ pub(super) async fn try_run_zsh_fork( windows_sandbox_level, arg0, sandbox_policy_cwd: ctx.turn.cwd.to_path_buf(), - macos_seatbelt_profile_extensions: ctx - .turn - .config - .permissions - .macos_seatbelt_profile_extensions - .clone(), codex_linux_sandbox_exe: ctx.turn.codex_linux_sandbox_exe.clone(), use_legacy_landlock: ctx.turn.features.use_legacy_landlock(), }; @@ -258,12 +251,6 @@ pub(crate) async fn prepare_unified_exec_zsh_fork( windows_sandbox_level: exec_request.windows_sandbox_level, arg0: exec_request.arg0.clone(), sandbox_policy_cwd: ctx.turn.cwd.to_path_buf(), - macos_seatbelt_profile_extensions: ctx - .turn - .config - .permissions - .macos_seatbelt_profile_extensions - .clone(), codex_linux_sandbox_exe: ctx.turn.codex_linux_sandbox_exe.clone(), use_legacy_landlock: ctx.turn.features.use_legacy_landlock(), }; @@ -359,7 +346,6 @@ impl CoreShellActionProvider { file_system_sandbox_policy: &FileSystemSandboxPolicy, network_sandbox_policy: NetworkSandboxPolicy, additional_permissions: Option<&PermissionProfile>, - macos_seatbelt_profile_extensions: Option<&MacOsSeatbeltProfileExtensions>, ) -> EscalationExecution { match sandbox_permissions { SandboxPermissions::UseDefault => EscalationExecution::TurnDefault, @@ -373,8 +359,6 @@ impl CoreShellActionProvider { sandbox_policy: sandbox_policy.clone(), file_system_sandbox_policy: file_system_sandbox_policy.clone(), network_sandbox_policy, - macos_seatbelt_profile_extensions: macos_seatbelt_profile_extensions - .cloned(), }, )) }) @@ -560,11 +544,6 @@ impl EscalationPolicy for CoreShellActionProvider { &self.file_system_sandbox_policy, self.network_sandbox_policy, self.prompt_permissions.as_ref(), - self.turn - .config - .permissions - .macos_seatbelt_profile_extensions - .as_ref(), ), }; self.process_decision( @@ -687,8 +666,6 @@ struct CoreShellCommandExecutor { windows_sandbox_level: WindowsSandboxLevel, arg0: Option, sandbox_policy_cwd: PathBuf, - #[cfg_attr(not(target_os = "macos"), allow(dead_code))] - macos_seatbelt_profile_extensions: Option, codex_linux_sandbox_exe: Option, use_legacy_landlock: bool, } @@ -701,8 +678,6 @@ struct PrepareSandboxedExecParams<'a> { file_system_sandbox_policy: &'a FileSystemSandboxPolicy, network_sandbox_policy: NetworkSandboxPolicy, additional_permissions: Option, - #[cfg(target_os = "macos")] - macos_seatbelt_profile_extensions: Option<&'a MacOsSeatbeltProfileExtensions>, } #[async_trait::async_trait] @@ -787,17 +762,12 @@ impl ShellCommandExecutor for CoreShellCommandExecutor { file_system_sandbox_policy: &self.file_system_sandbox_policy, network_sandbox_policy: self.network_sandbox_policy, additional_permissions: None, - #[cfg(target_os = "macos")] - macos_seatbelt_profile_extensions: self - .macos_seatbelt_profile_extensions - .as_ref(), })? } EscalationExecution::Permissions(EscalationPermissions::PermissionProfile( permission_profile, )) => { // Merge additive permissions into the existing turn/request sandbox policy. - // On macOS, additional profile extensions are unioned with the turn defaults. self.prepare_sandboxed_exec(PrepareSandboxedExecParams { command, workdir, @@ -806,10 +776,6 @@ impl ShellCommandExecutor for CoreShellCommandExecutor { file_system_sandbox_policy: &self.file_system_sandbox_policy, network_sandbox_policy: self.network_sandbox_policy, additional_permissions: Some(permission_profile), - #[cfg(target_os = "macos")] - macos_seatbelt_profile_extensions: self - .macos_seatbelt_profile_extensions - .as_ref(), })? } EscalationExecution::Permissions(EscalationPermissions::Permissions(permissions)) => { @@ -822,10 +788,6 @@ impl ShellCommandExecutor for CoreShellCommandExecutor { file_system_sandbox_policy: &permissions.file_system_sandbox_policy, network_sandbox_policy: permissions.network_sandbox_policy, additional_permissions: None, - #[cfg(target_os = "macos")] - macos_seatbelt_profile_extensions: permissions - .macos_seatbelt_profile_extensions - .as_ref(), })? } }; @@ -848,8 +810,6 @@ impl CoreShellCommandExecutor { file_system_sandbox_policy, network_sandbox_policy, additional_permissions, - #[cfg(target_os = "macos")] - macos_seatbelt_profile_extensions, } = params; let (program, args) = command .split_first() @@ -882,8 +842,6 @@ impl CoreShellCommandExecutor { enforce_managed_network: self.network.is_some(), network: self.network.as_ref(), sandbox_policy_cwd: &self.sandbox_policy_cwd, - #[cfg(target_os = "macos")] - macos_seatbelt_profile_extensions, codex_linux_sandbox_exe: self.codex_linux_sandbox_exe.as_ref(), use_legacy_landlock: self.use_legacy_landlock, windows_sandbox_level: self.windows_sandbox_level, diff --git a/codex-rs/core/src/tools/runtimes/shell/unix_escalation_tests.rs b/codex-rs/core/src/tools/runtimes/shell/unix_escalation_tests.rs index 46ba0cf90..41cfce515 100644 --- a/codex-rs/core/src/tools/runtimes/shell/unix_escalation_tests.rs +++ b/codex-rs/core/src/tools/runtimes/shell/unix_escalation_tests.rs @@ -1,6 +1,4 @@ use super::CoreShellActionProvider; -#[cfg(target_os = "macos")] -use super::CoreShellCommandExecutor; use super::InterceptedExecPolicyContext; use super::ParsedShellCommand; use super::commands_for_intercepted_exec_policy; @@ -8,12 +6,6 @@ use super::evaluate_intercepted_exec_policy; use super::extract_shell_script; use super::join_program_and_argv; use super::map_exec_result; -#[cfg(target_os = "macos")] -use crate::config::Constrained; -#[cfg(target_os = "macos")] -use crate::config::Permissions; -#[cfg(target_os = "macos")] -use crate::config::types::ShellEnvironmentPolicy; use crate::protocol::AskForApproval; use crate::protocol::GranularApprovalConfig; use crate::protocol::ReadOnlyAccess; @@ -23,11 +15,7 @@ use codex_execpolicy::Decision; use codex_execpolicy::Evaluation; use codex_execpolicy::PolicyParser; use codex_execpolicy::RuleMatch; -#[cfg(target_os = "macos")] -use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::FileSystemPermissions; -use codex_protocol::models::MacOsPreferencesPermission; -use codex_protocol::models::MacOsSeatbeltProfileExtensions; use codex_protocol::models::PermissionProfile; use codex_protocol::permissions::FileSystemAccessMode; use codex_protocol::permissions::FileSystemPath; @@ -36,18 +24,12 @@ use codex_protocol::permissions::FileSystemSandboxPolicy; use codex_protocol::permissions::FileSystemSpecialPath; use codex_protocol::permissions::NetworkSandboxPolicy; use codex_sandboxing::SandboxType; -#[cfg(target_os = "macos")] -use codex_sandboxing::seatbelt::MACOS_PATH_TO_SEATBELT_EXECUTABLE; use codex_shell_escalation::EscalationExecution; use codex_shell_escalation::EscalationPermissions; use codex_shell_escalation::ExecResult; use codex_shell_escalation::Permissions as EscalatedPermissions; -#[cfg(target_os = "macos")] -use codex_shell_escalation::ShellCommandExecutor; use codex_utils_absolute_path::AbsolutePathBuf; use pretty_assertions::assert_eq; -#[cfg(target_os = "macos")] -use std::collections::HashMap; use std::path::PathBuf; use std::time::Duration; @@ -76,11 +58,6 @@ fn read_only_file_system_sandbox_policy() -> FileSystemSandboxPolicy { }]) } -#[cfg(target_os = "macos")] -fn unrestricted_file_system_sandbox_policy() -> FileSystemSandboxPolicy { - FileSystemSandboxPolicy::unrestricted() -} - #[test] fn execve_prompt_rejection_keeps_prefix_rules_on_rules_flag() { assert_eq!( @@ -294,10 +271,6 @@ fn shell_request_escalation_execution_is_explicit() { }, ]); let network_sandbox_policy = NetworkSandboxPolicy::Restricted; - let macos_seatbelt_profile_extensions = MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadWrite, - ..Default::default() - }; assert_eq!( CoreShellActionProvider::shell_request_escalation_execution( @@ -306,7 +279,6 @@ fn shell_request_escalation_execution_is_explicit() { &file_system_sandbox_policy, network_sandbox_policy, None, - Some(&macos_seatbelt_profile_extensions), ), EscalationExecution::TurnDefault, ); @@ -317,7 +289,6 @@ fn shell_request_escalation_execution_is_explicit() { &file_system_sandbox_policy, network_sandbox_policy, None, - Some(&macos_seatbelt_profile_extensions), ), EscalationExecution::Unsandboxed, ); @@ -328,14 +299,12 @@ fn shell_request_escalation_execution_is_explicit() { &file_system_sandbox_policy, network_sandbox_policy, Some(&requested_permissions), - Some(&macos_seatbelt_profile_extensions), ), EscalationExecution::Permissions(EscalationPermissions::Permissions( EscalatedPermissions { sandbox_policy, file_system_sandbox_policy, network_sandbox_policy, - macos_seatbelt_profile_extensions: Some(macos_seatbelt_profile_extensions), }, )), ); @@ -558,191 +527,3 @@ host_executable(name = "git", paths = ["{allowed_git_literal}"]) evaluation.decision )); } - -#[cfg(target_os = "macos")] -#[tokio::test] -async fn prepare_escalated_exec_turn_default_preserves_macos_seatbelt_extensions() { - let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir()).unwrap(); - let executor = CoreShellCommandExecutor { - command: vec!["echo".to_string(), "ok".to_string()], - cwd: cwd.to_path_buf(), - env: HashMap::new(), - network: None, - sandbox: SandboxType::None, - sandbox_policy: SandboxPolicy::new_read_only_policy(), - file_system_sandbox_policy: read_only_file_system_sandbox_policy(), - network_sandbox_policy: NetworkSandboxPolicy::Restricted, - windows_sandbox_level: WindowsSandboxLevel::Disabled, - arg0: None, - sandbox_policy_cwd: cwd.to_path_buf(), - macos_seatbelt_profile_extensions: Some(MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadWrite, - ..Default::default() - }), - codex_linux_sandbox_exe: None, - use_legacy_landlock: false, - }; - - let prepared = executor - .prepare_escalated_exec( - &AbsolutePathBuf::from_absolute_path("/bin/echo").unwrap(), - &["echo".to_string(), "ok".to_string()], - &cwd, - HashMap::new(), - EscalationExecution::TurnDefault, - ) - .await - .unwrap(); - - assert_eq!( - prepared.command.first().map(String::as_str), - Some(MACOS_PATH_TO_SEATBELT_EXECUTABLE) - ); - assert_eq!(prepared.command.get(1).map(String::as_str), Some("-p")); - assert!( - prepared - .command - .get(2) - .is_some_and(|policy| policy.contains("(allow user-preference-write)")), - "expected seatbelt policy to include macOS extension profile: {:?}", - prepared.command - ); -} - -#[cfg(target_os = "macos")] -#[tokio::test] -async fn prepare_escalated_exec_permissions_preserve_macos_seatbelt_extensions() { - let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir()).unwrap(); - let executor = CoreShellCommandExecutor { - command: vec!["echo".to_string(), "ok".to_string()], - cwd: cwd.to_path_buf(), - env: HashMap::new(), - network: None, - sandbox: SandboxType::None, - sandbox_policy: SandboxPolicy::DangerFullAccess, - file_system_sandbox_policy: unrestricted_file_system_sandbox_policy(), - network_sandbox_policy: NetworkSandboxPolicy::Enabled, - windows_sandbox_level: WindowsSandboxLevel::Disabled, - arg0: None, - sandbox_policy_cwd: cwd.to_path_buf(), - macos_seatbelt_profile_extensions: None, - codex_linux_sandbox_exe: None, - use_legacy_landlock: false, - }; - - let permissions = Permissions { - approval_policy: Constrained::allow_any(AskForApproval::Never), - sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()), - file_system_sandbox_policy: read_only_file_system_sandbox_policy(), - network_sandbox_policy: codex_protocol::permissions::NetworkSandboxPolicy::Restricted, - network: None, - allow_login_shell: true, - shell_environment_policy: ShellEnvironmentPolicy::default(), - windows_sandbox_mode: None, - windows_sandbox_private_desktop: false, - macos_seatbelt_profile_extensions: Some(MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadWrite, - ..Default::default() - }), - }; - - let prepared = executor - .prepare_escalated_exec( - &AbsolutePathBuf::from_absolute_path("/bin/echo").unwrap(), - &["echo".to_string(), "ok".to_string()], - &cwd, - HashMap::new(), - EscalationExecution::Permissions(EscalationPermissions::Permissions( - EscalatedPermissions { - sandbox_policy: permissions.sandbox_policy.get().clone(), - file_system_sandbox_policy: permissions.file_system_sandbox_policy.clone(), - network_sandbox_policy: permissions.network_sandbox_policy, - macos_seatbelt_profile_extensions: permissions - .macos_seatbelt_profile_extensions - .clone(), - }, - )), - ) - .await - .unwrap(); - - assert_eq!( - prepared.command.first().map(String::as_str), - Some(MACOS_PATH_TO_SEATBELT_EXECUTABLE) - ); - assert_eq!(prepared.command.get(1).map(String::as_str), Some("-p")); - assert!( - prepared - .command - .get(2) - .is_some_and(|policy| policy.contains("(allow user-preference-write)")), - "expected seatbelt policy to include macOS extension profile: {:?}", - prepared.command - ); -} - -#[cfg(target_os = "macos")] -#[tokio::test] -async fn prepare_escalated_exec_permission_profile_unions_turn_and_requested_macos_extensions() { - let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir()).unwrap(); - let sandbox_policy = SandboxPolicy::new_read_only_policy(); - let executor = CoreShellCommandExecutor { - command: vec!["echo".to_string(), "ok".to_string()], - cwd: cwd.to_path_buf(), - env: HashMap::new(), - network: None, - sandbox: SandboxType::None, - sandbox_policy: sandbox_policy.clone(), - file_system_sandbox_policy: read_only_file_system_sandbox_policy(), - network_sandbox_policy: NetworkSandboxPolicy::from(&sandbox_policy), - windows_sandbox_level: WindowsSandboxLevel::Disabled, - arg0: None, - sandbox_policy_cwd: cwd.to_path_buf(), - macos_seatbelt_profile_extensions: Some(MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadOnly, - ..Default::default() - }), - codex_linux_sandbox_exe: None, - use_legacy_landlock: false, - }; - - let prepared = executor - .prepare_escalated_exec( - &AbsolutePathBuf::from_absolute_path("/bin/echo").unwrap(), - &["echo".to_string(), "ok".to_string()], - &cwd, - HashMap::new(), - EscalationExecution::Permissions(EscalationPermissions::PermissionProfile( - PermissionProfile { - macos: Some(MacOsSeatbeltProfileExtensions { - macos_calendar: true, - macos_reminders: false, - ..Default::default() - }), - ..Default::default() - }, - )), - ) - .await - .unwrap(); - - let policy = prepared - .command - .get(2) - .expect("seatbelt policy should be present"); - assert_eq!( - prepared.command.first().map(String::as_str), - Some(MACOS_PATH_TO_SEATBELT_EXECUTABLE) - ); - assert_eq!(prepared.command.get(1).map(String::as_str), Some("-p")); - assert!( - policy.contains("(allow user-preference-read)"), - "expected turn macOS seatbelt extensions to be preserved: {:?}", - prepared.command - ); - assert!( - policy.contains("(allow mach-lookup (global-name \"com.apple.CalendarAgent\"))"), - "expected requested macOS seatbelt extensions to be included: {:?}", - prepared.command - ); -} diff --git a/codex-rs/core/src/tools/sandboxing.rs b/codex-rs/core/src/tools/sandboxing.rs index 6cad5a329..5b7e95019 100644 --- a/codex-rs/core/src/tools/sandboxing.rs +++ b/codex-rs/core/src/tools/sandboxing.rs @@ -348,8 +348,6 @@ impl<'a> SandboxAttempt<'a> { enforce_managed_network: self.enforce_managed_network, network, sandbox_policy_cwd: self.sandbox_cwd, - #[cfg(target_os = "macos")] - macos_seatbelt_profile_extensions: None, codex_linux_sandbox_exe: self.codex_linux_sandbox_exe, use_legacy_landlock: self.use_legacy_landlock, windows_sandbox_level: self.windows_sandbox_level, diff --git a/codex-rs/core/tests/suite/request_permissions.rs b/codex-rs/core/tests/suite/request_permissions.rs index 7c16599bf..2cfd1cf6f 100644 --- a/codex-rs/core/tests/suite/request_permissions.rs +++ b/codex-rs/core/tests/suite/request_permissions.rs @@ -4,6 +4,7 @@ use anyhow::Result; use codex_core::config::Constrained; use codex_core::sandboxing::SandboxPermissions; use codex_features::Feature; +use codex_protocol::config_types::ApprovalsReviewer; use codex_protocol::models::FileSystemPermissions; use codex_protocol::models::PermissionProfile; use codex_protocol::protocol::AskForApproval; @@ -193,7 +194,7 @@ async fn submit_turn( final_output_json_schema: None, cwd: test.cwd.path().to_path_buf(), approval_policy, - approvals_reviewer: None, + approvals_reviewer: Some(ApprovalsReviewer::User), sandbox_policy, model: session_model, effort: None, @@ -1579,7 +1580,6 @@ async fn partial_request_permissions_grants_do_not_preapprove_new_permissions() .clone() .unwrap_or_else(|| panic!("expected merged additional permissions")); assert_eq!(approval_permissions.network, None); - assert_eq!(approval_permissions.macos, None); let approval_file_system = approval_permissions .file_system diff --git a/codex-rs/protocol/src/approvals.rs b/codex-rs/protocol/src/approvals.rs index 95ccdd624..cd2b3e695 100644 --- a/codex-rs/protocol/src/approvals.rs +++ b/codex-rs/protocol/src/approvals.rs @@ -1,5 +1,4 @@ use crate::mcp::RequestId; -use crate::models::MacOsSeatbeltProfileExtensions; use crate::models::PermissionProfile; use crate::parse_command::ParsedCommand; use crate::permissions::FileSystemSandboxPolicy; @@ -20,7 +19,6 @@ pub struct Permissions { pub sandbox_policy: SandboxPolicy, pub file_system_sandbox_policy: FileSystemSandboxPolicy, pub network_sandbox_policy: NetworkSandboxPolicy, - pub macos_seatbelt_profile_extensions: Option, } #[allow(clippy::large_enum_variant)] diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index 6fa8162bf..471790717 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -88,138 +88,15 @@ impl NetworkPermissions { } } -#[derive( - Debug, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Default, - Hash, - Serialize, - Deserialize, - JsonSchema, - TS, -)] -#[serde(rename_all = "snake_case")] -pub enum MacOsPreferencesPermission { - None, - // IMPORTANT: ReadOnly needs to be the default because it's the - // security-sensitive default and keeps cf prefs working. - #[default] - ReadOnly, - ReadWrite, -} - -#[derive( - Debug, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Default, - Hash, - Serialize, - Deserialize, - JsonSchema, - TS, -)] -#[serde(rename_all = "snake_case")] -pub enum MacOsContactsPermission { - #[default] - None, - ReadOnly, - ReadWrite, -} - -#[derive(Debug, Clone, PartialEq, Eq, Default, Hash, Serialize, Deserialize, JsonSchema, TS)] -#[serde(rename_all = "snake_case", try_from = "MacOsAutomationPermissionDe")] -pub enum MacOsAutomationPermission { - #[default] - None, - All, - BundleIds(Vec), -} - -#[derive(Debug, Deserialize)] -#[serde(untagged)] -enum MacOsAutomationPermissionDe { - Mode(String), - BundleIds(Vec), - BundleIdsObject { bundle_ids: Vec }, -} - -impl TryFrom for MacOsAutomationPermission { - type Error = String; - - /// Accepts one of: - /// - `"none"` or `"all"` - /// - a plain list of bundle IDs, e.g. `["com.apple.Notes"]` - /// - an object with bundle IDs, e.g. `{"bundle_ids": ["com.apple.Notes"]}` - fn try_from(value: MacOsAutomationPermissionDe) -> Result { - let permission = match value { - MacOsAutomationPermissionDe::Mode(value) => { - let normalized = value.trim().to_ascii_lowercase(); - if normalized == "all" { - MacOsAutomationPermission::All - } else if normalized == "none" { - MacOsAutomationPermission::None - } else { - return Err(format!( - "invalid macOS automation permission: {value}; expected none, all, or bundle ids" - )); - } - } - MacOsAutomationPermissionDe::BundleIds(bundle_ids) - | MacOsAutomationPermissionDe::BundleIdsObject { bundle_ids } => { - let bundle_ids = bundle_ids - .into_iter() - .map(|bundle_id| bundle_id.trim().to_string()) - .filter(|bundle_id| !bundle_id.is_empty()) - .collect::>(); - if bundle_ids.is_empty() { - MacOsAutomationPermission::None - } else { - MacOsAutomationPermission::BundleIds(bundle_ids) - } - } - }; - - Ok(permission) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Default, Hash, Serialize, Deserialize, JsonSchema, TS)] -#[serde(default)] -pub struct MacOsSeatbeltProfileExtensions { - #[serde(alias = "preferences")] - pub macos_preferences: MacOsPreferencesPermission, - #[serde(alias = "automations")] - pub macos_automation: MacOsAutomationPermission, - #[serde(alias = "launch_services")] - pub macos_launch_services: bool, - #[serde(alias = "accessibility")] - pub macos_accessibility: bool, - #[serde(alias = "calendar")] - pub macos_calendar: bool, - #[serde(alias = "reminders")] - pub macos_reminders: bool, - #[serde(alias = "contacts")] - pub macos_contacts: MacOsContactsPermission, -} - #[derive(Debug, Clone, Default, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, TS)] pub struct PermissionProfile { pub network: Option, pub file_system: Option, - pub macos: Option, } impl PermissionProfile { pub fn is_empty(&self) -> bool { - self.network.is_none() && self.file_system.is_none() && self.macos.is_none() + self.network.is_none() && self.file_system.is_none() } } @@ -726,7 +603,7 @@ fn granular_prompt_intro_text() -> &'static str { } fn request_permissions_tool_prompt_section() -> &'static str { - "# request_permissions Tool\n\nThe built-in `request_permissions` tool is available in this session. Invoke it when you need to request additional `network`, `file_system`, or `macos` permissions before later shell-like commands need them. Request only the specific permissions required for the task." + "# request_permissions Tool\n\nThe built-in `request_permissions` tool is available in this session. Invoke it when you need to request additional `network` or `file_system` permissions before later shell-like commands need them. Request only the specific permissions required for the task." } fn granular_instructions( @@ -1647,170 +1524,10 @@ mod tests { let permission_profile = PermissionProfile { network: Some(NetworkPermissions { enabled: None }), file_system: None, - macos: None, }; assert_eq!(permission_profile.is_empty(), false); } - #[test] - fn macos_preferences_permission_deserializes_read_write() { - let permission = serde_json::from_str::("\"read_write\"") - .expect("deserialize macos preferences permission"); - assert_eq!(permission, MacOsPreferencesPermission::ReadWrite); - } - - #[test] - fn macos_preferences_permission_order_matches_permissiveness() { - assert!(MacOsPreferencesPermission::None < MacOsPreferencesPermission::ReadOnly); - assert!(MacOsPreferencesPermission::ReadOnly < MacOsPreferencesPermission::ReadWrite); - } - - #[test] - fn macos_contacts_permission_order_matches_permissiveness() { - assert!(MacOsContactsPermission::None < MacOsContactsPermission::ReadOnly); - assert!(MacOsContactsPermission::ReadOnly < MacOsContactsPermission::ReadWrite); - } - - #[test] - fn permission_profile_deserializes_macos_seatbelt_profile_extensions() { - let permission_profile = serde_json::from_value::(serde_json::json!({ - "network": null, - "file_system": null, - "macos": { - "macos_preferences": "read_write", - "macos_automation": ["com.apple.Notes"], - "macos_launch_services": true, - "macos_accessibility": true, - "macos_calendar": true - } - })) - .expect("deserialize permission profile"); - - assert_eq!( - permission_profile, - PermissionProfile { - network: None, - file_system: None, - macos: Some(MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadWrite, - macos_automation: MacOsAutomationPermission::BundleIds(vec![ - "com.apple.Notes".to_string(), - ]), - macos_launch_services: true, - macos_accessibility: true, - macos_calendar: true, - macos_reminders: false, - macos_contacts: MacOsContactsPermission::None, - }), - } - ); - } - - #[test] - fn permission_profile_deserializes_macos_reminders_permission() { - let permission_profile = serde_json::from_value::(serde_json::json!({ - "macos": { - "macos_reminders": true - } - })) - .expect("deserialize reminders permission profile"); - - assert_eq!( - permission_profile, - PermissionProfile { - network: None, - file_system: None, - macos: Some(MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadOnly, - macos_automation: MacOsAutomationPermission::None, - macos_launch_services: false, - macos_accessibility: false, - macos_calendar: false, - macos_reminders: true, - macos_contacts: MacOsContactsPermission::None, - }), - } - ); - } - - #[test] - fn macos_seatbelt_profile_extensions_deserializes_missing_fields_to_defaults() { - let permissions = - serde_json::from_value::(serde_json::json!({ - "macos_automation": ["com.apple.Notes"] - })) - .expect("deserialize macos permissions"); - - assert_eq!( - permissions, - MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadOnly, - macos_automation: MacOsAutomationPermission::BundleIds(vec![ - "com.apple.Notes".to_string(), - ]), - macos_launch_services: false, - macos_accessibility: false, - macos_calendar: false, - macos_reminders: false, - macos_contacts: MacOsContactsPermission::None, - } - ); - } - - #[test] - fn macos_seatbelt_profile_extensions_deserializes_tool_schema_aliases() { - let permissions = - serde_json::from_value::(serde_json::json!({ - "preferences": "read_write", - "automations": ["com.apple.Notes"], - "launch_services": true, - "accessibility": true, - "calendar": true, - "reminders": true, - "contacts": "read_only" - })) - .expect("deserialize macos permissions"); - - assert_eq!( - permissions, - MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadWrite, - macos_automation: MacOsAutomationPermission::BundleIds(vec![ - "com.apple.Notes".to_string(), - ]), - macos_launch_services: true, - macos_accessibility: true, - macos_calendar: true, - macos_reminders: true, - macos_contacts: MacOsContactsPermission::ReadOnly, - } - ); - } - - #[test] - fn macos_automation_permission_deserializes_all_and_none() { - let all = serde_json::from_str::("\"all\"") - .expect("deserialize all automation permission"); - let none = serde_json::from_str::("\"none\"") - .expect("deserialize none automation permission"); - - assert_eq!(all, MacOsAutomationPermission::All); - assert_eq!(none, MacOsAutomationPermission::None); - } - - #[test] - fn macos_automation_permission_deserializes_bundle_ids_object() { - let permission = serde_json::from_value::(serde_json::json!({ - "bundle_ids": ["com.apple.Notes"] - })) - .expect("deserialize bundle_ids object automation permission"); - - assert_eq!( - permission, - MacOsAutomationPermission::BundleIds(vec!["com.apple.Notes".to_string(),]) - ); - } - #[test] fn convert_mcp_content_to_items_builds_data_urls_when_missing_prefix() { let contents = vec![serde_json::json!({ diff --git a/codex-rs/protocol/src/request_permissions.rs b/codex-rs/protocol/src/request_permissions.rs index db5396c5e..400fde4e2 100644 --- a/codex-rs/protocol/src/request_permissions.rs +++ b/codex-rs/protocol/src/request_permissions.rs @@ -32,7 +32,6 @@ impl From for PermissionProfile { Self { network: value.network, file_system: value.file_system, - macos: None, } } } diff --git a/codex-rs/sandboxing/Cargo.toml b/codex-rs/sandboxing/Cargo.toml index cd7558ec9..274e5bce2 100644 --- a/codex-rs/sandboxing/Cargo.toml +++ b/codex-rs/sandboxing/Cargo.toml @@ -15,7 +15,6 @@ workspace = true codex-network-proxy = { workspace = true } codex-protocol = { workspace = true } codex-utils-absolute-path = { workspace = true } -dirs = { workspace = true } dunce = { workspace = true } libc = { workspace = true } serde_json = { workspace = true } diff --git a/codex-rs/sandboxing/src/lib.rs b/codex-rs/sandboxing/src/lib.rs index eb6b4e33b..75bddad5e 100644 --- a/codex-rs/sandboxing/src/lib.rs +++ b/codex-rs/sandboxing/src/lib.rs @@ -1,13 +1,10 @@ #[cfg(target_os = "linux")] mod bwrap; pub mod landlock; -pub mod macos_permissions; mod manager; pub mod policy_transforms; #[cfg(target_os = "macos")] pub mod seatbelt; -#[cfg(target_os = "macos")] -mod seatbelt_permissions; #[cfg(target_os = "linux")] pub use bwrap::find_system_bwrap_in_path; diff --git a/codex-rs/sandboxing/src/macos_permissions.rs b/codex-rs/sandboxing/src/macos_permissions.rs deleted file mode 100644 index 22314937e..000000000 --- a/codex-rs/sandboxing/src/macos_permissions.rs +++ /dev/null @@ -1,154 +0,0 @@ -use std::collections::BTreeSet; - -use codex_protocol::models::MacOsAutomationPermission; -use codex_protocol::models::MacOsContactsPermission; -use codex_protocol::models::MacOsPreferencesPermission; -use codex_protocol::models::MacOsSeatbeltProfileExtensions; - -/// Merges macOS seatbelt profile extensions by taking the permissive union of -/// each permission field. -pub fn merge_macos_seatbelt_profile_extensions( - base: Option<&MacOsSeatbeltProfileExtensions>, - permissions: Option<&MacOsSeatbeltProfileExtensions>, -) -> Option { - let Some(permissions) = permissions else { - return base.cloned(); - }; - - match base { - Some(base) => Some(MacOsSeatbeltProfileExtensions { - macos_preferences: union_macos_preferences_permission( - &base.macos_preferences, - &permissions.macos_preferences, - ), - macos_automation: union_macos_automation_permission( - &base.macos_automation, - &permissions.macos_automation, - ), - macos_launch_services: base.macos_launch_services || permissions.macos_launch_services, - macos_accessibility: base.macos_accessibility || permissions.macos_accessibility, - macos_calendar: base.macos_calendar || permissions.macos_calendar, - macos_reminders: base.macos_reminders || permissions.macos_reminders, - macos_contacts: union_macos_contacts_permission( - &base.macos_contacts, - &permissions.macos_contacts, - ), - }), - None => Some(permissions.clone()), - } -} - -pub fn intersect_macos_seatbelt_profile_extensions( - requested: Option, - granted: Option, -) -> Option { - match (requested, granted) { - (Some(requested), Some(granted)) => { - let macos_automation = intersect_macos_automation_permission( - &requested.macos_automation, - &granted.macos_automation, - ); - - Some(MacOsSeatbeltProfileExtensions { - macos_preferences: requested.macos_preferences.min(granted.macos_preferences), - macos_automation, - macos_launch_services: requested.macos_launch_services - && granted.macos_launch_services, - macos_accessibility: requested.macos_accessibility && granted.macos_accessibility, - macos_calendar: requested.macos_calendar && granted.macos_calendar, - macos_reminders: requested.macos_reminders && granted.macos_reminders, - macos_contacts: requested.macos_contacts.min(granted.macos_contacts), - }) - } - _ => None, - } -} - -/// Unions two preferences permissions by keeping the more permissive one. -/// -/// The larger rank wins: `None < ReadOnly < ReadWrite`. When both sides have -/// the same rank, this keeps `base`. -fn union_macos_preferences_permission( - base: &MacOsPreferencesPermission, - requested: &MacOsPreferencesPermission, -) -> MacOsPreferencesPermission { - if base < requested { - requested.clone() - } else { - base.clone() - } -} - -fn union_macos_contacts_permission( - base: &MacOsContactsPermission, - requested: &MacOsContactsPermission, -) -> MacOsContactsPermission { - if base < requested { - requested.clone() - } else { - base.clone() - } -} - -/// Unions two automation permissions by keeping the more permissive result. -/// -/// `All` wins over everything, `None` yields to the other side, and two bundle -/// ID allowlists are unioned together. -fn union_macos_automation_permission( - base: &MacOsAutomationPermission, - requested: &MacOsAutomationPermission, -) -> MacOsAutomationPermission { - match (base, requested) { - (MacOsAutomationPermission::All, _) | (_, MacOsAutomationPermission::All) => { - MacOsAutomationPermission::All - } - (MacOsAutomationPermission::None, _) => requested.clone(), - (_, MacOsAutomationPermission::None) => base.clone(), - ( - MacOsAutomationPermission::BundleIds(base_bundle_ids), - MacOsAutomationPermission::BundleIds(requested_bundle_ids), - ) => MacOsAutomationPermission::BundleIds( - base_bundle_ids - .iter() - .chain(requested_bundle_ids.iter()) - .cloned() - .collect::>() - .into_iter() - .collect(), - ), - } -} - -fn intersect_macos_automation_permission( - requested: &MacOsAutomationPermission, - granted: &MacOsAutomationPermission, -) -> MacOsAutomationPermission { - match (requested, granted) { - (_, MacOsAutomationPermission::None) | (MacOsAutomationPermission::None, _) => { - MacOsAutomationPermission::None - } - (MacOsAutomationPermission::All, granted) => granted.clone(), - (MacOsAutomationPermission::BundleIds(requested), MacOsAutomationPermission::All) => { - MacOsAutomationPermission::BundleIds(requested.clone()) - } - ( - MacOsAutomationPermission::BundleIds(requested), - MacOsAutomationPermission::BundleIds(granted), - ) => { - let bundle_ids = requested - .iter() - .filter(|bundle_id| granted.contains(bundle_id)) - .cloned() - .collect::>(); - if bundle_ids.is_empty() { - MacOsAutomationPermission::None - } else { - MacOsAutomationPermission::BundleIds(bundle_ids) - } - } - } -} - -#[cfg(all(test, target_os = "macos"))] -#[path = "macos_permissions_tests.rs"] -mod tests; diff --git a/codex-rs/sandboxing/src/macos_permissions_tests.rs b/codex-rs/sandboxing/src/macos_permissions_tests.rs deleted file mode 100644 index 97a2a2c75..000000000 --- a/codex-rs/sandboxing/src/macos_permissions_tests.rs +++ /dev/null @@ -1,121 +0,0 @@ -use super::intersect_macos_automation_permission; -use super::intersect_macos_seatbelt_profile_extensions; -use super::merge_macos_seatbelt_profile_extensions; -use super::union_macos_automation_permission; -use super::union_macos_contacts_permission; -use super::union_macos_preferences_permission; -use codex_protocol::models::MacOsAutomationPermission; -use codex_protocol::models::MacOsContactsPermission; -use codex_protocol::models::MacOsPreferencesPermission; -use codex_protocol::models::MacOsSeatbeltProfileExtensions; -use pretty_assertions::assert_eq; - -#[test] -fn merge_extensions_widens_permissions() { - let base = MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadOnly, - macos_automation: MacOsAutomationPermission::BundleIds(vec![ - "com.apple.Calendar".to_string(), - ]), - macos_launch_services: false, - macos_accessibility: false, - macos_calendar: false, - macos_reminders: false, - macos_contacts: MacOsContactsPermission::ReadOnly, - }; - let requested = MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadWrite, - macos_automation: MacOsAutomationPermission::BundleIds(vec![ - "com.apple.Notes".to_string(), - "com.apple.Calendar".to_string(), - ]), - macos_launch_services: true, - macos_accessibility: true, - macos_calendar: true, - macos_reminders: true, - macos_contacts: MacOsContactsPermission::ReadWrite, - }; - - let merged = - merge_macos_seatbelt_profile_extensions(Some(&base), Some(&requested)).expect("merge"); - - assert_eq!( - merged, - MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadWrite, - macos_automation: MacOsAutomationPermission::BundleIds(vec![ - "com.apple.Calendar".to_string(), - "com.apple.Notes".to_string(), - ]), - macos_launch_services: true, - macos_accessibility: true, - macos_calendar: true, - macos_reminders: true, - macos_contacts: MacOsContactsPermission::ReadWrite, - } - ); -} - -#[test] -fn union_macos_preferences_permission_does_not_downgrade() { - let base = MacOsPreferencesPermission::ReadWrite; - let requested = MacOsPreferencesPermission::ReadOnly; - - let merged = union_macos_preferences_permission(&base, &requested); - - assert_eq!(merged, MacOsPreferencesPermission::ReadWrite); -} - -#[test] -fn union_macos_automation_permission_all_is_dominant() { - let base = MacOsAutomationPermission::BundleIds(vec!["com.apple.Notes".to_string()]); - let requested = MacOsAutomationPermission::All; - - let merged = union_macos_automation_permission(&base, &requested); - - assert_eq!(merged, MacOsAutomationPermission::All); -} - -#[test] -fn intersect_macos_automation_permission_keeps_common_bundle_ids() { - let requested = MacOsAutomationPermission::BundleIds(vec![ - "com.apple.Notes".to_string(), - "com.apple.Calendar".to_string(), - ]); - let granted = MacOsAutomationPermission::BundleIds(vec!["com.apple.Notes".to_string()]); - - let intersected = intersect_macos_automation_permission(&requested, &granted); - - assert_eq!( - intersected, - MacOsAutomationPermission::BundleIds(vec!["com.apple.Notes".to_string()]) - ); -} - -#[test] -fn intersect_macos_seatbelt_profile_extensions_preserves_default_grant() { - let requested = MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadWrite, - macos_automation: MacOsAutomationPermission::BundleIds(vec!["com.apple.Notes".to_string()]), - macos_launch_services: false, - macos_accessibility: true, - macos_calendar: true, - macos_reminders: false, - macos_contacts: MacOsContactsPermission::None, - }; - let granted = MacOsSeatbeltProfileExtensions::default(); - - let intersected = intersect_macos_seatbelt_profile_extensions(Some(requested), Some(granted)); - - assert_eq!(intersected, Some(MacOsSeatbeltProfileExtensions::default())); -} - -#[test] -fn union_macos_contacts_permission_does_not_downgrade() { - let base = MacOsContactsPermission::ReadWrite; - let requested = MacOsContactsPermission::ReadOnly; - - let merged = union_macos_contacts_permission(&base, &requested); - - assert_eq!(merged, MacOsContactsPermission::ReadWrite); -} diff --git a/codex-rs/sandboxing/src/manager.rs b/codex-rs/sandboxing/src/manager.rs index 3f121ba41..c89e7cd9b 100644 --- a/codex-rs/sandboxing/src/manager.rs +++ b/codex-rs/sandboxing/src/manager.rs @@ -8,11 +8,9 @@ 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_with_extensions; +use crate::seatbelt::create_seatbelt_command_args_for_policies; use codex_network_proxy::NetworkProxy; use codex_protocol::config_types::WindowsSandboxLevel; -#[cfg(target_os = "macos")] -use codex_protocol::models::MacOsSeatbeltProfileExtensions; use codex_protocol::models::PermissionProfile; use codex_protocol::permissions::FileSystemSandboxPolicy; use codex_protocol::permissions::NetworkSandboxPolicy; @@ -102,8 +100,6 @@ pub struct SandboxTransformRequest<'a> { // to make shared ownership explicit across runtime/sandbox plumbing. pub network: Option<&'a NetworkProxy>, pub sandbox_policy_cwd: &'a Path, - #[cfg(target_os = "macos")] - pub macos_seatbelt_profile_extensions: Option<&'a MacOsSeatbeltProfileExtensions>, pub codex_linux_sandbox_exe: Option<&'a PathBuf>, pub use_legacy_landlock: bool, pub windows_sandbox_level: WindowsSandboxLevel, @@ -181,27 +177,15 @@ impl SandboxManager { enforce_managed_network, network, sandbox_policy_cwd, - #[cfg(target_os = "macos")] - macos_seatbelt_profile_extensions, codex_linux_sandbox_exe, use_legacy_landlock, windows_sandbox_level, windows_sandbox_private_desktop, } = request; - #[cfg(not(target_os = "macos"))] - let macos_seatbelt_profile_extensions = None; let additional_permissions = command.additional_permissions.take(); let EffectiveSandboxPermissions { sandbox_policy: effective_policy, - #[cfg(target_os = "macos")] - macos_seatbelt_profile_extensions: effective_macos_seatbelt_profile_extensions, - #[cfg(not(target_os = "macos"))] - macos_seatbelt_profile_extensions: _, - } = EffectiveSandboxPermissions::new( - policy, - macos_seatbelt_profile_extensions, - additional_permissions.as_ref(), - ); + } = EffectiveSandboxPermissions::new(policy, additional_permissions.as_ref()); let effective_file_system_policy = effective_file_system_sandbox_policy( file_system_policy, additional_permissions.as_ref(), @@ -216,14 +200,13 @@ 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_with_extensions( + let mut args = create_seatbelt_command_args_for_policies( os_argv_to_strings(argv), &effective_file_system_policy, effective_network_policy, sandbox_policy_cwd, enforce_managed_network, network, - effective_macos_seatbelt_profile_extensions.as_ref(), ); let mut full_command = Vec::with_capacity(1 + args.len()); full_command.push(MACOS_PATH_TO_SEATBELT_EXECUTABLE.to_string()); diff --git a/codex-rs/sandboxing/src/manager_tests.rs b/codex-rs/sandboxing/src/manager_tests.rs index 67911bff6..a32c9e586 100644 --- a/codex-rs/sandboxing/src/manager_tests.rs +++ b/codex-rs/sandboxing/src/manager_tests.rs @@ -91,8 +91,6 @@ fn transform_preserves_unrestricted_file_system_policy_for_restricted_network() enforce_managed_network: false, network: None, sandbox_policy_cwd: cwd.as_path(), - #[cfg(target_os = "macos")] - macos_seatbelt_profile_extensions: None, codex_linux_sandbox_exe: None, use_legacy_landlock: false, windows_sandbox_level: WindowsSandboxLevel::Disabled, @@ -134,7 +132,6 @@ fn transform_additional_permissions_enable_network_for_external_sandbox() { read: Some(vec![path]), write: Some(Vec::new()), }), - ..Default::default() }), }, policy: &SandboxPolicy::ExternalSandbox { @@ -146,8 +143,6 @@ fn transform_additional_permissions_enable_network_for_external_sandbox() { enforce_managed_network: false, network: None, sandbox_policy_cwd: cwd.as_path(), - #[cfg(target_os = "macos")] - macos_seatbelt_profile_extensions: None, codex_linux_sandbox_exe: None, use_legacy_landlock: false, windows_sandbox_level: WindowsSandboxLevel::Disabled, @@ -216,8 +211,6 @@ fn transform_additional_permissions_preserves_denied_entries() { enforce_managed_network: false, network: None, sandbox_policy_cwd: cwd.as_path(), - #[cfg(target_os = "macos")] - macos_seatbelt_profile_extensions: None, codex_linux_sandbox_exe: None, use_legacy_landlock: false, windows_sandbox_level: WindowsSandboxLevel::Disabled, diff --git a/codex-rs/sandboxing/src/policy_transforms.rs b/codex-rs/sandboxing/src/policy_transforms.rs index 045c7e3f9..25c79fcc9 100644 --- a/codex-rs/sandboxing/src/policy_transforms.rs +++ b/codex-rs/sandboxing/src/policy_transforms.rs @@ -1,7 +1,4 @@ -use crate::macos_permissions::intersect_macos_seatbelt_profile_extensions; -use crate::macos_permissions::merge_macos_seatbelt_profile_extensions; use codex_protocol::models::FileSystemPermissions; -use codex_protocol::models::MacOsSeatbeltProfileExtensions; use codex_protocol::models::NetworkPermissions; use codex_protocol::models::PermissionProfile; use codex_protocol::permissions::FileSystemAccessMode; @@ -20,28 +17,21 @@ use std::collections::HashSet; #[derive(Debug, Clone, PartialEq, Eq)] pub struct EffectiveSandboxPermissions { pub sandbox_policy: SandboxPolicy, - pub macos_seatbelt_profile_extensions: Option, } impl EffectiveSandboxPermissions { pub fn new( sandbox_policy: &SandboxPolicy, - macos_seatbelt_profile_extensions: Option<&MacOsSeatbeltProfileExtensions>, additional_permissions: Option<&PermissionProfile>, ) -> Self { let Some(additional_permissions) = additional_permissions else { return Self { sandbox_policy: sandbox_policy.clone(), - macos_seatbelt_profile_extensions: macos_seatbelt_profile_extensions.cloned(), }; }; Self { sandbox_policy: effective_sandbox_policy(sandbox_policy, Some(additional_permissions)), - macos_seatbelt_profile_extensions: merge_macos_seatbelt_profile_extensions( - macos_seatbelt_profile_extensions, - additional_permissions.macos.as_ref(), - ), } } } @@ -64,12 +54,9 @@ pub fn normalize_additional_permissions( FileSystemPermissions { read, write } }) .filter(|file_system| !file_system.is_empty()); - let macos = additional_permissions.macos; - Ok(PermissionProfile { network, file_system, - macos, }) } @@ -110,15 +97,10 @@ pub fn merge_permission_profiles( (None, Some(permissions)) => Some(permissions.clone()), (None, None) => None, }; - let macos = merge_macos_seatbelt_profile_extensions( - base.macos.as_ref(), - permissions.macos.as_ref(), - ); Some(PermissionProfile { network, file_system, - macos, }) .filter(|permissions| !permissions.is_empty()) } @@ -134,26 +116,10 @@ pub fn intersect_permission_profiles( .file_system .map(|requested_file_system| { let granted_file_system = granted.file_system.unwrap_or_default(); - let read = requested_file_system - .read - .map(|requested_read| { - let granted_read = granted_file_system.read.unwrap_or_default(); - requested_read - .into_iter() - .filter(|path| granted_read.contains(path)) - .collect() - }) - .filter(|paths: &Vec<_>| !paths.is_empty()); - let write = requested_file_system - .write - .map(|requested_write| { - let granted_write = granted_file_system.write.unwrap_or_default(); - requested_write - .into_iter() - .filter(|path| granted_write.contains(path)) - .collect() - }) - .filter(|paths: &Vec<_>| !paths.is_empty()); + let read = + intersect_permission_paths(requested_file_system.read, granted_file_system.read); + let write = + intersect_permission_paths(requested_file_system.write, granted_file_system.write); FileSystemPermissions { read, write } }) .filter(|file_system| !file_system.is_empty()); @@ -171,15 +137,32 @@ pub fn intersect_permission_profiles( _ => None, }; - let macos = intersect_macos_seatbelt_profile_extensions(requested.macos, granted.macos); - PermissionProfile { network, file_system, - macos, } } +fn intersect_permission_paths( + requested: Option>, + granted: Option>, +) -> Option> { + requested.and_then(|requested_paths| { + if requested_paths.is_empty() { + return granted.map(|_| Vec::new()); + } + + let granted_paths = granted.unwrap_or_default(); + Some( + requested_paths + .into_iter() + .filter(|path| granted_paths.contains(path)) + .collect::>(), + ) + .filter(|paths| !paths.is_empty()) + }) +} + fn normalize_permission_paths( paths: Vec, _permission_kind: &str, diff --git a/codex-rs/sandboxing/src/policy_transforms_tests.rs b/codex-rs/sandboxing/src/policy_transforms_tests.rs index a9c759a20..30dbac924 100644 --- a/codex-rs/sandboxing/src/policy_transforms_tests.rs +++ b/codex-rs/sandboxing/src/policy_transforms_tests.rs @@ -1,21 +1,10 @@ -#[cfg(target_os = "macos")] -use super::EffectiveSandboxPermissions; use super::effective_file_system_sandbox_policy; -#[cfg(target_os = "macos")] use super::intersect_permission_profiles; use super::merge_file_system_policy_with_additional_permissions; use super::normalize_additional_permissions; use super::sandbox_policy_with_additional_permissions; use super::should_require_platform_sandbox; use codex_protocol::models::FileSystemPermissions; -#[cfg(target_os = "macos")] -use codex_protocol::models::MacOsAutomationPermission; -#[cfg(target_os = "macos")] -use codex_protocol::models::MacOsContactsPermission; -#[cfg(target_os = "macos")] -use codex_protocol::models::MacOsPreferencesPermission; -#[cfg(target_os = "macos")] -use codex_protocol::models::MacOsSeatbeltProfileExtensions; use codex_protocol::models::NetworkPermissions; use codex_protocol::models::PermissionProfile; use codex_protocol::permissions::FileSystemAccessMode; @@ -110,7 +99,6 @@ fn normalize_additional_permissions_preserves_network() { read: Some(vec![path.clone()]), write: Some(vec![path.clone()]), }), - ..Default::default() }) .expect("permissions"); @@ -172,104 +160,73 @@ fn normalize_additional_permissions_drops_empty_nested_profiles() { read: None, write: None, }), - macos: None, }) .expect("permissions"); assert_eq!(permissions, PermissionProfile::default()); } -#[cfg(target_os = "macos")] #[test] -fn normalize_additional_permissions_preserves_default_macos_preferences_permission() { - let permissions = normalize_additional_permissions(PermissionProfile { - macos: Some(MacOsSeatbeltProfileExtensions::default()), - ..Default::default() - }) - .expect("permissions"); - - assert_eq!( - permissions, - PermissionProfile { - macos: Some(MacOsSeatbeltProfileExtensions::default()), - ..Default::default() - } - ); -} - -#[cfg(target_os = "macos")] -#[test] -fn intersect_permission_profiles_preserves_default_macos_grants() { +fn intersect_permission_profiles_preserves_explicit_empty_requested_reads() { + let temp_dir = TempDir::new().expect("create temp dir"); + let path = AbsolutePathBuf::from_absolute_path( + canonicalize(temp_dir.path()).expect("canonicalize temp dir"), + ) + .expect("absolute temp dir"); let requested = PermissionProfile { file_system: Some(FileSystemPermissions { - read: Some(Vec::from(["/tmp/requested" - .try_into() - .expect("absolute path")])), - write: None, - }), - macos: Some(MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadWrite, - macos_automation: MacOsAutomationPermission::BundleIds(vec![ - "com.apple.Notes".to_string(), - ]), - macos_launch_services: false, - macos_accessibility: true, - macos_calendar: true, - macos_reminders: false, - macos_contacts: MacOsContactsPermission::None, + read: Some(vec![]), + write: Some(vec![path]), }), ..Default::default() }; - let granted = PermissionProfile { - file_system: Some(FileSystemPermissions { - read: Some(Vec::new()), - write: None, - }), - macos: Some(MacOsSeatbeltProfileExtensions::default()), - ..Default::default() - }; + let granted = requested.clone(); assert_eq!( - intersect_permission_profiles(requested, granted), - PermissionProfile { - macos: Some(MacOsSeatbeltProfileExtensions::default()), - ..Default::default() - } + intersect_permission_profiles(requested.clone(), granted), + requested ); } -#[cfg(target_os = "macos")] #[test] -fn normalize_additional_permissions_preserves_macos_permissions() { - let permissions = normalize_additional_permissions(PermissionProfile { - macos: Some(MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadWrite, - macos_automation: MacOsAutomationPermission::BundleIds(vec![ - "com.apple.Notes".to_string(), - ]), - macos_launch_services: true, - macos_accessibility: true, - macos_calendar: true, - macos_reminders: false, - macos_contacts: MacOsContactsPermission::None, +fn intersect_permission_profiles_drops_ungranted_nonempty_path_requests() { + let temp_dir = TempDir::new().expect("create temp dir"); + let path = AbsolutePathBuf::from_absolute_path( + canonicalize(temp_dir.path()).expect("canonicalize temp dir"), + ) + .expect("absolute temp dir"); + let requested = PermissionProfile { + file_system: Some(FileSystemPermissions { + read: Some(vec![path]), + write: None, }), ..Default::default() - }) - .expect("permissions"); + }; assert_eq!( - permissions.macos, - Some(MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadWrite, - macos_automation: MacOsAutomationPermission::BundleIds(vec![ - "com.apple.Notes".to_string(), - ]), - macos_launch_services: true, - macos_accessibility: true, - macos_calendar: true, - macos_reminders: false, - macos_contacts: MacOsContactsPermission::None, - }) + intersect_permission_profiles(requested, PermissionProfile::default()), + PermissionProfile::default() + ); +} + +#[test] +fn intersect_permission_profiles_drops_explicit_empty_reads_without_grant() { + let temp_dir = TempDir::new().expect("create temp dir"); + let path = AbsolutePathBuf::from_absolute_path( + canonicalize(temp_dir.path()).expect("canonicalize temp dir"), + ) + .expect("absolute temp dir"); + let requested = PermissionProfile { + file_system: Some(FileSystemPermissions { + read: Some(vec![]), + write: Some(vec![path]), + }), + ..Default::default() + }; + + assert_eq!( + intersect_permission_profiles(requested, PermissionProfile::default()), + PermissionProfile::default() ); } @@ -296,7 +253,6 @@ fn read_only_additional_permissions_can_enable_network_without_writes() { read: Some(vec![path.clone()]), write: Some(Vec::new()), }), - ..Default::default() }, ); @@ -312,70 +268,6 @@ fn read_only_additional_permissions_can_enable_network_without_writes() { ); } -#[cfg(target_os = "macos")] -#[test] -fn effective_permissions_merge_macos_extensions_with_additional_permissions() { - let temp_dir = TempDir::new().expect("create temp dir"); - let path = AbsolutePathBuf::from_absolute_path( - canonicalize(temp_dir.path()).expect("canonicalize temp dir"), - ) - .expect("absolute temp dir"); - let effective_permissions = EffectiveSandboxPermissions::new( - &SandboxPolicy::ReadOnly { - access: ReadOnlyAccess::Restricted { - include_platform_defaults: true, - readable_roots: vec![path.clone()], - }, - network_access: false, - }, - Some(&MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadOnly, - macos_automation: MacOsAutomationPermission::BundleIds(vec![ - "com.apple.Calendar".to_string(), - ]), - macos_launch_services: false, - macos_accessibility: false, - macos_calendar: false, - macos_reminders: false, - macos_contacts: MacOsContactsPermission::None, - }), - Some(&PermissionProfile { - file_system: Some(FileSystemPermissions { - read: Some(vec![path]), - write: Some(Vec::new()), - }), - macos: Some(MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadWrite, - macos_automation: MacOsAutomationPermission::BundleIds(vec![ - "com.apple.Notes".to_string(), - ]), - macos_launch_services: true, - macos_accessibility: true, - macos_calendar: true, - macos_reminders: false, - macos_contacts: MacOsContactsPermission::None, - }), - ..Default::default() - }), - ); - - assert_eq!( - effective_permissions.macos_seatbelt_profile_extensions, - Some(MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadWrite, - macos_automation: MacOsAutomationPermission::BundleIds(vec![ - "com.apple.Calendar".to_string(), - "com.apple.Notes".to_string(), - ]), - macos_launch_services: true, - macos_accessibility: true, - macos_calendar: true, - macos_reminders: false, - macos_contacts: MacOsContactsPermission::None, - }) - ); -} - #[test] fn external_sandbox_additional_permissions_can_enable_network() { let temp_dir = TempDir::new().expect("create temp dir"); @@ -395,7 +287,6 @@ fn external_sandbox_additional_permissions_can_enable_network() { read: Some(vec![path]), write: Some(Vec::new()), }), - ..Default::default() }, ); diff --git a/codex-rs/sandboxing/src/seatbelt.rs b/codex-rs/sandboxing/src/seatbelt.rs index e6ff71d0d..5c11c4fc4 100644 --- a/codex-rs/sandboxing/src/seatbelt.rs +++ b/codex-rs/sandboxing/src/seatbelt.rs @@ -2,7 +2,6 @@ use codex_network_proxy::NetworkProxy; use codex_network_proxy::PROXY_URL_ENV_KEYS; use codex_network_proxy::has_proxy_url_env_vars; use codex_network_proxy::proxy_url_env_value; -use codex_protocol::models::MacOsSeatbeltProfileExtensions; use codex_protocol::permissions::FileSystemSandboxPolicy; use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::protocol::SandboxPolicy; @@ -16,12 +15,18 @@ use std::path::PathBuf; use tracing::warn; use url::Url; -use crate::seatbelt_permissions::build_seatbelt_extensions; - 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_READ_ONLY_PLATFORM_DEFAULTS: &str = include_str!("restricted_read_only_platform_defaults.sbpl"); +const MACOS_DEFAULT_PREFERENCES_POLICY: &str = r#"; allow readonly user preferences +(allow ipc-posix-shm-read* (ipc-posix-name-prefix "apple.cfprefs.")) +(allow mach-lookup + (global-name "com.apple.cfprefsd.daemon") + (global-name "com.apple.cfprefsd.agent") + (local-name "com.apple.cfprefsd.agent")) +(allow user-preference-read) +"#; /// 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 @@ -356,33 +361,30 @@ fn build_seatbelt_access_policy( } #[cfg_attr(not(test), allow(dead_code))] -fn create_seatbelt_command_args_with_extensions( +fn create_seatbelt_command_args( command: Vec, sandbox_policy: &SandboxPolicy, sandbox_policy_cwd: &Path, enforce_managed_network: bool, network: Option<&NetworkProxy>, - extensions: Option<&MacOsSeatbeltProfileExtensions>, ) -> Vec { - create_seatbelt_command_args_for_policies_with_extensions( + create_seatbelt_command_args_for_policies( command, &FileSystemSandboxPolicy::from_legacy_sandbox_policy(sandbox_policy, sandbox_policy_cwd), NetworkSandboxPolicy::from(sandbox_policy), sandbox_policy_cwd, enforce_managed_network, network, - extensions, ) } -pub fn create_seatbelt_command_args_for_policies_with_extensions( +pub fn create_seatbelt_command_args_for_policies( command: Vec, file_system_sandbox_policy: &FileSystemSandboxPolicy, network_sandbox_policy: NetworkSandboxPolicy, sandbox_policy_cwd: &Path, enforce_managed_network: bool, network: Option<&NetworkProxy>, - extensions: Option<&MacOsSeatbeltProfileExtensions>, ) -> Vec { let unreadable_roots = file_system_sandbox_policy.get_unreadable_roots_with_cwd(sandbox_policy_cwd); @@ -470,17 +472,11 @@ pub fn create_seatbelt_command_args_for_policies_with_extensions( let proxy = proxy_policy_inputs(network); let network_policy = dynamic_network_policy_for_network(network_sandbox_policy, enforce_managed_network, &proxy); - let seatbelt_extensions = extensions.map_or_else( - || { - // Backward-compatibility default when no extension profile is provided. - build_seatbelt_extensions(&MacOsSeatbeltProfileExtensions::default()) - }, - build_seatbelt_extensions, - ); let include_platform_defaults = file_system_sandbox_policy.include_platform_defaults(); let mut policy_sections = vec![ MACOS_SEATBELT_BASE_POLICY.to_string(), + MACOS_DEFAULT_PREFERENCES_POLICY.to_string(), file_read_policy, file_write_policy, network_policy, @@ -488,9 +484,6 @@ pub fn create_seatbelt_command_args_for_policies_with_extensions( if include_platform_defaults { policy_sections.push(MACOS_RESTRICTED_READ_ONLY_PLATFORM_DEFAULTS.to_string()); } - if !seatbelt_extensions.policy.is_empty() { - policy_sections.push(seatbelt_extensions.policy.clone()); - } let full_policy = policy_sections.join("\n"); @@ -499,7 +492,6 @@ pub fn create_seatbelt_command_args_for_policies_with_extensions( file_write_dir_params, macos_dir_params(), unix_socket_dir_params(&proxy), - seatbelt_extensions.dir_params, ] .concat(); diff --git a/codex-rs/sandboxing/src/seatbelt_permissions.rs b/codex-rs/sandboxing/src/seatbelt_permissions.rs deleted file mode 100644 index d53e7496a..000000000 --- a/codex-rs/sandboxing/src/seatbelt_permissions.rs +++ /dev/null @@ -1,190 +0,0 @@ -use std::collections::BTreeSet; -use std::path::PathBuf; - -use codex_protocol::models::MacOsAutomationPermission; -use codex_protocol::models::MacOsContactsPermission; -use codex_protocol::models::MacOsPreferencesPermission; -use codex_protocol::models::MacOsSeatbeltProfileExtensions; - -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub(crate) struct SeatbeltExtensionPolicy { - pub(crate) policy: String, - pub(crate) dir_params: Vec<(String, PathBuf)>, -} - -fn normalized_extensions( - extensions: &MacOsSeatbeltProfileExtensions, -) -> MacOsSeatbeltProfileExtensions { - let mut normalized = extensions.clone(); - if let MacOsAutomationPermission::BundleIds(bundle_ids) = &extensions.macos_automation { - let bundle_ids = normalize_bundle_ids(bundle_ids); - normalized.macos_automation = if bundle_ids.is_empty() { - MacOsAutomationPermission::None - } else { - MacOsAutomationPermission::BundleIds(bundle_ids) - }; - } - - normalized -} - -pub(crate) fn build_seatbelt_extensions( - extensions: &MacOsSeatbeltProfileExtensions, -) -> SeatbeltExtensionPolicy { - let extensions = normalized_extensions(extensions); - let mut clauses = Vec::new(); - - match extensions.macos_preferences { - MacOsPreferencesPermission::None => {} - MacOsPreferencesPermission::ReadOnly => { - clauses.push( - "(allow ipc-posix-shm-read* (ipc-posix-name-prefix \"apple.cfprefs.\"))" - .to_string(), - ); - clauses.push( - "(allow mach-lookup\n (global-name \"com.apple.cfprefsd.daemon\")\n (global-name \"com.apple.cfprefsd.agent\")\n (local-name \"com.apple.cfprefsd.agent\"))" - .to_string(), - ); - clauses.push("(allow user-preference-read)".to_string()); - } - MacOsPreferencesPermission::ReadWrite => { - clauses.push( - "(allow ipc-posix-shm-read* (ipc-posix-name-prefix \"apple.cfprefs.\"))" - .to_string(), - ); - clauses.push( - "(allow mach-lookup\n (global-name \"com.apple.cfprefsd.daemon\")\n (global-name \"com.apple.cfprefsd.agent\")\n (local-name \"com.apple.cfprefsd.agent\"))" - .to_string(), - ); - clauses.push("(allow user-preference-read)".to_string()); - clauses.push("(allow user-preference-write)".to_string()); - clauses.push( - "(allow ipc-posix-shm-write-data (ipc-posix-name-prefix \"apple.cfprefs.\"))" - .to_string(), - ); - clauses.push( - "(allow ipc-posix-shm-write-create (ipc-posix-name-prefix \"apple.cfprefs.\"))" - .to_string(), - ); - } - } - - match extensions.macos_automation { - MacOsAutomationPermission::None => {} - MacOsAutomationPermission::All => { - clauses.push( - "(allow mach-lookup\n (global-name \"com.apple.coreservices.appleevents\"))" - .to_string(), - ); - clauses.push("(allow appleevent-send)".to_string()); - } - MacOsAutomationPermission::BundleIds(bundle_ids) => { - if !bundle_ids.is_empty() { - clauses.push( - "(allow mach-lookup\n (global-name \"com.apple.coreservices.appleevents\"))" - .to_string(), - ); - let destinations = bundle_ids - .iter() - .map(|bundle_id| format!(" (appleevent-destination \"{bundle_id}\")")) - .collect::>() - .join("\n"); - clauses.push(format!("(allow appleevent-send\n{destinations}\n)")); - } - } - } - - if extensions.macos_launch_services { - clauses.push( - "(allow mach-lookup\n (global-name \"com.apple.coreservices.launchservicesd\")\n (global-name \"com.apple.lsd.mapdb\")\n (global-name \"com.apple.coreservices.quarantine-resolver\")\n (global-name \"com.apple.lsd.modifydb\"))" - .to_string(), - ); - clauses.push("(allow lsopen)".to_string()); - } - - if extensions.macos_accessibility { - clauses.push("(allow mach-lookup (local-name \"com.apple.axserver\"))".to_string()); - } - - if extensions.macos_calendar { - clauses.push("(allow mach-lookup (global-name \"com.apple.CalendarAgent\"))".to_string()); - } - - if extensions.macos_reminders { - clauses.push( - "(allow mach-lookup\n (global-name \"com.apple.CalendarAgent\")\n (global-name \"com.apple.remindd\"))" - .to_string(), - ); - } - - let mut dir_params = Vec::new(); - match extensions.macos_contacts { - MacOsContactsPermission::None => {} - MacOsContactsPermission::ReadOnly => { - clauses.push( - "(allow file-read* file-test-existence\n (subpath \"/System/Library/Address Book Plug-Ins\")\n (subpath (param \"ADDRESSBOOK_DIR\")))" - .to_string(), - ); - clauses.push( - "(allow mach-lookup\n (global-name \"com.apple.tccd\")\n (global-name \"com.apple.tccd.system\")\n (global-name \"com.apple.contactsd.persistence\")\n (global-name \"com.apple.AddressBook.ContactsAccountsService\")\n (global-name \"com.apple.contacts.account-caching\")\n (global-name \"com.apple.accountsd.accountmanager\"))" - .to_string(), - ); - if let Some(addressbook_dir) = addressbook_dir() { - dir_params.push(("ADDRESSBOOK_DIR".to_string(), addressbook_dir)); - } - } - MacOsContactsPermission::ReadWrite => { - clauses.push( - "(allow file-read* file-write*\n (subpath \"/System/Library/Address Book Plug-Ins\")\n (subpath (param \"ADDRESSBOOK_DIR\"))\n (subpath \"/var/folders\")\n (subpath \"/private/var/folders\"))" - .to_string(), - ); - clauses.push( - "(allow mach-lookup\n (global-name \"com.apple.tccd\")\n (global-name \"com.apple.tccd.system\")\n (global-name \"com.apple.contactsd.persistence\")\n (global-name \"com.apple.AddressBook.ContactsAccountsService\")\n (global-name \"com.apple.contacts.account-caching\")\n (global-name \"com.apple.accountsd.accountmanager\")\n (global-name \"com.apple.securityd.xpc\"))" - .to_string(), - ); - if let Some(addressbook_dir) = addressbook_dir() { - dir_params.push(("ADDRESSBOOK_DIR".to_string(), addressbook_dir)); - } - } - } - - if clauses.is_empty() { - SeatbeltExtensionPolicy::default() - } else { - SeatbeltExtensionPolicy { - policy: format!( - "; macOS permission profile extensions\n{}\n", - clauses.join("\n") - ), - dir_params, - } - } -} - -fn addressbook_dir() -> Option { - Some(dirs::home_dir()?.join("Library/Application Support/AddressBook")) -} - -fn normalize_bundle_ids(bundle_ids: &[String]) -> Vec { - let mut unique = BTreeSet::new(); - for bundle_id in bundle_ids { - let candidate = bundle_id.trim(); - if is_valid_bundle_id(candidate) { - unique.insert(candidate.to_string()); - } - } - unique.into_iter().collect() -} - -fn is_valid_bundle_id(bundle_id: &str) -> bool { - if bundle_id.len() < 3 || !bundle_id.contains('.') { - return false; - } - bundle_id - .chars() - .all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-' || c == '_') -} - -#[cfg(test)] -#[path = "seatbelt_permissions_tests.rs"] -mod tests; diff --git a/codex-rs/sandboxing/src/seatbelt_permissions_tests.rs b/codex-rs/sandboxing/src/seatbelt_permissions_tests.rs deleted file mode 100644 index b52ccdfcb..000000000 --- a/codex-rs/sandboxing/src/seatbelt_permissions_tests.rs +++ /dev/null @@ -1,154 +0,0 @@ -use super::MacOsAutomationPermission; -use super::MacOsContactsPermission; -use super::MacOsPreferencesPermission; -use super::MacOsSeatbeltProfileExtensions; -use super::build_seatbelt_extensions; - -#[test] -fn preferences_read_only_emits_read_clauses_only() { - let policy = build_seatbelt_extensions(&MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadOnly, - ..Default::default() - }); - assert!(policy.policy.contains("(allow user-preference-read)")); - assert!(!policy.policy.contains("(allow user-preference-write)")); -} - -#[test] -fn preferences_read_write_emits_write_clauses() { - let policy = build_seatbelt_extensions(&MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadWrite, - ..Default::default() - }); - assert!(policy.policy.contains("(allow user-preference-read)")); - assert!(policy.policy.contains("(allow user-preference-write)")); - assert!( - policy.policy.contains( - "(allow ipc-posix-shm-write-create (ipc-posix-name-prefix \"apple.cfprefs.\"))" - ) - ); -} - -#[test] -fn automation_all_emits_unscoped_appleevents() { - let policy = build_seatbelt_extensions(&MacOsSeatbeltProfileExtensions { - macos_automation: MacOsAutomationPermission::All, - ..Default::default() - }); - assert!(policy.policy.contains("(allow appleevent-send)")); - assert!(policy.policy.contains("com.apple.coreservices.appleevents")); -} - -#[test] -fn automation_bundle_ids_are_normalized_and_scoped() { - let policy = build_seatbelt_extensions(&MacOsSeatbeltProfileExtensions { - macos_automation: MacOsAutomationPermission::BundleIds(vec![ - " com.apple.Notes ".to_string(), - "com.apple.Calendar".to_string(), - "bad bundle".to_string(), - "com.apple.Notes".to_string(), - ]), - ..Default::default() - }); - assert!( - policy - .policy - .contains("(appleevent-destination \"com.apple.Calendar\")") - ); - assert!( - policy - .policy - .contains("(appleevent-destination \"com.apple.Notes\")") - ); - assert!(!policy.policy.contains("bad bundle")); - assert!(policy.policy.contains("com.apple.coreservices.appleevents")); -} - -#[test] -fn launch_services_emit_launch_clauses() { - let policy = build_seatbelt_extensions(&MacOsSeatbeltProfileExtensions { - macos_launch_services: true, - ..Default::default() - }); - assert!( - policy - .policy - .contains("com.apple.coreservices.launchservicesd") - ); - assert!(policy.policy.contains("com.apple.lsd.mapdb")); - assert!( - policy - .policy - .contains("com.apple.coreservices.quarantine-resolver") - ); - assert!(policy.policy.contains("com.apple.lsd.modifydb")); - assert!(policy.policy.contains("(allow lsopen)")); -} - -#[test] -fn accessibility_and_calendar_emit_mach_lookups() { - let policy = build_seatbelt_extensions(&MacOsSeatbeltProfileExtensions { - macos_accessibility: true, - macos_calendar: true, - ..Default::default() - }); - assert!(policy.policy.contains("com.apple.axserver")); - assert!(policy.policy.contains("com.apple.CalendarAgent")); -} - -#[test] -fn reminders_emit_calendar_agent_and_remindd_lookups() { - let policy = build_seatbelt_extensions(&MacOsSeatbeltProfileExtensions { - macos_reminders: true, - ..Default::default() - }); - assert!(policy.policy.contains("com.apple.CalendarAgent")); - assert!(policy.policy.contains("com.apple.remindd")); -} - -#[test] -fn contacts_read_only_emit_contacts_read_clauses() { - let policy = build_seatbelt_extensions(&MacOsSeatbeltProfileExtensions { - macos_contacts: MacOsContactsPermission::ReadOnly, - ..Default::default() - }); - - assert!( - policy - .policy - .contains("(subpath \"/System/Library/Address Book Plug-Ins\")") - ); - assert!( - policy - .policy - .contains("(subpath (param \"ADDRESSBOOK_DIR\"))") - ); - assert!(policy.policy.contains("com.apple.contactsd.persistence")); - assert!(policy.policy.contains("com.apple.accountsd.accountmanager")); - assert!(!policy.policy.contains("com.apple.securityd.xpc")); - assert!( - policy - .dir_params - .iter() - .any(|(key, _)| key == "ADDRESSBOOK_DIR") - ); -} - -#[test] -fn contacts_read_write_emit_write_clauses() { - let policy = build_seatbelt_extensions(&MacOsSeatbeltProfileExtensions { - macos_contacts: MacOsContactsPermission::ReadWrite, - ..Default::default() - }); - - assert!(policy.policy.contains("(subpath \"/var/folders\")")); - assert!(policy.policy.contains("(subpath \"/private/var/folders\")")); - assert!(policy.policy.contains("com.apple.securityd.xpc")); -} - -#[test] -fn default_extensions_emit_preferences_read_only_policy() { - let policy = build_seatbelt_extensions(&MacOsSeatbeltProfileExtensions::default()); - assert!(policy.policy.contains("(allow user-preference-read)")); - assert!(!policy.policy.contains("(allow user-preference-write)")); -} diff --git a/codex-rs/sandboxing/src/seatbelt_tests.rs b/codex-rs/sandboxing/src/seatbelt_tests.rs index e508a028c..cc6fce56a 100644 --- a/codex-rs/sandboxing/src/seatbelt_tests.rs +++ b/codex-rs/sandboxing/src/seatbelt_tests.rs @@ -2,17 +2,13 @@ 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_for_policies_with_extensions; -use super::create_seatbelt_command_args_with_extensions; +use super::create_seatbelt_command_args; +use super::create_seatbelt_command_args_for_policies; 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_protocol::models::MacOsAutomationPermission; -use codex_protocol::models::MacOsContactsPermission; -use codex_protocol::models::MacOsPreferencesPermission; -use codex_protocol::models::MacOsSeatbeltProfileExtensions; use codex_protocol::permissions::FileSystemAccessMode; use codex_protocol::permissions::FileSystemPath; use codex_protocol::permissions::FileSystemSandboxEntry; @@ -115,14 +111,13 @@ fn explicit_unreadable_paths_are_excluded_from_full_disk_read_and_write_access() }, ]); - let args = create_seatbelt_command_args_for_policies_with_extensions( + let args = create_seatbelt_command_args_for_policies( vec!["/bin/true".to_string()], &file_system_policy, NetworkSandboxPolicy::Restricted, Path::new("/"), false, None, - None, ); let policy = seatbelt_policy_arg(&args); @@ -181,14 +176,13 @@ fn explicit_unreadable_paths_are_excluded_from_readable_roots() { }, ]); - let args = create_seatbelt_command_args_for_policies_with_extensions( + let args = create_seatbelt_command_args_for_policies( vec!["/bin/true".to_string()], &file_system_policy, NetworkSandboxPolicy::Restricted, Path::new("/"), false, None, - None, ); let policy = seatbelt_policy_arg(&args); @@ -217,96 +211,15 @@ fn explicit_unreadable_paths_are_excluded_from_readable_roots() { ); } -#[test] -fn seatbelt_args_include_macos_permission_extensions() { - let cwd = std::env::temp_dir(); - let args = create_seatbelt_command_args_with_extensions( - vec!["echo".to_string(), "ok".to_string()], - &SandboxPolicy::new_read_only_policy(), - cwd.as_path(), - false, - None, - Some(&MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadWrite, - macos_automation: MacOsAutomationPermission::BundleIds(vec![ - "com.apple.Notes".to_string(), - ]), - macos_launch_services: true, - macos_accessibility: true, - macos_calendar: true, - macos_reminders: false, - macos_contacts: MacOsContactsPermission::None, - }), - ); - let policy = &args[1]; - - assert!(policy.contains("(allow user-preference-write)")); - assert!(policy.contains("(appleevent-destination \"com.apple.Notes\")")); - assert!(policy.contains("com.apple.axserver")); - assert!(policy.contains("com.apple.CalendarAgent")); -} - -#[test] -fn bundle_id_automation_keeps_lsopen_denied() { - let tmp = TempDir::new().expect("tempdir"); - let cwd = tmp.path().join("cwd"); - fs::create_dir_all(&cwd).expect("create cwd"); - - let args = create_seatbelt_command_args_with_extensions( - vec![ - "/usr/bin/python3".to_string(), - "-c".to_string(), - r#"import ctypes -import os -import sys -lib = ctypes.CDLL("/usr/lib/libsandbox.1.dylib") -lib.sandbox_check.restype = ctypes.c_int -allowed = lib.sandbox_check(os.getpid(), b"lsopen", 0) == 0 -sys.exit(0 if allowed else 13) -"# - .to_string(), - ], - &SandboxPolicy::new_read_only_policy(), - cwd.as_path(), - false, - None, - Some(&MacOsSeatbeltProfileExtensions { - macos_automation: MacOsAutomationPermission::BundleIds(vec![ - "com.apple.Notes".to_string(), - ]), - ..Default::default() - }), - ); - - let output = Command::new(MACOS_PATH_TO_SEATBELT_EXECUTABLE) - .args(&args) - .current_dir(&cwd) - .output() - .expect("execute seatbelt command"); - - let stderr = String::from_utf8_lossy(&output.stderr); - if stderr.contains("sandbox-exec: sandbox_apply: Operation not permitted") { - return; - } - - assert_eq!( - Some(13), - output.status.code(), - "lsopen should remain denied even with bundle-scoped automation\nstdout: {}\nstderr: {stderr}", - String::from_utf8_lossy(&output.stdout), - ); -} - #[test] fn seatbelt_args_without_extension_profile_keep_legacy_preferences_read_access() { let cwd = std::env::temp_dir(); - let args = create_seatbelt_command_args_with_extensions( + let args = create_seatbelt_command_args( vec!["echo".to_string(), "ok".to_string()], &SandboxPolicy::new_read_only_policy(), cwd.as_path(), /*enforce_managed_network*/ false, None, - /*extensions*/ None, ); let policy = &args[1]; assert!(policy.contains("(allow user-preference-read)")); @@ -319,7 +232,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_with_extensions( + let args = create_seatbelt_command_args( vec!["/bin/true".to_string()], &SandboxPolicy::WorkspaceWrite { writable_roots: vec![], @@ -334,7 +247,6 @@ fn seatbelt_legacy_workspace_write_nested_readable_root_stays_writable() { cwd.as_path(), /*enforce_managed_network*/ false, None, - /*extensions*/ None, ); assert!( @@ -351,25 +263,6 @@ fn seatbelt_legacy_workspace_write_nested_readable_root_stays_writable() { ); } -#[test] -fn seatbelt_args_default_extension_profile_keeps_preferences_read_access() { - let cwd = std::env::temp_dir(); - let args = create_seatbelt_command_args_with_extensions( - vec!["echo".to_string(), "ok".to_string()], - &SandboxPolicy::new_read_only_policy(), - cwd.as_path(), - false, - None, - Some(&MacOsSeatbeltProfileExtensions::default()), - ); - let policy = &args[1]; - assert!(!policy.contains("appleevent-send")); - assert!(!policy.contains("com.apple.axserver")); - assert!(!policy.contains("com.apple.CalendarAgent")); - assert!(policy.contains("(allow user-preference-read)")); - assert!(!policy.contains("user-preference-write")); -} - #[test] fn create_seatbelt_args_allows_local_binding_when_explicitly_enabled() { let policy = dynamic_network_policy( @@ -664,13 +557,12 @@ 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_with_extensions( + let args = create_seatbelt_command_args( shell_command.clone(), &policy, &cwd, /*enforce_managed_network*/ false, None, - /*extensions*/ None, ); let policy_text = seatbelt_policy_arg(&args); @@ -779,13 +671,12 @@ 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_with_extensions( + let write_hooks_file_args = create_seatbelt_command_args( shell_command_git, &policy, &cwd, /*enforce_managed_network*/ false, None, - /*extensions*/ None, ); let output = Command::new(MACOS_PATH_TO_SEATBELT_EXECUTABLE) .args(&write_hooks_file_args) @@ -816,13 +707,12 @@ 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_with_extensions( + let write_allowed_file_args = create_seatbelt_command_args( shell_command_allowed, &policy, &cwd, /*enforce_managed_network*/ false, None, - /*extensions*/ None, ); let output = Command::new(MACOS_PATH_TO_SEATBELT_EXECUTABLE) .args(&write_allowed_file_args) @@ -882,13 +772,12 @@ 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_with_extensions( + let args = create_seatbelt_command_args( shell_command, &policy, repo_root.as_path(), /*enforce_managed_network*/ false, /*network*/ None, - /*extensions*/ None, ); let policy_text = seatbelt_policy_arg(&args); @@ -938,13 +827,12 @@ fn create_seatbelt_args_with_read_only_git_pointer_file() { .iter() .map(std::string::ToString::to_string) .collect(); - let args = create_seatbelt_command_args_with_extensions( + let args = create_seatbelt_command_args( shell_command, &policy, &cwd, /*enforce_managed_network*/ false, None, - /*extensions*/ None, ); let output = Command::new(MACOS_PATH_TO_SEATBELT_EXECUTABLE) @@ -975,13 +863,12 @@ 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_with_extensions( + let gitdir_args = create_seatbelt_command_args( shell_command_gitdir, &policy, &cwd, /*enforce_managed_network*/ false, None, - /*extensions*/ None, ); let output = Command::new(MACOS_PATH_TO_SEATBELT_EXECUTABLE) .args(&gitdir_args) @@ -1039,13 +926,12 @@ fn create_seatbelt_args_for_cwd_as_git_repo() { .iter() .map(std::string::ToString::to_string) .collect(); - let args = create_seatbelt_command_args_with_extensions( + let args = create_seatbelt_command_args( shell_command.clone(), &policy, vulnerable_root.as_path(), /*enforce_managed_network*/ false, None, - /*extensions*/ None, ); let tmpdir_env_var = std::env::var("TMPDIR") @@ -1067,19 +953,19 @@ fn create_seatbelt_args_for_cwd_as_git_repo() { // - write access to WRITABLE_ROOT_0 (but not its .git or .codex), WRITABLE_ROOT_1, and cwd as WRITABLE_ROOT_2. let expected_policy = format!( r#"{MACOS_SEATBELT_BASE_POLICY} -; allow read-only file operations -(allow file-read*) -(allow file-write* -(require-all (subpath (param "WRITABLE_ROOT_0")) (require-not (literal (param "WRITABLE_ROOT_0_EXCLUDED_0"))) (require-not (subpath (param "WRITABLE_ROOT_0_EXCLUDED_0"))) (require-not (literal (param "WRITABLE_ROOT_0_EXCLUDED_1"))) (require-not (subpath (param "WRITABLE_ROOT_0_EXCLUDED_1"))) ) (subpath (param "WRITABLE_ROOT_1")){tempdir_policy_entry} -) - -; macOS permission profile extensions +; allow readonly user preferences (allow ipc-posix-shm-read* (ipc-posix-name-prefix "apple.cfprefs.")) (allow mach-lookup (global-name "com.apple.cfprefsd.daemon") (global-name "com.apple.cfprefsd.agent") (local-name "com.apple.cfprefsd.agent")) (allow user-preference-read) + +; allow read-only file operations +(allow file-read*) +(allow file-write* +(require-all (subpath (param "WRITABLE_ROOT_0")) (require-not (literal (param "WRITABLE_ROOT_0_EXCLUDED_0"))) (require-not (subpath (param "WRITABLE_ROOT_0_EXCLUDED_0"))) (require-not (literal (param "WRITABLE_ROOT_0_EXCLUDED_1"))) (require-not (subpath (param "WRITABLE_ROOT_0_EXCLUDED_1"))) ) (subpath (param "WRITABLE_ROOT_1")){tempdir_policy_entry} +) "#, ); diff --git a/codex-rs/tui/src/bottom_pane/approval_overlay.rs b/codex-rs/tui/src/bottom_pane/approval_overlay.rs index 1b403c251..0d6ef77c1 100644 --- a/codex-rs/tui/src/bottom_pane/approval_overlay.rs +++ b/codex-rs/tui/src/bottom_pane/approval_overlay.rs @@ -19,9 +19,6 @@ use crate::render::renderable::Renderable; use codex_features::Features; use codex_protocol::ThreadId; use codex_protocol::mcp::RequestId; -use codex_protocol::models::MacOsAutomationPermission; -use codex_protocol::models::MacOsContactsPermission; -use codex_protocol::models::MacOsPreferencesPermission; use codex_protocol::models::PermissionProfile; use codex_protocol::protocol::ElicitationAction; use codex_protocol::protocol::FileChange; @@ -777,48 +774,6 @@ pub(crate) fn format_additional_permissions_rule( parts.push(format!("write {writes}")); } } - if let Some(macos) = additional_permissions.macos.as_ref() { - if !matches!( - macos.macos_preferences, - MacOsPreferencesPermission::ReadOnly - ) { - let value = match macos.macos_preferences { - MacOsPreferencesPermission::ReadOnly => "readonly", - MacOsPreferencesPermission::ReadWrite => "readwrite", - MacOsPreferencesPermission::None => "none", - }; - parts.push(format!("macOS preferences {value}")); - } - match &macos.macos_automation { - MacOsAutomationPermission::All => { - parts.push("macOS automation all".to_string()); - } - MacOsAutomationPermission::BundleIds(bundle_ids) => { - if !bundle_ids.is_empty() { - parts.push(format!("macOS automation {}", bundle_ids.join(", "))); - } - } - MacOsAutomationPermission::None => {} - } - if macos.macos_accessibility { - parts.push("macOS accessibility".to_string()); - } - if macos.macos_calendar { - parts.push("macOS calendar".to_string()); - } - if macos.macos_reminders { - parts.push("macOS reminders".to_string()); - } - if !matches!(macos.macos_contacts, MacOsContactsPermission::None) { - let value = match macos.macos_contacts { - MacOsContactsPermission::None => "none", - MacOsContactsPermission::ReadOnly => "readonly", - MacOsContactsPermission::ReadWrite => "readwrite", - }; - parts.push(format!("macOS contacts {value}")); - } - } - if parts.is_empty() { None } else { @@ -906,9 +861,6 @@ mod tests { use super::*; use crate::app_event::AppEvent; use codex_protocol::models::FileSystemPermissions; - use codex_protocol::models::MacOsAutomationPermission; - use codex_protocol::models::MacOsPreferencesPermission; - use codex_protocol::models::MacOsSeatbeltProfileExtensions; use codex_protocol::models::NetworkPermissions; use codex_protocol::protocol::ExecPolicyAmendment; use codex_protocol::protocol::NetworkApprovalProtocol; @@ -1334,7 +1286,6 @@ mod tests { read: Some(vec![absolute_path("/tmp/readme.txt")]), write: Some(vec![absolute_path("/tmp/out.txt")]), }), - ..Default::default() }), }; @@ -1382,7 +1333,6 @@ mod tests { read: Some(vec![absolute_path("/tmp/readme.txt")]), write: Some(vec![absolute_path("/tmp/out.txt")]), }), - ..Default::default() }), }; @@ -1404,42 +1354,6 @@ mod tests { ); } - #[test] - fn additional_permissions_macos_prompt_snapshot() { - let (tx, _rx) = unbounded_channel::(); - let tx = AppEventSender::new(tx); - let exec_request = ApprovalRequest::Exec { - thread_id: ThreadId::new(), - thread_label: None, - id: "test".into(), - command: vec!["osascript".into(), "-e".into(), "tell application".into()], - reason: Some("need macOS automation".into()), - available_decisions: vec![ReviewDecision::Approved, ReviewDecision::Abort], - network_approval_context: None, - additional_permissions: Some(PermissionProfile { - macos: Some(MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadWrite, - macos_automation: MacOsAutomationPermission::BundleIds(vec![ - "com.apple.Calendar".to_string(), - "com.apple.Notes".to_string(), - ]), - macos_launch_services: false, - macos_accessibility: true, - macos_calendar: true, - macos_reminders: true, - macos_contacts: MacOsContactsPermission::None, - }), - ..Default::default() - }), - }; - - let view = ApprovalOverlay::new(exec_request, tx, Features::with_defaults()); - assert_snapshot!( - "approval_overlay_additional_permissions_macos_prompt", - render_overlay_lines(&view, 120) - ); - } - #[test] fn network_exec_prompt_title_includes_host() { let (tx, _rx) = unbounded_channel::(); diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__approval_overlay__tests__approval_overlay_additional_permissions_macos_prompt.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__approval_overlay__tests__approval_overlay_additional_permissions_macos_prompt.snap deleted file mode 100644 index d9d8717fe..000000000 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__approval_overlay__tests__approval_overlay_additional_permissions_macos_prompt.snap +++ /dev/null @@ -1,18 +0,0 @@ ---- -source: tui/src/bottom_pane/approval_overlay.rs -expression: "render_overlay_lines(&view, 120)" ---- - - Would you like to run the following command? - - Reason: need macOS automation - - Permission rule: macOS preferences readwrite; macOS automation com.apple.Calendar, com.apple.Notes; macOS - accessibility; macOS calendar; macOS reminders - - $ osascript -e 'tell application' - -› 1. Yes, proceed (y) - 2. No, and tell Codex what to do differently (esc) - - Press enter to confirm or esc to cancel diff --git a/codex-rs/tui_app_server/src/app.rs b/codex-rs/tui_app_server/src/app.rs index a86a8c938..c36f793b7 100644 --- a/codex-rs/tui_app_server/src/app.rs +++ b/codex-rs/tui_app_server/src/app.rs @@ -7568,7 +7568,6 @@ guardian_approval = true enabled: Some(true), }), file_system: None, - macos: None, }); params.proposed_network_policy_amendments = Some(vec![AppServerNetworkPolicyAmendment { host: "example.com".to_string(), @@ -7601,7 +7600,6 @@ guardian_approval = true enabled: Some(true), }), file_system: None, - macos: None, }) ); assert_eq!( diff --git a/codex-rs/tui_app_server/src/bottom_pane/approval_overlay.rs b/codex-rs/tui_app_server/src/bottom_pane/approval_overlay.rs index f5d1cee62..84f6da6cb 100644 --- a/codex-rs/tui_app_server/src/bottom_pane/approval_overlay.rs +++ b/codex-rs/tui_app_server/src/bottom_pane/approval_overlay.rs @@ -19,9 +19,6 @@ use crate::render::renderable::Renderable; use codex_features::Features; use codex_protocol::ThreadId; use codex_protocol::mcp::RequestId; -use codex_protocol::models::MacOsAutomationPermission; -use codex_protocol::models::MacOsContactsPermission; -use codex_protocol::models::MacOsPreferencesPermission; use codex_protocol::models::PermissionProfile; use codex_protocol::protocol::ElicitationAction; use codex_protocol::protocol::FileChange; @@ -763,48 +760,6 @@ pub(crate) fn format_additional_permissions_rule( parts.push(format!("write {writes}")); } } - if let Some(macos) = additional_permissions.macos.as_ref() { - if !matches!( - macos.macos_preferences, - MacOsPreferencesPermission::ReadOnly - ) { - let value = match macos.macos_preferences { - MacOsPreferencesPermission::ReadOnly => "readonly", - MacOsPreferencesPermission::ReadWrite => "readwrite", - MacOsPreferencesPermission::None => "none", - }; - parts.push(format!("macOS preferences {value}")); - } - match &macos.macos_automation { - MacOsAutomationPermission::All => { - parts.push("macOS automation all".to_string()); - } - MacOsAutomationPermission::BundleIds(bundle_ids) => { - if !bundle_ids.is_empty() { - parts.push(format!("macOS automation {}", bundle_ids.join(", "))); - } - } - MacOsAutomationPermission::None => {} - } - if macos.macos_accessibility { - parts.push("macOS accessibility".to_string()); - } - if macos.macos_calendar { - parts.push("macOS calendar".to_string()); - } - if macos.macos_reminders { - parts.push("macOS reminders".to_string()); - } - if !matches!(macos.macos_contacts, MacOsContactsPermission::None) { - let value = match macos.macos_contacts { - MacOsContactsPermission::None => "none", - MacOsContactsPermission::ReadOnly => "readonly", - MacOsContactsPermission::ReadWrite => "readwrite", - }; - parts.push(format!("macOS contacts {value}")); - } - } - if parts.is_empty() { None } else { @@ -892,9 +847,6 @@ mod tests { use super::*; use crate::app_event::AppEvent; use codex_protocol::models::FileSystemPermissions; - use codex_protocol::models::MacOsAutomationPermission; - use codex_protocol::models::MacOsPreferencesPermission; - use codex_protocol::models::MacOsSeatbeltProfileExtensions; use codex_protocol::models::NetworkPermissions; use codex_protocol::protocol::ExecPolicyAmendment; use codex_protocol::protocol::NetworkApprovalProtocol; @@ -1320,7 +1272,6 @@ mod tests { read: Some(vec![absolute_path("/tmp/readme.txt")]), write: Some(vec![absolute_path("/tmp/out.txt")]), }), - ..Default::default() }), }; @@ -1368,7 +1319,6 @@ mod tests { read: Some(vec![absolute_path("/tmp/readme.txt")]), write: Some(vec![absolute_path("/tmp/out.txt")]), }), - ..Default::default() }), }; @@ -1390,42 +1340,6 @@ mod tests { ); } - #[test] - fn additional_permissions_macos_prompt_snapshot() { - let (tx, _rx) = unbounded_channel::(); - let tx = AppEventSender::new(tx); - let exec_request = ApprovalRequest::Exec { - thread_id: ThreadId::new(), - thread_label: None, - id: "test".into(), - command: vec!["osascript".into(), "-e".into(), "tell application".into()], - reason: Some("need macOS automation".into()), - available_decisions: vec![ReviewDecision::Approved, ReviewDecision::Abort], - network_approval_context: None, - additional_permissions: Some(PermissionProfile { - macos: Some(MacOsSeatbeltProfileExtensions { - macos_preferences: MacOsPreferencesPermission::ReadWrite, - macos_automation: MacOsAutomationPermission::BundleIds(vec![ - "com.apple.Calendar".to_string(), - "com.apple.Notes".to_string(), - ]), - macos_launch_services: false, - macos_accessibility: true, - macos_calendar: true, - macos_reminders: true, - macos_contacts: MacOsContactsPermission::None, - }), - ..Default::default() - }), - }; - - let view = ApprovalOverlay::new(exec_request, tx, Features::with_defaults()); - assert_snapshot!( - "approval_overlay_additional_permissions_macos_prompt", - render_overlay_lines(&view, 120) - ); - } - #[test] fn network_exec_prompt_title_includes_host() { let (tx, _rx) = unbounded_channel::(); diff --git a/codex-rs/tui_app_server/src/bottom_pane/snapshots/codex_tui__bottom_pane__approval_overlay__tests__approval_overlay_additional_permissions_macos_prompt.snap b/codex-rs/tui_app_server/src/bottom_pane/snapshots/codex_tui__bottom_pane__approval_overlay__tests__approval_overlay_additional_permissions_macos_prompt.snap deleted file mode 100644 index d9d8717fe..000000000 --- a/codex-rs/tui_app_server/src/bottom_pane/snapshots/codex_tui__bottom_pane__approval_overlay__tests__approval_overlay_additional_permissions_macos_prompt.snap +++ /dev/null @@ -1,18 +0,0 @@ ---- -source: tui/src/bottom_pane/approval_overlay.rs -expression: "render_overlay_lines(&view, 120)" ---- - - Would you like to run the following command? - - Reason: need macOS automation - - Permission rule: macOS preferences readwrite; macOS automation com.apple.Calendar, com.apple.Notes; macOS - accessibility; macOS calendar; macOS reminders - - $ osascript -e 'tell application' - -› 1. Yes, proceed (y) - 2. No, and tell Codex what to do differently (esc) - - Press enter to confirm or esc to cancel diff --git a/codex-rs/tui_app_server/src/bottom_pane/snapshots/codex_tui_app_server__bottom_pane__approval_overlay__tests__approval_overlay_additional_permissions_macos_prompt.snap b/codex-rs/tui_app_server/src/bottom_pane/snapshots/codex_tui_app_server__bottom_pane__approval_overlay__tests__approval_overlay_additional_permissions_macos_prompt.snap deleted file mode 100644 index 7731a880b..000000000 --- a/codex-rs/tui_app_server/src/bottom_pane/snapshots/codex_tui_app_server__bottom_pane__approval_overlay__tests__approval_overlay_additional_permissions_macos_prompt.snap +++ /dev/null @@ -1,18 +0,0 @@ ---- -source: tui_app_server/src/bottom_pane/approval_overlay.rs -expression: "render_overlay_lines(&view, 120)" ---- - - Would you like to run the following command? - - Reason: need macOS automation - - Permission rule: macOS preferences readwrite; macOS automation com.apple.Calendar, com.apple.Notes; macOS - accessibility; macOS calendar; macOS reminders - - $ osascript -e 'tell application' - -› 1. Yes, proceed (y) - 2. No, and tell Codex what to do differently (esc) - - Press enter to confirm or esc to cancel