| const logger = require('../../utils/logger') |
| const { CLIENT_DEFINITIONS } = require('../clientDefinitions') |
| const { bestSimilarityByTemplates, SYSTEM_PROMPT_THRESHOLD } = require('../../utils/contents') |
|
|
| |
| |
| |
| |
| class ClaudeCodeValidator { |
| |
| |
| |
| static getId() { |
| return CLIENT_DEFINITIONS.CLAUDE_CODE.id |
| } |
|
|
| |
| |
| |
| static getName() { |
| return CLIENT_DEFINITIONS.CLAUDE_CODE.name |
| } |
|
|
| |
| |
| |
| static getDescription() { |
| return CLIENT_DEFINITIONS.CLAUDE_CODE.description |
| } |
|
|
| |
| |
| |
| static getIcon() { |
| return CLIENT_DEFINITIONS.CLAUDE_CODE.icon || '🤖' |
| } |
|
|
| |
| |
| |
| |
| |
| static hasClaudeCodeSystemPrompt(body, customThreshold) { |
| if (!body || typeof body !== 'object') { |
| return false |
| } |
|
|
| const model = typeof body.model === 'string' ? body.model : null |
| if (!model) { |
| return false |
| } |
|
|
| const systemEntries = Array.isArray(body.system) ? body.system : null |
| if (!systemEntries) { |
| return false |
| } |
|
|
| const threshold = |
| typeof customThreshold === 'number' && Number.isFinite(customThreshold) |
| ? customThreshold |
| : SYSTEM_PROMPT_THRESHOLD |
|
|
| for (const entry of systemEntries) { |
| const rawText = typeof entry?.text === 'string' ? entry.text : '' |
| const { bestScore } = bestSimilarityByTemplates(rawText) |
| if (bestScore < threshold) { |
| logger.error( |
| `Claude system prompt similarity below threshold: score=${bestScore.toFixed(4)}, threshold=${threshold}, prompt=${rawText}` |
| ) |
| return false |
| } |
| } |
| return true |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static includesClaudeCodeSystemPrompt(body, customThreshold) { |
| if (!body || typeof body !== 'object') { |
| return false |
| } |
|
|
| const model = typeof body.model === 'string' ? body.model : null |
| if (!model) { |
| return false |
| } |
|
|
| const systemEntries = Array.isArray(body.system) ? body.system : null |
| if (!systemEntries) { |
| return false |
| } |
|
|
| const threshold = |
| typeof customThreshold === 'number' && Number.isFinite(customThreshold) |
| ? customThreshold |
| : SYSTEM_PROMPT_THRESHOLD |
|
|
| let bestMatchScore = 0 |
|
|
| for (const entry of systemEntries) { |
| const rawText = typeof entry?.text === 'string' ? entry.text : '' |
| const { bestScore } = bestSimilarityByTemplates(rawText) |
|
|
| if (bestScore > bestMatchScore) { |
| bestMatchScore = bestScore |
| } |
|
|
| if (bestScore >= threshold) { |
| return true |
| } |
| } |
|
|
| logger.debug( |
| `Claude system prompt not detected: bestScore=${bestMatchScore.toFixed(4)}, threshold=${threshold}` |
| ) |
|
|
| return false |
| } |
|
|
| |
| |
| |
| |
| |
| static validate(req) { |
| try { |
| const userAgent = req.headers['user-agent'] || '' |
| const path = req.path || '' |
|
|
| const claudeCodePattern = /^claude-cli\/\d+\.\d+\.\d+/i |
|
|
| if (!claudeCodePattern.test(userAgent)) { |
| |
| return false |
| } |
|
|
| |
| if (!path.includes('messages')) { |
| |
| logger.debug(`Claude Code detected for path: ${path}, allowing access`) |
| return true |
| } |
|
|
| |
| if (!this.hasClaudeCodeSystemPrompt(req.body)) { |
| logger.debug('Claude Code validation failed - missing or invalid Claude Code system prompt') |
| return false |
| } |
|
|
| |
| const xApp = req.headers['x-app'] |
| const anthropicBeta = req.headers['anthropic-beta'] |
| const anthropicVersion = req.headers['anthropic-version'] |
|
|
| if (!xApp || xApp.trim() === '') { |
| logger.debug('Claude Code validation failed - missing or empty x-app header') |
| return false |
| } |
|
|
| if (!anthropicBeta || anthropicBeta.trim() === '') { |
| logger.debug('Claude Code validation failed - missing or empty anthropic-beta header') |
| return false |
| } |
|
|
| if (!anthropicVersion || anthropicVersion.trim() === '') { |
| logger.debug('Claude Code validation failed - missing or empty anthropic-version header') |
| return false |
| } |
|
|
| logger.debug( |
| `Claude Code headers - x-app: ${xApp}, anthropic-beta: ${anthropicBeta}, anthropic-version: ${anthropicVersion}` |
| ) |
|
|
| |
| if (!req.body || !req.body.metadata || !req.body.metadata.user_id) { |
| logger.debug('Claude Code validation failed - missing metadata.user_id in body') |
| return false |
| } |
|
|
| const userId = req.body.metadata.user_id |
| |
| |
| const userIdPattern = /^user_[a-fA-F0-9]{64}_account__session_[\w-]+$/ |
|
|
| if (!userIdPattern.test(userId)) { |
| logger.debug(`Claude Code validation failed - invalid user_id format: ${userId}`) |
|
|
| |
| if (!userId.startsWith('user_')) { |
| logger.debug('user_id must start with "user_"') |
| } else { |
| const parts = userId.split('_') |
| if (parts.length < 4) { |
| logger.debug('user_id format is incomplete') |
| } else if (parts[1].length !== 64) { |
| logger.debug(`user hash must be 64 characters, got ${parts[1].length}`) |
| } else if (parts[2] !== 'account' || parts[3] !== '' || parts[4] !== 'session') { |
| logger.debug('user_id must contain "_account__session_"') |
| } |
| } |
| return false |
| } |
|
|
| |
| logger.debug(`Claude Code validation passed - UA: ${userAgent}, userId: ${userId}`) |
|
|
| |
| return true |
| } catch (error) { |
| logger.error('Error in ClaudeCodeValidator:', error) |
| |
| return false |
| } |
| } |
|
|
| |
| |
| |
| static getInfo() { |
| return { |
| id: this.getId(), |
| name: this.getName(), |
| description: this.getDescription(), |
| icon: CLIENT_DEFINITIONS.CLAUDE_CODE.icon |
| } |
| } |
| } |
|
|
| module.exports = ClaudeCodeValidator |
|
|