fix(gateway): skip client header passthrough on OAuth mimicry path

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.
This commit is contained in:
keh4l
2026-04-25 00:43:38 +08:00
parent 6dc89765fd
commit bdbd2916f5

View File

@@ -1197,8 +1197,7 @@ func (s *GatewayService) applyClaudeCodeOAuthMimicryToBody(
} }
systemRewritten := false systemRewritten := false
if !strings.Contains(strings.ToLower(model), "haiku") && if !strings.Contains(strings.ToLower(model), "haiku") {
!systemIncludesClaudeCodePrompt(systemRaw) {
body = rewriteSystemForNonClaudeCode(body, systemRaw) body = rewriteSystemForNonClaudeCode(body, systemRaw)
systemRewritten = true systemRewritten = true
} }
@@ -4163,9 +4162,13 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
shouldMimicClaudeCode := account.IsOAuth() shouldMimicClaudeCode := account.IsOAuth()
if shouldMimicClaudeCode { 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 systemRewritten := false
if !strings.Contains(strings.ToLower(reqModel), "haiku") && if !strings.Contains(strings.ToLower(reqModel), "haiku") {
!systemIncludesClaudeCodePrompt(parsed.System) {
body = rewriteSystemForNonClaudeCode(body, parsed.System) body = rewriteSystemForNonClaudeCode(body, parsed.System)
systemRewritten = true systemRewritten = true
} }
@@ -5766,7 +5769,12 @@ func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Contex
setHeaderRaw(req.Header, "x-api-key", token) setHeaderRaw(req.Header, "x-api-key", token)
} }
// 白名单透传headers(恢复真实 wire casing // 白名单透传 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 { for key, values := range clientHeaders {
lowerKey := strings.ToLower(key) lowerKey := strings.ToLower(key)
if allowedHeaders[lowerKey] { if allowedHeaders[lowerKey] {
@@ -5776,6 +5784,7 @@ func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Contex
} }
} }
} }
}
// OAuth账号应用缓存的指纹到请求头覆盖白名单透传的头 // OAuth账号应用缓存的指纹到请求头覆盖白名单透传的头
if fingerprint != nil { if fingerprint != nil {