Files
thrilltrack-explorer/src-old/lib/sanitize.ts

100 lines
2.4 KiB
TypeScript

/**
* Input Sanitization Utilities
*
* Provides XSS protection for user-generated content.
* All user input should be sanitized before rendering to prevent injection attacks.
*/
import DOMPurify from 'dompurify';
import { logger } from './logger';
/**
* Sanitize HTML content to prevent XSS attacks
*
* @param html - Raw HTML string from user input
* @returns Sanitized HTML safe for rendering
*/
export function sanitizeHTML(html: string): string {
return DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'u', 'a', 'ul', 'ol', 'li'],
ALLOWED_ATTR: ['href', 'target', 'rel'],
ALLOW_DATA_ATTR: false,
});
}
/**
* Sanitize URL to prevent javascript: and data: protocol injection
*
* @param url - URL from user input
* @returns Sanitized URL or '#' if invalid
*/
export function sanitizeURL(url: string): string {
if (!url || typeof url !== 'string') {
return '#';
}
try {
const parsed = new URL(url);
// Only allow http, https, and mailto protocols
const allowedProtocols = ['http:', 'https:', 'mailto:'];
if (!allowedProtocols.includes(parsed.protocol)) {
logger.warn('Blocked potentially dangerous URL protocol', { protocol: parsed.protocol });
return '#';
}
return url;
} catch {
// Invalid URL format
logger.warn('Invalid URL format', { url });
return '#';
}
}
/**
* Sanitize plain text to prevent any HTML rendering
* Escapes all HTML entities
*
* @param text - Plain text from user input
* @returns Escaped text safe for rendering
*/
export function sanitizePlainText(text: string): string {
if (!text || typeof text !== 'string') {
return '';
}
return text
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#x27;')
.replace(/\//g, '&#x2F;');
}
/**
* Check if a string contains potentially dangerous content
* Used for validation before sanitization
*
* @param input - User input to check
* @returns true if input contains suspicious patterns
*/
export function containsSuspiciousContent(input: string): boolean {
if (!input || typeof input !== 'string') {
return false;
}
const suspiciousPatterns = [
/<script/i,
/javascript:/i,
/on\w+\s*=/i, // Event handlers like onclick=
/<iframe/i,
/<object/i,
/<embed/i,
/data:text\/html/i,
];
return suspiciousPatterns.some(pattern => pattern.test(input));
}