{"record":{"id":"243a4c3d9f7480fd","repo":"Hmbown/CodeWhale","slug":"mcp-redirect-leaves-the-reviewed-plugin-origin","errorCode":null,"errorMessage":"MCP redirect leaves the reviewed plugin origin","messagePattern":"MCP redirect leaves the reviewed plugin origin","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp/http_client.rs","lineNumber":166,"sourceCode":"                // retaining just Authorization/ Cookie exclusions is insufficient.\n                let mut headers = header::HeaderMap::new();\n                for name in [header::ACCEPT, header::CONTENT_TYPE] {\n                    if let Some(value) = request.headers().get(&name) {\n                        headers.insert(name, value.clone());\n                    }\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","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/mcp/http_client.rs#L148-L184","documentation":"For reviewed plugins, every redirect target must still match the reviewed origin (via reviewed_redirect_matches_origin). A redirect that moves the client to a different origin would silently change which server receives MCP traffic and credentials, so it is blocked.","triggerScenarios":"client_for_target (invoked from execute_inner on a redirect) sees a next_url whose origin differs from the client's configured origin while self.reviewed_plugin is true and reviewed_redirect_matches_origin rejects the target.","commonSituations":"A reviewed plugin endpoint redirects to a CDN or auth domain on another origin; a server migrates domains and redirects old->new host, which reviewed-plugin policy does not allow; load balancer redirecting to a regional hostname.","solutions":["Re-review the plugin with the new origin so the redirect target is in the reviewed set","Have the server redirect only within the same origin (same scheme+host+port)","Contact the plugin reviewer/authority to add the redirect target origin"],"exampleFix":"// before\n// server at https://a.example.com/mcp responds: Location: https://b.example.com/mcp\n// after\n// server responds within its reviewed origin:\n// Location: https://a.example.com/mcp/v2  (same origin)","handlingStrategy":"validation","validationCode":"let origin = Url::parse(endpoint)?.origin().ascii_serialization();\n// before following redirects, confirm target stays on reviewed origin\nlet next = base.join(location)?;\nif next.origin().ascii_serialization() != origin {\n    // requires re-review of the plugin\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = client.execute(req).await {\n    if e.to_string().contains(\"leaves the reviewed plugin origin\") {\n        // re-review/re-approve the plugin for the new origin\n    }\n}","preventionTips":["Keep reviewed plugin endpoints redirect-free or same-origin only","Re-review plugins whenever their servers change domains","Pin plugin endpoints to stable hostnames (CNAME) rather than redirecting"],"tags":["mcp","http","redirect","origin","security"],"backgroundTag":"invalid-url","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"}