Files
sub2api/backend/internal/pkg/openai/request_identity_test.go
T
shaw 8a51119e39 fix(openai): pair originator with final User-Agent for codex upstream
上游 /backend-api/codex 的 404 gating 是配套校验:originator 必须与
User-Agent 首段(首个 '/' 前的 client 名)配套且为官方 codex 身份,
错配(如 originator=codex_cli_rs + UA=codex-tui/...)一律 404;version
头若携带须 >= 0.144.0。

修复:
- pkg/openai 新增 PairCodexClientIdentity:由最终出站 UA 推导配套
  originator(含 CODEX_INTERNAL_ORIGINATOR_OVERRIDE 尾部括号组身份恢复、
  精确集合大小写归一、长度/字符集校验)
- service 新增 enforceCodexIdentityHeaders 终态收口:originator 按最终
  UA 重配,推导不出官方身份整体回退默认 Codex CLI 身份;version 低于
  0.144.0 提升为内置版本;originator 缺失时 no-op(保护 messages bridge)
- 接入非透传/透传/WS 三条转发路径(透传与 WS 删除会把 codex-tui 等官方
  UA 强改为 codex_cli_rs 造成错配的旧兜底)、用量探针(指纹缓存 UA 与硬
  编码 originator 错配)、账号 responses/图像测试(opencode 错配)
- messages 兼容桥构建前显式打 bridge 标记,防止映射到非 gpt-5/codex
  模型时收口误改其刻意最小化的请求形态

Fixes #3901
2026-07-10 19:11:26 +08:00

87 lines
3.2 KiB
Go

package openai
import (
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func TestPairCodexClientIdentity(t *testing.T) {
tests := []struct {
name string
ua string
wantOriginator string
wantUA string
wantOK bool
}{
{
name: "cli 首段直接配对",
ua: "codex_cli_rs/0.144.1 (Ubuntu 22.4.0; x86_64) xterm-256color",
wantOriginator: "codex_cli_rs",
wantUA: "codex_cli_rs/0.144.1 (Ubuntu 22.4.0; x86_64) xterm-256color",
wantOK: true,
},
{
name: "tui 首段直接配对",
ua: "codex-tui/0.140.2 (Mac OS X 14.0; arm64) iTerm (codex-tui; 0.140.2)",
wantOriginator: "codex-tui",
wantUA: "codex-tui/0.140.2 (Mac OS X 14.0; arm64) iTerm (codex-tui; 0.140.2)",
wantOK: true,
},
{
name: "Codex 家族前缀配对保留原大小写",
ua: "Codex Desktop/1.2.3",
wantOriginator: "Codex Desktop",
wantUA: "Codex Desktop/1.2.3",
wantOK: true,
},
{
name: "originator override 用尾部 name 重写首段",
ua: "cccc/0.142.0 (Ubuntu 22.4.0; x86_64) screen (codex-tui; 0.142.0)",
wantOriginator: "codex-tui",
wantUA: "codex-tui/0.142.0 (Ubuntu 22.4.0; x86_64) screen (codex-tui; 0.142.0)",
wantOK: true,
},
{
name: "override 尾部恢复保留 Codex 家族真实大小写",
ua: "cccc/1.2.3 (Ubuntu 22.4.0; x86_64) term (Codex Desktop; 1.2.3)",
wantOriginator: "Codex Desktop",
wantUA: "Codex Desktop/1.2.3 (Ubuntu 22.4.0; x86_64) term (Codex Desktop; 1.2.3)",
wantOK: true,
},
{name: "含斜杠的尾部 name 拒绝配对(防自不一致身份)", ua: "foo/1.0 (Codex Desktop/2; 1.0)", wantOK: false},
{
name: "精确集合大小写变体归一为规范小写",
ua: "CODEX_CLI_RS/1.0.0",
wantOriginator: "codex_cli_rs",
wantUA: "codex_cli_rs/1.0.0",
wantOK: true,
},
{
name: "首段尾随空格重建为规范 UA",
ua: "codex-tui /1.0.0",
wantOriginator: "codex-tui",
wantUA: "codex-tui/1.0.0",
wantOK: true,
},
{name: "家族前缀夹带不可打印字节拒绝", ua: "Codex \x01evil/1.0.0", wantOK: false},
{name: "家族前缀夹带非 ASCII 字节拒绝", ua: "Codex \xc3\xa9vil/1.0.0", wantOK: false},
{name: "超长首段拒绝", ua: "Codex " + strings.Repeat("a", 80) + "/1.0.0", wantOK: false},
{name: "第三方 UA 不可配对", ua: "luna/1.0.0", wantOK: false},
{name: "伪造前缀不可配对", ua: "codex_cli_rs_evil/1.0.0", wantOK: false},
{name: "浏览器 UA 不可配对", ua: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36", wantOK: false},
{name: "无斜杠不可配对", ua: "curl", wantOK: false},
{name: "空 UA 不可配对", ua: "", wantOK: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
originator, pairedUA, ok := PairCodexClientIdentity(tt.ua)
require.Equal(t, tt.wantOK, ok)
require.Equal(t, tt.wantOriginator, originator)
require.Equal(t, tt.wantUA, pairedUA)
})
}
}