Skip to content

Match real Codex CLI TLS fingerprint for OpenAI OAuth forwarding - #6300

Open
JnyRoad wants to merge 2 commits into
Wei-Shaw:mainfrom
JnyRoad:feat/codex-tls-fingerprint
Open

Match real Codex CLI TLS fingerprint for OpenAI OAuth forwarding#6300
JnyRoad wants to merge 2 commits into
Wei-Shaw:mainfrom
JnyRoad:feat/codex-tls-fingerprint

Conversation

@JnyRoad

@JnyRoad JnyRoad commented Aug 27, 2026

Copy link
Copy Markdown

Summary

  • OpenAI Codex OAuth outbound traffic went out over Go's default crypto/tls ClientHello, which is trivially distinguishable from the real Codex CLI: cipher suite table, supported_groups, and extension set all differ, and real Codex CLI (reqwest 0.12 + rustls 0.23, aws_lc_rs provider, compiled without the http2 feature — confirmed from the official openai/codex source, codex-rs/http-client) never sends ALPN at all.
  • Add a tlsfingerprint.Profile matching the real client's cipher suites / supported_groups / ec_point_formats / extension set, cross-checked against three independent packet captures of a real codex-tui session (see specs/002-codex-tls-fingerprint/research.md for the full evidence trail — this doc isn't part of the diff, kept locally).
  • Real rustls clients also reshuffle their ClientHello extension order on every connection (three captures showed the same cipher/group/extension set each time, but three different extension orders) — a permanently fixed order is itself a distinguishing signal. Added a RandomizeExtensionOrder switch to tlsfingerprint.Profile that shuffles the extension list once per TLS connection; off by default so the existing Claude Code / Node.js profile is unaffected, on for the new Codex profile.
  • Wired the profile into doOpenAIUpstream: an account's explicitly configured TLS fingerprint (via the existing TLSFingerprintProfileService) still wins if set; otherwise OpenAI Codex OAuth accounts get the new profile automatically instead of requiring a manual per-account opt-in. Non-OAuth / non-OpenAI accounts are unaffected.

Test plan

  • go build ./... and go vet ./... clean
  • go test ./... — full repo, 0 failures (caught and fixed a real regression along the way: three internal/handler test fixtures embedded service.HTTPUpstream and only overrode Do, panicking once doOpenAIUpstream started calling DoWithTLS unconditionally — added matching DoWithTLS overrides)
  • TDD red→green cycle followed throughout (tests written and confirmed failing before each implementation step)
  • wire_gen.go regenerated via google/wire (not hand-edited) after adding the new DI wiring

🤖 Generated with Claude Code

Codex OAuth traffic was going out over Go's default crypto/tls
ClientHello, which is trivially distinguishable from the real Codex CLI
(reqwest 0.12 + rustls 0.23, aws_lc_rs provider, no http2 feature): the
cipher suite table, supported_groups, and extension set all differ, and
real Codex CLI never sends ALPN. Add a Profile matching the real
client's cipher suites / groups / point formats / extension set,
sourced from the official openai/codex source (codex-rs/http-client)
cross-checked against three independent packet captures.

Real rustls clients also reshuffle their ClientHello extension order on
every connection; a permanently fixed order is itself a distinguishing
signal. Give tlsfingerprint.Profile a RandomizeExtensionOrder switch
that shuffles the extension list once per TLS connection, off by
default so the existing Claude Code / Node.js profile is unaffected,
and turn it on for the new Codex profile.

Wire the profile into doOpenAIUpstream: an account's explicitly
configured TLS fingerprint (if any) still wins, otherwise Codex OAuth
accounts get the new profile automatically rather than requiring a
manual per-account opt-in.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA. ✅
Posted by the CLA Assistant Lite bot.

@JnyRoad

JnyRoad commented Aug 27, 2026

Copy link
Copy Markdown
Author

Thank you for your contribution! Before we can merge this PR, we need you to sign our Contributor License Agreement (CLA).

To sign, please reply with the following comment:

I have read the CLA Document and I hereby sign the CLA

You only need to sign once — it will be valid for all your future contributions to this project.

I have read the CLA Document and I hereby sign the CLA

You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

I have read the CLA Document and I hereby sign the CLA

@JnyRoad

JnyRoad commented Aug 27, 2026

Copy link
Copy Markdown
Author

recheck

doOpenAIUpstream now always calls DoWithTLS instead of Do (needed to
thread the Codex TLS fingerprint profile through). Three more
service.HTTPUpstream test fakes used by Grok credential-failover,
Responses failover-cancel, and Images failover tests embedded the
interface without overriding DoWithTLS, so calls fell through to the
embedded nil interface and were silently swallowed upstream, leaving
recorded account IDs empty instead of failing loudly.

Mirrors the same passthrough-to-Do pattern already applied to the
other three fakes in openai_gateway_handler_test.go. Verified: go test
-tags unit ./... and go vet -tags integration ./... both clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@JnyRoad

JnyRoad commented Aug 28, 2026

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

github-actions Bot added a commit that referenced this pull request Aug 28, 2026
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.

1 participant