CVE-2021-41086: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: jsuites is an open source collection of common required javascript web components. In affected versions users are subject to cross site scripting (XSS) attacks via clipboard content. jsuites is vulnerable to DOM based XSS if the user can be tricked into copying _anything_ from a malicious and pasting it into the html editor. This is because a part of the clipboard content is directly written to `innerHTML` allowing for javascript injection and thus XSS. Users are advised to update to version 4.9.11 to resolve.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 5.4
Published: 2021-09-21T21:15Z
Repository: https://github.com/jsuites/jsuites
Language: JavaScript
Files changed: dist/jsuites.basic.css, dist/jsuites.basic.js, dist/jsuites.css, dist/jsuites.js, package.json, src/contextmenu.css, src/editor.js, src/toolbar.js
Commit date: 2021-09-18 16:58:49 +0100
Commit message: Editor sec fix, toolbar reponsiveness tracking fix, responsive
contextmenu. v4.9.10
Diff: diff --git a/dist/jsuites.basic.css b/dist/jsuites.basic.css
index dd92f9f..71e1a8d 100644
--- a/dist/jsuites.basic.css
+++ b/dist/jsuites.basic.css
@@ -838,10 +838,44 @@
   -webkit-transform: translate(0, 0) scale(1);
   transform: translate(0, 0) scale(1);
   pointer-events: auto;
 }
 
