518931773452

fix: recovery words hide on window blur in both the recovery display and Codex generation views — words are cleared on visibilitychange-hidden (Codex requires the password step again) so an OS screenshot of a backgrounded popup never captures them; bump 5.0.14→5.0.15

time
2026-08-30 00:47 UTC · 1 month ago
machine
koh2
agent
glm-5.3-flash
session
pb-26 audit fixes
parent
7c55294ece28
5 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/views/CodexGenerateView.tsx+15 −0src/popup/views/RecoveryWordDisplay.tsx+29 −11
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "5.0.14",4+  "version": "5.0.15",55   "description": "Zero-knowledge password manager",66   "browser_specific_settings": {77     "gecko": {

view file

manifest.json+1 −1
@@ -2,7 +2,7 @@22   "manifest_version": 3,33   "minimum_chrome_version": "116",44   "name": "Peach Password Manager",5-  "version": "5.0.14",5+  "version": "5.0.15",66   "description": "Zero-knowledge password manager",77   "action": {88     "default_popup": "src/popup/index.html",

view file

package.json+1 −1
@@ -1,6 +1,6 @@11 {22   "name": "peach",3-  "version": "5.0.14",3+  "version": "5.0.15",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/popup/views/CodexGenerateView.tsx+15 −0
@@ -94,6 +94,21 @@9494     await loadSnapshot();9595   });9696 97+  // Screenshot hygiene: drop the displayed words when the window is98+  // backgrounded so an OS screenshot of the hidden popup never captures99+  // them; re-showing requires the master-password step again.100+  const handleVisibilityChange = () => {101+    const snap = snapshot();102+    if (document.visibilityState === "hidden" && snap?.recoveryWords) {103+      setSnapshot({ ...snap, recoveryWords: null });104+      setNeedsPassword(true);105+    }106+  };107+  document.addEventListener("visibilitychange", handleVisibilityChange);108+  onCleanup(() =>109+    document.removeEventListener("visibilitychange", handleVisibilityChange),110+  );111+97112   async function loadSnapshot(password?: string): Promise<void> {98113     const res = (await sendMessage({99114       type: "GET_CODEX_VAULT_DATA",

view file

src/popup/views/RecoveryWordDisplay.tsx+29 −11
@@ -36,18 +36,8 @@3636   let countdownTimer: ReturnType<typeof setInterval> | null = null;3737 3838   onMount(async () => {39-    // Fetch the recovery words from the background (vault must be unlocked).40-    const res = (await sendMessage({41-      type: "GET_RECOVERY_WORDS",42-    })) as { ok: boolean; data?: string[]; error?: string };39+    await refreshWords();4340 44-    if (res.ok && res.data) {45-      setWords(res.data);46-    } else {47-      setError("could not retrieve recovery words");48-    }49-    setLoading(false);50-5141     // Start 5-second countdown5242     let remaining = 5;5343     countdownTimer = setInterval(() => {@@ -60,8 +50,36 @@6050     }, 1000);6151   });6252 53+  async function refreshWords(): Promise<void> {54+    // Fetch the recovery words from the background (vault must be unlocked).55+    const res = (await sendMessage({56+      type: "GET_RECOVERY_WORDS",57+    })) as { ok: boolean; data?: string[]; error?: string };58+59+    if (res.ok && res.data) {60+      setWords(res.data);61+    } else {62+      setError("could not retrieve recovery words");63+    }64+    setLoading(false);65+  }66+67+  // Screenshot hygiene: blank the words whenever the window is backgrounded,68+  // so an OS screenshot of the hidden popup never captures them. They are69+  // re-fetched when the user returns (the recovery-pending window still70+  // exempts the fetch from password re-auth).71+  const handleVisibilityChange = () => {72+    if (document.visibilityState === "hidden") {73+      setWords([]);74+    } else if (!confirmed()) {75+      void refreshWords();76+    }77+  };78+  document.addEventListener("visibilitychange", handleVisibilityChange);79+6380   onCleanup(() => {6481     if (countdownTimer) clearInterval(countdownTimer);82+    document.removeEventListener("visibilitychange", handleVisibilityChange);6583   });6684 6785   async function handleConfirm() {

view file