Propagate authentication header failures before sending requests (#55607)

## Why

Synchronous auth header construction could silently return no credentials when managed authentication became unavailable. Callers need an error to avoid sending requests with missing or partial authentication.

## What changed

- Make `AuthProvider::add_auth_headers`, `to_auth_headers`, and auth header telemetry fallible, and propagate failures through request and WebSocket setup.
- Return a transient error when managed authentication is unavailable, and preserve network policy denials through auth error conversion.
- Retry transient MCP authentication errors while treating build errors and policy denials as terminal.
- Cancel local MCP event streams before resolving authentication so cleanup still works when credentials become unavailable.

## Testing

Add regression coverage for rejected partial authentication, async header resolution, terminal auth errors without sending requests, managed account changes, MCP retry classification, and local stream cancellation after authentication becomes unavailable.

GitOrigin-RevId: 39d981e232cba1e302de502a2fa6239101114ed1
This commit is contained in:
cooper-oai
2026-10-20 15:43:19 +00:00
committed by copyberry
parent 78d4c7c7fa
commit f29d55ff06
61 changed files with 536 additions and 252 deletions
+3 -1
View File
@@ -43,7 +43,9 @@ struct AwsSigV4AuthProvider {
}
impl AuthProvider for AwsSigV4AuthProvider {
fn add_auth_headers(&self, _headers: &mut HeaderMap) {}
fn add_auth_headers(&self, _headers: &mut HeaderMap) -> Result<(), AuthError> {
Ok(())
}
fn apply_auth(&self, mut request: Request) -> codex_api::AuthProviderFuture<'_> {
Box::pin(async move {
+14 -21
View File
@@ -16,7 +16,6 @@ use std::time::SystemTime;
use std::time::UNIX_EPOCH;
use codex_api::AuthError;
use codex_api::AuthHeadersFuture;
use codex_api::AuthProvider;
use http::HeaderMap;
use http::HeaderValue;
@@ -107,26 +106,20 @@ impl ExecutorCredentialProvider {
}
impl AuthProvider for ExecutorCredentialProvider {
// Only the fallible API can prevent an unauthenticated registry request.
fn add_auth_headers(&self, _headers: &mut HeaderMap) {}
fn resolve_auth_headers(&self) -> AuthHeadersFuture<'_> {
Box::pin(async {
let token = self
.current
.borrow()
.header()
.map_err(|error| AuthError::Build(error.to_string()))?;
let token = token
.to_str()
.map_err(|_| AuthError::Build(CredentialError::InvalidFrame.to_string()))?;
let mut authorization = HeaderValue::from_str(&format!("Bearer {token}"))
.map_err(|_| AuthError::Build(CredentialError::InvalidFrame.to_string()))?;
authorization.set_sensitive(true);
let mut headers = HeaderMap::new();
headers.insert(AUTHORIZATION, authorization);
Ok(headers)
})
fn add_auth_headers(&self, headers: &mut HeaderMap) -> Result<(), AuthError> {
let token = self
.current
.borrow()
.header()
.map_err(|error| AuthError::Build(error.to_string()))?;
let token = token
.to_str()
.map_err(|_| AuthError::Build(CredentialError::InvalidFrame.to_string()))?;
let mut authorization = HeaderValue::from_str(&format!("Bearer {token}"))
.map_err(|_| AuthError::Build(CredentialError::InvalidFrame.to_string()))?;
authorization.set_sensitive(true);
headers.insert(AUTHORIZATION, authorization);
Ok(())
}
}
@@ -43,7 +43,7 @@ async fn waits_for_initial_rotation_replaces_token_and_eof_clears_auth() {
Err(CredentialError::InitialTimeout)
);
assert!(provider.resolve_auth_headers().await.is_err());
assert!(provider.to_auth_headers().is_empty());
assert!(provider.to_auth_headers().is_err());
writer
.write_all(&frame("env-a", "first-scoped-token", expires))
.expect("write");