Honor system proxy settings for remote plugins (#34509)

## Why

Remote plugin requests did not use Codex's effective outbound proxy policy, so
they could bypass configured system proxy and PAC routing.

## What changed

- Route remote plugin catalog, mutation, sharing, upload, and bundle download
  requests through the configured route-aware HTTP client.
- Select routes using the complete request URL, including encoded query
  parameters and backend-provided signed upload or download URLs.
- Preserve standard Codex headers and suppress diagnostics for URLs or headers
  that may contain credentials.

## Testing

Add coverage for route selection of catalog queries, workspace plugin uploads,
and backend-provided bundle download URLs.

GitOrigin-RevId: 106936659e21decf145b6ab4d4be84992e386861
This commit is contained in:
Michael Bolin authored and copyberry committed 2026-07-21 10:05:06 +00:00
1 parent a148e0b50a
commit d937bfac84
17 files changed
+267 -113

No files matched your search

@@ -2,9 +2,9 @@
//!
//! Curated plugin startup sync normally uses git, so its HTTP path is also a recovery path for
//! machines where git is unavailable or fails. Under `ReqwestDefault`, that recovery path must
//! preserve the legacy `codex_login::default_client::build_reqwest_client()` behavior: invalid
//! custom-CA configuration is logged and falls back to a normal reqwest client instead of making
//! HTTP sync fail as well.
//! preserve the legacy `codex_login::default_client::create_client_without_request_logging()`
//! behavior: invalid custom-CA configuration is logged and falls back to a normal client instead
//! of making HTTP sync fail as well.
//!
//! When `RespectSystemProxy` is enabled, however, every concrete request URL—including download
//! URLs returned by another endpoint—must be routed through `RouteAwareClientPool` so PAC and
@@ -20,23 +20,28 @@ use std::time::Duration;
use crate::http_client_selector::HttpClientSelector;
use codex_http_client::ClientRouteClass;
use codex_http_client::HttpClient;
use codex_http_client::HttpClientFactory;
use codex_http_client::HttpResponse;
use codex_http_client::OutboundProxyPolicy;
use codex_http_client::RequestBuilder;
use codex_http_client::RouteAwareClientPool;
use codex_http_client::RouteAwareRequestBuilder;
use codex_login::default_client::build_reqwest_client;
use codex_login::default_client::create_client_without_request_logging;
use http::HeaderMap;
use http::Method;
pub(super) enum StartupSyncHttpClient {
Default(reqwest::Client),
Default(HttpClient),
RouteAware(Arc<dyn HttpClientSelector>),
}
impl StartupSyncHttpClient {
pub(super) fn new(http_client_factory: &HttpClientFactory) -> Self {
match http_client_factory.outbound_proxy_policy() {
OutboundProxyPolicy::ReqwestDefault => Self::Default(build_reqwest_client()),
OutboundProxyPolicy::ReqwestDefault => {
Self::Default(create_client_without_request_logging())
}
OutboundProxyPolicy::RespectSystemProxy => {
let http_clients =
RouteAwareClientPool::with_chatgpt_cloudflare_cookies_without_request_logging(
@@ -66,7 +71,7 @@ impl StartupSyncHttpClient {
}
pub(super) enum StartupSyncRequestBuilder {
Default(reqwest::RequestBuilder),
Default(RequestBuilder),
RouteAware(RouteAwareRequestBuilder),
}
@@ -92,7 +97,7 @@ impl StartupSyncRequestBuilder {
}
}
pub(super) async fn send(self) -> Result<reqwest::Response, String> {
pub(super) async fn send(self) -> Result<HttpResponse, String> {
match self {
Self::Default(request) => request.send().await.map_err(|err| err.to_string()),
Self::RouteAware(request) => request.send().await.map_err(|err| err.to_string()),