Skip to content

fix: 增强 Claude 反代检测对抗能力#2486

Closed
Soein wants to merge 4 commits intorouter-for-me:mainfrom
Soein:main
Closed

fix: 增强 Claude 反代检测对抗能力#2486
Soein wants to merge 4 commits intorouter-for-me:mainfrom
Soein:main

Conversation

@Soein
Copy link
Copy Markdown
Contributor

@Soein Soein commented Apr 2, 2026

Summary

基于 Claude Code v2.1.88 源码(source map 逆向)分析,修复了多个可被 Anthropic 检测的差距:

指纹与签名:

  • 实现消息指纹算法 SHA256(salt + msg[4,7,20] + version)[:3],替代随机 buildHash
  • billing header cc_version 从设备 profile 动态取版本号
  • billing header cc_entrypoint 从客户端 UA 解析(cli/vscode/local-agent)
  • billing header 新增 cc_workload 支持

HTTP 头部:

  • 新增 X-Claude-Code-Session-Id(每 apiKey 缓存 UUID,TTL=1h)
  • 新增 x-client-request-id(仅首方 API,每请求 UUID)
  • Anthropic-Dangerous-Direct-Browser-Access 仅在 API key 模式发送
  • 补全 4 个缺失 beta flags(structured-outputs/fast-mode/redact-thinking/token-efficient-tools)

TLS 指纹:

  • Claude API 请求使用 utls Chrome TLS 指纹,替代 Go 标准库 TLS
  • 新增 helps/utls_client.go,仅影响 Claude executor,其他 executor 不受影响

安全加固:

  • OAuth scope 对齐 Claude Code 2.1.88(5 个 scope)
  • 响应头网关指纹清洗(剥离 litellm/helicone/portkey/cloudflare/kong/braintrust 前缀头)

Test plan

  • go build ./... 编译通过
  • go test ./internal/runtime/executor/... 全部通过
  • go test ./sdk/api/handlers/... 全部通过
  • go test ./internal/auth/... 全部通过

🤖 Generated with Claude Code

基于 Claude Code v2.1.88 源码分析,修复多个可被 Anthropic 检测的差距:

- 实现消息指纹算法(SHA256 盐值 + 字符索引),替代随机 buildHash
- billing header cc_version 从设备 profile 动态取版本号,不再硬编码
- billing header cc_entrypoint 从客户端 UA 解析,支持 cli/vscode/local-agent
- billing header 新增 cc_workload 支持(通过 X-CPA-Claude-Workload 头传入)
- 新增 X-Claude-Code-Session-Id 头(每 apiKey 缓存 UUID,TTL=1h)
- 新增 x-client-request-id 头(仅 api.anthropic.com,每请求 UUID)
- 补全 4 个缺失的 beta flags(structured-outputs/fast-mode/redact-thinking/token-efficient-tools)
- OAuth scope 对齐 Claude Code 2.1.88(移除 org:create_api_key,添加 sessions/mcp/file_upload)
- Anthropic-Dangerous-Direct-Browser-Access 仅在 API key 模式发送
- 响应头网关指纹清洗(剥离 litellm/helicone/portkey/cloudflare/kong/braintrust 前缀头)
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements several anti-detection measures to better mimic the official Claude Code CLI. Key changes include the introduction of a uTLS HTTP client with Chrome fingerprinting to bypass TLS fingerprinting, the implementation of a deterministic build fingerprint and session ID caching, and the stripping of AI gateway-specific headers from upstream responses. Additionally, OAuth scopes were expanded and header injection logic was refined. A review comment correctly identified that the computeFingerprint function uses byte indexing instead of rune indexing, which could lead to incorrect fingerprints when processing multi-byte characters.

Comment on lines +1203 to +1211
var chars [3]byte
for i, idx := range indices {
if idx < len(messageText) {
chars[i] = messageText[idx]
} else {
chars[i] = '0'
}
}
input := fingerprintSalt + string(chars[:]) + version
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

computeFingerprint 函数中,直接通过字节索引 messageText[idx] 来获取字符,这在处理包含多字节字符(如中文或表情符号)的 messageText 时会产生不正确的指纹。JavaScript 中字符串是按字符(UTF-16 码元)索引的,为了正确模拟原始客户端的行为,应将字符串转换为 rune 切片再进行索引。

