ruvnet / ruvnet/ruflo

security(mcp): eliminate singleton session aliasing and preserve request-local principal context

Open
#3,294 1 comment 0 reactions 0 assignees View on GitHub

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.