Security: MCP streamable-HTTP client follows server redirects into internal hosts (SSRF + credentialed request forward)
Summary
While connecting to a remote MCP server (streamable HTTP, the MCP spec's transport over POST /mcp), goose builds its own reqwest::Client (crates/goose/src/agents/extension_manager.rs L789):
let mut auth_client_builder = reqwest::Client::builder().default_headers(auth_headers);
That builder never calls .redirect(...), so reqwest's default policy Policy::limited(10) applies: up to 10 redirects are followed to any host, including hosts the user never configured, while goose's custom headers/auth are silently re-sent there.
The streamable HTTP transport hands 100% of HTTP to whichever reqwest client it is given (via StreamableHttpClientTransport::with_client(...) in the same file), so nothing guards the redirect target.
The MCP specification explicitly permits servers to answer with 301/302/307/308 (spec: transports / streamable HTTP redirects; see ecosystem reports, e.g. modelcontextprotocol/python-sdk#3358 for the same class in Python).
Result: a compromised or malicious MCP server (or any Man-in-the-Middle of the TLS-to-MCP connection) can redirect goose's POST to an internal-only endpoint (e.g. AWS IMDS 169.254.169.254, a loopback service, an internal admin API) and:
- get a credentialed request (Authorization / custom
X-...headers / any auth_headers goose attached) smuggled to that internal host — header leakage; - have goose accept that internal host's reply as if it were the MCP server's response (protocol confusion), because the redirect target's 200 reply is consumed as the JSON-RPC response.
There is no host allow-list (only rust-sdk's rmcp default refuses to follow at all; goose does not use that default).
Severity
Moderate SSRF (CWE-918) + credentialed-forward / header leak (CWE-522-ish behavior). Realistic impact: unauthenticated read of internal services reachable from wherever goose runs; on cloud VMs, first-hop IMDS credential exposure.
Affected
All goose releases up to workspace 1.47.0 (HEAD 8d844ee, 2026-08-21). No patched release yet. Repo-scoped advisory; package/version data for goose-agent (crates.io) may be added by maintainers.
Proof of concept
Packaged, self-contained repro (needs only a Rust toolchain): cve-hunt/poc-goose-rust -> cargo run. It starts (a) a fake remote MCP endpoint that 307s to (b) an internal "victim" service that logs every request, then:
- goose-style client (
reqwest::Client::builder().default_headers(...), exactly L789): follows the redirect; victim reportsPOST /mcp,user-agent: goose,x-ext-auth: d31294a7f9c8— the client reached the internal host WITH the configured auth header, and accepted the victim's reply as the server response (victim hit count increments). - rust-sdk rmcp default client (
redirect(Policy::none()), the SDK's own privatedefault_http_client(), transport/common/reqwest, L381): refuses; victim never contacted; the redirect is surfaced asErr(StreamableHttpError::UnexpectedServerResponse("HTTP 307 ..."))(LEAD-5 conformance gap — a spec-permitted redirect hard-fails against the official Rust SDK).
Suggested fix
Option A (goose, minimal): on the auth client builder, .redirect(reqwest::redirect::Policy::limited(10).require_uri_rewrite(true).new_blocker()) with an allow-list that blocks HTTP→HTTPS-only, loopback, and private/link-local destinations (169.254.0.0/16), mirroring how other clients (e.g. TypeScript SDK / Python SDK discussion #2106→#2180) treat redirect targets.
Option B (goose, conservative): do not follow MCP redirects by default (Policy::none()), matching rust-sdk's safe default — the spec's redirect is then surfaced for explicit handling.
References
- goose
extension_manager.rsL789 (client construction): https://github.com/aaif-goose/goose/blob/main/crates/goose/src/agents/extension_manager.rs - rust-sdk rmcp
default_http_client()(Policy::none) L381 & "redirect should be returned to the transport" test: https://github.com/modelcontextprotocol/rust-sdk/blob/main/crates/rmcp/src/transport/common/reqwest/streamable_http_client.rs - Same vulnerability class in the official Python SDK (open): https://github.com/modelcontextprotocol/python-sdk/issues/3358
- MCP spec — streamable HTTP transport: https://modelcontextprotocol.io/specification/2025-06-18/transports#streamable-http
Happy to be credited in the advisory/release notes as: Mo (@trickyfalcon, https://trickyfalcon.com)
Credit: Happy to be credited in the advisory / release notes as: Mo (@trickyfalcon, https://trickyfalcon.com)
Source: aaif-goose/goose