// Package claudemask provides Node.js client emulation for Claude API requests package claudemask import ( "net/http" "strings" "github.com/Wei-Shaw/sub2api/internal/pkg/claude" ) // GoClientIndicators 是可能暴露 Go 客户端身份的 HTTP 头列表 // 这些头需要被清除或伪装 var GoClientIndicators = []string{ "Go-Http-Client/", "User-Agent", // Go 默认在 User-Agent 中会包含 "Go-http-client" } // SuspiciousHeaders 是在 Claude API 请求中不应出现的头 // 或应被移除的头(非 Node.js 客户端会发送) var SuspiciousHeaders = []string{ "Accept-Encoding", // Go http.Client 自动添加,但应由 utls 处理 "Content-Length", // 应由 http.Transport 自动管理 } // RequiredNodeHeaders 是 Node.js Claude Code 客户端必须有的头 var RequiredNodeHeaders = map[string]bool{ "User-Agent": true, "X-Stainless-Lang": true, "X-Stainless-Runtime": true, "X-Stainless-Runtime-Version": true, "X-Stainless-Package-Version": true, "X-Stainless-OS": true, "X-Stainless-Arch": true, "anthropic-version": true, } // ValidateNodeEmulation 验证请求是否正确伪装为 Node.js 客户端 // 返回 (isValid, errorMessages) func ValidateNodeEmulation(req *http.Request) (bool, []string) { if req == nil { return false, []string{"request is nil"} } var errors []string // 检查必要的 Node.js 指纹头 for header := range RequiredNodeHeaders { if req.Header.Get(header) == "" { errors = append(errors, "missing required header: "+header) } } // 检查是否包含 Go 客户端指示 ua := req.Header.Get("User-Agent") if ua == "" { errors = append(errors, "User-Agent is empty") } else if strings.Contains(ua, "Go-http-client") { errors = append(errors, "User-Agent contains Go-http-client indicator") } else if !strings.Contains(ua, "claude-cli") && !strings.Contains(ua, "node") { errors = append(errors, "User-Agent does not contain Node.js indicators") } // 验证 X-Stainless-Runtime 应为 "node" runtime := req.Header.Get("X-Stainless-Runtime") if runtime != "node" { errors = append(errors, "X-Stainless-Runtime should be 'node', got: "+runtime) } // 验证 X-Stainless-Lang 应为 "js" lang := req.Header.Get("X-Stainless-Lang") if lang != "js" { errors = append(errors, "X-Stainless-Lang should be 'js', got: "+lang) } return len(errors) == 0, errors } // CleanRequest 清除或修复任何会暴露 Go 客户端身份的请求头 // 这是一个"猴子补丁",用于修复任何遗漏的伪装 func CleanRequest(req *http.Request) { if req == nil { return } // 检查并修复可疑的 User-Agent ua := req.Header.Get("User-Agent") if ua == "" || strings.Contains(ua, "Go-http-client") { // 如果 User-Agent 缺失或包含 Go 指示,设置为 Node.js 格式 req.Header.Set("User-Agent", claude.DefaultUserAgent()) } // 确保 Accept-Encoding 由 utls 而非 http.Client 设置 // (通常 utls 会接管,但为保险起见检查) if req.Header.Get("Accept-Encoding") != "" { // 这通常是安全的,但某些情况下可能需要调整 } // 移除可能暴露 Go 版本的头 req.Header.Del("Go-Version") // 不标准,但为安全起见 } // ValidateAndClean 组合验证和清理 // 返回验证结果和清理后的请求 func ValidateAndClean(req *http.Request) (bool, []string) { CleanRequest(req) return ValidateNodeEmulation(req) }