/** * Security Audit Logging Module * * Provides comprehensive security event logging for ZCLAW application. * All security-relevant events are logged with timestamps and details. * * Security events logged: * - Authentication events (login, logout, failed attempts) * - API key operations (access, rotation, deletion) * - Data access events (encrypted data read/write) * - Security violations (failed decryption, tampering attempts) * - Configuration changes */ import { hashSha256, generateRandomString } from './crypto-utils'; // ============================================================================ // Types // ============================================================================ export type SecurityEventType = | 'auth_login' | 'auth_logout' | 'auth_failed' | 'auth_token_refresh' | 'key_accessed' | 'key_stored' | 'key_deleted' | 'key_rotated' | 'data_encrypted' | 'data_decrypted' | 'data_access' | 'data_export' | 'data_import' | 'security_violation' | 'decryption_failed' | 'integrity_check_failed' | 'config_changed' | 'permission_granted' | 'permission_denied' | 'session_started' | 'session_ended' | 'rate_limit_exceeded' | 'suspicious_activity' | 'hand_executed' | 'hand_approved' | 'hand_denied' | 'skill_executed'; export type SecurityEventSeverity = 'info' | 'warning' | 'error' | 'critical'; export interface SecurityEvent { id: string; type: SecurityEventType; severity: SecurityEventSeverity; timestamp: string; message: string; details: Record; userAgent?: string; ip?: string; sessionId?: string; agentId?: string; } export interface SecurityAuditReport { generatedAt: string; totalEvents: number; eventsByType: Record; eventsBySeverity: Record; recentCriticalEvents: SecurityEvent[]; recommendations: string[]; } // ============================================================================ // Constants // ============================================================================ const SECURITY_LOG_KEY = 'zclaw_security_audit_log'; const MAX_LOG_ENTRIES = 2000; const AUDIT_VERSION = 1; // ============================================================================ // Internal State // ============================================================================ let isAuditEnabled: boolean = true; let currentSessionId: string | null = null; // ============================================================================ // Core Functions // ============================================================================ /** * Generate a unique event ID */ function generateEventId(): string { return `evt_${Date.now()}_${generateRandomString(8)}`; } /** * Get the current session ID */ export function getCurrentSessionId(): string | null { return currentSessionId; } /** * Set the current session ID */ export function setCurrentSessionId(sessionId: string | null): void { currentSessionId = sessionId; } /** * Enable or disable audit logging */ export function setAuditEnabled(enabled: boolean): void { isAuditEnabled = enabled; logSecurityEventInternal('config_changed', 'info', `Audit logging ${enabled ? 'enabled' : 'disabled'}`, {}); } /** * Check if audit logging is enabled */ export function isAuditEnabledState(): boolean { return isAuditEnabled; } /** * Internal function to persist security events */ function persistEvent(event: SecurityEvent): void { try { const events = getStoredEvents(); events.push(event); // Trim old entries if needed if (events.length > MAX_LOG_ENTRIES) { events.splice(0, events.length - MAX_LOG_ENTRIES); } localStorage.setItem(SECURITY_LOG_KEY, JSON.stringify(events)); } catch (e) { // Ignore persistence failures to prevent application disruption // eslint-disable-next-line no-console if (process.env.NODE_ENV === 'development') { console.warn('[SecurityAudit] Failed to persist security event', e); } } } /** * Get stored security events */ function getStoredEvents(): SecurityEvent[] { try { const stored = localStorage.getItem(SECURITY_LOG_KEY); if (!stored) return []; return JSON.parse(stored) as SecurityEvent[]; } catch (e) { // eslint-disable-next-line no-console if (process.env.NODE_ENV === 'development') { console.warn('[SecurityAudit] Failed to read security events', e); } return []; } } /** * Determine severity based on event type */ function getDefaultSeverity(type: SecurityEventType): SecurityEventSeverity { const severityMap: Record = { auth_login: 'info', auth_logout: 'info', auth_failed: 'warning', auth_token_refresh: 'info', key_accessed: 'info', key_stored: 'info', key_deleted: 'warning', key_rotated: 'info', data_encrypted: 'info', data_decrypted: 'info', data_access: 'info', data_export: 'warning', data_import: 'warning', security_violation: 'critical', decryption_failed: 'error', integrity_check_failed: 'critical', config_changed: 'warning', permission_granted: 'info', permission_denied: 'warning', session_started: 'info', session_ended: 'info', rate_limit_exceeded: 'warning', suspicious_activity: 'critical', hand_executed: 'info', hand_approved: 'info', hand_denied: 'warning', skill_executed: 'info', }; return severityMap[type] || 'info'; } /** * Internal function to log security events */ function logSecurityEventInternal( type: SecurityEventType, severity: SecurityEventSeverity, message: string, details: Record ): void { if (!isAuditEnabled && type !== 'config_changed') { return; } const event: SecurityEvent = { id: generateEventId(), type, severity, timestamp: new Date().toISOString(), message, details, sessionId: currentSessionId || undefined, }; // Add user agent if in browser if (typeof navigator !== 'undefined') { event.userAgent = navigator.userAgent; } persistEvent(event); // Log to console for development if (process.env.NODE_ENV === 'development') { const logMethod = severity === 'critical' || severity === 'error' ? 'error' : severity === 'warning' ? 'warn' : 'log'; console[logMethod](`[SecurityAudit] ${type}: ${message}`, details); } } // ============================================================================ // Public API // ============================================================================ /** * Log a security event */ export function logSecurityEvent( type: SecurityEventType, message: string, details: Record = {}, severity?: SecurityEventSeverity ): void { const eventSeverity = severity || getDefaultSeverity(type); logSecurityEventInternal(type, eventSeverity, message, details); } /** * Log authentication event */ export function logAuthEvent( type: 'auth_login' | 'auth_logout' | 'auth_failed' | 'auth_token_refresh', message: string, details: Record = {} ): void { logSecurityEvent(type, message, details); } /** * Log key management event */ export function logKeyEvent( type: 'key_accessed' | 'key_stored' | 'key_deleted' | 'key_rotated', message: string, details: Record = {} ): void { logSecurityEvent(type, message, details); } /** * Log data access event */ export function logDataEvent( type: 'data_encrypted' | 'data_decrypted' | 'data_access' | 'data_export' | 'data_import', message: string, details: Record = {} ): void { logSecurityEvent(type, message, details); } /** * Log security violation */ export function logSecurityViolation( message: string, details: Record = {} ): void { logSecurityEvent('security_violation', message, details, 'critical'); } /** * Log decryption failure */ export function logDecryptionFailure( message: string, details: Record = {} ): void { logSecurityEvent('decryption_failed', message, details, 'error'); } /** * Log integrity check failure */ export function logIntegrityFailure( message: string, details: Record = {} ): void { logSecurityEvent('integrity_check_failed', message, details, 'critical'); } /** * Log permission event */ export function logPermissionEvent( type: 'permission_granted' | 'permission_denied', message: string, details: Record = {} ): void { logSecurityEvent(type, message, details); } /** * Log session event */ export function logSessionEvent( type: 'session_started' | 'session_ended', message: string, details: Record = {} ): void { logSecurityEvent(type, message, details); } /** * Log suspicious activity */ export function logSuspiciousActivity( message: string, details: Record = {} ): void { logSecurityEvent('suspicious_activity', message, details, 'critical'); } /** * Log rate limit event */ export function logRateLimitEvent( message: string, details: Record = {} ): void { logSecurityEvent('rate_limit_exceeded', message, details, 'warning'); } // ============================================================================ // Query Functions // ============================================================================ /** * Get all security events */ export function getSecurityEvents(): SecurityEvent[] { return getStoredEvents(); } /** * Get security events by type */ export function getSecurityEventsByType(type: SecurityEventType): SecurityEvent[] { return getStoredEvents().filter(event => event.type === type); } /** * Get security events by severity */ export function getSecurityEventsBySeverity(severity: SecurityEventSeverity): SecurityEvent[] { return getStoredEvents().filter(event => event.severity === severity); } /** * Get security events within a time range */ export function getSecurityEventsByTimeRange(start: Date, end: Date): SecurityEvent[] { const startTime = start.getTime(); const endTime = end.getTime(); return getStoredEvents().filter(event => { const eventTime = new Date(event.timestamp).getTime(); return eventTime >= startTime && eventTime <= endTime; }); } /** * Get recent critical events */ export function getRecentCriticalEvents(count: number = 10): SecurityEvent[] { return getStoredEvents() .filter(event => event.severity === 'critical' || event.severity === 'error') .slice(-count); } /** * Get events for a specific session */ export function getSecurityEventsBySession(sessionId: string): SecurityEvent[] { return getStoredEvents().filter(event => event.sessionId === sessionId); } // ============================================================================ // Report Generation // ============================================================================ /** * Generate a security audit report */ export function generateSecurityAuditReport(): SecurityAuditReport { const events = getStoredEvents(); const eventsByType = Object.create(null) as Record; const eventsBySeverity: Record = { info: 0, warning: 0, error: 0, critical: 0, }; for (const event of events) { eventsByType[event.type] = (eventsByType[event.type] || 0) + 1; eventsBySeverity[event.severity]++; } const recentCriticalEvents = getRecentCriticalEvents(10); const recommendations: string[] = []; // Generate recommendations based on findings if (eventsBySeverity.critical > 0) { recommendations.push('Investigate critical security events immediately'); } if ((eventsByType.auth_failed || 0) > 5) { recommendations.push('Multiple failed authentication attempts detected - consider rate limiting'); } if ((eventsByType.decryption_failed || 0) > 3) { recommendations.push('Multiple decryption failures - check key integrity'); } if ((eventsByType.suspicious_activity || 0) > 0) { recommendations.push('Suspicious activity detected - review access logs'); } if (events.length === 0) { recommendations.push('No security events recorded - ensure audit logging is enabled'); } return { generatedAt: new Date().toISOString(), totalEvents: events.length, eventsByType, eventsBySeverity, recentCriticalEvents, recommendations, }; } // ============================================================================ // Maintenance Functions // ============================================================================ /** * Clear all security events */ export function clearSecurityAuditLog(): void { localStorage.removeItem(SECURITY_LOG_KEY); logSecurityEventInternal('config_changed', 'warning', 'Security audit log cleared', {}); } /** * Export security events for external analysis */ export function exportSecurityEvents(): string { const events = getStoredEvents(); return JSON.stringify({ version: AUDIT_VERSION, exportedAt: new Date().toISOString(), events, }, null, 2); } /** * Import security events from external source */ export function importSecurityEvents(jsonData: string, merge: boolean = false): void { try { const data = JSON.parse(jsonData); const importedEvents = data.events as SecurityEvent[]; if (!importedEvents || !Array.isArray(importedEvents)) { throw new Error('Invalid import data format'); } if (merge) { const existingEvents = getStoredEvents(); const mergedEvents = [...existingEvents, ...importedEvents]; localStorage.setItem(SECURITY_LOG_KEY, JSON.stringify(mergedEvents.slice(-MAX_LOG_ENTRIES))); } else { localStorage.setItem(SECURITY_LOG_KEY, JSON.stringify(importedEvents.slice(-MAX_LOG_ENTRIES))); } logSecurityEventInternal('data_import', 'warning', `Imported ${importedEvents.length} security events`, { merge, sourceVersion: data.version, }); } catch (error) { logSecurityEventInternal('security_violation', 'error', 'Failed to import security events', { error: error instanceof Error ? error.message : String(error), }); throw error; } } /** * Verify audit log integrity */ export async function verifyAuditLogIntegrity(): Promise<{ valid: boolean; eventCount: number; hash: string; }> { const events = getStoredEvents(); const data = JSON.stringify(events); const hash = await hashSha256(data); return { valid: events.length > 0, eventCount: events.length, hash, }; } // ============================================================================ // Initialization // ============================================================================ /** * Initialize the security audit module */ export function initializeSecurityAudit(sessionId?: string): void { if (sessionId) { currentSessionId = sessionId; } logSecurityEventInternal('session_started', 'info', 'Security audit session started', { sessionId: currentSessionId, auditEnabled: isAuditEnabled, }); } /** * Shutdown the security audit module */ export function shutdownSecurityAudit(): void { logSecurityEventInternal('session_ended', 'info', 'Security audit session ended', { sessionId: currentSessionId, }); currentSessionId = null; }