/** * HTML Sanitization Module * * Provides secure HTML rendering with DOMPurify integration. * Ensures consistent XSS protection across the application. */ /** * Sanitize HTML content for safe rendering * * @param {string} html - HTML to sanitize * @param {object} options - DOMPurify options * @returns {string} Sanitized HTML */ export function sanitizeHtml(html, options = {}) { const defaultOptions = { ALLOWED_TAGS: [ 'div', 'span', 'p', 'a', 'button', 'input', 'textarea', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'table', 'tr', 'td', 'th', 'thead', 'tbody', 'strong', 'em', 'code', 'pre', 'blockquote', 'svg', 'img' ], ALLOWED_ATTR: [ 'class', 'id', 'style', 'data-*', 'href', 'target', 'rel', 'type', 'placeholder', 'disabled', 'checked', 'name', 'value', 'alt', 'src', 'width', 'height', 'aria-label', 'aria-describedby', 'aria-invalid', 'role' ], KEEP_CONTENT: true, RETURN_DOM: false }; const mergedOptions = { ...defaultOptions, ...options }; // Use DOMPurify if available (loaded in HTML) if (typeof DOMPurify !== 'undefined') { return DOMPurify.sanitize(html, mergedOptions); } // Fallback: escape HTML (basic protection) console.warn('DOMPurify not available, using basic HTML escaping'); return escapeHtml(html); } /** * Escape HTML special characters (basic XSS protection) * * @param {string} text - Text to escape * @returns {string} Escaped text */ export function escapeHtml(text) { const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; return text.replace(/[&<>"']/g, char => map[char]); } /** * Safely set innerHTML on an element * * @param {HTMLElement} element - Target element * @param {string} html - HTML to set (will be sanitized) * @param {object} options - Sanitization options */ export function setSafeHtml(element, html, options = {}) { if (!element) { console.warn('setSafeHtml: element is null or undefined'); return; } element.innerHTML = sanitizeHtml(html, options); } /** * Sanitize text for safe display (no HTML) * * @param {string} text - Text to sanitize * @returns {string} Sanitized text */ export function sanitizeText(text) { return escapeHtml(String(text || '')); } /** * Sanitize URL for safe linking * * @param {string} url - URL to sanitize * @returns {string} Safe URL or empty string */ export function sanitizeUrl(url) { try { // Only allow safe protocols const allowedProtocols = ['http:', 'https:', 'mailto:', 'tel:', 'data:']; const urlObj = new URL(url, window.location.href); if (allowedProtocols.includes(urlObj.protocol)) { return url; } console.warn(`Unsafe URL protocol: ${urlObj.protocol}`); return ''; } catch (e) { console.warn(`Invalid URL: ${url}`); return ''; } } /** * Create safe HTML element from template literal * * Usage: * const html = createSafeHtml` *
${description}
*