From cca27f58359241850201df73c74ca8a3ab27bd2f Mon Sep 17 00:00:00 2001 From: Sam Clarke Date: Thu, 22 Oct 2026 19:31:59 +0100 Subject: [PATCH] Fix toBBCode not sanitising HTML --- src/formats/bbcode.js | 5 ++- src/formats/xhtml.js | 2 +- src/lib/SCEditor.js | 3 ++ tests/unit/formats/bbcode.js | 16 ++++++-- tests/unit/formats/bbcode/matching.js | 56 ++++++++++++++++++++------- tests/unit/formats/bbcode/nesting.js | 4 +- tests/unit/lib/SCEditor.js | 42 ++++++++++++++++++++ 7 files changed, 107 insertions(+), 21 deletions(-) diff --git a/src/formats/bbcode.js b/src/formats/bbcode.js index 4a25c2f5..d080d807 100644 --- a/src/formats/bbcode.js +++ b/src/formats/bbcode.js @@ -2466,6 +2466,8 @@ return toBBCode(element, hasCodeParent); }; + var sanitize; + /** * Initializer * @private @@ -2484,6 +2486,7 @@ // Add BBCode helper methods this.toBBCode = base.toSource; this.fromBBCode = base.toHtml; + sanitize = this.__UNSTABLE__sanitize; }; /** @@ -2521,7 +2524,7 @@ var container = context.createElement('div'); var parser = new BBCodeParser(base.opts.parserOptions); - container.innerHTML = html; + container.innerHTML = sanitize(html); css(containerParent, 'visibility', 'hidden'); containerParent.appendChild(container); context.body.appendChild(containerParent); diff --git a/src/formats/xhtml.js b/src/formats/xhtml.js index 38358ef9..87828c00 100644 --- a/src/formats/xhtml.js +++ b/src/formats/xhtml.js @@ -575,7 +575,7 @@ base.toSource = toSource.bind(null, false); - base.fragmentToSource = toSource.bind(null, true);; + base.fragmentToSource = toSource.bind(null, true); /** * Runs all converters for the specified tagName diff --git a/src/lib/SCEditor.js b/src/lib/SCEditor.js index 71a5b0d5..bdda38b6 100644 --- a/src/lib/SCEditor.js +++ b/src/lib/SCEditor.js @@ -434,6 +434,9 @@ export default function SCEditor(original, userOptions) { }); }; + // eslint-disable-next-line camelcase + base.__UNSTABLE__sanitize = sanitize; + /** * Creates the editor iframe and textarea * @private diff --git a/tests/unit/formats/bbcode.js b/tests/unit/formats/bbcode.js index b382d899..c6c94be1 100644 --- a/tests/unit/formats/bbcode.js +++ b/tests/unit/formats/bbcode.js @@ -6,7 +6,9 @@ import 'src/formats/bbcode.js'; QUnit.module('formats/bbcode', { beforeEach: function () { this.mockEditor = { - opts: $.extend({}, defaultOptions) + opts: $.extend({}, defaultOptions), + // eslint-disable-next-line camelcase + __UNSTABLE__sanitize: x => x }; this.format = new sceditor.formats.bbcode(); @@ -95,7 +97,9 @@ QUnit.test('From BBCode method as fragment', function (assert) { QUnit.test('BBcode to HTML trim', function (assert) { this.mockEditor = { - opts: $.extend({}, defaultOptions, { bbcodeTrim: true }) + opts: $.extend({}, defaultOptions, { bbcodeTrim: true }), + // eslint-disable-next-line camelcase + __UNSTABLE__sanitize: x => x }; this.format = new sceditor.formats.bbcode(); @@ -122,7 +126,9 @@ QUnit.test('BBcode to HTML trim', function (assert) { QUnit.test('HTML to BBCode trim', function (assert) { this.mockEditor = { - opts: $.extend({}, defaultOptions, { bbcodeTrim: true }) + opts: $.extend({}, defaultOptions, { bbcodeTrim: true }), + // eslint-disable-next-line camelcase + __UNSTABLE__sanitize: x => x }; this.format = new sceditor.formats.bbcode(); @@ -152,7 +158,9 @@ QUnit.test('HTML to BBCode trim', function (assert) { QUnit.module('formats/bbcode - HTML to BBCode', { beforeEach: function () { this.mockEditor = { - opts: $.extend({}, defaultOptions) + opts: $.extend({}, defaultOptions), + // eslint-disable-next-line camelcase + __UNSTABLE__sanitize: x => x }; this.format = new sceditor.formats.bbcode(); diff --git a/tests/unit/formats/bbcode/matching.js b/tests/unit/formats/bbcode/matching.js index 2d0a5d97..abf57d05 100644 --- a/tests/unit/formats/bbcode/matching.js +++ b/tests/unit/formats/bbcode/matching.js @@ -16,7 +16,9 @@ QUnit.test('Should match only if all attributes match when strict matching', fun }); var mockEditor = { - opts: defaultOptions + opts: defaultOptions, + // eslint-disable-next-line camelcase + __UNSTABLE__sanitize: x => x }; (new sceditor.formats.bbcode()).init.call(mockEditor); @@ -58,7 +60,9 @@ QUnit.test('Should match if any attributes match when not strict matching', func }); var mockEditor = { - opts: defaultOptions + opts: defaultOptions, + // eslint-disable-next-line camelcase + __UNSTABLE__sanitize: x => x }; (new sceditor.formats.bbcode()).init.call(mockEditor); @@ -100,7 +104,9 @@ QUnit.test('Should default to matching if any attributes match', function (asser }); var mockEditor = { - opts: defaultOptions + opts: defaultOptions, + // eslint-disable-next-line camelcase + __UNSTABLE__sanitize: x => x }; (new sceditor.formats.bbcode()).init.call(mockEditor); @@ -144,7 +150,9 @@ QUnit.test('Should match twice if tag and styles match', function (assert) { }); var mockEditor = { - opts: defaultOptions + opts: defaultOptions, + // eslint-disable-next-line camelcase + __UNSTABLE__sanitize: x => x }; (new sceditor.formats.bbcode()).init.call(mockEditor); @@ -169,7 +177,9 @@ QUnit.test('Should only match if all styles match when strict matching', functio }); var mockEditor = { - opts: defaultOptions + opts: defaultOptions, + // eslint-disable-next-line camelcase + __UNSTABLE__sanitize: x => x }; (new sceditor.formats.bbcode()).init.call(mockEditor); @@ -197,7 +207,9 @@ QUnit.test('Should match if any styles match when not strict matching', function }); var mockEditor = { - opts: defaultOptions + opts: defaultOptions, + // eslint-disable-next-line camelcase + __UNSTABLE__sanitize: x => x }; (new sceditor.formats.bbcode()).init.call(mockEditor); @@ -230,7 +242,9 @@ QUnit.test('Should default to matching if any styles match', function (assert) { }); var mockEditor = { - opts: defaultOptions + opts: defaultOptions, + // eslint-disable-next-line camelcase + __UNSTABLE__sanitize: x => x }; (new sceditor.formats.bbcode()).init.call(mockEditor); @@ -262,7 +276,9 @@ QUnit.test('Should support matching wildcard tags', function (assert) { }); var mockEditor = { - opts: defaultOptions + opts: defaultOptions, + // eslint-disable-next-line camelcase + __UNSTABLE__sanitize: x => x }; (new sceditor.formats.bbcode()).init.call(mockEditor); @@ -289,7 +305,9 @@ QUnit.test('Should wildcard matching styles or attributes', function (assert) { }); var mockEditor = { - opts: defaultOptions + opts: defaultOptions, + // eslint-disable-next-line camelcase + __UNSTABLE__sanitize: x => x }; (new sceditor.formats.bbcode()).init.call(mockEditor); @@ -318,7 +336,9 @@ QUnit.test('Should only match when styles and attributes match if strict matchin }); var mockEditor = { - opts: defaultOptions + opts: defaultOptions, + // eslint-disable-next-line camelcase + __UNSTABLE__sanitize: x => x }; (new sceditor.formats.bbcode()).init.call(mockEditor); @@ -355,7 +375,9 @@ QUnit.test('Should match when any style or attribute matches if not strict match }); var mockEditor = { - opts: defaultOptions + opts: defaultOptions, + // eslint-disable-next-line camelcase + __UNSTABLE__sanitize: x => x }; (new sceditor.formats.bbcode()).init.call(mockEditor); @@ -390,7 +412,9 @@ QUnit.test('Should support style attribute', function (assert) { }); var mockEditor = { - opts: defaultOptions + opts: defaultOptions, + // eslint-disable-next-line camelcase + __UNSTABLE__sanitize: x => x }; (new sceditor.formats.bbcode()).init.call(mockEditor); @@ -414,7 +438,9 @@ QUnit.test('Should match null style only if style attribute is specified', funct }); var mockEditor = { - opts: defaultOptions + opts: defaultOptions, + // eslint-disable-next-line camelcase + __UNSTABLE__sanitize: x => x }; (new sceditor.formats.bbcode()).init.call(mockEditor); @@ -449,7 +475,9 @@ QUnit.test('Should match styles first', function (assert) { }); var mockEditor = { - opts: defaultOptions + opts: defaultOptions, + // eslint-disable-next-line camelcase + __UNSTABLE__sanitize: x => x }; (new sceditor.formats.bbcode()).init.call(mockEditor); diff --git a/tests/unit/formats/bbcode/nesting.js b/tests/unit/formats/bbcode/nesting.js index a3f81eb9..2192850f 100644 --- a/tests/unit/formats/bbcode/nesting.js +++ b/tests/unit/formats/bbcode/nesting.js @@ -48,7 +48,9 @@ QUnit.test('inline bbcodes must be inside block ones', assert => { ); var mockEditor = { - opts: defaultOptions + opts: defaultOptions, + // eslint-disable-next-line camelcase + __UNSTABLE__sanitize: x => x }; var format = new sceditor.formats.bbcode; format.init.call(mockEditor); diff --git a/tests/unit/lib/SCEditor.js b/tests/unit/lib/SCEditor.js index 4e26f925..4389fb82 100644 --- a/tests/unit/lib/SCEditor.js +++ b/tests/unit/lib/SCEditor.js @@ -658,3 +658,45 @@ QUnit.test('Do not wrap whitespace text nodes', function (assert) { assert.htmlEqual(body.innerHTML, testHtml); }); + + +QUnit.test('toBBCode() strips XSS', function (assert) { + var done = assert.async(); + + reloadEditor({ + format: 'bbcode' + }); + + document.body.xss = function () {}; + + var observer = new MutationObserver(function (mutations) { + var hasImage = false; + + mutations.forEach(function (mutation) { + Array.prototype.forEach.call(mutation.addedNodes, function (node) { + if (node.nodeType !== 1 || !node.querySelector) { + return; + } + + var img = node.querySelector('img'); + if (img) { + hasImage = true; + assert.notOk(img.hasAttribute('onerror'), 'onerror not stripped'); + } + }); + }); + + observer.disconnect(); + delete document.body.xss; + + assert.ok(hasImage); + done(); + }); + + observer.observe(document.body, { + childList: true, + subtree: true + }); + + sceditor.toBBCode(''); +});