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

  1. Make the server emit a canonical URL exactly as url::Url would serialize it (lowercase scheme/host, standard percent-encoding, explicit default port omitted).
  2. 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).
  3. 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

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


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/8e9c3a6ccafbbc33. Report an issue: GitHub.