allowed_client_test.go 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. package openai
  2. import "testing"
  3. // 真实的 Claude Code codex 插件请求头:originator 与 UA 前缀同源于 clientInfo.name="Claude Code"。
  4. const (
  5. testClaudeCodeOriginator = "Claude Code"
  6. testClaudeCodeUserAgent = "Claude Code/0.5.0 (Macos 15.5; arm64) iTerm2.app (Claude Code; 1.0.4)"
  7. )
  8. func TestIsAllowedClientMatch(t *testing.T) {
  9. entry := AllowedClientEntry{Originator: "Claude Code", UAContains: []string{"Claude Code/"}}
  10. tests := []struct {
  11. name string
  12. ua string
  13. originator string
  14. want bool
  15. }{
  16. {name: "真实签名命中", ua: testClaudeCodeUserAgent, originator: testClaudeCodeOriginator, want: true},
  17. {name: "大小写不敏感", ua: "claude code/0.5.0 (macos)", originator: "claude code", want: true},
  18. {name: "originator 两侧空白被裁剪", ua: testClaudeCodeUserAgent, originator: " Claude Code ", want: true},
  19. {name: "originator 非精确(带后缀)不命中", ua: testClaudeCodeUserAgent, originator: "Claude Code Extra", want: false},
  20. {name: "originator 为空不命中", ua: testClaudeCodeUserAgent, originator: "", want: false},
  21. {name: "originator 是官方 codex 不命中", ua: testClaudeCodeUserAgent, originator: "codex_cli_rs", want: false},
  22. {name: "UA 缺少 Claude Code/ 标记不命中", ua: "curl/8.0", originator: testClaudeCodeOriginator, want: false},
  23. }
  24. for _, tt := range tests {
  25. t.Run(tt.name, func(t *testing.T) {
  26. if got := IsAllowedClientMatch(tt.ua, tt.originator, entry); got != tt.want {
  27. t.Fatalf("IsAllowedClientMatch(%q, %q) = %v, want %v", tt.ua, tt.originator, got, tt.want)
  28. }
  29. })
  30. }
  31. }
  32. func TestIsAllowedClientMatch_EmptyOriginatorEntryNeverMatches(t *testing.T) {
  33. // registry 条目若没有配置 Originator,绝不放行,避免成为宽松后门。
  34. entry := AllowedClientEntry{Originator: "", UAContains: []string{"Claude Code/"}}
  35. if IsAllowedClientMatch(testClaudeCodeUserAgent, "", entry) {
  36. t.Fatal("空 Originator 的条目不应匹配任何请求")
  37. }
  38. }
  39. func TestIsAllowedClientMatch_EmptyUAContainsNeverMatches(t *testing.T) {
  40. // 预设必须声明 UA 特征,否则退化为仅凭可伪造的 originator 单因子匹配,绝不放行。
  41. entry := AllowedClientEntry{Originator: "Claude Code", UAContains: nil}
  42. if IsAllowedClientMatch(testClaudeCodeUserAgent, testClaudeCodeOriginator, entry) {
  43. t.Fatal("未声明 UA 特征的预设不应匹配,避免退化为单因子 originator 匹配")
  44. }
  45. }
  46. func TestIsAllowedClientMatch_WhitespaceUAMarkerNeverMatches(t *testing.T) {
  47. // 全空白 marker 归一化后为空,若被跳过则退化为仅 originator 单因子;
  48. // 任何空白 marker 视为非法预设配置,必须安全失败。
  49. entry := AllowedClientEntry{Originator: "Claude Code", UAContains: []string{" "}}
  50. if IsAllowedClientMatch(testClaudeCodeUserAgent, testClaudeCodeOriginator, entry) {
  51. t.Fatal("UAContains 含全空白 marker 不应匹配,避免退化为单因子 originator 匹配")
  52. }
  53. }
  54. func TestIsAllowedClientMatch_MixedEmptyUAMarkerNeverMatches(t *testing.T) {
  55. // 即便 UAContains 含一个真实 marker,只要其中混入任何空白 marker 也视为非法配置;
  56. // 防止维护者只为对齐凑数而插入空字符串。
  57. entry := AllowedClientEntry{Originator: "Claude Code", UAContains: []string{"", "Claude Code/"}}
  58. if IsAllowedClientMatch(testClaudeCodeUserAgent, testClaudeCodeOriginator, entry) {
  59. t.Fatal("UAContains 混入空白 marker 不应匹配")
  60. }
  61. }