d12b636cf3a3

require fresh master-password proof for cleartext vault export and bump v5.0.79

time
2026-09-03 23:03 UTC · 1 month ago
machine
koh2
agent
claude-sonnet-4-6
session
PB-26-311 export reauthentication
parent
aaadb859cc90
9 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/message-policy.test.ts+1 −1src/background/message-policy.ts+1 −0src/background/messages.test.ts+21 −0src/background/messages.ts+11 −0src/popup/views/Export.tsx+11 −1src/shared/types.ts+1 −0
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "5.0.78",4+  "version": "5.0.79",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.78",5+  "version": "5.0.79",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.78",3+  "version": "5.0.79",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/background/message-policy.test.ts+1 −1
@@ -13,7 +13,7 @@1313     const declaredTypes: MessageType[] = [1414       "GET_SYNC_ALERTS", "CLEAR_CLIPBOARD", "GET_PENDING_VOUCHED_DEVICES", "CONFIRM_VOUCHED_DEVICE",1515       "REJECT_VOUCHED_DEVICE", "VOUCHED_DEVICE_PENDING", "ROTATE_SYNC_IDENTITY", "DEVICE_KEY_ROTATED",16-      "UNLOCK", "UNLOCK_WITH_PIN", "GET_PIN_STATUS", "LOCK", "IS_UNLOCKED", "HAS_VAULT", "GET_ENTRIES", "ADD_ENTRY",16+      "UNLOCK", "UNLOCK_WITH_PIN", "GET_PIN_STATUS", "LOCK", "IS_UNLOCKED", "HAS_VAULT", "GET_ENTRIES", "EXPORT_ENTRIES", "ADD_ENTRY",1717       "BATCH_ADD_ENTRIES", "RESTORE_ENTRIES", "CLEAR_VAULT_ENTRIES", "UPDATE_ENTRY", "ADD_ADDITIONAL_URL",1818       "REQUEST_REMEMBER_SITE",1919       "DELETE_ENTRY", "AUTOFILL_QUERY", "COPY", "CREATE_VAULT", "CREATE_VAULT_FROM_ROOT_KEY",

view file

src/background/message-policy.ts+1 −0
@@ -30,6 +30,7 @@3030   IS_UNLOCKED: "extension-page-only",3131   HAS_VAULT: "extension-page-only",3232   GET_ENTRIES: "extension-page-only",33+  EXPORT_ENTRIES: "extension-page-only",3334   BATCH_ADD_ENTRIES: "extension-page-only",3435   RESTORE_ENTRIES: "extension-page-only",3536   CLEAR_VAULT_ENTRIES: "extension-page-only",

view file

src/background/messages.test.ts+21 −0
@@ -1829,6 +1829,27 @@18291829     expect((res.data as string[]).length).toBe(12);18301830   });18311831 1832+  it("PB-26-311: cleartext export requires a fresh master-password proof", async () => {1833+    const vault = await import("./vault");1834+    await vault.createVault("correct-pass-12345");1835+1836+    await expect(1837+      dispatch({ type: "EXPORT_ENTRIES" as never, payload: {} }),1838+    ).resolves.toMatchObject({ ok: false, error: "password_required" });1839+    await expect(1840+      dispatch({1841+        type: "EXPORT_ENTRIES" as never,1842+        payload: { password: "not-the-password" },1843+      }),1844+    ).resolves.toMatchObject({ ok: false, error: "wrong_password" });1845+    await expect(1846+      dispatch({1847+        type: "EXPORT_ENTRIES" as never,1848+        payload: { password: "correct-pass-12345" },1849+      }),1850+    ).resolves.toMatchObject({ ok: true, data: [] });1851+  });1852+18321853   it("GET_PIN_STATUS reports only a session-available PIN wrapper", async () => {18331854     const vault = await import("./vault");18341855     const password = "correct-pass-12345";

view file

src/background/messages.ts+11 −0
@@ -567,6 +567,17 @@567567       case "GET_ENTRIES":568568         if (!vault.isUnlocked()) return { ok: false, error: "vault_locked" };569569         return { ok: true, data: vault.getEntries() };570+      case "EXPORT_ENTRIES": {571+        if (!vault.isUnlocked()) return { ok: false, error: "vault_locked" };572+        const { password } = (msg.payload ?? {}) as { password?: unknown };573+        if (typeof password !== "string" || !password) {574+          return { ok: false, error: "password_required" };575+        }576+        if (!(await vault.verifyMainPassword(password))) {577+          return { ok: false, error: "wrong_password" };578+        }579+        return { ok: true, data: vault.getEntries() };580+      }570581       case "ADD_ENTRY": {571582         if (!vault.isUnlocked()) return { ok: false, error: "vault_locked" };572583         const payload = msg.payload;

view file

src/popup/views/Export.tsx+11 −1
@@ -326,6 +326,7 @@326326 327327 function CSVExport(props: { onBack: () => void }) {328328   const [acknowledged, setAcknowledged] = createSignal(false);329+  const [masterPassword, setMasterPassword] = createSignal("");329330   const [working, setWorking] = createSignal(false);330331   const [error, setError] = createSignal<string | null>(null);331332   const [done, setDone] = createSignal<string | null>(null);@@ -335,7 +336,7 @@335336     setDone(null);336337     setWorking(true);337338     try {338-      const res = await sendMessage({ type: "GET_ENTRIES" });339+      const res = await sendMessage({ type: "EXPORT_ENTRIES", payload: { password: masterPassword() } });339340       const entries = (res as { ok: boolean; data?: VaultEntry[] }).data ?? [];340341       // Passkeys are device-bound credentials and cannot be moved between341342       // devices via export. Strip the passkey sub-object out of any login@@ -389,6 +390,15 @@389390             </button>390391           </Show>391392           <Show when={acknowledged()}>393+            <label class="peach-settings-label" for="csv-export-password">master password</label>394+            <input395+              id="csv-export-password"396+              class="peach-settings-input"397+              type="password"398+              value={masterPassword()}399+              onInput={(e) => setMasterPassword((e.target as HTMLInputElement).value)}400+              placeholder="enter your master password"401+            />392402             <button393403               class="peach-btn peach-btn--subtle peach-btn--sm"394404               onClick={() => void handleExport()}

view file

src/shared/types.ts+1 −0
@@ -212,6 +212,7 @@212212   | "IS_UNLOCKED"213213   | "HAS_VAULT"214214   | "GET_ENTRIES"215+  | "EXPORT_ENTRIES"215216   | "ADD_ENTRY"216217   | "BATCH_ADD_ENTRIES"217218   | "RESTORE_ENTRIES"

view file