Files
anthropic-proxy/internal/proxy/billing.go
T
Alexander c4c1d4daa4 Anthropic API proxy with OAuth credential rotation and Claude Code fingerprinting
Sniffs a real Claude Code request on startup to capture exact HTTP headers,
then replays them for all proxied requests. Injects the billing header with
per-request SHA256 fingerprint into the system prompt. Uses utls with Chrome
TLS fingerprint to pass Cloudflare's bot detection on api.anthropic.com.

Supports both streaming (SSE) and non-streaming modes, round-robin credential
selection with automatic failover, and loading OAuth tokens from both
cli-proxy-api auth files and native ~/.claude/.credentials.json.
2026-04-09 21:05:32 +02:00

91 lines
2.4 KiB
Go

package proxy
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"unicode/utf16"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
const fingerprintSalt = "59cf53e54c78"
func computeFingerprint(firstUserMessage string, version string) string {
indices := []int{4, 7, 20}
runes := utf16.Encode([]rune(firstUserMessage))
var chars string
for _, i := range indices {
if i < len(runes) {
chars += string(rune(runes[i]))
} else {
chars += "0"
}
}
input := fingerprintSalt + chars + version
hash := sha256.Sum256([]byte(input))
return hex.EncodeToString(hash[:])[:3]
}
func extractFirstUserMessage(body []byte) string {
messages := gjson.GetBytes(body, "messages")
if !messages.Exists() || !messages.IsArray() {
return ""
}
for _, msg := range messages.Array() {
if msg.Get("role").String() != "user" {
continue
}
content := msg.Get("content")
if content.Type == gjson.String {
return content.String()
}
if content.IsArray() {
for _, block := range content.Array() {
if block.Get("type").String() == "text" {
return block.Get("text").String()
}
}
}
break
}
return ""
}
func buildBillingHeader(body []byte, version string) string {
userMsg := extractFirstUserMessage(body)
fp := computeFingerprint(userMsg, version)
return fmt.Sprintf("x-anthropic-billing-header: cc_version=%s.%s; cc_entrypoint=cli; cch=00000;", version, fp)
}
func injectBillingHeader(body []byte, version string) []byte {
header := buildBillingHeader(body, version)
billingBlock := map[string]interface{}{"type": "text", "text": header}
billingJSON, _ := json.Marshal(billingBlock)
existing := gjson.GetBytes(body, "system")
if !existing.Exists() {
body, _ = sjson.SetRawBytes(body, "system", []byte("["+string(billingJSON)+"]"))
return body
}
if existing.IsArray() {
items := make([]json.RawMessage, 0, len(existing.Array())+1)
items = append(items, billingJSON)
for _, item := range existing.Array() {
items = append(items, json.RawMessage(item.Raw))
}
systemJSON, _ := json.Marshal(items)
body, _ = sjson.SetRawBytes(body, "system", systemJSON)
return body
}
origText := existing.String()
origBlock := map[string]string{"type": "text", "text": origText}
origJSON, _ := json.Marshal(origBlock)
body, _ = sjson.SetRawBytes(body, "system", []byte("["+string(billingJSON)+","+string(origJSON)+"]"))
return body
}