Skip to content

feat(codex): 设备指纹收敛 + TLS 指纹支持 - #889

Merged
kittors merged 2 commits into
devfrom
feat/codex-fingerprint-convergence
Aug 14, 2026
Merged

feat(codex): 设备指纹收敛 + TLS 指纹支持#889
kittors merged 2 commits into
devfrom
feat/codex-fingerprint-convergence

Conversation

@kittors

@kittors kittors commented Aug 14, 2026

Copy link
Copy Markdown
Owner

背景

多人共享同一个 Codex OAuth 账号时,每个人的客户端各自携带 installation / session / thread 标识,上游据此判定设备数与会话数并限制配额。

严格检查后发现 CliRelay 的实际泄漏面与 sub2api 不同:x-codex-installation-idsession-idthread-idwindow-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,默认 session
  • 标识由账号 scope 确定性派生,重启后保持同一「设备」,而不是每次看起来像重装
  • installation-id 可由运维固定,用于回放真实客户端采集到的值
  • 头改写与请求体 client_metadata 改写共用同一份 ID:turn_id 在两处不一致本身就是指纹;turn_started_at_unix_ms 与 UUIDv7 取自同一时钟,避免时间互相矛盾
  • 只改写客户端已经发送的标识,不新增。这一点与 sub2api 不同:它无条件 Set 全部头,而本仓库原本不转发那些头,凭空补上等于增加上游原本看不到的信息
  • WebSocket 路径收窄到设备级:那里 Session_id 绑定 Conversation_idprompt_cache_key,改写会打断 prompt cache 命中
  • API key 凭据不参与收敛(不存在设备配额);identity-fingerprint.codex.enabled: false 时整体关闭

TLS 指纹

新增 internal/tlsfingerprint

  • 可配置 ClientHello:chrome / firefox / safari / edge / ios / android / randomized
  • 自行管理 HTTP/2 —— Go 仅把 crypto/tls 连接交给自身 HTTP/2 栈,utls 连接必须自己接管,否则会在已协商 h2 的连接上说 HTTP/1.1
  • 自行建立 CONNECT 隧道 —— http.Transport 会把隧道连接留给自己的 TLS 实现,指纹握手必须在隧道内进行
  • 同时支持 SOCKS5 与 HTTP/HTTPS 代理,含代理认证
  • 默认关闭:合适的 profile 取决于运维对上游的实测结果,且 ClientHello 与 User-Agent 自相矛盾本身也是破绽

同时修复一个缺陷:指纹 transport 自行完成 TLS,最初遗漏了 CACertInsecureSkipVerify,会导致开启指纹后静默丢弃用户配置的 CA 信任设置。现已接通,并有测试锁定。

兼容性

  • 默认档位 session 会改变现有默认行为(这是本 PR 的目的);需要保持原样的部署可设 convergence-mode: off
  • 旧运行时配置载荷不含新字段,仍会被正确识别为历史默认值,不会复活过期配置
  • 管理端直接序列化整个结构,新字段自动持久化,无需迁移

验证

./scripts/ci-pr.sh 全绿:gofmt、密钥扫描、结构检查、go vetgo test ./...、golangci-lint、go build

新增 24 个用例,其中包含真实 TLS 握手、HTTP/2 连接复用、CONNECT 隧道与代理认证、自定义 CA 生效性,以及「两种 profile 的 ClientHello 确实不同」的对比断言。

🤖 Generated with Claude Code

kittors and others added 2 commits August 14, 2026 11:45
…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>
@kittors
kittors merged commit d8ab21a into dev Aug 14, 2026
3 checks passed
@kittors
kittors deleted the feat/codex-fingerprint-convergence branch August 14, 2026 05:04
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