/**
* 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("