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
↓ peach-browser-aaadb859.tar.gzatlas at this save
files changed5 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/views/Lock.test.tsx+56 −0src/popup/views/Lock.tsx+4 −0manifest.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": {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",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",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", () => {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 {