fix(settings): preserve client version on invalid user agent

This commit is contained in:
wucm667
2026-09-19 10:36:51 +08:00
parent efe9aab1e4
commit 2d37088bd9
2 changed files with 69 additions and 13 deletions
@@ -394,13 +394,13 @@ func TestGetOpenAICodexCanonicalUserAgentRebuildsPanelUAVersion(t *testing.T) {
)
})
// 非 `{client}/{version}` 形态无法重建,原样返回,由收口整体回退规范身份。
t.Run("非 Codex 形态原样返回", func(t *testing.T) {
// 非 Codex UA 回退到规范身份。
t.Run("非 Codex 形态回退", func(t *testing.T) {
svc := NewSettingService(&codexVersionSettingRepoStub{values: map[string]string{
SettingKeyOpenAICodexUserAgent: "not-a-codex-client",
}}, nil)
require.Equal(t, "not-a-codex-client", svc.GetOpenAICodexCanonicalUserAgent(context.Background()))
require.Equal(t, codexCLIUserAgent, svc.GetOpenAICodexCanonicalUserAgent(context.Background()))
})
}
@@ -478,3 +478,60 @@ func TestCodexVersionComparisonIsNumericNotLexical(t *testing.T) {
require.Empty(t, repo.syncedWrites(), "0.99.0 低于已同步的 0.146.0,不得写入")
}
func TestGetOpenAICodexCanonicalUserAgentOutboundIdentity(t *testing.T) {
codexCanonicalUAMu.RLock()
previous := codexCanonicalUAResolver
codexCanonicalUAMu.RUnlock()
t.Cleanup(func() { SetCodexCanonicalUserAgentResolver(previous) })
for _, version := range []struct {
name, manual, synced, want string
}{
{"manual", "0.154.0", "0.155.1", "0.154.0"},
{"synced", "", "0.155.1", "0.155.1"},
{"invalid_manual", "latest", "0.155.1", "0.155.1"},
{"default", "", "", codexCLIVersion},
{"invalid_versions", "latest", "invalid", codexCLIVersion},
} {
for _, ua := range []struct {
name, value, client, suffix string
}{
{name: "browser", value: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"},
{name: "arbitrary", value: "not-a-codex-client"},
{name: "unknown_client", value: "custom/0.140.0 (Linux)"},
{name: "empty"},
{name: "missing_version", value: "codex_cli_rs/"},
{name: "crlf", value: "codex_cli_rs/0.140.0 (Linux)\r\nX-Injected: value"},
{name: "nul", value: "codex_cli_rs/0.140.0 (Linux\x00)"},
{name: "del", value: "codex_cli_rs/0.140.0 terminal\x7f"},
{name: "leading_newline", value: "\ncodex_cli_rs/0.140.0 (Linux)"},
{name: "trailing_newline", value: "codex_cli_rs/0.140.0 (Linux)\n"},
{name: "valid_suffix", value: "codex_cli_rs/0.140.0 (Mac OS X 15.1.0; arm64) iTerm.app", client: "codex_cli_rs", suffix: " (Mac OS X 15.1.0; arm64) iTerm.app"},
{name: "valid_tab", value: "codex_cli_rs/0.140.0 (Linux)\tterminal", client: "codex_cli_rs", suffix: " (Linux)\tterminal"},
{name: "recognized_trailer", value: "custom/0.140.0 (Linux) (codex-tui; 0.140.0)", client: "codex-tui", suffix: " (Linux) (codex-tui; " + version.want + ")"},
} {
t.Run(version.name+"/"+ua.name, func(t *testing.T) {
svc := NewSettingService(&codexVersionSettingRepoStub{values: map[string]string{
SettingKeyOpenAICodexUserAgent: ua.value,
SettingKeyOpenAICodexClientVersion: version.manual,
SettingKeyOpenAICodexClientVersionSynced: version.synced,
}}, nil)
wantUA := buildCodexCLIUserAgent(version.want)
wantOriginator := "codex-tui"
if ua.client != "" {
wantUA = ua.client + "/" + version.want + ua.suffix
wantOriginator = ua.client
}
SetCodexCanonicalUserAgentResolver(func() string {
return svc.GetOpenAICodexCanonicalUserAgent(context.Background())
})
require.Equal(t, wantUA, svc.GetOpenAICodexCanonicalUserAgent(context.Background()))
identity := resolveCodexOutboundIdentity("")
require.Equal(t, wantUA, identity.userAgent)
require.Equal(t, wantOriginator, identity.originator)
require.Equal(t, version.want, identity.version)
})
}
}
}
@@ -294,7 +294,8 @@ func (s *SettingService) GetOpenAICodexUserAgent(ctx context.Context) string {
})
return fallback, nil
}
ua := strings.TrimSpace(value)
// Preserve invalid header bytes for canonical identity validation.
ua := strings.Trim(value, " \t")
if ua == "" {
ua = fallback
}
@@ -389,16 +390,14 @@ func (s *SettingService) GetOpenAICodexCanonicalUserAgent(ctx context.Context) s
return codexCLIUserAgent
}
version := s.GetOpenAICodexClientVersion(ctx)
ua := strings.TrimSpace(s.GetOpenAICodexUserAgent(ctx))
if ua == "" {
return buildCodexCLIUserAgent(version)
ua := s.GetOpenAICodexUserAgent(ctx)
if _, pairedUA, ok := openai.PairCodexClientIdentity(ua); ok {
if rebuilt := openai.SetCodexUserAgentVersion(pairedUA, version); rebuilt != "" {
return rebuilt
}
}
if rebuilt := openai.SetCodexUserAgentVersion(ua, version); rebuilt != "" {
return rebuilt
}
// 非 `{client}/{version}` 形态:交给 PairCodexClientIdentity 判定,
// 推导不出官方身份时由收口整体回退规范身份。
return ua
// Invalid fingerprints must not discard the independently resolved version.
return buildCodexCLIUserAgent(version)
}
var legacyClaudeCodeCodexWhitelistEntry = openai.AllowedClientEntry{