/** * Basic HTML entity encoding for server-side sanitization * Escapes dangerous characters to prevent XSS */ function escapeHtml(text: string): string { const map: Record = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", "/": "/", }; return text.replace(/[&<>"'/]/g, (char) => map[char]); } /** * Sanitizes HTML content to prevent XSS attacks * @param dirty - The potentially unsafe HTML string * @param allowedTags - Optional array of allowed HTML tags (only works client-side) * @returns Sanitized HTML string safe for rendering */ export function sanitizeHtml( dirty: string | undefined | null, allowedTags?: string[] ): string { if (!dirty) return ""; // Server-side: escape all HTML if (typeof window === "undefined") { return escapeHtml(dirty); } // Client-side: use DOMPurify const DOMPurify = require("dompurify"); const config = { ALLOWED_TAGS: allowedTags || ["b", "i", "em", "strong", "a", "br", "p"], ALLOWED_ATTR: ["href", "target", "rel"], ALLOW_DATA_ATTR: false, }; return DOMPurify.sanitize(dirty, config); } /** * Sanitizes plain text by stripping all HTML tags * Use this for text that should never contain HTML * @param dirty - The potentially unsafe string * @returns Plain text with all HTML removed */ export function sanitizeText(dirty: string | undefined | null): string { if (!dirty) return ""; // Server-side: escape HTML entities if (typeof window === "undefined") { return escapeHtml(dirty); } // Client-side: use DOMPurify const DOMPurify = require("dompurify"); return DOMPurify.sanitize(dirty, { ALLOWED_TAGS: [], ALLOWED_ATTR: [], }); } /** * Sanitizes a URL to prevent javascript: and data: URI XSS attacks * @param url - The potentially unsafe URL * @returns Sanitized URL or empty string if invalid */ export function sanitizeUrl(url: string | undefined | null): string { if (!url) return ""; // Check for dangerous protocols first const lowerUrl = url.toLowerCase().trim(); if ( lowerUrl.startsWith("javascript:") || lowerUrl.startsWith("data:") || lowerUrl.startsWith("vbscript:") || lowerUrl.includes("