{"record":{"id":"b3900353943296b0","repo":"zeroclaw-labs/zeroclaw","slug":"url-must-include-a-valid-host-b39003","errorCode":null,"errorMessage":"URL must include a valid host","messagePattern":"URL must include a valid host","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/http_request.rs","lineNumber":746,"sourceCode":"        anyhow::bail!(\"URL userinfo is not allowed\");\n    }\n\n    let host = parsed\n        .host_str()\n        .ok_or_else(|| anyhow::Error::msg(\"URL must include a host\"))?;\n\n    let trimmed = host.trim();\n    let host_no_brackets = match (trimmed.starts_with('['), trimmed.ends_with(']')) {\n        (true, true) => &trimmed[1..trimmed.len() - 1],\n        (false, false) => trimmed,\n        _ => {\n            anyhow::bail!(\"URL host has unmatched IPv6 brackets\");\n        }\n    };\n    let host = host_no_brackets.trim_end_matches('.').to_lowercase();\n\n    if host.is_empty() {\n        anyhow::bail!(\"URL must include a valid host\");\n    }\n\n    Ok(host)\n}\n\nfn extract_port(url: &str) -> anyhow::Result<u16> {\n    let parsed = reqwest::Url::parse(url)\n        .map_err(|e| anyhow::Error::msg(format!(\"Invalid URL format: {e}\")))?;\n\n    parsed\n        .port_or_known_default()\n        .ok_or_else(|| anyhow::Error::msg(\"URL must include a valid port\"))\n}\n\nasync fn resolve_host_for_request(host: String, port: u16) -> anyhow::Result<Vec<SocketAddr>> {\n    let addrs = tokio::net::lookup_host((host.as_str(), port))\n        .await\n        .map_err(|e| anyhow::Error::msg(format!(\"Failed to resolve host '{host}': {e}\")))?","sourceCodeStart":728,"sourceCodeEnd":764,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/http_request.rs#L728-L764","documentation":"Thrown by extract_host in the http_request tool after the URL parsed and passed the scheme and userinfo checks, but its host reduced to an empty string once trailing root-label dots were stripped and the value lowercased. In other words the host consisted solely of one or more '.' characters (for example 'https://.'). A dot-only host cannot be resolved or SSRF-validated, so the request is rejected before any network I/O happens.","triggerScenarios":"Calling the http_request tool with a URL whose host part is only dots: 'http://.', 'https://..', 'https://...'. Any input where reqwest::Url::parse succeeds and host_str() returns a non-empty string made entirely of '.' characters hits this bail at http_request.rs:746. It cannot be reached with a truly empty host (that is the earlier 'URL must include a host' error at line 733) or with unmatched IPv6 brackets (line 740).","commonSituations":"Placeholder URLs pasted from templates or documentation ('http://...'), typos where dots were typed instead of a hostname, dynamic construction like format!(\"https://{}\", placeholder) where the placeholder was never filled in, and test fixtures that used dot-only hosts.","solutions":["Replace the dot-only host with a real hostname, e.g. 'https://api.example.com/path'.","If the URL is built dynamically, print it before invoking the tool and fix the interpolation that left a placeholder in the host position.","Pre-validate hosts (non-empty after trimming trailing dots) before passing URLs to the tool."],"exampleFix":"// before\nlet url = format!(\"https://{}\", host_placeholder); // host_placeholder = \".\"\ntool_http_request(url).await?; // -> URL must include a valid host\n\n// after\nlet url = format!(\"https://api.example.com{}\", path);\ntool_http_request(url).await?;","handlingStrategy":"validation","validationCode":"fn has_valid_http_host(url: &str) -> bool {\n    let Ok(parsed) = reqwest::Url::parse(url) else { return false };\n    match parsed.host_str() {\n        Some(h) => !h.trim().trim_end_matches('.').is_empty(),\n        None => false,\n    }\n}","typeGuard":"fn is_http_request_url(url: &str) -> bool {\n    (url.starts_with(\"http://\") || url.starts_with(\"https://\")) && has_valid_http_host(url)\n}","tryCatchPattern":"if let Err(e) = run_http_request(url).await {\n    if e.to_string().contains(\"URL must include a valid host\") {\n        // treat as caller input error: surface a fixable message, do not retry\n    }\n}","preventionTips":["Never build URLs by string interpolation with unfilled placeholders; assert the host segment is non-empty before invoking the tool.","Reject dot-only or empty hosts in your own URL validation layer before passing URLs to the agent."],"tags":["url","validation","http-request","ssrf-guard","rust"],"backgroundTag":"invalid-url-host","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}