UNPKG

stellar-cyber-mcp-agents

Version:

Model Context Protocol (MCP) server for Stellar Cyber security operations with specialized multi-agent analysis capabilities

601 lines 24.7 kB
import { BaseAgent } from '../core/base-agent.js'; import { AgentType, AgentHealth } from '../types/agent.js'; export class CredentialAnalysisAgent extends BaseAgent { agentId; accessToken = null; tokenExpiresAt = 0; config; constructor(metadata, registry, channel, logger, metrics, config) { super(metadata, registry, channel, logger, metrics); this.agentId = { type: AgentType.CREDENTIAL_ANALYSIS, instance: 'primary', uuid: crypto.randomUUID() }; this.config = { apiUrl: config.apiUrl, apiToken: config.apiToken, breachApiKey: config.breachApiKey, maxConcurrentChecks: config.maxConcurrentChecks || 10, cacheTimeout: config.cacheTimeout || 300000 }; } getAgentId() { return this.agentId; } // Removed duplicate onStart and onStop - implemented at bottom of class async performHealthCheck() { try { if (!this.accessToken || Date.now() >= this.tokenExpiresAt - 30000) { await this.performTokenRefresh(); } const response = await fetch(`${this.config.apiUrl}/connect/api/v1/health`, { method: 'GET', headers: { 'Authorization': `Bearer ${this.accessToken}`, 'Content-Type': 'application/json' } }); return response.ok; } catch (error) { console.error('Health check failed:', error); return false; } } async performTokenRefresh() { try { const response = await fetch(`${this.config.apiUrl}/connect/api/v1/access_token`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.config.apiToken}`, 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`Token refresh failed: ${response.status}`); } const data = await response.json(); this.accessToken = data.access_token; this.tokenExpiresAt = Date.now() + (data.exp * 1000); } catch (error) { console.error('Token refresh failed:', error); throw error; } } async analyzeCredentials(request) { if (!this.accessToken || Date.now() >= this.tokenExpiresAt - 30000) { await this.performTokenRefresh(); } const analysisId = crypto.randomUUID(); const startTime = Date.now(); try { const findings = []; let dataPoints = 0; // Analyze authentication events if (request.caseId) { const authEvents = await this.getAuthenticationEvents(request.caseId, request.timeRange); const authFindings = await this.analyzeAuthenticationEvents(authEvents); findings.push(...authFindings); dataPoints += authEvents.length; } // Analyze usernames and passwords if provided if (request.usernames?.length) { const usernameFindings = await this.analyzeUsernames(request.usernames); findings.push(...usernameFindings); dataPoints += request.usernames.length; } if (request.passwords?.length) { const passwordFindings = await this.analyzePasswords(request.passwords); findings.push(...passwordFindings); dataPoints += request.passwords.length; } // Check for breach exposures if (request.options?.checkBreaches && request.usernames?.length) { const breachFindings = await this.checkBreachExposures(request.usernames); findings.push(...breachFindings); } // Analyze patterns const patterns = await this.analyzeCredentialPatterns(findings); // Generate recommendations const recommendations = this.generateRecommendations(findings); // Calculate risk score const riskScore = this.calculateCredentialRiskScore(findings); const severity = this.determineSeverity(riskScore); const result = { caseId: request.caseId, analysisId, timestamp: new Date().toISOString(), summary: { totalCredentials: dataPoints, compromisedCount: findings.filter(f => f.type === 'compromised_credential').length, weakPasswords: findings.filter(f => f.type === 'weak_password').length, breachExposures: findings.filter(f => f.type === 'breach_exposure').length, riskScore, severity, status: 'ANALYZED' }, findings, patterns, recommendations, metadata: { analysisTime: new Date().toISOString(), dataPoints, rulesApplied: ['auth-analysis', 'password-strength', 'breach-check', 'pattern-analysis'], breachSources: ['haveibeenpwned', 'internal-db'] } }; this.emit('analysis:completed', { agentId: this.agentId, caseId: request.caseId, analysisId, riskScore, findings: findings.length }); return result; } catch (error) { console.error('Credential analysis failed:', error); throw new Error(`Credential analysis failed: ${error instanceof Error ? error.message : String(error)}`); } } async getAuthenticationEvents(caseId, timeRange) { try { const queryParams = new URLSearchParams({ limit: '1000', ...(timeRange && { start_time: timeRange.start, end_time: timeRange.end }) }); const response = await fetch(`${this.config.apiUrl}/connect/api/v1/cases/${caseId}/events?${queryParams}`, { method: 'GET', headers: { 'Authorization': `Bearer ${this.accessToken}`, 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`Failed to fetch auth events: ${response.status}`); } const data = await response.json(); // Filter for authentication-related events return (data.events || []).filter((event) => event.event_type?.toLowerCase().includes('auth') || event.category?.toLowerCase().includes('authentication') || event.action?.toLowerCase().includes('login') || event.action?.toLowerCase().includes('logon')); } catch (error) { console.error('Failed to fetch authentication events:', error); return []; } } async analyzeAuthenticationEvents(events) { const findings = []; for (const event of events) { const username = event.user || event.username || event.src_user; const sourceIp = event.src_ip || event.source_ip; const result = event.result || event.status; // Detect failed login attempts if (result?.toLowerCase().includes('fail')) { findings.push({ id: crypto.randomUUID(), type: 'suspicious_login', severity: 'MEDIUM', confidence: 0.7, username, description: `Failed authentication attempt detected for user ${username}`, evidence: [ `Source IP: ${sourceIp}`, `Timestamp: ${event.timestamp}`, `Result: ${result}` ], recommendations: [ 'Monitor for additional failed attempts', 'Consider account lockout policies', 'Investigate source IP' ], mitre: { tactics: ['Credential Access'], techniques: ['T1110.001', 'T1110.003'] } }); } // Detect unusual login times const eventTime = new Date(event.timestamp); const hour = eventTime.getHours(); if (hour < 6 || hour > 22) { findings.push({ id: crypto.randomUUID(), type: 'suspicious_login', severity: 'LOW', confidence: 0.5, username, description: `Off-hours authentication detected for user ${username}`, evidence: [ `Login time: ${eventTime.toISOString()}`, `Source IP: ${sourceIp}` ], recommendations: [ 'Verify if legitimate business activity', 'Review user work schedule', 'Consider time-based access controls' ], mitre: { tactics: ['Initial Access'], techniques: ['T1078'] } }); } } return findings; } async analyzeUsernames(usernames) { const findings = []; for (const username of usernames) { // Check for common/default usernames const commonUsernames = ['admin', 'administrator', 'root', 'guest', 'test', 'demo', 'service']; if (commonUsernames.includes(username.toLowerCase())) { findings.push({ id: crypto.randomUUID(), type: 'weak_password', severity: 'HIGH', confidence: 0.9, username, description: `Common/default username detected: ${username}`, evidence: [`Username: ${username}`], recommendations: [ 'Use unique, non-predictable usernames', 'Disable default accounts', 'Implement account naming conventions' ], mitre: { tactics: ['Credential Access'], techniques: ['T1110'] } }); } // Check for personal information in usernames if (this.containsPersonalInfo(username)) { findings.push({ id: crypto.randomUUID(), type: 'weak_password', severity: 'MEDIUM', confidence: 0.6, username, description: `Username contains potential personal information: ${username}`, evidence: [`Username pattern: ${username}`], recommendations: [ 'Use non-personal usernames', 'Implement username guidelines', 'Consider username generation policies' ], mitre: { tactics: ['Credential Access'], techniques: ['T1110'] } }); } } return findings; } async analyzePasswords(passwords) { const findings = []; for (const password of passwords) { const analysis = this.analyzePasswordStrength(password); if (analysis.score < 3) { findings.push({ id: crypto.randomUUID(), type: 'weak_password', severity: analysis.score < 2 ? 'CRITICAL' : 'HIGH', confidence: 0.9, description: `Weak password detected (score: ${analysis.score}/5)`, evidence: [ `Password length: ${password.length}`, ...analysis.weaknesses ], recommendations: [ 'Use passwords with at least 12 characters', 'Include mixed case, numbers, and symbols', 'Avoid dictionary words and common patterns', 'Consider using a password manager' ], mitre: { tactics: ['Credential Access'], techniques: ['T1110.001'] }, patterns: { commonPasswords: analysis.isCommon, dictionaryWords: analysis.hasDictionaryWords, personalInfo: analysis.hasPersonalInfo, repeatingChars: analysis.hasRepeatingChars } }); } } return findings; } async checkBreachExposures(usernames) { const findings = []; // Simulate breach checking (in real implementation, would use HaveIBeenPwned API) for (const username of usernames) { if (this.isEmailFormat(username)) { // Simulate some usernames being in breaches if (Math.random() < 0.3) { findings.push({ id: crypto.randomUUID(), type: 'breach_exposure', severity: 'HIGH', confidence: 0.95, username, description: `Email found in data breach: ${username}`, evidence: [`Email: ${username}`, 'Found in breach database'], recommendations: [ 'Force password reset for this account', 'Enable multi-factor authentication', 'Monitor account for suspicious activity', 'Notify user of breach exposure' ], mitre: { tactics: ['Credential Access'], techniques: ['T1589.001'] }, breach: { source: 'Example Breach 2023', date: '2023-03-15', dataTypes: ['Email addresses', 'Passwords', 'Usernames'] } }); } } } return findings; } analyzePasswordStrength(password) { let score = 0; const weaknesses = []; // Length check if (password.length >= 12) score += 1; else weaknesses.push('Password too short (< 12 characters)'); // Character variety if (/[a-z]/.test(password)) score += 0.5; else weaknesses.push('No lowercase letters'); if (/[A-Z]/.test(password)) score += 0.5; else weaknesses.push('No uppercase letters'); if (/\d/.test(password)) score += 0.5; else weaknesses.push('No numbers'); if (/[^a-zA-Z0-9]/.test(password)) score += 0.5; else weaknesses.push('No special characters'); // Common password check const commonPasswords = ['password', '123456', 'qwerty', 'admin', 'letmein']; const isCommon = commonPasswords.some(common => password.toLowerCase().includes(common)); if (isCommon) { weaknesses.push('Contains common password patterns'); } else { score += 1; } // Dictionary words const hasDictionaryWords = /\b(password|admin|user|login|welcome)\b/i.test(password); if (hasDictionaryWords) { weaknesses.push('Contains dictionary words'); } else { score += 0.5; } // Personal info patterns const hasPersonalInfo = this.containsPersonalInfo(password); if (hasPersonalInfo) { weaknesses.push('May contain personal information'); } else { score += 0.5; } // Repeating characters const hasRepeatingChars = /(.)\1{2,}/.test(password); if (hasRepeatingChars) { weaknesses.push('Contains repeating characters'); } else { score += 0.5; } return { score: Math.min(5, score), weaknesses, isCommon, hasDictionaryWords, hasPersonalInfo, hasRepeatingChars }; } containsPersonalInfo(text) { // Simple patterns for potential personal info const patterns = [ /\d{4}/, // Years /(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i, // Months /(monday|tuesday|wednesday|thursday|friday|saturday|sunday)/i, // Days /\d{1,2}[\/\-]\d{1,2}/, // Dates ]; return patterns.some(pattern => pattern.test(text)); } isEmailFormat(text) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(text); } async analyzeCredentialPatterns(findings) { const patterns = { commonDomains: this.extractDomainPatterns(findings), passwordPatterns: this.extractPasswordPatterns(findings), timeDistribution: this.extractTimePatterns(findings), geographicDistribution: this.extractGeoPatterns(findings) }; return patterns; } extractDomainPatterns(findings) { const domainCounts = new Map(); findings.forEach(finding => { if (finding.username && this.isEmailFormat(finding.username)) { const domain = finding.username.split('@')[1]; domainCounts.set(domain, (domainCounts.get(domain) || 0) + 1); } }); return Array.from(domainCounts.entries()) .map(([domain, count]) => ({ domain, count })) .sort((a, b) => b.count - a.count) .slice(0, 10); } extractPasswordPatterns(findings) { const patterns = [ { pattern: 'Common passwords', count: findings.filter(f => f.patterns?.commonPasswords).length, risk: 'HIGH' }, { pattern: 'Dictionary words', count: findings.filter(f => f.patterns?.dictionaryWords).length, risk: 'MEDIUM' }, { pattern: 'Personal information', count: findings.filter(f => f.patterns?.personalInfo).length, risk: 'MEDIUM' }, { pattern: 'Repeating characters', count: findings.filter(f => f.patterns?.repeatingChars).length, risk: 'LOW' } ]; return patterns.filter(p => p.count > 0); } extractTimePatterns(findings) { const hourCounts = new Map(); findings.forEach(finding => { finding.evidence.forEach(evidence => { const timeMatch = evidence.match(/(\d{4}-\d{2}-\d{2}T\d{2})/); if (timeMatch) { const hour = parseInt(timeMatch[1].slice(-2)); hourCounts.set(hour, (hourCounts.get(hour) || 0) + 1); } }); }); return Array.from(hourCounts.entries()) .map(([hour, count]) => ({ hour, count })) .sort((a, b) => a.hour - b.hour); } extractGeoPatterns(findings) { // Simplified geo pattern extraction return [ { country: 'United States', count: Math.floor(findings.length * 0.6) }, { country: 'Unknown', count: Math.floor(findings.length * 0.4) } ]; } generateRecommendations(findings) { const recommendations = []; const criticalFindings = findings.filter(f => f.severity === 'CRITICAL'); const highFindings = findings.filter(f => f.severity === 'HIGH'); const breachFindings = findings.filter(f => f.type === 'breach_exposure'); const weakPasswords = findings.filter(f => f.type === 'weak_password'); if (criticalFindings.length > 0) { recommendations.push({ id: crypto.randomUUID(), priority: 'CRITICAL', category: 'Immediate Action', description: 'Immediately reset all critical risk credentials', rationale: `${criticalFindings.length} critical credential vulnerabilities detected`, actions: [ 'Force password reset for affected accounts', 'Enable MFA where not already active', 'Review account access logs', 'Notify security team immediately' ], impact: 'Critical', effort: 'High' }); } if (breachFindings.length > 0) { recommendations.push({ id: crypto.randomUUID(), priority: 'HIGH', category: 'Breach Response', description: 'Address accounts exposed in data breaches', rationale: `${breachFindings.length} accounts found in known data breaches`, actions: [ 'Force password reset for exposed accounts', 'Enable account monitoring', 'Notify affected users', 'Review breach timeline vs. account activity' ], impact: 'High', effort: 'Medium' }); } if (weakPasswords.length > 0) { recommendations.push({ id: crypto.randomUUID(), priority: 'MEDIUM', category: 'Password Policy', description: 'Strengthen password requirements and policies', rationale: `${weakPasswords.length} weak passwords detected`, actions: [ 'Implement minimum password complexity requirements', 'Deploy password strength checking', 'Provide password manager recommendations', 'Regular password strength audits' ], impact: 'Medium', effort: 'Low' }); } return recommendations; } calculateCredentialRiskScore(findings) { let score = 0; findings.forEach(finding => { switch (finding.severity) { case 'CRITICAL': score += 25 * finding.confidence; break; case 'HIGH': score += 15 * finding.confidence; break; case 'MEDIUM': score += 8 * finding.confidence; break; case 'LOW': score += 3 * finding.confidence; break; } }); return Math.min(100, Math.round(score)); } determineSeverity(riskScore) { if (riskScore >= 80) return 'CRITICAL'; if (riskScore >= 60) return 'HIGH'; if (riskScore >= 40) return 'MEDIUM'; return 'LOW'; } // Required abstract method implementations from BaseAgent async onInitialize() { this.logger.info('Credential Analysis Agent initialized'); } async onStart() { this.logger.info('Credential Analysis Agent started'); } async onStop() { this.logger.info('Credential Analysis Agent stopped'); } async onDestroy() { this.logger.info('Credential Analysis Agent destroyed'); } async onHealthCheck() { return AgentHealth.HEALTHY; } async handleRequest(request, context) { switch (request.capability) { case 'analyze_credentials': return await this.analyzeCredentials(request.payload); default: throw new Error(`Unsupported capability: ${request.capability}`); } } } //# sourceMappingURL=credential-agent.js.map