Hmbown/CodeWhale · error
returned an unusable verification URI
Error message
{context} returned an unusable verification URI What it means
Thrown by validate_browser_verification_uri when the URI a provider returned during device-code (OAuth-style) login cannot be parsed into a scheme + host pair. The function deliberately avoids a full URL dependency and only accepts URIs it can split into scheme and host, so anything malformed, relative, or scheme-less is unusable. This prevents handing a browser a garbage or attacker-controlled link.
Solutions
- Fix the provider/server to return an absolute verification_uri including scheme and host
- Check the provider base URL configuration so redirects resolve to absolute URIs
- If you control the input, prepend the scheme/host before validation (only for trusted sources)
- Verify the device-authorization JSON actually populates verification_uri
Example fix
// before let uri = "/activate?code=ABC"; validate_browser_verification_uri(uri, "login")?; // after let uri = "https://auth.example.com/activate?code=ABC"; validate_browser_verification_uri(uri, "login")?;
Defensive patterns
Strategy: validation
Validate before calling
fn looks_like_absolute_uri(raw: &str) -> bool {
let t = raw.trim();
if let Some(i) = t.find("://") { i > 0 && t[..i].chars().all(|c| c.is_ascii_alphanumeric() || c == '+' || c == '-' || c == '.') }
else { false }
} Prevention
- Require providers to return absolute verification URIs per the device-authorization spec
- Validate provider responses at integration time with a contract test
- Never construct verification URLs from relative redirects without a base URL
When it happens
Trigger: Calling validate_browser_verification_uri with a verification URI that is empty, relative (e.g. "/activate"), missing a scheme ("example.com/activate"), or otherwise unparseable by url_scheme_and_host; a misbehaving or non-standard OAuth provider returning such a URI in its device-authorization response.
Common situations: Integrating a self-hosted or nonconformant identity provider whose device_authorization_endpoint returns a partial URL; typos in configured provider base URLs leading to relative redirects.
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
- MCP server URL ' ' must include a host
- --base-url must use http or https
- browser URL cannot be empty
- Codewhale account API base URL must be an origin without a…
- Codewhale account API base URL must not contain a query or…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/482d053862ce4a5b.
Report an issue: GitHub.
Appendix: source
Thrown at crates/config/src/device_code.rs:195
}
/// Reject a device-code verification URI that must not be handed to a browser
/// opener.
///
/// Ported from pi's `validateVerificationUri`
/// (`packages/ai/src/auth/oauth/xai.ts`, MIT, Copyright (c) 2025 Mario
/// Zechner): the URI comes straight off the wire and is passed to the platform
/// "open this" call, so a malicious or compromised response could otherwise
/// launch `file:`, a custom app scheme, or a helper with attacker-chosen
/// arguments. pi requires `https:`; Codewhale additionally allows `http:` on a
/// loopback host, which is what self-hosted issuers and the device-code tests
/// use — matching the loopback allowance the account login already makes.
///
/// Embedded credentials are rejected in every case.
pub fn validate_browser_verification_uri(raw: &str, context: &str) -> Result<String> {
let trimmed = raw.trim();
let Ok(url) = url_scheme_and_host(trimmed) else {
bail!("{context} returned an unusable verification URI");
};
let (scheme, host, has_credentials) = url;
if has_credentials {
bail!("{context} returned a verification URI with embedded credentials");
}
let allowed = scheme == "https" || (scheme == "http" && is_loopback_host(&host));
if !allowed {
bail!("{context} returned an untrusted verification URI");
}
Ok(trimmed.to_string())
}
/// Minimal scheme/host/credential split, so this module stays free of a URL
/// dependency (`codewhale-config` deliberately has no `reqwest`/`url`).
pub(crate) fn url_scheme_and_host(raw: &str) -> Result<(String, String, bool), ()> {
let (scheme, rest) = raw.split_once("://").ok_or(())?;
if scheme.is_empty()
|| !schemeView on GitHub (pinned to 73e0f67d83)