diff --git a/src/web-server/routes/cursor-routes.ts b/src/web-server/routes/cursor-routes.ts index 220dae9a..f3c5eafc 100644 --- a/src/web-server/routes/cursor-routes.ts +++ b/src/web-server/routes/cursor-routes.ts @@ -18,6 +18,7 @@ import { import cursorSettingsRoutes from './cursor-settings-routes'; import { getCursorConfig } from '../../config/config-loader-facade'; +import { isDashboardWebSocketOriginAllowed } from '../middleware/auth-middleware'; const router = Router(); @@ -192,7 +193,12 @@ router.get('/models', async (_req: Request, res: Response): Promise => { /** * POST /api/cursor/probe - Run a live authenticated runtime probe */ -router.post('/probe', async (_req: Request, res: Response): Promise => { +router.post('/probe', async (req: Request, res: Response): Promise => { + if (!isDashboardWebSocketOriginAllowed(req)) { + res.status(403).json({ error: 'Cross-origin probe requests are not allowed.' }); + return; + } + try { const cursorConfig = getCursorConfig(); const result = await probeCursorRuntime(cursorConfig); diff --git a/tests/unit/web-server/cursor-routes.test.ts b/tests/unit/web-server/cursor-routes.test.ts index 5934ed9c..2ff96d7b 100644 --- a/tests/unit/web-server/cursor-routes.test.ts +++ b/tests/unit/web-server/cursor-routes.test.ts @@ -510,6 +510,17 @@ describe('Cursor Routes Logic', () => { expect(json.current).toBe('gpt-5.3-codex'); }); + it('POST /api/cursor/probe rejects cross-origin requests', async () => { + const res = await fetch(`${baseUrl}/api/cursor/probe`, { + method: 'POST', + headers: { Origin: 'https://attacker.example' }, + }); + expect(res.status).toBe(403); + + const json = (await res.json()) as { error?: string }; + expect(json.error).toContain('Cross-origin probe requests are not allowed.'); + }); + it('POST /api/cursor/probe returns auth failure when credentials are missing', async () => { const res = await fetch(`${baseUrl}/api/cursor/probe`, { method: 'POST' }); expect(res.status).toBe(401);