Client or integration
Codex CLI / Direct HTTP/API client
Area
Authentication and account pool
Summary
OAuth token responses are parsed into credential expiries in four places without a Number.isFinite guard on expires_in. A malformed upstream response (missing, NaN, or string expires_in) yields a NaN expiry that flows into downstream time comparisons, where NaN <= x is always false — the credential is treated as never-expiring and never refreshed. This is the same data-quality class as the recently merged #1366/#1369 fix (Number.isFinite guards on local token expiry parsing) that remains unfixed at the token-response parse sites.
Affected sites:
src/oauth/anthropic.ts:87 — Date.now() + data.expires_in * 1000 - 5 * 60 * 1000 — missing expires_in → undefined * 1000 = NaN
src/oauth/kimi.ts:163 — Date.now() + payload.expires_in * 1000 - OAUTH_EXPIRY_SKEW_MS — line 154 checks typeof !== "number", but NaN passes (typeof NaN === "number")
src/oauth/chatgpt.ts:55 — Date.now() + ((data.expires_in as number) ?? 3600) * 1000 — ?? only guards null/undefined; NaN or a numeric string passes through
src/codex/account-store.ts:498 — Date.now() + data.expires_in * 1000 — no validation at all
Reference implementation with the correct guard already exists at src/oauth/xai.ts:128-129:
const expiresIn = typeof payload.expires_in === "number" && Number.isFinite(payload.expires_in) ? payload.expires_in : 3600;
Reproduction
Mock an upstream OAuth token response with expires_in missing (or null/NaN):
{ "access_token": "...", "refresh_token": "...", "expires_in": null }
Observe the stored credential carries expires: NaN, so expires <= now is always false — the credential never refreshes and ocx status reports it as logged in indefinitely (its health/refresh machinery cannot detect expiry).
Expected: a non-finite expires_in falls back to the same default used for a missing field (e.g. 3600s) so the credential stays refreshable.
Version
dev 0de4fd2d
Operating system
Cross-platform
Provider and model
anthropic / kimi / chatgpt / codex (account-store) — OAuth token response parsing
Checks
Client or integration
Codex CLI / Direct HTTP/API client
Area
Authentication and account pool
Summary
OAuth token responses are parsed into credential expiries in four places without a
Number.isFiniteguard onexpires_in. A malformed upstream response (missing,NaN, or stringexpires_in) yields aNaNexpiry that flows into downstream time comparisons, whereNaN <= xis alwaysfalse— the credential is treated as never-expiring and never refreshed. This is the same data-quality class as the recently merged #1366/#1369 fix (Number.isFiniteguards on local token expiry parsing) that remains unfixed at the token-response parse sites.Affected sites:
src/oauth/anthropic.ts:87—Date.now() + data.expires_in * 1000 - 5 * 60 * 1000— missingexpires_in→undefined * 1000=NaNsrc/oauth/kimi.ts:163—Date.now() + payload.expires_in * 1000 - OAUTH_EXPIRY_SKEW_MS— line 154 checkstypeof !== "number", butNaNpasses (typeof NaN === "number")src/oauth/chatgpt.ts:55—Date.now() + ((data.expires_in as number) ?? 3600) * 1000—??only guardsnull/undefined;NaNor a numeric string passes throughsrc/codex/account-store.ts:498—Date.now() + data.expires_in * 1000— no validation at allReference implementation with the correct guard already exists at
src/oauth/xai.ts:128-129:Reproduction
Mock an upstream OAuth token response with
expires_inmissing (ornull/NaN):{ "access_token": "...", "refresh_token": "...", "expires_in": null }Observe the stored credential carries
expires: NaN, soexpires <= nowis always false — the credential never refreshes andocx statusreports it as logged in indefinitely (its health/refresh machinery cannot detect expiry).Expected: a non-finite
expires_infalls back to the same default used for a missing field (e.g. 3600s) so the credential stays refreshable.Version
dev
0de4fd2dOperating system
Cross-platform
Provider and model
anthropic / kimi / chatgpt / codex (account-store) — OAuth token response parsing
Checks