Hmbown/CodeWhale · error

reviewed plugin MCP endpoint has an unsafe origin

Error message

reviewed plugin MCP endpoint has an unsafe origin

What it means

For remote (HTTP/SSE) MCP endpoints from reviewed plugins, the endpoint URL is sanitized: no credentials (userinfo), no query string or fragment, and finally reviewed_remote_origin must yield a safe origin (scheme/host/port without embedded userinfo). If the URL has a scheme/host combination the origin extractor cannot consider safe, this error is returned instead of trusting the endpoint.

Solutions

  1. Use a plain https:// (or http:// where allowed) URL with an explicit host and no credentials, query, or fragment: https://mcp.example.com/mcp.
  2. Move authentication out of the URL into headers/token config instead of user:pass@ userinfo.
  3. Fix the scheme — file://, ws://, and custom schemes are rejected.
  4. Validate the endpoint URL at plugin-review time so this never surfaces at connect time.

Example fix

// before (plugin manifest)
endpoint = "https://user:secret@mcp.example.com/rpc?env=prod"
// after
endpoint = "https://mcp.example.com/rpc"  # auth via headers
Defensive patterns

Strategy: validation

Validate before calling

// reject unsafe endpoints at config load
fn endpoint_ok(raw: &str) -> bool {
    reqwest::Url::parse(raw).map(|u| {
        matches!(u.scheme(), "http" | "https")
            && u.host_str().map_or(false, |h| !h.is_empty())
            && u.username().is_empty()
            && u.password().is_none()
            && u.query().is_none()
            && u.fragment().is_none()
    }).unwrap_or(false)
}

Type guard

fn is_safe_origin(u: &reqwest::Url) -> bool {
    crate::mcp::reviewed_remote_origin(u).is_some()
}

Try / catch

let (endpoint, origin) = sanitize_endpoint(raw).map_err(|e| {
    ConfigError::new(format!("plugin remote endpoint rejected: {e}; use a bare https://host/path URL"))
})?;

Prevention

When it happens

Trigger: Configuring a reviewed plugin's remote MCP endpoint with a URL whose origin cannot be safely derived — non-http(s) scheme, missing host, or (per reviewed_remote_origin) embedded username/password; also reached after the earlier checks for query/fragment pass.

Common situations: Typo'd scheme (ftp://, file://, ws://) in plugin config, a URL like https://user:token@host/mcp carrying credentials, empty/relative URL after template substitution, or an unresolvable/odd host form the URL parser produces no clean origin for.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/d1d3606739a85c00. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/mcp.rs:1117

        .context("open reviewed launch file without links, hard links, or write/delete sharing")
}

#[cfg(all(not(unix), not(windows)))]
fn open_reviewed_launch_file(path: &Path) -> Result<fs::File> {
    fs::File::open(path).context("open reviewed launch file")
}

fn reviewed_remote_endpoint_identity(endpoint: &str) -> Result<(String, String)> {
    let endpoint =
        reqwest::Url::parse(endpoint).context("reviewed plugin MCP endpoint is invalid")?;
    if !endpoint.username().is_empty() || endpoint.password().is_some() {
        anyhow::bail!("reviewed plugin MCP endpoint must not contain user information");
    }
    if endpoint.query().is_some() || endpoint.fragment().is_some() {
        anyhow::bail!("reviewed plugin MCP endpoint must not contain a query or fragment");
    }
    let origin = reviewed_remote_origin(&endpoint)
        .ok_or_else(|| anyhow::anyhow!("reviewed plugin MCP endpoint has an unsafe origin"))?;
    Ok((endpoint.to_string(), origin))
}

fn reviewed_remote_origin(endpoint: &reqwest::Url) -> Option<String> {
    if !endpoint.username().is_empty() || endpoint.password().is_some() {
        return None;
    }
    let host = endpoint.host_str()?;
    let allowed_scheme = endpoint.scheme() == "https"
        || (endpoint.scheme() == "http"
            && (host.eq_ignore_ascii_case("localhost")
                || host
                    .trim_matches(['[', ']'])
                    .parse::<std::net::IpAddr>()
                    .is_ok_and(|address| address.is_loopback())));
    allowed_scheme.then(|| endpoint.origin().ascii_serialization())
}

View on GitHub (pinned to 73e0f67d83)