{"record":{"id":"482d053862ce4a5b","repo":"Hmbown/CodeWhale","slug":"context-returned-an-unusable-verification-uri","errorCode":null,"errorMessage":"{context} returned an unusable verification URI","messagePattern":"(.+?) returned an unusable verification URI","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/config/src/device_code.rs","lineNumber":195,"sourceCode":"}\n\n/// Reject a device-code verification URI that must not be handed to a browser\n/// opener.\n///\n/// Ported from pi's `validateVerificationUri`\n/// (`packages/ai/src/auth/oauth/xai.ts`, MIT, Copyright (c) 2025 Mario\n/// Zechner): the URI comes straight off the wire and is passed to the platform\n/// \"open this\" call, so a malicious or compromised response could otherwise\n/// launch `file:`, a custom app scheme, or a helper with attacker-chosen\n/// arguments. pi requires `https:`; Codewhale additionally allows `http:` on a\n/// loopback host, which is what self-hosted issuers and the device-code tests\n/// use — matching the loopback allowance the account login already makes.\n///\n/// Embedded credentials are rejected in every case.\npub fn validate_browser_verification_uri(raw: &str, context: &str) -> Result<String> {\n    let trimmed = raw.trim();\n    let Ok(url) = url_scheme_and_host(trimmed) else {\n        bail!(\"{context} returned an unusable verification URI\");\n    };\n    let (scheme, host, has_credentials) = url;\n    if has_credentials {\n        bail!(\"{context} returned a verification URI with embedded credentials\");\n    }\n    let allowed = scheme == \"https\" || (scheme == \"http\" && is_loopback_host(&host));\n    if !allowed {\n        bail!(\"{context} returned an untrusted verification URI\");\n    }\n    Ok(trimmed.to_string())\n}\n\n/// Minimal scheme/host/credential split, so this module stays free of a URL\n/// dependency (`codewhale-config` deliberately has no `reqwest`/`url`).\npub(crate) fn url_scheme_and_host(raw: &str) -> Result<(String, String, bool), ()> {\n    let (scheme, rest) = raw.split_once(\"://\").ok_or(())?;\n    if scheme.is_empty()\n        || !scheme","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/config/src/device_code.rs#L177-L213","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet uri = \"/activate?code=ABC\";\nvalidate_browser_verification_uri(uri, \"login\")?;\n// after\nlet uri = \"https://auth.example.com/activate?code=ABC\";\nvalidate_browser_verification_uri(uri, \"login\")?;","handlingStrategy":"validation","validationCode":"fn looks_like_absolute_uri(raw: &str) -> bool {\n    let t = raw.trim();\n    if let Some(i) = t.find(\"://\") { i > 0 && t[..i].chars().all(|c| c.is_ascii_alphanumeric() || c == '+' || c == '-' || c == '.') }\n    else { false }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["oauth","device-code","url","validation"],"backgroundTag":"invalid-url-format","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}