fix(editor): strip event handler attributes from pasted HTML
Stacked on #3840.
Hardens the paste sanitizer flagged by code scanning alert #312 (js/xss).
sanitizePastedHtml skips its attribute allowlist when the pasted HTML contains the editor's node-* class marker, so intra-editor copy/paste keeps class, style and data-*. Anyone can put that marker in HTML they serve, and then event handler attributes went through the sanitizer too:
<div class="node-container" onclick="alert(1)"><img src="..." onerror="alert(2)"></div>
This wasn't exploitable: the sanitized HTML goes through Tiptap's generateJSON, and the schema keeps only declared attributes. Running the payload above through sanitizer → generateJSON → editor → composeReactEmail left no on* attribute in the JSON, the editor HTML or the exported email. But the sanitizer shouldn't rely on the schema for that, so it now strips them itself.
Changes
utils/paste-sanitizer.ts: newremoveEventHandlerAttributesstep that always runs, next toremoveForbiddenElementsandscrubUnsafeUrlAttributes. No editor extension declares an attribute starting withon, so nothing that round-trips today is affected..changeset/editor-strip-paste-event-handlers.md: patch for@react-email/editor.
The flagged line itself (DOMParser.parseFromString) builds an inert document where scripts and handlers don't run, so CodeQL may keep reporting it; that part is a false positive.
Testing
- New test, written first and failing on the base: HTML with the editor marker plus
onclick/onMouseOver/onerrorcomes out with noon*attributes and with itsnode-*classes kept. - Editor suite 541/541 (unit + browser, including the HTML paste integration tests).
pnpm build,pnpm typecheckand biome pass.
Summary by cubic
Hardens the paste sanitizer so event handler attributes (onclick, onerror, ...) are stripped from pasted HTML even when it carries the editor's node-* class marker. Previously that marker made the sanitizer skip its attribute allowlist, letting on* attributes through; although Tiptap's schema filtered them later, the sanitizer now removes them directly.
Notes
- Code scanning may keep flagging the
DOMParser.parseFromStringline; that finding is a false positive because the parser builds an inert document where scripts and handlers don't run. - No editor extension declares an attribute starting with
on, so intra-editor copy/paste round-trips are unaffected.