mirror of
https://github.com/openai/codex.git
synced 2026-10-21 17:48:44 +00:00
## What changed - Centralize the allowlist for bundled MCP cleanup hooks and use it for both local and executor-discovered plugins, including `unified-computer-use` cleanup through `cua_repl`. - Mark matching cleanup hooks as trusted built-ins so they run without saved hook trust and remain active when regular hooks or their per-hook state are disabled. Plugin enablement and managed-only policy still apply. - Hide built-in cleanup hooks from hook listings and lifecycle notifications while retaining their metrics. Keep the built-in classification out of serialized protocol data. ## Testing - Cover allowlist boundaries, trust and enablement behavior, inline and file-based hook declarations, MCP success and error responses, hook listing, lifecycle notifications, metrics, and protocol serialization. GitOrigin-RevId: f93b7bc99f4ed9694f529def8ec383b45f31430e
153 lines
4.6 KiB
Rust
153 lines
4.6 KiB
Rust
//! Temporary cleanup-hook allowlist shared by local and executor plugin discovery.
|
|
//! This narrowly authorizes known MCP cleanup calls; it does not verify plugin signatures.
|
|
|
|
use codex_config::HookHandlerConfig;
|
|
use codex_protocol::protocol::HookEventName;
|
|
|
|
struct BundledHook {
|
|
plugin_id: &'static str,
|
|
events: &'static [HookEventName],
|
|
target: BundledHookTarget,
|
|
}
|
|
|
|
enum BundledHookTarget {
|
|
McpServer {
|
|
server: &'static str,
|
|
tool: &'static str,
|
|
},
|
|
App {
|
|
server: &'static str,
|
|
connector_id: &'static str,
|
|
tool: &'static str,
|
|
},
|
|
}
|
|
|
|
// Keep unsigned plugin exceptions together so they can be removed as signing lands.
|
|
const ALLOWLISTED_BUNDLED_HOOKS: &[BundledHook] = &[
|
|
BundledHook {
|
|
plugin_id: "browser@openai-bundled",
|
|
events: &[
|
|
HookEventName::Stop,
|
|
HookEventName::Interrupt,
|
|
HookEventName::SubagentStop,
|
|
],
|
|
target: BundledHookTarget::McpServer {
|
|
server: "node_repl",
|
|
tool: "turn_ended",
|
|
},
|
|
},
|
|
BundledHook {
|
|
plugin_id: "chrome@openai-bundled",
|
|
events: &[
|
|
HookEventName::Stop,
|
|
HookEventName::Interrupt,
|
|
HookEventName::SubagentStop,
|
|
],
|
|
target: BundledHookTarget::McpServer {
|
|
server: "node_repl",
|
|
tool: "turn_ended",
|
|
},
|
|
},
|
|
BundledHook {
|
|
plugin_id: "chrome-dev@openai-bundled",
|
|
events: &[
|
|
HookEventName::Stop,
|
|
HookEventName::Interrupt,
|
|
HookEventName::SubagentStop,
|
|
],
|
|
target: BundledHookTarget::McpServer {
|
|
server: "node_repl",
|
|
tool: "turn_ended",
|
|
},
|
|
},
|
|
BundledHook {
|
|
plugin_id: "chrome-internal@openai-bundled",
|
|
events: &[
|
|
HookEventName::Stop,
|
|
HookEventName::Interrupt,
|
|
HookEventName::SubagentStop,
|
|
],
|
|
target: BundledHookTarget::McpServer {
|
|
server: "node_repl",
|
|
tool: "turn_ended",
|
|
},
|
|
},
|
|
BundledHook {
|
|
plugin_id: "computer-use@openai-bundled",
|
|
events: &[
|
|
HookEventName::Stop,
|
|
HookEventName::Interrupt,
|
|
HookEventName::SubagentStop,
|
|
],
|
|
target: BundledHookTarget::McpServer {
|
|
server: "node_repl",
|
|
tool: "turn_ended",
|
|
},
|
|
},
|
|
BundledHook {
|
|
plugin_id: "unified-computer-use@openai-bundled",
|
|
events: &[
|
|
HookEventName::Stop,
|
|
HookEventName::Interrupt,
|
|
HookEventName::SubagentStop,
|
|
],
|
|
target: BundledHookTarget::McpServer {
|
|
server: "cua_repl",
|
|
tool: "turn_ended",
|
|
},
|
|
},
|
|
BundledHook {
|
|
plugin_id: "browser@openai-curated-remote",
|
|
events: &[HookEventName::Stop, HookEventName::SubagentStop],
|
|
target: BundledHookTarget::App {
|
|
server: "codex_apps",
|
|
connector_id: "connector_openai_browser",
|
|
tool: "browser.turn_ended",
|
|
},
|
|
},
|
|
];
|
|
|
|
/// Matches a temporary unsigned cleanup exception. App targets additionally require the
|
|
/// connector identity for this handler's server and tool from the caller's enabled tool
|
|
/// catalog; callers without that catalog must pass `None`.
|
|
pub fn is_allowlisted_bundled_cleanup_hook(
|
|
plugin_id: &str,
|
|
event: HookEventName,
|
|
matcher: Option<&str>,
|
|
handler: &HookHandlerConfig,
|
|
app_connector_id: Option<&str>,
|
|
) -> bool {
|
|
let HookHandlerConfig::McpTool {
|
|
server,
|
|
tool,
|
|
input,
|
|
..
|
|
} = handler
|
|
else {
|
|
return false;
|
|
};
|
|
|
|
matcher.is_none()
|
|
&& ALLOWLISTED_BUNDLED_HOOKS.iter().any(|hook| {
|
|
hook.plugin_id == plugin_id
|
|
&& hook.events.contains(&event)
|
|
&& match hook.target {
|
|
BundledHookTarget::McpServer {
|
|
server: expected_server,
|
|
tool: expected_tool,
|
|
} => server == expected_server && tool == expected_tool,
|
|
BundledHookTarget::App {
|
|
server: expected_server,
|
|
connector_id,
|
|
tool: expected_tool,
|
|
} => {
|
|
// Raw Apps tool names can collide; require the registered connector.
|
|
server == expected_server
|
|
&& tool == expected_tool
|
|
&& input.is_empty()
|
|
&& app_connector_id == Some(connector_id)
|
|
}
|
|
}
|
|
})
|
|
}
|