{"record":{"id":"fe7a7cd1e073c461","repo":"Wei-Shaw/sub2api","slug":"xai-oauth-redirected-to-untrusted-host","errorCode":null,"errorMessage":"xAI OAuth redirected to untrusted host","messagePattern":"xAI OAuth redirected to untrusted host","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/internal/pkg/xai/sso_device.go","lineNumber":298,"sourceCode":"\t\tif len(data) > ssoMaxAuthBody {\n\t\t\treturn response.StatusCode, currentURL, nil, errors.New(\"xAI OAuth response exceeds 2 MiB\")\n\t\t}\n\t\tif response.StatusCode < 300 || response.StatusCode > 399 {\n\t\t\treturn response.StatusCode, currentURL, data, nil\n\t\t}\n\n\t\tlocation := strings.TrimSpace(response.Header.Get(\"Location\"))\n\t\tif location == \"\" {\n\t\t\treturn response.StatusCode, currentURL, data, errors.New(\"xAI OAuth redirect missing Location\")\n\t\t}\n\t\tbase, _ := url.Parse(currentURL)\n\t\tnext, err := url.Parse(location)\n\t\tif err != nil {\n\t\t\treturn response.StatusCode, currentURL, data, err\n\t\t}\n\t\tcurrentURL = base.ResolveReference(next).String()\n\t\tif !safeXAIAuthURL(currentURL) {\n\t\t\treturn response.StatusCode, currentURL, data, errors.New(\"xAI OAuth redirected to untrusted host\")\n\t\t}\n\t\tif response.StatusCode == http.StatusSeeOther || ((response.StatusCode == http.StatusMovedPermanently || response.StatusCode == http.StatusFound) && currentMethod != http.MethodGet && currentMethod != http.MethodHead) {\n\t\t\tcurrentMethod = http.MethodGet\n\t\t\tcurrentForm = nil\n\t\t}\n\t}\n\treturn 0, currentURL, nil, errors.New(\"xAI OAuth redirected too many times\")\n}\n\nfunc seedSSOCookies(jar http.CookieJar, token string) {\n\tif jar == nil {\n\t\treturn\n\t}\n\tfor _, rawURL := range []string{SSOAccountsURL, OAuthIssuer + \"/\"} {\n\t\ttarget, err := url.Parse(rawURL)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/Wei-Shaw/sub2api/blob/073e92d17178a1ccdb0a27017f572f10c9c7ab62/backend/internal/pkg/xai/sso_device.go#L280-L316","documentation":"After resolving each redirect target against the current URL, the flow re-validates it with safeXAIAuthURL. If the resolved absolute URL is not on a trusted xAI auth host, the request aborts with this error — an open-redirect guard so session cookies are never sent to a third party.","triggerScenarios":"A redirect Location pointing off-trusted-host: absolute URLs to other domains, or a relative resolution that lands on a non-auth xAI subdomain not in the trusted set. Also triggered by PTR-style tricks where ResolveReference yields an unexpected host.","commonSituations":"xAI adds a new auth host (e.g. a new SSO domain) not yet in safeXAIAuthURL's allowlist; third-party SSO (Okta etc.) inserted in the chain; malicious/compromised endpoint attempting cookie exfiltration (the guard doing its job).","solutions":["Inspect the redirect chain (log currentURL each hop) to find which host fell outside the trust set.","If xAI legitimately added a host, extend the allowlist inside safeXAIAuthURL.","Never disable the check to 'fix' the flow — an untrusted redirect with your session cookies is a credential leak.","Verify you are not being proxied through something rewriting hosts (HTTP Host header rewrites)."],"exampleFix":"// before (conceptual allowlist)\nfunc safeXAIAuthURL(u string) bool {\n    host := hostOf(u)\n    return host == \"accounts.x.ai\" || host == \"xai.com\"\n}\n\n// after (when xAI ships a new legit auth host)\nfunc safeXAIAuthURL(u string) bool {\n    host := hostOf(u)\n    return host == \"accounts.x.ai\" || host == \"xai.com\" || host == \"auth.x.ai\"\n}","handlingStrategy":"try-catch","validationCode":"if _, err := url.Parse(redirectTarget); err != nil {\n    return fmt.Errorf(\"bad redirect target: %w\", err)\n}\nif !isExpectedXAIHost(hostOf(redirectTarget)) {\n    log.Printf(\"refusing off-host redirect to %s\", redirectTarget)\n}","typeGuard":null,"tryCatchPattern":"if err != nil {\n    if strings.Contains(err.Error(), \"untrusted host\") {\n        // either xAI added a host (update allowlist after verifying) or something hostile is in the chain — investigate, never bypass\n        securityLog.Alert(ctx, err)\n    }\n    return err\n}","preventionTips":["Never disable the trusted-host check","Log the offending URL for triage","Track xAI auth-host changes in release notes / status page"],"tags":["xai","sso","security","redirect","allowlist"],"backgroundTag":null,"analyzedSha":"073e92d17178a1ccdb0a27017f572f10c9c7ab62","analyzedAt":"2026-08-15T14:33:00.750Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}