Skip to main content
Glama
bradcstevens

Copilot Studio Agent Direct Line MCP Server

by bradcstevens
security-monitor.ts14.5 kB
/** * Security monitoring and intrusion detection service */ import type { SecurityAuditEvent, SecurityEventType, } from '../middleware/security.js'; /** * Threat level classification */ export type ThreatLevel = 'low' | 'medium' | 'high' | 'critical'; /** * Security incident type */ export type IncidentType = | 'brute_force' | 'sql_injection' | 'xss_attempt' | 'path_traversal' | 'rate_limit_abuse' | 'unauthorized_access' | 'token_theft' | 'suspicious_pattern' | 'data_exfiltration'; /** * Security incident */ export interface SecurityIncident { id: string; type: IncidentType; threatLevel: ThreatLevel; timestamp: number; sourceIP: string; userAgent?: string; userId?: string; description: string; details: Record<string, unknown>; resolved: boolean; resolvedAt?: number; responseAction?: string; } /** * Threat pattern */ interface ThreatPattern { type: IncidentType; pattern: RegExp | ((event: SecurityAuditEvent) => boolean); threatLevel: ThreatLevel; description: string; } /** * IP tracking for threat detection */ interface IPThreatTracking { failedAttempts: number; suspiciousEvents: number; lastEventAt: number; firstSeenAt: number; blockedUntil?: number; } /** * Security monitoring service configuration */ export interface SecurityMonitorConfig { maxFailedAttempts?: number; suspiciousActivityThreshold?: number; blockDuration?: number; // milliseconds enableAutoBlock?: boolean; alertWebhookUrl?: string; } /** * Security Monitor - Detects and responds to security threats */ export class SecurityMonitor { private config: Required<SecurityMonitorConfig>; private incidents: SecurityIncident[] = []; private ipTracking: Map<string, IPThreatTracking> = new Map(); private blockedIPs: Set<string> = new Set(); private threatPatterns: ThreatPattern[] = []; private incidentCounter: number = 0; private alertHandlers: Array<(incident: SecurityIncident) => void> = []; /** * Create a new security monitor * @param config - Configuration options */ constructor(config?: SecurityMonitorConfig) { this.config = { maxFailedAttempts: config?.maxFailedAttempts || 5, suspiciousActivityThreshold: config?.suspiciousActivityThreshold || 10, blockDuration: config?.blockDuration || 15 * 60 * 1000, // 15 minutes enableAutoBlock: config?.enableAutoBlock !== false, alertWebhookUrl: config?.alertWebhookUrl || '', }; this.initializeThreatPatterns(); this.startCleanupTimer(); } /** * Initialize threat detection patterns */ private initializeThreatPatterns(): void { // Brute force detection this.threatPatterns.push({ type: 'brute_force', pattern: (event: SecurityAuditEvent) => event.event === 'authentication_failure' && this.getIPFailureCount(event.ipAddress) >= 3, threatLevel: 'high', description: 'Multiple failed authentication attempts detected', }); // SQL injection patterns this.threatPatterns.push({ type: 'sql_injection', pattern: /(\bor\b|\band\b).*?=.*?=|union.*?select|select.*?from|drop.*?table/i, threatLevel: 'critical', description: 'SQL injection attempt detected', }); // XSS patterns this.threatPatterns.push({ type: 'xss_attempt', pattern: /<script|javascript:|on\w+\s*=/i, threatLevel: 'high', description: 'Cross-site scripting (XSS) attempt detected', }); // Path traversal patterns this.threatPatterns.push({ type: 'path_traversal', pattern: /\.\.\//, threatLevel: 'high', description: 'Path traversal attempt detected', }); // Suspicious activity pattern this.threatPatterns.push({ type: 'suspicious_pattern', pattern: (event: SecurityAuditEvent) => event.event === 'suspicious_activity', threatLevel: 'medium', description: 'Suspicious activity pattern detected', }); } /** * Process security event * @param event - Security audit event */ processEvent(event: SecurityAuditEvent): void { const ipAddress = event.ipAddress; // Update IP tracking this.updateIPTracking(ipAddress, event); // Check if IP is blocked if (this.isIPBlocked(ipAddress)) { console.warn(`[SecurityMonitor] Blocked IP ${ipAddress} attempted access`); return; } // Detect threats this.detectThreats(event); // Check for brute force if (event.event === 'authentication_failure') { this.handleAuthenticationFailure(event); } // Check for rate limit violations if (event.event === 'rate_limit_exceeded') { this.handleRateLimitViolation(event); } } /** * Detect threats in security event * @param event - Security audit event */ private detectThreats(event: SecurityAuditEvent): void { for (const pattern of this.threatPatterns) { let matched = false; if (typeof pattern.pattern === 'function') { matched = pattern.pattern(event); } else if (pattern.pattern instanceof RegExp) { // Check event details for pattern matches const details = JSON.stringify(event.details || {}); matched = pattern.pattern.test(details); } if (matched) { this.createIncident({ type: pattern.type, threatLevel: pattern.threatLevel, sourceIP: event.ipAddress, userAgent: event.userAgent, userId: event.userId, description: pattern.description, details: { event: event.event, correlationId: event.correlationId, eventDetails: event.details, }, }); } } } /** * Handle authentication failure * @param event - Security event */ private handleAuthenticationFailure(event: SecurityAuditEvent): void { const tracking = this.ipTracking.get(event.ipAddress); if (!tracking) return; tracking.failedAttempts++; if (tracking.failedAttempts >= this.config.maxFailedAttempts) { this.createIncident({ type: 'brute_force', threatLevel: 'high', sourceIP: event.ipAddress, userAgent: event.userAgent, userId: event.userId, description: `Brute force attack detected: ${tracking.failedAttempts} failed attempts`, details: { failedAttempts: tracking.failedAttempts, firstSeenAt: tracking.firstSeenAt, }, }); if (this.config.enableAutoBlock) { this.blockIP(event.ipAddress, this.config.blockDuration); } } } /** * Handle rate limit violation * @param event - Security event */ private handleRateLimitViolation(event: SecurityAuditEvent): void { const tracking = this.ipTracking.get(event.ipAddress); if (!tracking) return; tracking.suspiciousEvents++; const violations = (event.details?.violations as number) || 0; if (violations >= 5) { this.createIncident({ type: 'rate_limit_abuse', threatLevel: 'medium', sourceIP: event.ipAddress, userAgent: event.userAgent, userId: event.userId, description: `Rate limit abuse detected: ${violations} violations`, details: { violations, count: event.details?.count, limit: event.details?.limit, }, }); if (this.config.enableAutoBlock && violations >= 10) { this.blockIP(event.ipAddress, this.config.blockDuration); } } } /** * Create security incident * @param incident - Partial incident data */ private createIncident(incident: Omit<SecurityIncident, 'id' | 'timestamp' | 'resolved'>): void { const fullIncident: SecurityIncident = { id: this.generateIncidentId(), timestamp: Date.now(), resolved: false, ...incident, }; this.incidents.push(fullIncident); // Keep only last 10000 incidents if (this.incidents.length > 10000) { this.incidents = this.incidents.slice(-10000); } console.warn( `[SecurityMonitor] ${fullIncident.threatLevel.toUpperCase()} THREAT: ${fullIncident.description} (${fullIncident.sourceIP})` ); // Notify alert handlers this.alertHandlers.forEach((handler) => handler(fullIncident)); // Send webhook alert for high/critical threats if (['high', 'critical'].includes(fullIncident.threatLevel) && this.config.alertWebhookUrl) { this.sendWebhookAlert(fullIncident); } } /** * Update IP tracking * @param ipAddress - IP address * @param event - Security event */ private updateIPTracking(ipAddress: string, event: SecurityAuditEvent): void { let tracking = this.ipTracking.get(ipAddress); if (!tracking) { tracking = { failedAttempts: 0, suspiciousEvents: 0, lastEventAt: Date.now(), firstSeenAt: Date.now(), }; this.ipTracking.set(ipAddress, tracking); } tracking.lastEventAt = Date.now(); // Reset counters on successful login if (event.event === 'login_success') { tracking.failedAttempts = 0; tracking.suspiciousEvents = 0; } } /** * Get failure count for IP * @param ipAddress - IP address * @returns Failure count */ private getIPFailureCount(ipAddress: string): number { return this.ipTracking.get(ipAddress)?.failedAttempts || 0; } /** * Block IP address * @param ipAddress - IP to block * @param duration - Block duration in milliseconds */ blockIP(ipAddress: string, duration: number): void { this.blockedIPs.add(ipAddress); const tracking = this.ipTracking.get(ipAddress); if (tracking) { tracking.blockedUntil = Date.now() + duration; } console.warn(`[SecurityMonitor] Blocked IP ${ipAddress} for ${duration}ms`); // Auto-unblock after duration setTimeout(() => { this.unblockIP(ipAddress); }, duration); } /** * Unblock IP address * @param ipAddress - IP to unblock */ unblockIP(ipAddress: string): void { this.blockedIPs.delete(ipAddress); const tracking = this.ipTracking.get(ipAddress); if (tracking) { tracking.blockedUntil = undefined; } console.error(`[SecurityMonitor] Unblocked IP ${ipAddress}`); } /** * Check if IP is blocked * @param ipAddress - IP to check * @returns True if blocked */ isIPBlocked(ipAddress: string): boolean { const tracking = this.ipTracking.get(ipAddress); if (tracking?.blockedUntil && Date.now() < tracking.blockedUntil) { return true; } return this.blockedIPs.has(ipAddress); } /** * Get all incidents * @param filter - Optional filter * @returns Filtered incidents */ getIncidents(filter?: { type?: IncidentType; threatLevel?: ThreatLevel; resolved?: boolean; since?: number; }): SecurityIncident[] { let filtered = this.incidents; if (filter) { if (filter.type) { filtered = filtered.filter((i) => i.type === filter.type); } if (filter.threatLevel) { filtered = filtered.filter((i) => i.threatLevel === filter.threatLevel); } if (filter.resolved !== undefined) { filtered = filtered.filter((i) => i.resolved === filter.resolved); } if (filter.since !== undefined) { filtered = filtered.filter((i) => i.timestamp >= filter.since!); } } return filtered; } /** * Resolve incident * @param incidentId - Incident ID * @param responseAction - Response action taken */ resolveIncident(incidentId: string, responseAction: string): void { const incident = this.incidents.find((i) => i.id === incidentId); if (incident) { incident.resolved = true; incident.resolvedAt = Date.now(); incident.responseAction = responseAction; console.error(`[SecurityMonitor] Resolved incident ${incidentId}: ${responseAction}`); } } /** * Register alert handler * @param handler - Alert handler function */ onAlert(handler: (incident: SecurityIncident) => void): void { this.alertHandlers.push(handler); } /** * Send webhook alert * @param incident - Security incident */ private async sendWebhookAlert(incident: SecurityIncident): Promise<void> { if (!this.config.alertWebhookUrl) return; try { await fetch(this.config.alertWebhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ severity: incident.threatLevel, type: incident.type, message: incident.description, timestamp: incident.timestamp, source_ip: incident.sourceIP, details: incident.details, }), }); } catch (error) { console.error('[SecurityMonitor] Failed to send webhook alert:', error); } } /** * Start cleanup timer */ private startCleanupTimer(): void { // Clean up old tracking data every hour setInterval(() => { const now = Date.now(); const oneHourAgo = now - 60 * 60 * 1000; for (const [ip, tracking] of this.ipTracking.entries()) { if (tracking.lastEventAt < oneHourAgo) { this.ipTracking.delete(ip); } } }, 60 * 60 * 1000); } /** * Generate incident ID * @returns Unique incident ID */ private generateIncidentId(): string { return `incident-${Date.now()}-${++this.incidentCounter}`; } /** * Get security metrics */ getMetrics(): { totalIncidents: number; unresolvedIncidents: number; blockedIPs: number; trackedIPs: number; incidentsByThreatLevel: Record<ThreatLevel, number>; incidentsByType: Record<string, number>; } { const incidentsByThreatLevel: Record<ThreatLevel, number> = { low: 0, medium: 0, high: 0, critical: 0, }; const incidentsByType: Record<string, number> = {}; for (const incident of this.incidents) { incidentsByThreatLevel[incident.threatLevel]++; incidentsByType[incident.type] = (incidentsByType[incident.type] || 0) + 1; } return { totalIncidents: this.incidents.length, unresolvedIncidents: this.incidents.filter((i) => !i.resolved).length, blockedIPs: this.blockedIPs.size, trackedIPs: this.ipTracking.size, incidentsByThreatLevel, incidentsByType, }; } }

Latest Blog Posts

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/bradcstevens/copilot-studio-agent-direct-line-mcp'

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