mirror of
https://github.com/openai/codex.git
synced 2026-05-15 16:53:05 +00:00
Compare commits
3 Commits
mbolin/exe
...
dev/bede/l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8f549d779 | ||
|
|
9c415f4a71 | ||
|
|
6720485f6f |
@@ -45,6 +45,12 @@
|
||||
"launchServices": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"machServices": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"preferences": {
|
||||
"$ref": "#/definitions/MacOsPreferencesPermission"
|
||||
},
|
||||
@@ -58,6 +64,7 @@
|
||||
"calendar",
|
||||
"contacts",
|
||||
"launchServices",
|
||||
"machServices",
|
||||
"preferences",
|
||||
"reminders"
|
||||
],
|
||||
|
||||
@@ -45,6 +45,12 @@
|
||||
"launchServices": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"machServices": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"preferences": {
|
||||
"$ref": "#/definitions/MacOsPreferencesPermission"
|
||||
},
|
||||
@@ -58,6 +64,7 @@
|
||||
"calendar",
|
||||
"contacts",
|
||||
"launchServices",
|
||||
"machServices",
|
||||
"preferences",
|
||||
"reminders"
|
||||
],
|
||||
|
||||
@@ -79,6 +79,15 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"machServices": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"preferences": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@@ -45,6 +45,12 @@
|
||||
"launchServices": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"machServices": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"preferences": {
|
||||
"$ref": "#/definitions/MacOsPreferencesPermission"
|
||||
},
|
||||
@@ -58,6 +64,7 @@
|
||||
"calendar",
|
||||
"contacts",
|
||||
"launchServices",
|
||||
"machServices",
|
||||
"preferences",
|
||||
"reminders"
|
||||
],
|
||||
|
||||
@@ -41,6 +41,12 @@
|
||||
"launchServices": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"machServices": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"preferences": {
|
||||
"$ref": "#/definitions/MacOsPreferencesPermission"
|
||||
},
|
||||
@@ -54,6 +60,7 @@
|
||||
"calendar",
|
||||
"contacts",
|
||||
"launchServices",
|
||||
"machServices",
|
||||
"preferences",
|
||||
"reminders"
|
||||
],
|
||||
@@ -2238,6 +2245,15 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"machServices": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"preferences": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@@ -5,4 +5,4 @@ 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, };
|
||||
export type AdditionalMacOsPermissions = { preferences: MacOsPreferencesPermission, automations: MacOsAutomationPermission, machServices: Array<string>, launchServices: boolean, accessibility: boolean, calendar: boolean, reminders: boolean, contacts: MacOsContactsPermission, };
|
||||
|
||||
@@ -5,4 +5,4 @@ import type { MacOsAutomationPermission } from "../MacOsAutomationPermission";
|
||||
import type { MacOsContactsPermission } from "../MacOsContactsPermission";
|
||||
import type { MacOsPreferencesPermission } from "../MacOsPreferencesPermission";
|
||||
|
||||
export type GrantedMacOsPermissions = { preferences?: MacOsPreferencesPermission, automations?: MacOsAutomationPermission, launchServices?: boolean, accessibility?: boolean, calendar?: boolean, reminders?: boolean, contacts?: MacOsContactsPermission, };
|
||||
export type GrantedMacOsPermissions = { preferences?: MacOsPreferencesPermission, automations?: MacOsAutomationPermission, machServices?: Array<string>, launchServices?: boolean, accessibility?: boolean, calendar?: boolean, reminders?: boolean, contacts?: MacOsContactsPermission, };
|
||||
|
||||
@@ -1021,6 +1021,7 @@ impl From<AdditionalFileSystemPermissions> for CoreFileSystemPermissions {
|
||||
pub struct AdditionalMacOsPermissions {
|
||||
pub preferences: CoreMacOsPreferencesPermission,
|
||||
pub automations: CoreMacOsAutomationPermission,
|
||||
pub mach_services: Vec<String>,
|
||||
pub launch_services: bool,
|
||||
pub accessibility: bool,
|
||||
pub calendar: bool,
|
||||
@@ -1033,6 +1034,7 @@ impl From<CoreMacOsSeatbeltProfileExtensions> for AdditionalMacOsPermissions {
|
||||
Self {
|
||||
preferences: value.macos_preferences,
|
||||
automations: value.macos_automation,
|
||||
mach_services: value.macos_mach_services,
|
||||
launch_services: value.macos_launch_services,
|
||||
accessibility: value.macos_accessibility,
|
||||
calendar: value.macos_calendar,
|
||||
@@ -1047,6 +1049,7 @@ impl From<AdditionalMacOsPermissions> for CoreMacOsSeatbeltProfileExtensions {
|
||||
Self {
|
||||
macos_preferences: value.preferences,
|
||||
macos_automation: value.automations,
|
||||
macos_mach_services: value.mach_services,
|
||||
macos_launch_services: value.launch_services,
|
||||
macos_accessibility: value.accessibility,
|
||||
macos_calendar: value.calendar,
|
||||
@@ -1120,6 +1123,9 @@ pub struct GrantedMacOsPermissions {
|
||||
pub automations: Option<CoreMacOsAutomationPermission>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub mach_services: Option<Vec<String>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub launch_services: Option<bool>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
@@ -1144,6 +1150,7 @@ impl From<GrantedMacOsPermissions> for CoreMacOsSeatbeltProfileExtensions {
|
||||
macos_automation: value
|
||||
.automations
|
||||
.unwrap_or(CoreMacOsAutomationPermission::None),
|
||||
macos_mach_services: value.mach_services.unwrap_or_default(),
|
||||
macos_launch_services: value.launch_services.unwrap_or(false),
|
||||
macos_accessibility: value.accessibility.unwrap_or(false),
|
||||
macos_calendar: value.calendar.unwrap_or(false),
|
||||
@@ -1173,6 +1180,7 @@ impl From<GrantedPermissionProfile> for CorePermissionProfile {
|
||||
let macos = value.macos.and_then(|macos| {
|
||||
if macos.preferences.is_none()
|
||||
&& macos.automations.is_none()
|
||||
&& macos.mach_services.as_ref().is_none_or(Vec::is_empty)
|
||||
&& macos.launch_services.is_none()
|
||||
&& macos.accessibility.is_none()
|
||||
&& macos.calendar.is_none()
|
||||
@@ -5900,6 +5908,7 @@ mod tests {
|
||||
"automations": {
|
||||
"bundle_ids": ["com.apple.Notes"]
|
||||
},
|
||||
"machServices": ["com.vendor.helper"],
|
||||
"launchServices": false,
|
||||
"accessibility": false,
|
||||
"calendar": false,
|
||||
@@ -5918,9 +5927,17 @@ mod tests {
|
||||
params
|
||||
.additional_permissions
|
||||
.and_then(|permissions| permissions.macos)
|
||||
.map(|macos| (macos.automations, macos.launch_services, macos.contacts)),
|
||||
.map(|macos| {
|
||||
(
|
||||
macos.automations,
|
||||
macos.mach_services,
|
||||
macos.launch_services,
|
||||
macos.contacts,
|
||||
)
|
||||
}),
|
||||
Some((
|
||||
CoreMacOsAutomationPermission::BundleIds(vec!["com.apple.Notes".to_string(),]),
|
||||
vec!["com.vendor.helper".to_string()],
|
||||
false,
|
||||
CoreMacOsContactsPermission::ReadOnly,
|
||||
))
|
||||
@@ -5944,6 +5961,7 @@ mod tests {
|
||||
"macos": {
|
||||
"preferences": "read_only",
|
||||
"automations": "none",
|
||||
"machServices": [],
|
||||
"launchServices": false,
|
||||
"accessibility": false,
|
||||
"calendar": false,
|
||||
@@ -6011,6 +6029,7 @@ mod tests {
|
||||
Some(CoreMacOsSeatbeltProfileExtensions {
|
||||
macos_preferences: CoreMacOsPreferencesPermission::ReadOnly,
|
||||
macos_automation: CoreMacOsAutomationPermission::None,
|
||||
macos_mach_services: Vec::new(),
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: false,
|
||||
macos_calendar: false,
|
||||
@@ -6035,6 +6054,26 @@ mod tests {
|
||||
macos_automation: CoreMacOsAutomationPermission::BundleIds(vec![
|
||||
"com.apple.Notes".to_string(),
|
||||
]),
|
||||
macos_mach_services: Vec::new(),
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: false,
|
||||
macos_calendar: false,
|
||||
macos_reminders: false,
|
||||
macos_contacts: CoreMacOsContactsPermission::None,
|
||||
}),
|
||||
),
|
||||
(
|
||||
json!({
|
||||
"machServices": ["com.vendor.helper"],
|
||||
}),
|
||||
Some(GrantedMacOsPermissions {
|
||||
mach_services: Some(vec!["com.vendor.helper".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
Some(CoreMacOsSeatbeltProfileExtensions {
|
||||
macos_preferences: CoreMacOsPreferencesPermission::None,
|
||||
macos_automation: CoreMacOsAutomationPermission::None,
|
||||
macos_mach_services: vec!["com.vendor.helper".to_string()],
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: false,
|
||||
macos_calendar: false,
|
||||
@@ -6053,6 +6092,7 @@ mod tests {
|
||||
Some(CoreMacOsSeatbeltProfileExtensions {
|
||||
macos_preferences: CoreMacOsPreferencesPermission::None,
|
||||
macos_automation: CoreMacOsAutomationPermission::None,
|
||||
macos_mach_services: Vec::new(),
|
||||
macos_launch_services: true,
|
||||
macos_accessibility: false,
|
||||
macos_calendar: false,
|
||||
@@ -6071,6 +6111,7 @@ mod tests {
|
||||
Some(CoreMacOsSeatbeltProfileExtensions {
|
||||
macos_preferences: CoreMacOsPreferencesPermission::None,
|
||||
macos_automation: CoreMacOsAutomationPermission::None,
|
||||
macos_mach_services: Vec::new(),
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: true,
|
||||
macos_calendar: false,
|
||||
@@ -6089,6 +6130,7 @@ mod tests {
|
||||
Some(CoreMacOsSeatbeltProfileExtensions {
|
||||
macos_preferences: CoreMacOsPreferencesPermission::None,
|
||||
macos_automation: CoreMacOsAutomationPermission::None,
|
||||
macos_mach_services: Vec::new(),
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: false,
|
||||
macos_calendar: true,
|
||||
@@ -6107,6 +6149,7 @@ mod tests {
|
||||
Some(CoreMacOsSeatbeltProfileExtensions {
|
||||
macos_preferences: CoreMacOsPreferencesPermission::None,
|
||||
macos_automation: CoreMacOsAutomationPermission::None,
|
||||
macos_mach_services: Vec::new(),
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: false,
|
||||
macos_calendar: false,
|
||||
@@ -6125,6 +6168,7 @@ mod tests {
|
||||
Some(CoreMacOsSeatbeltProfileExtensions {
|
||||
macos_preferences: CoreMacOsPreferencesPermission::None,
|
||||
macos_automation: CoreMacOsAutomationPermission::None,
|
||||
macos_mach_services: Vec::new(),
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: false,
|
||||
macos_calendar: false,
|
||||
|
||||
@@ -983,6 +983,8 @@ 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.
|
||||
|
||||
The macOS permission object supports `preferences`, `automations`, `machServices` (exact global Mach service names for `mach-lookup`), `launchServices`, `accessibility`, `calendar`, `reminders`, and `contacts`.
|
||||
|
||||
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.
|
||||
|
||||
If the session approval policy uses `Granular` with `request_permissions: false`, standalone `request_permissions` tool calls are auto-denied and no `item/permissions/requestApproval` prompt is sent. Inline `with_additional_permissions` command requests remain controlled by `sandbox_approval`, and any previously granted permissions remain sticky for later shell-like calls in the same turn.
|
||||
|
||||
@@ -35,6 +35,8 @@ Seatbelt also supports macOS permission-profile extensions layered on top of
|
||||
enables Apple Events send only to listed bundle IDs.
|
||||
- `macos_launch_services = true`:
|
||||
enables LaunchServices lookups and open/launch operations.
|
||||
- `macos_mach_services = ["com.vendor.helper", ...]`:
|
||||
enables `mach-lookup` for listed global Mach service names.
|
||||
- `macos_accessibility = true`:
|
||||
enables `com.apple.axserver` mach lookup.
|
||||
- `macos_calendar = true`:
|
||||
|
||||
@@ -25,6 +25,10 @@ pub(crate) fn merge_macos_seatbelt_profile_extensions(
|
||||
&base.macos_automation,
|
||||
&permissions.macos_automation,
|
||||
),
|
||||
macos_mach_services: union_string_permissions(
|
||||
&base.macos_mach_services,
|
||||
&permissions.macos_mach_services,
|
||||
),
|
||||
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,
|
||||
@@ -52,6 +56,10 @@ pub(crate) fn intersect_macos_seatbelt_profile_extensions(
|
||||
Some(MacOsSeatbeltProfileExtensions {
|
||||
macos_preferences: requested.macos_preferences.min(granted.macos_preferences),
|
||||
macos_automation,
|
||||
macos_mach_services: intersect_string_permissions(
|
||||
&requested.macos_mach_services,
|
||||
&granted.macos_mach_services,
|
||||
),
|
||||
macos_launch_services: requested.macos_launch_services
|
||||
&& granted.macos_launch_services,
|
||||
macos_accessibility: requested.macos_accessibility && granted.macos_accessibility,
|
||||
@@ -90,6 +98,15 @@ fn union_macos_contacts_permission(
|
||||
}
|
||||
}
|
||||
|
||||
fn union_string_permissions(base: &[String], requested: &[String]) -> Vec<String> {
|
||||
base.iter()
|
||||
.chain(requested.iter())
|
||||
.cloned()
|
||||
.collect::<BTreeSet<_>>()
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Unions two automation permissions by keeping the more permissive result.
|
||||
///
|
||||
/// `All` wins over everything, `None` yields to the other side, and two bundle
|
||||
@@ -149,6 +166,15 @@ fn intersect_macos_automation_permission(
|
||||
}
|
||||
}
|
||||
|
||||
fn intersect_string_permissions(requested: &[String], granted: &[String]) -> Vec<String> {
|
||||
let granted = granted.iter().collect::<BTreeSet<_>>();
|
||||
requested
|
||||
.iter()
|
||||
.filter(|value| granted.contains(value))
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(all(test, target_os = "macos"))]
|
||||
#[path = "macos_permissions_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -17,6 +17,7 @@ fn merge_extensions_widens_permissions() {
|
||||
macos_automation: MacOsAutomationPermission::BundleIds(vec![
|
||||
"com.apple.Calendar".to_string(),
|
||||
]),
|
||||
macos_mach_services: vec!["com.apple.logd".to_string()],
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: false,
|
||||
macos_calendar: false,
|
||||
@@ -29,6 +30,10 @@ fn merge_extensions_widens_permissions() {
|
||||
"com.apple.Notes".to_string(),
|
||||
"com.apple.Calendar".to_string(),
|
||||
]),
|
||||
macos_mach_services: vec![
|
||||
"com.vendor.helper".to_string(),
|
||||
"com.apple.logd".to_string(),
|
||||
],
|
||||
macos_launch_services: true,
|
||||
macos_accessibility: true,
|
||||
macos_calendar: true,
|
||||
@@ -47,6 +52,10 @@ fn merge_extensions_widens_permissions() {
|
||||
"com.apple.Calendar".to_string(),
|
||||
"com.apple.Notes".to_string(),
|
||||
]),
|
||||
macos_mach_services: vec![
|
||||
"com.apple.logd".to_string(),
|
||||
"com.vendor.helper".to_string(),
|
||||
],
|
||||
macos_launch_services: true,
|
||||
macos_accessibility: true,
|
||||
macos_calendar: true,
|
||||
@@ -97,6 +106,7 @@ 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_mach_services: vec!["com.apple.logd".to_string()],
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: true,
|
||||
macos_calendar: true,
|
||||
|
||||
@@ -265,6 +265,7 @@ fn intersect_permission_profiles_preserves_default_macos_grants() {
|
||||
macos_automation: MacOsAutomationPermission::BundleIds(vec![
|
||||
"com.apple.Notes".to_string(),
|
||||
]),
|
||||
macos_mach_services: vec!["com.apple.logd".to_string()],
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: true,
|
||||
macos_calendar: true,
|
||||
@@ -300,6 +301,7 @@ fn normalize_additional_permissions_preserves_macos_permissions() {
|
||||
macos_automation: MacOsAutomationPermission::BundleIds(vec![
|
||||
"com.apple.Notes".to_string(),
|
||||
]),
|
||||
macos_mach_services: vec!["com.apple.logd".to_string()],
|
||||
macos_launch_services: true,
|
||||
macos_accessibility: true,
|
||||
macos_calendar: true,
|
||||
@@ -317,6 +319,7 @@ fn normalize_additional_permissions_preserves_macos_permissions() {
|
||||
macos_automation: MacOsAutomationPermission::BundleIds(vec![
|
||||
"com.apple.Notes".to_string(),
|
||||
]),
|
||||
macos_mach_services: vec!["com.apple.logd".to_string()],
|
||||
macos_launch_services: true,
|
||||
macos_accessibility: true,
|
||||
macos_calendar: true,
|
||||
@@ -385,6 +388,7 @@ fn effective_permissions_merge_macos_extensions_with_additional_permissions() {
|
||||
macos_automation: MacOsAutomationPermission::BundleIds(vec![
|
||||
"com.apple.Calendar".to_string(),
|
||||
]),
|
||||
macos_mach_services: vec!["com.apple.logd".to_string()],
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: false,
|
||||
macos_calendar: false,
|
||||
@@ -401,6 +405,7 @@ fn effective_permissions_merge_macos_extensions_with_additional_permissions() {
|
||||
macos_automation: MacOsAutomationPermission::BundleIds(vec![
|
||||
"com.apple.Notes".to_string(),
|
||||
]),
|
||||
macos_mach_services: vec!["com.vendor.helper".to_string()],
|
||||
macos_launch_services: true,
|
||||
macos_accessibility: true,
|
||||
macos_calendar: true,
|
||||
@@ -419,6 +424,10 @@ fn effective_permissions_merge_macos_extensions_with_additional_permissions() {
|
||||
"com.apple.Calendar".to_string(),
|
||||
"com.apple.Notes".to_string(),
|
||||
]),
|
||||
macos_mach_services: vec![
|
||||
"com.apple.logd".to_string(),
|
||||
"com.vendor.helper".to_string(),
|
||||
],
|
||||
macos_launch_services: true,
|
||||
macos_accessibility: true,
|
||||
macos_calendar: true,
|
||||
|
||||
@@ -26,6 +26,7 @@ fn normalized_extensions(
|
||||
MacOsAutomationPermission::BundleIds(bundle_ids)
|
||||
};
|
||||
}
|
||||
normalized.macos_mach_services = normalize_mach_services(&extensions.macos_mach_services);
|
||||
|
||||
normalized
|
||||
}
|
||||
@@ -96,6 +97,16 @@ pub(crate) fn build_seatbelt_extensions(
|
||||
}
|
||||
}
|
||||
|
||||
if !extensions.macos_mach_services.is_empty() {
|
||||
let services = extensions
|
||||
.macos_mach_services
|
||||
.iter()
|
||||
.map(|service| format!(" (global-name \"{service}\")"))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
clauses.push(format!("(allow mach-lookup\n{services}\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\"))"
|
||||
@@ -178,6 +189,17 @@ fn normalize_bundle_ids(bundle_ids: &[String]) -> Vec<String> {
|
||||
unique.into_iter().collect()
|
||||
}
|
||||
|
||||
fn normalize_mach_services(mach_services: &[String]) -> Vec<String> {
|
||||
let mut unique = BTreeSet::new();
|
||||
for mach_service in mach_services {
|
||||
let candidate = mach_service.trim();
|
||||
if is_valid_mach_service(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;
|
||||
@@ -187,6 +209,15 @@ fn is_valid_bundle_id(bundle_id: &str) -> bool {
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-' || c == '_')
|
||||
}
|
||||
|
||||
fn is_valid_mach_service(mach_service: &str) -> bool {
|
||||
if mach_service.len() < 3 || !mach_service.contains('.') {
|
||||
return false;
|
||||
}
|
||||
mach_service
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-' || c == '_')
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "seatbelt_permissions_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -64,6 +64,22 @@ fn automation_bundle_ids_are_normalized_and_scoped() {
|
||||
assert!(policy.policy.contains("com.apple.coreservices.appleevents"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mach_services_emit_mach_lookup_clauses() {
|
||||
let policy = build_seatbelt_extensions(&MacOsSeatbeltProfileExtensions {
|
||||
macos_mach_services: vec![
|
||||
" com.vendor.helper ".to_string(),
|
||||
"com.apple.logd".to_string(),
|
||||
"bad service".to_string(),
|
||||
"com.apple.logd".to_string(),
|
||||
],
|
||||
..Default::default()
|
||||
});
|
||||
assert!(policy.policy.contains("com.vendor.helper"));
|
||||
assert!(policy.policy.contains("com.apple.logd"));
|
||||
assert!(!policy.policy.contains("bad service"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn launch_services_emit_launch_clauses() {
|
||||
let policy = build_seatbelt_extensions(&MacOsSeatbeltProfileExtensions {
|
||||
|
||||
@@ -202,6 +202,7 @@ fn seatbelt_args_include_macos_permission_extensions() {
|
||||
macos_automation: MacOsAutomationPermission::BundleIds(vec![
|
||||
"com.apple.Notes".to_string(),
|
||||
]),
|
||||
macos_mach_services: vec!["com.apple.logd".to_string()],
|
||||
macos_launch_services: true,
|
||||
macos_accessibility: true,
|
||||
macos_calendar: true,
|
||||
@@ -213,6 +214,7 @@ fn seatbelt_args_include_macos_permission_extensions() {
|
||||
|
||||
assert!(policy.contains("(allow user-preference-write)"));
|
||||
assert!(policy.contains("(appleevent-destination \"com.apple.Notes\")"));
|
||||
assert!(policy.contains("com.apple.logd"));
|
||||
assert!(policy.contains("com.apple.axserver"));
|
||||
assert!(policy.contains("com.apple.CalendarAgent"));
|
||||
}
|
||||
|
||||
@@ -689,6 +689,7 @@ permissions:
|
||||
macos_automation: MacOsAutomationPermission::BundleIds(vec![
|
||||
"com.apple.Notes".to_string(),
|
||||
]),
|
||||
macos_mach_services: Vec::new(),
|
||||
macos_launch_services: true,
|
||||
macos_accessibility: true,
|
||||
macos_calendar: true,
|
||||
@@ -718,6 +719,7 @@ permissions:
|
||||
macos: Some(MacOsSeatbeltProfileExtensions {
|
||||
macos_preferences: MacOsPreferencesPermission::ReadOnly,
|
||||
macos_automation: MacOsAutomationPermission::None,
|
||||
macos_mach_services: Vec::new(),
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: false,
|
||||
macos_calendar: false,
|
||||
@@ -795,6 +797,7 @@ permissions:
|
||||
macos_automation: MacOsAutomationPermission::BundleIds(vec![
|
||||
"com.apple.Notes".to_string()
|
||||
],),
|
||||
macos_mach_services: Vec::new(),
|
||||
macos_launch_services: true,
|
||||
macos_accessibility: true,
|
||||
macos_calendar: true,
|
||||
@@ -844,6 +847,7 @@ permissions:
|
||||
macos_automation: MacOsAutomationPermission::BundleIds(vec![
|
||||
"com.apple.Notes".to_string()
|
||||
],),
|
||||
macos_mach_services: Vec::new(),
|
||||
macos_launch_services: true,
|
||||
macos_accessibility: true,
|
||||
macos_calendar: true,
|
||||
|
||||
@@ -347,6 +347,7 @@ fn shell_request_escalation_execution_is_explicit() {
|
||||
let network_sandbox_policy = NetworkSandboxPolicy::Restricted;
|
||||
let macos_seatbelt_profile_extensions = MacOsSeatbeltProfileExtensions {
|
||||
macos_preferences: MacOsPreferencesPermission::ReadWrite,
|
||||
macos_mach_services: vec!["com.apple.logd".to_string()],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
||||
@@ -499,6 +499,80 @@ fn create_file_system_permissions_schema() -> JsonSchema {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_macos_permissions_schema() -> JsonSchema {
|
||||
JsonSchema::Object {
|
||||
properties: BTreeMap::from([
|
||||
(
|
||||
"preferences".to_string(),
|
||||
JsonSchema::String {
|
||||
description: Some(
|
||||
"macOS preferences access. Supported values: `none`, `read_only`, or `read_write`."
|
||||
.to_string(),
|
||||
),
|
||||
},
|
||||
),
|
||||
(
|
||||
"automations".to_string(),
|
||||
JsonSchema::Array {
|
||||
items: Box::new(JsonSchema::String { description: None }),
|
||||
description: Some(
|
||||
"macOS automation access as app bundle identifiers.".to_string(),
|
||||
),
|
||||
},
|
||||
),
|
||||
(
|
||||
"mach_services".to_string(),
|
||||
JsonSchema::Array {
|
||||
items: Box::new(JsonSchema::String { description: None }),
|
||||
description: Some(
|
||||
"Exact macOS global Mach service names to allow for mach-lookup (for example, `com.vendor.helper`)."
|
||||
.to_string(),
|
||||
),
|
||||
},
|
||||
),
|
||||
(
|
||||
"launch_services".to_string(),
|
||||
JsonSchema::Boolean {
|
||||
description: Some(
|
||||
"Whether to request macOS Launch Services access.".to_string(),
|
||||
),
|
||||
},
|
||||
),
|
||||
(
|
||||
"accessibility".to_string(),
|
||||
JsonSchema::Boolean {
|
||||
description: Some(
|
||||
"Whether to request macOS accessibility access.".to_string(),
|
||||
),
|
||||
},
|
||||
),
|
||||
(
|
||||
"calendar".to_string(),
|
||||
JsonSchema::Boolean {
|
||||
description: Some("Whether to request macOS calendar access.".to_string()),
|
||||
},
|
||||
),
|
||||
(
|
||||
"reminders".to_string(),
|
||||
JsonSchema::Boolean {
|
||||
description: Some("Whether to request macOS reminders access.".to_string()),
|
||||
},
|
||||
),
|
||||
(
|
||||
"contacts".to_string(),
|
||||
JsonSchema::String {
|
||||
description: Some(
|
||||
"macOS contacts access. Supported values: `none`, `read_only`, or `read_write`."
|
||||
.to_string(),
|
||||
),
|
||||
},
|
||||
),
|
||||
]),
|
||||
required: None,
|
||||
additional_properties: Some(false.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_additional_permissions_schema() -> JsonSchema {
|
||||
JsonSchema::Object {
|
||||
properties: BTreeMap::from([
|
||||
@@ -507,6 +581,7 @@ fn create_additional_permissions_schema() -> JsonSchema {
|
||||
"file_system".to_string(),
|
||||
create_file_system_permissions_schema(),
|
||||
),
|
||||
("macos".to_string(), create_macos_permissions_schema()),
|
||||
]),
|
||||
required: None,
|
||||
additional_properties: Some(false.into()),
|
||||
@@ -536,7 +611,7 @@ fn create_approval_parameters(
|
||||
JsonSchema::String {
|
||||
description: Some(
|
||||
if exec_permission_approvals_enabled {
|
||||
"Sandbox permissions for the command. Use \"with_additional_permissions\" to request additional sandboxed filesystem or network permissions (preferred), or \"require_escalated\" to request running without sandbox restrictions; defaults to \"use_default\"."
|
||||
"Sandbox permissions for the command. Use \"with_additional_permissions\" to request additional sandboxed filesystem, network, or macOS permissions (preferred), or \"require_escalated\" to request running without sandbox restrictions; defaults to \"use_default\"."
|
||||
} else {
|
||||
"Sandbox permissions for the command. Set to \"require_escalated\" to request running without sandbox restrictions; defaults to \"use_default\"."
|
||||
}
|
||||
|
||||
@@ -2269,7 +2269,7 @@ fn shell_tool_with_request_permission_includes_additional_permissions() {
|
||||
panic!("expected sandbox_permissions description");
|
||||
};
|
||||
assert!(description.contains("with_additional_permissions"));
|
||||
assert!(description.contains("filesystem or network permissions"));
|
||||
assert!(description.contains("macOS permissions"));
|
||||
|
||||
let Some(JsonSchema::Object {
|
||||
properties: additional_properties,
|
||||
@@ -2280,7 +2280,25 @@ fn shell_tool_with_request_permission_includes_additional_permissions() {
|
||||
};
|
||||
assert!(additional_properties.contains_key("network"));
|
||||
assert!(additional_properties.contains_key("file_system"));
|
||||
assert!(!additional_properties.contains_key("macos"));
|
||||
assert!(additional_properties.contains_key("macos"));
|
||||
|
||||
let Some(JsonSchema::Object {
|
||||
properties: macos_properties,
|
||||
additional_properties,
|
||||
..
|
||||
}) = additional_properties.get("macos")
|
||||
else {
|
||||
panic!("expected macos object");
|
||||
};
|
||||
assert_eq!(additional_properties, &Some(false.into()));
|
||||
assert!(macos_properties.contains_key("preferences"));
|
||||
assert!(macos_properties.contains_key("automations"));
|
||||
assert!(macos_properties.contains_key("mach_services"));
|
||||
assert!(macos_properties.contains_key("launch_services"));
|
||||
assert!(macos_properties.contains_key("accessibility"));
|
||||
assert!(macos_properties.contains_key("calendar"));
|
||||
assert!(macos_properties.contains_key("reminders"));
|
||||
assert!(macos_properties.contains_key("contacts"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -197,6 +197,8 @@ pub struct MacOsSeatbeltProfileExtensions {
|
||||
pub macos_preferences: MacOsPreferencesPermission,
|
||||
#[serde(alias = "automations")]
|
||||
pub macos_automation: MacOsAutomationPermission,
|
||||
#[serde(alias = "mach_services")]
|
||||
pub macos_mach_services: Vec<String>,
|
||||
#[serde(alias = "launch_services")]
|
||||
pub macos_launch_services: bool,
|
||||
#[serde(alias = "accessibility")]
|
||||
@@ -1657,6 +1659,7 @@ mod tests {
|
||||
macos_automation: MacOsAutomationPermission::BundleIds(vec![
|
||||
"com.apple.Notes".to_string(),
|
||||
]),
|
||||
macos_mach_services: Vec::new(),
|
||||
macos_launch_services: true,
|
||||
macos_accessibility: true,
|
||||
macos_calendar: true,
|
||||
@@ -1684,6 +1687,7 @@ mod tests {
|
||||
macos: Some(MacOsSeatbeltProfileExtensions {
|
||||
macos_preferences: MacOsPreferencesPermission::ReadOnly,
|
||||
macos_automation: MacOsAutomationPermission::None,
|
||||
macos_mach_services: Vec::new(),
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: false,
|
||||
macos_calendar: false,
|
||||
@@ -1709,6 +1713,7 @@ mod tests {
|
||||
macos_automation: MacOsAutomationPermission::BundleIds(vec![
|
||||
"com.apple.Notes".to_string(),
|
||||
]),
|
||||
macos_mach_services: Vec::new(),
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: false,
|
||||
macos_calendar: false,
|
||||
@@ -1724,6 +1729,7 @@ mod tests {
|
||||
serde_json::from_value::<MacOsSeatbeltProfileExtensions>(serde_json::json!({
|
||||
"preferences": "read_write",
|
||||
"automations": ["com.apple.Notes"],
|
||||
"mach_services": ["com.vendor.helper"],
|
||||
"launch_services": true,
|
||||
"accessibility": true,
|
||||
"calendar": true,
|
||||
@@ -1739,6 +1745,7 @@ mod tests {
|
||||
macos_automation: MacOsAutomationPermission::BundleIds(vec![
|
||||
"com.apple.Notes".to_string(),
|
||||
]),
|
||||
macos_mach_services: vec!["com.vendor.helper".to_string()],
|
||||
macos_launch_services: true,
|
||||
macos_accessibility: true,
|
||||
macos_calendar: true,
|
||||
@@ -1748,6 +1755,35 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macos_seatbelt_profile_extensions_deserializes_mach_services() {
|
||||
let permissions =
|
||||
serde_json::from_value::<MacOsSeatbeltProfileExtensions>(serde_json::json!({
|
||||
"mach_services": [
|
||||
"com.vendor.helper",
|
||||
"com.apple.logd",
|
||||
]
|
||||
}))
|
||||
.expect("deserialize macos mach services");
|
||||
|
||||
assert_eq!(
|
||||
permissions,
|
||||
MacOsSeatbeltProfileExtensions {
|
||||
macos_preferences: MacOsPreferencesPermission::ReadOnly,
|
||||
macos_automation: MacOsAutomationPermission::None,
|
||||
macos_mach_services: vec![
|
||||
"com.vendor.helper".to_string(),
|
||||
"com.apple.logd".to_string(),
|
||||
],
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: false,
|
||||
macos_calendar: false,
|
||||
macos_reminders: false,
|
||||
macos_contacts: MacOsContactsPermission::None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macos_automation_permission_deserializes_all_and_none() {
|
||||
let all = serde_json::from_str::<MacOsAutomationPermission>("\"all\"")
|
||||
|
||||
@@ -11,6 +11,14 @@ When you need extra sandboxed permissions for one command, use:
|
||||
- `network.enabled`: set to `true` to enable network access
|
||||
- `file_system.read`: list of paths that need read access
|
||||
- `file_system.write`: list of paths that need write access
|
||||
- `macos.preferences`: `read_only` or `read_write`
|
||||
- `macos.automations`: list of bundle IDs that need Apple Events access
|
||||
- `macos.mach_services`: list of exact global Mach service names that need `mach-lookup` (for example, `com.vendor.helper`)
|
||||
- `macos.launch_services`: set to `true` to allow Launch Services access
|
||||
- `macos.accessibility`: set to `true` to allow accessibility APIs
|
||||
- `macos.calendar`: set to `true` to allow Calendar access
|
||||
- `macos.reminders`: set to `true` to allow Reminders access
|
||||
- `macos.contacts`: `read_only` or `read_write`
|
||||
|
||||
When using the `request_permissions` tool directly, only request `network` and `file_system` permissions.
|
||||
|
||||
|
||||
@@ -800,6 +800,12 @@ pub(crate) fn format_additional_permissions_rule(
|
||||
}
|
||||
MacOsAutomationPermission::None => {}
|
||||
}
|
||||
if !macos.macos_mach_services.is_empty() {
|
||||
parts.push(format!(
|
||||
"macOS mach services {}",
|
||||
macos.macos_mach_services.join(", ")
|
||||
));
|
||||
}
|
||||
if macos.macos_accessibility {
|
||||
parts.push("macOS accessibility".to_string());
|
||||
}
|
||||
@@ -1423,6 +1429,7 @@ mod tests {
|
||||
"com.apple.Calendar".to_string(),
|
||||
"com.apple.Notes".to_string(),
|
||||
]),
|
||||
macos_mach_services: vec!["com.vendor.helper".to_string()],
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: true,
|
||||
macos_calendar: true,
|
||||
|
||||
@@ -7,8 +7,8 @@ expression: "render_overlay_lines(&view, 120)"
|
||||
|
||||
Reason: need macOS automation
|
||||
|
||||
Permission rule: macOS preferences readwrite; macOS automation com.apple.Calendar, com.apple.Notes; macOS
|
||||
accessibility; macOS calendar; macOS reminders
|
||||
Permission rule: macOS preferences readwrite; macOS automation com.apple.Calendar, com.apple.Notes; macOS mach
|
||||
services com.vendor.helper; macOS accessibility; macOS calendar; macOS reminders
|
||||
|
||||
$ osascript -e 'tell application'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user