Add PWA shell and a client-side biometric gate on secret reveal
...
PWA: hand-rolled service worker (public/sw.js, no build plugin) with a
precached shell, network-first navigations with a 4s timeout and an
inline offline page, and stale-while-revalidate for /assets and public
files that byte-compares in the background and replaces an entry only
when the server copy differs. API/auth/MCP/webhook traffic is never
intercepted or cached — cookie and secret responses stay out of cache
storage. manifest.webmanifest + theme-color + apple-touch-icon; icons
rasterized from the shield logo by tools/rasterize_icons.py (192/512,
any + maskable). SW registers in main.js under the same
secure-context condition the gate uses, with silent background update
ticks on visibility/online/15min.
Biometric gate (frontend/src/biometric.js): a platform-authenticator
passkey (Android fingerprints through BiometricPrompt, Touch ID,
Windows Hello) is enrolled opt-in from Settings; every reveal and
version reveal runs a fresh WebAuthn assertion with
userVerification:"required", whose clientData/authenticatorData/
signature are verified offline in the same browser against the stored
public key. The server never learns about biometrics — this is an
interface lock, not encryption. Disable requires the same proof, and
the toggle disables itself with an explanatory note on devices
without a platform authenticator.
Also fixes a pre-existing bug the verification run caught: the detail
panel's Hide button wrote `revealed.value = null` in the template,
where refs auto-unwrap, so the panel never returned to masked state.
Verified end to end with a CDP virtual authenticator: 16 checks cover
manifest/SW/offline shell/freshness/no-API-cache, enrollment, reveal
through the gate (HTTP 200), reveal blocked with no sensor, disable
refused without the sensor and allowed with it, and the disabled
toggle + note on a device without biometrics.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
6 hours ago