37c409102a5d
M6/COR-004: make changePassword single atomic writeSlot call
- time
- 2026-05-16 02:48 UTC · 5 months ago
- machine
- koh2
- parent
- 6b7be6db17e9
↓ peach-browser-37c40910.tar.gzatlas at this save
files changed1 file
src/background/vault.ts+15 −8src/background/vault.ts+15 −8
@@ -2073,6 +2073,7 @@20732073 20742074 const slotA = await loadSlot(SLOT_A);20752075 if (!slotA) return false;2076+ if (!rootKey) return false;20762077 20772078 // Derive fresh key from new password + new random salt20782079 const newSalt = generateSalt();@@ -2082,30 +2083,36 @@20822083 const plaintext = encodePayload(entries, duressConfigured);20832084 const { nonce, ciphertext, tag } = await encrypt(newKey, plaintext);20842085 2085- const freshSlotA: StoredVault = {2086+ // Pre-wrap root key under the new derived key. Recovery fields wrap rootKey2087+ // under recovery words and remain untouched — recovery phrase keeps working2088+ // across password changes (H4/COR-002).2089+ const pwRoot = await wrapRootKeyForDerivedKey(newKey, rootKey);2090+2091+ const finalSlotA: StoredVault = {20862092 ...slotA,20872093 salt: hex(newSalt),20882094 nonce,20892095 ciphertext,20902096 tag,2097+ ...pwRoot,20912098 };2099+20922100 // Snapshot the OLD-key blob before overwriting it. If the user later20932101 // restores from this snapshot they will need the OLD password — the20942102 // post-restore lock surfaces that immediately.20952103 await takeVaultSnapshot("local_save");2096- await writeSlot(SLOT_A, freshSlotA);20972104 2105+ // Single atomic write: ciphertext, salt, and pw_root_* fields all land in2106+ // one storage transaction. An SW kill before this point leaves the old2107+ // slot intact; after this point everything tied to the new password is2108+ // already on disk together (M6/COR-004).2109+ await writeSlot(SLOT_A, finalSlotA);2110+20982111 // Swap the in-memory key synchronously before any further await. If we20992112 // threw between the Slot A write and this swap, the next persistEntries()21002113 // would overwrite Slot A with old-key ciphertext, locking the user out.21012114 zeroKey(derivedKey);21022115 derivedKey = newKey;2103-2104- // Preserve the existing root key: re-wrap pw_root_* under the new derived2105- // key so the same recovery words continue to decrypt the vault after a2106- // password change (H4/COR-002). Rotating the root key here would silently2107- // invalidate every previously-saved recovery phrase.2108- await rewrapRootKeyForPassword(newKey, newSalt);21092116 21102117 // Regenerate noise for slot B when duress is not in use. Runs after the21112118 // key swap so a failure here is a disposable noise regeneration, not a