{"record":{"id":"d1d3606739a85c00","repo":"Hmbown/CodeWhale","slug":"reviewed-plugin-mcp-endpoint-has-an-unsafe-origin","errorCode":null,"errorMessage":"reviewed plugin MCP endpoint has an unsafe origin","messagePattern":"reviewed plugin MCP endpoint has an unsafe origin","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp.rs","lineNumber":1002,"sourceCode":"        .context(\"open reviewed launch file without links, hard links, or write/delete sharing\")\n}\n\n#[cfg(all(not(unix), not(windows)))]\nfn open_reviewed_launch_file(path: &Path) -> Result<fs::File> {\n    fs::File::open(path).context(\"open reviewed launch file\")\n}\n\nfn reviewed_remote_endpoint_identity(endpoint: &str) -> Result<(String, String)> {\n    let endpoint =\n        reqwest::Url::parse(endpoint).context(\"reviewed plugin MCP endpoint is invalid\")?;\n    if !endpoint.username().is_empty() || endpoint.password().is_some() {\n        anyhow::bail!(\"reviewed plugin MCP endpoint must not contain user information\");\n    }\n    if endpoint.query().is_some() || endpoint.fragment().is_some() {\n        anyhow::bail!(\"reviewed plugin MCP endpoint must not contain a query or fragment\");\n    }\n    let origin = reviewed_remote_origin(&endpoint)\n        .ok_or_else(|| anyhow::anyhow!(\"reviewed plugin MCP endpoint has an unsafe origin\"))?;\n    Ok((endpoint.to_string(), origin))\n}\n\nfn reviewed_remote_origin(endpoint: &reqwest::Url) -> Option<String> {\n    if !endpoint.username().is_empty() || endpoint.password().is_some() {\n        return None;\n    }\n    let host = endpoint.host_str()?;\n    let allowed_scheme = endpoint.scheme() == \"https\"\n        || (endpoint.scheme() == \"http\"\n            && (host.eq_ignore_ascii_case(\"localhost\")\n                || host\n                    .trim_matches(['[', ']'])\n                    .parse::<std::net::IpAddr>()\n                    .is_ok_and(|address| address.is_loopback())));\n    allowed_scheme.then(|| endpoint.origin().ascii_serialization())\n}\n","sourceCodeStart":984,"sourceCodeEnd":1020,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/mcp.rs#L984-L1020","documentation":"reviewed_remote_origin accepts only https endpoints, or http when the host is exactly `localhost` or a loopback IP literal, with a host present and no userinfo (crates/tui/src/mcp.rs:1006-1019); the resulting origin is pinned as the approved origin that later redirects must stay on. This error means the reviewed plugin's endpoint fell outside those rules - an unsafe origin for a trusted plugin.","triggerScenarios":"Endpoints like http://192.168.1.10:8080/mcp (plain http on a non-loopback host), ws:// or other schemes, or a URL with no host component.","commonSituations":"LAN/home-lab MCP servers over plain http; scheme typos (ws:// copied from examples); endpoints pasted without the host.","solutions":["Serve the endpoint over HTTPS (put a TLS reverse proxy in front if the server itself cannot do TLS).","For local servers keep http but address it as http://localhost:PORT or http://127.0.0.1:PORT - loopback IP literals are allowed.","Fix scheme typos, then re-trust the plugin so the new origin is pinned."],"exampleFix":"# before\nurl = \"http://192.168.1.20:8931/mcp\"\n# after: TLS via reverse proxy, or loopback literal for local servers\nurl = \"https://mcp.home.example.com/mcp\"\n# (local alternative: url = \"http://127.0.0.1:8931/mcp\")","handlingStrategy":"validation","validationCode":"fn reviewed_origin_ok(endpoint: &str) -> bool {\n    let Ok(u) = reqwest::Url::parse(endpoint) else { return false };\n    if !u.username().is_empty() || u.password().is_some() { return false; }\n    let Some(host) = u.host_str() else { return false; };\n    u.scheme() == \"https\"\n        || (u.scheme() == \"http\"\n            && (host.eq_ignore_ascii_case(\"localhost\")\n                || host.trim_matches(['[', ']'])\n                    .parse::<std::net::IpAddr>()\n                    .is_ok_and(|a| a.is_loopback())))\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Serve reviewed plugin endpoints over HTTPS; plain http only on localhost/loopback literals.","Avoid ws:// and other schemes in plugin endpoints.","Re-trust after any endpoint change so the new origin is pinned."],"tags":["security","tls","url","plugin","mcp"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}