package service import ( "net/http" "github.com/google/uuid" "github.com/tidwall/gjson" ) // ensureClaudeCodeSessionID 确保 X-Claude-Code-Session-Id header 被合理填充。 // // 真实 Claude Code 2.1.145 CLI 在 SDK 内会始终设置 X-Claude-Code-Session-Id 为 // 当前 CLI 会话的 UUID(源码:`"X-Claude-Code-Session-Id":y_()`)。上游若发现 // 该头缺失,可能将请求识别为非官方 Claude Code 第三方调用。 // // 行为按 tokenType / mimicClaudeCode 分两条路径: // // OAuth 路径 (tokenType == "oauth"): // OAuth 账号本身就是真实 Claude Code 客户端的凭证,可以信任 body 中的 // metadata.user_id 派生 session id。 // 1. metadata.user_id 派生 SessionID 是合法 UUID → canonical 写入 // 2. header 已有合法 UUID → canonical 保留 // 3. mimicClaudeCode == true → 兜底生成新 UUID // (mimicClaudeCode == false 且无 metadata 时不强制注入) // // API key 透传路径 (tokenType != "oauth"): // - 不从 body metadata 派生 header(避免污染客户端原始语义) // - 若客户端在 header 中传入 X-Claude-Code-Session-Id: // 合法 UUID → canonical 保留 // 非法值 → 删除(不向上游转发恶意值) // - 不兜底生成 // // 安全说明:metadata.user_id 由客户端控制,ParseMetadataUserID 的正则仅约束字符集, // 不保证 UUID 结构。因此所有写入 header 的 session id 都必须经 uuid.Parse 校验。 func ensureClaudeCodeSessionID(req *http.Request, body []byte, tokenType string, mimicClaudeCode bool) { if req == nil { return } if req.Header == nil { req.Header = make(http.Header) } isOAuth := tokenType == "oauth" // OAuth 路径:从 metadata 派生(OAuth 凭证可信任)。 if isOAuth { if uid := gjson.GetBytes(body, "metadata.user_id").String(); uid != "" { if parsed := ParseMetadataUserID(uid); parsed != nil { if id, err := uuid.Parse(parsed.SessionID); err == nil { setHeaderRaw(req.Header, "X-Claude-Code-Session-Id", id.String()) return } } } } // 处理客户端已传入的 header:合法 → canonicalize,非法 → 删除。 // 这条规则对所有路径生效,避免恶意非 UUID 值向上游透传。 if existing := getHeaderRaw(req.Header, "X-Claude-Code-Session-Id"); existing != "" { if id, err := uuid.Parse(existing); err == nil { canonical := id.String() if canonical != existing { setHeaderRaw(req.Header, "X-Claude-Code-Session-Id", canonical) } return } // 非法 UUID:删除避免透传 req.Header.Del("X-Claude-Code-Session-Id") } // OAuth mimic 兜底生成(仅 mimic 场景;API key/非 mimic 不污染)。 // uuid.NewString() 走 crypto/rand。 if isOAuth && mimicClaudeCode { setHeaderRaw(req.Header, "X-Claude-Code-Session-Id", uuid.NewString()) } }