zeroclaw-labs/zeroclaw · error · anyhow::Error
xAI OAuth discovery returned untrusted {label}: {endpoint}
Error message
xAI OAuth discovery returned untrusted {label}: {endpoint} What it means
After the HTTPS check, require_trusted_endpoint pins the host of every xAI OAuth endpoint to exactly x.ai or a subdomain of x.ai. It fires when the endpoint host is anything else, refusing to exchange codes or tokens against a foreign authorization server even over valid HTTPS. This blocks DNS-hijack and phishing discovery responses.
Source
Thrown at crates/zeroclaw-providers/src/auth/xai_oauth.rs:564
.ok()
.or_else(|| {
base64::engine::general_purpose::URL_SAFE
.decode(payload)
.ok()
})?;
serde_json::from_slice(&bytes).ok()
}
fn require_trusted_endpoint(endpoint: &str, label: &str) -> Result<String> {
let url = reqwest::Url::parse(endpoint).with_context(|| format!("Invalid xAI {label}"))?;
if url.scheme() != "https" {
anyhow::bail!("xAI OAuth discovery returned non-HTTPS {label}");
}
let host = url.host_str().unwrap_or_default();
if host == "x.ai" || host.ends_with(".x.ai") {
return Ok(endpoint.to_string());
}
anyhow::bail!("xAI OAuth discovery returned untrusted {label}: {endpoint}")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn authorize_url_contains_xai_oauth_params() {
let pkce = PkceState {
code_verifier: "verifier".into(),
code_challenge: "challenge".into(),
state: "state".into(),
};
let url = build_authorize_url("https://auth.x.ai/oauth2/authorize", &pkce);
assert!(url.contains("client_id=b1a00492-073a-47ea-816f-4c329264a828"));
assert!(url.contains(
"scope=openid%20profile%20email%20offline_access%20grok-cli%3Aaccess%20api%3Aaccess"
));View on GitHub (pinned to 88bb9c8533)
Solutions
- Confirm nothing repoints xAI endpoints at another domain; reset to the official x.ai endpoints
- Check DNS resolution of x.ai on this machine (hosts file, VPN split-DNS, corporate proxy)
- Retry the discovery fetch to rule out a transient poisoned response
- If you run a private xAI-compatible IdP, this client is intentionally unusable with it - use the generic OAuth flow instead
Defensive patterns
Strategy: try-catch
Validate before calling
// Optional pre-flight: pin the host allowlist in your own wiring too
let host = reqwest::Url::parse(endpoint)?
.host_str()
.unwrap_or_default()
.to_string();
if !(host == "x.ai" || host.ends_with(".x.ai")) {
anyhow::bail!("refusing xAI endpoint on foreign host: {host}");
} Try / catch
match exchange_code_for_tokens(&ctx, &code, &verifier).await {
Ok(tokens) => { /* store TokenSet */ }
Err(e) if e.to_string().contains("untrusted") => {
// do not retry; investigate DNS/proxy and surface to the user
}
Err(e) => return Err(e),
} Prevention
- Do not point the xAI client at xAI-compatible IdPs on other domains - the allowlist is intentional
- Check hosts-file and VPN split-DNS entries when xAI auth suddenly fails with this error
- Cache discovery responses only from verifiable https x.ai fetches
- Treat this as a phishing/hijack signal and audit the environment before retrying
When it happens
Trigger: The discovery document advertises an endpoint whose host is not x.ai / *.x.ai, and any discovery-consuming flow (token exchange, device-code start or poll) then rejects it before sending credentials.
Common situations: DNS hijack or hosts-file override for x.ai; corporate proxy rewriting discovery JSON; a stale or custom discovery cache pointing at old or test hosts; attempting to point the xAI client at a private xAI-compatible IdP on another domain.
Related errors
- xAI OAuth discovery returned non-HTTPS {label}
- xAI auth profile is not OAuth-based: {profile_id}
- xAI auth profile is missing token set: {profile_id}
- xAI token refresh is in backoff for {remaining}s due to prev
- xAI OAuth discovery failed ({status}): {body}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/c02cf187ee997e1b.
Report an issue: GitHub.