{"record":{"id":"53b937d81bc93bc4","repo":"Hmbown/CodeWhale","slug":"mcp-sse-endpoint-is-not-same-origin-as-ref","errorCode":null,"errorMessage":"MCP SSE endpoint {} is not same-origin as {} — refusing to send authenticated requests cross-origin","messagePattern":"MCP SSE endpoint (.+?) is not same-origin as (.+?) — refusing to send authenticated requests cross-origin","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp/sse.rs","lineNumber":260,"sourceCode":"        let base = reqwest::Url::parse(base_url)?;\n        let resolved =\n            if endpoint_url.starts_with(\"http://\") || endpoint_url.starts_with(\"https://\") {\n                reqwest::Url::parse(endpoint_url)?\n            } else {\n                base.join(endpoint_url)?\n            };\n        // Security: the server-supplied `endpoint` event must stay same-origin\n        // as the connect URL. The connect host is vetted by network policy\n        // once, but the endpoint host is never re-checked — so an absolute\n        // cross-origin endpoint would let a malicious MCP server redirect the\n        // client's *authenticated* POSTs (Bearer/OAuth headers attached) to an\n        // internal host (169.254.169.254, localhost admin ports, …): an SSRF /\n        // policy bypass. Relative endpoints are same-origin by construction.\n        if resolved.scheme() != base.scheme()\n            || resolved.host_str() != base.host_str()\n            || resolved.port_or_known_default() != base.port_or_known_default()\n        {\n            anyhow::bail!(\n                \"MCP SSE endpoint {} is not same-origin as {} — refusing to send \\\n                 authenticated requests cross-origin\",\n                mask_url_secrets(resolved.as_str()),\n                mask_url_secrets(base.as_str()),\n            );\n        }\n        Ok(resolved.to_string())\n    }\n}\n\n#[async_trait::async_trait]\nimpl McpTransport for SseTransport {\n    async fn send(&mut self, msg: Vec<u8>) -> Result<()> {\n        let endpoint = self\n            .endpoint_url\n            .as_ref()\n            .context(\"SSE endpoint not yet discovered\")?\n            .clone();","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/mcp/sse.rs#L242-L278","documentation":"The MCP SSE transport resolves a relative endpoint against a base URL and refuses to proceed when the resolved endpoint is not same-origin (scheme, host, and port) as the base. This is a security guard: sending authenticated requests (cookies/tokens) to a different origin would leak credentials and enable SSRF/policy bypass. Relative endpoints are same-origin by construction, so only explicitly absolute cross-origin endpoints trip this.","triggerScenarios":"Configuring an MCP server with an SSE endpoint URL whose scheme, host, or port differs from the base URL — e.g. base https://mcp.example.com with endpoint https://api.example.com/sse, or http base with https endpoint, or a different port.","commonSituations":"Copy-pasting an endpoint from another MCP provider; a load balancer or proxy exposing the SSE stream on a different host/port than the API base; switching from http to https (or vice versa) in only one of the two URL fields; pointing the endpoint at localhost while the base points at a remote host.","solutions":["Make the endpoint URL same-origin with the base URL (same scheme, host, and port).","Use a relative endpoint path (e.g. /sse) so it resolves against the base origin automatically.","If the endpoint truly lives on another origin, change the base URL to match that origin, or run a same-origin reverse proxy in front of it.","Verify with a URL parser that scheme, host, and effective port are identical before restarting."],"exampleFix":"// before (codewhale config, MCP server entry)\nurl = \"https://mcp.example.com\"\nendpoint = \"https://api.example.com/sse\"\n\n// after\nurl = \"https://mcp.example.com\"\nendpoint = \"/sse\"","handlingStrategy":"validation","validationCode":"let base = url::Url::parse(base_url)?;\nlet resolved = base.join(endpoint)?;\nif resolved.scheme() != base.scheme()\n    || resolved.host_str() != base.host_str()\n    || resolved.port_or_known_default() != base.port_or_known_default()\n{\n    return Err(\"endpoint must be same-origin as base\".into());\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Prefer relative endpoint paths so same-origin holds by construction.","Lint MCP configs for cross-origin endpoint/base pairs at load time.","Never point authenticated MCP endpoints at a different origin to work around routing; use a proxy instead."],"tags":["mcp","sse","same-origin","security","ssrf"],"backgroundTag":"invalid-url-format","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}