{"record":{"id":"f87dd23e98e7d128","repo":"Hmbown/CodeWhale","slug":"plain-http-is-only-allowed-for-loopback-hosts-use-https","errorCode":null,"errorMessage":"plain http is only allowed for loopback hosts; use https","messagePattern":"plain http is only allowed for loopback hosts; use https","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/cli/src/config_bundles.rs","lineNumber":825,"sourceCode":"        .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\nfn is_loopback_bundle_host(host: &str) -> bool {\n    let normalized = host\n        .strip_prefix('[')\n        .and_then(|value| value.strip_suffix(']'))\n        .unwrap_or(host);\n    normalized.eq_ignore_ascii_case(\"localhost\")","sourceCodeStart":807,"sourceCodeEnd":843,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/cli/src/config_bundles.rs#L807-L843","documentation":"validate_bundle_url only permits plain http for loopback hosts (localhost, 127.0.0.1, ::1, etc. per is_loopback_bundle_host); any other host must use https. This prevents bundle contents — which become executable configuration — from traveling unencrypted across a network where they could be tampered with or observed.","triggerScenarios":"fetch_bundle called with an http:// URL pointing at a non-loopback host (e.g. http://config.internal.example/bundle.toml); the same check applies to redirect targets via validate_bundle_redirect.","commonSituations":"Pointing the bundle URL at an internal HTTP-only mirror; using an http LAN address for a quick test from another machine; legacy infrastructure without TLS on the config host.","solutions":["Serve the bundle over https (enable TLS on the host or front it with a TLS-terminating proxy).","For local testing, bind the server to 127.0.0.1/localhost so plain http is permitted.","Use `ssh -L` or another tunnel so the bundle appears on loopback over http.","Install a self-signed/internal-CA certificate and use https with the CA trusted."],"exampleFix":"// before\nlet url = \"http://config.internal.example/bundle.toml\";\n// after\nlet url = \"https://config.internal.example/bundle.toml\";\n// local testing only:\n// let url = \"http://127.0.0.1:8080/bundle.toml\";","handlingStrategy":"validation","validationCode":"let url = reqwest::Url::parse(input)?;\nlet host = url.host_str().context(\"missing host\")?;\nif url.scheme() == \"http\" && !(host == \"localhost\" || host == \"127.0.0.1\" || host == \"::1\") {\n    return Err(anyhow!(\"non-loopback http bundle URLs are not allowed\"));\n}","typeGuard":"fn is_https_or_loopback(u: &reqwest::Url) -> bool {\n    match u.scheme() {\n        \"https\" => true,\n        \"http\" => matches!(u.host_str(), Some(\"localhost\") | Some(\"127.0.0.1\") | Some(\"::1\")),\n        _ => false,\n    }\n}","tryCatchPattern":null,"preventionTips":["Default to https for every hosted bundle.","For local development bind servers to 127.0.0.1 so loopback http is allowed.","Document internal mirrors with their https endpoints, not raw http addresses."],"tags":["security","tls","url-validation","config-bundles"],"backgroundTag":"invalid-url-format","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"}