From bdbd2916f5bf4bc08e2b0021cfcdf215b3f3f61f Mon Sep 17 00:00:00 2001 From: keh4l <2461454684@qq.com> Date: Sat, 25 Apr 2026 00:43:38 +0800 Subject: [PATCH] fix(gateway): skip client header passthrough on OAuth mimicry path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause of persistent third-party detection: sub2api's buildUpstreamRequest transparently forwards client headers via allowedHeaders whitelist (addHeaderRaw) before applying mimicry overrides. When third-party clients (opencode, etc.) send their own anthropic-beta / user-agent / x-stainless-* / x-claude-code-session-id values, these get appended to the request alongside our injected headers, creating an inconsistent header set that Anthropic detects. Parrot's build_upstream_headers constructs exactly 9 headers from scratch and never forwards anything from the client. This is why 'same opencode version, some users work some don't' — different opencode configs/versions send different header combinations. Fix: when tokenType=oauth and mimicClaudeCode=true, skip the client header passthrough loop entirely. The subsequent applyClaudeCodeMimicHeaders + ApplyFingerprint + beta merge pipeline constructs all necessary headers from our controlled values. Also: remove systemIncludesClaudeCodePrompt gate — OAuth accounts now unconditionally rewrite system (even if client already sent a Claude Code-style prompt), ensuring billing attribution block is always present. --- backend/internal/service/gateway_service.go | 31 +++++++++++++-------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/backend/internal/service/gateway_service.go b/backend/internal/service/gateway_service.go index 011cc5bc..d9fa81ea 100644 --- a/backend/internal/service/gateway_service.go +++ b/backend/internal/service/gateway_service.go @@ -1197,8 +1197,7 @@ func (s *GatewayService) applyClaudeCodeOAuthMimicryToBody( } systemRewritten := false - if !strings.Contains(strings.ToLower(model), "haiku") && - !systemIncludesClaudeCodePrompt(systemRaw) { + if !strings.Contains(strings.ToLower(model), "haiku") { body = rewriteSystemForNonClaudeCode(body, systemRaw) systemRewritten = true } @@ -4163,9 +4162,13 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A shouldMimicClaudeCode := account.IsOAuth() if shouldMimicClaudeCode { + // 与 Parrot 对齐:OAuth 账号无条件重写 system(即使客户端已发了 Claude Code + // 风格的 system prompt)。原因:第三方工具(opencode 等)会发 "You are Claude + // Code..." system prompt 但缺少 billing attribution block,导致 Anthropic + // 检测到"有 CC prompt 但无 billing block"的不一致而判为 third-party。 + // Parrot 的 transform_request 从不检查客户端 system 内容,直接覆盖。 systemRewritten := false - if !strings.Contains(strings.ToLower(reqModel), "haiku") && - !systemIncludesClaudeCodePrompt(parsed.System) { + if !strings.Contains(strings.ToLower(reqModel), "haiku") { body = rewriteSystemForNonClaudeCode(body, parsed.System) systemRewritten = true } @@ -5766,13 +5769,19 @@ func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Contex setHeaderRaw(req.Header, "x-api-key", token) } - // 白名单透传headers(恢复真实 wire casing) - for key, values := range clientHeaders { - lowerKey := strings.ToLower(key) - if allowedHeaders[lowerKey] { - wireKey := resolveWireCasing(key) - for _, v := range values { - addHeaderRaw(req.Header, wireKey, v) + // 白名单透传 headers + // OAuth mimicry 路径:跳过客户端 header 透传,与 Parrot 对齐。 + // Parrot 的 build_upstream_headers 只发 9 个精确 header,不透传任何客户端 header。 + // 透传客户端 header 会引入不一致的 x-stainless-* / anthropic-beta / user-agent / + // x-claude-code-session-id 等值,和我们注入的伪装 header 冲突,被 Anthropic 判 third-party。 + if !(tokenType == "oauth" && mimicClaudeCode) { + for key, values := range clientHeaders { + lowerKey := strings.ToLower(key) + if allowedHeaders[lowerKey] { + wireKey := resolveWireCasing(key) + for _, v := range values { + addHeaderRaw(req.Header, wireKey, v) + } } } }