{"record":{"id":"eebd61c1143abcb5","repo":"Hmbown/CodeWhale","slug":"oidc-discovery-returned-field-on-a-different-origin-than-the","errorCode":null,"errorMessage":"OIDC discovery returned {field} on a different origin than the issuer","messagePattern":"OIDC discovery returned (.+?) on a different origin than the issuer","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/tui/src/oauth.rs","lineNumber":700,"sourceCode":"    let endpoint = endpoint\n        .as_deref()\n        .map(str::trim)\n        .filter(|endpoint| !endpoint.is_empty())\n        .with_context(|| format!(\"OIDC discovery missing {field}\"))?;\n    let parsed = reqwest::Url::parse(endpoint)\n        .with_context(|| format!(\"OIDC discovery returned an invalid {field}\"))?;\n    if !matches!(parsed.scheme(), \"http\" | \"https\") {\n        bail!(\"OIDC discovery returned unsupported {field} scheme\");\n    }\n    let issuer = oauth_endpoint_url(issuer).context(\"OIDC issuer is not a trusted URL\")?;\n    if issuer.scheme() == \"https\" && parsed.scheme() != \"https\" {\n        bail!(\"OIDC discovery attempted to downgrade {field} from HTTPS\");\n    }\n    if !parsed.username().is_empty() || parsed.password().is_some() {\n        bail!(\"OIDC discovery returned credentials in {field}\");\n    }\n    if parsed.origin() != issuer.origin() {\n        bail!(\"OIDC discovery returned {field} on a different origin than the issuer\");\n    }\n    let _ = oauth_endpoint_url(parsed.as_str())?;\n    Ok(endpoint.to_string())\n}\n\n/// Documented-path endpoints for a provider row, no discovery.\nfn fallback_oauth_endpoints(params: &OAuthProviderParams, issuer: &str) -> OAuthEndpoints {\n    OAuthEndpoints {\n        device_authorization_endpoint: params\n            .device_code_path\n            .map(|path| format!(\"{}/{}\", issuer.trim_end_matches('/'), path)),\n        token_endpoint: format!(\"{}/{}\", issuer.trim_end_matches('/'), params.token_path),\n    }\n}\n\n/// POST a device-authorization request. Pure transport over an explicit\n/// endpoint: discovery (or its absence) is the caller's decision.\nfn request_device_grant(","sourceCodeStart":682,"sourceCodeEnd":718,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/oauth.rs#L682-L718","documentation":"A discovered OAuth endpoint is on a different origin (scheme + host + port) than the issuer that advertised it. OAuth security guidance requires endpoints to be same-origin with the issuer to prevent an attacker-controlled document from steering tokens to another host, so cross-origin endpoints are rejected.","triggerScenarios":"validate_discovered_oauth_endpoint comparing parsed.origin() of the {field} endpoint against issuer.origin() and finding them different (different host, scheme, or port).","commonSituations":"IdP behind a proxy advertising internal hostnames (e.g. http://internal-svc:8080) while the issuer is public; port mismatches (default 443 vs explicit :8443); DNS aliases or load-balancer hostnames differing from the issuer host.","solutions":["Configure the IdP so all advertised endpoints share the issuer's public origin.","Check port and scheme match exactly, including default-port handling.","Fix reverse-proxy headers (Host, X-Forwarded-Proto) so generated endpoint URLs use the public origin.","If the provider legitimately uses separate origins, use documented-path endpoint configuration instead of discovery."],"exampleFix":"// before\nissuer=https://auth.example.com, token_endpoint=https://internal.svc:8080/token // cross-origin\n// after\ntoken_endpoint=https://auth.example.com/token","handlingStrategy":"validation","validationCode":"let issuer = reqwest::Url::parse(issuer_url)?;\nlet endpoint = reqwest::Url::parse(doc[\"token_endpoint\"].as_str()?)?;\nif endpoint.origin() != issuer.origin() {\n    eprintln!(\"endpoint origin {} differs from issuer origin {}\", endpoint.origin(), issuer.origin());\n}","typeGuard":"fn same_origin(issuer: &Url, endpoint: &Url) -> bool {\n    endpoint.origin() == issuer.origin()\n}","tryCatchPattern":null,"preventionTips":["Keep all OAuth endpoints on the issuer's public origin; avoid separate internal hostnames/ports in advertised URLs.","Fix proxy Host/X-Forwarded-* headers so the IdP generates public-origin URLs.","Watch default-port handling: 443 vs an explicit :8443 are different origins.","If a provider legitimately uses other origins, use explicit documented-path endpoints rather than discovery."],"tags":["oauth","security","discovery","origin","url"],"backgroundTag":"cross-origin-endpoint","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T21:17:16.096Z"}