Summary
After a routine Chrome MV3 service-worker restart (happens after ~30s idle, not an edge case), the UI-facing "unlocked" flag is restored from persistent storage, but the actual cryptographic vault lock state is not — every sign handler proceeds as if unlocked, then fails deep inside the SDK instead of cleanly prompting for re-authentication.
Evidence
Full call chain traced:
apps/extension-wallet/src/security/storage-manager.ts — _storageManager is a plain module-level singleton (let _storageManager: ... = null), recreated fresh (encryptionKey: null) every time the MV3 service worker restarts.
packages/core-sdk/src/storage/secure-storage-manager.ts — encryptionKey is a private instance field with no persistence/restoration mechanism; only unlock(password) sets it.
apps/extension-wallet/src/background/session-state.ts — restoreUnlockSessionFromStorage() runs on every worker boot and restores a separate flag, _sessionUnlocked, from chrome.storage.session (which does survive worker restarts) — but this flag has no link back to the real SecureStorageManager instance's actual lock state.
- All 5 sign-capable handlers (
sign-transaction.ts, sign-message.ts, sign-auth-entry.ts, sign-relay-payload.ts, wallet-state.ts) gate only on isBackgroundSessionUnlocked() (the restored flag) — none check getSharedStorageManager().isUnlocked (a real getter on SecureStorageManager that correctly reflects encryptionKey !== null).
- Confirmed failure mode:
getSigningKeypair() → manager.getAccount() → getItem() → this.assertUnlocked() throws when locked.
Why it matters
The comment at the top of session-state.ts literally states the intent ("MV3 service worker restarts do not force password re-entry on every click") but the actual cryptographic unlock state is never restored to match — only the UI-facing flag is. Users hit a raw thrown error deep in the SDK instead of a clean re-auth prompt, for a completely routine browser behavior (service worker idle-kill).
Suggested fix
Either: (a) gate sign handlers on getSharedStorageManager().isUnlocked in addition to/instead of the flag, treating a mismatch as "needs re-auth" rather than a generic thrown error; or (b) restore the real encryption key on worker boot via a derive-and-cache pattern compatible with chrome.storage.session's in-memory-per-browser-session security properties.
Summary
After a routine Chrome MV3 service-worker restart (happens after ~30s idle, not an edge case), the UI-facing "unlocked" flag is restored from persistent storage, but the actual cryptographic vault lock state is not — every sign handler proceeds as if unlocked, then fails deep inside the SDK instead of cleanly prompting for re-authentication.
Evidence
Full call chain traced:
apps/extension-wallet/src/security/storage-manager.ts—_storageManageris a plain module-level singleton (let _storageManager: ... = null), recreated fresh (encryptionKey: null) every time the MV3 service worker restarts.packages/core-sdk/src/storage/secure-storage-manager.ts—encryptionKeyis a private instance field with no persistence/restoration mechanism; onlyunlock(password)sets it.apps/extension-wallet/src/background/session-state.ts—restoreUnlockSessionFromStorage()runs on every worker boot and restores a separate flag,_sessionUnlocked, fromchrome.storage.session(which does survive worker restarts) — but this flag has no link back to the realSecureStorageManagerinstance's actual lock state.sign-transaction.ts,sign-message.ts,sign-auth-entry.ts,sign-relay-payload.ts,wallet-state.ts) gate only onisBackgroundSessionUnlocked()(the restored flag) — none checkgetSharedStorageManager().isUnlocked(a real getter onSecureStorageManagerthat correctly reflectsencryptionKey !== null).getSigningKeypair()→manager.getAccount()→getItem()→this.assertUnlocked()throws when locked.Why it matters
The comment at the top of
session-state.tsliterally states the intent ("MV3 service worker restarts do not force password re-entry on every click") but the actual cryptographic unlock state is never restored to match — only the UI-facing flag is. Users hit a raw thrown error deep in the SDK instead of a clean re-auth prompt, for a completely routine browser behavior (service worker idle-kill).Suggested fix
Either: (a) gate sign handlers on
getSharedStorageManager().isUnlockedin addition to/instead of the flag, treating a mismatch as "needs re-auth" rather than a generic thrown error; or (b) restore the real encryption key on worker boot via a derive-and-cache pattern compatible withchrome.storage.session's in-memory-per-browser-session security properties.