Repository navigation
🐛 fix: drop the vault data key on lock, and let no await undo a lock - #85
Merged
Merged
Conversation
Locking cleared the password-derived key but left the vault's data key in memory until the next unlock replaced it — the key that opens the payload and every attachment, still usable by anything running. Clearing it exposed what that had been hiding. Several flows await and then write session state, and a lock that landed during the await was quietly undone when they resumed. The worst was the password change: after two key derivations it wrote the new key into memory and into the keystore, and the keystore write cleared the lock's revocation mark, so the next reload opened the vault with no password at all. Reproduced before fixing: lock mid-change, reload, and the full vault was on screen. One rule now covers every case. The keys object is the session: a lock drops it, and each unlock mints a new one. `stillOpen(keys)` is asked after an await and before writing secrets. The password change is abandoned whole. A sync or a pull keeps its writes that describe the cloud and skips putting the decrypted vault back in memory. A key rotation interrupted after the shares moved to the new key finishes from the values in hand, since stopping there would lock out everyone staying.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Locking cleared the password-derived key but left the vault's data key in memory until the next unlock replaced it. That's the key that opens the payload and wraps every attachment key — still usable by anything running. Noticed while building #83, whose sign-out does clear it.
What clearing it exposed
Every reader of
keysRef, and what it does with a lock behind it:persistLocal·mutatemutatechecks the payload and calls it in one synchronous run, so no edit is lostreadAttachmentsharingContextprepareAttachment·importBundle·returnToOwnVaultrunSyncsetPayloadafter the attachment upload and after a mergepullAfterUnlocksetPayload(merged)after the downloadchangeMasterPasswordrotateDataKeyThe worst one, reproduced before fixing
The password change runs two PBKDF2 derivations — most of a second each — and only then writes: the new key into memory, and into the keystore through
syncKeystore. That keystore write also clears the lock's revocation mark from #81. A lock pressed during those seconds was undone, and the next reload opened the vault with no password at all.I wrote the smoke check first and ran it against
main:— with the full vault, all 14 items, on screen after the reload.
It is not a race: the check clicks Alterar senha and dispatches
Ctrl+Lin the same synchronous run of script, so the lock always lands while the first derivation is pending.One rule for all of them
The keys object is the session: a lock drops it, and every unlock mints a new one.
stillOpen(keys)—keysRef.current === keys— is asked after an await and before writing secrets. What each flow does with a "no":Tests, and what they do not cover
Two new smoke checks — the lock is not undone, and the old password still opens the vault. Each half of the fix was removed separately, each time with a changed bundle hash to rule out a stale
dist/:Not covered by any executed test: the guards in
runSync,pullAfterUnlockandrotateDataKey. All three need a real cloud account, and the smoke vault is local-only. They rest on reading the code and on the type checker.Not handled: locking and unlocking again while a rotation is still on the network. The new session would open with the old key and write over the rotation. That takes typing the master password faster than two Drive writes complete, so I left it out rather than add machinery for it.
npm test404 green · smoke 244 green, up from 242.