feat(connectors): support managed app tool approval requirements (#21061)

## Why

Managed requirements can already centrally disable apps, but they could
not express the per-tool app approval rules that normal config already
supports. That left admins without a way to enforce connector tool
approvals through `/etc/codex/requirements.toml` or cloud requirements.

## What changed

- Extend app requirements with per-tool `approval_mode` entries.
- Merge managed app tool requirements across managed sources while
preserving higher-precedence exact tool settings.
- Apply managed tool approvals separately from user app config so
managed policy is matched only on raw MCP `tool.name`, while user config
keeps the existing raw-name-then-title convenience fallback.
- Add coverage for local requirements, cloud requirements parsing,
managed-over-user precedence, and a title-collision case that must not
widen managed auto-approval.

## Configuration shape

Local `/etc/codex/requirements.toml` and cloud requirements use the same
TOML shape:

```toml
[apps.connector_123123.tools."calendar/list_events"]
approval_mode = "approve"
```

This is a per-tool approval rule keyed by app ID and raw MCP tool name,
not an app-level boolean such as `apps.connector_123123.approve = true`.
This commit is contained in:
viyatb-oai
2026-05-11 19:08:26 +00:00
committed by GitHub
parent 6506765168
commit d0fa2d81d8
6 changed files with 460 additions and 24 deletions
+35
View File
@@ -829,6 +829,7 @@ mod tests {
use super::*;
use base64::Engine;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use codex_config::AppToolApproval;
use codex_config::types::AuthCredentialsStoreMode;
use codex_login::auth::AgentIdentityAuth;
use codex_login::auth::AgentIdentityAuthRecord;
@@ -1399,6 +1400,40 @@ enabled = false
"connector_5f3c8c41a1e54ad7a76272c89e2554fa".to_string(),
codex_config::AppRequirementToml {
enabled: Some(false),
tools: None,
},
)]),
}),
..Default::default()
})
);
}
#[tokio::test]
async fn fetch_cloud_requirements_parses_apps_tool_requirements_toml() {
let result = parse_for_fetch(Some(
r#"
[apps.connector_5f3c8c41a1e54ad7a76272c89e2554fa.tools."calendar/list_events"]
approval_mode = "approve"
"#,
));
assert_eq!(
result,
Some(ConfigRequirementsToml {
apps: Some(codex_config::AppsRequirementsToml {
apps: BTreeMap::from([(
"connector_5f3c8c41a1e54ad7a76272c89e2554fa".to_string(),
codex_config::AppRequirementToml {
enabled: None,
tools: Some(codex_config::AppToolsRequirementsToml {
tools: BTreeMap::from([(
"calendar/list_events".to_string(),
codex_config::AppToolRequirementToml {
approval_mode: Some(AppToolApproval::Approve),
},
)]),
}),
},
)]),
}),