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.js, dist/jsuites.js, dist/jsuites.layout.css, dist/jsuites.layout.js, package.json, src/editor.js
Commit date: 2021-09-18 19:31:24 +0100
Commit message: Editor sanitize parser updates.
Diff: diff --git a/dist/jsuites.basic.js b/dist/jsuites.basic.js
index fa1098a..bcc33d7 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.4.0';
+    var version = '4.9.11';
 
     var find = function(DOMElement, component) {
         if (DOMElement[component.type] && DOMElement[component.type] == component) {
             return true;
         }
@@ -4997,10 +4997,14 @@ jSuites.editor = (function(el, options) {
             });
         }
     }
 
     obj.addImage = function(src, asSnippet) {
+        if (! src) {
+            src = '';
+        }
+
         if (src.substr(0,4) != 'data' && ! obj.options.remoteParser) {
             console.error('remoteParser not defined in your initialization');
         } else {
             // This is to process cross domain images
             if (src.substr(0,4) == 'data') {
@@ -5271,10 +5275,13 @@ jSuites.editor = (function(el, options) {
     }
 
     // Elements to be removed
     var remove = [HTMLUnknownElement,HTMLAudioElement,HTMLEmbedElement,HTMLIFrameElement,HTMLTextAreaElement,HTMLInputElement,HTMLScriptElement];
 
+    // Valid properties
+    var validProperty = ['width', 'height', 'align', 'border', 'src', 'tabindex'];
+
     // Valid CSS attributes
     var validStyle = ['color', 'font-weight', 'font-size', 'background', 'background-color', 'margin'];
 
     var parse = function(element) {
        // Remove attributes
@@ -5295,24 +5302,31 @@ jSuites.editor = (function(el, options) {
                    }
                }
            }
            // Process image
            if (element.tagName.toUpperCase() == 'IMG') {
-               if (! obj.options.acceptImages) {
+               if (! obj.options.acceptImages || ! element.src) {
                    element.parentNode.removeChild(element);
                } else {
                    // Check if is data
                    element.setAttribute('tabindex', '900');
                    // Check attributes for persistance
                    obj.addImage(element.src);
                }
-           } else {
-               // Remove attributes
-               var numAttributes = element.attributes.length - 1;
+           }
+           // Remove attributes
+           var attr = [];
+           var numAttributes = element.attributes.length - 1;
+           if (numAttributes > 0) {
                for (var i = numAttributes; i >= 0 ; i--) {
-                   element.removeAttribute(element.attributes[i].name);
+                   attr.push(element.attributes[i].name);
                }
+               attr.forEach(function(v) {
+                   if (validProperty.indexOf(v) == -1) {
+                       element.removeAttribute(v);
+                   }
+               });
            }
            element.style = '';
            // Add valid style
            if (style && style.length) {
                element.setAttribute('style', style.join(';'));
diff --git a/dist/jsuites.js b/dist/jsuites.js
index 7957182..8145ef4 100644
--- a/dist/jsuites.js
+++ b/dist/jsuites.js
@@ -15,11 +15,11 @@
 
     'use strict';
 
 var jSuites = function(options) {
     var obj = {}
-    var version = '4.4.0';
+    var version = '4.9.11';
 
     var find = function(DOMElement, component) {
         if (DOMElement[component.type] && DOMElement[component.type] == component) {
             return true;
         }
@@ -5008,10 +5008,14 @@ jSuites.editor = (function(el, options) {
             });
         }
     }
 
     obj.addImage = function(src, asSnippet) {
+        if (! src) {
+            src = '';
+        }
+
         if (src.substr(0,4) != 'data' && ! obj.options.remoteParser) {
             console.error('remoteParser not defined in your initialization');
         } else {
             // This is to process cross domain images
             if (src.substr(0,4) == 'data') {
@@ -5282,10 +5286,13 @@ jSuites.editor = (function(el, options) {
     }
 
     // Elements to be removed
     var remove = [HTMLUnknownElement,HTMLAudioElement,HTMLEmbedElement,HTMLIFrameElement,HTMLTextAreaElement,HTMLInputElement,HTMLScriptElement];
 
+    // Valid properties
+    var validProperty = ['width', 'height', 'align', 'border', 'src', 'tabindex'];
+
     // Valid CSS attributes
     var validStyle = ['color', 'font-weight', 'font-size', 'background', 'background-color', 'margin'];
 
     var parse = function(element) {
        // Remove attributes
@@ -5306,24 +5313,31 @@ jSuites.editor = (function(el, options) {
                    }
                }
            }
            // Process image
            if (element.tagName.toUpperCase() == 'IMG') {
-               if (! obj.options.acceptImages) {
+               if (! obj.options.acceptImages || ! element.src) {
                    element.parentNode.removeChild(element);
                } else {
                    // Check if is data
                    element.setAttribute('tabindex', '900');
                    // Check attributes for persistance
                    obj.addImage(element.src);
                }
-           } else {
-               // Remove attributes
-               var numAttributes = element.attributes.length - 1;
+           }
+           // Remove attributes
+           var attr = [];
+           var numAttributes = element.attributes.length - 1;
+           if (numAttributes > 0) {
                for (var i = numAttributes; i >= 0 ; i--) {
-                   element.removeAttribute(element.attributes[i].name);
+                   attr.push(element.attributes[i].name);
                }
+               attr.forEach(function(v) {
+                   if (validProperty.indexOf(v) == -1) {
+                       element.removeAttribute(v);
+                   }
+               });
            }
            element.style = '';
            // Add valid style
            if (style && style.length) {
                element.setAttribute('style', style.join(';'));
diff --git a/dist/jsuites.layout.css b/dist/jsuites.layout.css
index 0622975..8ef5842 100644
--- a/dist/jsuites.layout.css
+++ b/dist/jsuites.layout.css
@@ -1586,10 +1586,11 @@ section.middle {
     text-decoration: none;
 }
 
 .jmenu a:hover, .jmenu a.selected {
     text-decoration: underline;
+    font-weight: bold;
 }
 
 .jmenu .title {
     border-bottom: 1px solid #ddd;
     margin-bottom: 20px;
diff --git a/dist/jsuites.layout.js b/dist/jsuites.layout.js
index 90806cc..65b64b7 100644
--- a/dist/jsuites.layout.js
+++ b/dist/jsuites.layout.js
@@ -1699,11 +1699,11 @@ jSuites.menu = (function(el, options) {
             var selected = null;
             for (var i = 0; i < menu.length; i++) {
                 menu[i].classList.remove('selected');
                 if (menu[i].getAttribute('data-id')) {
                     var state = localStorage.getItem('jmenu-' + menu[i].getAttribute('data-id'));
-                    if (state === null || state == 1) {
+                    if (state == 1) {
                         menu[i].classList.add('selected');
                     }
                 }
             }
             var href = window.location.pathname;
@@ -1714,70 +1714,81 @@ jSuites.menu = (function(el, options) {
                 }
             }
         }
     }
 
-    obj.select = function(o) {
-        var menu = el.querySelectorAll('nav a');
-        for (var i = 0; i < menu.length; i++) {
-            menu[i].classList.remove('selected');
-        }
-        o.classList.add('selected');
-
-        // Better navigation
-        if (options && options.collapse == true) {
-            if (o.classList.contains('show')) {
-                menu = el.querySelectorAll('nav');
-                for (var i = 0; i < menu.length; i++) {
-                    menu[i].style.display = '';
-                }
-                o.style.display = 'none';
-            } else {
-                menu = el.querySelectorAll('nav');
-                for (var i = 0; i < menu.length; i++) {
-                    menu[i].style.display = 'none';
-                }
+    obj.select = function(o, e) {
+        if (o.tagName == 'NAV') {
+            var m = el.querySelectorAll('nav');
+            for (var i = 0; i < m.length; i++) {
+                m[i].style.display = 'none';
+            }
+            o.style.display = '';
+            o.classList.add('selected');
+        } else {
+            var m = el.querySelectorAll('nav a');
+            for (var i = 0; i < m.length; i++) {
+                m[i].classList.remove('selected');
+            }
+            o.classList.add('selected');
+
+            // Better navigation
+            if (options && options.collapse == true) {
+                if (o.classList.contains('show')) {
+                    m = el.querySelectorAll('nav');
+                    for (var i = 0; i < m.length; i++) {
+                        m[i].style.display = '';
+                    }
+                    o.style.display = 'none';
+                } else {
+                    m = el.querySelectorAll('nav');
+                    for (var i = 0; i < m.length; i++) {
+                        m[i].style.display = 'none';
+                    }
 
-                menu = el.querySelector('.show');
-                if (menu) {
-                    menu.style.display = 'block';
-                }
+                    m = el.querySelector('.show');
+                    if (m) {
+                        m.style.display = 'block';
+                    }
 
-                menu = jSuites.findElement(o.parentNode, 'selected');
-                if (menu) {
-                    menu.style.display = '';
+                    m = jSuites.findElement(o.parentNode, 'selected');
+                    if (m) {
+                        m.style.display = '';
+                    }
                 }
             }
         }
 
+        if (options && typeof(options.onclick) == 'function') {
+            options.onclick(obj, e);
+        }
+
         // Close menu if is oped
         if (jSuites.getWindowWidth() < 800) {
-            setTimeout(function() {
-                obj.hide();
-            }, 0);
+            obj.hide();
         }
     }
 
-    var actionDown = function(e) {
+    var action = function(e) {
         if (e.target.tagName == 'H2') {
             if (e.target.parentNode.classList.contains('selected')) {
                 e.target.parentNode.classList.remove('selected');
                 localStorage.setItem('jmenu-' + e.target.parentNode.getAttribute('data-id'), 0);
             } else {
                 e.target.parentNode.classList.add('selected');
                 localStorage.setItem('jmenu-' + e.target.parentNode.getAttribute('data-id'), 1);
             }
         } else if (e.target.tagName == 'A') {
             // Mark link as selected
-            obj.select(e.target);
+            obj.select(e.target, e);
         }
     }
 
     if ('ontouchstart' in document.documentElement === true) {
-        el.addEventListener('touchstart', actionDown);
+        el.addEventListener('touchsend', action);
     } else {
-        el.addEventListener('mousedown', actionDown);
+        el.addEventListener('mouseup', action);
     }
 
     // Add close action
     var i = document.createElement('i');
     i.className = 'material-icons small-screen-only close';
diff --git a/package.json b/package.json
index 58946c4..48a085f 100644
--- a/package.json
+++ b/package.json
@@ -17,11 +17,11 @@
     ],
     "keywords": [
         "javascript plugins"
     ],
     "main": "dist/jsuites.js",
-    "version": "4.9.10",
+    "version": "4.9.11",
     "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/editor.js b/src/editor.js
index be6d685..308c1ff 100644
--- a/src/editor.js
+++ b/src/editor.js
@@ -450,10 +450,14 @@ jSuites.editor = (function(el, options) {
             });
         }
     }
 
     obj.addImage = function(src, asSnippet) {
+        if (! src) {
+            src = '';
+        }
+
         if (src.substr(0,4) != 'data' && ! obj.options.remoteParser) {
             console.error('remoteParser not defined in your initialization');
         } else {
             // This is to process cross domain images
             if (src.substr(0,4) == 'data') {
@@ -724,10 +728,13 @@ jSuites.editor = (function(el, options) {
     }
 
     // Elements to be removed
     var remove = [HTMLUnknownElement,HTMLAudioElement,HTMLEmbedElement,HTMLIFrameElement,HTMLTextAreaElement,HTMLInputElement,HTMLScriptElement];
 
+    // Valid properties
+    var validProperty = ['width', 'height', 'align', 'border', 'src', 'tabindex'];
+
     // Valid CSS attributes
     var validStyle = ['color', 'font-weight', 'font-size', 'background', 'background-color', 'margin'];
 
     var parse = function(element) {
        // Remove attributes
@@ -748,24 +755,31 @@ jSuites.editor = (function(el, options) {
                    }
                }
            }
            // Process image
            if (element.tagName.toUpperCase() == 'IMG') {
-               if (! obj.options.acceptImages) {
+               if (! obj.options.acceptImages || ! element.src) {
                    element.parentNode.removeChild(element);
                } else {
                    // Check if is data
                    element.setAttribute('tabindex', '900');
                    // Check attributes for persistance
                    obj.addImage(element.src);
                }
-           } else {
-               // Remove attributes
-               var numAttributes = element.attributes.length - 1;
+           }
+           // Remove attributes
+           var attr = [];
+           var numAttributes = element.attributes.length - 1;
+           if (numAttributes > 0) {
                for (var i = numAttributes; i >= 0 ; i--) {
-                   element.removeAttribute(element.attributes[i].name);
+                   attr.push(element.attributes[i].name);
                }
+               attr.forEach(function(v) {
+                   if (validProperty.indexOf(v) == -1) {
+                       element.removeAttribute(v);
+                   }
+               });
            }
            element.style = '';
            // Add valid style
            if (style && style.length) {
                element.setAttribute('style', style.join(';'));

Vulnerable code:     var version = '4.4.0';
               if (! obj.options.acceptImages) {
           } else {
               // Remove attributes
               var numAttributes = element.attributes.length - 1;
                   element.removeAttribute(element.attributes[i].name);
    var version = '4.4.0';
               if (! obj.options.acceptImages) {
           } else {
               // Remove attributes
               var numAttributes = element.attributes.length - 1;
                   element.removeAttribute(element.attributes[i].name);
                    if (state === null || state == 1) {
    obj.select = function(o) {
        var menu = el.querySelectorAll('nav a');
        for (var i = 0; i < menu.length; i++) {
            menu[i].classList.remove('selected');
        }
        o.classList.add('selected');

        // Better navigation
        if (options && options.collapse == true) {
            if (o.classList.contains('show')) {
                menu = el.querySelectorAll('nav');
                for (var i = 0; i < menu.length; i++) {
                    menu[i].style.display = '';
                }
                o.style.display = 'none';
            } else {
                menu = el.querySelectorAll('nav');
                for (var i = 0; i < menu.length; i++) {
                    menu[i].style.display = 'none';
                }
                menu = el.querySelector('.show');
                if (menu) {
                    menu.style.display = 'block';
                }
                menu = jSuites.findElement(o.parentNode, 'selected');
                if (menu) {
                    menu.style.display = '';
            setTimeout(function() {
                obj.hide();
            }, 0);
    var actionDown = function(e) {
            obj.select(e.target);
        el.addEventListener('touchstart', actionDown);
        el.addEventListener('mousedown', actionDown);
    "version": "4.9.10",
               if (! obj.options.acceptImages) {
           } else {
               // Remove attributes
               var numAttributes = element.attributes.length - 1;
                   element.removeAttribute(element.attributes[i].name);
Fixed code:     var version = '4.9.11';
        if (! src) {
            src = '';
        }

    // Valid properties
    var validProperty = ['width', 'height', 'align', 'border', 'src', 'tabindex'];

               if (! obj.options.acceptImages || ! element.src) {
           }
           // Remove attributes
           var attr = [];
           var numAttributes = element.attributes.length - 1;
           if (numAttributes > 0) {
                   attr.push(element.attributes[i].name);
               attr.forEach(function(v) {
                   if (validProperty.indexOf(v) == -1) {
                       element.removeAttribute(v);
                   }
               });
    var version = '4.9.11';
        if (! src) {
            src = '';
        }

    // Valid properties
    var validProperty = ['width', 'height', 'align', 'border', 'src', 'tabindex'];

               if (! obj.options.acceptImages || ! element.src) {
           }
           // Remove attributes
           var attr = [];
           var numAttributes = element.attributes.length - 1;
           if (numAttributes > 0) {
                   attr.push(element.attributes[i].name);
               attr.forEach(function(v) {
                   if (validProperty.indexOf(v) == -1) {
                       element.removeAttribute(v);
                   }
               });
    font-weight: bold;
                    if (state == 1) {
    obj.select = function(o, e) {
        if (o.tagName == 'NAV') {
            var m = el.querySelectorAll('nav');
            for (var i = 0; i < m.length; i++) {
                m[i].style.display = 'none';
            }
            o.style.display = '';
            o.classList.add('selected');
        } else {
            var m = el.querySelectorAll('nav a');
            for (var i = 0; i < m.length; i++) {
                m[i].classList.remove('selected');
            }
            o.classList.add('selected');

            // Better navigation
            if (options && options.collapse == true) {
                if (o.classList.contains('show')) {
                    m = el.querySelectorAll('nav');
                    for (var i = 0; i < m.length; i++) {
                        m[i].style.display = '';
                    }
                    o.style.display = 'none';
                } else {
                    m = el.querySelectorAll('nav');
                    for (var i = 0; i < m.length; i++) {
                        m[i].style.display = 'none';
                    }
                    m = el.querySelector('.show');
                    if (m) {
                        m.style.display = 'block';
                    }
                    m = jSuites.findElement(o.parentNode, 'selected');
                    if (m) {
                        m.style.display = '';
                    }
        if (options && typeof(options.onclick) == 'function') {
            options.onclick(obj, e);
        }

            obj.hide();
    var action = function(e) {
            obj.select(e.target, e);
        el.addEventListener('touchsend', action);
        el.addEventListener('mouseup', action);
    "version": "4.9.11",
        if (! src) {
            src = '';
        }

    // Valid properties
    var validProperty = ['width', 'height', 'align', 'border', 'src', 'tabindex'];

               if (! obj.options.acceptImages || ! element.src) {
           }
           // Remove attributes
           var attr = [];
           var numAttributes = element.attributes.length - 1;
           if (numAttributes > 0) {
                   attr.push(element.attributes[i].name);
               attr.forEach(function(v) {
                   if (validProperty.indexOf(v) == -1) {
                       element.removeAttribute(v);
                   }
               });