diff --git a/extension/src/service-worker/__tests__/session.test.ts b/extension/src/service-worker/__tests__/session.test.ts new file mode 100644 index 0000000..e83f5d4 --- /dev/null +++ b/extension/src/service-worker/__tests__/session.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it, vi, beforeEach } from 'vitest'; +import type { SessionHandle } from '../../../wasm/relicario_wasm'; +import { clearCurrent, getCurrent, setCurrent } from '../session'; + +describe('session', () => { + beforeEach(() => { + // Reset module-scope `current` between tests. Overwrite first with a + // benign no-throw fake so a prior test's throwing handle can't escape. + setCurrent({ free: vi.fn(), value: 0 } as unknown as SessionHandle); + clearCurrent(); + }); + + it('clearCurrent() is a no-op when no handle is set', () => { + expect(() => clearCurrent()).not.toThrow(); + expect(getCurrent()).toBeNull(); + }); + + it('clearCurrent() calls free() exactly once and clears current', () => { + const free = vi.fn(); + setCurrent({ free, value: 1 } as unknown as SessionHandle); + + clearCurrent(); + expect(free).toHaveBeenCalledTimes(1); + expect(getCurrent()).toBeNull(); + + clearCurrent(); + expect(free).toHaveBeenCalledTimes(1); + expect(getCurrent()).toBeNull(); + }); + + it('clearCurrent() propagates exceptions from free()', () => { + const free = vi.fn(() => { throw new Error('boom'); }); + setCurrent({ free, value: 2 } as unknown as SessionHandle); + + expect(() => clearCurrent()).toThrow(/boom/); + }); +}); diff --git a/extension/src/service-worker/session.ts b/extension/src/service-worker/session.ts index 89d0992..4859646 100644 --- a/extension/src/service-worker/session.ts +++ b/extension/src/service-worker/session.ts @@ -7,6 +7,12 @@ /// Future multi-vault (β+) would replace `current` with /// `Map` and thread `vaultId` through every /// handler. Deliberate α simplicity — not an oversight. +/// +/// As of Phase 1 of the security-polish series, `impl Drop for SessionHandle` +/// on the Rust side makes `.free()` the meaningful cleanup call: it removes +/// the entry from the SESSIONS registry and zeroizes `master_key` and +/// `image_secret`. Calling `wasm.lock(handle.value)` before `.free()` would +/// be redundant belt-and-suspenders; this module intentionally does not. import type { SessionHandle } from '../../wasm/relicario_wasm'; @@ -23,6 +29,6 @@ export function requireCurrent(): SessionHandle { export function clearCurrent(): void { if (!current) return; - try { current.free(); } catch { /* already freed */ } + current.free(); current = null; }