mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
## Why The cleanup-hook allowlist for `browser@openai-curated-remote` accepted only `Stop` and `SubagentStop`, leaving its cleanup hook unavailable when a turn was interrupted. ## What changed Allow `Interrupt` to invoke `browser.turn_ended` through `codex_apps`, retaining the registered browser connector and empty-input requirements. ## Testing Extend hook metadata tests to cover interrupts, rejecting mismatched connectors and manifest-provided arguments. Extend executor integration coverage to interrupt an active turn and verify that browser app and computer-use cleanup calls use their separate MCP routes. GitOrigin-RevId: 355801f9ca3086bae80d1e623d0be7e1772a39e1
157 lines
4.7 KiB
Rust
157 lines
4.7 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::Interrupt,
|
|
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)
|
|
}
|
|
}
|
|
})
|
|
}
|