当前实现仅对 ASCII 字符串有效,对于非 ASCII 字符串,生成的指纹将与目标算法不匹配,这可能导致您的反检测措施失效。

Suggested change
var chars [3]byte
for i, idx := range indices {
if idx < len(messageText) {
chars[i] = messageText[idx]
} else {
chars[i] = '0'
}
}
input := fingerprintSalt + string(chars[:]) + version
runes := []rune(messageText)
var sb strings.Builder
for _, idx := range indices {
if idx < len(runes) {
sb.WriteRune(runes[idx])
} else {
sb.WriteRune('0')
}
}
input := fingerprintSalt + sb.String() + version

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eb3f03ae6b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +140 to +142
if req.URL.Scheme == "https" {
return f.utls.RoundTrip(req)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fallback to standard transport when uTLS cannot open HTTP/2

This routes all HTTPS requests through utlsRoundTripper, but that path always constructs an http2.ClientConn and never retries with the normal transport. Claude auth supports custom base_url targets, and many reverse proxies/gateways are HTTPS but HTTP/1.1-only; in that setup every request fails instead of using the existing fallback transport. Please gate the uTLS path to Anthropic hosts (or retry f.fallback) when the uTLS/HTTP2 round trip fails.

Useful? React with 👍 / 👎.

Comment on lines +108 to +109
if !strings.Contains(addr, ":") {
addr += ":443"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Build dial address with net.JoinHostPort for IPv6 hosts

The strings.Contains(addr, ":") check treats an IPv6 literal host as if it already has a port, so requests to URLs like https://[2001:db8::1]/... keep addr without :443 and Dial fails with a missing-port error. Since base_url is configurable, this breaks Claude requests for IPv6 upstreams; construct the dial target from hostname+port explicitly instead of colon detection.

Useful? React with 👍 / 👎.

Soein added 3 commits April 2, 2026 19:09
Claude executor 的 API 请求之前使用 Go 标准库 crypto/tls,JA3 指纹
与真实 Claude Code(Bun/BoringSSL)不匹配,可被 Cloudflare 识别。

- 新增 helps/utls_client.go,封装 utls Chrome 指纹 + HTTP/2 + 代理支持
- Claude executor 的 4 处 NewProxyAwareHTTPClient 替换为 NewUtlsHTTPClient
- 其他 executor(Gemini/Codex/iFlow 等)不受影响,仍用标准 TLS
- 非 HTTPS 请求自动回退到标准 transport
- computeFingerprint 使用 rune 索引替代字节索引,修复多字节字符指纹不匹配
- utls Chrome TLS 指纹仅对 Anthropic 官方域名生效,自定义 base_url 走标准 transport
- IPv6 地址使用 net.JoinHostPort 正确拼接端口
@Soein
Copy link
Copy Markdown
Contributor Author

Soein commented Apr 2, 2026

重新提交为新 PR,移除了 fork 专用的 CI workflow,并修复了 review 中的 3 个问题

@Soein Soein closed this Apr 2, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6abc0aa1af

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

}

billingText := generateBillingHeader(payload, experimentalCCHSigning, version, messageText, entrypoint, workload)
billingBlock := fmt.Sprintf(`{"type":"text","text":"%s"}`, billingText)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Escape billing text before embedding it into JSON

billingText now includes entrypoint and optional workload, both derived from request headers, but billingBlock is still assembled with raw string interpolation. If either value contains a quote, backslash, or newline (e.g. custom User-Agent/X-CPA-Claude-Workload), the generated system JSON becomes invalid and Anthropic will reject the request. Build this block via JSON encoding (sjson/json.Marshal) instead of fmt.Sprintf.

Useful? React with 👍 / 👎.

}

func (t *utlsRoundTripper) createConnection(host, addr string) (*http2.ClientConn, error) {
conn, err := t.dialer.Dial("tcp", addr)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor request cancellation while dialing uTLS connections

Connection setup uses t.dialer.Dial("tcp", addr) without any context, so canceled or timed-out request contexts cannot interrupt dial/TLS handshake. Since Claude executor calls NewUtlsHTTPClient(..., 0), these stalls can block goroutines for a long time during network/proxy issues instead of failing fast with the request context. Use a context-aware dial path (or explicit deadlines) for the uTLS connection establishment.

Useful? React with 👍 / 👎.

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