Neah/lib/utils/dom-purify-config.ts
2025-05-01 17:01:26 +02:00

120 lines
5.1 KiB
TypeScript

/**
* CENTRALIZED DOMPURIFY CONFIGURATION
*
* This file provides a consistent, centralized configuration for DOMPurify
* used throughout the application. All components that need to sanitize HTML
* should import from this file instead of configuring DOMPurify directly.
*/
import DOMPurify from 'isomorphic-dompurify';
// Reset any existing hooks to start with a clean slate
DOMPurify.removeAllHooks();
/**
* Configure DOMPurify with safe defaults for email content
* This balances security with the need to display rich email content
*/
export function configureDOMPurify() {
// Enhanced configuration for email content
DOMPurify.setConfig({
ADD_TAGS: [
// SVG elements for simple charts/logos that might be in emails
'svg', 'path', 'g', 'circle', 'rect', 'line', 'polygon', 'ellipse',
// Common email-specific elements
'o:p', 'font',
// Allow comments for conditional HTML in emails
'!--...--'
],
ADD_ATTR: [
// SVG attributes
'viewbox', 'd', 'cx', 'cy', 'r', 'fill', 'stroke', 'stroke-width', 'x', 'y', 'width', 'height',
// Additional HTML attributes commonly used in emails
'align', 'valign', 'bgcolor', 'color', 'cellpadding', 'cellspacing', 'colspan', 'rowspan',
'face', 'size', 'direction', 'role', 'aria-label', 'aria-hidden',
// List attributes
'start', 'type', 'value',
// Table attributes and styles
'border', 'frame', 'rules', 'summary', 'headers', 'scope', 'abbr',
// Blockquote attributes
'cite', 'datetime',
// Form elements attributes (read-only)
'readonly', 'disabled', 'selected', 'checked', 'multiple', 'wrap',
// Additional attributes for forwarded emails
'style', 'class', 'id', 'dir', 'lang', 'title',
// Table attributes commonly used in email clients
'background', 'bordercolor', 'width', 'height'
],
FORBID_TAGS: [
// Remove dangerous tags
'script', 'object', 'iframe', 'embed', 'applet', 'meta', 'link',
// Form elements that could be used for phishing
'form', 'button', 'input', 'textarea', 'select', 'option'
],
FORBID_ATTR: [
// Remove JavaScript and dangerous attributes
'onerror', 'onload', 'onclick', 'onmouseover', 'onmouseout', 'onmouseenter', 'onmouseleave',
'onkeydown', 'onkeypress', 'onkeyup', 'onchange', 'onsubmit', 'onreset', 'onselect', 'onblur',
'onfocus', 'onscroll', 'onbeforeunload', 'onunload', 'onhashchange', 'onpopstate', 'onpageshow',
'onpagehide', 'onabort', 'oncanplay', 'oncanplaythrough', 'ondurationchange', 'onemptied',
'onended', 'onloadeddata', 'onloadedmetadata', 'onloadstart', 'onpause', 'onplay', 'onplaying',
'onprogress', 'onratechange', 'onseeked', 'onseeking', 'onstalled', 'onsuspend', 'ontimeupdate',
'onvolumechange', 'onwaiting', 'animationend', 'animationiteration', 'animationstart',
// Dangerous attributes
'formaction', 'xlink:href'
],
ALLOW_DATA_ATTR: false, // Disable data-* attributes which can be used for XSS
WHOLE_DOCUMENT: false, // Don't parse the entire document - just fragments
SANITIZE_DOM: true, // Sanitize the DOM to prevent XSS
KEEP_CONTENT: true, // Keep content of elements that are removed
RETURN_DOM: false, // Return a DOM object rather than HTML string
RETURN_DOM_FRAGMENT: false, // Return a DocumentFragment rather than HTML string
FORCE_BODY: false, // Add a <body> tag if one doesn't exist
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|data|irc):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,
ALLOW_UNKNOWN_PROTOCOLS: true, // Some email clients use custom protocols for images/attachments
USE_PROFILES: { html: true } // Use the HTML profile for more permissive sanitization
});
return DOMPurify;
}
// Singleton instance of configured DOMPurify for the app
export const purify = configureDOMPurify();
/**
* Sanitize HTML content using our email-specific configuration
*/
export function sanitizeHtml(content: string, options?: { preserveReplyFormat?: boolean }): string {
if (!content) return '';
try {
// Special handling for reply/forward emails to be less aggressive with sanitization
const extraTags = options?.preserveReplyFormat
? ['style', 'blockquote', 'table', 'thead', 'tbody', 'tr', 'td', 'th']
: ['style'];
const extraAttrs = options?.preserveReplyFormat
? ['style', 'class', 'align', 'valign', 'bgcolor', 'colspan', 'rowspan', 'width', 'height', 'border']
: ['style', 'class'];
// Sanitize with our configured instance and options
return purify.sanitize(content, {
ADD_TAGS: extraTags,
ADD_ATTR: extraAttrs
});
} catch (error) {
console.error('Failed to sanitize HTML content:', error);
// Fallback to basic sanitization
return content
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/javascript:/gi, 'blocked:');
}
}
/**
* Get the configured DOMPurify instance
* Use this if you need to perform custom sanitization beyond the standard function
*/
export function getDOMPurify(): typeof DOMPurify {
return DOMPurify;
}