fix(claude): 指纹自愈路径同样执行 claude-cli 版本下限

GetOrCreateFingerprint 的 heal 分支(畸形缓存自愈)此前在版本下限逻辑之外:
畸形缓存 + 合法但过旧的客户端 UA 会在首次读取时把过旧 UA 原样落库并返回,
下一次读取才被抬到下限——该窗口内账号仍以过旧版本撞上游客户端版本闸门
(PR #6481 review 指出的边界 case)。

修复:把 floorClaudeCLIUserAgentVersion 后置块从 healthy else 内移到
heal/healthy if-else 之后、TTL 续期检查之前,作为两条缓存命中路径的共同
后置条件;heal 分支 needWrite 本已为 true,单次落库语义不变。

测试:TestGetOrCreateFingerprintHealsPoisonedCacheUsingValidClientUA
期望改为首次返回/落库即 claude-cli/CLICurrentVersion(保留 ClientID 不变、
恰好一次落库断言);floor/UA 全部既有用例保持绿。
This commit is contained in:
alfadb
2026-09-07 16:34:35 +08:00
parent 3cb2381bdc
commit ce157b32ed
2 changed files with 18 additions and 12 deletions
+14 -10
View File
@@ -202,16 +202,20 @@ func (s *IdentityService) GetOrCreateFingerprint(ctx context.Context, accountID
logger.LegacyPrintf("service.identity", "Updated fingerprint for account %d: %s (merge update)", accountID, clientUA)
}
// 版本下限抬升(floor):与上面的客户端升级相互独立、二者取更新者。客户端送来
// 更旧版本时 isNewerVersion 不触发,此处仍能把低于 CLICurrentVersion 的存量指纹
// 就地抬到下限并持久化,否则升 CLICurrentVersion 对所有已有账号无效,新模型的
// 客户端版本闸门(如 Fable 5.1 要求 >= 2.1.251)永远过不去。
if flooredUA, changed := floorClaudeCLIUserAgentVersion(cached.UserAgent); changed {
cached.UserAgent = flooredUA
needWrite = true
logger.LegacyPrintf("service.identity",
"Floored cached fingerprint claude-cli version for account %d: %s", accountID, flooredUA)
}
}
// 版本下限抬升(floor):heal 与 healthy 两条缓存命中路径共同的后置条件,与
// 上面的客户端常规升级相互独立、二者取更新者。客户端送来更旧版本时
// isNewerVersion 不触发,此处仍能把低于 CLICurrentVersion 的存量指纹就地抬到
// 下限并持久化,否则升 CLICurrentVersion 对所有已有账号无效,新模型的客户端
// 版本闸门(如 Fable 5.1 要求 >= 2.1.251)永远过不去。自愈路径同样必须经过:
// 畸形缓存 + 合法但过旧的客户端 UA 首次读取就要落到下限,不能先落库一个
// 过旧版本、等下一次读取才纠正。
if flooredUA, changed := floorClaudeCLIUserAgentVersion(cached.UserAgent); changed {
cached.UserAgent = flooredUA
needWrite = true
logger.LegacyPrintf("service.identity",
"Floored cached fingerprint claude-cli version for account %d: %s", accountID, flooredUA)
}
if !needWrite && time.Since(time.Unix(cached.UpdatedAt, 0)) > 24*time.Hour {
@@ -179,8 +179,10 @@ func TestGetOrCreateFingerprintHealsPoisonedCacheUsingValidClientUA(t *testing.T
fp, err := svc.GetOrCreateFingerprint(context.Background(), 1, headersWithUA(realUA))
require.NoError(t, err)
require.Equal(t, realUA, fp.UserAgent,
"真实客户端必须能从被毒化的指纹手中夺回账号身份")
// 真实客户端从被毒化的指纹手中夺回账号身份,但版本下限在自愈的同一读取内生效:
// 合法而过旧的 UA 不能先以过旧版本落库、等下次读取才被抬升。
require.Equal(t, "claude-cli/"+claude.CLICurrentVersion+" (external, cli)", fp.UserAgent)
require.Equal(t, fp.UserAgent, cache.lastSet.UserAgent, "自愈与下限抬升应合并为同一次落库")
require.Equal(t, 1, cache.setCalls)
require.NotContains(t, cache.lastSet.UserAgent, "999.0.0")
require.Equal(t, "cid-1", fp.ClientID, "自愈不应重置 ClientID")