GHSA-9g45-5xwm-f3wc: Infoleak
Summary
The rmcp crate's StreamableHttpClientTransport forwards caller-supplied custom HTTP headers (such as X-API-Key, X-Auth-Token, Api-Key) to cross-origin redirect targets. The defaulthttpclient() function builds a reqwest::Client without a redirect policy override, so the default limited(10) policy follows 307/308 redirects and forwards all per-request headers except Authorization, Cookie, and Proxy-Authorization. Custom auth headers injected via StreamableHttpClientTransportConfig.customheaders are not classified as sensitive and are therefore forwarded verbatim to any redirect target — including an attacker-controlled server.
Affected versions
- Repository: github.com/modelcontextprotocol/rust-sdk - Crate: rmcp - Commit tested: c330fede90e4729c234f8e87fdbc5ea27a1dd10c (HEAD, 2026-05-21)
Vulnerability
File: crates/rmcp/src/transport/common/reqwest/streamablehttpclient.rs
Root cause 1 — no redirect policy override:
rust // Lines 302-307 fn defaulthttpclient() -> reqwest::Client { reqwest::Client::builder() .poolmaxidleperhost(0) .build() .expect("failed to build default reqwest client") }
No .redirect(reqwest::redirect::Policy::none()) call. The default limited(10) policy follows up to 10 redirects and, on cross-origin redirects, strips only Authorization, Cookie, and Proxy-Authorization.
Root cause 2 — custom headers not sensitivity-marked:
rust // Lines 26-35 fn applycustomheaders( mut builder: reqwest::RequestBuilder, customheaders: HashMap<HeaderName, HeaderValue>, ) -> Result<reqwest::RequestBuilder, StreamableHttpError<reqwest::Error>> { for (name, value) in customheaders { validatecustomheader(&name).maperr(StreamableHttpError::ReservedHeaderConflict)?; builder = builder.header(name, value); // no sensitivity marker } Ok(builder) }
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.customheaders (line 1070), intended for custom auth headers:
rust /// Custom HTTP headers to include with every request pub customheaders: HashMap<HeaderName, HeaderValue>,
Attack scenario
1. A caller sets customheaders with an API key for the MCP server: rust let config = StreamableHttpClientTransportConfig::withuri("https://mcp.example.com/mcp") .customheaders([(HeaderName::fromstatic("x-api-key"), HeaderValue::fromstatic("my-secret-key"))].into()); 2. An attacker compromises mcp.example.com to return 307 Temporary Redirect to https://attacker.example.net/capture. 3. rmcp follows the redirect, forwarding X-API-Key: my-secret-key to attacker.example.net. 4. The attacker captures the secret and reuses it to call the MCP server directly.
Negative control
The authheader path (StreamableHttpClientTransportConfig::authheader()) sets the value via builder.bearerauth(authheader), which maps to the Authorization header — stripped by reqwest on cross-origin redirects. That path is not affected. Only customheaders is vulnerable.
Fix
In defaulthttpclient(), disable automatic redirect following:
rust fn defaulthttpclient() -> reqwest::Client { reqwest::Client::builder() .poolmaxidleperhost(0) .redirect(reqwest::redirect::Policy::none()) // <-- add this .build() .expect("failed to build default reqwest client") }
The transport can then inspect 3xx responses and decide whether to follow, stripping sensitive headers before doing so. Alternatively, use reqwest::ClientBuilder::connectionverbose or per-request Request::headersmut() to remove auth headers before the redirect is followed.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
rust/rmcpto a version that resolves this vulnerability.Fixed in 2.1.0 - Configuration
In default_http_client(), disable automatic redirect following by adding `.redirect(reqwest::redirect::Policy::none())` to the reqwest::Client/RequestBuilder so that `307/308` redirects are not followed (prevents forwarding `StreamableHttpClientTransportConfig.custom_headers` like `X-API-Key` to attacker-controlled redirect targets).
rmcp::StreamableHttpClientTransport (default_http_client) reqwest redirect policy (redirect(reqwest::redirect::Policy::none())) = Policy::none() - Configuration
Ensure values provided via `StreamableHttpClientTransportConfig.custom_headers` (e.g., `x-api-key`) are sensitivity-marked/classified as sensitive so they are not forwarded to redirect targets on cross-origin redirects. (The issue is that custom headers are not classified as sensitive and are therefore forwarded verbatim.)
rmcp::StreamableHttpClientTransportConfig.custom_headers custom_headers sensitivity marking (sensitive-header classification) = mark auth-like custom headers as sensitive so reqwest strips them on cross-origin redirect
Event History
Frequently Asked Questions
Who is exposed to credential leakage?
Deployments using rmcp's StreamableHttpClientTransport with custom headers such as X-API-Key, X-Auth-Token, or Api-Key are exposed when the client follows a cross-origin redirect. Clients that do not send sensitive values in custom headers are not described as affected by this issue.
What must an attacker be able to do to exploit this?
An attacker needs to cause a request to receive a cross-origin 307 or 308 redirect to an attacker-controlled server. The redirected request can then include caller-supplied custom headers, excluding only headers that reqwest classifies as sensitive.
Is the default HTTP client configuration affected?
Yes. The described default_http_client() builds a reqwest client without overriding its redirect policy, leaving reqwest's default limited(10) redirect behavior in place. That behavior follows 307 and 308 redirects and does not treat custom authentication headers as sensitive.
How can I determine whether my use is exposed?
Review whether StreamableHttpClientTransportConfig.custom_headers is populated with credentials or other secrets, and whether requests can be redirected to a different origin. The advisory identifies commit c330fede90e4729c234f8e87fdbc5ea27a1dd10c as tested, but does not provide an affected version range.