feat(codex): 设备指纹收敛 + TLS 指纹支持 - #889
Merged
Merged
Conversation
…port Several people sharing one Codex OAuth account each send their own installation/session/thread identifiers, so upstream counts them as separate devices and sessions and applies device quota limits to the account. Convergence rewrites those identifiers to account-derived stable values before the request leaves the proxy, in four strengths (off/device/session/full, default session). Derived values are deterministic, so an account keeps the same installation identity across restarts instead of looking freshly reinstalled. Header and body rewriting share one resolved id set, because a turn id that disagrees between the x-codex-turn-metadata header and client_metadata would be a fingerprint of its own. Only identifiers the client actually sent are rewritten: adding a field upstream was never going to receive would widen the fingerprint rather than converge it. The websocket path is narrowed to device scope, since Session_id there is pinned to Conversation_id and prompt_cache_key for prompt cache matching. Also adds internal/tlsfingerprint, which gives upstream requests a ClientHello matching a real client (chrome/firefox/safari/edge/ios/android/randomized) instead of Go's distinctive default. It manages HTTP/2 itself because Go only routes crypto/tls connections to its HTTP/2 stack, and tunnels CONNECT itself because http.Transport would otherwise keep the tunnelled connection for its own TLS. Trust settings (CA bundle, skip-verify) are threaded through so enabling fingerprinting cannot silently drop them. Off by default: the right profile depends on what the operator has verified against the upstream. Verification: ./scripts/ci-pr.sh (gofmt, secret scan, structure check, go vet, go test ./..., golangci-lint, go build) — all green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
govulncheck reports six standard library vulnerabilities against go1.26.5 (encoding/xml and encoding/asn1 recursion depth, net/http idna punycode handling among them), all fixed in 1.26.6. They predate this branch; the vulncheck required check now blocks any PR until the toolchain moves. go.mod, the PR workflow and the deploy workflow are bumped together, as the repository requires these to stay in sync. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
多人共享同一个 Codex OAuth 账号时,每个人的客户端各自携带 installation / session / thread 标识,上游据此判定设备数与会话数并限制配额。
严格检查后发现 CliRelay 的实际泄漏面与 sub2api 不同:
x-codex-installation-id、session-id、thread-id、window-id这些头本仓库原本就不转发,真正把客户端标识送到上游的是无条件透传的x-codex-turn-metadata(JSON 内含全套 ID)与x-client-request-id。此外session-mode: per-request会为每个请求生成新的随机Session_id,同一账号在上游看起来像源源不断的新会话。改动
设备指纹收敛
off/device/session/full,配置项identity-fingerprint.codex.convergence-mode,默认sessioninstallation-id可由运维固定,用于回放真实客户端采集到的值client_metadata改写共用同一份 ID:turn_id 在两处不一致本身就是指纹;turn_started_at_unix_ms与 UUIDv7 取自同一时钟,避免时间互相矛盾Set全部头,而本仓库原本不转发那些头,凭空补上等于增加上游原本看不到的信息Session_id绑定Conversation_id与prompt_cache_key,改写会打断 prompt cache 命中identity-fingerprint.codex.enabled: false时整体关闭TLS 指纹
新增
internal/tlsfingerprint:chrome/firefox/safari/edge/ios/android/randomizedcrypto/tls连接交给自身 HTTP/2 栈,utls 连接必须自己接管,否则会在已协商 h2 的连接上说 HTTP/1.1http.Transport会把隧道连接留给自己的 TLS 实现,指纹握手必须在隧道内进行同时修复一个缺陷:指纹 transport 自行完成 TLS,最初遗漏了
CACert与InsecureSkipVerify,会导致开启指纹后静默丢弃用户配置的 CA 信任设置。现已接通,并有测试锁定。兼容性
session会改变现有默认行为(这是本 PR 的目的);需要保持原样的部署可设convergence-mode: off验证
./scripts/ci-pr.sh全绿:gofmt、密钥扫描、结构检查、go vet、go test ./...、golangci-lint、go build。新增 24 个用例,其中包含真实 TLS 握手、HTTP/2 连接复用、CONNECT 隧道与代理认证、自定义 CA 生效性,以及「两种 profile 的 ClientHello 确实不同」的对比断言。
🤖 Generated with Claude Code