chore(deps): Update Rust crate rmcp to v2 [SECURITY] - #238
Open
renovate[bot] wants to merge 1 commit into
Open
renovate[bot] wants to merge 1 commit into
renovate[bot] wants to merge 1 commit into
Conversation
renovate
Bot
force-pushed
the
renovate/crate-rmcp-vulnerability
branch
from
September 18, 2026 00:20
782a347 to
ab032cb
Compare
renovate
Bot
force-pushed
the
renovate/crate-rmcp-vulnerability
branch
from
September 20, 2026 15:30
ab032cb to
f5141f8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
1→21→2RMCP: Missing Resource Field Validation in OAuth Protected Resource Metadata Discovery
CVE-2026-63127 / GHSA-33f5-2c5q-wgwj
More information
Details
Summary
The
rmcplibrary does not validate theresourceparameter in OAuth Protected Resource metadata (RFC 9728), allowing a malicious MCP server to redirect OAuth flows to a legitimate authorization server and steal the resulting access tokens.Details
RFC 9728 specifies two MUST requirements for resource parameter validation:
resourcevalue in the returned metadata document.resourcevalue returned is not identical to the URL the client used, the data MUST NOT be used.In the current implementation (
crates/rmcp/src/transport/auth.rs), theResourceServerMetadatastruct (lines 390–394) does not include a resource field:And discover_oauth_server_via_resource_metadata() (lines 1446–1465) proceeds without any resource URL validation.
Recommended fix
resourcefield to the struct:PoC
Attacker sets up a malicious MCP server at
fake-mcp.com/mcp.At
fake-mcp.com/mcp/.well-known/oauth-protected-resource, the attacker serves metadata declaring:real-mcp.com/mcp(the legitimate server)real-mcp.com/mcpVictim configures any MCP client using
rmcpto connect tofake-mcp.com/mcp.rmcpfetches the protected resource metadata and, without validating that theresourcefield (real-mcp.com/mcp) differs from the configured server (fake-mcp.com/mcp), initiates an OAuth flow with the legitimate authorization server.The victim sees a legitimate authorization prompt and completes the flow.
The resulting access token — valid for
real-mcp.com/mcp— is sent tofake-mcp.com/mcpin subsequent requests.The attacker captures the token and can impersonate the victim on
real-mcp.com/mcp.Impact
This is an access token theft vulnerability via OAuth resource metadata spoofing. All MCP clients built on
rmcpthat rely on OAuth-protected MCP servers are affected. An attacker who tricks a user into connecting to a malicious MCP server can steal valid access tokens for any legitimate MCP server, enabling full impersonation of the victim.Credit
Jian Cui, Minsun Shim, Zhou Li, Xiaojing Liao
University of Illinois Urbana-Champaign (UIUC)
University of California, Irvine (UCI)
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
RMCP: Unauthenticated permanent session-table leak in rmcp Streamable HTTP server transport leads to remote denial-of-service
CVE-2026-63128 / GHSA-9pj6-vhgr-3mwh
More information
Details
Summary
An unauthenticated remote attacker can leak one entry per HTTP request out of the in-memory session table of
LocalSessionManagerby sending a well-formed JSON-RPCPOSTthat is not anInitializeRequest. The Streamable HTTP server'shandle_postallocates the session before it validates the body, then early-returns on the validation failure without callingclose_session. TheLocalSessionHandle(and the tokio mpsc channel internals it holds) is never released for the remainder of the process's lifetime — turning a ~250-byte request into a permanent ~400–550-byte server-side allocation that scales linearly with request volume and eventually exhausts memory. In the verified reproduction below, a single Python client sustains over 2 000 leak requests per second; that translates to roughly 170 million leaked entries per day, equivalent to ≈75 GB of resident memory just from the session table.Details
The bug lives in
crates/rmcp/src/transport/streamable_http_server/tower.rsinsideStreamableHttpService::handle_post. The relevant slice of1.7.0source (lines1126–1170) is:Two facts make this unsafe:
(★)inserts aLocalSessionHandleintoLocalSessionManager.sessions(atokio::sync::RwLock<HashMap<SessionId, LocalSessionHandle>>) and spawns aLocalSessionWorkertask.(★★)spawn_session_workeris the only code path in the entire transport (besides a client-initiated HTTPDELETEreachinghandle_delete) that ever invokesself.session_manager.close_session(&session_id).Therefore the four early-returns
(A),(B),(C), and(D)all skip the cleanup. What happens concretely after such an early return:transport: WorkerTransport<LocalSessionWorker>goes out of scope; its_drop_guardcancels the worker'sCancellationToken.event_rx.recv(), exits within milliseconds viaWorkerQuitReason::Cancelled. Itsevent_rxreceiver is dropped.LocalSessionHandle.event_tx(theSenderhalf of the same mpsc channel) is still alive because it is owned by the HashMap entry that nothing ever removes. The channel'sInner(sized tochannel_capacity = 16by default) remains pinned in memory.Because the worker has already exited, the
SessionConfig::keep_aliveandinit_timeoutcleanup paths cannot run either — they only fire from inside a running worker. The leak is therefore permanent for the lifetime of the server process and grows unbounded with sustained traffic.The bug is reachable with zero authentication, the default
StreamableHttpServerConfig, and the defaultLocalSessionManager. It is independent of the Host-header DNS-rebinding flaw fixed in 1.4.0 (GHSA-89vp-x53w-74fx / CVE-2026-42559): the attacker sends a legitimateHost: <bound-address>value and is allowed throughvalidate_dns_rebinding_headersnormally.A secondary side-effect amplifies the impact: every legitimate operation (session lookup, restore, new initialize) takes
self.sessions.write().awaitor.read().awaitagainst the sameRwLock. As the HashMap grows into the millions of phantom entries, honest clients see growing tail latency from write-lock starvation, before the box runs out of memory.Proof of concept
The reproduction is fully self-contained — no clone of the rust-sdk repository is required. Create an empty directory and save the three files below into it, then run two commands.
Step 1 — server harness
Cargo.toml(paste verbatim):src/main.rs(paste verbatim):Start it:
Initial output:
Step 2 — attacker
attack.py(paste verbatim — Python 3 standard library only, nopip installrequired):Run it:
Step 3 — observed evidence
Attacker output (verbatim, measured on Rust 1.92.0 stable, macOS):
Server output during and after the attack:
The behavioural evidence that confirms the vulnerability:
HTTP 422 Unprocessable Entitywith bodyUnexpected message, expect initialize request).1000 / 0.46 ≈ 2 174leak requests per second.active_sessions=1000never decreased. The session table holds those entries for the rest of the process's lifetime.Impact
allowed_hosts = ["localhost", "127.0.0.1", "::1"]accepts anything reaching it over the loopback interface. In the dominant deployment model — a Streamable HTTP MCP server embedded into an IDE or local agent — any co-resident process on the host is a candidate attacker. In LAN deployments where the operator widenedallowed_hoststo a public hostname, the attack is reachable from the network.SessionIdArc<str>, theLocalSessionHandlestruct, and the half-dropped mpsc channelInner). At the measured rate of 2 174 leak requests per second from one Python client:LocalSessionManager.sessionsis behind atokio::sync::RwLock. Every legitimate session operation (has_session,create_session,close_session,restore_session) takes that lock. As the HashMap grows, write-lock contention degrades latency for all clients well before OOM.Suggested fix
Two minimally invasive options. Both have been considered against the existing API; the maintainers will know which fits better with the internal contracts.
ClientJsonRpcMessage::Request(InitializeRequest)discriminant check and thevalidate_header_matches_init_bodycall above theself.session_manager.create_session().awaitline. Reject non-initialize bodies with422before any state is created. This removes a class of bugs rather than patching one path. The downside is thatvalidate_header_matches_init_bodycurrently readsinit_req.params.protocol_version, so theInitializeRequestdiscriminant has to be deconstructed earlier — a small refactor.session_idreturned bycreate_sessionin a guard whoseDropimpl spawns aclose_sessioncall. Demote the guard to a no-op only after the handshake has fully succeeded (i.e. at the very end of the happy-path arm, just before the response is returned). This keeps the existing flow but converts every early-return into a cleanup trigger automatically — including future early-returns that reviewers might miss.A regression test that asserts
session_manager.sessions.read().await.len() == 0after sending a non-initialize POST and a header-mismatched initialize POST would catch this and any similar future regressions.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:HReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
RMCP: Custom HTTP headers leak to cross-origin redirect targets
CVE-2026-64684 / GHSA-9g45-5xwm-f3wc
More information
Details
Summary
The
rmcpcrate'sStreamableHttpClientTransportforwards caller-supplied custom HTTP headers (such asX-API-Key,X-Auth-Token,Api-Key) to cross-origin redirect targets. Thedefault_http_client()function builds areqwest::Clientwithout a redirect policy override, so the defaultlimited(10)policy follows307/308redirects and forwards all per-request headers exceptAuthorization,Cookie, andProxy-Authorization. Custom auth headers injected viaStreamableHttpClientTransportConfig.custom_headersare not classified as sensitive and are therefore forwarded verbatim to any redirect target — including an attacker-controlled server.Affected versions
github.com/modelcontextprotocol/rust-sdkrmcpc330fede90e4729c234f8e87fdbc5ea27a1dd10c(HEAD, 2026-05-21)Vulnerability
File:
crates/rmcp/src/transport/common/reqwest/streamable_http_client.rsRoot cause 1 — no redirect policy override:
No
.redirect(reqwest::redirect::Policy::none())call. The defaultlimited(10)policy follows up to 10 redirects and, on cross-origin redirects, strips onlyAuthorization,Cookie, andProxy-Authorization.Root cause 2 — custom headers not sensitivity-marked:
Headers added via
RequestBuilder::header()are forwarded to redirect targets because reqwest only strips headers from its own sensitive-header list (Authorization,Cookie,Proxy-Authorization).Exposed API:
StreamableHttpClientTransportConfig.custom_headers(line 1070), intended for custom auth headers:Attack scenario
custom_headerswith an API key for the MCP server:mcp.example.comto return307 Temporary Redirecttohttps://attacker.example.net/capture.rmcpfollows the redirect, forwardingX-API-Key: my-secret-keytoattacker.example.net.Negative control
The
auth_headerpath (StreamableHttpClientTransportConfig::auth_header()) sets the value viabuilder.bearer_auth(auth_header), which maps to theAuthorizationheader — stripped by reqwest on cross-origin redirects. That path is not affected. Onlycustom_headersis vulnerable.Fix
In
default_http_client(), disable automatic redirect following:The transport can then inspect
3xxresponses and decide whether to follow, stripping sensitive headers before doing so. Alternatively, usereqwest::ClientBuilder::connection_verboseor per-requestRequest::headers_mut()to remove auth headers before the redirect is followed.Severity
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:N/A:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Release Notes
modelcontextprotocol/rust-sdk (rmcp)
v2.1.0Compare Source
Added
Fixed
v2.0.0Compare Source
Migration guide: https://redirect.github.com/modelcontextprotocol/rust-sdk/discussions/926
Added
Fixed
Other
v1.8.0Compare Source
Added
Fixed
Peer::peer_info()signature, see Breaking Changes aboveOther
Configuration
📅 Schedule: (in timezone Asia/Tokyo)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about these updates again.
This PR was generated by Mend Renovate. View the repository job log.