759ec348c2cb

test: align sender fixtures with fail-closed URL policy (PB-26-115, v5.0.35)

time
2026-09-01 20:48 UTC · 1 month ago
machine
koh2
agent
gpt-5
session
2026-09-01 security assessment remediation
parent
74c52ffb68e6
9 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/messages.adversarial.test.ts+10 −7src/background/messages.test.ts+19 −10src/background/notes.test.ts+5 −1src/background/recovery-words.test.ts+5 −1src/background/recovery.adversarial.test.ts+5 −1src/background/vault.adversarial.test.ts+5 −1
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "5.0.34",4+  "version": "5.0.35",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.34",5+  "version": "5.0.35",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.34",3+  "version": "5.0.35",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/background/messages.adversarial.test.ts+10 −7
@@ -158,7 +158,10 @@158158 159159   async function dispatch(160160     message: Message,161-    sender: chrome.runtime.MessageSender = { id: "peach-extension" } as chrome.runtime.MessageSender,161+    sender: chrome.runtime.MessageSender = {162+      id: "peach-extension",163+      url: "chrome-extension://peach-extension/popup.html",164+    } as chrome.runtime.MessageSender,162165   ): Promise<MessageResponse> {163166     if (!listener) throw new Error("listener not registered");164167     return new Promise<MessageResponse>((resolve) => {@@ -345,7 +348,7 @@345348       expect(r.error).toBe("invalid_sender");346349     });347350 348-    it("sender with no id (extension UI) is accepted", async () => {351+    it("sender with no URL is rejected", async () => {349352       if (!listener) throw new Error("listener not registered");350353       await setupUnlockedVault();351354       const r = await new Promise<MessageResponse>((resolve) => {@@ -355,7 +358,7 @@355358           resolve356359         );357360       });358-      expect(r.ok).toBe(true);361+      expect(r).toMatchObject({ ok: false, error: "invalid_sender" });359362     });360363   });361364 @@ -569,10 +572,10 @@569572         url: "https://github.com",570573         type: "login",571574       });572-      const r = await dispatch({573-        type: "GET_ENTRY_FOR_FILL",574-        payload: { id: e.id },575-      });575+      const r = await dispatch(576+        { type: "GET_ENTRY_FOR_FILL", payload: { id: e.id } },577+        { id: "peach-extension", url: "https://github.com/login" },578+      );576579       expect(r.ok).toBe(true);577580       const data = (r as { data: Record<string, unknown> }).data;578581       expect("passkey" in data).toBe(false);

view file

src/background/messages.test.ts+19 −10
@@ -135,7 +135,10 @@135135 136136   async function dispatch(137137     message: Message,138-    sender: chrome.runtime.MessageSender = { id: "peach-extension" }138+    sender: chrome.runtime.MessageSender = {139+      id: "peach-extension",140+      url: "chrome-extension://peach-extension/popup.html",141+    }139142   ): Promise<MessageResponse> {140143     return await new Promise<MessageResponse>((resolve) => {141144       const keepChannelOpen = listener(message, sender, (response) => resolve(response));@@ -317,10 +320,10 @@317320       url: "https://github.com",318321     });319322 320-    const response = await dispatch({321-      type: "GET_ENTRY_FOR_FILL",322-      payload: { id: entry.id },323-    });323+    const response = await dispatch(324+      { type: "GET_ENTRY_FOR_FILL", payload: { id: entry.id } },325+      { id: "peach-extension", url: "https://github.com/login" },326+    );324327 325328     expect(response).toMatchObject({326329       ok: true,@@ -602,7 +605,7 @@602605     breachSpy.mockRestore();603606   });604607 605-  it("messages from a different extension id are rejected while undefined sender.id is allowed", async () => {608+  it("messages from foreign or URL-less senders are rejected", async () => {606609     const vault = await import("./vault");607610     await vault.createVault("correct-pass-12345");608611 @@ -612,8 +615,8 @@612615     });613616 614617     await expect(dispatch({ type: "GET_ENTRIES" }, {})).resolves.toMatchObject({615-      ok: true,616-      data: [],618+      ok: false,619+      error: "invalid_sender",617620     });618621   });619622 @@ -887,7 +890,10 @@887890       totp: "JBSWY3DPEHPK3PXP",888891     });889892 890-    const res = await dispatch({ type: "GET_ENTRY_FOR_FILL", payload: { id: e.id } });893+    const res = await dispatch(894+      { type: "GET_ENTRY_FOR_FILL", payload: { id: e.id } },895+      { id: "peach-extension", url: "https://mail.test/login" },896+    );891897     expect(res.ok).toBe(true);892898     const data = res.data as { totp?: string; totpCode?: string; totpRemaining?: number };893899     expect(data.totp).toBeUndefined();@@ -907,7 +913,10 @@907913     });908914     await vault.updateEntry(e.id, { password: "new-pw" });909915 910-    const fill = await dispatch({ type: "GET_ENTRY_FOR_FILL", payload: { id: e.id } });916+    const fill = await dispatch(917+      { type: "GET_ENTRY_FOR_FILL", payload: { id: e.id } },918+      { id: "peach-extension", url: "https://bank.test/login" },919+    );911920     expect(fill.ok).toBe(true);912921     // Passive autofill never receives prior passwords (blast-radius containment).913922     expect((fill.data as Record<string, unknown>).passwordHistory).toBeUndefined();

