Preserve __oailb routing cookies in ChatGPT HTTP clients (#43895)

## What changed

Add `__oailb` to the shared ChatGPT infrastructure cookie allowlist so clients store and replay the routing cookie on subsequent requests. Document its inclusion in the process-local cookie jar.

## Testing

Add a regression test covering replay on a matching path, rejection outside the cookie's host, path, and HTTPS scope, and removal with `Max-Age=0`.

GitOrigin-RevId: abcb888e8867d94fdf0f7d346fded8eaafdc52ae
This commit is contained in:
Harry Bagdi
2026-09-08 19:40:43 +00:00
committed by copyberry
parent 9d83c48e5c
commit 94e4b3d0bd
@@ -8,9 +8,9 @@ use reqwest::header::HeaderValue;
use crate::chatgpt_hosts::is_allowed_chatgpt_host;
// WARNING: this HTTP cookie store is process-global and may be shared across auth contexts.
// It must only ever contain Cloudflare infrastructure cookies. Never extend this
// store to persist ChatGPT account, session, auth, or other user-specific cookie
// data.
// It must only ever contain Cloudflare infrastructure cookies and the `__oailb`
// routing cookie. Never extend this store to persist ChatGPT account, session,
// auth, or other user-specific cookie data.
static SHARED_CHATGPT_CLOUDFLARE_COOKIE_STORE: LazyLock<Arc<ChatGptCloudflareCookieStore>> =
LazyLock::new(|| Arc::new(ChatGptCloudflareCookieStore::default()));
@@ -96,12 +96,13 @@ impl CookieStore for ChatGptCookieStore {
}
}
/// Adds the process-local ChatGPT Cloudflare cookie jar used by Codex HTTP clients.
/// Adds the process-local ChatGPT infrastructure cookie jar used by Codex HTTP clients.
///
/// WARNING: this jar is global within the process. It is only acceptable because it hardcodes a
/// small allowlist of Cloudflare cookie names and refuses all other ChatGPT cookies. Do not store
/// ChatGPT account, session, auth, or other user-specific cookies here. If a future caller needs
/// those cookies, the store must be scoped to the auth/session owner instead of shared globally.
/// small allowlist of Cloudflare cookie names plus `__oailb` for routing, and refuses all other
/// ChatGPT cookies. Do not store ChatGPT account, session, auth, or other user-specific cookies
/// here. If a future caller needs those cookies, the store must be scoped to the auth/session
/// owner instead of shared globally.
pub fn with_chatgpt_cloudflare_cookie_store(
builder: reqwest::ClientBuilder,
) -> reqwest::ClientBuilder {
@@ -155,6 +156,7 @@ fn only_cloudflare_cookies(header: HeaderValue) -> Option<HeaderValue> {
}
fn is_allowed_cloudflare_cookie_name(name: &str) -> bool {
// `__oailb` is an OpenAI infrastructure routing cookie, not an authentication cookie.
// Keep this allowlist aligned with Cloudflare's documented service cookies:
// https://developers.cloudflare.com/fundamentals/reference/policies-compliances/cloudflare-cookies/
matches!(
@@ -164,6 +166,7 @@ fn is_allowed_cloudflare_cookie_name(name: &str) -> bool {
| "__cfruid"
| "__cfseq"
| "__cfwaitingroom"
| "__oailb"
| "_cfuvid"
| "cf_clearance"
| "cf_ob_info"
@@ -265,6 +268,37 @@ mod tests {
);
}
#[test]
fn oailb_cookies_are_replayed_with_scope_and_expiration() {
let store = ChatGptCloudflareCookieStore::default();
let url = reqwest::Url::parse("https://chatgpt.com/backend-api/codex/responses").unwrap();
let cookie = HeaderValue::from_static(
"__oailb=route; Path=/backend-api; Max-Age=3600; Secure; HttpOnly; SameSite=Lax",
);
store.set_cookies(&mut std::iter::once(&cookie), &url);
let followup_url = reqwest::Url::parse("https://chatgpt.com/backend-api/ps/mcp").unwrap();
assert_eq!(
store.cookies(&followup_url),
Some(HeaderValue::from_static("__oailb=route"))
);
for outside_scope in [
"https://chatgpt.com/",
"https://other.chatgpt.com/backend-api/ps/mcp",
"https://api.openai.com/backend-api/ps/mcp",
"http://chatgpt.com/backend-api/ps/mcp",
] {
let outside_scope = reqwest::Url::parse(outside_scope).unwrap();
assert_eq!(store.cookies(&outside_scope), None);
}
let expired = HeaderValue::from_static(
"__oailb=; Path=/backend-api; Max-Age=0; Secure; HttpOnly; SameSite=Lax",
);
store.set_cookies(&mut std::iter::once(&expired), &url);
assert_eq!(store.cookies(&followup_url), None);
}
#[test]
fn ignores_non_chatgpt_cookies() {
let store = ChatGptCloudflareCookieStore::default();