{"record":{"id":"b23ddb394cb0d084","repo":"decolua/9router","slug":"xai-discovery-field-is-empty","errorCode":null,"errorMessage":"xai discovery ${field} is empty","messagePattern":"xai discovery (.+?) is empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/lib/oauth/providerHelpers.js","lineNumber":5,"sourceCode":"const BASE64_BLOCK_SIZE = 4;\n\nfunction validateXaiOAuthEndpoint(rawUrl, field) {\n  const value = String(rawUrl || \"\").trim();\n  if (!value) throw new Error(`xai discovery ${field} is empty`);\n  let parsed;\n  try { parsed = new URL(value); } catch (err) {\n    throw new Error(`xai discovery ${field} is invalid: ${err.message}`);\n  }\n  if (parsed.protocol !== \"https:\") throw new Error(`xai discovery ${field} must use https: ${value}`);\n  const host = parsed.hostname.toLowerCase().trim();\n  if (host !== \"x.ai\" && !host.endsWith(\".x.ai\")) {\n    throw new Error(`xai discovery ${field} host ${host} is not on x.ai`);\n  }\n  return value;\n}\n\nfunction decodeXaiIdTokenEmail(idToken) {\n  if (!idToken || typeof idToken !== \"string\") return undefined;\n  const parts = idToken.split(\".\");\n  if (parts.length !== 3) return undefined;\n  try {\n    const base64 = parts[1].replace(/-/g, \"+\").replace(/_/g, \"/\");","sourceCodeStart":1,"sourceCodeEnd":23,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/providerHelpers.js#L1-L23","documentation":"validateXaiOAuthEndpoint (src/lib/oauth/providerHelpers.js) validates endpoints returned by the xAI OIDC discovery document before the OAuth flow uses them. It rejects empty values, non-URLs, non-https URLs, and hosts outside x.ai. This specific error fires when the discovery document's field (authorization_endpoint or token_endpoint) is missing, empty, or whitespace after trimming. In practice discoverXaiEndpoints wraps the call in try/catch and falls back to static xAI endpoints, so this surfaces only if the catch is bypassed or the helper is called directly.","triggerScenarios":"discoverXaiEndpoints fetches the xAI discovery URL successfully (res.ok) but the JSON has no authorization_endpoint/token_endpoint, or the field is an empty string; also any direct call to validateXaiOAuthEndpoint('', 'token_endpoint').","commonSituations":"xAI discovery endpoint temporarily serving an error-shaped 2xx JSON body, a proxy/MITM returning empty JSON {}, cached/intercepted responses, or tests calling the validator with undefined/empty input.","solutions":["Rely on the built-in fallback: discoverXaiEndpoints catches this and uses XAI_CONFIG.authorizeUrl/tokenUrl; verify you are not swallowing the fallback path.","Re-run discovery later — a transiently bad discovery response is usually upstream.","If calling validateXaiOAuthEndpoint directly, pass the discovered field value or the static config value.","Check network/proxy integrity: a 200 with {} suggests a captive portal or broken proxy."],"exampleFix":"// before: data.authorization_endpoint missing -> throws\nconst { authorizeUrl } = { authorizeUrl: validateXaiOAuthEndpoint(data.authorization_endpoint, 'authorization_endpoint') };\n// after: guard before validating\nconst authEp = data.authorization_endpoint || XAI_CONFIG.authorizeUrl;\nconst authorizeUrl = validateXaiOAuthEndpoint(authEp, 'authorization_endpoint');","handlingStrategy":"fallback","validationCode":"const raw = data.authorization_endpoint;\nif (typeof raw !== 'string' || !raw.trim()) {\n  endpoints = { authorizeUrl: XAI_CONFIG.authorizeUrl, tokenUrl: XAI_CONFIG.tokenUrl }; // static fallback\n}","typeGuard":"function isNonEmptyUrlString(v) {\n  return typeof v === 'string' && v.trim().length > 0;\n}","tryCatchPattern":"try {\n  const authUrl = validateXaiOAuthEndpoint(data.authorization_endpoint, 'authorization_endpoint');\n  // use authUrl\n} catch (err) {\n  if (/xai discovery .* is empty/.test(err.message)) {\n    return { authorizeUrl: XAI_CONFIG.authorizeUrl, tokenUrl: XAI_CONFIG.tokenUrl };\n  }\n  throw err;\n}","preventionTips":["Never bypass discoverXaiEndpoints' built-in try/catch fallback by calling the validator directly on raw discovery values.","Log the raw discovery payload when validation fails to diagnose upstream changes.","Cache discovery results but add a TTL so a bad response isn't stuck in cachedXaiDiscovery forever.","Pin XAI_CONFIG static endpoints as the always-available safety net."],"tags":["oauth","xai","discovery","url-validation"],"backgroundTag":"oauth-discovery-invalid-endpoint","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}