浏览代码

Fix DOMPurify paths. Part 4.

Thanks to xet7 !
Lauri Ojansivu 5 天之前
父节点
当前提交
2119c6ab0c

+ 0 - 8
client/components/main/layouts.jade

@@ -52,14 +52,6 @@ template(name="userFormsLayout")
       else
         // ARIA live region for error messages
         div#login-error-message(role="alert" aria-live="assertive" style="color: #d32f2f; margin-bottom: 1em;")
-        // Add autocomplete attribute to login input for WCAG compliance
-        script.
-          document.addEventListener('DOMContentLoaded', function() {
-            var loginInput = document.querySelector('input[type="text"], input[type="email"]');
-            if (loginInput && loginInput.name && (loginInput.name.toLowerCase().includes('user') || loginInput.name.toLowerCase().includes('email'))) {
-              loginInput.setAttribute('autocomplete', 'username email');
-            }
-          });
         +Template.dynamic(template=content)
         if currentSetting.displayAuthenticationMethod
           +connectionMethod(authenticationMethod=currentSetting.defaultAuthenticationMethod)

+ 6 - 0
client/components/main/layouts.js

@@ -86,6 +86,12 @@ Template.userFormsLayout.onRendered(() => {
       validator,
     );
     EscapeActions.executeAll();
+
+    // Add autocomplete attribute to login input for WCAG compliance
+    const loginInput = document.querySelector('input[type="text"], input[type="email"]');
+    if (loginInput && loginInput.name && (loginInput.name.toLowerCase().includes('user') || loginInput.name.toLowerCase().includes('email'))) {
+      loginInput.setAttribute('autocomplete', 'username email');
+    }
   });
 });
 

+ 121 - 0
packages/markdown/src/secureDOMPurify.js

@@ -0,0 +1,121 @@
+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
+    ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,
+    // Remove dangerous protocols
+    ALLOW_UNKNOWN_PROTOCOLS: false,
+    // Sanitize URLs to prevent malicious content loading
+    SANITIZE_DOM: true,
+    // Remove dangerous elements completely
+    KEEP_CONTENT: false,
+    // Additional security measures
+    ADD_ATTR: [],
+    // Block data URIs that could contain malicious content
+    ALLOW_DATA_ATTR: false,
+    // Custom hook to further sanitize content
+    HOOKS: {
+      uponSanitizeElement: function(node, data) {
+        // Block any remaining dangerous elements
+        const dangerousTags = ['svg', 'style', 'script', 'link', 'meta', 'iframe', 'object', 'embed', 'applet'];
+        if (node.tagName && dangerousTags.includes(node.tagName.toLowerCase())) {
+          if (process.env.DEBUG === 'true') {
+            console.warn('Blocked potentially dangerous element:', node.tagName);
+          }
+          return false;
+        }
+
+        // Block img tags with SVG data URIs
+        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);
+            }
+            return false;
+          }
+        }
+
+        // Block elements with dangerous attributes
+        const dangerousAttrs = ['style', 'onload', 'onerror', 'onclick', 'onmouseover', 'onfocus', 'onblur'];
+        for (const attr of dangerousAttrs) {
+          if (node.hasAttribute && node.hasAttribute(attr)) {
+            if (process.env.DEBUG === 'true') {
+              console.warn('Blocked element with dangerous attribute:', node.tagName, attr);
+            }
+            return false;
+          }
+        }
+
+        return true;
+      },
+      uponSanitizeAttribute: function(node, data) {
+        // Block style attributes completely
+        if (data.attrName === 'style') {
+          if (process.env.DEBUG === 'true') {
+            console.warn('Blocked style attribute');
+          }
+          return false;
+        }
+
+        // Block class and id attributes that might be used for CSS injection
+        if (data.attrName === 'class' || data.attrName === 'id') {
+          if (process.env.DEBUG === 'true') {
+            console.warn('Blocked class/id attribute:', data.attrName, data.attrValue);
+          }
+          return false;
+        }
+
+        // Block data attributes
+        if (data.attrName && data.attrName.startsWith('data-')) {
+          if (process.env.DEBUG === 'true') {
+            console.warn('Blocked data attribute:', data.attrName);
+          }
+          return false;
+        }
+
+        return true;
+      }
+    }
+  };
+}
+
+// Convenience function for secure sanitization
+export function sanitizeHTML(html) {
+  return DOMPurify.sanitize(html, getSecureDOMPurifyConfig());
+}
+
+// Convenience function for sanitizing text (no HTML)
+export function sanitizeText(text) {
+  return DOMPurify.sanitize(text, {
+    ALLOWED_TAGS: [],
+    ALLOWED_ATTR: [],
+    KEEP_CONTENT: true
+  });
+}

+ 1 - 1
packages/markdown/src/template-integration.js

@@ -1,5 +1,5 @@
 import DOMPurify from 'dompurify';
-import { getSecureDOMPurifyConfig } from '/imports/lib/secureDOMPurify';
+import { getSecureDOMPurifyConfig } from './secureDOMPurify';
 
 var Markdown = require('markdown-it')({
   html: true,