Hmbown/CodeWhale · error · anyhow::Error
The Codewhale service returned an unsafe verification URL
Error message
The Codewhale service returned an unsafe verification URL
What it means
Thrown by `validate_verification_url` in crates/cli/src/cloud.rs:1049 when the device-login verification URL string returned by the Codewhale service does not round-trip through `Url::parse` exactly (`value != url.as_str()`). Any non-canonical serialization — extra characters, backslashes, embedded whitespace, percent-encoded tricks, Unicode confusables — is rejected as a potential URL-spoofing / open-redirect attempt before the URL is ever opened in a browser.
Solutions
- Verify the service returns an already-canonical verification URL: paste it into a `Url::parse(...)` round-trip test and compare `as_str()` to the original
- Remove whitespace, backslashes, or redundant encoding from the URL the server emits
- If a custom API base is in use, point login back at the canonical api.codewhale.net endpoint or fix the staging server's URL emission
- Capture the raw response body from POST /device/authorize and inspect the verification_url field for hidden characters
Example fix
// server response
{ "verification_url": "https://app.codewhale.net/cli/authorize/" }
// after
{ "verification_url": "https://app.codewhale.net/cli/authorize" } Defensive patterns
Strategy: validation
Validate before calling
fn is_canonical_url(value: &str) -> bool {
url::Url::parse(value).map(|u| u.as_str() == value).unwrap_or(false)
} Type guard
fn looks_canonical(value: &str) -> bool {
!value.chars().any(|c| c.is_whitespace() || c == '\\')
&& url::Url::parse(value).map(|u| u.as_str() == value).unwrap_or(false)
} Try / catch
match validate_verification_url(&value, api_base, &code, complete) {
Ok(url) => open_browser(&url),
Err(e) if e.to_string().contains("unsafe verification URL") => {
eprintln!("Refusing non-canonical verification URL from server: {value:?}");
}
Err(e) => return Err(e),
} Prevention
- Always round-trip server URLs through Url::parse and compare as_str() before display
- Reject URLs containing whitespace, backslashes, or control characters at the source
- Test staging servers with the same URL validator the CLI uses
- Keep verification URLs generated by a single canonical serializer on the server
When it happens
Trigger: A compromised or misbehaving device-authorization endpoint returns a verification_url whose raw string differs from its canonical `Url::as_str()` serialization, e.g. `https://app.codewhale.net/cli/authorize ` (trailing space), `https://app.codewhale.net\/cli/authorize`, uppercase percent-encoding, or any string the `url` crate normalizes.
Common situations: A staging/mock API (CODEWHALE_API_BASE override) returning hand-written or template-generated verification URLs instead of canonical ones; a proxy rewriting the URL; a man-in-the-middle injecting a look-alike URL during `codewhale login`.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- bundle URLs may not include credentials
- Codewhale-owned OAuth path escaped the credentials directory
- Codewhale-owned OAuth path has an invalid basename
- Codewhale-owned xAI OAuth DACL is not current-user-only
- Codewhale-owned xAI OAuth DACL must grant only one user
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/8e9c3a6ccafbbc33.
Report an issue: GitHub.
Appendix: source
Thrown at crates/cli/src/cloud.rs:1049
bail!(
"Codewhale account API base URL must use HTTPS (loopback HTTP is allowed for testing)"
);
}
url.set_path("/");
let display = url.as_str().trim_end_matches('/').to_string();
Ok(ValidatedApiBase { url, display })
}
fn validate_verification_url(
value: &str,
api_base: &str,
user_code: &str,
complete: bool,
) -> Result<String> {
let url =
Url::parse(value).context("The Codewhale service returned an invalid verification URL")?;
if value != url.as_str() {
bail!("The Codewhale service returned an unsafe verification URL");
}
let host = url.host_str().ok_or_else(|| {
anyhow!("The Codewhale service returned a verification URL without a host")
})?;
if !url.username().is_empty() || url.password().is_some() || url.fragment().is_some() {
bail!("The Codewhale service returned an unsafe verification URL");
}
if url.path() != "/cli/authorize" {
bail!("The Codewhale service returned an unsafe verification URL");
}
let api = Url::parse(api_base).context("invalid Codewhale account API base URL")?;
let canonical_api = api.scheme() == "https"
&& api.host_str() == Some("api.codewhale.net")
&& api.port_or_known_default() == Some(443);
let loopback_api = api.host_str().is_some_and(is_loopback_host);
if canonical_api {
if url.scheme() != "https"View on GitHub (pinned to 73e0f67d83)