zeroclaw-labs/zeroclaw · warning · anyhow::Error

Only http:// and https:// URLs are allowed

Error message

Only http:// and https:// URLs are allowed

What it means

validate_text_browser_url only permits URLs whose string form starts with http:// or https://. Any other scheme — file://, ftp://, data:, gopher://, about:, or an uppercase HTTP:// — fails this prefix check before reqwest::Url::parse is even attempted.

Source

Thrown at crates/zeroclaw-tools/src/text_browser.rs:226

}

fn validate_text_browser_url(
    url: &str,
    allowed_private_hosts: &[String],
    validate_dns: impl FnOnce(&str, bool) -> anyhow::Result<()>,
) -> anyhow::Result<String> {
    let url = url.trim();

    if url.is_empty() {
        anyhow::bail!("URL cannot be empty");
    }

    if url.chars().any(char::is_whitespace) {
        anyhow::bail!("URL cannot contain whitespace");
    }

    if !url.starts_with("http://") && !url.starts_with("https://") {
        anyhow::bail!("Only http:// and https:// URLs are allowed");
    }

    let parsed = reqwest::Url::parse(url)
        .map_err(|e| anyhow::Error::msg(format!("Invalid URL format: {e}")))?;

    if !parsed.username().is_empty() || parsed.password().is_some() {
        anyhow::bail!("URL userinfo is not allowed");
    }

    let host_str = parsed
        .host_str()
        .ok_or_else(|| anyhow::Error::msg("URL must include a host"))?;

    let bare_host = host_str.trim_start_matches('[').trim_end_matches(']');
    let is_ipv6 = bare_host.parse::<std::net::Ipv6Addr>().is_ok();
    let (host, display_host) = if is_ipv6 {
        let bare = bare_host.parse::<std::net::Ipv6Addr>().unwrap().to_string();
        (bare.clone(), format!("[{bare}]"))

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Use an http:// or https:// URL
  2. Normalize the scheme to lowercase before calling the tool
  3. For local file reading, use the dedicated file tools — the text browser intentionally refuses other schemes

Example fix

// before
{"url":"HTTP://Example.COM/docs"}
// after
{"url":"http://example.com/docs"}
Defensive patterns

Strategy: validation

Validate before calling

let normalized = url.trim().to_ascii_lowercase();
if !(normalized.starts_with("http://") || normalized.starts_with("https://")) { /* reject */ }

Type guard

fn is_http_url(u: &str) -> bool {
    let u = u.trim().to_ascii_lowercase();
    u.starts_with("http://") || u.starts_with("https://")
}

Try / catch

Err(e) if e.to_string().starts_with("Only http://") => {
    // non-http schemes are unsupported by design: reject the input, do not retry
}

Prevention

When it happens

Trigger: Passing "ftp://mirror.example.com/file", "file:///etc/passwd", "data:text/html,...", or "HTTP://EXAMPLE.COM" (the check is case-sensitive on the scheme); relative URLs like "/docs/index.html" also fail.

Common situations: Users pasting links from file managers or older docs that use ftp; attempts to read local files through the browser tool (blocked by design); uppercase schemes from auto-generated URLs that were never normalized to lowercase.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/206c27332794a805. Report an issue: GitHub.