{"record":{"id":"954592712b0afa4c","repo":"Hmbown/CodeWhale","slug":"the-codewhale-service-returned-an-untrusted-verifi","errorCode":null,"errorMessage":"The Codewhale service returned an untrusted verification origin","messagePattern":"The Codewhale service returned an untrusted verification origin","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/cli/src/cloud.rs","lineNumber":817,"sourceCode":"    })?;\n    if !url.username().is_empty() || url.password().is_some() || url.fragment().is_some() {\n        bail!(\"The Codewhale service returned an unsafe verification URL\");\n    }\n    if url.path() != \"/cli/authorize\" {\n        bail!(\"The Codewhale service returned an unsafe verification URL\");\n    }\n\n    let api = Url::parse(api_base).context(\"invalid Codewhale account API base URL\")?;\n    let canonical_api = api.scheme() == \"https\"\n        && api.host_str() == Some(\"api.codewhale.net\")\n        && api.port_or_known_default() == Some(443);\n    let loopback_api = api.host_str().is_some_and(is_loopback_host);\n    if canonical_api {\n        if url.scheme() != \"https\"\n            || !host.eq_ignore_ascii_case(\"app.codewhale.net\")\n            || url.port_or_known_default() != Some(443)\n        {\n            bail!(\"The Codewhale service returned an untrusted verification origin\");\n        }\n    } else if loopback_api {\n        if !matches!(url.scheme(), \"http\" | \"https\") || !is_loopback_host(host) {\n            bail!(\"The Codewhale service returned an untrusted verification origin\");\n        }\n    } else {\n        bail!(\n            \"Browser login is only enabled for the canonical Codewhale account API or a loopback test API\"\n        );\n    }\n\n    let query = url.query_pairs().collect::<Vec<_>>();\n    if complete {\n        if query.len() != 1 || query[0].0 != \"user_code\" || query[0].1 != user_code {\n            bail!(\"The Codewhale service returned an unsafe verification URL\");\n        }\n    } else if !query.is_empty() {\n        bail!(\"The Codewhale service returned an unsafe verification URL\");","sourceCodeStart":799,"sourceCodeEnd":835,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/cli/src/cloud.rs#L799-L835","documentation":"When the account API base is the canonical https://api.codewhale.net:443, the CLI requires the login verification URL to be exactly https on app.codewhale.net with the default port 443. This bail fires when any of those three origin properties drift, because a different origin could be an attacker-controlled page receiving the user code.","triggerScenarios":"--api-base is https://api.codewhale.net (or https://api.codewhale.net:443) and the returned verification URL is http://, a host other than app.codewhale.net (even a subdomain like eu.app.codewhale.net), or carries a non-default port such as :8443.","commonSituations":"A local mock or proxy that answers on api.codewhale.net while returning localhost URLs; a service-side config pointing the frontend at a vanity domain or staging host; a port-forward that adds an explicit port to the URL.","solutions":["Confirm --api-base is the real canonical API and not a local override masquerading as it","Fix the service/config so verification URLs are exactly https://app.codewhale.net/cli/authorize[?user_code=...]","Drop any explicit :443 or non-default port from the returned URL (the check requires port_or_known_default() == 443)","Update CLI and service to matching releases if the frontend host recently changed"],"exampleFix":"// before\nverification_url: \"https://app.codewhale.net:8443/cli/authorize\"\n// after\nverification_url: \"https://app.codewhale.net/cli/authorize\"","handlingStrategy":"validation","validationCode":"fn is_trusted_origin(raw: &str, api_base: &str) -> bool {\n    let (Ok(url), Ok(api)) = (url::Url::parse(raw), url::Url::parse(api_base)) else { return false };\n    let canonical = api.scheme() == \"https\"\n        && api.host_str() == Some(\"api.codewhale.net\")\n        && api.port_or_known_default() == Some(443);\n    canonical && url.scheme() == \"https\"\n        && url.host_str().is_some_and(|h| h.eq_ignore_ascii_case(\"app.codewhale.net\"))\n        && url.port_or_known_default() == Some(443)\n}","typeGuard":null,"tryCatchPattern":"if !is_trusted_origin(&verification_url, api_base) {\n    anyhow::bail!(\"refusing to open untrusted origin: {verification_url}\");\n}","preventionTips":["Compare origins exactly (scheme+host+default port), never substring-match hosts","Keep the canonical frontend host in one config constant shared by client and service","Add a test that a malicious look-alike host (e.g. app.codewhale.net.evil.com) is rejected"],"tags":["cloud","oauth","device-flow","origin-pinning","security"],"backgroundTag":"origin-allowlist-mismatch","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}