diff --git a/src/web-server/routes/image-analysis-routes.ts b/src/web-server/routes/image-analysis-routes.ts index 25ec7fc9..db7a44f3 100644 --- a/src/web-server/routes/image-analysis-routes.ts +++ b/src/web-server/routes/image-analysis-routes.ts @@ -404,6 +404,9 @@ router.put('/', async (req: Request, res: Response): Promise => { if (!normalizedBackend || normalizedModel.length === 0) { return acc; } + if (!knownBackends.has(normalizedBackend)) { + throw new Error(`Unsupported provider backend "${backendId}".`); + } acc[normalizedBackend] = normalizedModel; return acc; }, @@ -470,6 +473,10 @@ router.put('/', async (req: Request, res: Response): Promise => { res.json(await buildDashboardPayload()); } catch (error) { + if (error instanceof Error && error.message.startsWith('Unsupported provider backend')) { + res.status(400).json({ error: error.message }); + return; + } res.status(500).json({ error: (error as Error).message }); } }); diff --git a/tests/unit/web-server/image-analysis-routes.test.ts b/tests/unit/web-server/image-analysis-routes.test.ts index 27a6ef6f..20e6ca76 100644 --- a/tests/unit/web-server/image-analysis-routes.test.ts +++ b/tests/unit/web-server/image-analysis-routes.test.ts @@ -340,4 +340,31 @@ describe('image-analysis routes', () => { error: 'Profile mapping for "codexProfile" references an unknown backend.', }); }); + + it('rejects unsupported provider backends and preserves existing configuration', async () => { + const putResponse = await fetch(`${baseUrl}/api/image-analysis`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + providerModels: { + gemini: 'gemini-3-flash-preview', + evil: 'vision-model', + }, + fallbackBackend: 'gemini', + }), + }); + + expect(putResponse.status).toBe(400); + expect(await putResponse.json()).toEqual({ + error: 'Unsupported provider backend "evil".', + }); + + const getResponse = await fetch(`${baseUrl}/api/image-analysis`); + expect(getResponse.status).toBe(200); + const payload = await getResponse.json(); + expect(payload.config.providerModels).toMatchObject({ + gemini: 'gemini-3-flash-preview', + ghcp: 'claude-haiku-4.5', + }); + }); });