{"record":{"id":"bf1a30a9d997a575","repo":"Hmbown/CodeWhale","slug":"bundle-urls-may-not-include-credentials","errorCode":null,"errorMessage":"bundle URLs may not include credentials","messagePattern":"bundle URLs may not include credentials","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/cli/src/config_bundles.rs","lineNumber":819,"sourceCode":"    // Read at most MAX_BUNDLE_BYTES + 1 so an oversize body is detected\n    // rather than silently truncated.\n    let mut buffer = Vec::new();\n    let body = response;\n    body.take(MAX_BUNDLE_BYTES + 1)\n        .read_to_end(&mut buffer)\n        .map_err(|_| anyhow!(\"reading remote bundle failed\"))?;\n    if buffer.len() as u64 > MAX_BUNDLE_BYTES {\n        bail!(\"remote bundle exceeds the {MAX_BUNDLE_BYTES} byte limit; refused\");\n    }\n    Ok(buffer)\n}\n\nfn validate_bundle_url(url: &reqwest::Url) -> Result<()> {\n    if !matches!(url.scheme(), \"http\" | \"https\") {\n        bail!(\"unsupported bundle URL scheme; use https\");\n    }\n    if !url.username().is_empty() || url.password().is_some() {\n        bail!(\"bundle URLs may not include credentials\");\n    }\n    let host = url.host_str().context(\"bundle URL must include a host\")?;\n    match url.scheme() {\n        \"https\" => Ok(()),\n        \"http\" if is_loopback_bundle_host(host) => Ok(()),\n        \"http\" => bail!(\"plain http is only allowed for loopback hosts; use https\"),\n        _ => unreachable!(\"scheme was validated above\"),\n    }\n}\n\nfn validate_bundle_redirect(initial_scheme: &str, next_url: &reqwest::Url) -> Result<()> {\n    validate_bundle_url(next_url)?;\n    if next_url.scheme() != initial_scheme {\n        bail!(\"bundle redirects may not change URL scheme\");\n    }\n    Ok(())\n}\n","sourceCodeStart":801,"sourceCodeEnd":837,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/cli/src/config_bundles.rs#L801-L837","documentation":"validate_bundle_url rejects any bundle URL that embeds credentials (userinfo) — a username or password in the URL itself (e.g. https://user:pass@host/bundle.toml). Embedding secrets in URLs leaks them into logs, shell history, redirect targets, and process listings, so the bundle fetcher refuses them outright rather than forwarding them.","triggerScenarios":"Calling fetch_bundle with a URL containing `user:password@` before the host, or a URL with just a username component; validate_bundle_redirect rejects the same shape when a server redirects to a credential-bearing URL.","commonSituations":"Users pasting a URL copied from a tool that embeds an API token in the path userinfo; internal artifact servers that historically used basic-auth-in-URL; CI secrets templated directly into the bundle URL.","solutions":["Remove the username/password from the URL and rely on a credential-free endpoint (token in header or network-level auth).","Serve the bundle over plain https without basic auth — e.g. behind SSO, mTLS, or an IP-allowlisted proxy.","If credentials are unavoidable, pre-fetch the bundle with curl using an auth header and host it locally on loopback HTTP (allowed).","Never place the token in the URL; audit shell history and logs for the leaked credential and rotate it."],"exampleFix":"// before\nlet url = \"https://ci-bot:s3cret@config.internal.example/bundle.toml\";\n// after\nlet url = \"https://config.internal.example/bundle.toml\"; // auth via network layer, not URL","handlingStrategy":"validation","validationCode":"let url = reqwest::Url::parse(input)?;\nif !url.username().is_empty() || url.password().is_some() {\n    return Err(anyhow!(\"strip userinfo from bundle URL\"));\n}","typeGuard":"fn is_credential_free(u: &reqwest::Url) -> bool {\n    u.username().is_empty() && u.password().is_none()\n}","tryCatchPattern":null,"preventionTips":["Never embed user:password@ in URLs; use headers or network-level auth.","Rotate any credential that was ever embedded in a URL — it likely reached logs/history.","Lint CI scripts for the `@` userinfo pattern in bundle URLs."],"tags":["security","credentials","url-validation","config-bundles"],"backgroundTag":"missing-credentials","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}