Hmbown/CodeWhale · error

MCP HTTP DNS resolved to a restricted IP address

Error message

MCP HTTP DNS resolved to a restricted IP address

What it means

validated_public_address rejects DNS resolutions for an MCP HTTP destination when ANY resolved socket address is a restricted IP. This is the hostname path of the same SSRF defense as error 1150: even if you use a domain name, the client pins DNS resolution and refuses to connect if the resolver returns private/loopback/link-local addresses. It prevents DNS-based SSRF and rebinding to internal networks.

Solutions

  1. Update DNS so the MCP server hostname resolves to a public, non-restricted address
  2. Use a different hostname that resolves publicly for the same server
  3. If the server is intentionally internal, expose it via an allowed public endpoint instead of pointing DNS at the private IP
  4. Verify with dig/nslookup what the hostname resolves to in this environment and correct the record

Example fix

// before
mcp.example.com.  IN A  10.0.0.5
// after
mcp.example.com.  IN A  203.0.113.10
Defensive patterns

Strategy: validation

Validate before calling

async fn resolves_to_public(host: &str) -> bool {
    tokio::net::lookup_host((host, 443u16)).await
        .map(|addrs| addrs.all(|a| !is_restricted_ip(&a.ip())))
        .unwrap_or(false)
}

Try / catch

match client_for_target(&url).await {
    Err(e) if e.to_string().contains("DNS resolved to a restricted IP") => {
        eprintln!("host resolves to a private address; fix DNS or use a public endpoint");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Connecting to an MCP server via hostname whose DNS A/AAAA records (resolved through the pinned resolver in public_dns_pin) include a restricted IP such as 10.x.x.x, 127.0.0.1, 169.254.169.254, or ::1.

Common situations: A DNS record pointing at an internal service, split-horizon DNS returning private IPs in the current environment, /etc/hosts or corporate resolver entries mapping the host to a local address, or a tunneling setup resolving to loopback.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

    let Some(host) = url.host_str() else {
        return false;
    };
    let host = host.trim_end_matches('.');
    host.eq_ignore_ascii_case("localhost")
        || host.to_ascii_lowercase().ends_with(".localhost")
        || host
            .trim_start_matches('[')
            .trim_end_matches(']')
            .parse::<IpAddr>()
            .is_ok_and(|ip| is_restricted_ip(&ip))
}

fn validated_public_address(addresses: &[SocketAddr]) -> Result<SocketAddr> {
    if addresses
        .iter()
        .any(|address| is_restricted_ip(&address.ip()))
    {
        bail!("MCP HTTP DNS resolved to a restricted IP address");
    }
    addresses
        .first()
        .copied()
        .context("MCP HTTP DNS resolved to no addresses")
}

#[cfg(test)]
mod tests {
    use super::*;
    use tokio::io::{AsyncReadExt, AsyncWriteExt};
    use tokio::net::TcpListener;

    fn client(url: &str, runtime_added: bool) -> McpHttpClient {
        McpHttpClient::new(
            url,
            runtime_added,
            false,

View on GitHub (pinned to 73e0f67d83)