Hmbown/CodeWhale · critical
OIDC discovery returned credentials in
Error message
OIDC discovery returned credentials in {field} What it means
A discovered OAuth endpoint URL embeds userinfo credentials (a username or password component, e.g. https://user:pass@host/path). Embedding credentials in an OAuth endpoint URL leaks them into logs and discovery documents and is never a legitimate configuration, so validation rejects it.
Solutions
- Remove the username/password from the endpoint URL in the IdP configuration.
- If the endpoint genuinely requires auth, use proper OAuth client authentication (client_id/secret in the token request), not URL userinfo.
- Rotate any credentials that were embedded in the URL, since they may have been logged.
- Re-check the discovery document after fixing and confirm the endpoints are plain host[:port]/path URLs.
Example fix
// before "token_endpoint": "https://admin:s3cret@auth.example.com/token" // after "token_endpoint": "https://auth.example.com/token"
Defensive patterns
Strategy: validation
Validate before calling
if let Ok(url) = reqwest::Url::parse(endpoint) {
if !url.username().is_empty() || url.password().is_some() {
eprintln!("endpoint URL contains embedded credentials — remove and rotate them");
}
} Type guard
fn no_url_credentials(s: &str) -> bool {
reqwest::Url::parse(s).map(|u| u.username().is_empty() && u.password().is_none()).unwrap_or(false)
} Prevention
- Never paste basic-auth URLs (user:pass@host) into IdP endpoint settings.
- Use OAuth client authentication (client_id/client_secret) instead of URL userinfo.
- Rotate any credential that has ever been embedded in a URL.
- Lint config files for '@' userinfo in endpoint URLs.
When it happens
Trigger: validate_discovered_oauth_endpoint finding parsed.username() non-empty or parsed.password() Some on the {field} endpoint URL.
Common situations: Someone pasting a URL with basic-auth credentials (https://user:pass@host) into an IdP endpoint setting; a templated endpoint URL left with credential placeholders filled in; misconfigured reverse proxy docs examples copied verbatim.
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 returned
- OIDC discovery returned unsupported
- Codewhale-owned xAI OAuth storage must have an owner-only…
- returned an untrusted verification URI
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/edfff7eee06bdc5f.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/oauth.rs:697
field: &str,
issuer: &str,
) -> Result<String> {
let endpoint = endpoint
.as_deref()
.map(str::trim)
.filter(|endpoint| !endpoint.is_empty())
.with_context(|| format!("OIDC discovery missing {field}"))?;
let parsed = reqwest::Url::parse(endpoint)
.with_context(|| format!("OIDC discovery returned an invalid {field}"))?;
if !matches!(parsed.scheme(), "http" | "https") {
bail!("OIDC discovery returned unsupported {field} scheme");
}
let issuer = oauth_endpoint_url(issuer).context("OIDC issuer is not a trusted URL")?;
if issuer.scheme() == "https" && parsed.scheme() != "https" {
bail!("OIDC discovery attempted to downgrade {field} from HTTPS");
}
if !parsed.username().is_empty() || parsed.password().is_some() {
bail!("OIDC discovery returned credentials in {field}");
}
if parsed.origin() != issuer.origin() {
bail!("OIDC discovery returned {field} on a different origin than the issuer");
}
let _ = oauth_endpoint_url(parsed.as_str())?;
Ok(endpoint.to_string())
}
/// Documented-path endpoints for a provider row, no discovery.
fn fallback_oauth_endpoints(params: &OAuthProviderParams, issuer: &str) -> OAuthEndpoints {
OAuthEndpoints {
device_authorization_endpoint: params
.device_code_path
.map(|path| format!("{}/{}", issuer.trim_end_matches('/'), path)),
token_endpoint: format!("{}/{}", issuer.trim_end_matches('/'), params.token_path),
}
}
View on GitHub (pinned to 73e0f67d83)