import test from 'node:test'; import assert from 'node:assert/strict'; import {AUTO_LOCK} from '../vault-session.mjs'; import {VAULT_CAPABILITY} from '../connection-state.mjs'; test('worker keys stay private; trusted popup only; close, timer, auth loss and lock races fail closed', async () => { let connect, valid = true; const stored = {[VAULT_CAPABILITY]:{owner:'owner',capability:'A'.repeat(43)}}, timers = []; globalThis.setInterval = () => 1; globalThis.clearInterval = () => {}; globalThis.setTimeout = callback => { timers.push(callback); return timers.length; }; globalThis.clearTimeout = () => {}; globalThis.chrome = { runtime: {id:'fixture', getURL:path=>'chrome-extension://fixture/'+path, getPlatformInfo:async()=>({}), onConnect:{addListener:fn=>connect=fn}}, storage:{local:{get:async key=>({[key]:stored[key]}),set:async data=>Object.assign(stored,data)}} }; globalThis.fetch = async()=>new Response(JSON.stringify({owner:valid?'owner':'other',items:[]}),{headers:{'content-type':'application/json'}}); const {vaultUnlocked} = await import('../vault-worker.mjs'); function popup(url='chrome-extension://fixture/popup.html') { let listener, close, disconnected = false, sequence = 0; const replies = new Map(); connect({name:'human-vault',sender:{id:'fixture',url},disconnect:()=>disconnected=true, onMessage:{addListener:fn=>listener=fn},onDisconnect:{addListener:fn=>close=fn}, postMessage:message=>{replies.get(message.id)?.(message);replies.delete(message.id)}}); return {get disconnected(){return disconnected}, close:()=>close(), send:(op,data={})=>new Promise(resolve=>{const id=++sequence;replies.set(id,resolve);listener({id,op,owner:'owner',...data})})}; } assert.equal(popup('https://persimmons.app/').disconnected,true); assert.equal(popup('chrome-extension://fixture/other.html').disconnected,true); let port = popup(); const unlock = () => port.send('unlock',{passphrase:'synthetic test passphrase'}); const firstUnlock = unlock(); await new Promise(resolve=>setImmediate(resolve)); assert.equal(vaultUnlocked('owner'),false,'status during unlock does not cancel it'); assert.equal((await firstUnlock).result,true); assert.equal(vaultUnlocked('owner'),true); assert.equal(vaultUnlocked('other'),false); assert.equal(vaultUnlocked('owner'),true,'another owner status cannot lock the session'); const record=stored['human-vault:https://persimmons.app:owner']; assert.ok(record.wrappedKey); assert.equal(Object.values(stored).some(x=>x instanceof CryptoKey),false); assert.doesNotMatch(JSON.stringify(stored),/synthetic test passphrase/); port.close(); port=popup();assert.equal((await port.send('status')).result,false); stored[AUTO_LOCK]='5';assert.equal((await unlock()).result,true); port.close();port=popup();assert.equal((await port.send('status')).result,true); const encrypted=await port.send('encrypt',{origin:'https://wp.phly.cloud',account:{username:'fixture',password:'synthetic'}}); assert.equal(encrypted.ok,true);assert.ok(encrypted.result.ciphertext); assert.deepEqual((await port.send('decrypt',{item:encrypted.result})).result,{username:'fixture',password:'synthetic'}); timers.at(-1)();assert.equal((await port.send('status')).result,false); assert.equal((await unlock()).result,true); valid=false;assert.equal((await port.send('status')).ok,false);valid=true; assert.equal((await port.send('status')).result,false); assert.equal((await port.send('unlock',{passphrase:'wrong'})).ok,false); const pending=unlock();await port.send('lock');assert.equal((await pending).ok,false); // Close invalidates any pending unlock and never installs its key. unlock();port.close();port=popup(); await new Promise(resolve=>setImmediate(resolve)); assert.equal((await port.send('status')).result,false); });