mirror of
https://github.com/openai/codex.git
synced 2026-04-29 17:06:51 +00:00
permissions: remove macOS seatbelt extension profiles (#15918)
## Why `PermissionProfile` should only describe the per-command permissions we still want to grant dynamically. Keeping `MacOsSeatbeltProfileExtensions` in that surface forced extra macOS-only approval, protocol, schema, and TUI branches for a capability we no longer want to expose. ## What changed - Removed the macOS-specific permission-profile types from `codex-protocol`, the app-server v2 API, and the generated schema/TypeScript artifacts. - Deleted the core and sandboxing plumbing that threaded `MacOsSeatbeltProfileExtensions` through execution requests and seatbelt construction. - Simplified macOS seatbelt generation so it always includes the fixed read-only preferences allowlist instead of carrying a configurable profile extension. - Removed the macOS additional-permissions UI/docs/test coverage and deleted the obsolete macOS permission modules. - Tightened `request_permissions` intersection handling so explicitly empty requested read lists are preserved only when that field was actually granted, avoiding zero-grant responses being stored as active permissions.
This commit is contained in:
@@ -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<MacOsSeatbeltProfileExtensions>,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
|
||||
@@ -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<String>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum MacOsAutomationPermissionDe {
|
||||
Mode(String),
|
||||
BundleIds(Vec<String>),
|
||||
BundleIdsObject { bundle_ids: Vec<String> },
|
||||
}
|
||||
|
||||
impl TryFrom<MacOsAutomationPermissionDe> 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<Self, Self::Error> {
|
||||
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::<Vec<String>>();
|
||||
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<NetworkPermissions>,
|
||||
pub file_system: Option<FileSystemPermissions>,
|
||||
pub macos: Option<MacOsSeatbeltProfileExtensions>,
|
||||
}
|
||||
|
||||
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::<MacOsPreferencesPermission>("\"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::<PermissionProfile>(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::<PermissionProfile>(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::<MacOsSeatbeltProfileExtensions>(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::<MacOsSeatbeltProfileExtensions>(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::<MacOsAutomationPermission>("\"all\"")
|
||||
.expect("deserialize all automation permission");
|
||||
let none = serde_json::from_str::<MacOsAutomationPermission>("\"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::<MacOsAutomationPermission>(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!({
|
||||
|
||||
@@ -32,7 +32,6 @@ impl From<RequestPermissionProfile> for PermissionProfile {
|
||||
Self {
|
||||
network: value.network,
|
||||
file_system: value.file_system,
|
||||
macos: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user