security: stop caching the raw vault key in sessionStorage
A memory dump of the WebView2 renderer (vault unlocked) showed the AES vault key sitting in sessionStorage as a clean base64 string — a plaintext copy of the master secret, trivially grep-able, present the whole session. persistCryptoKey/restoreCryptoKey existed only to survive a page reload, but the shell has no reload path (F5/Ctrl+R blocked, every navigation is a cmd:// bridge call), so the branch that consumed it was unreachable. Removed both functions, their 7 call sites, and collapsed the init restore branch into the plain cold-start path (Quick Unlock → PIN → master-pw, all already there). Also scrub the derived key bytes from the heap (keyBytes.fill(0)) once the CryptoKey and verifier are built — the CryptoKey keeps its own copy. Not fixable here, documented: the key stays extractable:true (Quick Unlock, PIN, recovery and master-pw change all exportKey it), and decrypted password strings still linger in the V8/Blink heap after lock — immutable strings the GC never zeroes, inherent to a JS/WebView2 password manager. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -401,7 +401,6 @@ async function loginViaPin(pin) {
|
||||
sessionStorage.setItem('hashAlgo', state.hashAlgo);
|
||||
sessionStorage.setItem('authToken', state.token);
|
||||
sessionStorage.setItem('csrfToken', state.csrf);
|
||||
await persistCryptoKey();
|
||||
|
||||
state.locked = false;
|
||||
state.justRecovered = false;
|
||||
@@ -547,7 +546,6 @@ async function tryQuickUnlock() {
|
||||
sessionStorage.setItem('hashAlgo', state.hashAlgo);
|
||||
sessionStorage.setItem('authToken', state.token);
|
||||
sessionStorage.setItem('csrfToken', state.csrf);
|
||||
await persistCryptoKey();
|
||||
|
||||
state.locked = false;
|
||||
return true;
|
||||
@@ -891,7 +889,6 @@ async function doRecoveryRedeem() {
|
||||
// so master-pw change can later re-export and re-wrap as needed).
|
||||
state.cryptoKey = await crypto.subtle.importKey(
|
||||
'raw', rawKey, { name: 'AES-GCM' }, true, ['encrypt', 'decrypt']);
|
||||
await persistCryptoKey();
|
||||
|
||||
const remaining = (typeof r.remainingUses === 'number') ? r.remainingUses : 0;
|
||||
if (remaining <= 0) {
|
||||
|
||||
Reference in New Issue
Block a user