T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- server/index.js:42
- Finding
- Unauthenticated Privileged API Exposed on Network Interfaces<![CDATA[ ## Vulnerability Details **File Location**: `server/index.js:42-71, 83-86`; `server/routes/chat.js:27-101`; `server/routes/schedule.js:28-79` **Vulnerability Type**: Missing authentication and authorization on privileged HTTP endpoints **Risk Level**: Critical ### Vulnerable Code ```js // server/index.js:42-71 const app = express(); app.use(express.json()); // Shared session key used by routes app.locals.sessionKey = SESSION_KEY; app.get('/api/events', (req, res) => { res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive', 'Access-Control-Allow-Origin': '*', }); res.write('data: {"type":"connected"}\n\n'); addClient(res); req.on('close', () => removeClient(res)); }); app.use('/api', chatRoutes); app.use('/api', statusRoutes(config, OC_CONFIG)); app.use('/api', ttsRoutes); app.use('/api', tasksRoutes); app.use('/api', skillsRoutes); app.use('/api', memoryRoutes); app.use('/api', scheduleRoutes); app.use('/api', voiceRoutes); // server/index.js:83-86 app.listen(PORT, () => { console.log(`[JARVIS] API server on http://localhost:${PORT}`); if (SERVE_STATIC) console.log(`[JARVIS] Serving static files from dist/`); }); ``` Representative privileged endpoints: ```js // server/routes/chat.js:63-101 router.post('/chat', async (req, res) => { const { message } = req.body; if (!message) return res.status(400).json({ error: 'message required' }); bumpMsgCount(); try { const idempotencyKey = `jarvis-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; const result = await gwRequest('chat.send', { message, sessionKey: req.app.locals.sessionKey, idempotencyKey, deliver: false, }); res.json({ ok: true, ...result }); } catch (err) { res.status(502).json({ error: err.message || 'gateway error' }); } }); router.get('/history', async (req, res) => { try { const result = await gwRequest('chat.history', { sessionKey: req.app.l ...[truncated 3565 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Bind to loopback by default: ```js const HOST = process.env.HOST || '127.0.0.1'; app.listen(PORT, HOST, () => { /* ... */ }); ``` 2. Require authenticated sessions or a strong API credential on all `/api/*` routes, including `/api/events`. 3. Apply route-specific authorization. Read-only dashboard access must not automatically grant chat, upload, abort, task mutation, or schedule-control privileges. 4. Add CSRF protection for state-changing requests and validate `Origin` and `Host` against an explicit allowlist. 5. Remove wildcard CORS behavior and allow only the intended dashboard origin. 6. Add rate limits, request timeouts, upload quotas, and explicit JSON body-size limits. 7. For remote access, place the service behind TLS and an authenticated reverse proxy or VPN. Do not expose port 9999 directly. 8. Add automated tests proving that unauthenticated users cannot read private data or invoke mutating operations. ]]>
