5cd09213d011

fix: standalone notes editor pastes as plain text through the shared handleRichTextPaste guard (PB-26-009) — pasted rich HTML can no longer enter the extension-origin DOM live; guard extracted from the inline editor so both editors share one paste handler; bump 5.0.8→5.0.9

time
2026-08-30 00:16 UTC · 1 month ago
machine
koh2
agent
glm-5.3-flash
session
pb-26 audit fixes
parent
2ee6e8b00769
5 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/views/NoteEditor.tsx+12 −12src/popup/views/NotesEditor.tsx+3 −1
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "5.0.8",4+  "version": "5.0.9",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.8",5+  "version": "5.0.9",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.8",3+  "version": "5.0.9",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/popup/views/NoteEditor.tsx+12 −12
@@ -107,6 +107,17 @@107107   entryKey?: string;108108 }109109 110+// PB-26-009: shared by the inline editor and the standalone notes editor —111+// pasted rich HTML must never enter the extension-origin DOM live. CSP blocks112+// inline script execution, but outbound <img>/<style> beacons remain possible.113+// insertText keeps the body clean and wrapping intact, and fires `input` so114+// autosave handlers persist the change.115+export function handleRichTextPaste(e: ClipboardEvent) {116+  e.preventDefault();117+  const text = e.clipboardData?.getData("text/plain") ?? "";118+  document.execCommand("insertText", false, text);119+}120+110121 export function NoteEditorInline(props: NoteEditorInlineProps) {111122   let editorRef: HTMLDivElement | undefined;112123 @@ -128,17 +139,6 @@128139     editorRef?.focus();129140   }130141 131-  // Paste as plain text. The live editor writes innerHTML directly and only132-  // load-time content is sanitized (toHtml); pasted HTML would otherwise carry133-  // foreign inline styles (white-space:pre kills wrapping) and disallowed tags.134-  // insertText keeps the body clean and wrapping intact, and fires `input` so135-  // onContentChange persists the change.136-  function handlePaste(e: ClipboardEvent) {137-    e.preventDefault();138-    const text = e.clipboardData?.getData("text/plain") ?? "";139-    document.execCommand("insertText", false, text);140-  }141-142142   function handleKeyDown(e: KeyboardEvent) {143143     const mod = e.metaKey || e.ctrlKey;144144     // Only intercept shortcuts for non-alphanumeric keys to avoid swallowing@@ -156,7 +156,7 @@156156         class="peach-note-editor-body peach-note-editor-body--inline"157157         contenteditable="true"158158         onKeyDown={handleKeyDown}159-        onPaste={handlePaste}159+        onPaste={handleRichTextPaste}160160         onInput={() => props.onContentChange?.(editorRef?.innerHTML ?? "")}161161         onBlur={() => props.onBlur?.(editorRef?.innerHTML ?? "")}162162         aria-multiline="true"

view file

src/popup/views/NotesEditor.tsx+3 −1
@@ -2,7 +2,7 @@22 import type { VaultEntry } from "../../shared/types";33 import { sendMessage } from "../../shared/bridge";44 import { NoteToolbar, type ToolbarExec } from "../components/NoteToolbar";5-import { toHtml } from "./NoteEditor";5+import { toHtml, handleRichTextPaste } from "./NoteEditor";66 77 const AUTOSAVE_MS = 800;88 const SAVED_DISPLAY_MS = 1500;@@ -201,6 +201,7 @@201201               bodyRef?.focus();202202             }203203           }}204+          onPaste={handleRichTextPaste}204205           aria-label="Note title"205206         />206207 @@ -221,6 +222,7 @@221222             if (mod && e.key === "i") { e.preventDefault(); exec("italic"); }222223             if (mod && e.key === "u") { e.preventDefault(); exec("underline"); }223224           }}225+          onPaste={handleRichTextPaste}224226           aria-label="Note body"225227           aria-multiline="true"226228         />

view file