security(mcp): eliminate singleton session aliasing and preserve request-local principal context
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 72.8k
- Forks
- 8.6k
- Avg merge
- 2d 23h
- Merged PRs (30d)
- 82
Description
## Finding
The canonical `v3/@claude-flow/mcp` HTTP path currently cannot preserve request-local authority context across concurrent clients.
Direct source audit on the isolated MCP 2026 migration branch found three coupled conditions:
1. `MCPServer` owns a mutable singleton `currentSession`; `initialize` replaces it and subsequent tool, sampling, and resource-subscription paths derive `sessionId` from that singleton.
2. `RequestHandler` accepts only the JSON-RPC `MCPRequest`, so transports cannot pass a request-local authenticated principal, protocol version, routing headers, connection identity, or legacy session identifier into the server.
3. `HttpTransport` validates Authorization as a boolean and then invokes `requestHandler(message as MCPRequest)` without forwarding the validated principal or HTTP headers. Its CORS allowlist also does not currently include the MCP 2026 routing/version headers.
This means two legacy HTTP clients cannot be strongly associated with independent server-side session/authority context. The second initializer can replace the singleton context used by later calls. If an authorizer or subscription policy relies on `ToolContext.sessionId`, that value can identify the wrong logical client. The same architecture cannot safely implement MCP 2026-07-28 because modern requests require per-request protocol/client metadata and header/body routing validation.
Evidence class: **direct repository source audit**. This is a structural finding, not yet a live exploit reproduction against a deployed endpoint.
## Security impact
Treat as high priority until falsified by an end-to-end test proving transport-level isolation outside this code path.
Potential effects:
* cross-client session aliasing
* wrong session identity reaching `ToolAuthorizer`
* resource subscriptions attributed to the wrong session
* sampling context attributed to the wrong session
* inability to bind modern MCP routing/version metadata to the exact request
* inability to prove two-tenant isolation for MCP 2026 migration
Authentication success MUST NOT be treated as principal identity, and request metadata MUST NOT create authority.
## Required primitive
Introduce a transport-neutral, immutable `MCPRequestContext` created by the transport after authentication and before dispatch. At minimum it should carry:
* transport kind
* validated protocol version
* normalized routing method/name
* authenticated principal reference or non-secret credential fingerprint where available
* connection identifier where relevant
* legacy `Mcp-Session-Id` only for legacy protocol eras
* request/trace identifiers
Do not place raw credentials in this object.
`RequestHandler` should become backward-compatible with an optional second request-context argument during migration. HTTP and WebSocket transports must populate it. The server must stop deriving authority context from a singleton `currentSession`.
Legacy mode should map requests to sessions by explicit request-local session identity. Modern `2026-07-28` mode must not create a protocol session at all.
## Invariants
1. No mutable server-global variable may determine the calling principal or logical client for a request.
2. Two clients initializing concurrently never alter each other's tool authorization, subscriptions, tasks, sampling context, or cache scope.
3. Modern MCP requests never require or emit `Mcp-Session-Id`.
4. `MCP-Protocol-Version`, `Mcp-Method`, and `Mcp-Name` are validated against the body before dispatch; mismatch fails closed.
5. Client metadata, trace data, discovery data, signatures, and cached evidence are descriptive only and never expand authority.
6. Raw bearer tokens/API keys never enter logs, `ToolContext`, RVF receipts, or cache keys.
7. Legacy rollback remains available until retained-client conformance passes.
## Reproduction matrix
Freeze before implementation:
* transports: in-process, stdio, Streamable HTTP, WebSocket, legacy HTTP+SSE where retained
* principals: token A, token B, anonymous only where allowed
* clients: two concurrent legacy clients plus two concurrent modern clients
* operations: initialize for legacy, tools/list, tools/call, resources/subscribe, resources/unsubscribe, sampling if retained, server/discover for modern
* schedules: A-init → B-init → A-call, simultaneous A/B calls, reconnect, process restart, load-balanced instance change
* attacks: missing session, stolen/unknown session ID, replayed session ID under another principal, header/body mismatch, malformed `_meta`, oversized headers, cancellation races
Record exact commit, Node/pnpm versions, SDK versions, seeds, concurrency, request counts, p50/p95/p99, throughput, RSS, network bytes, auth failures, and all leakage attempts.
## Acceptance gate
* zero cross-principal or cross-session authorization-context aliases across at least 10,000 interleaved cheap requests
* zero successful use of a legacy session identifier under a different authenticated principal
* zero dispatch on MCP routing header/body mismatch
* zero authority expansion from `_meta`
* 100% retained legacy fixtures pass
* 100% modern 2026-07-28 fixtures pass
* p95 request-context overhead <= 5% versus the frozen legacy baseline
* no raw secret appears in logs, traces, cache keys, receipts, or errors
* restart and round-robin tests pass for modern requests with no shared session store
## Implementation coordination
Relates to #2542, draft PR #3268 / ADR-387, and `ruvnet/metaharness#305` Track A.
Add ADR-388 for request-local authority context and a read-only audit gate on the existing isolated branch. Runtime changes remain draft until the reproduction matrix passes. No autonomous merge, deployment, credential escalation, or compatibility waiver.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by auditing the named MCPServer, RequestHandler, and HttpTransport entry points on the isolated migration branch, then review draft PR #3268 and ADR-387. Freeze and run the listed reproduction matrix before implementation; done requires the stated zero-alias, routing-mismatch, secret-leakage, legacy-fixture, modern-fixture, and performance acceptance gates, plus ADR-388.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, authentication, backend-api-design, security
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 20/100