fix(oauth): guard local token expiry parsing against NaN - #1369
Conversation
|
✅ READY
Review readiness checklist
✅ 4/4 boxes ticked. This pull request is already Ready for Review. |
📝 WalkthroughWalkthroughOAuth credential imports now normalize invalid expiry values and reject unusable disk credentials. Login status remains true for credentials that can refresh lazily and becomes false when the active account requires reauthentication. ChangesOAuth credential validity
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant LocalCredential
participant OAuthDiscovery
participant OAuthTokenEndpoint
participant LoginStatus
LocalCredential->>OAuthDiscovery: reject malformed disk credential
OAuthDiscovery->>OAuthTokenEndpoint: refresh with stored refresh token
OAuthTokenEndpoint-->>LocalCredential: return OAuth credential
LocalCredential->>LoginStatus: evaluate active account
LoginStatus-->>LocalCredential: loggedIn depends on needsReauth
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
|
Wibias
left a comment
There was a problem hiding this comment.
Request changes — one behavioral regression plus a missing lifecycle regression test.
- High:
getLoginStatus()now treats any expired access token as logged out, even when the account still has a valid refresh token andneedsReauthis false. That conflicts with the existing lazy-refresh contract: request resolution refreshes expired/near-expiry credentials on demand. This can makeocx statussay “not logged in” for a normal refreshable account that will successfully refresh on the next request.
Please narrow this change so ordinary expired-but-refreshable OAuth credentials remain logged in. The invalid/unknown local-import expiry case should be handled separately, with needsReauth remaining authoritative for a true login failure. Add a regression covering: expired access token + valid refresh token + needsReauth === false => still logged in / refreshable.
- Medium: the new tests cover parsing and
shouldAdoptGrokGeneration(), but not the actual lifecycle where an existingsource: "local-cli"credential encounters a malformed/new external generation during refresh/reconciliation. Initial xAI/Anthropic local import already attempts refresh when the expiry is invalid because the freshness comparison fails, so the important regression is the stored-local-credential -> malformed external generation -> refresh-resolution path. Please add an integration-level test for that lifecycle.
The Number.isFinite normalization itself looks good and scoped. After the behavioral fix/test, this branch also needs rebasing onto current dev and exact-head CI; the auth-surface hygiene gate additionally requires maintainer sponsorship/security review.
cfd4858 to
9468a57
Compare
|
Tip For best results, initiate chat on the files or code changes.
The hygiene check remains pending. A maintainer must review the OAuth changes in You are interacting with an AI system. |
|
Addressed the review (Wibias): both points are fixed in the new commits, rebased onto current 1. High — expired-but-refreshable credentials stay logged in. 2. Medium — lifecycle regression test added in
|
|
|
|
Tip For best results, initiate chat on the files or code changes.
You are interacting with an AI system. |
|
Tip For best results, initiate chat on the files or code changes.
The PR remains ready for maintainer review. You are interacting with an AI system. |
…zy-refresh contract
9468a57 to
4d737e5
Compare
|
Tip For best results, initiate chat on the files or code changes.
The PR is ready for maintainer review. You are interacting with an AI system. |
|
|
|
Tip For best results, initiate chat on the files or code changes.
The PR remains ready for maintainer review. You are interacting with an AI system. |
|
Thanks @Bruce-Yii — nice work on this. 🙌 The final revision fixed the malformed/non-finite local-token expiry handling, preserved the lazy-refresh contract for expired-but-refreshable credentials, and added the missing lifecycle coverage to prove malformed disk generations are rejected while refresh continues from the stored credential. I re-reviewed the rebased head and no remaining code/security blocker was found. Merged — thank you for the careful follow-up fixes. |
Summary
Fixes #1366: an imported local CLI credential (Grok
~/.grok/auth.jsonor Claude.credentials.json) whoseexpires_atis invalid/unparseable (e.g."not-a-date",NaN, epoch-garbage like1970-01-22) was treated as valid and never refreshed, andocx statusshowed✓ logged infor it.Root cause
src/oauth/local-token-detect.ts—new Date(entry.expires_at as string).getTime()returnsNaNfor unparseable strings. Downstream comparisons then misbehave:shouldAdoptGrokGeneration():disk.expires <= now + refreshSkewMsis alwaysfalseforNaN(passes the expiry gate);bothExpiriesExistisfalsebecauseNaN > 0isfalse, so it falls through toreturn true— the garbage credential is adopted as the authoritative generation (authoritative()insrc/oauth/index.ts), and the refresh path never re-validates it.getLoginStatus()reportedloggedIn: !!cred(credential exists), ignoringneedsReauth— the CLI "OAuth logins" section printed✓ logged infor reauth-required credentials.Changes
src/oauth/local-token-detect.ts—detectGrokCliToken():Number.isFiniteguard on the parsedexpires_at; non-finite →0(unknown → forces the refresh-validation path downstream instead of "valid forever").src/oauth/local-token-detect.ts—shouldAdoptGrokGeneration(): explicit!Number.isFinite(disk.expires) → false(defense-in-depth; a garbage disk credential is never adopted as an upgrade).src/oauth/local-token-detect.ts—parseClaudeOauthPayload():Number.isFiniteguard onexpiresAt(strings/NaN →0).src/oauth/index.ts—getLoginStatus():loggedIn = !!cred && !needsReauth.needsReauthis the authoritative login-failure signal; an expired-but-refreshable access token stays logged in per the lazy-refresh contract (request resolution refreshes it on demand). Invalid/unknown local-import expiries are handled solely at parse/adoption time (points 1–3), never by over-reporting login state.Tests
tests/local-token-detect.test.ts: +9 tests (string/NaNexpiresAt→0;detectGrokCliTokenwith unparseable/futureexpires_at;shouldAdoptGrokGenerationwith NaN/0/expired/newer-valid disk expiry).tests/oauth-status-privacy.test.ts: +4 tests — expired / unknown-0 / non-finite (1e999) expiries stay logged in (lazy-refresh contract);needsReauthreports not logged in.tests/oauth-refresh.test.ts: +1 lifecycle regression — storedlocal-clixAI credential + malformed diskexpires_at: "not-a-date"→ generation not adopted, refresh resolves with the stored refresh token (1 discovery + 1 token exchange), detaches tosource: "oauth".Verification (head
9468a57b, rebased ontodev0757b10):tests/oauth-*+ local-token + cli-status + login-summary): 174/174 passbun x tsc --noEmit→ cleanFixes #1366
Review readiness checklist
This PR stays in draft until every box below is ticked. Tick all four boxes once the requirements are met:
All CI tests are green on my local testing.
I pushed my PR to the latest dev commit.
I resolved all correct Codex and CodeRabbit findings.
My PR is ready for review.
Summary by CodeRabbit
Bug Fixes
Tests