T05 · Unauthorized Access and Privilege Escalation
- Location
src/server.ts:155- Finding
Unauthenticated SSE Subscription Enables Agent Impersonation and Private Event Disclosure
- Content
View full analysis
Vulnerability Details
File Location:
src/server.ts, lines 155-230
Vulnerability Type: Missing authentication and object-level authorization on an agent event stream
Risk Level: CriticalComplete Code Snippet
ts app.get("/events/:agent_id", optionalAuthMiddleware, (req: Request, res: Response) => { const { agent_id } = req.params; const authContext: AuthContext | undefined = req.auth?.agent; res.setHeader("Content-Type", "text/event-stream"); res.setHeader("Cache-Control", "no-cache"); res.setHeader("Connection", "keep-alive"); res.setHeader("X-Accel-Buffering", "no"); res.flushHeaders(); if (connectedCount() >= MAX_SSE_CONNECTIONS) { res.status(503).json({ error: "SSE connection limit reached" }); return; } const connId = registerClient(agent_id, res); markAgentSseOnline(agent_id); incrementGauge("active_sse_connections"); const lastEventId = req.headers["last-event-id"] as string | undefined; const parsedLast = lastEventId ? parseInt(lastEventId, 10) : NaN; if (!isNaN(parsedLast)) { const missed = eventLogRepo.getEventsAfter(parsedLast, agent_id); if (missed.length > 0) { for (const ev of missed) { if (writeStoredEvent(agent_id, ev)) { eventLogRepo.markDelivered(ev.id); } } } } else { const undelivered = eventLogRepo.getUndelivered(agent_id); for (const ev of undelivered) { if (writeStoredEvent(agent_id, ev)) { eventLogRepo.markDelivered(ev.id); } } const pending = messageRepo.pendingFor(agent_id); if (pending.length > 0) { for (const msg of pending) { const ok = pushToAgent(agent_id, { event: "new_message", message: msg }); if (ok) { messageRepo.markDelivered(msg.id); } } } } const pendingTasks = taskRepo.listFor(agent_id, "pending"); for (const task of pendingTasks) { pushToAgent(agent_id, { event: "task_as ...[truncated 2307 chars]- Remediation
View remediation
Remediation Suggestions
- Replace
optionalAuthMiddlewarewith mandatory authentication for/events/:agent_id. - Before setting SSE headers or registering the connection, enforce:
ts const ctx = req.auth?.agent; if (!ctx) { res.status(401).json({ error: "Authentication required" }); return; } if (ctx.agentId !== req.params.agent_id && ctx.role !== "admin") { res.status(403).json({ error: "Agent event stream access denied" }); return; } - Prefer deriving the subscription identity from the authenticated context rather than accepting it from the URL.
- Do not mark an agent online until identity and ownership checks succeed.
- Add tests covering missing tokens, invalid tokens, cross-agent tokens, administrative access, and connection replacement attempts.
- Consider allowing only one authenticated session or explicitly managed session set per agent, rather than silently replacing the existing connection.
- Replace
