120 lines
5.1 KiB
TypeScript
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;
|
|
}
|