StatelessHTTPServerTransport keys per-request HTTP context by client-chosen JSON-RPC id — concurrent clients with colliding ids can observe each other's HTTPRequest (including Authorization)

Open
#265 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
52/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Quiet
Tech stack
swift

Research direction

Start with StatelessHTTPServerTransport.swift at the httpRequestContexts storage and dispatch paths, then inspect Server.swift around the currentHandlerContext lookup. Reproduce concurrent POST requests with the same JSON-RPC id and distinct X-Marker headers. Done means each handler sees its own HTTPRequest and response-waiter state is not shared across clients.

Written by the indexing model from the issue text.

Description

Summary

Server.currentHandlerContext.httpContext is documented as the way for method handlers to observe the originating HTTP request ("headers, auth, path, body"). For StatelessHTTPServerTransport, that context is served from a transport-global dictionary keyed only by the JSON-RPC request id:

  • private var httpRequestContexts: [String: HTTPRequest] = [:]StatelessHTTPServerTransport.swift:58 (v0.12.1)
  • stored on receipt: httpRequestContexts[requestID] = request:224
  • read at dispatch via HTTPContextProviding.httpRequestContext(for:):249, called from Server.swift:785 before the handler runs under Server.$currentHandlerContext.withValue(...)

A stateless transport explicitly serves many independent clients concurrently, and JSON-RPC ids are client-chosen (most clients count from 1). JSON-RPC only requires ids to be unique per client session — per-client uniqueness does not imply transport-global uniqueness, which is exactly what the map assumes. When two in-flight requests share an id, the second store overwrites the first (:224), and either dispatch can read the other client's HTTPRequest in the window between store and dispatch-time read.

Why it matters

The task-local is positioned for auth-adjacent use (the doc comment names "auth" explicitly), so downstream servers resolve per-request authorization from it. In our server (Susurro, a local-first macOS app), handlers map the Authorization: Bearer token to a permission scope via currentHandlerContext.httpContext — under an id collision, a lower-privileged client's handler can (probabilistically) observe a concurrent higher-privileged client's Authorization header and act with its scope. Severity is deployment-dependent (ours is loopback-only with pre-authenticated clients), but for any server using httpContext in authorization decisions this is a cross-client confused-deputy risk, not just a correctness nit.

The response-waiter plumbing appears to share the same root cause (id-keyed transport-global state), so colliding ids can presumably also clobber each other's response continuations — worth fixing together.

Reproduction sketch

  1. Start a Server on StatelessHTTPServerTransport with a CallTool handler that echoes Server.currentHandlerContext?.httpContext?.header("X-Marker") back in its result.
  2. Run two concurrent client loops POSTing tools/call with the same "id": 1, each sending a distinct X-Marker header.
  3. Assert every response's echoed marker matches its own request. Under load, mismatches appear — each one is a swapped context.

Suggested fix direction

handlePOST has the per-request HTTPRequest in hand before it enqueues the message; binding it through the dispatch path (paired with the message through the pump, or keyed by a transport-generated unique per-request token instead of the client-chosen JSON-RPC id) would make HandlerContext.httpContext genuinely per-dispatch. Alternatively/additionally, rejecting duplicate in-flight ids would close the collision at the door — though that changes observable behavior for clients that legitimately reuse ids across their own sequential requests.

Happy to provide more detail or test our workload against a patch. Thanks for the SDK — the task-local context design is otherwise exactly what a server like ours needs.


Version: swift-sdk 0.12.1 (exact pin). Server stack: StatelessHTTPServerTransport + FlyingFox HTTP listener bridging into transport.handleRequest, custom validation pipeline, Swift 6.

Dominant language
Swift
Stars
1.5k
Forks
243
PR merge metrics
No merged PRs in 30d

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.

More from modelcontextprotocol/swift-sdk

All issues in modelcontextprotocol/swift-sdk

Similar issues

More Swift issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.