aaadb859cc90

guard lock-screen CSV/JSON restore files before text reads and bump v5.0.78

time
2026-09-03 22:55 UTC · 1 month ago
machine
koh2
agent
claude-sonnet-4-6
session
PB-26-310 lock restore size guard
parent
a0473016f16e
5 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/views/Lock.test.tsx+56 −0src/popup/views/Lock.tsx+4 −0
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "5.0.77",4+  "version": "5.0.78",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.77",5+  "version": "5.0.78",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.77",3+  "version": "5.0.78",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/popup/views/Lock.test.tsx+56 −0
@@ -55,6 +55,20 @@5555   return view;5656 }5757 58+function renderCsvRestore() {59+  const view = renderView(() => (60+    <LockView61+      hasVault={true}62+      ready={true}63+      onUnlock={() => undefined}64+      initialRestore={true}65+      initialRestoreSource="csv"66+    />67+  ));68+  mounted.push(view);69+  return view;70+}71+5872 beforeEach(() => {5973   mounted = [];6074   mockSendMessage.mockReset();@@ -83,6 +97,48 @@8397   for (const view of mounted) view.dispose();8498   mounted = [];8599   vi.restoreAllMocks();100+});101+102+describe("PB-26-310: lock-screen CSV/JSON paths bound the file before reading it", () => {103+  it("the CSV/JSON picker refuses an oversized file without reading it", async () => {104+    const view = renderCsvRestore();105+    await flushView();106+107+    const picker = view.container.querySelector<HTMLInputElement>(108+      'input[type="file"][accept=".csv,.json,.peach,application/x-peach"]',109+    );110+    expect(picker, "expected a CSV/JSON file input").not.toBeNull();111+112+    const file = oversizedFile("huge.csv");113+    Object.defineProperty(picker!, "files", {114+      configurable: true,115+      get: () => [file],116+    });117+    picker!.dispatchEvent(new Event("change", { bubbles: true }));118+    await flushView();119+120+    expect(file.readCalls).toBe(0);121+    expect(view.container.textContent).toContain("too large");122+  });123+124+  it("CSV/JSON drag/drop refuses an oversized file without reading it", async () => {125+    const view = renderCsvRestore();126+    await flushView();127+128+    const form = view.container.querySelector<HTMLElement>(".peach-restore-form");129+    expect(form, "expected the CSV/JSON restore form").not.toBeNull();130+131+    const file = oversizedFile("huge.json");132+    const drop = new Event("drop", { bubbles: true, cancelable: true }) as Event & {133+      dataTransfer?: { files: File[] };134+    };135+    drop.dataTransfer = { files: [file] };136+    form!.dispatchEvent(drop);137+    await flushView();138+139+    expect(file.readCalls).toBe(0);140+    expect(view.container.textContent).toContain("too large");141+  });86142 });87143 88144 describe("PB-26-208: lock-screen .peach paths bound the file before reading it", () => {

view file

src/popup/views/Lock.tsx+4 −0
@@ -1139,6 +1139,10 @@11391139   // input change) passes gatePassword first, so the file is always accepted11401140   // under a real master password — never an empty key.11411141   async function processFile(file: File) {1142+    if (file.size > IMPORT_LIMITS.MAX_JSON_BYTES) {1143+      setError("That file is too large.");1144+      return;1145+    }11421146     setImporting(true);11431147     setError(null);11441148     try {

view file