| 40 | |
| 41 | // Configure DOMPurify with security-focused settings |
| 42 | function configureDOMPurify(): void { |
| 43 | // Hook to validate iframes before allowing them |
| 44 | DOMPurify.addHook('uponSanitizeElement' as any, (node: Element, data: any) => { |
| 45 | const hookData = data as SanitizeElementHookEvent; |
| 46 | if (hookData.tagName === 'iframe') { |
| 47 | const src = node.getAttribute('src') || ''; |
| 48 | try { |
| 49 | const url = new URL(src); |
| 50 | const hostname = url.hostname.toLowerCase(); |
| 51 | |
| 52 | // Check if the iframe source is from a trusted domain |
| 53 | const isTrusted = TRUSTED_IFRAME_DOMAINS.some( |
| 54 | (domain) => hostname === domain || hostname.endsWith('.' + domain), |
| 55 | ); |
| 56 | |
| 57 | if (!isTrusted) { |
| 58 | // Remove untrusted iframes entirely |
| 59 | node.remove(); |
| 60 | } |
| 61 | } catch { |
| 62 | // Invalid URL - remove the iframe |
| 63 | node.remove(); |
| 64 | } |
| 65 | } |
| 66 | }); |
| 67 | |
| 68 | // Hook to validate attributes and block dangerous patterns |
| 69 | DOMPurify.addHook('uponSanitizeAttribute' as any, (_node: Element, data: any) => { |
| 70 | const hookData = data as SanitizeAttributeHookEvent; |
| 71 | // Block javascript: and data: URLs in href/src attributes |
| 72 | if (hookData.attrName === 'href' || hookData.attrName === 'src') { |
| 73 | const value = hookData.attrValue.toLowerCase().trim(); |
| 74 | if ( |
| 75 | value.startsWith('javascript:') || |
| 76 | value.startsWith('data:') || |
| 77 | value.startsWith('vbscript:') |
| 78 | ) { |
| 79 | hookData.keepAttr = false; |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | // Block event handlers (onclick, onerror, etc.) |
| 84 | if (hookData.attrName.startsWith('on')) { |
| 85 | hookData.keepAttr = false; |
| 86 | } |
| 87 | }); |
| 88 | } |
| 89 | |
| 90 | // Initialize DOMPurify configuration once |
| 91 | configureDOMPurify(); |