9af036ca50b9
test: adapt recovery-words + recovery.adversarial suites to LV-4 GET_RECOVERY_WORDS password re-auth; full suite green (3141)
- time
- 2026-06-10 03:08 UTC · 4 months ago
- machine
- koh2
- agent
- claude-opus-4-8
- session
- audit fix integration
- parent
- 56b407befb6e
↓ peach-browser-9af036ca.tar.gzatlas at this save
files changed2 files
src/background/recovery-words.test.ts+28 −28src/background/recovery.adversarial.test.ts+6 −1src/background/recovery-words.test.ts+28 −28
@@ -116,14 +116,14 @@116116 beforeEach(async () => { await setupUnlockedVault(); });117117 118118 it('returns 12 words', async () => {119- const result = await dispatch({ type: 'GET_RECOVERY_WORDS' });119+ const result = await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } });120120 expect(result.ok).toBe(true);121121 const words = (result as { data: string[] }).data;122122 expect(words).toHaveLength(12);123123 });124124 125125 it('all words are from the BIP39 word list', async () => {126- const result = await dispatch({ type: 'GET_RECOVERY_WORDS' });126+ const result = await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } });127127 const words = (result as { data: string[] }).data;128128 for (const word of words) {129129 expect(WORD_LIST).toContain(word);@@ -131,14 +131,14 @@131131 });132132 133133 it('returns same words on subsequent calls (deterministic)', async () => {134- const r1 = await dispatch({ type: 'GET_RECOVERY_WORDS' });135- const r2 = await dispatch({ type: 'GET_RECOVERY_WORDS' });134+ const r1 = await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } });135+ const r2 = await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } });136136 expect((r1 as { data: string[] }).data).toEqual((r2 as { data: string[] }).data);137137 });138138 139139 it('returns error when vault is locked', async () => {140140 await lockVault();141- const result = await dispatch({ type: 'GET_RECOVERY_WORDS' });141+ const result = await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } });142142 expect(result.ok).toBe(false);143143 });144144 });@@ -147,7 +147,7 @@147147 beforeEach(async () => { await setupUnlockedVault(); });148148 149149 it('returns no invalid words for correct list', async () => {150- const r = await dispatch({ type: 'GET_RECOVERY_WORDS' });150+ const r = await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } });151151 const words = (r as { data: string[] }).data;152152 const result = await dispatch({153153 type: 'VALIDATE_RECOVERY_WORDS',@@ -169,7 +169,7 @@169169 });170170 171171 it('is case-insensitive for validation', async () => {172- const r = await dispatch({ type: 'GET_RECOVERY_WORDS' });172+ const r = await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } });173173 const words = (r as { data: string[] }).data;174174 const uppercased = words.map((w: string) => w.toUpperCase());175175 const result = await dispatch({@@ -184,7 +184,7 @@184184 describe('RECOVER_WITH_WORDS handler', () => {185185 it('recovery succeeds with correct words and new passphrase', async () => {186186 await setupUnlockedVault();187- const r = await dispatch({ type: 'GET_RECOVERY_WORDS' });187+ const r = await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } });188188 const words = (r as { data: string[] }).data;189189 await lockVault();190190 @@ -211,7 +211,7 @@211211 212212 it('fails with partial word list (fewer than 12)', async () => {213213 await setupUnlockedVault();214- const r = await dispatch({ type: 'GET_RECOVERY_WORDS' });214+ const r = await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } });215215 const words = (r as { data: string[] }).data;216216 await lockVault();217217 const partial = words.slice(0, 6);@@ -224,7 +224,7 @@224224 225225 it('new passphrase works after recovery', async () => {226226 await setupUnlockedVault();227- const r = await dispatch({ type: 'GET_RECOVERY_WORDS' });227+ const r = await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } });228228 const words = (r as { data: string[] }).data;229229 await lockVault();230230 await dispatch({@@ -241,7 +241,7 @@241241 242242 it('old passphrase does not work after recovery', async () => {243243 await setupUnlockedVault();244- const r = await dispatch({ type: 'GET_RECOVERY_WORDS' });244+ const r = await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } });245245 const words = (r as { data: string[] }).data;246246 await lockVault();247247 await dispatch({@@ -258,7 +258,7 @@258258 259259 it('preserves recovery escrow fields after recovery password rewrap', async () => {260260 await setupUnlockedVault();261- const r = await dispatch({ type: 'GET_RECOVERY_WORDS' });261+ const r = await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } });262262 const words = (r as { data: string[] }).data;263263 const before = { ...rawSlotA() };264264 await lockVault();@@ -280,7 +280,7 @@280280 281281 it('same recovery words succeed on a second recovery', async () => {282282 await setupUnlockedVault();283- const r = await dispatch({ type: 'GET_RECOVERY_WORDS' });283+ const r = await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } });284284 const words = (r as { data: string[] }).data;285285 286286 await lockVault();@@ -307,7 +307,7 @@307307 308308 it('same recovery words succeed on a third recovery', async () => {309309 await setupUnlockedVault();310- const r = await dispatch({ type: 'GET_RECOVERY_WORDS' });310+ const r = await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } });311311 const words = (r as { data: string[] }).data;312312 313313 for (let i = 0; i < 3; i += 1) {@@ -322,7 +322,7 @@322322 323323 it('vault remains usable after recovery', async () => {324324 await setupUnlockedVault();325- const r = await dispatch({ type: 'GET_RECOVERY_WORDS' });325+ const r = await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } });326326 const words = (r as { data: string[] }).data;327327 328328 await lockVault();@@ -345,19 +345,19 @@345345 // every password change would silently brick recovery.346346 it('recovery words are preserved across CHANGE_PASSWORD', async () => {347347 await setupUnlockedVault();348- const before = (await dispatch({ type: 'GET_RECOVERY_WORDS' }) as { data: string[] }).data;348+ const before = (await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } }) as { data: string[] }).data;349349 await dispatch({350350 type: 'CHANGE_PASSWORD',351351 payload: { currentPassword: 'test-pass-12345', newPassword: 'new-pass-67890' },352352 });353- const after = (await dispatch({ type: 'GET_RECOVERY_WORDS' }) as { data: string[] }).data;353+ const after = (await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'new-pass-67890' } }) as { data: string[] }).data;354354 expect(after).toEqual(before);355355 });356356 357357 it('recovery is not Pro-gated', async () => {358358 await setupUnlockedVault();359359 await mockNoPro();360- const r = await dispatch({ type: 'GET_RECOVERY_WORDS' });360+ const r = await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } });361361 const words = (r as { data: string[] }).data;362362 await lockVault();363363 const result = await dispatch({@@ -374,7 +374,7 @@374374 describe('changePassword preserves root key and recovery words', () => {375375 it('recovery words work to unlock after password change', async () => {376376 await setupUnlockedVault();377- const words = (await dispatch({ type: 'GET_RECOVERY_WORDS' }) as { data: string[] }).data;377+ const words = (await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } }) as { data: string[] }).data;378378 const ch = await dispatch({379379 type: 'CHANGE_PASSWORD',380380 payload: { currentPassword: 'test-pass-12345', newPassword: 'new-pass-aaa' },@@ -390,12 +390,12 @@390390 391391 it('recovery words are identical before and after password change', async () => {392392 await setupUnlockedVault();393- const before = (await dispatch({ type: 'GET_RECOVERY_WORDS' }) as { data: string[] }).data;393+ const before = (await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } }) as { data: string[] }).data;394394 await dispatch({395395 type: 'CHANGE_PASSWORD',396396 payload: { currentPassword: 'test-pass-12345', newPassword: 'another-pass-bbb' },397397 });398- const after = (await dispatch({ type: 'GET_RECOVERY_WORDS' }) as { data: string[] }).data;398+ const after = (await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'another-pass-bbb' } }) as { data: string[] }).data;399399 expect(after).toEqual(before);400400 });401401 @@ -429,7 +429,7 @@429429 430430 it('GET_RECOVERY_WORDS still works after a password change + lock + unlock', async () => {431431 await setupUnlockedVault();432- const before = (await dispatch({ type: 'GET_RECOVERY_WORDS' }) as { data: string[] }).data;432+ const before = (await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } }) as { data: string[] }).data;433433 await dispatch({434434 type: 'CHANGE_PASSWORD',435435 payload: { currentPassword: 'test-pass-12345', newPassword: 'pass-after-cycle' },@@ -439,7 +439,7 @@439439 type: 'UNLOCK',440440 payload: { password: 'pass-after-cycle' },441441 });442- const after = (await dispatch({ type: 'GET_RECOVERY_WORDS' }) as { data: string[] }).data;442+ const after = (await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'pass-after-cycle' } }) as { data: string[] }).data;443443 expect(after).toEqual(before);444444 });445445 });@@ -450,7 +450,7 @@450450 describe('deriveRootKeyFromDerivedKey and recovery persistence', () => {451451 it('GET_RECOVERY_WORDS works after lock and re-unlock', async () => {452452 await setupUnlockedVault();453- const r1 = await dispatch({ type: 'GET_RECOVERY_WORDS' });453+ const r1 = await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } });454454 const words1 = (r1 as { data: string[] }).data;455455 expect(words1).toHaveLength(12);456456 await lockVault();@@ -459,14 +459,14 @@459459 payload: { password: 'test-pass-12345' },460460 });461461 expect(unlocked.ok).toBe(true);462- const r2 = await dispatch({ type: 'GET_RECOVERY_WORDS' });462+ const r2 = await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } });463463 const words2 = (r2 as { data: string[] }).data;464464 expect(words2).toEqual(words1);465465 });466466 467467 it('GET_RECOVERY_WORDS works after multiple lock/unlock cycles', async () => {468468 await setupUnlockedVault();469- const original = (await dispatch({ type: 'GET_RECOVERY_WORDS' }) as { data: string[] }).data;469+ const original = (await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } }) as { data: string[] }).data;470470 for (let i = 0; i < 5; i++) {471471 await lockVault();472472 await dispatch({@@ -474,7 +474,7 @@474474 payload: { password: 'test-pass-12345' },475475 });476476 }477- const after = (await dispatch({ type: 'GET_RECOVERY_WORDS' }) as { data: string[] }).data;477+ const after = (await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } }) as { data: string[] }).data;478478 expect(after).toEqual(original);479479 });480480 @@ -499,7 +499,7 @@499499 });500500 expect(unlocked.ok).toBe(true);501501 502- const result = await dispatch({ type: 'GET_RECOVERY_WORDS' });502+ const result = await dispatch({ type: 'GET_RECOVERY_WORDS', payload: { password: 'test-pass-12345' } });503503 expect(result.ok).toBe(true);504504 const words = (result as { data: string[] }).data;505505 expect(words).toHaveLength(12);src/background/recovery.adversarial.test.ts+6 −1
@@ -133,7 +133,12 @@133133 }134134 135135 async function getWords(): Promise<string[]> {136- const r = await dispatch({ type: "GET_RECOVERY_WORDS" });136+ // LV-4: GET_RECOVERY_WORDS now requires a fresh password proof. Every137+ // caller fetches words right after setupUnlockedVault()'s default password.138+ const r = await dispatch({139+ type: "GET_RECOVERY_WORDS",140+ payload: { password: "test-pass-12345" },141+ });137142 expect(r.ok).toBe(true);138143 return (r as { data: string[] }).data;139144 }