Skip to content

oauth/xai: validateXaiEndpoint accepts any *.x.ai subdomain and URLs with embedded userinfo #4048

Closed

Description

@lidge-jun

Where

src/oauth/xai.ts lines 47-54 (validateXaiEndpoint), on dev:

const host = parsed.hostname.toLowerCase();
if (parsed.protocol !== "https:" || (host !== "x.ai" && !host.endsWith(".x.ai"))) {
  throw new Error(...);
}
return parsed.toString();

Problem

Two gaps in the endpoint trust check applied to the OIDC discovery response, which is the URL that then receives the refresh_token in a POST body:

  1. Suffix-only host match: https://anything.x.ai/token passes. If the discovery document were ever tampered with (or a subdomain compromised), the refresh token would be posted there.
  2. new URL() preserves userinfo, and parsed.toString() returns it: https://u:p@auth.x.ai/oauth2/token passes unchanged, and fetch will turn the userinfo into an Authorization header on the request.

Both measured on Node 24.

Fix

Pin to the known auth hosts and reject userinfo:

const TRUSTED = new Set(["auth.x.ai", "accounts.x.ai"]);
if (parsed.protocol !== "https:" || parsed.username || parsed.password || !TRUSTED.has(host)) throw ...;

auth.x.ai is the live issuer per https://auth.x.ai/.well-known/openid-configuration (checked 2026-09-09); accounts.x.ai is the second host the progrok reference implementation allow-lists.

Severity: low (discovery is fetched over TLS from a fixed host), filed as hardening. Found while porting this module into ima2-gen (devlog 260909_grok_native_oauth/001_opencodex_port_spec.md D6).

Activity

  1. added
    bugSomething isn't working
    providerProvider adapters, OpenAI-compat presets, upstream API quirks
    on Sep 8, 2026
  2. lidge-jun commented on Sep 8, 2026

    @lidge-jun
    OwnerAuthor

    리뷰 · 우선순위 42 / 80

    이 이슈는 OIDC discovery가 돌려 준 token/authorization URL을 검사하는 validateXaiEndpoint가 너무 느슨하다는 하드닝 요청입니다. 지금 dev HEAD cfff026 src/oauth/xai.ts 47–54행은 https이고 호스트가 x.ai이거나 *.x.ai로 끝나면 통과시킨 뒤 parsed.toString()을 그대로 반환합니다.

    두 구멍이 있습니다. (1) 접미사만 보면 https://anything.x.ai/token도 통과합니다. discovery가 조작되거나 서브도메인이 뚫리면 refresh_token이 그쪽으로 POST됩니다. (2) new URL은 userinfo를 유지하고 toString()도 유지합니다. https://u:p@auth.x.ai/oauth2/token이 그대로 나가면 fetch가 userinfo를 Authorization으로 바꿀 수 있습니다. 작성자도 severity low로 적었고, discovery 자체는 고정 호스트 https://auth.x.ai/.well-known/openid-configuration에서 TLS로 가져옵니다.

    제안은 허용 호스트를 auth.x.ai / accounts.x.ai로 고정하고 username/password가 있으면 거절하는 것입니다. 라이브 issuer가 auth.x.ai인 점, progrok 참고 구현이 accounts.x.ai도 넣는 점과 맞습니다. 다만 미래에 xAI가 새 auth 호스트를 추가하면 여기 allow-list를 고쳐야 합니다. #4045–#4047과 같은 파일이지만, 긴급도는 더 낮습니다.

    라인 47–54 · validateXaiEndpoint - endsWith(".x.ai")와 userinfo 미검사가 핵심입니다. 측정·설명과 코드가 일치합니다.

    경로 discoverXaiOAuthEndpoints - discovery JSON의 endpoint 문자열만 이 함수를 탑니다. 고정 discovery URL 자체는 이미 안전합니다.

    경로 allow-list 유지비 - 호스트를 좁히면 보안은 좋아지고, xAI가 엔드포인트를 옮기면 로그인 전면 실패로 드러납니다. 그게 원하는 실패 모드인지 정하면 됩니다.

    메인테이너의 판단이 필요한 지점

    너의 추천
    받아 두되 큐 앞에는 두지 마세요. #4045/#4047 같은 동작 버그 PR이 열린 뒤에 같은 파일 후속으로 넣거나, 작은 단독 PR로 help wanted에 두면 됩니다. types/config 분할과 무관하고 close 대상도 아닙니다.

    이 댓글은 grok-bot이 작성했습니다

  3. added 2 commits that reference this issue on Sep 8, 2026
    638b396
    4711cae
  4. lidge-jun commented on Sep 9, 2026

    @lidge-jun
    OwnerAuthor

    Fixed on dev via #4094 at 4a08c75.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    account-poolOAuth, credentials, Codex pool, quota, failover, plansbugSomething isn't workingproviderProvider adapters, OpenAI-compat presets, upstream API quirks

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions