Files
codex/codex-rs/http-client/src/lib.rs
T
jif e742db9f7b Preserve parent routing affinity for Guardian reviews (#57710)
## Why

Shared routing cookies can change after a parent connection is established. Guardian reviews need the routing affinity captured with their parent response so unrelated traffic does not replace that hint.

## What changed

- Capture `__oailb` and `__cflb` cookies from successful ChatGPT WebSocket handshakes and pair them with the parent response ID.
- Retain the snapshot throughout a review and its retries, and apply it only to Guardian requests with the matching parent response. Reconnect cached reviewer sockets when the parent affinity changes.
- Preserve cookie expiry and restrict reuse to the original secure origin and endpoint path. Replace routing cookies while retaining unrelated cookies, and keep the captured affinity out of prompts, history, and serialized metadata.

## Testing

Add coverage for cookie scope, expiry, duplicate rejection, and merging; replaying a captured pin after shared cookies change or are deleted; and retaining the matching parent snapshot through compaction without exposing cookies in review prompts or metadata.

GitOrigin-RevId: d180679e6265b612a3617f20ca1f5dec89e73aa8
2026-10-28 11:48:00 +00:00

122 lines
4.6 KiB
Rust

use url::Host;
use url::Url;
mod chatgpt_cloudflare_cookies;
mod chatgpt_hosts;
mod chatgpt_routing_affinity;
mod client;
mod client_builder;
mod client_tls;
mod custom_ca;
mod error;
mod native_roots;
mod network_policy;
mod openai_endpoints;
mod openai_headers;
mod outbound_proxy;
mod request;
mod request_builder;
mod request_draft;
mod response;
mod retry_after;
mod route_aware_client_pool;
mod route_aware_redirect;
mod tls_backend_fallback;
mod transport;
pub use crate::chatgpt_cloudflare_cookies::with_chatgpt_cloudflare_cookie_store;
pub use crate::chatgpt_hosts::is_allowed_chatgpt_host;
pub use crate::chatgpt_routing_affinity::ChatGptRoutingAffinity;
pub use crate::client::HttpClient;
pub use crate::client::HttpError;
pub use crate::client_builder::HttpClientBuilder;
pub use crate::client_tls::HttpClientTlsConfig;
pub use crate::custom_ca::BuildCustomCaTransportError;
/// Test-only subprocess hook for custom CA coverage.
///
/// This stays public only so the `custom_ca_probe` binary target can reuse the shared helper. It
/// is hidden from normal docs because ordinary callers should use
/// [`build_reqwest_client_with_custom_ca`] instead.
#[doc(hidden)]
pub use crate::custom_ca::build_reqwest_client_for_subprocess_tests;
pub use crate::custom_ca::build_reqwest_client_with_custom_ca;
pub use crate::custom_ca::build_rustls_client_config_with_custom_ca;
pub use crate::custom_ca::maybe_build_rustls_client_config_with_custom_ca;
pub use crate::error::StreamError;
pub use crate::error::TransportError;
pub use crate::network_policy::DestinationPolicy;
pub use crate::network_policy::NetworkPermit;
pub use crate::network_policy::NetworkPolicy;
pub use crate::network_policy::NetworkPolicyController;
pub use crate::network_policy::NetworkPolicyDenied;
pub use crate::network_policy::NetworkPolicyRevision;
pub use crate::openai_endpoints::OpenAiDestination;
pub use crate::openai_endpoints::classify_openai_api_url;
pub use crate::openai_endpoints::is_openai_api_url;
pub use crate::openai_headers::CHATGPT_ACCOUNT_ID_HEADER;
pub use crate::openai_headers::OPENAI_ORGANIZATION_HEADER;
pub use crate::openai_headers::OPENAI_PROJECT_HEADER;
pub use crate::outbound_proxy::BuildRouteAwareHttpClientError;
pub use crate::outbound_proxy::ClientRouteClass;
pub use crate::outbound_proxy::HttpClientFactory;
#[cfg(target_os = "macos")]
pub use crate::outbound_proxy::MacosSystemProxyConfiguration;
pub use crate::outbound_proxy::OutboundProxyPolicy;
pub use crate::outbound_proxy::OutboundProxyRoute;
pub use crate::outbound_proxy::RouteFailureClass;
#[doc(hidden)]
pub use crate::outbound_proxy::cache_system_proxy_route_for_test;
#[cfg(target_os = "macos")]
pub use crate::outbound_proxy::macos_system_proxy_configuration;
pub use crate::request::EncodedJsonBody;
pub use crate::request::PreparedRequestBody;
pub use crate::request::Request;
pub use crate::request::RequestBody;
pub use crate::request::RequestCompression;
pub use crate::request::Response;
pub use crate::request_builder::RequestBuilder;
pub use crate::response::HttpResponse;
pub use crate::retry_after::RetryAfter;
pub use crate::route_aware_client_pool::RouteAwareClientPool;
pub use crate::route_aware_client_pool::RouteAwareClientPoolError;
pub use crate::route_aware_client_pool::RouteAwareRequestError;
pub use crate::transport::ByteStream;
pub use crate::transport::HttpTransport;
pub use crate::transport::ReqwestTransport;
pub use crate::transport::StreamResponse;
#[cfg(windows)]
mod windows_proxy_auth;
#[cfg(windows)]
mod windows_tls;
#[cfg(windows)]
pub use crate::windows_proxy_auth::WindowsProxyTls;
#[cfg(windows)]
pub use crate::windows_proxy_auth::connect_windows_proxy;
#[cfg(windows)]
pub use crate::windows_tls::build_windows_platform_tls_config;
/// Validates HTTPS or loopback HTTP endpoints, rejecting userinfo and fragments.
/// Errors identify the caller-supplied field without including the URL.
pub fn validate_endpoint_url(value: &str, field: &str) -> Result<Url, String> {
let url = Url::parse(value).map_err(|_| format!("invalid {field} URL"))?;
let loopback = match url.host() {
Some(Host::Domain(host)) => host.eq_ignore_ascii_case("localhost"),
Some(Host::Ipv4(host)) => host.is_loopback(),
Some(Host::Ipv6(host)) => host.is_loopback(),
None => false,
};
if url.host().is_none()
|| !url.username().is_empty()
|| url.password().is_some()
|| url.fragment().is_some()
|| !(url.scheme() == "https" || (url.scheme() == "http" && loopback))
{
return Err(format!(
"{field} must use HTTPS (or loopback HTTP), without userinfo or a fragment"
));
}
Ok(url)
}