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:
Michael Bolin
2026-03-26 17:12:45 -07:00
committed by GitHub
parent 44d28f500f
commit e6e2999209
50 changed files with 148 additions and 2269 deletions

View File

@@ -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::<AppEvent>();
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::<AppEvent>();

View File

@@ -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