win 435ae221bc
Some checks failed
CI / test (push) Failing after 1m32s
CI / golangci-lint (push) Failing after 31s
Security Scan / backend-security (push) Failing after 1m32s
Security Scan / frontend-security (push) Failing after 9s
x
2026-04-16 19:11:47 +08:00

109 lines
3.4 KiB
Go

// 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)
}