{"record":{"id":"53e8c0632bff927d","repo":"zeroclaw-labs/zeroclaw","slug":"generated-image-url-userinfo-is-not-allowed","errorCode":null,"errorMessage":"Generated image URL userinfo is not allowed","messagePattern":"Generated image URL userinfo is not allowed","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/image_gen.rs","lineNumber":35,"sourceCode":"\nstruct ValidatedImageTarget {\n    url: reqwest::Url,\n    host: String,\n    resolved_addrs: Vec<SocketAddr>,\n}\n\nfn parse_public_https_url(raw_url: &str) -> anyhow::Result<(reqwest::Url, String, u16)> {\n    let raw_url = raw_url.trim();\n    if raw_url.is_empty() || raw_url.chars().any(char::is_whitespace) {\n        anyhow::bail!(\"Generated image URL must be a non-empty URL without whitespace\");\n    }\n\n    let mut url = reqwest::Url::parse(raw_url).context(\"Invalid generated image URL\")?;\n    if url.scheme() != \"https\" {\n        anyhow::bail!(\"Generated image URL must use HTTPS\");\n    }\n    if !url.username().is_empty() || url.password().is_some() {\n        anyhow::bail!(\"Generated image URL userinfo is not allowed\");\n    }\n\n    let request_host = url\n        .host_str()\n        .ok_or_else(|| anyhow::Error::msg(\"Generated image URL must include a host\"))?;\n    if request_host.ends_with('.') {\n        anyhow::bail!(\"Generated image URL host must not end with a dot\");\n    }\n    let host = domain_guard::normalize_domain(request_host)\n        .ok_or_else(|| anyhow::Error::msg(\"Generated image URL host is invalid\"))?;\n    let ip_literal = host.parse::<IpAddr>().ok();\n    if domain_guard::is_private_or_local_host(&host) {\n        anyhow::bail!(\"Generated image URL targets a local or non-global host\");\n    }\n    if ip_literal.is_some_and(domain_guard::is_cloud_metadata_ip) {\n        anyhow::bail!(\"Generated image URL targets a cloud metadata host\");\n    }\n","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/image_gen.rs#L17-L53","documentation":"parse_public_https_url rejects any generated image URL containing userinfo (a username or password before the host, as in https://user:pass@host/path). Embedded credentials can confuse host parsing and leak secrets into logs, so the SSRF validation pipeline refuses them outright. The same rule exists in the http_request tool's extract_host.","triggerScenarios":"A fal.ai response or custom storage configuration returns a URL like 'https://access-key@cdn.example.com/img.png' or 'https://user:pass@storage.internal/img.png' — typically signed URLs that put tokens in the userinfo position or proxies that embed basic-auth credentials in the link.","commonSituations":"Storage backends or CDNs that generate authenticated links with credentials embedded in the URL, shared-secret SAS-style tokens placed before the @ sign, and copy-pasted URLs from curl examples that include -u style credentials.","solutions":["Reconfigure the storage/CDN to issue credential-free URLs (signed query parameters or short-lived links) and pass any secret via headers if the producer supports it.","Strip the userinfo component from the URL at the source that produces it, so the delivered link is bare host + path.","Never work around this by relaying credentials another way inside the image URL; the guard exists to keep secrets out of the fetch path."],"exampleFix":"# before\nhttps://user:pass@cdn.example.com/files/img.png   # rejected\n\n# after\nhttps://cdn.example.com/files/img.png?token=...   # accepted","handlingStrategy":"validation","validationCode":"fn without_userinfo(raw: &str) -> bool {\n    reqwest::Url::parse(raw).map(|u| u.username().is_empty() && u.password().is_none()).unwrap_or(false)\n}","typeGuard":"fn is_bare_host_url(u: &reqwest::Url) -> bool { u.username().is_empty() && u.password().is_none() }","tryCatchPattern":"match validate_image_target(url, nat64).await {\n    Err(e) if e.to_string().contains(\"userinfo is not allowed\") => {\n        // credentials embedded in the link: move auth to signed query params or headers at the producer\n    }\n    other => other,\n}","preventionTips":["Configure storage to sign URLs with query parameters instead of userinfo credentials.","Scan provider URLs for '@' before the host in your validation layer."],"tags":["url","credentials","image-gen","ssrf-guard","security","rust"],"backgroundTag":"credentials-in-url-rejected","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}