feat: allow img in HTML identity signatures

- Restricts src to https: URLs or base64-embedded raster data: URIs (png/jpeg/gif/webp).
- SVG is excluded for safety reasons.
- Images with a disallowed src are removed entirely so they don't render as broken-image icons.
This commit is contained in:
Timo Streule
2026-05-15 14:46:43 +02:00
committed by Linus Rath
parent d5dddba6df
commit 55a408e810
2 changed files with 86 additions and 14 deletions
+21 -6
View File
@@ -58,24 +58,39 @@ export function sanitizeEmailHtmlForIframe(html: string): string {
/**
* Sanitize HTML signature with stricter rules
* Only allows basic formatting, no external resources
* Allows basic formatting plus <img> for company logos
*/
export const SIGNATURE_SANITIZE_CONFIG = {
ALLOWED_TAGS: ['p', 'br', 'b', 'strong', 'i', 'em', 'u', 'a', 'span', 'div'],
ALLOWED_ATTR: ['href', 'style', 'class'],
ALLOWED_TAGS: ['p', 'br', 'b', 'strong', 'i', 'em', 'u', 'a', 'span', 'div', 'img'],
ALLOWED_ATTR: ['href', 'style', 'class', 'src', 'alt', 'width', 'height', 'title'],
ALLOW_DATA_ATTR: false,
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'img', 'video', 'audio'],
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'video', 'audio'],
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover'],
};
/**
* Sanitize HTML signature for storage and display
* Sanitize HTML signature for storage and display.
* img src is restricted to https: or base64-embedded raster data: URIs
* (png/jpeg/gif/webp). SVG is excluded because DOMPurify cannot inspect
* bytes inside a data: URI. Images with a disallowed src are removed
* entirely so they don't render as broken-image icons.
* @param html - User-provided HTML signature
* @returns Sanitized signature (no scripts, no external resources)
*/
export function sanitizeSignatureHtml(html: string): string {
if (!html?.trim()) return '';
return DOMPurify.sanitize(html, SIGNATURE_SANITIZE_CONFIG);
DOMPurify.addHook('afterSanitizeAttributes', (node) => {
if (node.tagName !== 'IMG') return;
const src = node.getAttribute('src');
if (!src || !/^(?:https:\/\/|data:image\/(?:png|jpe?g|gif|webp);base64,)/i.test(src)) {
node.remove();
}
});
try {
return DOMPurify.sanitize(html, SIGNATURE_SANITIZE_CONFIG);
} finally {
DOMPurify.removeAllHooks();
}
}
/**