07bf0e84d1db
fix popup port authentication to require extension-page sender origin and reject content scripts (v5.0.73)
- time
- 2026-09-03 22:19 UTC · 1 month ago
- machine
- koh2
- agent
- claude-sonnet-4-6
- session
- PB-26-305 popup port authentication
- parent
- 37b22166bab5
↓ peach-browser-07bf0e84.tar.gzatlas at this save
files changed5 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/entry.lock-port.test.ts+21 −5src/background/entry.ts+3 −0manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "5.0.72",4+ "version": "5.0.73",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.72",5+ "version": "5.0.73",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.72",3+ "version": "5.0.73",44 "description": "Zero-knowledge password manager",55 "license": "GPL-3.0-only",66 "type": "module",src/background/entry.lock-port.test.ts+21 −5
@@ -1,19 +1,21 @@1-// LV-5: the lock-on-close onConnect listener accepts only our nonce'd2-// "popup-<uuid>" port and ignores anything else.1+// The lock-on-close onConnect listener accepts only our nonce'd2+// "popup-<uuid>" port from the extension page and ignores anything else.33 import { beforeEach, describe, expect, it, vi } from "vitest";44 55 type StorageShape = Record<string, unknown>;66 77 interface FakePort {88 name: string;9+ sender?: chrome.runtime.MessageSender;910 onDisconnect: { addListener: (fn: () => void) => void };1011 fireDisconnect: () => Promise<void>;1112 }1213 13-function makePort(name: string): FakePort {14+function makePort(name: string, sender?: chrome.runtime.MessageSender): FakePort {1415 let handler: (() => void) | null = null;1516 return {1617 name,18+ sender,1719 onDisconnect: {1820 addListener: (fn: () => void) => {1921 handler = fn;@@ -45,7 +47,7 @@4547 onMessage: { addListener: vi.fn() },4648 sendMessage: vi.fn(async () => undefined),4749 connect: vi.fn(),48- getURL: vi.fn((p: string) => p),50+ getURL: vi.fn((p: string) => `chrome-extension://test/${p}`),4951 },5052 storage: {5153 local: {@@ -114,11 +116,25 @@114116 const vault = await setupUnlockedVaultWithLockOnClose();115117 expect(connectListeners).toHaveLength(1);116118 117- const port = makePort(`popup-${"a1b2c3"}`);119+ const port = makePort(`popup-${"a1b2c3"}`, { url: "chrome-extension://test/src/popup/index.html" });118120 connectListeners[0](port);119121 await port.fireDisconnect();120122 121123 expect(vault.isUnlocked()).toBe(false);124+ });125+126+ it("ignores a nonce'd popup port from a content script", async () => {127+ await import("./entry");128+ const vault = await setupUnlockedVaultWithLockOnClose();129+130+ const port = makePort(`popup-${"a1b2c3"}`, {131+ url: "https://example.com/",132+ tab: { id: 7 } as chrome.tabs.Tab,133+ });134+ connectListeners[0](port);135+ await port.fireDisconnect();136+137+ expect(vault.isUnlocked()).toBe(true);122138 });123139 124140 it.each(["popup", "evil"])("ignores the non-nonce port name %s", async (name) => {src/background/entry.ts+3 −0
@@ -48,6 +48,9 @@4848 4949 chrome.runtime.onConnect.addListener((port) => {5050 if (!port.name.startsWith(POPUP_PORT_PREFIX)) return;51+ const senderUrl = port.sender?.url;52+ const extensionOrigin = new URL(chrome.runtime.getURL("")).origin;53+ if (port.sender?.tab || !senderUrl || new URL(senderUrl).origin !== extensionOrigin) return;5154 port.onDisconnect.addListener(async () => {5255 await denyAllPendingPasskeyRequests();5356 const s = await ensureSettingsLoaded();