From 2373100c177654910922c6ef0e5ffc7cfe087b26 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Fri, 19 Dec 2025 09:55:27 -0500 Subject: [PATCH] fix(web-server): add comprehensive reserved name validation - Import isReservedName from centralized reserved-names module - Add validation to POST /api/profiles endpoint - Add validation to POST /api/cliproxy/openai-compat endpoint - Refactor hardcoded arrays to use centralized isReservedName() - Fixes DRY violation and ensures consistent validation across all endpoints --- src/web-server/routes.ts | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/web-server/routes.ts b/src/web-server/routes.ts index 5ff038eb..6a599bfc 100644 --- a/src/web-server/routes.ts +++ b/src/web-server/routes.ts @@ -66,6 +66,7 @@ import { getWebSearchConfig } from '../config/unified-config-loader'; import type { WebSearchConfig } from '../config/unified-config-types'; import { isUnifiedConfig } from '../config/unified-config-types'; import { isSensitiveKey, maskSensitiveValue } from '../utils/sensitive-keys'; +import { isReservedName, RESERVED_PROFILE_NAMES } from '../config/reserved-names'; import { getWebSearchReadiness, getGeminiCliStatus, @@ -275,6 +276,15 @@ apiRoutes.post('/profiles', (req: Request, res: Response): void => { return; } + // Validate reserved names + if (isReservedName(name)) { + res.status(400).json({ + error: `Profile name '${name}' is reserved`, + reserved: RESERVED_PROFILE_NAMES, + }); + return; + } + const config = readConfigSafe(); if (config.profiles[name]) { @@ -378,11 +388,11 @@ apiRoutes.post('/cliproxy', (req: Request, res: Response): void => { return; } - // Reject reserved provider names as variant names - const reservedNames = ['gemini', 'codex', 'agy', 'qwen', 'iflow']; - if (reservedNames.includes(name.toLowerCase())) { + // Reject reserved names as variant names (prevents collision with built-in providers) + if (isReservedName(name)) { res.status(400).json({ - error: `Cannot use reserved provider name '${name}' as variant name`, + error: `Cannot use reserved name '${name}' as variant name`, + reserved: RESERVED_PROFILE_NAMES, }); return; } @@ -482,8 +492,7 @@ apiRoutes.delete('/cliproxy/:name', (req: Request, res: Response): void => { } // Never delete settings files for reserved provider names (safety guard) - const reservedNames = ['gemini', 'codex', 'agy', 'qwen', 'iflow']; - if (!reservedNames.includes(name.toLowerCase())) { + if (!isReservedName(name)) { // Only delete settings file for non-reserved variant names const settingsPath = path.join(getCcsDir(), `${name}.settings.json`); if (fs.existsSync(settingsPath)) { @@ -1614,6 +1623,14 @@ apiRoutes.post('/cliproxy/openai-compat', (req: Request, res: Response): void => res.status(400).json({ error: 'name is required' }); return; } + // Validate reserved names + if (isReservedName(name)) { + res.status(400).json({ + error: `Provider name '${name}' is reserved`, + reserved: RESERVED_PROFILE_NAMES, + }); + return; + } if (!baseUrl || typeof baseUrl !== 'string') { res.status(400).json({ error: 'baseUrl is required' }); return;