de64511b0d62
fix: PCH-01-007 gate three document-level inject keydown handlers on e.isTrusted — page-synthesised keys can no longer drive inline selection/fill (v3.8.1)
- time
- 2026-06-10 19:44 UTC · 4 months ago
- machine
- koh2
- agent
- claude-opus-4-8
- session
- pch-01 fix integration
- parent
- 0efc6954e26c
↓ peach-browser-de64511b.tar.gzatlas at this save
files changed2 files
src/content/inject.keyguard.test.ts+70 −0src/content/inject.ts+6 −0src/content/inject.keyguard.test.ts+70 −0
@@ -0,0 +1,70 @@1+// @vitest-environment jsdom2+3+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";4+5+// PCH-01-007: the three document-level keydown handlers inside the inline6+// selector must ignore page-synthesised KeyboardEvents (isTrusted === false),7+// so a host page cannot script selection + fill without a real keypress.8+9+describe("PCH-01-007 — inject keydown handlers gate on e.isTrusted", () => {10+ let showAutofillSelector: typeof import("./inject").showAutofillSelector;11+ let cleanup: (() => void) | null = null;12+13+ beforeEach(async () => {14+ vi.resetModules();15+ (globalThis as unknown as { chrome: unknown }).chrome = {16+ runtime: { sendMessage: vi.fn(), getURL: (p: string) => p },17+ };18+ class NoopObserver {19+ observe() {}20+ unobserve() {}21+ disconnect() {}22+ }23+ (globalThis as unknown as { ResizeObserver: unknown }).ResizeObserver = NoopObserver;24+ (globalThis as unknown as { IntersectionObserver: unknown }).IntersectionObserver = NoopObserver;25+ ({ showAutofillSelector } = await import("./inject"));26+ });27+28+ afterEach(() => {29+ cleanup?.();30+ cleanup = null;31+ document.body.innerHTML = "";32+ });33+34+ it("ignores untrusted ArrowDown/Enter — no fill without user intent", () => {35+ const input = document.createElement("input");36+ document.body.appendChild(input);37+ const onSelect = vi.fn();38+ cleanup = showAutofillSelector(39+ input,40+ [41+ { id: "1", title: "GitHub", username: "june", url: "https://github.com" },42+ { id: "2", title: "GitLab", username: "june", url: "https://gitlab.com" },43+ ],44+ onSelect,45+ { domain: "github.com" },46+ );47+48+ // Synthetic events have isTrusted === false in jsdom → handlers no-op.49+ document.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true }));50+ document.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));51+52+ expect(onSelect).not.toHaveBeenCalled();53+ });54+55+ it("ignores untrusted single-character keys (type-to-search handlers no-op)", () => {56+ const input = document.createElement("input");57+ document.body.appendChild(input);58+ const onSelect = vi.fn();59+ cleanup = showAutofillSelector(60+ input,61+ [{ id: "1", title: "GitHub", username: "june", url: "https://github.com" }],62+ onSelect,63+ { domain: "github.com" },64+ );65+66+ // A page-dispatched printable key must not drive selection/fill.67+ document.dispatchEvent(new KeyboardEvent("keydown", { key: "a", bubbles: true }));68+ expect(onSelect).not.toHaveBeenCalled();69+ });70+});src/content/inject.ts+6 −0
@@ -3207,6 +3207,10 @@32073207 }32083208 32093209 function handleKeyDown(e: KeyboardEvent) {3210+ // PCH-01-007: reject page-synthesised KeyboardEvents — only genuine3211+ // keystrokes may drive selection/fill. A page hosting an open selector could3212+ // otherwise dispatch ArrowDown/Enter at `document` to fill without user intent.3213+ if (!e.isTrusted) return;32103214 if (options.locked) {32113215 if (e.key === "Escape") { e.preventDefault(); dismiss(true); }32123216 return;@@ -3251,6 +3255,7 @@32513255 // clicking the search icon. Capture phase so the anchor input does not32523256 // receive the character before we hijack it.32533257 const silverPlatterTypeHandler = (e: KeyboardEvent) => {3258+ if (!e.isTrusted) return; // PCH-01-007: ignore page-synthesised keys32543259 if (currentMode !== "silver-platter") return;32553260 if (e.ctrlKey || e.metaKey || e.altKey) return;32563261 if (e.key.length !== 1) return;@@ -3265,6 +3270,7 @@32653270 cleanupHooks.push(() => document.removeEventListener("keydown", silverPlatterTypeHandler, true));32663271 32673272 const identityHelperTypeHandler = (e: KeyboardEvent) => {3273+ if (!e.isTrusted) return; // PCH-01-007: ignore page-synthesised keys32683274 if (currentMode !== "identity-helper") return;32693275 if (e.ctrlKey || e.metaKey || e.altKey) return;32703276 const active = (shadow.activeElement ?? document.activeElement) as Element | null;