Files
sub2api/backend/internal/service/gateway_billing_block.go
T
harukaandClaude Opus 4.8 6cfb7898df fix(claude-mimicry): drop the cch sign to match new Claude Code CLI
Recent Claude Code CLI versions no longer emit the cch=... signature field in
their x-anthropic-billing-header system block (issue #3358). sub2api still
injected cch=00000 when mimicking Claude Code for OAuth accounts and optionally
signed it, so mimicked requests now diverge from real CLI traffic — the opposite
of what the mimicry is for.

- buildBillingAttributionText emits the block without the cch=00000 segment;
  cc_version + cc_entrypoint=cli are kept (detection and Anthropic's first-party
  signal rely on the block, not on cch).
- Retire signing: remove the two enableCCH signBillingHeaderCCH call sites in
  buildUpstreamRequest / buildCountTokensRequest and delete the now-dead
  signBillingHeaderCCH, cchPlaceholderRe, cchSeed, xxHash64Seeded helpers.
- enable_cch_signing is now a documented no-op (kept for backward compat).
- Drop the obsolete signing tests (TestSignBillingHeaderCCH, TestXXHash64Seeded,
  TestSanitizeMustBeBeforeCCHSigning_HashConsistency) and update the prompt test
  to assert the injected block no longer carries cch=.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 08:01:29 -07:00

96 lines
3.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"github.com/tidwall/gjson"
)
// fingerprintSalt 是计算 cc_version 后缀指纹的盐值。
//
// 来源:与 Parrot src/transform/cc_mimicry.py 的 FINGERPRINT_SALT 完全一致;
// 这是真实 Claude Code CLI 抓包推导出的常量,改动会导致 fp 与 CLI 不一致,
// 进一步触发 Anthropic 的第三方检测。
const fingerprintSalt = "59cf53e54c78"
// computeClaudeCodeFingerprint 复刻真实 Claude Code CLI 的 cc_version 指纹算法:
//
// 1. 取 messages 中第一条 role=user 的纯文本(首块 text)
// 2. 取该文本的第 4、7、20 字符(不足以 '0' 补齐)
// 3. SHA256(SALT + chars + cc_version) 取 hex 前 3 字符
//
// 算法来自 Parrot src/transform/cc_mimicry.py:compute_fingerprint,与官方 CLI 字节对齐。
// 任何偏差都会导致 cc_version=X.Y.Z.{fp} 在上游侧与真实 CLI 不一致。
func computeClaudeCodeFingerprint(body []byte, version string) string {
firstText := extractFirstUserText(body)
indices := []int{4, 7, 20}
chars := make([]byte, 0, 3)
for _, i := range indices {
if i < len(firstText) {
chars = append(chars, firstText[i])
} else {
chars = append(chars, '0')
}
}
sum := sha256.Sum256([]byte(fingerprintSalt + string(chars) + version))
return hex.EncodeToString(sum[:])[:3]
}
// extractFirstUserText 提取 messages 中第一条 user 消息的首段 text 内容。
// 兼容 string 和 []block 两种 content 格式。
func extractFirstUserText(body []byte) string {
messages := gjson.GetBytes(body, "messages")
if !messages.IsArray() {
return ""
}
first := ""
messages.ForEach(func(_, msg gjson.Result) bool {
if msg.Get("role").String() != "user" {
return true
}
content := msg.Get("content")
if content.Type == gjson.String {
first = content.String()
return false
}
if content.IsArray() {
content.ForEach(func(_, block gjson.Result) bool {
if block.Get("type").String() == "text" {
first = block.Get("text").String()
return false
}
return true
})
return false
}
return false
})
return first
}
// buildBillingAttributionText 构造 system 数组的 billing attribution 文本。
//
// 形态对齐真实 Claude Code CLI:
//
// x-anthropic-billing-header: cc_version=2.1.161.{fp}; cc_entrypoint=cli;
//
// 注意:新版 Claude Code CLI 已不再发送 cch=... 签名字段(见 issue #3358)。我们
// 随之去掉了 cch 段——继续注入它反而会让伪装请求偏离真实 CLI 流量。cc_version +
// cc_entrypoint=cli 仍保留:它们是客户端识别(claude_code_validator)与 Anthropic
// 第一方判定都依赖的稳定信号。
//
// 此 block 不带 cache_control(与真实 CLI 一致;cache breakpoint 由后续的
// Claude Code prompt block 承担)。
func buildBillingAttributionText(body []byte, cliVersion string) (string, error) {
if cliVersion == "" {
return "", fmt.Errorf("cliVersion required")
}
fp := computeClaudeCodeFingerprint(body, cliVersion)
return fmt.Sprintf(
"x-anthropic-billing-header: cc_version=%s.%s; cc_entrypoint=cli;",
cliVersion, fp,
), nil
}