{"record":{"id":"d2b958e666709df2","repo":"Hmbown/CodeWhale","slug":"mcp-http-discovered-url-must-not-contain-credentials","errorCode":null,"errorMessage":"MCP HTTP discovered URL must not contain credentials","messagePattern":"MCP HTTP discovered URL must not contain credentials","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp/http_client.rs","lineNumber":171,"sourceCode":"                    }\n                }\n                *request.headers_mut() = headers;\n            }\n            *request.url_mut() = next_url;\n        }\n        unreachable!(\"redirect loop is bounded\")\n    }\n\n    async fn client_for_target(&self, url: &Url) -> Result<reqwest::Client> {\n        validate_url(url)?;\n        let same_origin = url.origin().ascii_serialization() == self.origin;\n        if self.reviewed_plugin && !super::reviewed_redirect_matches_origin(url, &self.origin) {\n            bail!(\"MCP redirect leaves the reviewed plugin origin\");\n        }\n        validate_network_policy(url, self.network_policy.as_ref())?;\n        let operator_origin = self.operator_configured && same_origin;\n        if !operator_origin && url_has_credentials(url) {\n            bail!(\"MCP HTTP discovered URL must not contain credentials\");\n        }\n        let proxy =\n            super::configured_mcp_proxy(url, !operator_origin || self.reviewed_plugin, |key| {\n                std::env::var(key)\n            })?;\n        // A selected operator proxy resolves its own destinations. This is\n        // delegated proxy authority, never evidence of a local DNS pin.\n        let pin = if (self.private_origin_allowed && same_origin) || proxy.is_some() {\n            None\n        } else {\n            self.public_dns_pin(url).await?\n        };\n        // Validate DNS before reusing a client too: a new private answer revokes\n        // this request. Each cached client itself remains pinned to its old public\n        // address, including reconnects after a keep-alive socket expires.\n        let key = format!(\"{}:{pin:?}\", url.origin().ascii_serialization());\n        if proxy.is_none()\n            && let Some(client) = self","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/mcp/http_client.rs#L153-L189","documentation":"During redirect handling, discovered (non-operator, non-same-origin) targets are checked for URL-embedded credentials. Only the operator-configured same-origin path is exempt; any other redirect target carrying user:password@ is rejected to avoid sending credentials to an endpoint that was never explicitly configured.","triggerScenarios":"client_for_target computes operator_origin = operator_configured && same_origin; when operator_origin is false and url_has_credentials(url) is true (redirect target on another origin, or runtime-added client, with userinfo in the URL).","commonSituations":"Redirect chain lands on a different-origin host that includes basic-auth in the URL; a plugin bounces to an auth gateway with embedded credentials; legacy staging URL with credentials reached via redirect.","solutions":["Authenticate at the discovered target with headers (via configured proxy/headers), not URL userinfo","Make the redirect target same-origin and operator-configured if credentials-in-URL is unavoidable","Remove credentials from the redirecting server's Location header"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"let next = base.join(location)?;\nlet same_origin = next.origin().ascii_serialization() == client_origin;\nif !same_origin && (!next.username().is_empty() || next.password().is_some()) {\n    // discovered target carries credentials -> will be rejected\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = client.execute(req).await {\n    if e.to_string().contains(\"discovered URL must not contain credentials\") {\n        // switch auth to headers or restrict redirect to operator-configured origin\n    }\n}","preventionTips":["Send credentials via configured headers on every hop, never via URL","Limit reviewed plugins to single-origin redirect paths","Inventory plugin endpoints for embedded credentials before review"],"tags":["mcp","http","redirect","credentials","security"],"backgroundTag":"missing-credentials","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T21:17:16.096Z"}