Hmbown/CodeWhale · error

MCP SSE endpoint {} is not same-origin as {} — refusing to s

Error message

MCP SSE endpoint {} is not same-origin as {} — refusing to send authenticated requests cross-origin

What it means

Security guard: the endpoint event's URL resolved to a different origin (scheme, host, or port) than the connect URL. Because authenticated POSTs (Bearer/OAuth headers) target that endpoint, a cross-origin value would let a malicious MCP server redirect the client's credentials at internal hosts (169.254.169.254, localhost admin ports) — an SSRF/policy bypass. Relative endpoints are same-origin by construction and always pass.

Source

Thrown at crates/tui/src/mcp/sse.rs:260

        let base = reqwest::Url::parse(base_url)?;
        let resolved =
            if endpoint_url.starts_with("http://") || endpoint_url.starts_with("https://") {
                reqwest::Url::parse(endpoint_url)?
            } else {
                base.join(endpoint_url)?
            };
        // Security: the server-supplied `endpoint` event must stay same-origin
        // as the connect URL. The connect host is vetted by network policy
        // once, but the endpoint host is never re-checked — so an absolute
        // cross-origin endpoint would let a malicious MCP server redirect the
        // client's *authenticated* POSTs (Bearer/OAuth headers attached) to an
        // internal host (169.254.169.254, localhost admin ports, …): an SSRF /
        // policy bypass. Relative endpoints are same-origin by construction.
        if resolved.scheme() != base.scheme()
            || resolved.host_str() != base.host_str()
            || resolved.port_or_known_default() != base.port_or_known_default()
        {
            anyhow::bail!(
                "MCP SSE endpoint {} is not same-origin as {} — refusing to send \
                 authenticated requests cross-origin",
                mask_url_secrets(resolved.as_str()),
                mask_url_secrets(base.as_str()),
            );
        }
        Ok(resolved.to_string())
    }
}

#[async_trait::async_trait]
impl McpTransport for SseTransport {
    async fn send(&mut self, msg: Vec<u8>) -> Result<()> {
        let endpoint = self
            .endpoint_url
            .as_ref()
            .context("SSE endpoint not yet discovered")?
            .clone();

View on GitHub (pinned to 8880682c63)

Solutions

  1. Fix the server to send a relative endpoint path (e.g. /messages?sessionId=...) or an absolute URL with the same scheme, host, and port as the connect URL
  2. If the server legitimately moved, change the configured MCP server URL to the new origin rather than the endpoint event
  3. Treat unexpected cross-origin endpoints as a security finding in the server, never as a client bug to bypass
Defensive patterns

Strategy: try-catch

Type guard

```rust
fn is_cross_origin_refusal(err: &anyhow::Error) -> bool {
    format!("{err:#}").contains("is not same-origin as")
}
```

Try / catch

```rust
Err(e) if is_cross_origin_refusal(&e) => {
    // Security: the server tried to redirect authenticated POSTs off-origin.
    report_untrusted_server(&server_name, &e); // never bypass; surface prominently
    return Err(e);
}
```

Prevention

When it happens

Trigger: The server sends `event: endpoint` with an absolute URL on another origin — e.g. connect to https://mcp.example.com but endpoint data: http://169.254.169.254/latest or http://127.0.0.1:8081/messages.

Common situations: Misconfigured server advertising an internal or admin origin; load balancers rewriting the advertised endpoint to a different host/port; genuinely malicious servers probing for SSRF.

Understand the failure class

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/53b937d81bc93bc4. Report an issue: GitHub.