fix: self-heal a corrupt Core Kit store instead of wedging the app - #1183
Conversation
`AsyncStorage.get` parses the Core Kit store with a bare `JSON.parse`, and `init()` has no guard around it, so an unreadable `corekit_store` threw out of `restore()`. `CoreKitProvider` handed the same `settle` to fulfil and reject, so that throw was indistinguishable from a clean signed-out restore: no console surface, no error state, and nothing cleared the blob. The next login hit the same parse in `createSession` and threw again, wedging the tab across reloads. `Web3AuthSession.restore()` now clears the store when the failure was the store itself, reusing the existing `clearStore()`. The trigger is narrow on purpose: `init()` also ends in a bare `fetch` for the SDK's feature check, so an unconditional purge would delete a good store on an offline reload — and that blob carries the device factor share once MFA is reachable. `CoreKitProvider` now distinguishes reject from resolve and records the failure. The tab still lands at the front door and can never present as authenticated, but it lands there carrying the failure, and it keeps the session in hand because logging in again is the way out. The message is a fixed string rather than the underlying throw: V8 quotes the offending input in a `JSON.parse` failure, and that input is bearer key material. Closes #1182 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
WalkthroughCore Kit restoration now detects malformed persisted state, clears it, and rethrows initialization failures. ChangesCore Kit restore recovery
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant CoreKitProvider
participant coreKit
participant Web3AuthMPCCoreKit
participant PersistedStore
CoreKitProvider->>coreKit: restore()
coreKit->>Web3AuthMPCCoreKit: initialize()
Web3AuthMPCCoreKit->>PersistedStore: read persisted state
PersistedStore-->>Web3AuthMPCCoreKit: initialization result
alt malformed persisted state
coreKit->>PersistedStore: clear store
coreKit-->>CoreKitProvider: restoration failure
CoreKitProvider->>CoreKitProvider: set RESTORE_FAILED
else valid persisted state
coreKit-->>CoreKitProvider: restored session
end
Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/src/auth/CoreKitProvider.test.tsx`:
- Around line 16-25: Update the CoreKitProvider test mock and cases around
readStore to exercise the SDK’s documented parsed-store property access,
including the JSON.parse('null') failure path. Add a NULL_STORE = 'null'
scenario that verifies restore clears the stored value and reports the restore
failure, covering the parsed !== null recovery branch.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 20199d36-9536-4163-a545-9e7e5495aff5
📒 Files selected for processing (3)
apps/web/src/auth/CoreKitProvider.test.tsxapps/web/src/auth/CoreKitProvider.tsxapps/web/src/auth/coreKit.ts
The fake SDK only parsed its store, while the real `AsyncStorage.get` parses and then indexes the result for `sessionId`. A store holding the JSON `null` literal therefore survived the fake and threw in the SDK, so the `parsed !== null` arm of `storeIsReadable` was never exercised. The fake now performs the index, and both corrupt-store tests run over every shape the SDK's read throws on: a truncated write and a null literal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Problem
AsyncStorage.getparses the Core Kit store with a bareJSON.parse, andinit()has no guard around it, so an unreadablecorekit_storethrew out ofrestore().CoreKitProviderhanded the samesettleto both fulfil and reject, andsettledwas{ session, status: 'ready', error: null }. A rejected restore was therefore indistinguishable from "restored, signed out" — no unhandled rejection, no console surface, no error state. Nothing cleared the bad blob either, so the next login hit the same parse in the SDK'screateSessionand threw again, surfacing as a rawSyntaxErroron the login page. The tab stayed wedged across reloads; the only escape was clicking sign out, whose unconditionalclearStore()runs whether or not a session is live.Change
coreKit.ts—Web3AuthSession.restore()clears the store when the failure was the store itself, reusing theclearStore()that already backs every other teardown path.The trigger is deliberately narrow.
init()does not only read the store: it ends inawait this.featureRequest(), a barefetchof the Web3Auth feature-access endpoint with notry/catch, and its session rehydrate swallows its own failures and falls through to it. An unconditional purge would therefore delete a perfectly good store on an offline reload — origin-wide, so one background tab booting offline would evict the store every other tab's leader promotion depends on. The blob also carries the device factor share once MFA is reachable, which turns that into share destruction rather than a cache eviction. Sorestore()probes readability against the shape the SDK actually requires —JSON.parse(raw || '{}')[key], i.e. parses and is a non-null object — and purges only when that fails.CoreKitProvider.tsx— reject and resolve now settle differently, and a rejected restore records the failure.The tab still lands at the front door and can never present as authenticated:
authStoreonly flips on a completed engine handoff, andinit()opens withresetState(), so a throw leavesisLoggedIn()false and the restore effect inuseAuthreturns early. But it lands there carrying the failure instead of a clean sign-out, and it keeps the session in hand rather than nulling it — signing in again is the way out of this state, anduseAuthgatesloginon the session being present.The surfaced message is a fixed string, not the underlying throw. V8 quotes the offending input in a
JSON.parsefailure —JSON.parse('secret-key-bytes')throwsUnexpected token 's', "secret-key-bytes" is not valid JSON— and that input is bearer key material, so neither the UI nor the console gets the cause.Tests
New
apps/web/src/auth/CoreKitProvider.test.tsx, mounting the provider over a realcreateCoreKitSessionso the store under test is the real one, with the SDK class faked to reproduce its unguarded store read on both the restore and the login path:Each was proven red by reverting the production code. Against unmodified
mainthe first two fail. With only the provider change reverted, the first fails. With only the purge reverted, the first two fail. With the purge left unconditional, the third fails —expected null to be '{"deviceFactor":"a-device-factor"}'.Known residual
The recorded failure is latched in provider state for the tab's lifetime, and
useAuthreturnserror ?? coreKitError.auth.erroris rendered only byLoginPage, which is mounted only at/and redirects away on authentication, so this is invisible while signed in — but a clean sign-out in the same tab returns to a login page still showing the restore notice. Clearing it needs either new context API or new state inuseAuth, both outside a fail-closed fix to the restore path.Closes #1182
Note
Fix corrupt Core Kit store causing app to wedge on restore failure
Web3AuthSession.restorefailure, a newstoreIsReadable()check determines whether the local MPC Core Kit store is corrupt; unreadable stores are cleared before rethrowing the error.CoreKitProvidernow distinguishes restore failures from success: a rejected restore setserrorto a genericRESTORE_FAILEDmessage instead of silently clearing it.Macroscope summarized 7669450.
Summary by CodeRabbit