Add strict automatic review for MCP elicitations (#36365)

## What changed

- Recognize the `codex_strict_auto_review` MCP elicitation marker and route
  marked approval requests through the configured automatic reviewer.
- Accept only canonical automatic-review approvals and fail closed without a
  user prompt when review is unavailable, denied, malformed, disallowed by
  policy, or requests persistence.
- Validate Codex Apps approval metadata against the active MCP invocation and
  build the review request from the trusted invocation details.

## Testing

- Add unit coverage for strict review decisions, malformed markers, reviewer
  failures, explicit policy denials, and lifecycle cleanup.
- Add app-server round-trip coverage for approvals, denials, configuration
  constraints, spoofed metadata, persistent requests, and subsequent turns.

GitOrigin-RevId: af73b45a9b63118f25f6429d95b3222c5dbb59fb
This commit is contained in:
Ankush Gupta authored and copyberry committed 2026-07-31 17:18:08 +00:00
1 parent 0d109f097c
commit 287e1020ae
8 files changed
+773 -27

No files matched your search

+9 -7
View File
@@ -211,7 +211,7 @@ pub(crate) async fn handle_mcp_tool_call(
.unwrap_or_else(|| JsonValue::Object(serde_json::Map::new())),
};
}
sess.register_mcp_tool_approval_metadata(turn_context, &call_id, metadata.clone())
sess.register_mcp_tool_approval_metadata(turn_context, &call_id, &invocation, metadata.clone())
.await;
notify_mcp_tool_call_started(
sess.as_ref(),
@@ -1047,7 +1047,7 @@ impl McpToolApprovalPolicy {
#[derive(Clone)]
pub(crate) struct McpToolApprovalMetadata {
annotations: Option<ToolAnnotations>,
connector_id: Option<String>,
pub(crate) connector_id: Option<String>,
link_id: Option<String>,
connector_name: Option<String>,
connector_description: Option<String>,
@@ -1065,6 +1065,7 @@ impl Session {
&self,
turn_context: &TurnContext,
call_id: &str,
invocation: &McpInvocation,
metadata: McpToolApprovalMetadata,
) {
let Some(turn_state) = self
@@ -1074,17 +1075,18 @@ impl Session {
else {
return;
};
turn_state
.lock()
.await
.insert_mcp_tool_approval_metadata(call_id.to_string(), metadata);
turn_state.lock().await.insert_mcp_tool_approval_metadata(
call_id.to_string(),
(invocation.server == CODEX_APPS_MCP_SERVER_NAME).then(|| invocation.clone()),
metadata,
);
}
pub(crate) async fn mcp_tool_approval_metadata(
&self,
sub_id: &str,
call_id: &str,
) -> Option<McpToolApprovalMetadata> {
) -> Option<(Option<McpInvocation>, McpToolApprovalMetadata)> {
let turn_state = self
.input_queue
.turn_state_for_sub_id(&self.active_turn, sub_id)