diff --git a/client/lib/secureDOMPurify.js b/client/lib/secureDOMPurify.js index c4e352e87..898687dad 100644 --- a/client/lib/secureDOMPurify.js +++ b/client/lib/secureDOMPurify.js @@ -3,43 +3,25 @@ import DOMPurify from 'dompurify'; // Centralized secure DOMPurify configuration to prevent XSS and CSS injection attacks export function getSecureDOMPurifyConfig() { return { - // Block dangerous elements that can cause XSS and CSS injection - FORBID_TAGS: [ - 'svg', 'defs', 'use', 'g', 'symbol', 'marker', 'pattern', 'mask', 'clipPath', - 'linearGradient', 'radialGradient', 'stop', 'animate', 'animateTransform', - 'animateMotion', 'set', 'switch', 'foreignObject', 'script', 'style', 'link', - 'meta', 'iframe', 'object', 'embed', 'applet', 'form', 'input', 'textarea', - 'select', 'option', 'button', 'label', 'fieldset', 'legend', 'frameset', - 'frame', 'noframes', 'base', 'basefont', 'isindex', 'dir', 'menu', 'menuitem' - ], - // Block dangerous attributes that can cause XSS and CSS injection - FORBID_ATTR: [ - 'xlink:href', 'href', 'onload', 'onerror', 'onclick', 'onmouseover', - 'onfocus', 'onblur', 'onchange', 'onsubmit', 'onreset', 'onselect', - 'onunload', 'onresize', 'onscroll', 'onkeydown', 'onkeyup', 'onkeypress', - 'onmousedown', 'onmouseup', 'onmouseover', 'onmouseout', 'onmousemove', - 'ondblclick', 'oncontextmenu', 'onwheel', 'ontouchstart', 'ontouchend', - 'ontouchmove', 'ontouchcancel', 'onabort', 'oncanplay', 'oncanplaythrough', - 'ondurationchange', 'onemptied', 'onended', 'onerror', 'onloadeddata', - 'onloadedmetadata', 'onloadstart', 'onpause', 'onplay', 'onplaying', - 'onprogress', 'onratechange', 'onseeked', 'onseeking', 'onstalled', - 'onsuspend', 'ontimeupdate', 'onvolumechange', 'onwaiting', 'onbeforeunload', - 'onhashchange', 'onpagehide', 'onpageshow', 'onpopstate', 'onstorage', - 'onunload', 'style', 'class', 'id', 'data-*', 'aria-*' - ], - // Allow only safe image formats and protocols + // Allow common markdown elements including anchor tags + ALLOWED_TAGS: ['a', 'p', 'br', 'strong', 'em', 'u', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'blockquote', 'pre', 'code', 'img', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'hr', 'div', 'span'], + // Allow safe attributes including href for anchor tags + ALLOWED_ATTR: ['href', 'title', 'alt', 'src', 'width', 'height', 'target', 'rel'], + // Allow safe protocols for links ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i, - // Remove dangerous protocols + // Allow unknown protocols but be cautious ALLOW_UNKNOWN_PROTOCOLS: false, - // Sanitize URLs to prevent malicious content loading + // Sanitize DOM for security SANITIZE_DOM: true, - // Remove dangerous elements completely - KEEP_CONTENT: false, - // Additional security measures - ADD_ATTR: [], + // Keep content but sanitize it + KEEP_CONTENT: true, + // Block dangerous elements that can cause XSS + FORBID_TAGS: ['script', 'style', 'iframe', 'object', 'embed', 'applet', 'svg', 'defs', 'use', 'g', 'symbol', 'marker', 'pattern', 'mask', 'clipPath', 'linearGradient', 'radialGradient', 'stop', 'animate', 'animateTransform', 'animateMotion', 'set', 'switch', 'foreignObject', 'link', 'meta', 'form', 'input', 'textarea', 'select', 'option', 'button', 'label', 'fieldset', 'legend', 'frameset', 'frame', 'noframes', 'base', 'basefont', 'isindex', 'dir', 'menu', 'menuitem'], + // Block dangerous attributes but allow safe href + FORBID_ATTR: ['xlink:href', 'onload', 'onerror', 'onclick', 'onmouseover', 'onfocus', 'onblur', 'onchange', 'onsubmit', 'onreset', 'onselect', 'onunload', 'onresize', 'onscroll', 'onkeydown', 'onkeyup', 'onkeypress', 'onmousedown', 'onmouseup', 'onmouseover', 'onmouseout', 'onmousemove', 'ondblclick', 'oncontextmenu', 'onwheel', 'ontouchstart', 'ontouchend', 'ontouchmove', 'ontouchcancel', 'onabort', 'oncanplay', 'oncanplaythrough', 'ondurationchange', 'onemptied', 'onended', 'onerror', 'onloadeddata', 'onloadedmetadata', 'onloadstart', 'onpause', 'onplay', 'onplaying', 'onprogress', 'onratechange', 'onseeked', 'onseeking', 'onstalled', 'onsuspend', 'ontimeupdate', 'onvolumechange', 'onwaiting', 'onbeforeunload', 'onhashchange', 'onpagehide', 'onpageshow', 'onpopstate', 'onstorage', 'onunload', 'style', 'class', 'id', 'data-*', 'aria-*'], // Block data URIs that could contain malicious content ALLOW_DATA_ATTR: false, - // Custom hook to further sanitize content + // Custom hooks for additional security HOOKS: { uponSanitizeElement: function(node, data) { // Block any remaining dangerous elements @@ -51,14 +33,37 @@ export function getSecureDOMPurifyConfig() { return false; } - // Block img tags with SVG data URIs + // Block img tags with SVG data URIs that could contain malicious JavaScript if (node.tagName && node.tagName.toLowerCase() === 'img') { const src = node.getAttribute('src'); - if (src && (src.startsWith('data:image/svg') || src.endsWith('.svg'))) { - if (process.env.DEBUG === 'true') { - console.warn('Blocked potentially malicious SVG image:', src); + if (src) { + // Block all SVG data URIs to prevent XSS via embedded JavaScript + if (src.startsWith('data:image/svg') || src.endsWith('.svg')) { + if (process.env.DEBUG === 'true') { + console.warn('Blocked potentially malicious SVG image:', src); + } + return false; + } + + // Additional check for base64 encoded SVG with script tags + if (src.startsWith('data:image/svg+xml;base64,')) { + try { + const base64Content = src.split(',')[1]; + const decodedContent = atob(base64Content); + if (decodedContent.includes('