From 0bb9ee4327d180a3b6e640d1ef3484efdb959f0f Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 19 Aug 2025 12:00:23 +0200 Subject: [PATCH] feat(sanitization): enhance HTML sanitization with improved DOMPurify configuration - Updated the `sanitizeHTML` function to include additional sanitization options for better security. - Introduced a URL regex to validate links and added a hook to manage `rel` attributes for external links. - Ensured that only safe attributes and tags are retained, further preventing XSS vulnerabilities. --- resources/views/layouts/base.blade.php | 39 ++++++++++++++++++-------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/resources/views/layouts/base.blade.php b/resources/views/layouts/base.blade.php index c94297a7a..2e1ba57e2 100644 --- a/resources/views/layouts/base.blade.php +++ b/resources/views/layouts/base.blade.php @@ -65,16 +65,15 @@ // Global HTML sanitization function using DOMPurify window.sanitizeHTML = function(html) { if (!html) return ''; - - // Use DOMPurify with strict configuration for toast notifications - const purified = DOMPurify.sanitize(html, { - ALLOWED_TAGS: ['a', 'b', 'br', 'code', 'del', 'div', 'em', 'i', 'p', 'pre', 's', 'span', - 'strong', 'u' + const URL_RE = /^(https?:|mailto:)/i; + const config = { + ALLOWED_TAGS: ['a', 'b', 'br', 'code', 'del', 'div', 'em', 'i', 'p', 'pre', 's', 'span', 'strong', + 'u' ], - ALLOWED_ATTR: ['class', 'href', 'target', 'title'], + ALLOWED_ATTR: ['class', 'href', 'target', 'title', 'rel'], ALLOW_DATA_ATTR: false, - FORBID_TAGS: ['script', 'object', 'embed', 'applet', 'iframe', 'form', 'input', 'button', - 'select', 'textarea', 'details', 'summary', 'dialog', 'style' + FORBID_TAGS: ['script', 'object', 'embed', 'applet', 'iframe', 'form', 'input', 'button', 'select', + 'textarea', 'details', 'summary', 'dialog', 'style' ], FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onfocus', 'onblur', 'onchange', 'onsubmit', 'ontoggle', 'style' @@ -82,10 +81,26 @@ KEEP_CONTENT: true, RETURN_DOM: false, RETURN_DOM_FRAGMENT: false, - SANITIZE_DOM: true - }); - console.log(purified); - return purified; + SANITIZE_DOM: true, + SANITIZE_NAMED_PROPS: true, + SAFE_FOR_TEMPLATES: true, + ALLOWED_URI_REGEXP: URL_RE + }; + + // One-time hook registration (idempotent pattern) + if (!window.__dpLinkHook) { + DOMPurify.addHook('afterSanitizeAttributes', node => { + if (node.nodeName === 'A' && node.hasAttribute('href')) { + const href = node.getAttribute('href') || ''; + if (!URL_RE.test(href)) node.removeAttribute('href'); + if (node.getAttribute('target') === '_blank') { + node.setAttribute('rel', 'noopener noreferrer'); + } + } + }); + window.__dpLinkHook = true; + } + return DOMPurify.sanitize(html, config); }; if (!('theme' in localStorage)) {