UNPKG

datapilot-cli

Version:

Enterprise-grade streaming multi-format data analysis with comprehensive statistical insights and intelligent relationship detection - supports CSV, JSON, Excel, TSV, Parquet - memory-efficient, cross-platform

623 lines 21.2 kB
"use strict"; /** * Security Audit Logging System * Comprehensive logging and monitoring for security events */ Object.defineProperty(exports, "__esModule", { value: true }); exports.SecurityMonitor = exports.SecurityAuditLogger = void 0; exports.getSecurityAuditLogger = getSecurityAuditLogger; const fs_1 = require("fs"); const path_1 = require("path"); const crypto_1 = require("crypto"); const logger_1 = require("../utils/logger"); class SecurityAuditLogger { static instance; config; events = []; alertRules = new Map(); alertCounts = new Map(); writeStream; currentLogFile; logRotationTimer; constructor(config) { this.config = { enabled: true, logFilePath: (0, path_1.resolve)(process.cwd(), 'logs', 'security-audit.log'), maxFileSize: 10 * 1024 * 1024, // 10MB maxFiles: 10, minSeverity: 'medium', enableAlerts: true, alertThresholds: { highSeverityCount: 5, criticalSeverityCount: 1, timeWindowMinutes: 15, }, encryptLogs: false, retentionDays: 90, ...config, }; this.initializeLogging(); this.setupDefaultAlertRules(); this.startCleanupTimer(); } static getInstance(config) { if (!SecurityAuditLogger.instance) { SecurityAuditLogger.instance = new SecurityAuditLogger(config); } return SecurityAuditLogger.instance; } /** * Log a security event */ async logSecurityEvent(type, description, data = {}, options = {}) { const eventId = this.generateEventId(); const timestamp = new Date().toISOString(); const event = { id: eventId, timestamp, type, severity: options.severity || this.calculateSeverity(type, data), source: options.source || 'datapilot', userId: options.userId, ipAddress: this.extractIpAddress(options.context), filePath: options.filePath, operation: options.operation, description, data: this.sanitiseEventData(data), outcome: options.outcome || 'success', riskScore: options.riskScore || this.calculateRiskScore(type, options.severity || 'medium'), }; try { // Store in memory this.events.push(event); // Check if we should log based on severity if (this.shouldLogEvent(event)) { await this.writeEventToLog(event); } // Check alert rules await this.checkAlertRules(event); // Log to application logger logger_1.logger.info('Security event logged', { eventId, type, severity: event.severity, outcome: event.outcome, riskScore: event.riskScore, ...options.context, }); return eventId; } catch (error) { logger_1.logger.error('Failed to log security event', { eventId, type, error: error instanceof Error ? error.message : 'Unknown error', ...options.context, }); throw error; } } /** * Log authentication event */ async logAuthenticationEvent(operation, outcome, userId, data = {}, context) { return this.logSecurityEvent('authentication', `Authentication ${operation}`, { operation, ...data }, { severity: outcome === 'failure' ? 'high' : 'low', userId, operation, outcome, context, }); } /** * Log file access event */ async logFileAccessEvent(filePath, operation, outcome, userId, data = {}, context) { return this.logSecurityEvent('file_access', `File ${operation}: ${filePath}`, { fileSize: data.fileSize, permissions: data.permissions, ...data }, { severity: outcome === 'blocked' ? 'high' : 'medium', filePath, operation, outcome, userId, context, }); } /** * Log policy violation */ async logPolicyViolation(policyName, violationType, data = {}, context) { return this.logSecurityEvent('policy_violation', `Policy violation: ${policyName} - ${violationType}`, { policyName, violationType, ...data }, { severity: 'high', outcome: 'blocked', riskScore: 8, context, }); } /** * Log intrusion attempt */ async logIntrusionAttempt(attackType, source, data = {}, context) { return this.logSecurityEvent('intrusion_attempt', `Intrusion attempt detected: ${attackType}`, { attackType, attackSource: source, ...data }, { severity: 'critical', outcome: 'blocked', riskScore: 10, context, }); } /** * Add custom alert rule */ addAlertRule(rule) { this.alertRules.set(rule.id, rule); logger_1.logger.info('Security alert rule added', { ruleId: rule.id, ruleName: rule.name, eventTypes: rule.eventTypes, }); } /** * Remove alert rule */ removeAlertRule(ruleId) { const removed = this.alertRules.delete(ruleId); if (removed) { logger_1.logger.info('Security alert rule removed', { ruleId }); } return removed; } /** * Get security events with filtering */ getEvents(filter) { let filtered = [...this.events]; if (filter) { filtered = filtered.filter((event) => { if (filter.type && event.type !== filter.type) return false; if (filter.severity && event.severity !== filter.severity) return false; if (filter.outcome && event.outcome !== filter.outcome) return false; if (filter.userId && event.userId !== filter.userId) return false; if (filter.filePath && event.filePath !== filter.filePath) return false; if (filter.minRiskScore && event.riskScore < filter.minRiskScore) return false; const eventTime = new Date(event.timestamp); if (filter.startTime && eventTime < filter.startTime) return false; if (filter.endTime && eventTime > filter.endTime) return false; return true; }); } // Sort by timestamp (newest first) filtered.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); // Apply limit if (filter?.limit) { filtered = filtered.slice(0, filter.limit); } return filtered; } /** * Get security statistics */ getStatistics(timeRange) { const events = timeRange ? this.getEvents({ startTime: timeRange.start, endTime: timeRange.end }) : this.events; const eventsBySeverity = { low: 0, medium: 0, high: 0, critical: 0, }; const eventsByType = { authentication: 0, authorization: 0, file_access: 0, input_validation: 0, configuration_change: 0, policy_violation: 0, intrusion_attempt: 0, data_integrity: 0, system_security: 0, compliance: 0, }; const eventsByOutcome = {}; let totalRiskScore = 0; for (const event of events) { eventsBySeverity[event.severity]++; eventsByType[event.type]++; eventsByOutcome[event.outcome] = (eventsByOutcome[event.outcome] || 0) + 1; totalRiskScore += event.riskScore; } const topRiskEvents = events.sort((a, b) => b.riskScore - a.riskScore).slice(0, 10); return { totalEvents: events.length, eventsBySeverity, eventsByType, eventsByOutcome, averageRiskScore: events.length > 0 ? totalRiskScore / events.length : 0, topRiskEvents, }; } /** * Export audit log */ async exportAuditLog(filePath, format = 'json', filter) { const events = this.getEvents(filter); try { if (format === 'json') { await fs_1.promises.writeFile(filePath, JSON.stringify(events, null, 2), 'utf8'); } else if (format === 'csv') { const csvContent = this.eventsToCSV(events); await fs_1.promises.writeFile(filePath, csvContent, 'utf8'); } logger_1.logger.info('Audit log exported', { filePath, format, eventCount: events.length, }); } catch (error) { logger_1.logger.error('Failed to export audit log', { filePath, format, error: error instanceof Error ? error.message : 'Unknown error', }); throw error; } } /** * Clear old events based on retention policy */ async clearOldEvents() { const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - this.config.retentionDays); const initialCount = this.events.length; this.events = this.events.filter((event) => new Date(event.timestamp) > cutoffDate); const removedCount = initialCount - this.events.length; if (removedCount > 0) { logger_1.logger.info('Cleared old security events', { removedCount, remainingCount: this.events.length, cutoffDate: cutoffDate.toISOString(), }); } return removedCount; } // Private helper methods async initializeLogging() { if (!this.config.enabled) { return; } try { // Ensure log directory exists const logDir = (0, path_1.dirname)(this.config.logFilePath); await fs_1.promises.mkdir(logDir, { recursive: true }); // Initialize write stream await this.createLogStream(); logger_1.logger.info('Security audit logging initialized', { logFilePath: this.config.logFilePath, minSeverity: this.config.minSeverity, }); } catch (error) { logger_1.logger.error('Failed to initialize security audit logging', { error: error instanceof Error ? error.message : 'Unknown error', }); throw error; } } async createLogStream() { if (this.writeStream) { this.writeStream.end(); } this.currentLogFile = this.config.logFilePath; this.writeStream = (0, fs_1.createWriteStream)(this.currentLogFile, { flags: 'a' }); this.writeStream.on('error', (error) => { logger_1.logger.error('Security audit log write error', { error: error.message, logFile: this.currentLogFile, }); }); } setupDefaultAlertRules() { // Critical events this.addAlertRule({ id: 'critical-events', name: 'Critical Security Events', eventTypes: ['intrusion_attempt', 'policy_violation'], minSeverity: 'critical', threshold: { count: 1, timeWindowMinutes: 1 }, action: 'escalate', enabled: true, }); // Failed authentication attempts this.addAlertRule({ id: 'failed-auth', name: 'Failed Authentication Attempts', eventTypes: ['authentication'], minSeverity: 'medium', threshold: { count: 5, timeWindowMinutes: 15 }, action: 'notify', enabled: true, }); // Suspicious file access this.addAlertRule({ id: 'suspicious-file-access', name: 'Suspicious File Access Patterns', eventTypes: ['file_access'], minSeverity: 'high', threshold: { count: 10, timeWindowMinutes: 5 }, action: 'block', enabled: true, }); } generateEventId() { return (0, crypto_1.randomBytes)(8).toString('hex'); } calculateSeverity(type, data) { // Default severity based on event type const typeSeverity = { authentication: 'medium', authorization: 'medium', file_access: 'low', input_validation: 'medium', configuration_change: 'high', policy_violation: 'high', intrusion_attempt: 'critical', data_integrity: 'high', system_security: 'high', compliance: 'medium', }; let severity = typeSeverity[type] || 'medium'; // Adjust based on data content if (data.blocked || data.quarantined) { severity = 'high'; } if (data.attackDetected || data.maliciousContent) { severity = 'critical'; } return severity; } calculateRiskScore(type, severity) { const baseScores = { authentication: 3, authorization: 4, file_access: 2, input_validation: 3, configuration_change: 6, policy_violation: 7, intrusion_attempt: 9, data_integrity: 8, system_security: 7, compliance: 5, }; const severityMultiplier = { low: 0.5, medium: 1.0, high: 1.5, critical: 2.0, }; const baseScore = baseScores[type] || 5; const multiplier = severityMultiplier[severity] || 1.0; return Math.min(10, Math.round(baseScore * multiplier)); } shouldLogEvent(event) { if (!this.config.enabled) { return false; } const severityLevels = { low: 1, medium: 2, high: 3, critical: 4, }; return severityLevels[event.severity] >= severityLevels[this.config.minSeverity]; } async writeEventToLog(event) { if (!this.writeStream) { return; } const logEntry = JSON.stringify(event) + '\n'; return new Promise((resolve, reject) => { this.writeStream.write(logEntry, (error) => { if (error) { reject(error); } else { resolve(); } }); }); } async checkAlertRules(event) { for (const rule of this.alertRules.values()) { if (!rule.enabled || !rule.eventTypes.includes(event.type)) { continue; } const severityLevels = { low: 1, medium: 2, high: 3, critical: 4, }; if (severityLevels[event.severity] < severityLevels[rule.minSeverity]) { continue; } // Check pattern if specified if (rule.pattern && !rule.pattern.test(JSON.stringify(event.data))) { continue; } // Check threshold const key = `${rule.id}_${event.type}`; const now = Date.now(); const windowMs = rule.threshold.timeWindowMinutes * 60 * 1000; const countData = this.alertCounts.get(key) || { count: 0, timestamp: now }; // Reset count if outside time window if (now - countData.timestamp > windowMs) { countData.count = 0; countData.timestamp = now; } countData.count++; this.alertCounts.set(key, countData); // Trigger alert if threshold exceeded if (countData.count >= rule.threshold.count) { await this.triggerAlert(rule, event, countData.count); // Reset counter after triggering countData.count = 0; countData.timestamp = now; } } } async triggerAlert(rule, event, count) { const alertData = { ruleId: rule.id, ruleName: rule.name, triggerEvent: event, count, action: rule.action, timestamp: new Date(), }; logger_1.logger.warn('Security alert triggered', alertData); // Log the alert as a security event await this.logSecurityEvent('system_security', `Security alert: ${rule.name}`, alertData, { severity: 'high', outcome: 'warning', riskScore: 8, }); // TODO: Implement additional alert actions (email, webhook, etc.) switch (rule.action) { case 'notify': // Send notification break; case 'block': // Implement blocking logic break; case 'escalate': // Escalate to security team break; } } sanitiseEventData(data) { const sanitised = { ...data }; // Remove sensitive fields const sensitiveFields = ['password', 'token', 'key', 'secret', 'credential']; for (const field of sensitiveFields) { if (field in sanitised) { sanitised[field] = '[REDACTED]'; } } return sanitised; } extractIpAddress(context) { // Extract IP from context if available return context?.ipAddress; } eventsToCSV(events) { const headers = [ 'id', 'timestamp', 'type', 'severity', 'source', 'userId', 'ipAddress', 'filePath', 'operation', 'description', 'outcome', 'riskScore', ]; const csvRows = [headers.join(',')]; for (const event of events) { const row = headers.map((header) => { const value = event[header]; if (typeof value === 'string') { return `"${value.replace(/"/g, '""')}"`; // Escape quotes } return value || ''; }); csvRows.push(row.join(',')); } return csvRows.join('\n'); } startCleanupTimer() { // Run cleanup every hour this.logRotationTimer = setInterval(() => { this.clearOldEvents().catch((error) => { logger_1.logger.error('Security audit cleanup failed', { error: error instanceof Error ? error.message : 'Unknown error', }); }); }, 3600000); } /** * Shutdown audit logger gracefully */ async shutdown() { if (this.logRotationTimer) { clearInterval(this.logRotationTimer); } if (this.writeStream) { return new Promise((resolve) => { this.writeStream.end(() => { logger_1.logger.info('Security audit logger shutdown complete'); resolve(); }); }); } } } exports.SecurityAuditLogger = SecurityAuditLogger; /** * Factory function for easy access */ function getSecurityAuditLogger() { return SecurityAuditLogger.getInstance(); } /** * Security monitoring middleware */ class SecurityMonitor { auditLogger; constructor() { this.auditLogger = getSecurityAuditLogger(); } /** * Monitor function execution for security events */ async monitorExecution(operation, fn, context) { const startTime = Date.now(); try { const result = await fn(); await this.auditLogger.logSecurityEvent('system_security', `Operation completed: ${operation}`, { operation, duration: Date.now() - startTime, }, { severity: 'low', outcome: 'success', context, }); return result; } catch (error) { await this.auditLogger.logSecurityEvent('system_security', `Operation failed: ${operation}`, { operation, error: error instanceof Error ? error.message : 'Unknown error', duration: Date.now() - startTime, }, { severity: 'medium', outcome: 'failure', context, }); throw error; } } } exports.SecurityMonitor = SecurityMonitor; //# sourceMappingURL=audit-logger.js.map