UNPKG

mcp-quiz-server

Version:

🧠 AI-Powered Quiz Management via Model Context Protocol (MCP) - Create, manage, and take quizzes directly from VS Code, Claude, and other AI agents.

531 lines (530 loc) â€ĸ 19.7 kB
"use strict"; /** * @fileoverview Security Monitor - Real-time Security Event Tracking * @version 1.0.0 * @since 2025-08-03 * @lastUpdated 2025-08-03 * @module SecurityMonitor * @description Real-time security monitoring with alerts, metrics, and dashboards * @contributors GitHub Copilot * @requirements SECURITY_003 (Security Monitoring), SECURITY_004 (Real-time Alerts) * @testCoverage Security event tracking and alert validation tests */ Object.defineProperty(exports, "__esModule", { value: true }); exports.SecurityMonitor = exports.SecurityEventType = void 0; exports.getSecurityMonitor = getSecurityMonitor; exports.resetSecurityMonitor = resetSecurityMonitor; const events_1 = require("events"); /** * Security event types for monitoring */ var SecurityEventType; (function (SecurityEventType) { SecurityEventType["TOOL_EXECUTION_STARTED"] = "tool_execution_started"; SecurityEventType["TOOL_EXECUTION_COMPLETED"] = "tool_execution_completed"; SecurityEventType["TOOL_EXECUTION_FAILED"] = "tool_execution_failed"; SecurityEventType["SECURITY_VIOLATION"] = "security_violation"; SecurityEventType["AUTHENTICATION_SUCCESS"] = "authentication_success"; SecurityEventType["AUTHENTICATION_FAILURE"] = "authentication_failure"; SecurityEventType["AUTHORIZATION_DENIED"] = "authorization_denied"; SecurityEventType["RESOURCE_LIMIT_EXCEEDED"] = "resource_limit_exceeded"; SecurityEventType["SUSPICIOUS_ACTIVITY"] = "suspicious_activity"; SecurityEventType["SYSTEM_ALERT"] = "system_alert"; })(SecurityEventType || (exports.SecurityEventType = SecurityEventType = {})); /** * Security Monitor class for real-time tracking */ class SecurityMonitor extends events_1.EventEmitter { constructor(config, alertConfig) { super(); this.events = []; this.violationCounts = new Map(); // userId -> [timestamp, timestamp, ...] this.realTimeClients = new Set(); // WebSocket connections for real-time updates this.config = config; this.alertConfig = alertConfig || { enabled: false, threshold: { violations: 5, timeWindowMs: 300000 }, // 5 violations in 5 minutes }; this.metrics = this.initializeMetrics(); this.setupEventHandlers(); console.log('🔒 Security Monitor initialized with real-time tracking'); } /** * Initialize metrics structure */ initializeMetrics() { const eventsByType = {}; Object.values(SecurityEventType).forEach(type => { eventsByType[type] = 0; }); return { totalEvents: 0, eventsByType, eventsBySeverity: { low: 0, medium: 0, high: 0, critical: 0 }, violationsLast24h: 0, authenticationsLast24h: 0, blockedToolsCount: 0, averageExecutionTime: 0, resourceViolationRate: 0, lastUpdated: Date.now(), }; } /** * Setup event handlers for different security events */ setupEventHandlers() { this.on('securityEvent', this.handleSecurityEvent.bind(this)); this.on('violationThresholdExceeded', this.handleViolationThreshold.bind(this)); this.on('criticalAlert', this.sendCriticalAlert.bind(this)); } /** * Record a security event */ recordEvent(event) { const fullEvent = { ...event, id: this.generateEventId(), timestamp: Date.now(), }; this.events.push(fullEvent); this.updateMetrics(fullEvent); this.emit('securityEvent', fullEvent); // Check for violation thresholds if (this.isViolationEvent(fullEvent)) { this.trackViolation(fullEvent); } // Send real-time updates this.broadcastRealTimeUpdate(fullEvent); // Log based on severity this.logEvent(fullEvent); } /** * Handle security event processing */ handleSecurityEvent(event) { // Critical events trigger immediate alerts if (event.severity === 'critical') { this.emit('criticalAlert', event); } // High severity events in production trigger alerts if (event.severity === 'high' && this.config.environment === 'production') { this.sendAlert(event); } } /** * Update metrics based on new event */ updateMetrics(event) { this.metrics.totalEvents++; this.metrics.eventsByType[event.type]++; this.metrics.eventsBySeverity[event.severity]++; this.metrics.lastUpdated = Date.now(); // Calculate 24h metrics const last24h = Date.now() - 24 * 60 * 60 * 1000; const recent24hEvents = this.events.filter(e => e.timestamp > last24h); this.metrics.violationsLast24h = recent24hEvents.filter(e => e.type === SecurityEventType.SECURITY_VIOLATION || e.type === SecurityEventType.RESOURCE_LIMIT_EXCEEDED).length; this.metrics.authenticationsLast24h = recent24hEvents.filter(e => e.type === SecurityEventType.AUTHENTICATION_SUCCESS).length; // Calculate average execution time const executionEvents = recent24hEvents.filter(e => { var _a; return e.type === SecurityEventType.TOOL_EXECUTION_COMPLETED && ((_a = e.details.metadata) === null || _a === void 0 ? void 0 : _a.executionTime); }); if (executionEvents.length > 0) { const totalTime = executionEvents.reduce((sum, e) => { var _a; return sum + (((_a = e.details.metadata) === null || _a === void 0 ? void 0 : _a.executionTime) || 0); }, 0); this.metrics.averageExecutionTime = totalTime / executionEvents.length; } // Calculate violation rate const totalExecutions = recent24hEvents.filter(e => e.type === SecurityEventType.TOOL_EXECUTION_STARTED).length; if (totalExecutions > 0) { this.metrics.resourceViolationRate = this.metrics.violationsLast24h / totalExecutions; } } /** * Track violation for threshold monitoring */ trackViolation(event) { const userId = event.details.userId || 'anonymous'; const now = Date.now(); if (!this.violationCounts.has(userId)) { this.violationCounts.set(userId, []); } const violations = this.violationCounts.get(userId); violations.push(now); // Clean old violations outside time window const cutoff = now - this.alertConfig.threshold.timeWindowMs; const recentViolations = violations.filter(timestamp => timestamp > cutoff); this.violationCounts.set(userId, recentViolations); // Check threshold if (recentViolations.length >= this.alertConfig.threshold.violations) { this.emit('violationThresholdExceeded', { userId, violationCount: recentViolations.length, event, }); } } /** * Handle violation threshold exceeded */ handleViolationThreshold(data) { const alertEvent = { id: this.generateEventId(), type: SecurityEventType.SYSTEM_ALERT, timestamp: Date.now(), severity: 'critical', source: 'SecurityMonitor', details: { userId: data.userId, errorMessage: `Violation threshold exceeded: ${data.violationCount} violations`, metadata: { alertType: 'violation_threshold_exceeded', violationCount: data.violationCount, originalEvent: data.event, threshold: this.alertConfig.threshold, timeWindow: this.alertConfig.threshold.timeWindowMs, }, }, }; this.recordEvent(alertEvent); } /** * Send critical alert */ async sendCriticalAlert(event) { if (!this.alertConfig.enabled) return; const alertMessage = this.formatAlertMessage(event); console.error('🚨 CRITICAL SECURITY ALERT:', alertMessage); // Send to configured channels await Promise.allSettled([ this.sendWebhookAlert(event), this.sendEmailAlert(event), this.sendSlackAlert(event), this.sendDiscordAlert(event), ]); } /** * Send general alert */ async sendAlert(event) { if (!this.alertConfig.enabled) return; const alertMessage = this.formatAlertMessage(event); console.warn('âš ī¸ Security Alert:', alertMessage); // Send to webhook only for non-critical alerts await this.sendWebhookAlert(event); } /** * Format alert message */ formatAlertMessage(event) { return (`Security Alert: ${event.type} (${event.severity}) - ${event.details.errorMessage || 'No details'} ` + `[User: ${event.details.userId || 'unknown'}, Tool: ${event.details.toolName || 'none'}, ` + `IP: ${event.details.ipAddress || 'unknown'}]`); } /** * Send webhook alert */ async sendWebhookAlert(event) { if (!this.alertConfig.webhookUrl) return; try { const response = await fetch(this.alertConfig.webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ alert: 'MCP Quiz Server Security Alert', event, timestamp: new Date(event.timestamp).toISOString(), severity: event.severity, environment: this.config.environment, }), }); if (!response.ok) { console.error('Failed to send webhook alert:', response.statusText); } } catch (error) { console.error('Error sending webhook alert:', error); } } /** * Send email alert (placeholder for email service integration) */ async sendEmailAlert(event) { var _a; if (!((_a = this.alertConfig.emailNotifications) === null || _a === void 0 ? void 0 : _a.length)) return; // TODO: Integrate with email service (SendGrid, SES, etc.) console.log('📧 Email alert would be sent to:', this.alertConfig.emailNotifications); } /** * Send Slack alert */ async sendSlackAlert(event) { if (!this.alertConfig.slackWebhook) return; const slackMessage = { text: `🚨 Security Alert: ${event.type}`, attachments: [ { color: event.severity === 'critical' ? 'danger' : 'warning', fields: [ { title: 'Severity', value: event.severity, short: true }, { title: 'Type', value: event.type, short: true }, { title: 'User', value: event.details.userId || 'unknown', short: true }, { title: 'Tool', value: event.details.toolName || 'none', short: true }, { title: 'Details', value: event.details.errorMessage || 'No details', short: false }, ], timestamp: Math.floor(event.timestamp / 1000), }, ], }; try { await fetch(this.alertConfig.slackWebhook, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(slackMessage), }); } catch (error) { console.error('Error sending Slack alert:', error); } } /** * Send Discord alert */ async sendDiscordAlert(event) { if (!this.alertConfig.discordWebhook) return; const discordMessage = { embeds: [ { title: `🚨 Security Alert: ${event.type}`, color: event.severity === 'critical' ? 0xff0000 : 0xffa500, fields: [ { name: 'Severity', value: event.severity, inline: true }, { name: 'User', value: event.details.userId || 'unknown', inline: true }, { name: 'Tool', value: event.details.toolName || 'none', inline: true }, { name: 'Details', value: event.details.errorMessage || 'No details', inline: false }, ], timestamp: new Date(event.timestamp).toISOString(), }, ], }; try { await fetch(this.alertConfig.discordWebhook, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(discordMessage), }); } catch (error) { console.error('Error sending Discord alert:', error); } } /** * Check if event is a violation type */ isViolationEvent(event) { return [ SecurityEventType.SECURITY_VIOLATION, SecurityEventType.RESOURCE_LIMIT_EXCEEDED, SecurityEventType.AUTHORIZATION_DENIED, SecurityEventType.SUSPICIOUS_ACTIVITY, ].includes(event.type); } /** * Log event based on severity */ logEvent(event) { const message = `[${event.severity.toUpperCase()}] ${event.type}: ${event.source}`; switch (event.severity) { case 'critical': console.error(`🚨 ${message}`, event.details); break; case 'high': console.warn(`âš ī¸ ${message}`, event.details); break; case 'medium': console.info(`â„šī¸ ${message}`); break; case 'low': if (this.config.monitoring.logLevel === 'debug') { console.debug(`🔍 ${message}`); } break; } } /** * Broadcast real-time update to connected clients */ broadcastRealTimeUpdate(event) { if (!this.config.monitoring.realTimeAlerts) return; const update = { type: 'security_event', event, metrics: this.getMetrics(), timestamp: Date.now(), }; // Send to all connected real-time clients (WebSocket, SSE, etc.) this.realTimeClients.forEach(client => { try { if (typeof client.send === 'function') { client.send(JSON.stringify(update)); } } catch (error) { console.warn('Failed to send real-time update to client:', error); this.realTimeClients.delete(client); } }); } /** * Add real-time client for monitoring updates */ addRealTimeClient(client) { this.realTimeClients.add(client); // Send current metrics to new client const initialData = { type: 'initial_metrics', metrics: this.getMetrics(), recentEvents: this.getRecentEvents(50), timestamp: Date.now(), }; try { if (typeof client.send === 'function') { client.send(JSON.stringify(initialData)); } } catch (error) { console.warn('Failed to send initial data to real-time client:', error); this.realTimeClients.delete(client); } } /** * Remove real-time client */ removeRealTimeClient(client) { this.realTimeClients.delete(client); } /** * Get current security metrics */ getMetrics() { return { ...this.metrics }; } /** * Get recent security events */ getRecentEvents(limit = 100) { return this.events.slice(-limit); } /** * Get events by type */ getEventsByType(type, limit = 100) { return this.events.filter(e => e.type === type).slice(-limit); } /** * Generate unique event ID */ generateEventId() { return `sec_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Get security dashboard data */ getDashboardData() { const recentEvents = this.getRecentEvents(50); const last24h = Date.now() - 24 * 60 * 60 * 1000; // Calculate violation trends (hourly buckets for last 24h) const violationTrends = this.calculateViolationTrends(last24h); // Get top violating users const topViolatingUsers = this.getTopViolatingUsers(last24h); // System health indicators const systemHealth = { status: this.metrics.violationsLast24h > 50 ? 'degraded' : 'healthy', violationRate: this.metrics.resourceViolationRate, activeConnections: this.realTimeClients.size, lastEventTime: this.events.length > 0 ? this.events[this.events.length - 1].timestamp : 0, }; return { metrics: this.getMetrics(), recentEvents, violationTrends, topViolatingUsers, systemHealth, }; } /** * Calculate violation trends for dashboard */ calculateViolationTrends(since) { const hourlyBuckets = new Map(); this.events .filter(e => e.timestamp > since && this.isViolationEvent(e)) .forEach(e => { const hour = Math.floor(e.timestamp / (60 * 60 * 1000)); hourlyBuckets.set(hour, (hourlyBuckets.get(hour) || 0) + 1); }); return Array.from(hourlyBuckets.entries()) .map(([hour, count]) => ({ hour: hour * 60 * 60 * 1000, violations: count })) .sort((a, b) => a.hour - b.hour); } /** * Get top violating users */ getTopViolatingUsers(since) { const userViolations = new Map(); this.events .filter(e => e.timestamp > since && this.isViolationEvent(e) && e.details.userId) .forEach(e => { const userId = e.details.userId; userViolations.set(userId, (userViolations.get(userId) || 0) + 1); }); return Array.from(userViolations.entries()) .map(([userId, violations]) => ({ userId, violations })) .sort((a, b) => b.violations - a.violations) .slice(0, 10); } /** * Reset monitoring data (for testing) */ reset() { this.events = []; this.metrics = this.initializeMetrics(); this.violationCounts.clear(); this.realTimeClients.clear(); } /** * Cleanup resources */ async cleanup() { this.realTimeClients.clear(); this.removeAllListeners(); console.log('🔒 Security Monitor cleaned up'); } } exports.SecurityMonitor = SecurityMonitor; /** * Global security monitor instance */ let globalSecurityMonitor = null; /** * Get or create global security monitor instance */ function getSecurityMonitor(config, alertConfig) { if (!globalSecurityMonitor && config) { globalSecurityMonitor = new SecurityMonitor(config, alertConfig); } if (!globalSecurityMonitor) { throw new Error('Security monitor not initialized. Provide config on first call.'); } return globalSecurityMonitor; } /** * Reset global security monitor (for testing) */ function resetSecurityMonitor() { globalSecurityMonitor = null; }