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
2 files src/content/inject.keyguard.test.ts+70 −0src/content/inject.ts+6 −0
src/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+});

view file

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;

view file