decolua/9router · warning · Error

`xai discovery ${field} host ${host} is not on x.ai`

Error message

`xai discovery ${field} host ${host} is not on x.ai`

What it means

validateXaiOAuthEndpoint allowlists endpoint hosts: the hostname must be exactly 'x.ai' or a subdomain ending in '.x.ai'. Any other host (including lookalikes like xai.com, evil-x.ai, or an IP address) is rejected with this error naming the field and offending host. This blocks phishing/token-exfiltration via a compromised discovery document; discoverXaiEndpoints catches it and falls back to static x.ai endpoints.

Source

Thrown at src/lib/oauth/providerHelpers.js:13

const BASE64_BLOCK_SIZE = 4;

function validateXaiOAuthEndpoint(rawUrl, field) {
  const value = String(rawUrl || "").trim();
  if (!value) throw new Error(`xai discovery ${field} is empty`);
  let parsed;
  try { parsed = new URL(value); } catch (err) {
    throw new Error(`xai discovery ${field} is invalid: ${err.message}`);
  }
  if (parsed.protocol !== "https:") throw new Error(`xai discovery ${field} must use https: ${value}`);
  const host = parsed.hostname.toLowerCase().trim();
  if (host !== "x.ai" && !host.endsWith(".x.ai")) {
    throw new Error(`xai discovery ${field} host ${host} is not on x.ai`);
  }
  return value;
}

function decodeXaiIdTokenEmail(idToken) {
  if (!idToken || typeof idToken !== "string") return undefined;
  const parts = idToken.split(".");
  if (parts.length !== 3) return undefined;
  try {
    const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
    const padding = (BASE64_BLOCK_SIZE - (base64.length % BASE64_BLOCK_SIZE)) % BASE64_BLOCK_SIZE;
    const json = Buffer.from(base64 + "=".repeat(padding), "base64").toString("utf8");
    const payload = JSON.parse(json);
    return payload.email || payload.preferred_username || payload.sub || undefined;
  } catch {
    return undefined;
  }
}

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Use the official discovery URL so endpoints resolve to x.ai hosts; drop any custom discoveryUrl override.
  2. Accept the built-in static fallback endpoints in discoverXaiEndpoints.
  3. If you legitimately need a different host (e.g. corporate proxy), you must fork/extend the allowlist rather than passing the URL through.
  4. Investigate DNS/proxy tampering if the host changed unexpectedly.

Example fix

// before
validateXaiOAuthEndpoint('https://auth.xai-mirror.example.com/token', 'token_endpoint'); // throws: host is not on x.ai
// after
validateXaiOAuthEndpoint('https://auth.x.ai/token', 'token_endpoint');
Defensive patterns

Strategy: validation

Validate before calling

function isXaiHost(v) {
  try {
    const h = new URL(String(v).trim()).hostname.toLowerCase();
    return h === 'x.ai' || h.endsWith('.x.ai');
  } catch { return false; }
}
// if (!isXaiHost(data.token_endpoint)) use static x.ai endpoints

Type guard

function isXaiEndpoint(v) {
  if (typeof v !== 'string') return false;
  try {
    const u = new URL(v.trim());
    if (u.protocol !== 'https:') return false;
    const h = u.hostname.toLowerCase();
    return h === 'x.ai' || h.endsWith('.x.ai');
  } catch { return false; }
}

Try / catch

try {
  const url = validateXaiOAuthEndpoint(raw, 'authorization_endpoint');
  // use url
} catch (err) {
  if (/is not on x\.ai/.test(err.message)) {
    // suspected tampering/mirror — use official static endpoints and alert
    console.error('xAI discovery host rejected:', err.message);
    return XAI_CONFIG.authorizeUrl;
  }
  throw err;
}

Prevention

When it happens

Trigger: Discovery response advertises authorization_endpoint/token_endpoint on a foreign host, or a developer points discoveryUrl at a mirror whose endpoints live on another domain.

Common situations: Third-party xAI-compatible proxies, DNS hijacking or captive portals rewriting hostnames, DNS-based content filters, or security tests verifying the allowlist.

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/aceceeed408abe72. Report an issue: GitHub.