Restrict MCP OAuth authorization endpoints to HTTP(S) (#47326)

## What changed

Reject authorization endpoints with schemes other than `http` or `https` before client registration or authorization URL return. Document this constraint for `mcpServer/oauth/login`.

## Testing

Make existing asynchronous tests wait for cleanup: wait for `ThreadIdle` before injecting user input in the guardian cached-score test, and poll for queue consumption before asserting that dispatched items are gone.

GitOrigin-RevId: e5f6b4c5556234c3cc2aa56910486e892c011116
This commit is contained in:
viyatb-oai
2026-09-22 17:31:02 +00:00
committed by copyberry
parent 8a577399c7
commit 2c2a42e65d
4 changed files with 33 additions and 5 deletions
@@ -57,6 +57,7 @@ use codex_protocol::protocol::ReviewDecision;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::TruncationPolicy;
use codex_protocol::security_risk::SecurityRiskScore;
use core_test_support::ThreadIdle;
use core_test_support::responses;
use core_test_support::responses::WebSocketConnectionConfig;
use core_test_support::responses::ev_assistant_message;
@@ -853,7 +854,10 @@ async fn sample_configured_conversation_history_with_source(
"{guardian_config}\n[features.guardianv2.review_scope]\ncomputer_use_only = false\n"
);
let has_model_defaults = model_defaults.is_some();
let mut extensions = ExtensionRegistryBuilder::new();
extensions.thread_lifecycle_contributor(Arc::new(ThreadIdle));
let builder = test_codex()
.with_extensions(Arc::new(extensions.build()))
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
.with_model_info_override("codex-auto-review", |model_info| {
model_info
@@ -2376,6 +2380,8 @@ async fn cached_score_survives_compaction_and_internal_context_but_not_user_inpu
matches!(event, EventMsg::TurnComplete(_))
})
.await;
// TurnComplete precedes active-turn cleanup; wait before injecting user input.
ThreadIdle::wait(&test.codex).await;
assert_eq!(
cached_approval(
&registry,
+18 -4
View File
@@ -576,6 +576,21 @@ async fn advance_queue_poll() {
tokio::time::resume();
}
async fn wait_for_queue_empty(
queue: &QueuedItemService,
thread_id: ThreadId,
) -> anyhow::Result<()> {
// A turn can complete before the dispatcher finishes deleting its queued item.
tokio::time::timeout(Duration::from_secs(/*secs*/ 10), async {
while !queue.list(thread_id).await?.is_empty() {
tokio::time::sleep(Duration::from_millis(/*millis*/ 10)).await;
}
anyhow::Ok(())
})
.await
.context("queued item was not consumed")?
}
#[tokio::test]
async fn externally_changed_queues_dispatch_independently_and_retry_failed_wakes()
-> anyhow::Result<()> {
@@ -662,10 +677,9 @@ async fn externally_changed_queues_dispatch_independently_and_retry_failed_wakes
Duration::from_secs(/*secs*/ 25),
)
.await;
advance_queue_poll().await;
assert!(queue.list(thread_id).await?.is_empty());
assert!(queue.list(independent_thread.thread_id).await?.is_empty());
wait_for_queue_empty(&queue, thread_id).await?;
wait_for_queue_empty(&queue, independent_thread.thread_id).await?;
let rollout_path = test.codex.rollout_path().context("rollout path missing")?;
test.codex.shutdown_and_wait().await?;
@@ -691,7 +705,7 @@ async fn externally_changed_queues_dispatch_independently_and_retry_failed_wakes
Duration::from_secs(/*secs*/ 25),
)
.await;
assert!(queue.list(thread_id).await?.is_empty());
wait_for_queue_empty(&queue, thread_id).await?;
let prompts = model_responses
.requests()