File size: 4,632 Bytes
69b897d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6c6056a
 
69b897d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
const logger = require('../../utils/logger')
const { CLIENT_DEFINITIONS } = require('../clientDefinitions')

/**
 * Codex CLI 验证器
 * 验证请求是否来自 Codex CLI
 */
class CodexCliValidator {
  /**
   * 获取客户端ID
   */
  static getId() {
    return CLIENT_DEFINITIONS.CODEX_CLI.id
  }

  /**
   * 获取客户端名称
   */
  static getName() {
    return CLIENT_DEFINITIONS.CODEX_CLI.name
  }

  /**
   * 获取客户端描述
   */
  static getDescription() {
    return CLIENT_DEFINITIONS.CODEX_CLI.description
  }

  /**
   * 验证请求是否来自 Codex CLI
   * @param {Object} req - Express 请求对象
   * @returns {boolean} 验证结果
   */
  static validate(req) {
    try {
      const userAgent = req.headers['user-agent'] || ''
      const originator = req.headers['originator'] || ''
      const sessionId = req.headers['session_id']

      // 1. 基础 User-Agent 检查
      // Codex CLI 的 UA 格式:
      // - codex_vscode/0.35.0 (Windows 10.0.26100; x86_64) unknown (Cursor; 0.4.10)
      // - codex_cli_rs/0.38.0 (Ubuntu 22.4.0; x86_64) WindowsTerminal
      const codexCliPattern = /^(codex_vscode|codex_cli_rs)\/[\d\.]+/i
      const uaMatch = userAgent.match(codexCliPattern)

      if (!uaMatch) {
        logger.debug(`Codex CLI validation failed - UA mismatch: ${userAgent}`)
        return false
      }

      // 2. 对于特定路径,进行额外的严格验证
      // 对于 /openai 和 /azure 路径需要完整验证
      const strictValidationPaths = ['/openai', '/azure']
      const needsStrictValidation =
        req.path && strictValidationPaths.some((path) => req.path.startsWith(path))

      if (!needsStrictValidation) {
        // 其他路径,只要 User-Agent 匹配就认为是 Codex CLI
        logger.debug(`Codex CLI detected for path: ${req.path}, allowing access`)
        return true
      }

      // 3. 验证 originator 头必须与 UA 中的客户端类型匹配
      const clientType = uaMatch[1].toLowerCase()
      if (originator.toLowerCase() !== clientType) {
        logger.debug(
          `Codex CLI validation failed - originator mismatch. UA: ${clientType}, originator: ${originator}`
        )
        return false
      }

      // 4. 检查 session_id - 必须存在且长度大于20
      if (!sessionId || sessionId.length <= 20) {
        logger.debug(`Codex CLI validation failed - session_id missing or too short: ${sessionId}`)
        return false
      }

      // 5. 对于 /openai/responses 和 /azure/response 路径,额外检查 body 中的 instructions 字段
      if (
        req.path &&
        (req.path.includes('/openai/responses') || req.path.includes('/azure/response'))
      ) {
        if (!req.body || !req.body.instructions) {
          logger.debug(`Codex CLI validation failed - missing instructions in body for ${req.path}`)
          return false
        }

        const expectedPrefix =
          'You are Codex, based on GPT-5. You are running as a coding agent in the Codex CLI'
        if (!req.body.instructions.startsWith(expectedPrefix)) {
          logger.debug(`Codex CLI validation failed - invalid instructions prefix for ${req.path}`)
          logger.debug(`Expected: "${expectedPrefix}..."`)
          logger.debug(`Received: "${req.body.instructions.substring(0, 100)}..."`)
          return false
        }

        // 额外检查 model 字段应该是 gpt-5-codex
        if (req.body.model && req.body.model !== 'gpt-5-codex') {
          logger.debug(`Codex CLI validation warning - unexpected model: ${req.body.model}`)
          // 只记录警告,不拒绝请求
        }
      }

      // 所有必要检查通过
      logger.debug(`Codex CLI validation passed for UA: ${userAgent}`)
      return true
    } catch (error) {
      logger.error('Error in CodexCliValidator:', error)
      // 验证出错时默认拒绝
      return false
    }
  }

  /**
   * 比较版本号
   * @returns {number} -1: v1 < v2, 0: v1 = v2, 1: v1 > v2
   */
  static compareVersions(v1, v2) {
    const parts1 = v1.split('.').map(Number)
    const parts2 = v2.split('.').map(Number)

    for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
      const part1 = parts1[i] || 0
      const part2 = parts2[i] || 0

      if (part1 < part2) return -1
      if (part1 > part2) return 1
    }

    return 0
  }

  /**
   * 获取验证器信息
   */
  static getInfo() {
    return {
      id: this.getId(),
      name: this.getName(),
      description: this.getDescription(),
      icon: CLIENT_DEFINITIONS.CODEX_CLI.icon
    }
  }
}

module.exports = CodexCliValidator