Restrict cloud task credentials to trusted origins (#41403)

## Why

Cloud task requests can use saved ChatGPT credentials, so their destination must be constrained before authentication is loaded or any request is made.

## What changed

- Validate `CODEX_CLOUD_TASKS_BASE_URL` against trusted ChatGPT HTTPS origins on port 443 and reject URLs containing user information, queries, or fragments.
- Disable redirects for cloud task backend and environment-discovery clients so credentials are never forwarded to a redirect destination.
- Normalize the validated base URL before configuring authentication and HTTP clients.

## Testing

Add a CLI integration test that verifies a custom cloud backend is rejected without receiving a request, while the staging origin continues to use explicit token authentication.

GitOrigin-RevId: f0dddf363b4e44ea3530f81d9a817e978c15ead2
This commit is contained in:
Won Park
2026-08-28 19:44:50 +00:00
committed by copyberry
parent d9511fb788
commit 3ae4225b17
5 changed files with 151 additions and 8 deletions
+21 -5
View File
@@ -158,7 +158,27 @@ impl fmt::Debug for Client {
impl Client {
pub fn new(base_url: impl Into<String>, http_client_factory: HttpClientFactory) -> Self {
let mut base_url = base_url.into();
let http = RouteAwareClientPool::with_chatgpt_cloudflare_cookies_without_request_logging(
http_client_factory,
ClientRouteClass::Api,
);
Self::with_http(base_url.into(), http)
}
/// Creates a client that never forwards its credentials to a redirect destination.
pub fn new_without_redirects(
base_url: impl Into<String>,
http_client_factory: HttpClientFactory,
) -> Self {
let http =
RouteAwareClientPool::with_chatgpt_cloudflare_cookies_without_redirects_or_request_logging(
http_client_factory,
ClientRouteClass::Api,
);
Self::with_http(base_url.into(), http)
}
fn with_http(mut base_url: String, http: RouteAwareClientPool) -> Self {
// Normalize common ChatGPT hostnames to include /backend-api so we hit the WHAM paths.
// Also trim trailing slashes for consistent URL building.
while base_url.ends_with('/') {
@@ -170,10 +190,6 @@ impl Client {
{
base_url = format!("{base_url}/backend-api");
}
let http = RouteAwareClientPool::with_chatgpt_cloudflare_cookies_without_request_logging(
http_client_factory,
ClientRouteClass::Api,
);
let path_style = PathStyle::from_base_url(&base_url);
Self {
base_url,