view file

src/background/notes.test.ts+5 −1
@@ -27,6 +27,7 @@2727     const chromeMock = {2828       runtime: {2929         id: 'peach-extension',30+        getURL: (path = '') => `chrome-extension://peach-extension/${path}`,3031         onMessage: {3132           addListener: vi.fn((fn: MessageListener) => { listener = fn; }),3233         },@@ -87,7 +88,10 @@8788 8889   async function dispatch(message: Message): Promise<MessageResponse> {8990     return new Promise<MessageResponse>((resolve) => {90-      const keepOpen = listener(message, { id: 'peach-extension' }, resolve);91+      const keepOpen = listener(message, {92+        id: 'peach-extension',93+        url: 'chrome-extension://peach-extension/popup.html',94+      }, resolve);9195       expect(keepOpen).toBe(true);9296     });9397   }

view file

src/background/recovery-words.test.ts+5 −1
@@ -35,6 +35,7 @@3535     const chromeMock = {3636       runtime: {3737         id: 'peach-extension',38+        getURL: (path = '') => `chrome-extension://peach-extension/${path}`,3839         onMessage: { addListener: vi.fn((fn: MessageListener) => { listener = fn; }) },3940         sendMessage: vi.fn(async () => ({ ok: true })),4041       },@@ -93,7 +94,10 @@9394 9495   async function dispatch(message: Message): Promise<MessageResponse> {9596     return new Promise<MessageResponse>((resolve) => {96-      const keepOpen = listener(message, { id: 'peach-extension' }, resolve);97+      const keepOpen = listener(message, {98+        id: 'peach-extension',99+        url: 'chrome-extension://peach-extension/popup.html',100+      }, resolve);97101       expect(keepOpen).toBe(true);98102     });99103   }

view file

src/background/recovery.adversarial.test.ts+5 −1
@@ -39,6 +39,7 @@3939     const chromeMock = {4040       runtime: {4141         id: "peach-extension",42+        getURL: (path = "") => `chrome-extension://peach-extension/${path}`,4243         onMessage: {4344           addListener: vi.fn((fn: MessageListener) => {4445             listener = fn;@@ -120,7 +121,10 @@120121     return new Promise<MessageResponse>((resolve) => {121122       const keepOpen = listener!(122123         message,123-        { id: "peach-extension" } as chrome.runtime.MessageSender,124+        {125+          id: "peach-extension",126+          url: "chrome-extension://peach-extension/popup.html",127+        } as chrome.runtime.MessageSender,124128         resolve125129       );126130       expect(keepOpen).toBe(true);

view file

src/background/vault.adversarial.test.ts+5 −1
@@ -56,6 +56,7 @@5656   return {5757     runtime: {5858       id: "peach-extension",59+      getURL: (path = "") => `chrome-extension://peach-extension/${path}`,5960       onMessage: {6061         addListener: vi.fn((fn: MessageListener) => {6162           listenerSink.listener = fn;@@ -160,7 +161,10 @@160161     return new Promise<MessageResponse>((resolve) => {161162       const keepOpen = sink.listener!(162163         message,163-        { id: "peach-extension" } as chrome.runtime.MessageSender,164+        {165+          id: "peach-extension",166+          url: "chrome-extension://peach-extension/popup.html",167+        } as chrome.runtime.MessageSender,164168         resolve165169       );166170       expect(keepOpen).toBe(true);

view file