Skip to content

Prevent drop malicious content on the editor#3319

Merged
juliaroldi merged 14 commits intomasterfrom
u/juliaroldi/block-drop
Apr 9, 2026
Merged

Prevent drop malicious content on the editor#3319
juliaroldi merged 14 commits intomasterfrom
u/juliaroldi/block-drop

Conversation

@juliaroldi
Copy link
Copy Markdown
Contributor

@juliaroldi juliaroldi commented Apr 7, 2026

Introduced protection to prevent potentially harmful HTML content from being added to the editor through drag-and-drop. The DragAndDrop Plugin was implemented to manage external content drops, block the default drop action, sanitize any dropped content, and insert only the sanitized content into the editor.

@juliaroldi juliaroldi changed the title Prevent Prevent drop Malicious content on the editor Apr 7, 2026
@juliaroldi juliaroldi changed the title Prevent drop Malicious content on the editor Prevent drop malicious content on the editor Apr 7, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a security guardrail for drag-and-drop into the editor by inspecting dropped text/html and attempting to block drops that appear malicious (e.g., forbidden elements or content altered by sanitization).

Changes:

  • Introduces new DropOptions on EditorOptions to configure forbidden elements and whether to prevent potentially malicious dropped HTML.
  • Updates DOMEventPlugin drop handling to inspect/sanitize dropped HTML and prevent the drop under certain conditions.
  • Adds/updates unit tests covering drop-malicious-content prevention scenarios.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
packages/roosterjs-content-model-types/lib/index.ts Exports the newly added DropOptions type.
packages/roosterjs-content-model-types/lib/editor/EditorOptions.ts Adds DropOptions and includes it in EditorOptions.
packages/roosterjs-content-model-core/lib/corePlugin/domEvent/DOMEventPlugin.ts Adds drop-time HTML inspection + prevention logic based on sanitization/forbidden elements.
packages/roosterjs-content-model-core/test/corePlugin/domEvent/DomEventPluginTest.ts Adds tests for the new drop prevention behavior and updates existing drop test to pass an event.
Comments suppressed due to low confidence (1)

packages/roosterjs-content-model-core/lib/corePlugin/domEvent/DOMEventPlugin.ts:164

  • Even when the drop is blocked via preventDefault()/stopPropagation(), the plugin still takes an undo snapshot and fires contentChanged with ChangeSource.Drop. That can trigger downstream drop handlers (e.g., ImageEditPlugin) even though no DOM change occurred. Consider skipping the snapshot/event when e.defaultPrevented is true (or returning early after blocking).
            if (this.editor) {
                this.editor.takeSnapshot();
                this.editor.triggerEvent('contentChanged', {
                    source: ChangeSource.Drop,
                });

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +149 to +153
const sanitizedHTML = this.editor?.getDOMCreator().htmlToDOM(html).body.innerHTML;
if (
sanitizedHTML !== html ||
this.forbiddenDropElement.some(element => html.includes(element))
) {
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.forbiddenDropElement.some(element => html.includes(element)) is a fragile security check: it is case-sensitive and can false-positive on text/attributes (e.g., "giftiframe") and miss <IFRAME> variants. Prefer parsing to DOM and checking actual tag presence (e.g., querySelector for the forbidden tags, case-insensitive/boundary-safe).

Suggested change
const sanitizedHTML = this.editor?.getDOMCreator().htmlToDOM(html).body.innerHTML;
if (
sanitizedHTML !== html ||
this.forbiddenDropElement.some(element => html.includes(element))
) {
const parsedHtml = this.editor?.getDOMCreator().htmlToDOM(html);
const sanitizedHTML = parsedHtml?.body.innerHTML;
const hasForbiddenElement = !!parsedHtml?.body.querySelector(
this.forbiddenDropElement.join(',')
);
if (sanitizedHTML !== html || hasForbiddenElement) {

Copilot uses AI. Check for mistakes.
const doc = this.editor.getDocument();
const html = e.dataTransfer?.getData('text/html');

if (html && this.forbiddenDropElement.length > 0) {
Copy link
Copy Markdown
Contributor

@BryanValverdeU BryanValverdeU Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should still allow the drop to proceed, but set the HTML data transfer with the sanitized content from htmlToDom. The trustedHTMLHandler should already strip the <script> tag from the content. And to be extra safe, we could use querySelector to remove all <iframe> elements from the content before inserting it.

With the current approach, if a user drags and drops HTML that contains both legitimate content and an <iframe>, the entire drop becomes a no-op, we'd lose the non-malicious HTML as well.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree

@juliaroldi juliaroldi merged commit e429c23 into master Apr 9, 2026
7 checks passed
@juliaroldi juliaroldi deleted the u/juliaroldi/block-drop branch April 9, 2026 20:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants