/** * Security Manager * * Provides security-related utilities including CSRF, XSS protection, and CSP helpers. */ import { Logger } from '../../core/logger.js'; import { CsrfManager } from './CsrfManager.js'; /** * SecurityManager - Centralized security utilities */ export class SecurityManager { constructor(config = {}) { this.config = { csrf: config.csrf || {}, xss: { enabled: config.xss?.enabled ?? true, sanitizeOnInput: config.xss?.sanitizeOnInput ?? false }, csp: { enabled: config.csp?.enabled ?? false, reportOnly: config.csp?.reportOnly ?? false }, ...config }; this.csrfManager = null; // Initialize this.init(); } /** * Create a new SecurityManager instance */ static create(config = {}) { return new SecurityManager(config); } /** * Initialize security manager */ init() { // Initialize CSRF manager this.csrfManager = CsrfManager.create(this.config.csrf); // Initialize XSS protection if enabled if (this.config.xss.enabled) { this.initXssProtection(); } // Initialize CSP if enabled if (this.config.csp.enabled) { this.initCsp(); } Logger.info('[SecurityManager] Initialized', { csrf: !!this.csrfManager, xss: this.config.xss.enabled, csp: this.config.csp.enabled }); } /** * Initialize XSS protection */ initXssProtection() { // Add input sanitization if enabled if (this.config.xss.sanitizeOnInput) { document.addEventListener('input', (event) => { if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') { this.sanitizeInput(event.target); } }, true); } } /** * Sanitize input value */ sanitizeInput(element) { const originalValue = element.value; const sanitized = this.sanitizeHtml(originalValue); if (sanitized !== originalValue) { element.value = sanitized; Logger.warn('[SecurityManager] Sanitized potentially dangerous input', { field: element.name || element.id }); } } /** * Sanitize HTML string */ sanitizeHtml(html) { if (typeof html !== 'string') { return html; } // Remove script tags and event handlers return html .replace(/)<[^<]*)*<\/script>/gi, '') .replace(/on\w+\s*=\s*["'][^"']*["']/gi, '') .replace(/javascript:/gi, '') .replace(/data:text\/html/gi, ''); } /** * Initialize Content Security Policy */ initCsp() { // CSP is typically set server-side, but we can validate it client-side const cspHeader = this.getCspHeader(); if (cspHeader) { Logger.debug('[SecurityManager] CSP header found', cspHeader); this.validateCsp(cspHeader); } else { Logger.warn('[SecurityManager] No CSP header found'); } } /** * Get CSP header from meta tag or response headers */ getCspHeader() { // Try meta tag const metaTag = document.querySelector('meta[http-equiv="Content-Security-Policy"]'); if (metaTag) { return metaTag.getAttribute('content'); } // Note: Response headers are not accessible from JavaScript // This would need to be passed from server or checked server-side return null; } /** * Validate CSP header */ validateCsp(cspHeader) { // Basic validation - check for common security directives const requiredDirectives = ['default-src', 'script-src', 'style-src']; const directives = cspHeader.split(';').map(d => d.trim().split(' ')[0]); const missing = requiredDirectives.filter(dir => !directives.some(d => d.toLowerCase() === dir.toLowerCase()) ); if (missing.length > 0) { Logger.warn('[SecurityManager] CSP missing recommended directives', missing); } } /** * Get CSRF token */ getCsrfToken() { return this.csrfManager?.getToken() || null; } /** * Get CSRF token header */ getCsrfTokenHeader() { return this.csrfManager?.getTokenHeader() || {}; } /** * Refresh CSRF token */ async refreshCsrfToken() { if (this.csrfManager) { return await this.csrfManager.refreshToken(); } } /** * Validate security headers */ validateSecurityHeaders() { const issues = []; // Check for HTTPS if (location.protocol !== 'https:' && location.hostname !== 'localhost') { issues.push('Not using HTTPS'); } // Check for CSP if (!this.getCspHeader()) { issues.push('No Content Security Policy header'); } // Check for X-Frame-Options // Note: Headers are not accessible from JavaScript, would need server-side check return { valid: issues.length === 0, issues }; } /** * Escape HTML to prevent XSS */ escapeHtml(text) { if (typeof text !== 'string') { return text; } const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; return text.replace(/[&<>"']/g, m => map[m]); } /** * Validate URL to prevent XSS */ validateUrl(url) { try { const parsed = new URL(url, window.location.origin); // Block javascript: and data: URLs if (parsed.protocol === 'javascript:' || parsed.protocol === 'data:') { return false; } return true; } catch { return false; } } /** * Destroy security manager */ destroy() { if (this.csrfManager) { this.csrfManager.destroy(); this.csrfManager = null; } Logger.info('[SecurityManager] Destroyed'); } }