{"record":{"id":"4f7d4438aeea52eb","repo":"zeroclaw-labs/zeroclaw","slug":"url-userinfo-is-not-allowed-4f7d44","errorCode":null,"errorMessage":"URL userinfo is not allowed","messagePattern":"URL userinfo is not allowed","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/http_request.rs","lineNumber":728,"sourceCode":"                .with_attrs(::serde_json::json!({\"url\": url})),\n            \"http_request: non-http(s) URL rejected\"\n        );\n        anyhow::bail!(\"Only http:// and https:// URLs are allowed\");\n    }\n\n    let parsed = reqwest::Url::parse(url).map_err(|e| {\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            \"http_request: invalid URL\"\n        );\n        anyhow::Error::msg(format!(\"Invalid URL format: {e}\"))\n    })?;\n\n    if !parsed.username().is_empty() || parsed.password().is_some() {\n        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\");","sourceCodeStart":710,"sourceCodeEnd":746,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/http_request.rs#L710-L746","documentation":"Thrown by extract_host (crates/zeroclaw-tools/src/http_request.rs:728) when the parsed URL carries userinfo — a username and/or password before the host, as in \"https://user:pass@example.com\". Credentials embedded in URLs leak into logs, referers, and error messages, and defeat the Authorization/auth_secret secret handling, so the tool rejects them outright.","triggerScenarios":"url = \"https://admin:secret@192.168.1.5/api\"; pasting a database-style or basic-auth URL from browser history/docs (browsers show user:pass@host); scrapers reusing URLs that embed API keys as userinfo; LLM-generated URLs copying an authenticated link verbatim.","commonSituations":"Basic-auth protected endpoints where users copy the authenticated URL instead of the host; legacy bookmarks containing credentials; API docs that illustrate auth via userinfo; secrets accidentally committed inside URLs.","solutions":["Remove userinfo from the URL and send credentials via the Authorization header, ideally through the auth_secret parameter so the value stays in config.toml.","For basic auth, store the Base64 of \"user:pass\" (or the raw credentials, depending on your scheme) as a secret and pass its name in auth_secret.","Rotate any credential that was embedded in a logged URL."],"exampleFix":"# before\nurl = \"https://admin:s3cret@example.com/api\"\n\n# after\n[http_request.secrets]\nbasic_auth = \"Basic YWRtaW46czNjcmV0\"  # base64 of admin:s3cret\n# caller: {\"url\": \"https://example.com/api\", \"auth_secret\": \"basic_auth\"}","handlingStrategy":"validation","validationCode":"fn url_has_userinfo(url: &str) -> bool {\n    reqwest::Url::parse(url)\n        .map(|u| !u.username().is_empty() || u.password().is_some())\n        .unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"let result = tool.execute(args).await?;\nif let Some(err) = &result.error {\n    if err.contains(\"URL userinfo is not allowed\") {\n        // move credentials to auth_secret / Authorization header, then retry\n    }\n}","preventionTips":["Never construct URLs with embedded user:pass; keep credentials in [http_request.secrets].","Sanitize pasted URLs by stripping anything before the host.","If a credential ever landed in a URL, rotate it: URLs end up in logs."],"tags":["http","url","auth","security","zeroclaw"],"backgroundTag":"url-userinfo-rejected","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}