Hmbown/CodeWhale · error
returned an untrusted verification URI
Error message
{context} returned an untrusted verification URI What it means
Thrown when a device-code verification URI uses a scheme/host combination the library does not trust: anything other than https, except plain http which is allowed only for loopback hosts (localhost/127.0.0.1 style). This blocks open-redirect and downgrade attacks where a provider hands back an http:// or arbitrary-scheme URL that could be intercepted or handled by a malicious local handler.
Solutions
- Serve the verification endpoint over HTTPS and return that URL
- If developing locally, use http://localhost or http://127.0.0.1 which are allowed
- Put a TLS-terminating proxy in front of an HTTP-only auth server
- Replace custom app schemes with an https web URL
Example fix
// before let uri = "http://auth.internal.lan/activate"; validate_browser_verification_uri(uri, "login")?; // after let uri = "https://auth.internal.lan/activate"; validate_browser_verification_uri(uri, "login")?;
Defensive patterns
Strategy: validation
Validate before calling
fn uri_is_trusted(raw: &str) -> bool {
let t = raw.trim();
let Some((scheme, rest)) = t.split_once("://") else { return false };
let host = rest.split('/').next().unwrap_or("");
scheme == "https" || (scheme == "http" && (host == "localhost" || host.starts_with("127.0.0.1") || host.starts_with("[::1]")))
} Prevention
- Serve all verification endpoints over HTTPS
- Only use plain http for localhost development
- Replace custom app-scheme deep links with https web URLs
When it happens
Trigger: validate_browser_verification_uri receiving http://example.com/activate (non-loopback host), ftp:// or custom-scheme URIs, or a loopback-unqualified http URL on a remote host.
Common situations: Self-hosted auth servers behind plain HTTP on a LAN hostname; providers returning app-specific deep links (myapp://activate) instead of web URLs.
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
- returned a verification URI with embedded credentials
- OIDC discovery attempted to downgrade
- OIDC discovery returned credentials in
- OIDC discovery returned
- OIDC discovery returned unsupported
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/d3f93209f41a8247.
Report an issue: GitHub.
Appendix: source
Thrown at crates/config/src/device_code.rs:203
/// "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()
|| !scheme
.bytes()
.all(|b| b.is_ascii_alphanumeric() || b == b'+' || b == b'-' || b == b'.')
{
return Err(());
}
let authority = rest
.split(['/', '?', '#'])
.next()View on GitHub (pinned to 73e0f67d83)