+@media only screen and (max-width: 420px) {
+    .jcontextmenu {
+        top: initial !important;
+        left: 0px !important;
+        bottom: 0px !important;
+        width: 100vw;
+        height: 260px;
+        overflow: scroll;
+        animation: slide-bottom-in 0.4s forwards;
+    }
+    .jcontextmenu div {
+        width: 100%;
+        text-align: center;
+        border-bottom: 1px solid #ccc;
+        padding: 15px;
+    }
+    .jcontextmenu > div::before {
+        display: none;
+    }
+    .jcontextmenu a {
+        font-size: 1.4em;
+        text-transform: uppercase;
+    }
+    .jcontextmenu span {
+        display: none;
+    }
+    .jcontextmenu span {
+        display: none;
+    }
+    .jcontextmenu hr {
+        display: none;
+    }
+}
+
 .jdropdown {
     cursor:pointer;
     -webkit-touch-callout: none;
     -webkit-user-select: none;
     -khtml-user-select: none;
diff --git a/dist/jsuites.basic.js b/dist/jsuites.basic.js
index 7f1db8c..fa1098a 100644
--- a/dist/jsuites.basic.js
+++ b/dist/jsuites.basic.js
@@ -15,11 +15,11 @@
 
     'use strict';
 
 var jSuites = function(options) {
     var obj = {}
-    var version = '4.9.9';
+    var version = '4.4.0';
 
     var find = function(DOMElement, component) {
         if (DOMElement[component.type] && DOMElement[component.type] == component) {
             return true;
         }
@@ -5269,11 +5269,11 @@ jSuites.editor = (function(el, options) {
             }
         }
     }
 
     // Elements to be removed
-    var remove = [HTMLUnknownElement];
+    var remove = [HTMLUnknownElement,HTMLAudioElement,HTMLEmbedElement,HTMLIFrameElement,HTMLTextAreaElement,HTMLInputElement,HTMLScriptElement];
 
     // Valid CSS attributes
     var validStyle = ['color', 'font-weight', 'font-size', 'background', 'background-color', 'margin'];
 
     var parse = function(element) {
@@ -5294,13 +5294,13 @@ jSuites.editor = (function(el, options) {
                        style.push(k + ':' + v);
                    }
                }
            }
            // Process image
-           if (element.tagName == 'IMG') {
+           if (element.tagName.toUpperCase() == 'IMG') {
                if (! obj.options.acceptImages) {
-                   element.remove();
+                   element.parentNode.removeChild(element);
                } else {
                    // Check if is data
                    element.setAttribute('tabindex', '900');
                    // Check attributes for persistance
                    obj.addImage(element.src);
@@ -5332,13 +5332,15 @@ jSuites.editor = (function(el, options) {
 
     var filter = function(data) {
         if (data) {
             data = data.replace(new RegExp('<!--(.*?)-->', 'gsi'), '');
         }
+        var parser = new DOMParser();
+        var d = parser.parseFromString(data, "text/html");
+        parse(d);
         var span = document.createElement('span');
-        span.innerHTML = data;
-        parse(span);
+        span.innerHTML = d.firstChild.innerHTML;
         return span;
     } 
 
     var editorPaste = function(e) {
         if (obj.options.filterPaste == true) {
@@ -9112,20 +9114,22 @@ jSuites.tabs = (function(el, options) {
 
     return obj;
 });
 
 jSuites.toolbar = (function(el, options) {
-    var obj = {};
+    // New instance
+    var obj = { type:'toolbar' };
     obj.options = {};
 
     // Default configuration
     var defaults = {
         app: null,
         container: false,
         badge: false,
         title: false,
         responsive: false,
+        maxWidth: null,
         items: [],
     }
 
     // Loop through our object
     for (var property in defaults) {
@@ -9320,20 +9324,25 @@ jSuites.toolbar = (function(el, options) {
         jSuites.tracking(obj, false);
     }
 
     obj.refresh = function() {
         if (obj.options.responsive == true) {
+            // Width of the c
+            var rect = el.parentNode.getBoundingClientRect();
+            if (! obj.options.maxWidth) {
+                obj.options.maxWidth = rect.width;
+            }
+            // Max width
+            var width = parseInt(obj.options.maxWidth); 
             // Remove arrow
             toolbarArrow.remove();
             // Move all items to the toolbar
             while (toolbarFloating.firstChild) {
                 toolbarContent.appendChild(toolbarFloating.firstChild);
             }
-            // Width of the c
-            var rect = el.parentNode.getBoundingClientRect();
             // Available parent space
-            var available = rect.width;
+            var available = obj.options.maxWidth;
             // Toolbar is larger than the parent, move elements to the floating element
             if (available < toolbarContent.offsetWidth) {
                 // Give space to the floating element
                 available -= 50;
                 // Move to the floating option
diff --git a/dist/jsuites.css b/dist/jsuites.css
index 62cc88f..429d9de 100644
--- a/dist/jsuites.css
+++ b/dist/jsuites.css
@@ -838,10 +838,44 @@
   -webkit-transform: translate(0, 0) scale(1);
   transform: translate(0, 0) scale(1);
   pointer-events: auto;
 }
 
+@media only screen and (max-width: 420px) {
+    .jcontextmenu {
+        top: initial !important;
+        left: 0px !important;
+        bottom: 0px !important;
+        width: 100vw;
+        height: 260px;
+        overflow: scroll;
+        animation: slide-bottom-in 0.4s forwards;
+    }
+    .jcontextmenu div {
+        width: 100%;
+        text-align: center;
+        border-bottom: 1px solid #ccc;
+        padding: 15px;
+    }
+    .jcontextmenu > div::before {
+        display: none;
+    }
+    .jcontextmenu a {
+        font-size: 1.4em;
+        text-transform: uppercase;
+    }
+    .jcontextmenu span {
+        display: none;
+    }
+    .jcontextmenu span {
+        display: none;
+    }
+    .jcontextmenu hr {
+        display: none;
+    }
+}
+
 .jdropdown {
     cursor:pointer;
     -webkit-touch-callout: none;
     -webkit-user-select: none;
     -khtml-user-select: none;
diff --git a/dist/jsuites.js b/dist/jsuites.js
index d3035ce..7957182 100644
--- a/dist/jsuites.js
+++ b/dist/jsuites.js
@@ -15,11 +15,11 @@
 
     'use strict';
 
 var jSuites = function(options) {
     var obj = {}
-    var version = '4.9.9';
+    var version = '4.4.0';
 
     var find = function(DOMElement, component) {
         if (DOMElement[component.type] && DOMElement[component.type] == component) {
             return true;
         }
@@ -5280,11 +5280,11 @@ jSuites.editor = (function(el, options) {
             }
         }
     }
 
     // Elements to be removed
-    var remove = [HTMLUnknownElement];
+    var remove = [HTMLUnknownElement,HTMLAudioElement,HTMLEmbedElement,HTMLIFrameElement,HTMLTextAreaElement,HTMLInputElement,HTMLScriptElement];
 
     // Valid CSS attributes
     var validStyle = ['color', 'font-weight', 'font-size', 'background', 'background-color', 'margin'];
 
     var parse = function(element) {
@@ -5305,13 +5305,13 @@ jSuites.editor = (function(el, options) {
                        style.push(k + ':' + v);
                    }
                }
            }
            // Process image
-           if (element.tagName == 'IMG') {
+           if (element.tagName.toUpperCase() == 'IMG') {
                if (! obj.options.acceptImages) {
-                   element.remove();
+                   element.parentNode.removeChild(element);
                } else {
                    // Check if is data
                    element.setAttribute('tabindex', '900');
                    // Check attributes for persistance
                    obj.addImage(element.src);
@@ -5343,13 +5343,15 @@ jSuites.editor = (function(el, options) {
 
     var filter = function(data) {
         if (data) {
             data = data.replace(new RegExp('<!--(.*?)-->', 'gsi'), '');
         }
+        var parser = new DOMParser();
+        var d = parser.parseFromString(data, "text/html");
+        parse(d);
         var span = document.createElement('span');
-        span.innerHTML = data;
-        parse(span);
+        span.innerHTML = d.firstChild.innerHTML;
         return span;
     } 
 
     var editorPaste = function(e) {
         if (obj.options.filterPaste == true) {
@@ -11017,20 +11019,22 @@ jSuites.tags = (function(el, options) {
 
     return obj;
 });
 
 jSuites.toolbar = (function(el, options) {
-    var obj = {};
+    // New instance
+    var obj = { type:'toolbar' };
     obj.options = {};
 
     // Default configuration
     var defaults = {
         app: null,
         container: false,
         badge: false,
         title: false,
         responsive: false,
+        maxWidth: null,
         items: [],
     }
 
     // Loop through our object
     for (var property in defaults) {
@@ -11225,20 +11229,25 @@ jSuites.toolbar = (function(el, options) {
         jSuites.tracking(obj, false);
     }
 
     obj.refresh = function() {
         if (obj.options.responsive == true) {
+            // Width of the c
+            var rect = el.parentNode.getBoundingClientRect();
+            if (! obj.options.maxWidth) {
+                obj.options.maxWidth = rect.width;
+            }
+            // Max width
+            var width = parseInt(obj.options.maxWidth); 
             // Remove arrow
             toolbarArrow.remove();
             // Move all items to the toolbar
             while (toolbarFloating.firstChild) {
                 toolbarContent.appendChild(toolbarFloating.firstChild);
             }
-            // Width of the c
-            var rect = el.parentNode.getBoundingClientRect();
             // Available parent space
-            var available = rect.width;
+            var available = obj.options.maxWidth;
             // Toolbar is larger than the parent, move elements to the floating element
             if (available < toolbarContent.offsetWidth) {
                 // Give space to the floating element
                 available -= 50;
                 // Move to the floating option
diff --git a/package.json b/package.json
index e26271d..58946c4 100644
--- a/package.json
+++ b/package.json
@@ -17,11 +17,11 @@
     ],
     "keywords": [
         "javascript plugins"
     ],
     "main": "dist/jsuites.js",
-    "version": "4.9.9",
+    "version": "4.9.10",
     "bugs": "https://github.com/jsuites/jsuites/issues",
     "homepage": "https://github.com/jsuites/jsuites",
     "docs": "https://jsuites.net",
     "download": "https://github.com/jsuites/jsuites/archive/master.zip"
 }
diff --git a/src/contextmenu.css b/src/contextmenu.css
index 1d176f3..90cba2b 100644
--- a/src/contextmenu.css
+++ b/src/contextmenu.css
@@ -93,6 +93,40 @@
   display: block;
   opacity: 1;
   -webkit-transform: translate(0, 0) scale(1);
   transform: translate(0, 0) scale(1);
   pointer-events: auto;
+}
+
+@media only screen and (max-width: 420px) {
+    .jcontextmenu {
+        top: initial !important;
+        left: 0px !important;
+        bottom: 0px !important;
+        width: 100vw;
+        height: 260px;
+        overflow: scroll;
+        animation: slide-bottom-in 0.4s forwards;
+    }
+    .jcontextmenu div {
+        width: 100%;
+        text-align: center;
+        border-bottom: 1px solid #ccc;
+        padding: 15px;
+    }
+    .jcontextmenu > div::before {
+        display: none;
+    }
+    .jcontextmenu a {
+        font-size: 1.4em;
+        text-transform: uppercase;
+    }
+    .jcontextmenu span {
+        display: none;
+    }
+    .jcontextmenu span {
+        display: none;
+    }
+    .jcontextmenu hr {
+        display: none;
+    }
 }
\ No newline at end of file
diff --git a/src/editor.js b/src/editor.js
index 48b7e21..be6d685 100644
--- a/src/editor.js
+++ b/src/editor.js
@@ -722,11 +722,11 @@ jSuites.editor = (function(el, options) {
             }
         }
     }
 
     // Elements to be removed
-    var remove = [HTMLUnknownElement];
+    var remove = [HTMLUnknownElement,HTMLAudioElement,HTMLEmbedElement,HTMLIFrameElement,HTMLTextAreaElement,HTMLInputElement,HTMLScriptElement];
 
     // Valid CSS attributes
     var validStyle = ['color', 'font-weight', 'font-size', 'background', 'background-color', 'margin'];
 
     var parse = function(element) {
@@ -747,13 +747,13 @@ jSuites.editor = (function(el, options) {
                        style.push(k + ':' + v);
                    }
                }
            }
            // Process image
-           if (element.tagName == 'IMG') {
+           if (element.tagName.toUpperCase() == 'IMG') {
                if (! obj.options.acceptImages) {
-                   element.remove();
+                   element.parentNode.removeChild(element);
                } else {
                    // Check if is data
                    element.setAttribute('tabindex', '900');
                    // Check attributes for persistance
                    obj.addImage(element.src);
@@ -785,13 +785,15 @@ jSuites.editor = (function(el, options) {
 
     var filter = function(data) {
         if (data) {
             data = data.replace(new RegExp('<!--(.*?)-->', 'gsi'), '');
         }
+        var parser = new DOMParser();
+        var d = parser.parseFromString(data, "text/html");
+        parse(d);
         var span = document.createElement('span');
-        span.innerHTML = data;
-        parse(span);
+        span.innerHTML = d.firstChild.innerHTML;
         return span;
     } 
 
     var editorPaste = function(e) {
         if (obj.options.filterPaste == true) {
diff --git a/src/toolbar.js b/src/toolbar.js
index 8a8dfdb..90cd7e9 100644
--- a/src/toolbar.js
+++ b/src/toolbar.js
@@ -1,16 +1,18 @@
 jSuites.toolbar = (function(el, options) {
-    var obj = {};
+    // New instance
+    var obj = { type:'toolbar' };
     obj.options = {};
 
     // Default configuration
     var defaults = {
         app: null,
         container: false,
         badge: false,
         title: false,
         responsive: false,
+        maxWidth: null,
         items: [],
     }
 
     // Loop through our object
     for (var property in defaults) {
@@ -205,20 +207,25 @@ jSuites.toolbar = (function(el, options) {
         jSuites.tracking(obj, false);
     }
 
     obj.refresh = function() {
         if (obj.options.responsive == true) {
+            // Width of the c
+            var rect = el.parentNode.getBoundingClientRect();
+            if (! obj.options.maxWidth) {
+                obj.options.maxWidth = rect.width;
+            }
+            // Max width
+            var width = parseInt(obj.options.maxWidth); 
             // Remove arrow
             toolbarArrow.remove();
             // Move all items to the toolbar
             while (toolbarFloating.firstChild) {
                 toolbarContent.appendChild(toolbarFloating.firstChild);
             }
-            // Width of the c
-            var rect = el.parentNode.getBoundingClientRect();
             // Available parent space
-            var available = rect.width;
+            var available = obj.options.maxWidth;
             // Toolbar is larger than the parent, move elements to the floating element
             if (available < toolbarContent.offsetWidth) {
                 // Give space to the floating element
                 available -= 50;
                 // Move to the floating option

Vulnerable code:     var version = '4.9.9';
    var remove = [HTMLUnknownElement];
           if (element.tagName == 'IMG') {
                   element.remove();
        span.innerHTML = data;
        parse(span);
    var obj = {};
            // Width of the c
            var rect = el.parentNode.getBoundingClientRect();
            var available = rect.width;
    var version = '4.9.9';
    var remove = [HTMLUnknownElement];
           if (element.tagName == 'IMG') {
                   element.remove();
        span.innerHTML = data;
        parse(span);
    var obj = {};
            // Width of the c
            var rect = el.parentNode.getBoundingClientRect();
            var available = rect.width;
    "version": "4.9.9",
    var remove = [HTMLUnknownElement];
           if (element.tagName == 'IMG') {
                   element.remove();
        span.innerHTML = data;
        parse(span);
    var obj = {};
            // Width of the c
            var rect = el.parentNode.getBoundingClientRect();
            var available = rect.width;
Fixed code: @media only screen and (max-width: 420px) {
    .jcontextmenu {
        top: initial !important;
        left: 0px !important;
        bottom: 0px !important;
        width: 100vw;
        height: 260px;
        overflow: scroll;
        animation: slide-bottom-in 0.4s forwards;
    }
    .jcontextmenu div {
        width: 100%;
        text-align: center;
        border-bottom: 1px solid #ccc;
        padding: 15px;
    }
    .jcontextmenu > div::before {
        display: none;
    }
    .jcontextmenu a {
        font-size: 1.4em;
        text-transform: uppercase;
    }
    .jcontextmenu span {
        display: none;
    }
    .jcontextmenu span {
        display: none;
    }
    .jcontextmenu hr {
        display: none;
    }
}

    var version = '4.4.0';
    var remove = [HTMLUnknownElement,HTMLAudioElement,HTMLEmbedElement,HTMLIFrameElement,HTMLTextAreaElement,HTMLInputElement,HTMLScriptElement];
           if (element.tagName.toUpperCase() == 'IMG') {
                   element.parentNode.removeChild(element);
        var parser = new DOMParser();
        var d = parser.parseFromString(data, "text/html");
        parse(d);
        span.innerHTML = d.firstChild.innerHTML;
    // New instance
    var obj = { type:'toolbar' };
        maxWidth: null,
            // Width of the c
            var rect = el.parentNode.getBoundingClientRect();
            if (! obj.options.maxWidth) {
                obj.options.maxWidth = rect.width;
            }
            // Max width
            var width = parseInt(obj.options.maxWidth); 
            var available = obj.options.maxWidth;
@media only screen and (max-width: 420px) {
    .jcontextmenu {
        top: initial !important;
        left: 0px !important;
        bottom: 0px !important;
        width: 100vw;
        height: 260px;
        overflow: scroll;
        animation: slide-bottom-in 0.4s forwards;
    }
    .jcontextmenu div {
        width: 100%;
        text-align: center;
        border-bottom: 1px solid #ccc;
        padding: 15px;
    }
    .jcontextmenu > div::before {
        display: none;
    }
    .jcontextmenu a {
        font-size: 1.4em;
        text-transform: uppercase;
    }
    .jcontextmenu span {
        display: none;
    }
    .jcontextmenu span {
        display: none;
    }
    .jcontextmenu hr {
        display: none;
    }
}

    var version = '4.4.0';
    var remove = [HTMLUnknownElement,HTMLAudioElement,HTMLEmbedElement,HTMLIFrameElement,HTMLTextAreaElement,HTMLInputElement,HTMLScriptElement];
           if (element.tagName.toUpperCase() == 'IMG') {
                   element.parentNode.removeChild(element);
        var parser = new DOMParser();
        var d = parser.parseFromString(data, "text/html");
        parse(d);
        span.innerHTML = d.firstChild.innerHTML;
    // New instance
    var obj = { type:'toolbar' };
        maxWidth: null,
            // Width of the c
            var rect = el.parentNode.getBoundingClientRect();
            if (! obj.options.maxWidth) {
                obj.options.maxWidth = rect.width;
            }
            // Max width
            var width = parseInt(obj.options.maxWidth); 
            var available = obj.options.maxWidth;
    "version": "4.9.10",
}

@media only screen and (max-width: 420px) {
    .jcontextmenu {
        top: initial !important;
        left: 0px !important;
        bottom: 0px !important;
        width: 100vw;
        height: 260px;
        overflow: scroll;
        animation: slide-bottom-in 0.4s forwards;
    }
    .jcontextmenu div {
        width: 100%;
        text-align: center;
        border-bottom: 1px solid #ccc;
        padding: 15px;
    }
    .jcontextmenu > div::before {
        display: none;
    }
    .jcontextmenu a {
        font-size: 1.4em;
        text-transform: uppercase;
    }
    .jcontextmenu span {
        display: none;
    }
    .jcontextmenu span {
        display: none;
    }
    .jcontextmenu hr {
        display: none;
    }
    var remove = [HTMLUnknownElement,HTMLAudioElement,HTMLEmbedElement,HTMLIFrameElement,HTMLTextAreaElement,HTMLInputElement,HTMLScriptElement];
           if (element.tagName.toUpperCase() == 'IMG') {
                   element.parentNode.removeChild(element);
        var parser = new DOMParser();
        var d = parser.parseFromString(data, "text/html");
        parse(d);
        span.innerHTML = d.firstChild.innerHTML;
    // New instance
    var obj = { type:'toolbar' };
        maxWidth: null,
            // Width of the c
            var rect = el.parentNode.getBoundingClientRect();
            if (! obj.options.maxWidth) {
                obj.options.maxWidth = rect.width;
            }
            // Max width
            var width = parseInt(obj.options.maxWidth); 
            var available = obj.options.maxWidth;