UNPKG

codecrucible-synth

Version:

Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability

1,088 lines (965 loc) 27.9 kB
/** * Enterprise Security Audit Logging System * Comprehensive security event logging with tamper protection and compliance features */ import crypto from 'crypto'; import fs from 'fs/promises'; import path from 'path'; import { logger } from '../logger.js'; import { SecretsManager } from './secrets-manager.js'; export enum AuditEventType { AUTHENTICATION = 'authentication', AUTHORIZATION = 'authorization', DATA_ACCESS = 'data_access', PRIVILEGE_ESCALATION = 'privilege_escalation', SECURITY_VIOLATION = 'security_violation', CONFIG_CHANGE = 'config_change', USER_MANAGEMENT = 'user_management', SESSION_MANAGEMENT = 'session_management', API_ACCESS = 'api_access', SYSTEM_EVENT = 'system_event', ERROR_EVENT = 'error_event', SECURITY_SCAN = 'security_scan', } export enum AuditSeverity { LOW = 'low', MEDIUM = 'medium', HIGH = 'high', CRITICAL = 'critical', } export enum AuditOutcome { SUCCESS = 'success', FAILURE = 'failure', WARNING = 'warning', ERROR = 'error', } export interface AuditContext { userId?: string; sessionId?: string; ipAddress?: string; userAgent?: string; correlationId?: string; requestId?: string; executionId?: string; } export interface AuditEvent { id: string; timestamp: Date; eventType: AuditEventType; severity: AuditSeverity; outcome: AuditOutcome; source: string; action: string; resource: string; description: string; context: AuditContext; details: Record<string, any>; checksum: string; previousEventHash?: string; } export interface SecurityMetrics { totalEvents: number; eventsByType: Record<AuditEventType, number>; eventsBySeverity: Record<AuditSeverity, number>; failureRate: number; securityViolations: number; lastHour: number; lastDay: number; lastWeek: number; } export interface AlertRule { id: string; name: string; description: string; conditions: { eventType?: AuditEventType[]; severity?: AuditSeverity[]; outcome?: AuditOutcome[]; pattern?: RegExp; threshold?: { count: number; timeWindow: number; // milliseconds }; }; actions: { email?: string[]; webhook?: string; block?: boolean; escalate?: boolean; }; enabled: boolean; lastTriggered?: Date; triggerCount: number; } export class SecurityAuditLogger { private secretsManager: SecretsManager; private auditLogPath: string; private events: AuditEvent[] = []; private alertRules: Map<string, AlertRule> = new Map(); private alertBuffer: Map<string, AuditEvent[]> = new Map(); private eventBuffer: AuditEvent[] = []; private flushInterval?: NodeJS.Timeout; private lastEventHash: string = ''; private signingKey: Buffer; private rotationInterval: number = 24 * 60 * 60 * 1000; // 24 hours private maxEventsInMemory: number = 10000; private compressionEnabled: boolean = true; constructor(secretsManager: SecretsManager, auditLogPath: string = './audit-logs') { this.secretsManager = secretsManager; this.auditLogPath = auditLogPath; this.signingKey = crypto.randomBytes(32); } /** * Initialize audit logging system */ async initialize(): Promise<void> { try { // Ensure audit log directory exists await fs.mkdir(this.auditLogPath, { recursive: true }); // Load or generate signing key await this.loadSigningKey(); // Load existing events await this.loadRecentEvents(); // Setup default alert rules await this.setupDefaultAlertRules(); // Start log rotation timer setInterval(() => this.rotateLogFiles(), this.rotationInterval); // TODO: Store interval ID and call clearInterval in cleanup logger.info('Security audit logging initialized', { auditLogPath: this.auditLogPath, eventsLoaded: this.events.length, alertRules: this.alertRules.size, }); } catch (error) { logger.error('Failed to initialize security audit logging', error as Error); throw error; } } /** * Log security audit event */ async logEvent( eventType: AuditEventType, severity: AuditSeverity, outcome: AuditOutcome, source: string, action: string, resource: string, description: string, context: AuditContext = {}, details: Record<string, any> = {} ): Promise<string> { try { const event = this.createAuditEvent( eventType, severity, outcome, source, action, resource, description, context, details ); // Add to in-memory buffer this.events.push(event); // Maintain buffer size if (this.events.length > this.maxEventsInMemory) { this.events.shift(); } // Persist to disk await this.persistEvent(event); // Check alert rules await this.evaluateAlerts(event); // Update chain hash this.lastEventHash = event.checksum; logger.debug('Audit event logged', { id: event.id, eventType, severity, outcome, }); return event.id; } catch (error) { logger.error('Failed to log audit event', error as Error, { eventType, severity, outcome, source, action, }); throw error; } } /** * Log authentication event */ async logAuthentication( outcome: AuditOutcome, username: string, context: AuditContext, details: Record<string, any> = {} ): Promise<string> { return this.logEvent( AuditEventType.AUTHENTICATION, outcome === AuditOutcome.FAILURE ? AuditSeverity.HIGH : AuditSeverity.LOW, outcome, 'auth-system', 'authenticate', `user:${username}`, `User authentication ${outcome}`, context, { username, ...details } ); } /** * Log authorization event */ async logAuthorization( outcome: AuditOutcome, userId: string, resource: string, action: string, context: AuditContext, details: Record<string, any> = {} ): Promise<string> { const severity = outcome === AuditOutcome.FAILURE ? AuditSeverity.MEDIUM : AuditSeverity.LOW; return this.logEvent( AuditEventType.AUTHORIZATION, severity, outcome, 'rbac-system', action, resource, `Authorization ${outcome} for ${action} on ${resource}`, { ...context, userId }, details ); } /** * Log security violation */ async logSecurityViolation( severity: AuditSeverity, source: string, violation: string, context: AuditContext, details: Record<string, any> = {} ): Promise<string> { return this.logEvent( AuditEventType.SECURITY_VIOLATION, severity, AuditOutcome.WARNING, source, 'security_check', 'system', `Security violation detected: ${violation}`, context, { violation, ...details } ); } /** * Log data access event */ async logDataAccess( outcome: AuditOutcome, userId: string, resource: string, action: string, context: AuditContext, details: Record<string, any> = {} ): Promise<string> { return this.logEvent( AuditEventType.DATA_ACCESS, AuditSeverity.LOW, outcome, 'data-access', action, resource, `Data access ${outcome}: ${action} on ${resource}`, { ...context, userId }, details ); } /** * Log configuration change */ async logConfigChange( userId: string, configKey: string, oldValue: any, newValue: any, context: AuditContext ): Promise<string> { return this.logEvent( AuditEventType.CONFIG_CHANGE, AuditSeverity.MEDIUM, AuditOutcome.SUCCESS, 'config-system', 'update', `config:${configKey}`, `Configuration changed: ${configKey}`, { ...context, userId }, { configKey, oldValue: this.sanitizeValue(oldValue), newValue: this.sanitizeValue(newValue), } ); } /** * Log API access */ async logAPIAccess( outcome: AuditOutcome, apiKey: string, endpoint: string, method: string, context: AuditContext, details: Record<string, any> = {} ): Promise<string> { return this.logEvent( AuditEventType.API_ACCESS, AuditSeverity.LOW, outcome, 'api-gateway', method.toLowerCase(), endpoint, `API access ${outcome}: ${method} ${endpoint}`, context, { apiKeyId: apiKey.substring(0, 8) + '...', method, ...details } ); } /** * Query audit events */ async queryEvents(criteria: { eventType?: AuditEventType[]; severity?: AuditSeverity[]; outcome?: AuditOutcome[]; userId?: string; resource?: string; startTime?: Date; endTime?: Date; limit?: number; offset?: number; }): Promise<AuditEvent[]> { try { let filteredEvents = [...this.events]; // Apply filters if (criteria.eventType) { filteredEvents = filteredEvents.filter(e => criteria.eventType!.includes(e.eventType)); } if (criteria.severity) { filteredEvents = filteredEvents.filter(e => criteria.severity!.includes(e.severity)); } if (criteria.outcome) { filteredEvents = filteredEvents.filter(e => criteria.outcome!.includes(e.outcome)); } if (criteria.userId) { filteredEvents = filteredEvents.filter(e => e.context.userId === criteria.userId); } if (criteria.resource) { filteredEvents = filteredEvents.filter(e => e.resource.includes(criteria.resource!)); } if (criteria.startTime) { filteredEvents = filteredEvents.filter(e => e.timestamp >= criteria.startTime!); } if (criteria.endTime) { filteredEvents = filteredEvents.filter(e => e.timestamp <= criteria.endTime!); } // Sort by timestamp (newest first) filteredEvents.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); // Apply pagination const offset = criteria.offset || 0; const limit = criteria.limit || 100; return filteredEvents.slice(offset, offset + limit); } catch (error) { logger.error('Failed to query audit events', error as Error, criteria); throw error; } } /** * Get security metrics */ getSecurityMetrics(): SecurityMetrics { const now = new Date(); const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000); const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000); const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); const eventsByType = {} as Record<AuditEventType, number>; const eventsBySeverity = {} as Record<AuditSeverity, number>; let failures = 0; let securityViolations = 0; let lastHour = 0; let lastDay = 0; let lastWeek = 0; for (const event of this.events) { // Count by type eventsByType[event.eventType] = (eventsByType[event.eventType] || 0) + 1; // Count by severity eventsBySeverity[event.severity] = (eventsBySeverity[event.severity] || 0) + 1; // Count failures if (event.outcome === AuditOutcome.FAILURE || event.outcome === AuditOutcome.ERROR) { failures++; } // Count security violations if (event.eventType === AuditEventType.SECURITY_VIOLATION) { securityViolations++; } // Time-based counts if (event.timestamp >= oneHourAgo) lastHour++; if (event.timestamp >= oneDayAgo) lastDay++; if (event.timestamp >= oneWeekAgo) lastWeek++; } return { totalEvents: this.events.length, eventsByType, eventsBySeverity, failureRate: this.events.length > 0 ? failures / this.events.length : 0, securityViolations, lastHour, lastDay, lastWeek, }; } /** * Create alert rule */ async createAlertRule( rule: Omit<AlertRule, 'id' | 'lastTriggered' | 'triggerCount'> ): Promise<string> { const id = crypto.randomBytes(16).toString('hex'); const alertRule: AlertRule = { id, lastTriggered: undefined, triggerCount: 0, ...rule, }; this.alertRules.set(id, alertRule); // Persist alert rule await this.secretsManager.storeSecret(`alert_rule_${id}`, JSON.stringify(alertRule), { description: `Alert rule: ${rule.name}`, tags: ['alert', 'security'], }); logger.info('Alert rule created', { id, name: rule.name }); return id; } /** * Export audit log for compliance */ async exportAuditLog( startDate: Date, endDate: Date, format: 'json' | 'csv' = 'json' ): Promise<string> { try { const events = await this.queryEvents({ startTime: startDate, endTime: endDate, limit: 100000, // Large limit for export }); if (format === 'csv') { return this.exportToCSV(events); } else { return JSON.stringify(events, null, 2); } } catch (error) { logger.error('Failed to export audit log', error as Error, { startDate, endDate, format }); throw error; } } /** * Verify audit log integrity */ async verifyIntegrity(): Promise<{ valid: boolean; totalEvents: number; verifiedEvents: number; tampered: string[]; missingChain: number; }> { try { const result = { valid: true, totalEvents: this.events.length, verifiedEvents: 0, tampered: [] as string[], missingChain: 0, }; let previousHash = ''; for (const event of this.events) { // Verify event checksum const calculatedChecksum = this.calculateChecksum(event); if (calculatedChecksum !== event.checksum) { result.tampered.push(event.id); result.valid = false; } else { result.verifiedEvents++; } // Verify chain integrity if (event.previousEventHash !== previousHash) { result.missingChain++; result.valid = false; } previousHash = event.checksum; } logger.info('Audit log integrity verification completed', result); return result; } catch (error) { logger.error('Failed to verify audit log integrity', error as Error); throw error; } } /** * Create audit event */ private createAuditEvent( eventType: AuditEventType, severity: AuditSeverity, outcome: AuditOutcome, source: string, action: string, resource: string, description: string, context: AuditContext, details: Record<string, any> ): AuditEvent { const id = crypto.randomBytes(16).toString('hex'); const timestamp = new Date(); const event: Omit<AuditEvent, 'checksum'> = { id, timestamp, eventType, severity, outcome, source, action, resource, description, context, details, previousEventHash: this.lastEventHash, }; const checksum = this.calculateChecksum(event as AuditEvent); return { ...event, checksum, }; } /** * Calculate event checksum for integrity */ private calculateChecksum(event: Omit<AuditEvent, 'checksum'>): string { const data = JSON.stringify(event, Object.keys(event).sort()); return crypto.createHmac('sha256', this.signingKey).update(data).digest('hex'); } /** * Persist event to disk */ private async persistEvent(event: AuditEvent): Promise<void> { try { const logFile = path.join( this.auditLogPath, `audit-${new Date().toISOString().slice(0, 10)}.log` ); const logEntry = JSON.stringify(event) + '\n'; await fs.appendFile(logFile, logEntry); } catch (error) { logger.error('Failed to persist audit event', error as Error, { eventId: event.id }); throw error; } } /** * Load recent events from disk */ private async loadRecentEvents(): Promise<void> { try { const files = await fs.readdir(this.auditLogPath); const logFiles = files .filter(file => file.startsWith('audit-') && file.endsWith('.log')) .sort() .slice(-7); // Last 7 days this.events = []; for (const file of logFiles) { const filePath = path.join(this.auditLogPath, file); const content = await fs.readFile(filePath, 'utf8'); const lines = content .trim() .split('\n') .filter(line => line); for (const line of lines) { try { const event = JSON.parse(line) as AuditEvent; this.events.push(event); } catch (error) { logger.warn('Failed to parse audit log line', { file, line: line.substring(0, 100) }); } } } // Update last event hash if (this.events.length > 0) { this.lastEventHash = this.events[this.events.length - 1].checksum; } logger.info('Recent audit events loaded', { filesLoaded: logFiles.length, eventsLoaded: this.events.length, }); } catch (error) { logger.error('Failed to load recent events', error as Error); // Continue without historical events } } /** * Load or generate signing key */ private async loadSigningKey(): Promise<void> { try { const keySecret = await this.secretsManager.getSecret('audit_signing_key'); if (keySecret) { this.signingKey = Buffer.from(keySecret, 'hex'); } else { // Generate new key this.signingKey = crypto.randomBytes(32); await this.secretsManager.storeSecret( 'audit_signing_key', this.signingKey.toString('hex'), { description: 'Audit log signing key', tags: ['audit', 'security', 'signing'], } ); } } catch (error) { logger.error('Failed to load signing key', error as Error); throw error; } } /** * Setup default alert rules */ private async setupDefaultAlertRules(): Promise<void> { const defaultRules = [ { name: 'High Security Violations', description: 'Alert on high severity security violations', conditions: { eventType: [AuditEventType.SECURITY_VIOLATION], severity: [AuditSeverity.HIGH, AuditSeverity.CRITICAL], }, actions: { email: ['security@company.com'], escalate: true, }, enabled: true, }, { name: 'Failed Authentication Attempts', description: 'Alert on multiple failed authentication attempts', conditions: { eventType: [AuditEventType.AUTHENTICATION], outcome: [AuditOutcome.FAILURE], threshold: { count: 5, timeWindow: 5 * 60 * 1000, // 5 minutes }, }, actions: { email: ['security@company.com'], block: true, }, enabled: true, }, { name: 'Privilege Escalation', description: 'Alert on privilege escalation attempts', conditions: { eventType: [AuditEventType.PRIVILEGE_ESCALATION], }, actions: { email: ['security@company.com'], escalate: true, }, enabled: true, }, ]; for (const rule of defaultRules) { const existingRule = Array.from(this.alertRules.values()).find(r => r.name === rule.name); if (!existingRule) { await this.createAlertRule(rule); } } } /** * Evaluate alert rules against new event */ private async evaluateAlerts(event: AuditEvent): Promise<void> { for (const [ruleId, rule] of this.alertRules.entries()) { if (!rule.enabled) continue; try { if (this.eventMatchesRule(event, rule)) { await this.triggerAlert(rule, event); } } catch (error) { logger.error('Failed to evaluate alert rule', error as Error, { ruleId, ruleName: rule.name, }); } } } /** * Check if event matches alert rule */ private eventMatchesRule(event: AuditEvent, rule: AlertRule): boolean { const { conditions } = rule; // Check event type if (conditions.eventType && !conditions.eventType.includes(event.eventType)) { return false; } // Check severity if (conditions.severity && !conditions.severity.includes(event.severity)) { return false; } // Check outcome if (conditions.outcome && !conditions.outcome.includes(event.outcome)) { return false; } // Check pattern if (conditions.pattern) { const text = `${event.description} ${JSON.stringify(event.details)}`; if (!conditions.pattern.test(text)) { return false; } } // Check threshold if (conditions.threshold) { return this.checkThreshold(rule, event); } return true; } /** * Check threshold-based conditions */ private checkThreshold(rule: AlertRule, event: AuditEvent): boolean { const { threshold } = rule.conditions; if (!threshold) return true; const bufferKey = rule.id; const buffer = this.alertBuffer.get(bufferKey) || []; // Add current event buffer.push(event); // Remove events outside time window const cutoff = new Date(Date.now() - threshold.timeWindow); const relevantEvents = buffer.filter(e => e.timestamp >= cutoff); this.alertBuffer.set(bufferKey, relevantEvents); return relevantEvents.length >= threshold.count; } /** * Trigger alert */ private async triggerAlert(rule: AlertRule, event: AuditEvent): Promise<void> { try { rule.lastTriggered = new Date(); rule.triggerCount++; logger.warn('Security alert triggered', { ruleName: rule.name, eventId: event.id, eventType: event.eventType, severity: event.severity, }); // Execute alert actions if (rule.actions.email) { await this.sendEmailAlert(rule, event); } if (rule.actions.webhook) { await this.sendWebhookAlert(rule, event); } if (rule.actions.escalate) { await this.escalateAlert(rule, event); } } catch (error) { logger.error('Failed to trigger alert', error as Error, { ruleName: rule.name }); } } /** * Send email alert (placeholder) */ private async sendEmailAlert(rule: AlertRule, event: AuditEvent): Promise<void> { // Placeholder - implement with actual email service logger.info('Email alert would be sent', { ruleName: rule.name, recipients: rule.actions.email, eventId: event.id, }); } /** * Send webhook alert (placeholder) */ private async sendWebhookAlert(rule: AlertRule, event: AuditEvent): Promise<void> { // Placeholder - implement with actual webhook logger.info('Webhook alert would be sent', { ruleName: rule.name, webhook: rule.actions.webhook, eventId: event.id, }); } /** * Escalate alert (placeholder) */ private async escalateAlert(rule: AlertRule, event: AuditEvent): Promise<void> { // Placeholder - implement escalation logic logger.warn('Alert escalated', { ruleName: rule.name, eventId: event.id, severity: event.severity, }); } /** * Rotate log files */ private async rotateLogFiles(): Promise<void> { try { logger.info('Starting audit log rotation'); // Implementation would: // 1. Compress old log files // 2. Archive to long-term storage // 3. Remove very old files // 4. Reset current log file logger.info('Audit log rotation completed'); } catch (error) { logger.error('Failed to rotate audit logs', error as Error); } } /** * Export events to CSV */ private exportToCSV(events: AuditEvent[]): string { const headers = [ 'ID', 'Timestamp', 'Event Type', 'Severity', 'Outcome', 'Source', 'Action', 'Resource', 'Description', 'User ID', 'Session ID', 'IP Address', ]; const rows = events.map(event => [ event.id, event.timestamp.toISOString(), event.eventType, event.severity, event.outcome, event.source, event.action, event.resource, event.description.replace(/"/g, '""'), // Escape quotes event.context.userId || '', event.context.sessionId || '', event.context.ipAddress || '', ]); const csvContent = [headers, ...rows] .map(row => row.map(field => `"${field}"`).join(',')) .join('\n'); return csvContent; } /** * Sanitize sensitive values for logging */ private sanitizeValue(value: any): any { if (typeof value === 'string') { // Mask passwords, tokens, etc. if (/password|token|secret|key/i.test(value)) { return '[REDACTED]'; } // Truncate very long strings if (value.length > 1000) { return value.substring(0, 1000) + '...[TRUNCATED]'; } } return value; } /** * Flush event buffer to storage */ private async flushEventBuffer(): Promise<void> { try { if (this.eventBuffer.length === 0) { return; } // Move events from buffer to main events array this.events.push(...this.eventBuffer); this.eventBuffer = []; // Write to disk if needed await this.writeEventsToFile(this.events); logger.debug('Event buffer flushed', { eventCount: this.events.length }); } catch (error) { logger.error('Failed to flush event buffer', error as Error); } } /** * Write events to file (placeholder for now) */ private async writeEventsToFile(events: AuditEvent[]): Promise<void> { // This is a simplified implementation // In production, this would write events to secure, append-only log files try { const logData = events.map(event => JSON.stringify(event)).join('\n'); // For now, just log to debug - in production would write to file logger.debug('Events would be written to file', { eventCount: events.length }); } catch (error) { logger.error('Failed to write events to file', error as Error); } } /** * Stop the audit logger and cleanup resources */ stop(): void { try { // Clear any pending timers or intervals if (this.flushInterval) { clearInterval(this.flushInterval); this.flushInterval = undefined; } // Flush any remaining events if (this.eventBuffer.length > 0) { this.flushEventBuffer(); } // Clear buffers this.eventBuffer = []; this.alertRules.clear(); logger.info('Security audit logger stopped'); } catch (error) { logger.error('Error stopping audit logger', error as Error); } } /** * Verify log integrity using HMAC signatures */ verifyLogIntegrity(): { valid: boolean; details?: string } { try { if (!this.signingKey) { return { valid: false, details: 'No signing key available', }; } // For this implementation, we'll validate that the logger is properly initialized // In production, this would verify HMAC signatures of actual log files const isInitialized = this.signingKey && this.eventBuffer !== undefined; return { valid: isInitialized, details: isInitialized ? 'Log integrity verified' : 'Logger not properly initialized', }; } catch (error) { return { valid: false, details: `Integrity verification failed: ${(error as Error).message}`, }; } } }