Skip to main content
Glama

DollhouseMCP

by DollhouseMCP
SecurityTelemetry.tsโ€ข8.34 kB
/** * Security Telemetry for DollhouseMCP * * Tracks and aggregates security metrics for blocked attacks, * providing insights into threat patterns and system defense effectiveness. * * Issue #1269: Enhanced telemetry for memory injection protection */ import { SecurityEvent } from '../securityMonitor.js'; export interface AttackVector { type: string; count: number; lastSeen: string; severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'; blockedPatterns: string[]; } export interface SecurityMetrics { totalBlockedAttempts: number; uniqueAttackVectors: number; criticalAttacksBlocked: number; highSeverityBlocked: number; mediumSeverityBlocked: number; lowSeverityBlocked: number; topAttackVectors: AttackVector[]; attacksPerHour: number[]; lastUpdated: string; } export interface AttackTelemetryEntry { timestamp: string; attackType: string; pattern: string; severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'; source: string; blocked: boolean; metadata?: Record<string, any>; } export class SecurityTelemetry { private static attackHistory: AttackTelemetryEntry[] = []; private static readonly MAX_HISTORY = 10000; // Keep last 10k attack attempts private static readonly METRIC_WINDOW_HOURS = 24; // Track last 24 hours private static readonly attackVectorMap: Map<string, AttackVector> = new Map(); /** * Records a blocked attack attempt * FIX (PR #1313 review): Use UTC timestamps for consistency across timezones */ static recordBlockedAttack( attackType: string, pattern: string, severity: SecurityEvent['severity'], source: string, metadata?: Record<string, any> ): void { const entry: AttackTelemetryEntry = { timestamp: new Date().toISOString(), // ISO string is always UTC attackType, pattern, severity, source, blocked: true, metadata }; // Add to history with circular buffer this.attackHistory.push(entry); if (this.attackHistory.length > this.MAX_HISTORY) { this.attackHistory.shift(); } // Update attack vector map const vectorKey = `${attackType}:${pattern}`; const existing = this.attackVectorMap.get(vectorKey); if (existing) { existing.count++; existing.lastSeen = entry.timestamp; if (!existing.blockedPatterns.includes(pattern)) { existing.blockedPatterns.push(pattern); } } else { this.attackVectorMap.set(vectorKey, { type: attackType, count: 1, lastSeen: entry.timestamp, severity, blockedPatterns: [pattern] }); } } /** * Get aggregated security metrics */ static getMetrics(): SecurityMetrics { const now = new Date(); const windowStart = new Date(now.getTime() - this.METRIC_WINDOW_HOURS * 60 * 60 * 1000); // Filter to recent attacks const recentAttacks = this.attackHistory.filter( attack => new Date(attack.timestamp) >= windowStart ); // Count by severity let criticalCount = 0; let highCount = 0; let mediumCount = 0; let lowCount = 0; for (const attack of recentAttacks) { switch (attack.severity) { case 'CRITICAL': criticalCount++; break; case 'HIGH': highCount++; break; case 'MEDIUM': mediumCount++; break; case 'LOW': lowCount++; break; } } // Calculate attacks per hour // FIX (PR #1313 review): Ensure consistent UTC timezone handling for metrics const attacksPerHour: number[] = new Array(24).fill(0); const nowUTC = Date.now(); // Unix timestamp in UTC for (const attack of recentAttacks) { // attack.timestamp is ISO string (UTC), parse to get UTC time const attackTimeUTC = new Date(attack.timestamp).getTime(); const hoursAgo = Math.floor((nowUTC - attackTimeUTC) / (60 * 60 * 1000)); if (hoursAgo >= 0 && hoursAgo < 24) { attacksPerHour[23 - hoursAgo]++; } } // Get top attack vectors const vectorArray = Array.from(this.attackVectorMap.values()); // FIX: Create copy before sorting to avoid mutation (SonarCloud S4043) const topVectors = [...vectorArray] .sort((a, b) => b.count - a.count) .slice(0, 10); return { totalBlockedAttempts: recentAttacks.length, uniqueAttackVectors: new Set(recentAttacks.map(a => a.attackType)).size, criticalAttacksBlocked: criticalCount, highSeverityBlocked: highCount, mediumSeverityBlocked: mediumCount, lowSeverityBlocked: lowCount, topAttackVectors: topVectors, attacksPerHour, lastUpdated: new Date().toISOString() }; } /** * Get attack patterns by type */ static getAttackPatternsByType(attackType: string): string[] { const patterns = new Set<string>(); for (const attack of this.attackHistory) { if (attack.attackType === attackType) { patterns.add(attack.pattern); } } return Array.from(patterns); } /** * Get attack timeline for visualization */ static getAttackTimeline(hours: number = 24): { hour: string; count: number; severity: Record<string, number> }[] { const now = new Date(); const timeline: { hour: string; count: number; severity: Record<string, number> }[] = []; for (let i = hours - 1; i >= 0; i--) { const hourStart = new Date(now.getTime() - (i + 1) * 60 * 60 * 1000); const hourEnd = new Date(now.getTime() - i * 60 * 60 * 1000); const hourAttacks = this.attackHistory.filter(attack => { const attackTime = new Date(attack.timestamp); // For the most recent hour (i=0), include attacks up to and including "now" return i === 0 ? attackTime >= hourStart && attackTime <= now : attackTime >= hourStart && attackTime < hourEnd; }); const severityCounts: Record<string, number> = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 }; for (const attack of hourAttacks) { severityCounts[attack.severity]++; } timeline.push({ hour: hourStart.toISOString().split('T')[1].split(':')[0] + ':00', count: hourAttacks.length, severity: severityCounts }); } return timeline; } /** * Get summary report for security audits */ static generateReport(): string { const metrics = this.getMetrics(); const report = ` === Security Telemetry Report === Generated: ${new Date().toISOString()} Total Blocked Attacks (24h): ${metrics.totalBlockedAttempts} Unique Attack Vectors: ${metrics.uniqueAttackVectors} Severity Breakdown: - Critical: ${metrics.criticalAttacksBlocked} - High: ${metrics.highSeverityBlocked} - Medium: ${metrics.mediumSeverityBlocked} - Low: ${metrics.lowSeverityBlocked} Top Attack Vectors: ${metrics.topAttackVectors.map((v, i) => `${i + 1}. ${v.type} (${v.count} attempts, severity: ${v.severity})` ).join('\n')} Hourly Distribution (last 24h): ${metrics.attacksPerHour.map((count, i) => `Hour ${23 - i}: ${count} attacks` ).join(', ')} `; return report; } /** * Clear old telemetry data */ static clearOldData(daysToKeep: number = 30): void { if (daysToKeep === 0) { // Clear all data immediately this.attackHistory = []; this.attackVectorMap.clear(); return; } const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - daysToKeep); const cutoffTimestamp = cutoffDate.toISOString(); const index = this.attackHistory.findIndex( attack => attack.timestamp >= cutoffTimestamp ); if (index > 0) { this.attackHistory.splice(0, index); } // Clean up old vectors that haven't been seen recently for (const [key, vector] of this.attackVectorMap.entries()) { if (new Date(vector.lastSeen) < cutoffDate) { this.attackVectorMap.delete(key); } } } /** * Export telemetry data for external analysis */ static exportData(): { history: AttackTelemetryEntry[]; vectors: AttackVector[]; metrics: SecurityMetrics; } { return { history: [...this.attackHistory], vectors: Array.from(this.attackVectorMap.values()), metrics: this.getMetrics() }; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DollhouseMCP/DollhouseMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server