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
JavaScript
"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;
}