fix(claude): learn official fingerprints after custom baselines
This commit is contained in:
@@ -70,6 +70,7 @@ type claudeDeviceProfile struct {
|
|||||||
OS string
|
OS string
|
||||||
Arch string
|
Arch string
|
||||||
Version claudeCLIVersion
|
Version claudeCLIVersion
|
||||||
|
HasVersion bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type claudeDeviceProfileCacheEntry struct {
|
type claudeDeviceProfileCacheEntry struct {
|
||||||
@@ -106,6 +107,7 @@ func defaultClaudeDeviceProfile(cfg *config.Config) claudeDeviceProfile {
|
|||||||
}
|
}
|
||||||
if version, ok := parseClaudeCLIVersion(profile.UserAgent); ok {
|
if version, ok := parseClaudeCLIVersion(profile.UserAgent); ok {
|
||||||
profile.Version = version
|
profile.Version = version
|
||||||
|
profile.HasVersion = true
|
||||||
}
|
}
|
||||||
return profile
|
return profile
|
||||||
}
|
}
|
||||||
@@ -161,15 +163,12 @@ func parseClaudeCLIVersion(userAgent string) (claudeCLIVersion, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func shouldUpgradeClaudeDeviceProfile(candidate, current claudeDeviceProfile) bool {
|
func shouldUpgradeClaudeDeviceProfile(candidate, current claudeDeviceProfile) bool {
|
||||||
if candidate.UserAgent == "" {
|
if candidate.UserAgent == "" || !candidate.HasVersion {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if current.UserAgent == "" {
|
if current.UserAgent == "" || !current.HasVersion {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if current.Version == (claudeCLIVersion{}) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return candidate.Version.Compare(current.Version) > 0
|
return candidate.Version.Compare(current.Version) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,11 +182,12 @@ func pinClaudeDeviceProfilePlatform(profile, baseline claudeDeviceProfile) claud
|
|||||||
// baseline platform and enforces the baseline software fingerprint as a floor.
|
// baseline platform and enforces the baseline software fingerprint as a floor.
|
||||||
func normalizeClaudeDeviceProfile(profile, baseline claudeDeviceProfile) claudeDeviceProfile {
|
func normalizeClaudeDeviceProfile(profile, baseline claudeDeviceProfile) claudeDeviceProfile {
|
||||||
profile = pinClaudeDeviceProfilePlatform(profile, baseline)
|
profile = pinClaudeDeviceProfilePlatform(profile, baseline)
|
||||||
if profile.UserAgent == "" || profile.Version == (claudeCLIVersion{}) || shouldUpgradeClaudeDeviceProfile(baseline, profile) {
|
if profile.UserAgent == "" || !profile.HasVersion || shouldUpgradeClaudeDeviceProfile(baseline, profile) {
|
||||||
profile.UserAgent = baseline.UserAgent
|
profile.UserAgent = baseline.UserAgent
|
||||||
profile.PackageVersion = baseline.PackageVersion
|
profile.PackageVersion = baseline.PackageVersion
|
||||||
profile.RuntimeVersion = baseline.RuntimeVersion
|
profile.RuntimeVersion = baseline.RuntimeVersion
|
||||||
profile.Version = baseline.Version
|
profile.Version = baseline.Version
|
||||||
|
profile.HasVersion = baseline.HasVersion
|
||||||
}
|
}
|
||||||
return profile
|
return profile
|
||||||
}
|
}
|
||||||
@@ -211,6 +211,7 @@ func extractClaudeDeviceProfile(headers http.Header, cfg *config.Config) (claude
|
|||||||
OS: firstNonEmptyHeader(headers, "X-Stainless-Os", baseline.OS),
|
OS: firstNonEmptyHeader(headers, "X-Stainless-Os", baseline.OS),
|
||||||
Arch: firstNonEmptyHeader(headers, "X-Stainless-Arch", baseline.Arch),
|
Arch: firstNonEmptyHeader(headers, "X-Stainless-Arch", baseline.Arch),
|
||||||
Version: version,
|
Version: version,
|
||||||
|
HasVersion: true,
|
||||||
}
|
}
|
||||||
return profile, true
|
return profile, true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -260,6 +260,58 @@ func TestApplyClaudeHeaders_UpgradesCachedSoftwareFingerprintWhenBaselineAdvance
|
|||||||
assertClaudeFingerprint(t, thirdPartyReq.Header, "claude-cli/2.1.77 (external, cli)", "0.87.0", "v24.8.0", "MacOS", "arm64")
|
assertClaudeFingerprint(t, thirdPartyReq.Header, "claude-cli/2.1.77 (external, cli)", "0.87.0", "v24.8.0", "MacOS", "arm64")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApplyClaudeHeaders_LearnsOfficialFingerprintAfterCustomBaselineFallback(t *testing.T) {
|
||||||
|
resetClaudeDeviceProfileCache()
|
||||||
|
stabilize := true
|
||||||
|
|
||||||
|
cfg := &config.Config{
|
||||||
|
ClaudeHeaderDefaults: config.ClaudeHeaderDefaults{
|
||||||
|
UserAgent: "my-gateway/1.0",
|
||||||
|
PackageVersion: "custom-pkg",
|
||||||
|
RuntimeVersion: "custom-runtime",
|
||||||
|
OS: "MacOS",
|
||||||
|
Arch: "arm64",
|
||||||
|
StabilizeDeviceProfile: &stabilize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
auth := &cliproxyauth.Auth{
|
||||||
|
ID: "auth-custom-baseline-learning",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"api_key": "key-custom-baseline-learning",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
thirdPartyReq := newClaudeHeaderTestRequest(t, http.Header{
|
||||||
|
"User-Agent": []string{"curl/8.7.1"},
|
||||||
|
"X-Stainless-Package-Version": []string{"0.10.0"},
|
||||||
|
"X-Stainless-Runtime-Version": []string{"v18.0.0"},
|
||||||
|
"X-Stainless-Os": []string{"Linux"},
|
||||||
|
"X-Stainless-Arch": []string{"x64"},
|
||||||
|
})
|
||||||
|
applyClaudeHeaders(thirdPartyReq, auth, "key-custom-baseline-learning", false, nil, cfg)
|
||||||
|
assertClaudeFingerprint(t, thirdPartyReq.Header, "my-gateway/1.0", "custom-pkg", "custom-runtime", "MacOS", "arm64")
|
||||||
|
|
||||||
|
officialReq := newClaudeHeaderTestRequest(t, http.Header{
|
||||||
|
"User-Agent": []string{"claude-cli/2.1.77 (external, cli)"},
|
||||||
|
"X-Stainless-Package-Version": []string{"0.87.0"},
|
||||||
|
"X-Stainless-Runtime-Version": []string{"v24.8.0"},
|
||||||
|
"X-Stainless-Os": []string{"Linux"},
|
||||||
|
"X-Stainless-Arch": []string{"x64"},
|
||||||
|
})
|
||||||
|
applyClaudeHeaders(officialReq, auth, "key-custom-baseline-learning", false, nil, cfg)
|
||||||
|
assertClaudeFingerprint(t, officialReq.Header, "claude-cli/2.1.77 (external, cli)", "0.87.0", "v24.8.0", "MacOS", "arm64")
|
||||||
|
|
||||||
|
postLearningThirdPartyReq := newClaudeHeaderTestRequest(t, http.Header{
|
||||||
|
"User-Agent": []string{"curl/8.7.1"},
|
||||||
|
"X-Stainless-Package-Version": []string{"0.10.0"},
|
||||||
|
"X-Stainless-Runtime-Version": []string{"v18.0.0"},
|
||||||
|
"X-Stainless-Os": []string{"Linux"},
|
||||||
|
"X-Stainless-Arch": []string{"x64"},
|
||||||
|
})
|
||||||
|
applyClaudeHeaders(postLearningThirdPartyReq, auth, "key-custom-baseline-learning", false, nil, cfg)
|
||||||
|
assertClaudeFingerprint(t, postLearningThirdPartyReq.Header, "claude-cli/2.1.77 (external, cli)", "0.87.0", "v24.8.0", "MacOS", "arm64")
|
||||||
|
}
|
||||||
|
|
||||||
func TestResolveClaudeDeviceProfile_RechecksCacheBeforeStoringCandidate(t *testing.T) {
|
func TestResolveClaudeDeviceProfile_RechecksCacheBeforeStoringCandidate(t *testing.T) {
|
||||||
resetClaudeDeviceProfileCache()
|
resetClaudeDeviceProfileCache()
|
||||||
stabilize := true
|
stabilize := true
|
||||||
|
|||||||
Reference in New Issue
Block a user