{"record":{"id":"8093696236919700","repo":"zeroclaw-labs/zeroclaw","slug":"url-userinfo-is-not-allowed-809369","errorCode":null,"errorMessage":"URL userinfo is not allowed","messagePattern":"URL userinfo is not allowed","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/browser_open.rs","lineNumber":305,"sourceCode":"        })?;\n\n    let authority = rest.split(['/', '?', '#']).next().ok_or_else(|| {\n        ::zeroclaw_log::record!(\n            WARN,\n            ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)\n                .with_outcome(::zeroclaw_log::EventOutcome::Failure)\n                .with_attrs(::serde_json::json!({\"url\": url})),\n            \"browser_open: invalid URL\"\n        );\n        anyhow::Error::msg(\"Invalid URL\")\n    })?;\n\n    if authority.is_empty() {\n        anyhow::bail!(\"URL must include a host\");\n    }\n\n    if authority.contains('@') {\n        anyhow::bail!(\"URL userinfo is not allowed\");\n    }\n\n    if authority.starts_with('[') {\n        anyhow::bail!(\"IPv6 hosts are not supported in browser_open\");\n    }\n\n    let host = authority\n        .split(':')\n        .next()\n        .unwrap_or_default()\n        .trim()\n        .trim_end_matches('.')\n        .to_lowercase();\n\n    if host.is_empty() {\n        anyhow::bail!(\"URL must include a valid host\");\n    }\n","sourceCodeStart":287,"sourceCodeEnd":323,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/browser_open.rs#L287-L323","documentation":"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.","triggerScenarios":"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.","commonSituations":"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 '@'.","solutions":["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.","Never place secrets in URLs routed through tools; pass them via headers or a secrets store in the calling application instead.","If an '@' appears unexpectedly, inspect where the URL was assembled (env var, prompt context) and strip the credentials there."],"exampleFix":"// before\nlet url = format!(\"https://{user}:{pass}@api.example.com/v1\");\n\n// after: no credentials in the URL; the browser login flow handles auth\nlet url = \"https://api.example.com/v1\".to_string();","handlingStrategy":"validation","validationCode":"fn url_has_no_userinfo(url: &str) -> bool {\n    let Some(rest) = url\n        .strip_prefix(\"https://\")\n        .or_else(|| url.strip_prefix(\"http://\"))\n    else {\n        return false;\n    };\n    let authority = rest.split(['/', '?', '#']).next().unwrap_or(\"\");\n    !authority.contains('@')\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never embed user:pass@ in URLs sent to tools; pass credentials via headers or the site's auth flow.","Scrub '@' from prompt-provided URLs before invoking browser_open.","Treat any '@' before the first '/' in a URL as a credential leak in logs and reject it early."],"tags":["url-validation","security","userinfo","rust","zeroclaw"],"backgroundTag":"url-userinfo-rejected","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}