UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

392 lines (391 loc) 16.3 kB
import fs from 'fs-extra'; import path from 'path'; import crypto from 'crypto'; import { AppError } from '../../../utils/errors.js'; import logger from '../../../logger.js'; export class SecurityAuditLogger { static instance = null; config; auditEvents = []; suspiciousPatterns = new Map(); eventCounter = 0; lastEventId = null; currentLogFile = null; logFileHandle = null; constructor(config) { this.config = { enabled: true, logDirectory: path.join(process.cwd(), 'data', 'audit-logs'), maxLogFileSize: 10 * 1024 * 1024, maxLogFiles: 100, enableIntegrityProtection: true, enableSuspiciousActivityDetection: true, enableComplianceReporting: true, retentionPeriodDays: 365, encryptLogs: false, ...config }; this.initializeAuditSystem(); this.initializeSuspiciousActivityPatterns(); logger.info({ config: this.config }, 'Security Audit Logger initialized'); } static getInstance(config) { if (!SecurityAuditLogger.instance) { SecurityAuditLogger.instance = new SecurityAuditLogger(config); } return SecurityAuditLogger.instance; } async logSecurityEvent(eventType, severity, source, action, outcome, description, options) { if (!this.config.enabled) { return; } try { const eventId = `audit_${++this.eventCounter}_${Date.now()}`; const auditEvent = { id: eventId, timestamp: new Date(), eventType, severity, source, actor: { userId: options?.actor?.userId, sessionId: options?.actor?.sessionId, ipAddress: options?.actor?.ipAddress, userAgent: options?.actor?.userAgent }, resource: { type: options?.resource?.type || 'unknown', id: options?.resource?.id, path: options?.resource?.path }, action, outcome, details: { description, metadata: options?.metadata, errorCode: options?.errorCode, stackTrace: options?.stackTrace }, integrity: { checksum: '', previousEventId: this.lastEventId || undefined } }; if (this.config.enableIntegrityProtection) { auditEvent.integrity.checksum = this.calculateEventChecksum(auditEvent); } this.auditEvents.push(auditEvent); this.lastEventId = eventId; if (this.auditEvents.length > 10000) { this.auditEvents = this.auditEvents.slice(-10000); } await this.writeToLogFile(auditEvent); if (this.config.enableSuspiciousActivityDetection) { await this.detectSuspiciousActivity(auditEvent); } const logData = { eventId, eventType, severity, source, action, outcome, description: description.substring(0, 200) }; switch (severity) { case 'critical': logger.error(logData, 'Critical security event'); break; case 'high': logger.warn(logData, 'High severity security event'); break; case 'medium': logger.info(logData, 'Medium severity security event'); break; default: logger.debug(logData, 'Security event logged'); } } catch (error) { logger.error({ err: error }, 'Failed to log security audit event'); } } async initializeAuditSystem() { try { await fs.ensureDir(this.config.logDirectory); await this.initializeLogFile(); await this.cleanupOldLogFiles(); } catch (error) { logger.error({ err: error }, 'Failed to initialize audit system'); throw new AppError('Failed to initialize security audit system'); } } async initializeLogFile() { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); this.currentLogFile = path.join(this.config.logDirectory, `audit-${timestamp}.log`); this.logFileHandle = fs.createWriteStream(this.currentLogFile, { flags: 'a' }); await this.logSecurityEvent('system_event', 'info', 'audit-logger', 'startup', 'success', 'Security audit system initialized'); } async writeToLogFile(event) { if (!this.logFileHandle) { return; } try { let logData = JSON.stringify(event) + '\n'; if (this.config.encryptLogs && this.config.encryptionKey) { logData = this.encryptLogData(logData); } const stats = await fs.stat(this.currentLogFile); if (stats.size > this.config.maxLogFileSize) { await this.rotateLogFile(); } this.logFileHandle.write(logData); } catch (error) { logger.error({ err: error }, 'Failed to write to audit log file'); } } async rotateLogFile() { if (this.logFileHandle) { this.logFileHandle.end(); } await this.initializeLogFile(); } calculateEventChecksum(event) { const eventData = { ...event, integrity: { ...event.integrity, checksum: '' } }; const eventString = JSON.stringify(eventData); return crypto.createHash('sha256').update(eventString).digest('hex').substring(0, 8); } encryptLogData(data) { if (!this.config.encryptionKey) { return data; } try { const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(this.config.encryptionKey.substring(0, 32)), iv); let encrypted = cipher.update(data, 'utf8', 'hex'); encrypted += cipher.final('hex'); return iv.toString('hex') + ':' + encrypted + '\n'; } catch (error) { logger.error({ err: error }, 'Failed to encrypt log data'); return data; } } initializeSuspiciousActivityPatterns() { const patterns = [ { id: 'multiple_failed_auth', name: 'Multiple Failed Authentication Attempts', description: 'Multiple failed authentication attempts from same source', pattern: { eventTypes: ['authentication'], timeWindow: 300000, threshold: 5, conditions: { outcome: 'failure' } }, severity: 'high', enabled: true }, { id: 'rapid_data_access', name: 'Rapid Data Access', description: 'Unusually rapid data access patterns', pattern: { eventTypes: ['data_access'], timeWindow: 60000, threshold: 50, conditions: { outcome: 'success' } }, severity: 'medium', enabled: true }, { id: 'security_violations', name: 'Multiple Security Violations', description: 'Multiple security violations in short time', pattern: { eventTypes: ['security_violation'], timeWindow: 600000, threshold: 3 }, severity: 'critical', enabled: true }, { id: 'privilege_escalation', name: 'Potential Privilege Escalation', description: 'Attempts to access unauthorized resources', pattern: { eventTypes: ['authorization'], timeWindow: 300000, threshold: 10, conditions: { outcome: 'blocked' } }, severity: 'high', enabled: true } ]; for (const pattern of patterns) { this.suspiciousPatterns.set(pattern.id, pattern); } } async detectSuspiciousActivity(newEvent) { const now = Date.now(); for (const pattern of this.suspiciousPatterns.values()) { if (!pattern.enabled || !pattern.pattern.eventTypes.includes(newEvent.eventType)) { continue; } const recentEvents = this.auditEvents.filter(event => { const eventTime = event.timestamp.getTime(); const withinTimeWindow = now - eventTime <= pattern.pattern.timeWindow; const matchesType = pattern.pattern.eventTypes.includes(event.eventType); let matchesConditions = true; if (pattern.pattern.conditions) { for (const [key, value] of Object.entries(pattern.pattern.conditions)) { if (event[key] !== value) { matchesConditions = false; break; } } } return withinTimeWindow && matchesType && matchesConditions; }); if (recentEvents.length >= pattern.pattern.threshold) { await this.logSecurityEvent('suspicious_activity', pattern.severity, 'audit-logger', 'pattern_detection', 'warning', `Suspicious activity detected: ${pattern.description}`, { metadata: { patternId: pattern.id, patternName: pattern.name, eventCount: recentEvents.length, threshold: pattern.pattern.threshold, timeWindow: pattern.pattern.timeWindow, triggeringEvents: recentEvents.slice(-5).map(e => e.id) } }); } } } async generateComplianceReport(startDate, endDate) { const reportId = `compliance_${Date.now()}`; const periodEvents = this.auditEvents.filter(event => event.timestamp >= startDate && event.timestamp <= endDate); const eventsByType = {}; const eventsBySeverity = {}; for (const event of periodEvents) { eventsByType[event.eventType] = (eventsByType[event.eventType] || 0) + 1; eventsBySeverity[event.severity] = (eventsBySeverity[event.severity] || 0) + 1; } const violations = periodEvents.filter(event => event.eventType === 'security_violation' || event.eventType === 'suspicious_activity'); const recommendations = this.generateRecommendations(periodEvents, violations); const report = { id: reportId, generatedAt: new Date(), period: { start: startDate, end: endDate }, summary: { totalEvents: periodEvents.length, eventsByType, eventsBySeverity, securityViolations: violations.length, suspiciousActivities: periodEvents.filter(e => e.eventType === 'suspicious_activity').length }, violations, recommendations }; await this.logSecurityEvent('compliance_event', 'info', 'audit-logger', 'report_generation', 'success', `Compliance report generated for period ${startDate.toISOString()} to ${endDate.toISOString()}`, { metadata: { reportId, totalEvents: periodEvents.length, violations: violations.length } }); return report; } generateRecommendations(events, violations) { const recommendations = []; const authFailures = events.filter(e => e.eventType === 'authentication' && e.outcome === 'failure').length; if (authFailures > 100) { recommendations.push('Consider implementing account lockout policies due to high authentication failure rate'); } if (violations.length > 10) { recommendations.push('Review and strengthen security policies due to multiple violations'); } const suspiciousEvents = events.filter(e => e.eventType === 'suspicious_activity').length; if (suspiciousEvents > 5) { recommendations.push('Investigate suspicious activity patterns and consider additional monitoring'); } const criticalEvents = events.filter(e => e.severity === 'critical').length; if (criticalEvents > 0) { recommendations.push('Address all critical security events immediately'); } return recommendations; } async cleanupOldLogFiles() { try { const files = await fs.readdir(this.config.logDirectory); const logFiles = files .filter(file => file.startsWith('audit-') && file.endsWith('.log')) .map(file => ({ name: file, path: path.join(this.config.logDirectory, file), stats: fs.statSync(path.join(this.config.logDirectory, file)) })) .sort((a, b) => b.stats.mtime.getTime() - a.stats.mtime.getTime()); if (logFiles.length > this.config.maxLogFiles) { const filesToRemove = logFiles.slice(this.config.maxLogFiles); for (const file of filesToRemove) { await fs.remove(file.path); logger.debug({ file: file.name }, 'Removed old audit log file'); } } const retentionCutoff = Date.now() - (this.config.retentionPeriodDays * 24 * 60 * 60 * 1000); for (const file of logFiles) { if (file.stats.mtime.getTime() < retentionCutoff) { await fs.remove(file.path); logger.debug({ file: file.name }, 'Removed expired audit log file'); } } } catch (error) { logger.warn({ err: error }, 'Failed to cleanup old audit log files'); } } getAuditStatistics() { const eventsByType = {}; const eventsBySeverity = {}; for (const event of this.auditEvents) { eventsByType[event.eventType] = (eventsByType[event.eventType] || 0) + 1; eventsBySeverity[event.severity] = (eventsBySeverity[event.severity] || 0) + 1; } const recentViolations = this.auditEvents .filter(event => event.eventType === 'security_violation' || event.eventType === 'suspicious_activity') .slice(-20); return { totalEvents: this.auditEvents.length, eventsByType, eventsBySeverity, recentViolations, suspiciousPatterns: this.suspiciousPatterns.size }; } async shutdown() { await this.logSecurityEvent('system_event', 'info', 'audit-logger', 'shutdown', 'success', 'Security audit system shutdown'); if (this.logFileHandle) { this.logFileHandle.end(); } this.auditEvents = []; this.suspiciousPatterns.clear(); logger.info('Security Audit Logger shutdown'); } } export async function logSecurityEvent(eventType, severity, source, action, outcome, description, options) { const auditLogger = SecurityAuditLogger.getInstance(); return auditLogger.logSecurityEvent(eventType, severity, source, action, outcome, description, options); } export function getSecurityAuditLogger() { return SecurityAuditLogger.getInstance(); }