Skip to content

fix(codex): add Cloudflare-required headers to primary and auxiliary client paths#12664

Merged
teknium1 merged 2 commits into
mainfrom
hermes/hermes-cbf1f93f
Apr 19, 2026
Merged

fix(codex): add Cloudflare-required headers to primary and auxiliary client paths#12664
teknium1 merged 2 commits into
mainfrom
hermes/hermes-cbf1f93f

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Codex provider no longer 403s from non-residential IPs. Requests to chatgpt.com/backend-api/codex now advertise originator: codex_cli_rs, a codex_cli_rs-shaped User-Agent, and ChatGPT-Account-ID (canonical PascalCase) extracted from the OAuth JWT — matching the whitelisted header set Cloudflare expects.

Closes the Cloudflare 403 regression originally reported in #6391.

Changes

  • agent/auxiliary_client.py: new _codex_cloudflare_headers(access_token) helper (originator + User-Agent + account-ID extraction). Wired into _try_codex and the raw_codex branch of resolve_provider_client.
  • run_agent.py: replaced two inlined JWT-decode blocks (AIAgent.__init__ + _apply_client_headers_for_base_url) with calls to the shared helper. Net -25 lines of duplicated inline logic.
  • tests/agent/test_codex_cloudflare_headers.py: 13 regression tests — helper correctness, token edge cases, wiring at all four insertion points.

Salvage notes

Cherry-picked @admin28980's commit from #6391 preserving their authorship via --author=271152998+admin28980@users.noreply.github.com (their commit shipped with a generic User <user@local> identity; public noreply email used to route credit to their GitHub account). Fix-up commit on top corrects the header values and extends coverage to the aux client.

Corrections vs the original PR

  • originator: hermes-agentcodex_cli_rs (Cloudflare whitelists codex_cli_rs, codex_vscode, codex_sdk_ts, and Codex* prefixes — hermes-agent isn't on the list, so the original value wasn't actually mitigating the block).
  • ChatGPT-Account-IdChatGPT-Account-ID (canonical casing from upstream codex-rs auth.rs).
  • User-Agent: hermes-agent/1.0codex_cli_rs/0.0.0 (Hermes Agent) (matches the codex-rs UA shape while keeping product attribution).
  • Auxiliary client now gets the same headers — compression, title generation, vision, session search, and web_extract no longer 403 from VPS IPs when routed through Codex.

Validation

Before After
Primary client, chatgpt.com base no codex headers (main) / wrong originator (original PR) codex_cli_rs + canonical account-id header
Credential-rotation to chatgpt.com same same helper, same result
Aux client (_try_codex) no headers codex headers
Aux client raw_codex branch no headers codex headers
Non-chatgpt base URLs no codex headers no codex headers (unchanged)
Malformed / missing token inline try/except at 2 sites single helper, tested

Targeted tests: tests/agent/test_codex_cloudflare_headers.py 13/13 pass. Adjacent: 78/78 (auxiliary_client, create_openai_client kwargs-isolation / reuse / proxy-env).

admin28980 and others added 2 commits April 19, 2026 11:55
Add ChatGPT-Account-Id and originator headers when using chatgpt.com
backend-api endpoint. Matches official codex-rs CLI behavior to prevent
Cloudflare JavaScript challenges on non-residential IPs (VPS, Mac Mini,
always-on servers).

Applied in AIAgent.__init__ and _update_base_url_headers to cover both
initial setup and credential rotation paths.
…ient

The cherry-picked salvage (admin28980's commit) added codex headers only on the
primary chat client path, with two inaccuracies:

  - originator was 'hermes-agent' — Cloudflare whitelists codex_cli_rs,
    codex_vscode, codex_sdk_ts, and Codex* prefixes. 'hermes-agent' isn't on
    the list, so the header had no mitigating effect on the 403 (the
    account-id header alone may have been carrying the fix).
  - account-id header was 'ChatGPT-Account-Id' — upstream codex-rs auth.rs
    uses canonical 'ChatGPT-Account-ID' (PascalCase, trailing -ID).

Also, the auxiliary client (_try_codex + resolve_provider_client raw_codex
branch) constructs OpenAI clients against the same chatgpt.com endpoint with
no default headers at all — so compression, title generation, vision, session
search, and web_extract all still 403 from VPS IPs.

Consolidate the header set into _codex_cloudflare_headers() in
agent/auxiliary_client.py (natural home next to _read_codex_access_token and
the existing JWT decode logic) and call it from all four insertion points:

  - run_agent.py: AIAgent.__init__ (initial construction)
  - run_agent.py: _apply_client_headers_for_base_url (credential rotation)
  - agent/auxiliary_client.py: _try_codex (aux client)
  - agent/auxiliary_client.py: resolve_provider_client raw_codex branch

Net: -36/+55 lines, -25 lines of duplicated inline JWT decode replaced by a
single helper. User-Agent switched to 'codex_cli_rs/0.0.0 (Hermes Agent)' to
match the codex-rs shape while keeping product attribution.

Tests in tests/agent/test_codex_cloudflare_headers.py cover:
  - originator value, User-Agent shape, canonical header casing
  - account-ID extraction from a real JWT fixture
  - graceful handling of malformed / non-string / claim-missing tokens
  - wiring at all four insertion points (primary init, rotation, both aux paths)
  - non-chatgpt base URLs (openrouter) do NOT get codex headers
  - switching away from chatgpt.com drops the headers
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Supply Chain Risk Detected

This PR contains patterns commonly associated with supply chain attacks. This does not mean the PR is malicious — but these patterns require careful human review before merging.

⚠️ WARNING: base64 encoding/decoding detected

Base64 has legitimate uses (images, JWT, etc.) but is also commonly used to obfuscate malicious payloads. Verify the usage is appropriate.

Matches (first 20):

39:+        claims = json.loads(base64.urlsafe_b64decode(payload_b64))

Automated scan triggered by supply-chain-audit. If this is a false positive, a maintainer can approve after manual review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants