openai_client_restriction_detector_hardening_test.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. package service
  2. import (
  3. "net/http"
  4. "net/http/httptest"
  5. "testing"
  6. "github.com/Wei-Shaw/sub2api/internal/pkg/openai"
  7. "github.com/gin-gonic/gin"
  8. "github.com/stretchr/testify/require"
  9. )
  10. func hdrCtx(h map[string]string) *gin.Context {
  11. gin.SetMode(gin.TestMode)
  12. rec := httptest.NewRecorder()
  13. c, _ := gin.CreateTestContext(rec)
  14. c.Request = httptest.NewRequest(http.MethodPost, "/v1/responses", nil)
  15. for k, v := range h {
  16. c.Request.Header.Set(k, v)
  17. }
  18. return c
  19. }
  20. func codexOnlyAccount() *Account {
  21. return &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth, Extra: map[string]any{"codex_cli_only": true}}
  22. }
  23. func TestDetect_N1_StrictOfficialUA(t *testing.T) {
  24. det := NewOpenAICodexClientRestrictionDetector(nil)
  25. acc := codexOnlyAccount()
  26. // 构造「中段 codex token」伪装:首段带可解析版本(绕过版本门),codex_app 在中段。
  27. // 旧 lax(Contains) 会判为官方 UA → 放行(空策略指纹门开放);N1 收紧后应判非官方 → NotMatchedUA。
  28. ua := "x/0.141.0 codex_app/0.141.0"
  29. r := det.Detect(hdrCtx(map[string]string{"User-Agent": ua}), acc, CodexRestrictionPolicy{}, nil)
  30. require.False(t, r.Matched)
  31. require.Equal(t, CodexClientRestrictionReasonNotMatchedUA, r.Reason)
  32. }
  33. func TestDetectCodexClientRestriction_NilSettingServiceFailsClosed(t *testing.T) {
  34. gin.SetMode(gin.TestMode)
  35. // settingService 缺失(仅测试/误配可达):账号已开 codex_cli_only、官方 UA、但无 x-codex- 指纹头。
  36. // 零值 policy 不得让指纹门失败开放——gateway 应回退默认种子指纹信号并拒(MissingEngineFingerprint)。
  37. s := &OpenAIGatewayService{}
  38. r := s.detectCodexClientRestriction(hdrCtx(map[string]string{"User-Agent": "codex_cli_rs/0.141.0 (x)"}), codexOnlyAccount(), nil)
  39. require.True(t, r.Enabled)
  40. require.False(t, r.Matched)
  41. require.Equal(t, CodexClientRestrictionReasonMissingEngineFingerprint, r.Reason)
  42. }
  43. func TestDetect_Hardening(t *testing.T) {
  44. det := NewOpenAICodexClientRestrictionDetector(nil)
  45. acc := codexOnlyAccount()
  46. fp := map[string]string{"x-codex-installation-id": "i1"} // 引擎指纹
  47. t.Run("黑名单优先于官方身份", func(t *testing.T) {
  48. pol := CodexRestrictionPolicy{Blacklist: []openai.AllowedClientEntry{{Originator: "codex_cli_rs"}}}
  49. h := map[string]string{"User-Agent": "codex_cli_rs/0.141.0 (x)", "originator": "codex_cli_rs", "x-codex-installation-id": "i1"}
  50. r := det.Detect(hdrCtx(h), acc, pol, nil)
  51. require.False(t, r.Matched)
  52. require.Equal(t, CodexClientRestrictionReasonBlacklisted, r.Reason)
  53. })
  54. t.Run("strict 指纹缺失→拒(即便官方 UA)", func(t *testing.T) {
  55. r := det.Detect(hdrCtx(map[string]string{"User-Agent": "codex_cli_rs/0.141.0 (x)"}), acc, CodexRestrictionPolicy{EngineFingerprintSignals: openai.DefaultEngineFingerprintSignals}, nil)
  56. require.False(t, r.Matched)
  57. require.Equal(t, CodexClientRestrictionReasonMissingEngineFingerprint, r.Reason)
  58. })
  59. t.Run("strict 带指纹→放行", func(t *testing.T) {
  60. h := map[string]string{"User-Agent": "codex_cli_rs/0.141.0 (x)"}
  61. for k, v := range fp {
  62. h[k] = v
  63. }
  64. r := det.Detect(hdrCtx(h), acc, CodexRestrictionPolicy{EngineFingerprintSignals: openai.DefaultEngineFingerprintSignals}, nil)
  65. require.True(t, r.Matched)
  66. })
  67. t.Run("版本过低→拒", func(t *testing.T) {
  68. h := map[string]string{"User-Agent": "codex_cli_rs/0.130.0 (x)"}
  69. for k, v := range fp {
  70. h[k] = v
  71. }
  72. r := det.Detect(hdrCtx(h), acc, CodexRestrictionPolicy{MinCodexVersion: "0.141.0"}, nil)
  73. require.False(t, r.Matched)
  74. require.Equal(t, CodexClientRestrictionReasonVersionTooLow, r.Reason)
  75. })
  76. t.Run("白名单 app-server 新 client + 指纹→放行", func(t *testing.T) {
  77. h := map[string]string{"User-Agent": "opencode/0.141.0 (x)", "originator": "opencode"}
  78. for k, v := range fp {
  79. h[k] = v
  80. }
  81. pol := CodexRestrictionPolicy{
  82. Whitelist: []openai.AllowedClientEntry{{Originator: "opencode", UAContains: []string{"opencode/"}}},
  83. EngineFingerprintSignals: openai.DefaultEngineFingerprintSignals,
  84. }
  85. r := det.Detect(hdrCtx(h), acc, pol, nil)
  86. require.True(t, r.Matched)
  87. require.Equal(t, CodexClientRestrictionReasonMatchedWhitelistClient, r.Reason)
  88. })
  89. t.Run("非 codex 不命中→拒", func(t *testing.T) {
  90. r := det.Detect(hdrCtx(map[string]string{"User-Agent": "curl/8"}), acc, CodexRestrictionPolicy{}, nil)
  91. require.False(t, r.Matched)
  92. require.Equal(t, CodexClientRestrictionReasonNotMatchedUA, r.Reason)
  93. })
  94. t.Run("版本不可识别→拒(originator 命中但 UA 无版本)", func(t *testing.T) {
  95. h := map[string]string{"User-Agent": "curl/8.0", "originator": "codex_chatgpt_desktop"}
  96. for k, v := range fp {
  97. h[k] = v
  98. }
  99. r := det.Detect(hdrCtx(h), acc, CodexRestrictionPolicy{}, nil)
  100. require.False(t, r.Matched)
  101. require.Equal(t, CodexClientRestrictionReasonVersionUndetectable, r.Reason)
  102. })
  103. t.Run("版本过高→拒", func(t *testing.T) {
  104. h := map[string]string{"User-Agent": "codex_cli_rs/0.200.0 (x)"}
  105. for k, v := range fp {
  106. h[k] = v
  107. }
  108. r := det.Detect(hdrCtx(h), acc, CodexRestrictionPolicy{MaxCodexVersion: "0.141.0"}, nil)
  109. require.False(t, r.Matched)
  110. require.Equal(t, CodexClientRestrictionReasonVersionTooHigh, r.Reason)
  111. })
  112. t.Run("max 边界(==max)放行", func(t *testing.T) {
  113. h := map[string]string{"User-Agent": "codex_cli_rs/0.141.0 (x)"}
  114. for k, v := range fp {
  115. h[k] = v
  116. }
  117. r := det.Detect(hdrCtx(h), acc, CodexRestrictionPolicy{MaxCodexVersion: "0.141.0"}, nil)
  118. require.True(t, r.Matched)
  119. })
  120. }