VulnWatch VulnWatch
← Back to dashboard
Medium github · GHSA-9g45-5xwm-f3wc

RMCP: Custom HTTP headers leak to cross-origin redirect targets

Published Sep 17, 2026 CVSS 6.8

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:

// 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:

// Lines 26-35
fn apply_custom_headers(
    mut builder: reqwest::RequestBuilder,
    custom_headers: HashMap,
) -> Result {
    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:

/// Custom HTTP headers to include with every request
pub custom_headers: HashMap,

Attack scenario

  1. A caller sets custom_headers with an API key for the MCP server:
    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:

fn default_http_client() -> reqwest::Client {
    reqwest::Client::builder()
        .pool_max_idle_per_host(0)
        .redirect(reqwest::redirect::Policy::none())  //

Affected AI Products

mcp server
Get the weekly digest. Every Monday: top AI security stories of the week. Free.