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
This commit is contained in:
kaitranntt committed 2025-12-19 09:55:27 -05:00
1 parent 7ea64bdb43
commit 2373100c17
1 file changed
+23 -6
+23 -6
View File
@@ -66,6 +66,7 @@ import { getWebSearchConfig } from '../config/unified-config-loader';
import type { WebSearchConfig } from '../config/unified-config-types'; import type { WebSearchConfig } from '../config/unified-config-types';
import { isUnifiedConfig } from '../config/unified-config-types'; import { isUnifiedConfig } from '../config/unified-config-types';
import { isSensitiveKey, maskSensitiveValue } from '../utils/sensitive-keys'; import { isSensitiveKey, maskSensitiveValue } from '../utils/sensitive-keys';
import { isReservedName, RESERVED_PROFILE_NAMES } from '../config/reserved-names';
import { import {
getWebSearchReadiness, getWebSearchReadiness,
getGeminiCliStatus, getGeminiCliStatus,
@@ -275,6 +276,15 @@ apiRoutes.post('/profiles', (req: Request, res: Response): void => {
return; 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(); const config = readConfigSafe();
if (config.profiles[name]) { if (config.profiles[name]) {
@@ -378,11 +388,11 @@ apiRoutes.post('/cliproxy', (req: Request, res: Response): void => {
return; return;
} }
// Reject reserved provider names as variant names // Reject reserved names as variant names (prevents collision with built-in providers)
const reservedNames = ['gemini', 'codex', 'agy', 'qwen', 'iflow']; if (isReservedName(name)) {
if (reservedNames.includes(name.toLowerCase())) {
res.status(400).json({ 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; return;
} }
@@ -482,8 +492,7 @@ apiRoutes.delete('/cliproxy/:name', (req: Request, res: Response): void => {
} }
// Never delete settings files for reserved provider names (safety guard) // Never delete settings files for reserved provider names (safety guard)
const reservedNames = ['gemini', 'codex', 'agy', 'qwen', 'iflow']; if (!isReservedName(name)) {
if (!reservedNames.includes(name.toLowerCase())) {
// Only delete settings file for non-reserved variant names // Only delete settings file for non-reserved variant names
const settingsPath = path.join(getCcsDir(), `${name}.settings.json`); const settingsPath = path.join(getCcsDir(), `${name}.settings.json`);
if (fs.existsSync(settingsPath)) { 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' }); res.status(400).json({ error: 'name is required' });
return; 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') { if (!baseUrl || typeof baseUrl !== 'string') {
res.status(400).json({ error: 'baseUrl is required' }); res.status(400).json({ error: 'baseUrl is required' });
return; return;