{"record":{"id":"5b6748c080e343d5","repo":"Hmbown/CodeWhale","slug":"codewhale-account-api-base-url-must-not-contain-cr","errorCode":null,"errorMessage":"Codewhale account API base URL must not contain credentials","messagePattern":"Codewhale account API base URL must not contain credentials","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/cli/src/cloud.rs","lineNumber":1018,"sourceCode":"        writeln!(out, \"Email: {}\", printable(&user.email))?;\n    }\n    if !user.plan.trim().is_empty() {\n        writeln!(out, \"Plan: {}\", printable(&user.plan))?;\n    }\n    writeln!(out, \"Profile: {}\", printable(profile))?;\n    writeln!(out, \"API: {api_base}\")?;\n    Ok(())\n}\n\nstruct ValidatedApiBase {\n    url: Url,\n    display: String,\n}\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();","sourceCodeStart":1000,"sourceCodeEnd":1036,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/cli/src/cloud.rs#L1000-L1036","documentation":"validate_api_base parses the user-supplied account API base URL and rejects URLs embedding userinfo (username or password). Credentials in the base URL would be sent with every request and can leak into logs and config, so they are prohibited outright.","triggerScenarios":"Setting the account API base URL (env/config/flag) to a URL containing user:pass@host userinfo, e.g. https://admin:secret@api.example.com.","commonSituations":"Pasting an internal service URL that includes basic-auth credentials; templating base URLs from connection strings that embed credentials.","solutions":["Strip the user:password@ portion and use the host-only origin: https://api.example.com.","Supply credentials another way if the endpoint needs them (headers, a proxy you control) — never in the base URL.","Re-run the command with the cleaned origin."],"exampleFix":"// before\nCODEWHALE_API_BASE=https://user:secret@api.example.com\n// after\nCODEWHALE_API_BASE=https://api.example.com","handlingStrategy":"validation","validationCode":"fn has_no_credentials(url: &str) -> bool {\n    url::Url::parse(url.trim())\n        .map(|u| u.username().is_empty() && u.password().is_none())\n        .unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never embed user:password in a base URL.","Strip userinfo before storing the base URL in config/env.","If an endpoint needs basic auth, use a proxy or header-based auth instead.","Review connection-string-derived values for embedded credentials."],"tags":["cli","url","security"],"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"}