{"record":{"id":"239a306d0e33be3b","repo":"Hmbown/CodeWhale","slug":"codewhale-account-api-base-url-must-use-https-loo","errorCode":null,"errorMessage":"Codewhale account API base URL must use HTTPS (loopback HTTP is allowed for testing)","messagePattern":"Codewhale account API base URL must use HTTPS \\(loopback HTTP is allowed for testing\\)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/cli/src/cloud.rs","lineNumber":1031,"sourceCode":"}\n\nfn validate_api_base(value: &str) -> Result<ValidatedApiBase> {\n    let mut url = Url::parse(value.trim()).context(\"invalid Codewhale account API base URL\")?;\n    if !url.username().is_empty() || url.password().is_some() {\n        bail!(\"Codewhale account API base URL must not contain credentials\");\n    }\n    if url.query().is_some() || url.fragment().is_some() {\n        bail!(\"Codewhale account API base URL must not contain a query or fragment\");\n    }\n    if !matches!(url.path(), \"\" | \"/\") {\n        bail!(\"Codewhale account API base URL must be an origin without a path\");\n    }\n    let host = url\n        .host_str()\n        .ok_or_else(|| anyhow!(\"Codewhale account API base URL must include a host\"))?;\n    let allowed = url.scheme() == \"https\" || (url.scheme() == \"http\" && is_loopback_host(host));\n    if !allowed {\n        bail!(\n            \"Codewhale account API base URL must use HTTPS (loopback HTTP is allowed for testing)\"\n        );\n    }\n    url.set_path(\"/\");\n    let display = url.as_str().trim_end_matches('/').to_string();\n    Ok(ValidatedApiBase { url, display })\n}\n\nfn validate_verification_url(\n    value: &str,\n    api_base: &str,\n    user_code: &str,\n    complete: bool,\n) -> Result<String> {\n    let url =\n        Url::parse(value).context(\"The Codewhale service returned an invalid verification URL\")?;\n    if value != url.as_str() {\n        bail!(\"The Codewhale service returned an unsafe verification URL\");","sourceCodeStart":1013,"sourceCodeEnd":1049,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/cli/src/cloud.rs#L1013-L1049","documentation":"For security the account API must be reached over TLS; validate_api_base requires https, with one carve-out: plain http is accepted only when the host is loopback (localhost/127.0.0.1/::1) for local testing. Any non-loopback http URL is rejected so tokens are never sent in cleartext over a network.","triggerScenarios":"Setting the base URL to http://api.example.com or http://<lan-ip>:port — any http URL whose host is not loopback.","commonSituations":"Pointing at an internal staging server without TLS; self-hosting behind plain http on a LAN; skipping the reverse-proxy TLS step.","solutions":["Use https:// for the endpoint (Let's Encrypt or your internal CA).","For local testing only, use a loopback host: http://localhost:8080 or http://127.0.0.1:8080.","If a LAN hostname is required, put TLS in front via a reverse proxy and use its https origin."],"exampleFix":"// before\nCODEWHALE_API_BASE=http://staging.internal:8080\n// after\nCODEWHALE_API_BASE=https://staging.internal\n// or, local testing only:\nCODEWHALE_API_BASE=http://127.0.0.1:8080","handlingStrategy":"validation","validationCode":"fn is_https_or_loopback(url: &str) -> bool {\n    url::Url::parse(url.trim()).map_or(false, |u| {\n        let host = u.host_str().unwrap_or(\"\");\n        u.scheme() == \"https\"\n            || (u.scheme() == \"http\"\n                && (host == \"localhost\" || host.starts_with(\"127.\")))\n    })\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Default to https for every remote endpoint.","Restrict http to localhost/127.0.0.1 for local development only.","Terminate TLS at a reverse proxy for internal staging servers.","Never point the account API at a plain-http LAN host in production configs."],"tags":["cli","url","tls"],"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"}