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
validate_verification_url parses the service-provided device-flow URL and requires the raw string to be byte-identical to its parsed/serialized form (value == url.as_str()). Any difference — percent-encoding normalization, lowercase host rewrites, added trailing slashes, unicode punycode changes — means the URL contains components the validator did not inspect, so it is rejected as unsafe rather than canonicalized.
Source
Thrown at crates/cli/src/cloud.rs:795
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 0c42157ee5)
Solutions
- Make the server emit a canonical URL exactly as url::Url would serialize it (lowercase scheme/host, standard percent-encoding, explicit default port omitted).
- Reproduce locally: parse the URL with the url crate in a scratch test and compare with the raw string to see the differing byte(s).
- If you control a test server, build the URL via Url::join/parse instead of string concatenation.
Example fix
// test server before: hand-built, non-canonical string
let uri = format!("https://App.Codewhale.NET/cli/authorize?code={}", code);
// after: canonical, matches url::Url serialization
let uri = format!("https://app.codewhale.net/cli/authorize?code={}", code); Defensive patterns
Strategy: validation
Validate before calling
// Server-side: emit exactly what url::Url would serialize
let url: url::Url = format!("https://app.codewhale.net/cli/authorize?user_code={code}").parse()?;
let raw = url.as_str(); // canonical bytes; return this string to clients Type guard
fn is_canonical_url(value: &str) -> bool {
match url::Url::parse(value) { Ok(u) => u.as_str() == value, Err(_) => false }
} Prevention
- Construct URLs with a parser, not string concatenation.
- Round-trip-assert emitted URLs in server tests (parse -> serialize -> compare).
- Lowercase scheme and host; use standard percent-encoding.
When it happens
Trigger: Server returns a verification URL with non-canonical spelling: uppercase percent-encodings (%7E vs ~), mixed-case scheme/host that url::Url normalizes, unencoded characters the parser re-encodes, or trailing punctuation the parser moves.
Common situations: A backend or template change that stops emitting canonical URLs, middlewares that re-serialize URLs, or test servers hand-building the URL string with unnormalized characters.
Related errors
- Codewhale account API base URL must not contain credentials
- Codewhale account API base URL must use HTTPS (loopback HTTP
- Codewhale account login timed out; run `codewhale account lo
- Codewhale account API base URL must not contain a query or f
- Codewhale account API base URL must be an origin without a p
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/8e9c3a6ccafbbc33.
Report an issue: GitHub.