Skip to content

MCP GET stream registers a notification sink for any Mcp-Session-Id, without checking the session belongs to the calling key #814

Description

@simonholmes

Found by the security review on the t-673 branch (org binding for credentials); pre-existing, not introduced there.

What happens. handleGet in app/api/v1/mcp/route.ts (stateful mode only) reads Mcp-Session-Id from the request and calls sessionManager.registerSseListener(sessionId, sink) without checking that the session exists or that session.apiKeyId === auth.apiKeyId. The POST and DELETE paths do make that check (Session not found, 404). registerSseListener is a Map.set, so a second registration replaces the first.

Consequence. Any authenticated MCP key that learns another key's session id can (a) receive that session's server-push notifications and (b) displace the legitimate client's sink, which then stops receiving. Session ids are server-generated random values, so this needs a leaked id — but once credentials are org-bound (§106 t-673) this is a cross-org channel at TENANCY_MODE=multi, which is why it is worth closing.

Fix shape. In handleGet, when a session id is presented: look it up, and answer 404 Session not found unless it exists and belongs to auth.apiKeyId (the DELETE path's check, verbatim). A GET with no session id keeps its current behaviour (a connected-only stream with no per-session fan-in).

Severity. Medium-low: needs a leaked session id, stateful mode only, notifications only (no tool results).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions