zeroclaw-labs/zeroclaw · error

IPv6 hosts are not supported in browser_open

Error message

IPv6 hosts are not supported in browser_open

What it means

extract_host rejects authorities beginning with '[', which is the RFC 3986 marker for an IPv6 literal such as [2001:db8::1] or [::1] (browser_open.rs:308-310). The colon-based host parser downstream cannot handle bracketed v6 addresses, so browser_open explicitly refuses them instead of mis-parsing. It is a capability limit, not a transient fault.

Source

Thrown at crates/zeroclaw-tools/src/browser_open.rs:309

            WARN,
            ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
                .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                .with_attrs(::serde_json::json!({"url": url})),
            "browser_open: invalid URL"
        );
        anyhow::Error::msg("Invalid URL")
    })?;

    if authority.is_empty() {
        anyhow::bail!("URL must include a host");
    }

    if authority.contains('@') {
        anyhow::bail!("URL userinfo is not allowed");
    }

    if authority.starts_with('[') {
        anyhow::bail!("IPv6 hosts are not supported in browser_open");
    }

    let host = authority
        .split(':')
        .next()
        .unwrap_or_default()
        .trim()
        .trim_end_matches('.')
        .to_lowercase();

    if host.is_empty() {
        anyhow::bail!("URL must include a valid host");
    }

    Ok(host)
}

#[cfg(test)]

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Use a hostname instead of the literal: map a DNS name or /etc/hosts entry (e.g. 'service.local') to the IPv6 address and open 'http://service.local:8080/health'.
  2. Use the IPv4 form of the address if the service also listens on v4; note that private/local v4 hosts additionally require a browser.allowed_private_hosts entry.
  3. Add the chosen hostname to [browser].allowed_domains in config.toml so the allowlist check passes.

Example fix

// before
{"url": "http://[::1]:8080/health"}

// after: /etc/hosts maps service.local -> ::1, config allowlists it
{"url": "http://service.local:8080/health"}
# config.toml
[browser]
allowed_domains = ["service.local"]
Defensive patterns

Strategy: validation

Validate before calling

fn url_host_is_not_ipv6_literal(url: &str) -> bool {
    let Some(rest) = url
        .strip_prefix("https://")
        .or_else(|| url.strip_prefix("http://"))
    else {
        return false;
    };
    let authority = rest.split(['/', '?', '#']).next().unwrap_or("");
    !authority.starts_with('[')
}

Prevention

When it happens

Trigger: Calling browser_open with 'http://[::1]:8080/health', 'https://[2001:db8::25de::ad24]/page', or any URL whose host is written as a bracketed IPv6 literal. Typical when targeting local or LAN services addressed by raw v6 address.

Common situations: Local development against services bound to ::1, dual-stack networks where DNS returns v6 and someone copied the literal, and monitoring links to v6-only hosts.

Related errors


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