import { FilterXSS, whiteList } from 'xss'; /** * Sanitizes HTML content to prevent XSS attacks while allowing safe tags and attributes. * This is particularly important for content rendered with dangerouslySetInnerHTML. * * @param html The raw HTML string to sanitize * @returns The sanitized HTML string */ export const sanitizeHtml = (html: string): string => { if (!html) return ''; // Custom options can be added here if we need to allow specific tags or attributes // For now, using default options which are quite secure // Custom options to restrict allowed tags // By NOT spreading ...whiteList, we explicitly only allow what we define const options = { whiteList: { // Text formatting 'p': ['style', 'class'], 'br': [], 'b': [], 'i': [], 'u': [], 'strong': [], 'em': [], 's': [], 'strike': [], 'del': [], 'sub': [], 'sup': [], 'mark': [], 'small': [], // Headings 'h1': ['style', 'class'], 'h2': ['style', 'class'], 'h3': ['style', 'class'], 'h4': ['style', 'class'], 'h5': ['style', 'class'], 'h6': ['style', 'class'], // Lists 'ul': ['style', 'class'], 'ol': ['style', 'class', 'start', 'type'], 'li': ['style', 'class'], // Block elements 'blockquote': ['style', 'class'], 'pre': ['style', 'class'], 'code': ['style', 'class'], 'hr': [], 'div': ['style', 'class'], 'span': ['style', 'class'], // Tables 'table': ['style', 'class', 'width', 'border', 'cellpadding', 'cellspacing'], 'thead': ['style', 'class'], 'tbody': ['style', 'class'], 'tfoot': ['style', 'class'], 'tr': ['style', 'class'], 'th': ['style', 'class', 'colspan', 'rowspan'], 'td': ['style', 'class', 'colspan', 'rowspan'], 'caption': ['style', 'class'], 'colgroup': [], 'col': ['width'], // Links 'a': ['href', 'title', 'target', 'rel'], // Images 'img': ['src', 'alt', 'title', 'width', 'height'], }, stripIgnoreTag: true, stripIgnoreTagBody: ['script'] }; const xssFilter = new FilterXSS(options); return xssFilter.process(html); }; /** * Sanitizes an object by recursively sanitizing all string properties. * Useful for sanitizing request bodies or complex nested structures. * * @param obj The object to sanitize * @returns The sanitized object */ export const sanitizeObject = (obj: T): T => { if (!obj || typeof obj !== 'object') return obj; if (Array.isArray(obj)) { return obj.map(item => sanitizeObject(item)) as any; } const sanitized: any = {}; for (const [key, value] of Object.entries(obj)) { if (typeof value === 'string') { sanitized[key] = sanitizeHtml(value); } else if (typeof value === 'object' && value !== null) { sanitized[key] = sanitizeObject(value); } else { sanitized[key] = value; } } return sanitized as T; };