mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 20:12:39 +08:00
fix: validate Codex User-Agent before identity parsing
This commit is contained in:
@@ -3,6 +3,8 @@ package openai
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
|
||||
// CodexCLIUserAgentPrefixes matches Codex CLI User-Agent patterns
|
||||
@@ -198,6 +200,10 @@ func matchCodexClientHeaderStrictPrefixes(value string, prefixes []string) bool
|
||||
// UA 首段后配对,保留真实版本/OS/终端指纹;
|
||||
// 3. 均不命中 → ok=false,调用方应整体回退为默认官方身份。
|
||||
func PairCodexClientIdentity(userAgent string) (originator string, pairedUA string, ok bool) {
|
||||
// Validate before trimming so control bytes cannot become a valid identity.
|
||||
if !validCodexUserAgentValue(userAgent) {
|
||||
return "", "", false
|
||||
}
|
||||
ua := strings.TrimSpace(userAgent)
|
||||
slash := strings.IndexByte(ua, '/')
|
||||
if slash <= 0 {
|
||||
@@ -217,6 +223,15 @@ func PairCodexClientIdentity(userAgent string) (originator string, pairedUA stri
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
func validCodexUserAgentValue(value string) bool {
|
||||
if !httpguts.ValidHeaderFieldValue(value) {
|
||||
return false
|
||||
}
|
||||
// httpguts follows the legacy field-value grammar and permits obs-fold.
|
||||
// User-Agent is not an obs-folded header; reject CR/LF before forwarding.
|
||||
return !strings.ContainsAny(value, "\r\n")
|
||||
}
|
||||
|
||||
// codexOriginatorMaxLen 官方 clientInfo.name 均为短 ASCII 标识,远低于此上限。
|
||||
const codexOriginatorMaxLen = 64
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package openai
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPairCodexClientIdentityRejectsInvalidHeaderBytes(t *testing.T) {
|
||||
for name, ua := range map[string]string{
|
||||
"crlf_suffix": "codex-tui/0.146.0 (Linux)\r\nX-Injected: value",
|
||||
"nul_suffix": "codex-tui/0.146.0 (Linux\x00)",
|
||||
"del_suffix": "codex-tui/0.146.0 terminal\x7f",
|
||||
"newline_prefix": "\ncodex-tui/0.146.0",
|
||||
"newline_suffix": "codex-tui/0.146.0\n",
|
||||
"control_in_trailer": "custom/0.146.0 bad\x01 (codex-tui; 0.146.0)",
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
originator, paired, ok := PairCodexClientIdentity(ua)
|
||||
require.False(t, ok)
|
||||
require.Empty(t, originator)
|
||||
require.Empty(t, paired)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPairCodexClientIdentityPreservesValidSuffix(t *testing.T) {
|
||||
for _, ua := range []string{
|
||||
"codex-tui/0.146.0 (Mac OS X 14.0; arm64) iTerm",
|
||||
"codex-tui/0.146.0 (Windows NT 10.0; x86_64) terminal",
|
||||
"codex-tui/0.146.0 (Linux)\tterminal",
|
||||
} {
|
||||
_, paired, ok := PairCodexClientIdentity(ua)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, ua, paired)
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ func codexCanonicalUserAgent() string {
|
||||
resolver := codexCanonicalUAResolver
|
||||
codexCanonicalUAMu.RUnlock()
|
||||
if resolver != nil {
|
||||
if ua := strings.TrimSpace(resolver()); ua != "" {
|
||||
if ua := resolver(); strings.TrimSpace(ua) != "" {
|
||||
return ua
|
||||
}
|
||||
}
|
||||
@@ -139,8 +139,11 @@ type codexOutboundIdentity struct {
|
||||
// 需要固定版本请填「Codex 客户端版本号」并关闭自动同步。
|
||||
func resolveCodexOutboundIdentity(candidateUA string) codexOutboundIdentity {
|
||||
canonical := codexCanonicalUserAgent()
|
||||
ua := strings.TrimSpace(candidateUA)
|
||||
if ua == "" {
|
||||
if _, _, ok := openai.PairCodexClientIdentity(canonical); !ok {
|
||||
canonical = codexCLIUserAgent
|
||||
}
|
||||
ua := candidateUA
|
||||
if strings.TrimSpace(ua) == "" {
|
||||
ua = canonical
|
||||
}
|
||||
originator, pairedUA, ok := openai.PairCodexClientIdentity(ua)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCanonicalCodexIdentityValidatesBeforeTrimming(t *testing.T) {
|
||||
codexCanonicalUAMu.RLock()
|
||||
previous := codexCanonicalUAResolver
|
||||
codexCanonicalUAMu.RUnlock()
|
||||
t.Cleanup(func() { SetCodexCanonicalUserAgentResolver(previous) })
|
||||
|
||||
for _, raw := range []string{
|
||||
"\ncodex-tui/0.200.1 (Windows)",
|
||||
"codex-tui/0.200.1 (Windows)\n",
|
||||
"codex-tui/0.200.1 bad\x00",
|
||||
} {
|
||||
SetCodexCanonicalUserAgentResolver(func() string { return raw })
|
||||
identity := resolveCodexOutboundIdentity("")
|
||||
require.Equal(t, codexCLIUserAgent, identity.userAgent)
|
||||
require.Equal(t, codexCLIVersion, identity.version)
|
||||
}
|
||||
|
||||
SetCodexCanonicalUserAgentResolver(nil)
|
||||
identity := resolveCodexOutboundIdentity("codex_cli_rs/0.145.2 (Windows)\n")
|
||||
require.Equal(t, codexCLIUserAgent, identity.userAgent)
|
||||
}
|
||||
Reference in New Issue
Block a user