From 6e7a63ac74052f64b474ecf5abcffa70ae269410 Mon Sep 17 00:00:00 2001 From: KozakLordOfMatrix Date: Tue, 7 Jul 2026 18:00:39 +0300 Subject: [PATCH] fix(security): sanitize smuggled html in disconnected elements and check location existence --- src/core/helpers/html/safe-html.ts | 5 +++-- src/plugins/clean-html/clean-html.test.js | 22 ++++++++++++++++++++++ test/loader.js | 2 +- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/core/helpers/html/safe-html.ts b/src/core/helpers/html/safe-html.ts index 15702a6d5..44543bc17 100644 --- a/src/core/helpers/html/safe-html.ts +++ b/src/core/helpers/html/safe-html.ts @@ -78,7 +78,7 @@ export function safeHTML( const foreign = box.querySelectorAll('math *, svg *'); for (let i = 0; i < foreign.length; i++) { - if (foreign[i].isConnected && isSmuggledForeignHtml(foreign[i])) { + if (box.contains(foreign[i]) && isSmuggledForeignHtml(foreign[i])) { Dom.safeRemove(foreign[i]); } } @@ -219,7 +219,8 @@ export function sanitizeHTMLElement( // scheme (e.g. `java\tscript:`) — all of which the browser still resolves to // `javascript:` on click. See GHSA-j839-gqq4-gf9j. if (safeJavaScriptLink && href && isDangerousUrl(href, tagName)) { - attr(elm, 'href', location.protocol + '//' + href); + const protocol = typeof location !== 'undefined' ? location.protocol : 'http:'; + attr(elm, 'href', protocol + '//' + href); effected = true; } diff --git a/src/plugins/clean-html/clean-html.test.js b/src/plugins/clean-html/clean-html.test.js index 0d02ccba1..1ab9efa3a 100644 --- a/src/plugins/clean-html/clean-html.test.js +++ b/src/plugins/clean-html/clean-html.test.js @@ -832,6 +832,28 @@ describe('Clean html plugin', function () { }); }); + describe('smuggled HTML in MathML/SVG', function () { + it('Should remove HTML smuggled into MathML or SVG when parsed in disconnected fragment', function () { + const editor = getJodit({ + cleanHTML: { useIframeSandbox: false } + }); + editor.value = ''; + expect(editor.value).does.not.contain('onload'); + expect(editor.value).does.not.contain('body'); + }); + }); + + describe('smuggled HTML in MathML/SVG', function () { + it('Should remove HTML smuggled into MathML or SVG when parsed in disconnected fragment', function () { + const editor = getJodit({ + cleanHTML: { useIframeSandbox: false } + }); + editor.value = ''; + expect(editor.value).does.not.contain('onload'); + expect(editor.value).does.not.contain('body'); + }); + }); + describe('denyTags (default: script,iframe,object,embed)', function () { ['script', 'iframe', 'object', 'embed'].forEach(function (tag) { it('Should remove <' + tag + '> by default', function (done) { diff --git a/test/loader.js b/test/loader.js index 8d47350f2..8279b22f9 100644 --- a/test/loader.js +++ b/test/loader.js @@ -1,2 +1,2 @@ // prettier-ignore -window.fileCasesJodit = ["./tests/acceptance/uploader-before-upload.test.js","./tests/acceptance/states.test.js","./tests/acceptance/shadow-dom.test.js","./tests/acceptance/security.test.js","./tests/acceptance/owner-window.test.js","./tests/acceptance/interface.test.js","./tests/acceptance/inline-mode.test.js","./tests/acceptance/image.test.js","./tests/acceptance/image-editor-save-event.test.js","./tests/acceptance/editor.test.js","./tests/acceptance/editor-ready.test.js","./tests/acceptance/commands.test.js","../src/jodit.test.js","../src/styles/slots.test.js","../src/plugins/plugins.test.js","../src/plugins/xpath/xpath.test.js","../src/plugins/wrap-nodes/wrap-nodes.test.js","../src/plugins/video/video.test.js","../src/plugins/table/table.test.js","../src/plugins/tab/tab.test.js","../src/plugins/symbols/symbols.test.js","../src/plugins/sticky/sticky.test.js","../src/plugins/stat/stat.test.js","../src/plugins/speech-recognize/speech-recognize.test.js","../src/plugins/source/source.test.js","../src/plugins/size/size.test.js","../src/plugins/select-cells/select-cells.test.js","../src/plugins/select/select.test.js","../src/plugins/search/search.test.js","../src/plugins/resizer/resizer.test.js","../src/plugins/resize-handler/resize-handler.test.js","../src/plugins/resize-cells/resize-cells.test.js","../src/plugins/preview/preview.test.js","../src/plugins/placeholder/placeholder.test.js","../src/plugins/paste-storage/paste-storage.test.js","../src/plugins/paste/paste.test.js","../src/plugins/ordered-list/ordered-list.test.js","../src/plugins/mobile/mobile.test.js","../src/plugins/link/link.test.js","../src/plugins/line-height/line-height.test.js","../src/plugins/limit/limit.test.js","../src/plugins/inline-popup/inline-popup.test.js","../src/plugins/indent/indent.test.js","../src/plugins/image-properties/image-properties.test.js","../src/plugins/image-processor/image-processor.test.js","../src/plugins/image/image.test.js","../src/plugins/iframe/iframe.test.js","../src/plugins/hr/hr.test.js","../src/plugins/hotkeys/hotkeys.test.js","../src/plugins/fullsize/fullsize.test.js","../src/plugins/format-block/format-block.test.js","../src/plugins/font/font.test.js","../src/plugins/focus/focus.test.js","../src/plugins/enter/enter.test.js","../src/plugins/dtd/dtd.test.js","../src/plugins/drag-and-drop-element/drag-and-drop-element.test.js","../src/plugins/drag-and-drop/drag-and-drop.test.js","../src/plugins/delete/delete.test.js","../src/plugins/copy-format/copy-format.test.js","../src/plugins/color/color.test.js","../src/plugins/clipboard/clipboard.test.js","../src/plugins/clean-html/clean-html.test.js","../src/plugins/class-span/class-span.test.js","../src/plugins/bold/bold.test.js","../src/plugins/backspace/backspace.test.js","../src/plugins/ai-assistant/ai-assistant.test.js","../src/plugins/add-new-line/add-new-line.test.js","../src/modules/uploader/uploader.test.js","../src/modules/toolbar/toolbar.test.js","../src/modules/table/table.test.js","../src/modules/messages/messages.test.js","../src/modules/image-editor/image-editor.test.js","../src/modules/history/history.test.js","../src/modules/file-browser/file-browser.test.js","../src/modules/dialog/dialog.test.js","../src/langs/i18n.test.js","../src/core/ui/icon.test.js","../src/core/ui/accessibility.test.js","../src/core/ui/progress-bar/progress-bar.test.js","../src/core/ui/popup/popup.test.js","../src/core/ui/group/group.test.js","../src/core/ui/form/ui.form.test.js","../src/core/ui/form/inputs/input/input.test.js","../src/core/ui/button/tooltip/tooltip.test.js","../src/core/ui/button/button/button.test.js","../src/core/storage/storage.test.js","../src/core/storage/async-storage.test.js","../src/core/selection/selection.test.js","../src/core/selection/style/style.test.js","../src/core/request/ajax.test.js","../src/core/plugin/plugin.test.js","../src/core/helpers/helpers.test.js","../src/core/helpers/size/position.test.js","../src/core/helpers/array/array.helpers.test.js","../src/core/event-emitter/object-observer.test.js","../src/core/event-emitter/event-emitter.test.js","../src/core/dom/dom.test.js","../src/core/decorators/decorators.test.js","../src/core/create/create.test.js","../src/core/async/async.test.js"]; \ No newline at end of file +window.fileCasesJodit = ["./tests/acceptance/uploader-before-upload.test.js","./tests/acceptance/states.test.js","./tests/acceptance/shadow-dom.test.js","./tests/acceptance/security.test.js","./tests/acceptance/owner-window.test.js","./tests/acceptance/interface.test.js","./tests/acceptance/inline-mode.test.js","./tests/acceptance/image.test.js","./tests/acceptance/image-editor-save-event.test.js","./tests/acceptance/editor.test.js","./tests/acceptance/editor-ready.test.js","./tests/acceptance/commands.test.js","../src/jodit.test.js","../src/styles/slots.test.js","../src/plugins/plugins.test.js","../src/plugins/xpath/xpath.test.js","../src/plugins/wrap-nodes/wrap-nodes.test.js","../src/plugins/video/video.test.js","../src/plugins/table/table.test.js","../src/plugins/tab/tab.test.js","../src/plugins/symbols/symbols.test.js","../src/plugins/sticky/sticky.test.js","../src/plugins/stat/stat.test.js","../src/plugins/speech-recognize/speech-recognize.test.js","../src/plugins/source/source.test.js","../src/plugins/size/size.test.js","../src/plugins/select-cells/select-cells.test.js","../src/plugins/select/select.test.js","../src/plugins/search/search.test.js","../src/plugins/resizer/resizer.test.js","../src/plugins/resize-handler/resize-handler.test.js","../src/plugins/resize-cells/resize-cells.test.js","../src/plugins/preview/preview.test.js","../src/plugins/placeholder/placeholder.test.js","../src/plugins/paste-storage/paste-storage.test.js","../src/plugins/paste/paste.test.js","../src/plugins/ordered-list/ordered-list.test.js","../src/plugins/mobile/mobile.test.js","../src/plugins/link/link.test.js","../src/plugins/line-height/line-height.test.js","../src/plugins/limit/limit.test.js","../src/plugins/inline-popup/inline-popup.test.js","../src/plugins/indent/indent.test.js","../src/plugins/image-properties/image-properties.test.js","../src/plugins/image-processor/image-processor.test.js","../src/plugins/image/image.test.js","../src/plugins/iframe/iframe.test.js","../src/plugins/hr/hr.test.js","../src/plugins/hotkeys/hotkeys.test.js","../src/plugins/fullsize/fullsize.test.js","../src/plugins/format-block/format-block.test.js","../src/plugins/font/font.test.js","../src/plugins/focus/focus.test.js","../src/plugins/enter/enter.test.js","../src/plugins/dtd/dtd.test.js","../src/plugins/drag-and-drop-element/drag-and-drop-element.test.js","../src/plugins/drag-and-drop/drag-and-drop.test.js","../src/plugins/delete/delete.test.js","../src/plugins/copy-format/copy-format.test.js","../src/plugins/color/color.test.js","../src/plugins/clipboard/clipboard.test.js","../src/plugins/clean-html/clean-html.test.js","../src/plugins/class-span/class-span.test.js","../src/plugins/bold/bold.test.js","../src/plugins/backspace/backspace.test.js","../src/plugins/ai-assistant/ai-assistant.test.js","../src/plugins/add-new-line/add-new-line.test.js","../src/modules/uploader/uploader.test.js","../src/modules/toolbar/toolbar.test.js","../src/modules/table/table.test.js","../src/modules/messages/messages.test.js","../src/modules/image-editor/image-editor.test.js","../src/modules/history/history.test.js","../src/modules/file-browser/file-browser.test.js","../src/modules/dialog/dialog.test.js","../src/langs/i18n.test.js","../src/core/ui/icon.test.js","../src/core/ui/accessibility.test.js","../src/core/ui/progress-bar/progress-bar.test.js","../src/core/ui/popup/popup.test.js","../src/core/ui/group/group.test.js","../src/core/ui/form/ui.form.test.js","../src/core/ui/form/inputs/input/input.test.js","../src/core/ui/button/tooltip/tooltip.test.js","../src/core/ui/button/button/button.test.js","../src/core/storage/storage.test.js","../src/core/storage/async-storage.test.js","../src/core/selection/selection.test.js","../src/core/selection/style/style.test.js","../src/core/request/ajax.test.js","../src/core/plugin/plugin.test.js","../src/core/helpers/helpers.test.js","../src/core/helpers/utils/scroll-into-view.test.js","../src/core/helpers/size/position.test.js","../src/core/helpers/array/array.helpers.test.js","../src/core/event-emitter/object-observer.test.js","../src/core/event-emitter/event-emitter.test.js","../src/core/dom/dom.test.js","../src/core/decorators/decorators.test.js","../src/core/create/create.test.js","../src/core/async/async.test.js"]; \ No newline at end of file