Files
codex/codex-rs/app-server-protocol/src/export_user_verification.rs
T
riley-oai 555b82afa9 Add opt-in MCP user-verification transport (#43352)
## Why

Device-authenticated verification needs an app response outside automated approval and review. Previously, MCP user-verification requests were always cancelled.

## What changed

- Add typed `openai/userVerification` elicitations carrying a title, description, and challenge through core and app-server.
- Route verification to one connection enabled by trusted host activation. Restrict responses to that owner, exclude requests from replay, and cancel on disconnect or authentication changes, including account switches away and back.
- Return proofs in `content`, discard response metadata, and cancel malformed acceptances. Suppress verification response logging and analytics, and send only a generic verification notice to realtime.
- Keep the mode experimental and exclude it from stable schema exports. Capability advertisement remains disabled, and the TUI cancels verification requests.

## Testing

Add coverage for owner isolation, disconnect and authentication races, proof response handling, automated-review bypass, inactive-host cancellation, stable schema filtering, and realtime payload exclusion. Update the MCP integration test to verify that an activated request holds the tool until the client responds.

GitOrigin-RevId: c8c7b6691a3f7756682bbcca9def1df0d1feb906
2026-09-07 04:33:22 +00:00

58 lines
1.8 KiB
Rust

//! Removes the opt-in verification mode from the flattened stable elicitation union.
//!
//! `ExperimentalApi` enum annotations currently drive runtime checks only. Keep this new mode
//! out of stable exports without changing the export behavior of existing gated enum variants.
use super::extract_discriminator_from_arm;
use super::split_top_level;
use super::split_type_alias;
use serde_json::Value;
const MODE: &str = "openai/userVerification";
pub(super) fn filter_ts(content: &str) -> String {
let Some((prefix, body, suffix)) = split_type_alias(content) else {
return content.to_string();
};
let parts = split_top_level(&body, '&')
.into_iter()
.map(|part| {
let Some(union) = part
.strip_prefix('(')
.and_then(|part| part.strip_suffix(')'))
else {
return part;
};
let arms = split_top_level(union, '|')
.into_iter()
.filter(|arm| extract_discriminator_from_arm(arm, "mode").as_deref() != Some(MODE))
.collect::<Vec<_>>();
format!("({})", arms.join(" | "))
})
.collect::<Vec<_>>();
format!("{prefix} {}{suffix}", parts.join(" & "))
}
pub(super) fn filter_json(value: &mut Value) {
match value {
Value::Array(items) => {
items.retain(|item| {
item.pointer("/properties/mode/enum") != Some(&serde_json::json!([MODE]))
});
for item in items {
filter_json(item);
}
}
Value::Object(map) => {
for entry in map.values_mut() {
filter_json(entry);
}
}
Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {}
}
}
#[cfg(test)]
#[path = "export_user_verification_tests.rs"]
mod tests;