Finding 5 — the MIME parser runs on attacker-controlled input: the inner
content recovered after decrypt/verify is whatever the sender put there.
Upstream had no depth limit on nested multiparts and no size cap anywhere.
Verified against the unpatched upstream parser with the same input:
UPSTREAM CRASHED: RangeError - Maximum call stack size exceeded
UPSTREAM: 65MB accepted (no size cap)
So this was a live decrypt-time DoS reachable by anyone who can send mail.
Caps added: depth 20, parts 500, bytes 64 MB — generous enough that no
legitimate message comes close (real mail nests 3-4 levels). Past a limit a
subtree degrades to a leaf rather than throwing, so one pathological branch
doesn't discard the legitimate parts above it. Oversize input is refused
outright rather than truncated: half a MIME tree parses into misleading
nonsense, and showing part of a message is worse than saying no. Both
bodyStructure walkers in smime-detect.js are capped too — those run on
server-supplied structure BEFORE any decrypt/verify gate.
Finding 4 — hardened, not eliminated, per the agreed scope. Unlocked
CryptoKeys still live in durable IndexedDB rather than memory; moving them
would mean refactoring how the plugin shares state across iframes and
risking the unlock->decrypt path just verified.
What changed instead:
- Removed the lockOnLogout opt-out from the logout/account-switch wipes. A
non-extractable key cannot be exported but can still be USED, so a handle
outliving the session lets anyone with the browser profile decrypt mail
without knowing the passphrase. That is not a preference to toggle off.
- Added a best-effort wipe on pagehide and beforeunload to narrow the window
in which a usable handle exists on disk. Best-effort by nature: an
IndexedDB write may not complete during teardown and neither event fires
on a crash — which is precisely why the boot wipe in activate() remains
the load-bearing control.
- Deliberately NOT wiping on visibilitychange: tabbing away would drop the
unlock and force a passphrase re-entry every time, which trains users into
turning S/MIME off entirely.
- Dropped the now-dead lockOnLogout setting from the manifest. A toggle that
silently does nothing is worse than no toggle.
Tests: 49 unit assertions + 28 round trip. The round trip now feeds genuinely
hostile MIME through the real parser (5000-level nesting, 5000 siblings,
65 MB) and still confirms a normal multipart/alternative parses correctly.
Full crypto round trip unchanged and passing, so neither fix broke S/MIME.
Findings 6, 7, 8 and 9 remain open.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>