RMCP is an official Rust SDK for the Model Context Protocol. Prior to 2.1.0, the rmcp crate's StreamableHttpClientTransport in crates/rmcp/src/transport/common/reqwest/streamable_http_client.rs builds its default_http_client with reqwest's automatic redirect policy and applies caller-supplied values from StreamableHttpClientTransportConfig.custom_headers without marking them as sensitive. When a malicious or compromised MCP endpoint returns a cross-origin 307 or 308 redirect, reqwest follows the redirect and apply_custom_headers causes custom API keys or authentication tokens to be replayed to the new origin, where an attacker can capture and reuse them. The separate auth_header path is not affected because it uses the standard Authorization header, which reqwest strips on cross-origin redirects. This issue is fixed in version 2.1.0.
RMCP is an official Rust SDK for the Model Context Protocol. Prior to 2.1.0, the rmcp crate's StreamableHttpClientTransport in crates/rmcp/src/transport/common/reqwest/streamable_http_client.rs builds its default_http_client with reqwest's automatic redirect policy and applies caller-supplied values from StreamableHttpClientTransportConfig.custom_headers without marking them as sensitive. When a malicious or compromised MCP endpoint returns a cross-origin 307 or 308 redirect, reqwest follows the redirect and apply_custom_headers causes custom API keys or authentication tokens to be replayed to the new origin, where an attacker can capture and reuse them. The separate auth_header path is not affected because it uses the standard Authorization header, which reqwest strips on cross-origin redirects. This issue is fixed in version 2.1.0.
RMCP is an official Rust SDK for the Model Context Protocol. Prior to 2.1.0, the rmcp crate's StreamableHttpClientTransport in crates/rmcp/src/transport/common/reqwest/streamable_http_client.rs builds its default_http_client with reqwest's automatic redirect policy and applies caller-supplied values from StreamableHttpClientTransportConfig.custom_headers without marking them as sensitive. When a malicious or compromised MCP endpoint returns a cross-origin 307 or 308 redirect, reqwest follows the redirect and apply_custom_headers causes custom API keys or authentication tokens to be replayed to the new origin, where an attacker can capture and reuse them. The separate auth_header path is not affected because it uses the standard Authorization header, which reqwest strips on cross-origin redirects. This issue is fixed in version 2.1.0.
## 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 `default_http_client()` 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.custom_headers` 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/streamable_http_client.rs`
**Root cause 1 — no redirect policy override:**
```rust
// Lines 302-307
fn default_http_client() -> reqwest::Client {
reqwest::Client::builder()
.pool_max_idle_per_host(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 apply_custom_headers(
mut builder: reqwest::RequestBuilder,
custom_headers: HashMap<HeaderName, HeaderValue>,
) -> Result<reqwest::RequestBuilder, StreamableHttpError<reqwest::Error>> {
for (name, value) in custom_headers {
validate_custom_header(&name).map_err(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.custom_headers` (line 1070), intended for custom auth headers:
```rust
/// Custom HTTP headers to include with every request
pub custom_headers: HashMap<HeaderName, HeaderValue>,
```
## Attack scenario
1. A caller sets `custom_headers` with an API key for the MCP server:
```rust
let config = StreamableHttpClientTransportConfig::with_uri("https://mcp.example.com/mcp")
.custom_headers([(HeaderName::from_static("x-api-key"),
HeaderValue::from_static("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 `auth_header` path (`StreamableHttpClientTransportConfig::auth_header()`) sets the value via `builder.bearer_auth(auth_header)`, which maps to the `Authorization` header — stripped by reqwest on cross-origin redirects. That path is not affected. Only `custom_headers` is vulnerable.
## Fix
In `default_http_client()`, disable automatic redirect following:
```rust
fn default_http_client() -> reqwest::Client {
reqwest::Client::builder()
.pool_max_idle_per_host(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::connection_verbose` or per-request `Request::headers_mut()` to remove auth headers before the redirect is followed.
EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON.
Original publisher date fields
datePublished: Sep 16, 2026, 9:18:48 PM
dateUpdated: Sep 19, 2026, 1:56:51 AM
1 related source bulletins · separate from aliases
openSUSE-SU-2026:11849-1: openai-codex-0.155.1-1.1 on GA media
CVE-2026-63127 · CVE-2026-63128 · CVE-2026-64684
No normalized affected-package data is projected for this source revision. The original publisher record may contain additional product and version evidence; this does not establish that a package is unaffected.
NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON.
Source analysis status: Awaiting Analysis
Weakness classifications: CWE-200
Original publisher date fields
published: 2026-09-16T22:17:04.577
lastModified: 2026-09-23T18:12:04.247
No normalized affected-package data is projected for this source revision. The original publisher record may contain additional product and version evidence; this does not establish that a package is unaffected.