32a6e4e07185

bind manual summon recall to attested sender domain with bounded payload validation (PB-26-401, v5.0.102)

time
2026-09-05 03:36 UTC · 1 month ago
machine
koh2
agent
claude-sonnet-4-6
session
summon domain validation
parent
5b4ed4f90c88
5 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/messages.test.ts+26 −0src/background/messages.ts+14 −1
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "5.0.101",4+  "version": "5.0.102",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.101",5+  "version": "5.0.102",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.101",3+  "version": "5.0.102",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/background/messages.test.ts+26 −0
@@ -198,6 +198,32 @@198198     });199199   });200200 201+  it("RECORD_MANUAL_SUMMON binds and bounds the sender domain", async () => {202+    const sender = {203+      id: "peach-extension",204+      url: "https://login.example.com/form",205+      origin: "https://login.example.com",206+      tab: { id: 1 },207+      frameId: 0,208+    } as chrome.runtime.MessageSender;209+210+    await expect(dispatch({211+      type: "RECORD_MANUAL_SUMMON",212+      payload: { domain: "evil.example" },213+    }, sender)).resolves.toEqual({ ok: false, error: "invalid_domain" });214+    await expect(dispatch({215+      type: "RECORD_MANUAL_SUMMON",216+      payload: { domain: "example.com" },217+    }, sender)).resolves.toEqual({ ok: true });218+    await expect(dispatch({219+      type: "RECORD_MANUAL_SUMMON",220+      payload: { domain: "a".repeat(254) },221+    }, { ...sender, origin: "https://a.com", url: "https://a.com/form" })).resolves.toEqual({222+      ok: false,223+      error: "invalid_domain",224+    });225+  });226+201227   it("GET_ENTRIES when unlocked returns the entry array", async () => {202228     const vault = await import("./vault");203229     await vault.createVault("correct-pass-12345");

view file

src/background/messages.ts+14 −1
@@ -1329,7 +1329,20 @@13291329         }13301330       }13311331       case "RECORD_MANUAL_SUMMON": {1332-        const { domain } = msg.payload as { domain: string };1332+        const { domain } = (msg.payload ?? {}) as { domain?: unknown };1333+        const origin = attestedSenderOrigin(sender);1334+        let senderDomain: string | null = null;1335+        if (origin) {1336+          try {1337+            const hostname = new URL(origin).hostname;1338+            senderDomain = extractDomain(hostname) ?? hostname;1339+          } catch {1340+            senderDomain = null;1341+          }1342+        }1343+        if (typeof domain !== "string" || domain.length > 253 || domain !== senderDomain) {1344+          return { ok: false, error: "invalid_domain" };1345+        }13331346         const settings = vault.getSettings();13341347         const existing = settings.manualSummonDomains?.[domain];13351348         const now = Date.now();

view file