auth hardening: generateOAuthState requires WebCrypto; OAuth callback rejects unparseable state#1589
Conversation
|
@claude review |
|
Claude finished @cliffhall's task in 1m 19s —— View job Review — PR #1589 (OAuth state hardening)
Overall this is a clean, tightly-scoped, well-documented hardening PR. Both changes are correct and the tests cover the new branches. A few observations below — none blocking. ✅ What's solid
🔎 Observations / possible follow-ups
None of these block merge — the security posture strictly improves. Items 1–2 are the ones worth a maintainer decision. |
|
Thanks for the review. Addressed all three observations in d21da16:
|
|
@claude review |
|
Claude finished @cliffhall's task in 48s —— View job Re-review — PR #1589 (OAuth state hardening)
Reviewed commit ✅ Observations resolved
🔎 Spot checks (all pass)
Nothing blocking — the security posture strictly improves and the follow-up items from the first round are all handled. Ship it. |
|
📋 Smoke test & AGENTS.md audit for this PR: #1562 (comment) (posted on the linked issue #1562). |
…ase change; tree unchanged)
Closes #1562
Wave 1 of the PR #1510 decomposition (tracking #1579). Two small OAuth hardening fixes, scoped to the core auth state generation + web OAuth callback validation.
Changes
generateOAuthState()(core/auth/utils.ts) now throws whencrypto.getRandomValuesis unavailable instead of silently falling back toMath.random().Math.random()is not a CSRF-grade entropy source — better to fail loudly on exotic runtimes than mint a guessable state. The throw guards both the missing-crypto-global and missing-getRandomValuescases.The web
/oauth/callbackhandler (App.tsx) now parses the returnedstateparam and rejects anything that does not match the expected 64-char-hexauthIdshape produced bygenerateOAuthState. An unparseablestateis a forgery indicator; the handler surfaces a clear "OAuth callback rejected" toast and returns instead of silently proceeding with an undefined session id.Tests
core/auth/utils.ts— added throw-path tests for both the absent-cryptoand missing-getRandomValuesbranches (expected error is asserted viatoThrow, no stray console output). utils.ts clears the ≥90 per-file gate on all four dimensions.App.test.tsx— added a/oauth/callbackdescribe covering the reject-unparseable-state path (asserts the rejection toast) and the accept-valid-state path (asserts the rejection toast is not shown).Validation
cd clients/web && npm run validate— pass (format, lint, build, 2442 unit tests).npm run test:integration— pass (799 tests).core/auth/utils.tsat 100% lines/functions/statements, ≥90% branches.Notes
The reference PR #1510 used a
{mode}:{authId}state shape (guided auth), but guided auth was since removed onv2/main, so this re-implementation adapts to the current singleauthIdshape. The reference also routed the callback error throughrecordConnectError(part of a separate Wave 1 EMA/data-status change not present here), so this uses the current handler'snotifications.showstyle.🤖 Generated with Claude Code
https://claude.ai/code/session_01S3fTN8H3R8YV4yUGvZjYnX