mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-11-10 17:02:32 +00:00
将 codex_cli_only 客户端识别从「单一 strict 开关 + 固定 OR 头集合」重构为 可逐项管理的引擎指纹信号列表,加固整条判定链,并补齐账号级 app-server 控制、 对齐前端设置文案。 判定链(每步可短路): - 账号未开 codex_cli_only → 不限制;gateway.force_codex_cli → 旁路放行 - 全局黑名单命中(OR 宽 deny)→ 立即拒 - 身份候选:官方 UA(strict,仅前缀)/ 官方 originator(OR)/ 全局白名单(双因子 AND) / 全局 app-server 开关 OR 账号 app-server 开关;均不命中 → 拒 - 版本门(仅官方候选):UA 须可解析引擎版本,再校验 [min,max] 区间 - 引擎指纹 AND 硬门:按信号列表逐条勾选 AND、每条行内变体 OR;无 Required 信号 → 放行 引擎指纹信号列表(唯一真源) - 新增 openai.EngineFingerprintSignal 类型 + EvaluateEngineFingerprint 求值器 (勾选 AND / 行内变体 OR / 无勾选 → 放行) - CodexRestrictionPolicy 增 EngineFingerprintSignals;信号列表单一决定是否启用指纹门, 不再保留独立「要求引擎指纹」总开关(与「信号全不选」语义重复) - 新设置键 codex_cli_only_engine_fingerprint_signals(默认只勾 x-codex- 前缀); 旧 body 指纹开关幂等迁移并入信号列表;wire 接线 - 黑/白名单自由条目、命名预设、版本区间 全局设置管线 - gateway 缺 settingService(仅测试/误配可达)时指纹门回退默认种子信号、失败关闭, 不再因零值 policy(nil 信号)失败开放 账号级 Codex app-server(替换已失效的 ClaudeCode 放行机制) - account.IsCodexCLIOnlyAppServerAllowed() 读 extra.codex_cli_only_allow_app_server, 仅在 codex_cli_only 开启时生效;候选身份门「全局 OR 账号」,与旧系统双层控制对齐 - 移除已无入口的 claude_code 预设机制(allowedClientRegistry / MatchAllowedClients / 账号 GetCodexCLIOnlyAllowedClients / reason);白名单 AllowedClientEntry / IsAllowedClientMatch 保留 门加固(反伪 + 写入校验) - 官方 UA 访问门改 strict:IsCodexOfficialClientRequestStrict 仅前缀匹配,收窄「浏览器前缀 + 中段 codex token」伪造面(strict 仍保留 Codex 家族前缀与 UA 尾部兜底,故对「任意前缀 + 官方尾部 (name;ver)」仍放行——与 UA 可伪造、真正反伪靠引擎指纹门的设计一致) - 官方客户端识别扩展:新增 codex-tui/、codex_vscode_copilot/ 前缀 + UA 尾部 (name;ver) 兜底 (恢复 CODEX_INTERNAL_ORIGINATOR_OVERRIDE 的真实 client,如 cccc→codex-tui),originator 改 精确集。该识别经 IsCodexOfficialClientByHeaders 被 passthrough 复用,故透传的官方判定一并 修正(codex-tui 等不再被误改写 UA)——非「行为不变」,属有意修正 - 白名单写入校验 ValidateCodexWhitelistEntriesJSON + AllowedClientEntry.IsWhitelistable: 双因子 AND 条目须可命中(非空 originator + 非空 ua_contains),拒绝写入会静默失效的死规则; 黑名单(OR 宽 deny,允许 originator-only)不受约束 管理端 / 前端 - handler / DTO / settings_view / 契约测试;gateway 接入判定链 - 信号列表编辑器(替换 body 开关)、api 类型、SettingsView;无勾选给常驻警告 - Create/Edit/Bulk 三弹窗「Codex Only」下新增 app-server 开关(OR 合并全局) - 文案:UA/Originator → User-Agent/Originator;黑/白名单重命名为 User-Agent/Originator 黑/白名单; 「允许 App Server 第三方客户端」→「Codex app-server」+ 简介示例;i18n zh/en 同步 - 移除死代码 HasCodex*Fingerprint helper 测试:引擎指纹求值器 / 账号 app-server(OR 语义)/ detector(含 N1 strict、失败关闭)/ 白名单写入校验 / BulkEdit spec 等;后端 build + service/openai/admin 单测全绿,前端 vue-tsc + vitest 全绿。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
363 lines
15 KiB
Go
363 lines
15 KiB
Go
package service
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/config"
|
|
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func newCodexDetectorTestContext(ua string, originator string) *gin.Context {
|
|
rec := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(rec)
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/v1/responses", nil)
|
|
if ua != "" {
|
|
c.Request.Header.Set("User-Agent", ua)
|
|
}
|
|
if originator != "" {
|
|
c.Request.Header.Set("originator", originator)
|
|
}
|
|
return c
|
|
}
|
|
|
|
func TestOpenAICodexClientRestrictionDetector_Detect(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
t.Run("未开启开关时绕过", func(t *testing.T) {
|
|
detector := NewOpenAICodexClientRestrictionDetector(nil)
|
|
account := &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth, Extra: map[string]any{}}
|
|
|
|
result := detector.Detect(newCodexDetectorTestContext("curl/8.0", ""), account, CodexRestrictionPolicy{}, nil)
|
|
require.False(t, result.Enabled)
|
|
require.False(t, result.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonDisabled, result.Reason)
|
|
})
|
|
|
|
t.Run("开启后 codex_cli_rs 命中", func(t *testing.T) {
|
|
detector := NewOpenAICodexClientRestrictionDetector(nil)
|
|
account := &Account{
|
|
Platform: PlatformOpenAI,
|
|
Type: AccountTypeOAuth,
|
|
Extra: map[string]any{"codex_cli_only": true},
|
|
}
|
|
|
|
result := detector.Detect(newCodexDetectorTestContext("codex_cli_rs/0.99.0", ""), account, CodexRestrictionPolicy{}, nil)
|
|
require.True(t, result.Enabled)
|
|
require.True(t, result.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonMatchedUA, result.Reason)
|
|
})
|
|
|
|
t.Run("开启后 codex_vscode 命中", func(t *testing.T) {
|
|
detector := NewOpenAICodexClientRestrictionDetector(nil)
|
|
account := &Account{
|
|
Platform: PlatformOpenAI,
|
|
Type: AccountTypeOAuth,
|
|
Extra: map[string]any{"codex_cli_only": true},
|
|
}
|
|
|
|
result := detector.Detect(newCodexDetectorTestContext("codex_vscode/1.0.0", ""), account, CodexRestrictionPolicy{}, nil)
|
|
require.True(t, result.Enabled)
|
|
require.True(t, result.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonMatchedUA, result.Reason)
|
|
})
|
|
|
|
t.Run("开启后 codex_app 命中", func(t *testing.T) {
|
|
detector := NewOpenAICodexClientRestrictionDetector(nil)
|
|
account := &Account{
|
|
Platform: PlatformOpenAI,
|
|
Type: AccountTypeOAuth,
|
|
Extra: map[string]any{"codex_cli_only": true},
|
|
}
|
|
|
|
result := detector.Detect(newCodexDetectorTestContext("codex_app/2.1.0", ""), account, CodexRestrictionPolicy{}, nil)
|
|
require.True(t, result.Enabled)
|
|
require.True(t, result.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonMatchedUA, result.Reason)
|
|
})
|
|
|
|
t.Run("开启后 originator 命中", func(t *testing.T) {
|
|
detector := NewOpenAICodexClientRestrictionDetector(nil)
|
|
account := &Account{
|
|
Platform: PlatformOpenAI,
|
|
Type: AccountTypeOAuth,
|
|
Extra: map[string]any{"codex_cli_only": true},
|
|
}
|
|
|
|
result := detector.Detect(newCodexDetectorTestContext("myterm/0.141.0", "codex_chatgpt_desktop"), account, CodexRestrictionPolicy{}, nil)
|
|
require.True(t, result.Enabled)
|
|
require.True(t, result.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonMatchedOriginator, result.Reason)
|
|
})
|
|
|
|
t.Run("开启后非官方客户端拒绝", func(t *testing.T) {
|
|
detector := NewOpenAICodexClientRestrictionDetector(nil)
|
|
account := &Account{
|
|
Platform: PlatformOpenAI,
|
|
Type: AccountTypeOAuth,
|
|
Extra: map[string]any{"codex_cli_only": true},
|
|
}
|
|
|
|
result := detector.Detect(newCodexDetectorTestContext("curl/8.0", "my_client"), account, CodexRestrictionPolicy{}, nil)
|
|
require.True(t, result.Enabled)
|
|
require.False(t, result.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonNotMatchedUA, result.Reason)
|
|
})
|
|
|
|
t.Run("开启 ForceCodexCLI 时允许通过", func(t *testing.T) {
|
|
detector := NewOpenAICodexClientRestrictionDetector(&config.Config{
|
|
Gateway: config.GatewayConfig{ForceCodexCLI: true},
|
|
})
|
|
account := &Account{
|
|
Platform: PlatformOpenAI,
|
|
Type: AccountTypeOAuth,
|
|
Extra: map[string]any{"codex_cli_only": true},
|
|
}
|
|
|
|
result := detector.Detect(newCodexDetectorTestContext("curl/8.0", "my_client"), account, CodexRestrictionPolicy{}, nil)
|
|
require.True(t, result.Enabled)
|
|
require.True(t, result.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonForceCodexCLI, result.Reason)
|
|
})
|
|
}
|
|
|
|
func TestOpenAICodexClientRestrictionDetector_Detect_AllowedClients(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
const (
|
|
claudeCodeUA = "Claude Code/0.5.0 (Macos 15.5; arm64) iTerm2.app (Claude Code; 1.0.4)"
|
|
claudeCodeOriginator = "Claude Code"
|
|
)
|
|
|
|
t.Run("未配置白名单时 Claude Code 签名仍拒绝", func(t *testing.T) {
|
|
detector := NewOpenAICodexClientRestrictionDetector(nil)
|
|
account := &Account{
|
|
Platform: PlatformOpenAI,
|
|
Type: AccountTypeOAuth,
|
|
Extra: map[string]any{"codex_cli_only": true},
|
|
}
|
|
|
|
result := detector.Detect(newCodexDetectorTestContext(claudeCodeUA, claudeCodeOriginator), account, CodexRestrictionPolicy{}, nil)
|
|
require.True(t, result.Enabled)
|
|
require.False(t, result.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonNotMatchedUA, result.Reason)
|
|
})
|
|
|
|
t.Run("未开启 codex_cli_only 时直接绕过", func(t *testing.T) {
|
|
detector := NewOpenAICodexClientRestrictionDetector(nil)
|
|
account := &Account{
|
|
Platform: PlatformOpenAI,
|
|
Type: AccountTypeOAuth,
|
|
Extra: map[string]any{},
|
|
}
|
|
|
|
result := detector.Detect(newCodexDetectorTestContext(claudeCodeUA, claudeCodeOriginator), account, CodexRestrictionPolicy{}, nil)
|
|
require.False(t, result.Enabled)
|
|
require.False(t, result.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonDisabled, result.Reason)
|
|
})
|
|
|
|
t.Run("全局白名单含 Claude Code 签名 → 放行(whitelist)", func(t *testing.T) {
|
|
detector := NewOpenAICodexClientRestrictionDetector(nil)
|
|
account := &Account{
|
|
Platform: PlatformOpenAI,
|
|
Type: AccountTypeOAuth,
|
|
Extra: map[string]any{"codex_cli_only": true},
|
|
}
|
|
result := detector.Detect(
|
|
newCodexDetectorTestContext("Claude Code/0.5.0 (Macos 15.5; arm64) iTerm2.app (Claude Code; 1.0.4)", "Claude Code"),
|
|
account,
|
|
CodexRestrictionPolicy{Whitelist: []openai.AllowedClientEntry{{Originator: "Claude Code", UAContains: []string{"Claude Code/"}}}},
|
|
nil,
|
|
)
|
|
require.True(t, result.Enabled)
|
|
require.True(t, result.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonMatchedWhitelistClient, result.Reason)
|
|
})
|
|
|
|
t.Run("全局白名单含 Claude Code + 非签名 → 403", func(t *testing.T) {
|
|
detector := NewOpenAICodexClientRestrictionDetector(nil)
|
|
account := &Account{
|
|
Platform: PlatformOpenAI,
|
|
Type: AccountTypeOAuth,
|
|
Extra: map[string]any{"codex_cli_only": true},
|
|
}
|
|
result := detector.Detect(
|
|
newCodexDetectorTestContext("curl/8.0", "my_client"),
|
|
account,
|
|
CodexRestrictionPolicy{Whitelist: []openai.AllowedClientEntry{{Originator: "Claude Code", UAContains: []string{"Claude Code/"}}}},
|
|
nil,
|
|
)
|
|
require.True(t, result.Enabled)
|
|
require.False(t, result.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonNotMatchedUA, result.Reason)
|
|
})
|
|
|
|
t.Run("全局列表为空 + 账号未配 → 403", func(t *testing.T) {
|
|
detector := NewOpenAICodexClientRestrictionDetector(nil)
|
|
account := &Account{
|
|
Platform: PlatformOpenAI,
|
|
Type: AccountTypeOAuth,
|
|
Extra: map[string]any{"codex_cli_only": true},
|
|
}
|
|
result := detector.Detect(
|
|
newCodexDetectorTestContext("Claude Code/0.5.0 (Macos) (Claude Code; 1.0.4)", "Claude Code"),
|
|
account,
|
|
CodexRestrictionPolicy{},
|
|
nil,
|
|
)
|
|
require.True(t, result.Enabled)
|
|
require.False(t, result.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonNotMatchedUA, result.Reason)
|
|
})
|
|
|
|
}
|
|
|
|
func TestDetect_V3_AppServerAndSkipAndVersionScope(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
acc := func() *Account {
|
|
return &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth, Extra: map[string]any{"codex_cli_only": true}}
|
|
}
|
|
|
|
t.Run("AppServer OFF:未列名客户端拒", func(t *testing.T) {
|
|
d := NewOpenAICodexClientRestrictionDetector(nil)
|
|
r := d.Detect(newCodexDetectorTestContext("opencode/1.0", "opencode"), acc(), CodexRestrictionPolicy{}, nil)
|
|
require.False(t, r.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonNotMatchedUA, r.Reason)
|
|
})
|
|
|
|
t.Run("AppServer ON + 引擎头 → 放行(app_server)", func(t *testing.T) {
|
|
d := NewOpenAICodexClientRestrictionDetector(nil)
|
|
c := newCodexDetectorTestContext("opencode/1.0", "opencode")
|
|
c.Request.Header.Set("x-codex-window-id", "1")
|
|
r := d.Detect(c, acc(), CodexRestrictionPolicy{AllowAppServerClients: true, EngineFingerprintSignals: openai.DefaultEngineFingerprintSignals}, nil)
|
|
require.True(t, r.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonMatchedAppServerClient, r.Reason)
|
|
})
|
|
|
|
t.Run("AppServer ON + 无引擎头 + strict → 拒", func(t *testing.T) {
|
|
d := NewOpenAICodexClientRestrictionDetector(nil)
|
|
r := d.Detect(newCodexDetectorTestContext("opencode/1.0", "opencode"), acc(), CodexRestrictionPolicy{AllowAppServerClients: true, EngineFingerprintSignals: openai.DefaultEngineFingerprintSignals}, nil)
|
|
require.False(t, r.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonMissingEngineFingerprint, r.Reason)
|
|
})
|
|
|
|
t.Run("白名单 skip=true + 无引擎头 + strict → 放行", func(t *testing.T) {
|
|
d := NewOpenAICodexClientRestrictionDetector(nil)
|
|
pol := CodexRestrictionPolicy{
|
|
Whitelist: []openai.AllowedClientEntry{{Originator: "opencode", UAContains: []string{"opencode/"}, SkipEngineFingerprint: true}},
|
|
}
|
|
r := d.Detect(newCodexDetectorTestContext("opencode/1.0", "opencode"), acc(), pol, nil)
|
|
require.True(t, r.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonMatchedWhitelistClient, r.Reason)
|
|
})
|
|
|
|
t.Run("白名单 skip=false + 无引擎头 + strict → 拒", func(t *testing.T) {
|
|
d := NewOpenAICodexClientRestrictionDetector(nil)
|
|
pol := CodexRestrictionPolicy{
|
|
EngineFingerprintSignals: openai.DefaultEngineFingerprintSignals,
|
|
Whitelist: []openai.AllowedClientEntry{{Originator: "opencode", UAContains: []string{"opencode/"}}},
|
|
}
|
|
r := d.Detect(newCodexDetectorTestContext("opencode/1.0", "opencode"), acc(), pol, nil)
|
|
require.False(t, r.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonMissingEngineFingerprint, r.Reason)
|
|
})
|
|
|
|
t.Run("版本门仅官方:白名单无版本不拒", func(t *testing.T) {
|
|
d := NewOpenAICodexClientRestrictionDetector(nil)
|
|
pol := CodexRestrictionPolicy{
|
|
Whitelist: []openai.AllowedClientEntry{{Originator: "opencode", UAContains: []string{"opencode"}}},
|
|
}
|
|
r := d.Detect(newCodexDetectorTestContext("opencode", "opencode"), acc(), pol, nil)
|
|
require.True(t, r.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonMatchedWhitelistClient, r.Reason)
|
|
})
|
|
|
|
t.Run("版本门仍卡官方:官方 originator 无版本 → VersionUndetectable", func(t *testing.T) {
|
|
d := NewOpenAICodexClientRestrictionDetector(nil)
|
|
r := d.Detect(newCodexDetectorTestContext("noversion", "codex_cli_rs"), acc(), CodexRestrictionPolicy{}, nil)
|
|
require.False(t, r.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonVersionUndetectable, r.Reason)
|
|
})
|
|
}
|
|
|
|
func TestDetect_EngineFingerprintSignals(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
det := NewOpenAICodexClientRestrictionDetector(&config.Config{})
|
|
acct := &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth, Extra: map[string]any{"codex_cli_only": true}}
|
|
|
|
officialUA := "codex_cli_rs/0.141.0 (x) (codex_cli_rs; 0.141.0)"
|
|
policy := CodexRestrictionPolicy{
|
|
EngineFingerprintSignals: openai.DefaultEngineFingerprintSignals, // 只勾 x-codex-
|
|
}
|
|
|
|
t.Run("官方UA+带x-codex-头 → 放行", func(t *testing.T) {
|
|
c := newCodexDetectorTestContext(officialUA, "")
|
|
c.Request.Header.Set("x-codex-window-id", "a1")
|
|
got := det.Detect(c, acct, policy, nil)
|
|
require.True(t, got.Matched)
|
|
})
|
|
t.Run("官方UA+无x-codex-头 → 拒(缺指纹)", func(t *testing.T) {
|
|
c := newCodexDetectorTestContext(officialUA, "")
|
|
c.Request.Header.Set("session-id", "u1") // 默认 session 未勾,不满足必须项
|
|
got := det.Detect(c, acct, policy, nil)
|
|
require.False(t, got.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonMissingEngineFingerprint, got.Reason)
|
|
})
|
|
t.Run("body通道: 勾body_path后 仅body命中 → 放行", func(t *testing.T) {
|
|
bodyPolicy := CodexRestrictionPolicy{
|
|
EngineFingerprintSignals: []openai.EngineFingerprintSignal{
|
|
{Type: openai.FingerprintSignalBodyPath, Match: []string{"client_metadata.x-codex-window-id"}, Required: true},
|
|
},
|
|
}
|
|
c := newCodexDetectorTestContext(officialUA, "")
|
|
got := det.Detect(c, acct, bodyPolicy, []byte(`{"client_metadata":{"x-codex-window-id":"c3"}}`))
|
|
require.True(t, got.Matched)
|
|
})
|
|
}
|
|
|
|
func TestDetect_AccountAppServerToggle(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
d := NewOpenAICodexClientRestrictionDetector(nil)
|
|
acctOn := func() *Account {
|
|
return &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth, Extra: map[string]any{"codex_cli_only": true, "codex_cli_only_allow_app_server": true}}
|
|
}
|
|
acctOff := func() *Account {
|
|
return &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth, Extra: map[string]any{"codex_cli_only": true}}
|
|
}
|
|
withFP := func(ua, originator string) *gin.Context {
|
|
c := newCodexDetectorTestContext(ua, originator)
|
|
c.Request.Header.Set("x-codex-window-id", "1")
|
|
return c
|
|
}
|
|
defaultSignals := CodexRestrictionPolicy{EngineFingerprintSignals: openai.DefaultEngineFingerprintSignals}
|
|
|
|
t.Run("账号 app-server ON + 引擎头 → 放行(全局 OFF 也放行)", func(t *testing.T) {
|
|
r := d.Detect(withFP("opencode/1.0", "opencode"), acctOn(), defaultSignals, nil)
|
|
require.True(t, r.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonMatchedAppServerClient, r.Reason)
|
|
})
|
|
|
|
t.Run("账号 app-server ON + 无引擎头 → 拒(缺指纹)", func(t *testing.T) {
|
|
r := d.Detect(newCodexDetectorTestContext("opencode/1.0", "opencode"), acctOn(), defaultSignals, nil)
|
|
require.False(t, r.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonMissingEngineFingerprint, r.Reason)
|
|
})
|
|
|
|
t.Run("账号 app-server OFF + 全局 OFF → 拒(未命中)", func(t *testing.T) {
|
|
r := d.Detect(withFP("opencode/1.0", "opencode"), acctOff(), defaultSignals, nil)
|
|
require.False(t, r.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonNotMatchedUA, r.Reason)
|
|
})
|
|
|
|
t.Run("账号 app-server OFF + 全局 ON → 放行(OR)", func(t *testing.T) {
|
|
pol := CodexRestrictionPolicy{AllowAppServerClients: true, EngineFingerprintSignals: openai.DefaultEngineFingerprintSignals}
|
|
r := d.Detect(withFP("opencode/1.0", "opencode"), acctOff(), pol, nil)
|
|
require.True(t, r.Matched)
|
|
require.Equal(t, CodexClientRestrictionReasonMatchedAppServerClient, r.Reason)
|
|
})
|
|
}
|