{"record":{"id":"06855256eea4df4e","repo":"Hmbown/CodeWhale","slug":"browser-login-is-only-enabled-for-the-canonical-co","errorCode":null,"errorMessage":"Browser login is only enabled for the canonical Codewhale account API or a loopback test API","messagePattern":"Browser login is only enabled for the canonical Codewhale account API or a loopback test API","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/cli/src/cloud.rs","lineNumber":1078,"sourceCode":"\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\");\n    }\n    Ok(url.to_string())\n}\n\nfn is_loopback_host(host: &str) -> bool {\n    let host = host\n        .strip_prefix('[')","sourceCodeStart":1060,"sourceCodeEnd":1096,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/cli/src/cloud.rs#L1060-L1096","documentation":"Raised by `validate_verification_url` in crates/cli/src/cloud.rs:1078 when the configured Codewhale account API base is neither the canonical `https://api.codewhale.net:443` nor a loopback host. Browser-based device login is hard-disabled for any other API endpoint, so the login flow refuses before validating the verification URL's origin.","triggerScenarios":"Running `codewhale login` (device/browser flow) with CODEWHALE_API_BASE (or config api_base) set to a remote non-canonical host, e.g. `https://api.staging.example.com` or an internal corporate gateway — anything that is not api.codewhale.net on 443 and not localhost/127.x/::1.","commonSituations":"Developers pointing the CLI at a staging or self-hosted Codewhale API and attempting interactive browser login; a proxy URL typo; environment variable left over from an earlier test session.","solutions":["Unset the custom API base (remove CODEWHALE_API_BASE / reset config) so login uses the canonical https://api.codewhale.net","If testing locally, change the API base to a loopback address such as http://127.0.0.1:8080, which the login flow explicitly supports","For a remote staging environment, use a non-browser login path (e.g. paste an API key) instead of the device login flow","Verify the api_base string parses to scheme https, host api.codewhale.net, default port 443"],"exampleFix":"// before\nexport CODEWHALE_API_BASE=https://api.staging.example.com\ncodewhale login\n// after\nunset CODEWHALE_API_BASE\ncodewhale login","handlingStrategy":"validation","validationCode":"fn api_base_allows_browser_login(api_base: &str) -> bool {\n    url::Url::parse(api_base).map(|api| {\n        let canonical = api.scheme() == \"https\"\n            && api.host_str() == Some(\"api.codewhale.net\")\n            && api.port_or_known_default() == Some(443);\n        let loopback = api.host_str()\n            .map(|h| h.eq_ignore_ascii_case(\"localhost\")\n                || h.parse::<std::net::IpAddr>().is_ok_and(|a| a.is_loopback()))\n            .unwrap_or(false);\n        canonical || loopback\n    }).unwrap_or(false)\n}","typeGuard":"fn is_canonical_or_loopback_api(api_base: &str) -> bool {\n    api_base_allows_browser_login(api_base)\n}","tryCatchPattern":"match run_device_login() {\n    Err(e) if e.to_string().contains(\"only enabled for the canonical\") => {\n        eprintln!(\"Browser login disabled for this API base; use an API key or switch to api.codewhale.net / localhost.\");\n    }\n    other => return other,\n}","preventionTips":["Check CODEWHALE_API_BASE and config before interactive login","Use API-key auth for staging/self-hosted endpoints instead of browser login","Keep browser login limited to canonical or loopback API bases by policy","Clear leftover env overrides between environments"],"tags":["device-login","configuration","api-base","unsupported"],"backgroundTag":"unsupported-operation","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"}