Hmbown/CodeWhale · error

MCP redirect leaves the reviewed plugin origin

Error message

MCP redirect leaves the reviewed plugin origin

What it means

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.

Solutions

  1. Re-review the plugin with the new origin so the redirect target is in the reviewed set
  2. Have the server redirect only within the same origin (same scheme+host+port)
  3. Contact the plugin reviewer/authority to add the redirect target origin

Example fix

// before
// server at https://a.example.com/mcp responds: Location: https://b.example.com/mcp
// after
// server responds within its reviewed origin:
// Location: https://a.example.com/mcp/v2  (same origin)
Defensive patterns

Strategy: validation

Validate before calling

let origin = Url::parse(endpoint)?.origin().ascii_serialization();
// before following redirects, confirm target stays on reviewed origin
let next = base.join(location)?;
if next.origin().ascii_serialization() != origin {
    // requires re-review of the plugin
}

Try / catch

if let Err(e) = client.execute(req).await {
    if e.to_string().contains("leaves the reviewed plugin origin") {
        // re-review/re-approve the plugin for the new origin
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


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

Appendix: source

Thrown at crates/tui/src/mcp/http_client.rs:166

                // retaining just Authorization/ Cookie exclusions is insufficient.
                let mut headers = header::HeaderMap::new();
                for name in [header::ACCEPT, header::CONTENT_TYPE] {
                    if let Some(value) = request.headers().get(&name) {
                        headers.insert(name, value.clone());
                    }
                }
                *request.headers_mut() = headers;
            }
            *request.url_mut() = next_url;
        }
        unreachable!("redirect loop is bounded")
    }

    async fn client_for_target(&self, url: &Url) -> Result<reqwest::Client> {
        validate_url(url)?;
        let same_origin = url.origin().ascii_serialization() == self.origin;
        if self.reviewed_plugin && !super::reviewed_redirect_matches_origin(url, &self.origin) {
            bail!("MCP redirect leaves the reviewed plugin origin");
        }
        validate_network_policy(url, self.network_policy.as_ref())?;
        let operator_origin = self.operator_configured && same_origin;
        if !operator_origin && url_has_credentials(url) {
            bail!("MCP HTTP discovered URL must not contain credentials");
        }
        let proxy =
            super::configured_mcp_proxy(url, !operator_origin || self.reviewed_plugin, |key| {
                std::env::var(key)
            })?;
        // A selected operator proxy resolves its own destinations. This is
        // delegated proxy authority, never evidence of a local DNS pin.
        let pin = if (self.private_origin_allowed && same_origin) || proxy.is_some() {
            None
        } else {
            self.public_dns_pin(url).await?
        };
        // Validate DNS before reusing a client too: a new private answer revokes

View on GitHub (pinned to 73e0f67d83)