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)
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
- Domain
- backend-api-design, networking, security
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 fromServer.swift:785before the handler runs underServer.$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
- Start a
ServeronStatelessHTTPServerTransportwith aCallToolhandler that echoesServer.currentHandlerContext?.httpContext?.header("X-Marker")back in its result. - Run two concurrent client loops POSTing
tools/callwith the same"id": 1, each sending a distinctX-Markerheader. - 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
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.
More from modelcontextprotocol/swift-sdk
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
enhancement
Difficulty 1/5 Under an hour Newbie friendliness 78/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
All issues in modelcontextprotocol/swift-sdk
Similar issues
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
type: docs
Difficulty 1/5 Under an hour Newbie friendliness 95/100
googleapis/google-cloud-swift#971 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
bitcoindevkit/bdk-ffi#1125 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
mozilla-mobile/firefox-ios#35743 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
manaflow-ai/cmux#13417 ·