fix: accept two macOS automation input shapes for approval payload compatibility (#13683)
## Summary
This PR:
1. fixes a deserialization mismatch for macOS automation permissions in
approval payloads by making core parsing accept both supported wire
shapes for bundle IDs.
2. added `#[serde(default)]` to `MacOsSeatbeltProfileExtensions` so
omitted fields deserialize to secure defaults.
## Why this change is needed
`MacOsAutomationPermission` uses `#[serde(try_from =
"MacOsAutomationPermissionDe")]`, so deserialization is controlled by
`MacOsAutomationPermissionDe`. After we aligned v2
`additionalPermissions.macos.automations` to the core shape, approval
payloads started including `{ "bundle_ids": [...] }` in some paths.
`MacOsAutomationPermissionDe` previously accepted only `"none" | "all"`
or a plain array, so object-shaped bundle IDs failed with `data did not
match any variant of untagged enum MacOsAutomationPermissionDe`. This
change restores compatibility by accepting both forms while preserving
existing normalization behavior (trim values and map empty bundle lists
to `None`).
## Validation
saw this error went away when running
```
cargo run -p codex-app-server-test-client -- \
--codex-bin ./target/debug/codex \
-c 'approval_policy="on-request"' \
-c 'features.shell_zsh_fork=true' \
-c 'zsh_path="/tmp/codex-zsh-fork/package/vendor/aarch64-apple-darwin/zsh/macos-15/zsh"' \
send-message-v2 --experimental-api \
'Use $apple-notes and run scripts/notes_info now.'
```
:
```
Error: failed to deserialize ServerRequest from JSONRPCRequest
Caused by:
data did not match any variant of untagged enum MacOsAutomationPermissionDe
```
This commit is contained in:
parent
fb9fcf060f
commit
f9ce403b5a
5 changed files with 121 additions and 25 deletions
|
|
@ -3794,24 +3794,30 @@
|
|||
"MacOsSeatbeltProfileExtensions": {
|
||||
"properties": {
|
||||
"macos_accessibility": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"macos_automation": {
|
||||
"$ref": "#/definitions/MacOsAutomationPermission"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsAutomationPermission"
|
||||
}
|
||||
],
|
||||
"default": "none"
|
||||
},
|
||||
"macos_calendar": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"macos_preferences": {
|
||||
"$ref": "#/definitions/MacOsPreferencesPermission"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsPreferencesPermission"
|
||||
}
|
||||
],
|
||||
"default": "read_only"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"macos_accessibility",
|
||||
"macos_automation",
|
||||
"macos_calendar",
|
||||
"macos_preferences"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpAuthStatus": {
|
||||
|
|
|
|||
|
|
@ -5277,24 +5277,30 @@
|
|||
"MacOsSeatbeltProfileExtensions": {
|
||||
"properties": {
|
||||
"macos_accessibility": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"macos_automation": {
|
||||
"$ref": "#/definitions/MacOsAutomationPermission"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsAutomationPermission"
|
||||
}
|
||||
],
|
||||
"default": "none"
|
||||
},
|
||||
"macos_calendar": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"macos_preferences": {
|
||||
"$ref": "#/definitions/MacOsPreferencesPermission"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsPreferencesPermission"
|
||||
}
|
||||
],
|
||||
"default": "read_only"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"macos_accessibility",
|
||||
"macos_automation",
|
||||
"macos_calendar",
|
||||
"macos_preferences"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpInvocation": {
|
||||
|
|
|
|||
|
|
@ -7412,24 +7412,30 @@
|
|||
"MacOsSeatbeltProfileExtensions": {
|
||||
"properties": {
|
||||
"macos_accessibility": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"macos_automation": {
|
||||
"$ref": "#/definitions/MacOsAutomationPermission"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsAutomationPermission"
|
||||
}
|
||||
],
|
||||
"default": "none"
|
||||
},
|
||||
"macos_calendar": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"macos_preferences": {
|
||||
"$ref": "#/definitions/MacOsPreferencesPermission"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsPreferencesPermission"
|
||||
}
|
||||
],
|
||||
"default": "read_only"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"macos_accessibility",
|
||||
"macos_automation",
|
||||
"macos_calendar",
|
||||
"macos_preferences"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpAuthStatus": {
|
||||
|
|
|
|||
|
|
@ -4550,6 +4550,46 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_execution_request_approval_accepts_macos_automation_bundle_ids_object() {
|
||||
let params = serde_json::from_value::<CommandExecutionRequestApprovalParams>(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"]
|
||||
},
|
||||
"accessibility": false,
|
||||
"calendar": false
|
||||
}
|
||||
},
|
||||
"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),
|
||||
Some(CoreMacOsAutomationPermission::BundleIds(vec![
|
||||
"com.apple.Notes".to_string(),
|
||||
]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_policy_round_trips_external_sandbox_network_access() {
|
||||
let v2_policy = SandboxPolicy::ExternalSandbox {
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ pub enum MacOsAutomationPermission {
|
|||
enum MacOsAutomationPermissionDe {
|
||||
Mode(String),
|
||||
BundleIds(Vec<String>),
|
||||
BundleIdsObject { bundle_ids: Vec<String> },
|
||||
}
|
||||
|
||||
impl TryFrom<MacOsAutomationPermissionDe> for MacOsAutomationPermission {
|
||||
|
|
@ -124,6 +125,7 @@ impl TryFrom<MacOsAutomationPermissionDe> for MacOsAutomationPermission {
|
|||
/// 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) => {
|
||||
|
|
@ -138,7 +140,8 @@ impl TryFrom<MacOsAutomationPermissionDe> for MacOsAutomationPermission {
|
|||
));
|
||||
}
|
||||
}
|
||||
MacOsAutomationPermissionDe::BundleIds(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())
|
||||
|
|
@ -157,6 +160,7 @@ impl TryFrom<MacOsAutomationPermissionDe> for MacOsAutomationPermission {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Hash, Serialize, Deserialize, JsonSchema, TS)]
|
||||
#[serde(default)]
|
||||
pub struct MacOsSeatbeltProfileExtensions {
|
||||
pub macos_preferences: MacOsPreferencesPermission,
|
||||
pub macos_automation: MacOsAutomationPermission,
|
||||
|
|
@ -1421,6 +1425,27 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[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_accessibility: false,
|
||||
macos_calendar: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macos_automation_permission_deserializes_all_and_none() {
|
||||
let all = serde_json::from_str::<MacOsAutomationPermission>("\"all\"")
|
||||
|
|
@ -1432,6 +1457,19 @@ mod tests {
|
|||
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!({
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue