From 038f0ee8d39e6a982c838b88582cdcc23bef3cb8 Mon Sep 17 00:00:00 2001 From: win Date: Sat, 2 May 2026 17:07:07 +0800 Subject: [PATCH] fix: always sign cch=00000 placeholder and normalize env fingerprints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs identified from article "Claude Code封号真相": 1. cch=00000 never replaced (fix): signBillingHeaderCCH was gated by enableCCH (default false), so the cch=00000 placeholder injected by buildBillingAttributionBlockJSON was sent to Anthropic as-is — an obvious fake signal. The function already guards itself via regex match, so the enableCCH gate is removed. 2. NormalizeSystemPromptEnv was dead code (fix): Platform/Shell/OS Version/Working directory fields in user system prompts leaked real machine info (e.g. "Darwin 25.3.0", "/Users/win/...") that Anthropic could use to correlate requests across accounts. Now normalized to canonical values before injecting into the messages pair. --- backend/internal/service/gateway_service.go | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/backend/internal/service/gateway_service.go b/backend/internal/service/gateway_service.go index 319d4d0c..40d82fe3 100644 --- a/backend/internal/service/gateway_service.go +++ b/backend/internal/service/gateway_service.go @@ -4124,10 +4124,12 @@ func rewriteSystemForNonClaudeCode(body []byte, system any) []byte { // 模型仍通过 messages 接收完整指令,保留客户端功能 ccPromptTrimmed := strings.TrimSpace(claudeCodeSystemPrompt) if originalSystemText != "" && originalSystemText != ccPromptTrimmed && !hasClaudeCodePrefix(originalSystemText) { + // 规范化 env 字段(Platform/Shell/OS/路径),防止真实机器信息被 Anthropic 用作跨账号关联信号。 + normalizedSystemText := NormalizeSystemPromptEnv(originalSystemText) instrMsg, err1 := json.Marshal(map[string]any{ "role": "user", "content": []map[string]any{ - {"type": "text", "text": "[System Instructions]\n" + originalSystemText}, + {"type": "text", "text": "[System Instructions]\n" + normalizedSystemText}, }, }) ackMsg, err2 := json.Marshal(map[string]any{ @@ -6064,9 +6066,9 @@ func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Contex // OAuth账号:应用统一指纹和metadata重写(受设置开关控制) var fingerprint *Fingerprint - enableFP, enableMPT, enableCCH := true, false, false + enableFP, enableMPT, _ := true, false, false if s.settingService != nil { - enableFP, enableMPT, enableCCH = s.settingService.GetGatewayForwardingSettings(ctx) + enableFP, enableMPT, _ = s.settingService.GetGatewayForwardingSettings(ctx) } if account.IsOAuth() && s.identityService != nil { // 1. 获取或创建指纹(包含随机生成的ClientID) @@ -6097,10 +6099,9 @@ func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Contex if fingerprint != nil { body = syncBillingHeaderVersion(body, fingerprint.UserAgent) } - // CCH 签名:将 cch=00000 占位符替换为 xxHash64 签名(需在所有 body 修改之后) - if enableCCH { - body = signBillingHeaderCCH(body) - } + // CCH 签名:将 cch=00000 占位符替换为 xxHash64 签名(需在所有 body 修改之后)。 + // 无占位符时函数为 no-op,故无需 enableCCH gate — 占位符存在即意味着必须签名。 + body = signBillingHeaderCCH(body) req, err := http.NewRequestWithContext(ctx, "POST", targetURL, bytes.NewReader(body)) if err != nil { @@ -9278,9 +9279,9 @@ func (s *GatewayService) buildCountTokensRequest(ctx context.Context, c *gin.Con // OAuth 账号:应用统一指纹和重写 userID(受设置开关控制) // 如果启用了会话ID伪装,会在重写后替换 session 部分为固定值 - ctEnableFP, ctEnableMPT, ctEnableCCH := true, false, false + ctEnableFP, ctEnableMPT, _ := true, false, false if s.settingService != nil { - ctEnableFP, ctEnableMPT, ctEnableCCH = s.settingService.GetGatewayForwardingSettings(ctx) + ctEnableFP, ctEnableMPT, _ = s.settingService.GetGatewayForwardingSettings(ctx) } var ctFingerprint *Fingerprint if account.IsOAuth() && s.identityService != nil { @@ -9302,9 +9303,8 @@ func (s *GatewayService) buildCountTokensRequest(ctx context.Context, c *gin.Con if ctFingerprint != nil && ctEnableFP { body = syncBillingHeaderVersion(body, ctFingerprint.UserAgent) } - if ctEnableCCH { - body = signBillingHeaderCCH(body) - } + // 无占位符时函数为 no-op,故无需 ctEnableCCH gate。 + body = signBillingHeaderCCH(body) req, err := http.NewRequestWithContext(ctx, "POST", targetURL, bytes.NewReader(body)) if err != nil {