zeroclaw-labs/zeroclaw · error

URL userinfo is not allowed

Error message

URL userinfo is not allowed

What it means

While parsing the authority, extract_host rejects any URL containing '@', i.e. userinfo of the form user:pass@host (browser_open.rs:304-306). This is a deliberate security policy: credentials embedded in the URL would leak into logs, browser history, and the tool-result text. The check is substring-based, so even a bare 'https://@example.com' is rejected.

Source

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

        })?;

    let authority = rest.split(['/', '?', '#']).next().ok_or_else(|| {
        ::zeroclaw_log::record!(
            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");
    }

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Remove the userinfo component: use 'https://example.com/path' and authenticate via the target site's own login or OAuth flow after the browser opens.
  2. Never place secrets in URLs routed through tools; pass them via headers or a secrets store in the calling application instead.
  3. If an '@' appears unexpectedly, inspect where the URL was assembled (env var, prompt context) and strip the credentials there.

Example fix

// before
let url = format!("https://{user}:{pass}@api.example.com/v1");

// after: no credentials in the URL; the browser login flow handles auth
let url = "https://api.example.com/v1".to_string();
Defensive patterns

Strategy: validation

Validate before calling

fn url_has_no_userinfo(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.contains('@')
}

Prevention

When it happens

Trigger: Calling browser_open with 'https://user:pass@example.com/', 'http://admin@10.0.0.1/', or any URL where an '@' appears before the first '/', '?', or '#'. Common with URLs copied from database-style connection strings or basic-auth test links.

Common situations: Developers pasting authenticated URLs (internal dashboards, basic-auth staging sites) into the agent; LLMs helpfully appending credentials they saw in context; template code injecting an apikey subdomain like 'https://{key}.service.com' only when the key contains '@'.

Related errors


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