UNPKG

stellar-cyber-mcp-agents

Version:

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

1,059 lines 46.5 kB
import { BaseAgent } from '../core/base-agent.js'; import { AgentHealth } from '../types/agent.js'; export class InvestigationAgent extends BaseAgent { config; accessToken = null; tokenExpiresAt = 0; refreshPromise = null; constructor(metadata, registry, channel, logger, metrics, config) { super(metadata, registry, channel, logger, metrics); this.config = { requestTimeout: 30000, maxRetries: 3, retryDelay: 1000, ...config }; } async onInitialize() { this.logger.info('Initializing Investigation Agent'); // Initialize authentication await this.refreshToken(); this.logger.info('Investigation Agent initialized successfully'); } async onStart() { this.logger.info('Starting Investigation Agent'); // Start token refresh monitoring this.startTokenRefreshMonitoring(); this.logger.info('Investigation Agent started successfully'); } async onStop() { this.logger.info('Stopping Investigation Agent'); // Clean up any ongoing operations this.refreshPromise = null; this.logger.info('Investigation Agent stopped successfully'); } async onDestroy() { this.logger.info('Destroying Investigation Agent'); // Clear tokens and state this.accessToken = null; this.tokenExpiresAt = 0; this.refreshPromise = null; this.logger.info('Investigation Agent destroyed successfully'); } async onHealthCheck() { try { // Check API connectivity const response = await this.makeRequest('GET', '/connect/api/v1/health'); if (response.ok) { return AgentHealth.HEALTHY; } else { return AgentHealth.DEGRADED; } } catch (error) { this.logger.error('Health check failed', { error }); return AgentHealth.UNHEALTHY; } } async handleRequest(request, context) { const { capability, payload } = request; this.logger.debug('Handling Investigation Agent request', { capability, requestId: request.id, sourceAgent: request.sourceAgentId }); switch (capability) { case 'investigate_case': return await this.investigateCase(payload.caseId, payload.options); case 'get_case_details': return await this.getCaseDetails(payload.caseId); case 'analyze_case_observables': return await this.analyzeCaseObservables(payload.caseId); case 'get_case_timeline': return await this.getCaseTimeline(payload.caseId); case 'get_case_artifacts': return await this.getCaseArtifacts(payload.caseId); case 'suggest_workflow': return await this.suggestWorkflow(payload.caseId); case 'search_cases': return await this.searchCases(payload.query, payload.filters); case 'get_related_cases': return await this.getRelatedCases(payload.caseId, payload.method); case 'update_case_status': return await this.updateCaseStatus(payload.caseId, payload.status, payload.comment); case 'add_case_comment': return await this.addCaseComment(payload.caseId, payload.comment); case 'get_case_activities': return await this.getCaseActivities(payload.caseId); case 'get_case_observables': return await this.getCaseObservables(payload.caseId); case 'get_case_alerts': return await this.getCaseAlerts(payload.caseId); case 'get_case_comments': return await this.getCaseComments(payload.caseId); case 'get_case_scores': return await this.getCaseScores(payload.caseId); case 'get_threat_intelligence': return await this.getThreatIntelligence(payload.caseId); default: throw new Error(`Unknown capability: ${capability}`); } } async investigateCase(caseId, options = {}) { const investigationId = crypto.randomUUID(); const timestamp = new Date().toISOString(); this.logger.info('Starting case investigation', { caseId, investigationId }); try { // Fetch comprehensive case data in parallel const [caseDetails, caseScores, observables, activities, comments, alerts, threatIntel] = await Promise.allSettled([ this.getCaseDetails(caseId), this.getCaseScores(caseId), this.getCaseObservables(caseId), this.getCaseActivities(caseId), this.getCaseComments(caseId), this.getCaseAlerts(caseId), this.getThreatIntelligence(caseId) ]); // Combine all available data const caseData = this.combineInvestigationData({ caseDetails: this.getSettledValue(caseDetails), caseScores: this.getSettledValue(caseScores), observables: this.getSettledValue(observables), activities: this.getSettledValue(activities), comments: this.getSettledValue(comments), alerts: this.getSettledValue(alerts), threatIntel: this.getSettledValue(threatIntel) }); // Perform analysis const findings = await this.analyzeFindings(caseData); const recommendations = await this.generateRecommendations(caseData, findings); const nextSteps = await this.generateNextSteps(caseData, findings); const relatedCases = await this.findRelatedCases(caseId, caseData); const timeline = await this.buildTimeline(caseData); const artifacts = await this.extractArtifacts(caseData); const workflow = await this.suggestWorkflow(caseId); // Generate summary const summary = this.generateInvestigationSummary(caseData, findings); const result = { caseId, investigationId, timestamp, summary, findings, recommendations, nextSteps, relatedCases, timeline, artifacts, workflow }; this.logger.info('Case investigation completed', { caseId, investigationId, findingsCount: findings.length, recommendationsCount: recommendations.length }); return result; } catch (error) { this.logger.error('Case investigation failed', { caseId, investigationId, error }); throw error; } } async getCaseDetails(caseId) { const response = await this.makeRequest('GET', `/connect/api/v1/cases/${caseId}`); if (!response.ok) { throw new Error(`Failed to fetch case details: ${response.status}`); } return await response.json(); } async getCaseScores(caseId) { const response = await this.makeRequest('GET', `/connect/api/v1/cases/${caseId}/scores`); if (!response.ok) { throw new Error(`Failed to fetch case scores: ${response.status}`); } return await response.json(); } async getCaseObservables(caseId) { try { const response = await this.makeRequest('GET', `/connect/api/v1/cases/${caseId}/observables`); if (!response.ok) { this.logger.warn(`Failed to fetch case observables: ${response.status}`, { caseId }); return []; } const data = await response.json(); // Handle the actual API response structure if (data.data && data.data.observables) { // Convert the grouped observables format to a flat array const observables = []; const observableTypes = data.data.observables; // Process each observable type (host, user, process, etc.) Object.keys(observableTypes).forEach(type => { const typeData = observableTypes[type]; if (typeData.values && Array.isArray(typeData.values)) { typeData.values.forEach((item, index) => { observables.push({ id: `${type}_${index}`, type: type, value: item.value, tlp: 'WHITE', // Default TLP confidence: 0.8, // Default confidence tags: [type], firstSeen: new Date().toISOString(), // Default to current time lastSeen: new Date().toISOString(), // Default to current time count: 1, // Default count metadata: { count: typeData.count, source: 'stellar-cyber' } }); }); } }); this.logger.info(`Converted ${observables.length} observables from grouped format`, { caseId }); return observables; } // Fallback for direct array format return data.observables || data.data || []; } catch (error) { this.logger.warn('Case observables endpoint not available, returning empty array', { caseId, error: error instanceof Error ? error.message : 'Unknown error' }); return []; } } async getCaseActivities(caseId) { try { const response = await this.makeRequest('GET', `/connect/api/v1/cases/${caseId}/activities`); if (!response.ok) { this.logger.warn(`Failed to fetch case activities: ${response.status}`, { caseId }); return []; } const data = await response.json(); this.logger.info(`Retrieved ${data.data?.length || 0} activities from API`, { caseId }); return data.data || data.activities || []; } catch (error) { this.logger.warn('Case activities endpoint not available, returning empty array', { caseId, error: error instanceof Error ? error.message : 'Unknown error' }); return []; } } async getCaseComments(caseId) { try { const response = await this.makeRequest('GET', `/connect/api/v1/cases/${caseId}/comments`); if (!response.ok) { this.logger.warn(`Failed to fetch case comments: ${response.status}`, { caseId }); return []; } const data = await response.json(); this.logger.info(`Retrieved ${data.data?.length || 0} comments from API`, { caseId }); return data.data || data.comments || []; } catch (error) { this.logger.warn('Case comments endpoint not available, returning empty array', { caseId, error: error instanceof Error ? error.message : 'Unknown error' }); return []; } } async getCaseAlerts(caseId) { try { // First try to get alerts from main case details const caseDetails = await this.getCaseDetails(caseId); // Check if alerts are embedded in case details if (caseDetails.alerts && Array.isArray(caseDetails.alerts)) { this.logger.info(`Found ${caseDetails.alerts.length} alerts in case details`, { caseId }); return caseDetails.alerts; } // If case details contain alert_ids or similar, extract those if (caseDetails.alert_ids && Array.isArray(caseDetails.alert_ids)) { this.logger.info(`Found ${caseDetails.alert_ids.length} alert IDs in case details`, { caseId }); // Convert alert IDs to basic alert objects return caseDetails.alert_ids.map((id, index) => ({ id, name: `Alert ${index + 1}`, severity: caseDetails.severity || 'medium', timestamp: caseDetails.created_at || caseDetails.createdAt || new Date().toISOString(), source: 'stellar-cyber', description: `Alert associated with case ${caseId}` })); } // Fall back to separate endpoint with required parameters const response = await this.makeRequest('GET', `/connect/api/v1/cases/${caseId}/alerts?skip=0&limit=50`); if (!response.ok) { this.logger.warn(`Failed to fetch case alerts from separate endpoint: ${response.status}`, { caseId }); return []; } const data = await response.json(); // Handle the actual API response structure for alerts if (data.data && data.data.docs && Array.isArray(data.data.docs)) { this.logger.info(`Retrieved ${data.data.docs.length} alerts from API`, { caseId }); // Convert Elasticsearch docs to Alert objects return data.data.docs.map((doc, index) => ({ id: doc._id, name: `Alert ${index + 1}`, severity: 'medium', // Default since not in alert doc timestamp: new Date().toISOString(), // Default since not in alert doc source: doc._index, description: `Alert from ${doc._index}`, details: doc._source, found: doc.found })); } // Fallback for other possible formats this.logger.info(`Retrieved ${data.data?.length || 0} alerts from API (fallback format)`, { caseId }); return data.data || []; } catch (error) { this.logger.warn('Unable to fetch case alerts, returning empty array', { caseId, error: error instanceof Error ? error.message : 'Unknown error' }); return []; } } async getThreatIntelligence(caseId) { try { const response = await this.makeRequest('GET', `/connect/api/v1/cases/${caseId}/threat-intel`); if (!response.ok) { this.logger.warn(`Failed to fetch threat intelligence: ${response.status}`, { caseId }); return { indicators: [], campaigns: [], actors: [], recommendations: [], sources: [] }; } return await response.json(); } catch (error) { this.logger.warn('Threat intelligence endpoint not available, returning empty data', { caseId, error: error instanceof Error ? error.message : 'Unknown error' }); return { indicators: [], campaigns: [], actors: [], recommendations: [], sources: [] }; } } async analyzeCaseObservables(caseId) { try { const observables = await this.getCaseObservables(caseId); // Ensure observables is an array if (!Array.isArray(observables)) { this.logger.warn('Observables is not an array, treating as empty', { caseId, observables: typeof observables }); return { totalObservables: 0, byType: {}, highConfidence: [], suspicious: [], malicious: [], recommendations: [], error: 'Observables data is not in expected array format' }; } this.logger.info(`Analyzing ${observables.length} observables for case`, { caseId }); const analysis = { totalObservables: observables.length, byType: this.groupObservablesByType(observables), highConfidence: observables.filter(o => o.confidence && o.confidence > 0.8), suspicious: observables.filter(o => o.tags && Array.isArray(o.tags) && o.tags.includes('suspicious')), malicious: observables.filter(o => o.tags && Array.isArray(o.tags) && o.tags.includes('malicious')), recommendations: this.generateObservableRecommendations(observables) }; return analysis; } catch (error) { this.logger.error('Error analyzing case observables', { caseId, error: error instanceof Error ? error.message : 'Unknown error' }); return { totalObservables: 0, byType: {}, highConfidence: [], suspicious: [], malicious: [], recommendations: [], error: error instanceof Error ? error.message : 'Unknown error during analysis' }; } } async getCaseTimeline(caseId) { try { const activities = await this.getCaseActivities(caseId); const alerts = await this.getCaseAlerts(caseId); const timeline = []; // Add activities to timeline - ensure activities is an array if (Array.isArray(activities)) { activities.forEach(activity => { timeline.push({ timestamp: activity.timestamp, type: 'activity', description: `${activity.actor} performed ${activity.action}`, severity: activity.severity, source: 'activities', details: activity }); }); } // Add alerts to timeline - ensure alerts is an array if (Array.isArray(alerts)) { alerts.forEach(alert => { timeline.push({ timestamp: alert.timestamp, type: 'alert', description: alert.name, severity: alert.severity, source: alert.source, details: alert }); }); } // Sort by timestamp timeline.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()); return timeline; } catch (error) { this.logger.error('Error building case timeline', { caseId, error: error instanceof Error ? error.message : 'Unknown error' }); return []; } } async getCaseArtifacts(caseId) { try { const response = await this.makeRequest('GET', `/connect/api/v1/cases/${caseId}/artifacts`); if (!response.ok) { this.logger.warn(`Failed to fetch case artifacts: ${response.status}`, { caseId }); return []; } const data = await response.json(); this.logger.info(`Retrieved ${data.data?.length || 0} artifacts from API`, { caseId }); return data.data || data.artifacts || []; } catch (error) { this.logger.warn('Case artifacts endpoint not available, returning empty array', { caseId, error: error instanceof Error ? error.message : 'Unknown error' }); return []; } } async suggestWorkflow(caseId) { const caseDetails = await this.getCaseDetails(caseId); // Analyze case characteristics to suggest appropriate workflow const threatType = this.identifyThreatType(caseDetails); const severity = caseDetails.severity; const workflows = this.getWorkflowDefinitions(); const suggestedWorkflow = this.selectWorkflow(workflows, threatType, severity); return suggestedWorkflow; } async searchCases(query, filters = {}) { const params = new URLSearchParams(); // Only use basic filtering parameters that we know work // Based on get-latest-case.js example: limit and sort are supported if (filters.limit) { // Enforce API limit cap to prevent 500 errors const limit = Math.min(filters.limit, 20); params.append('limit', limit.toString()); } else { params.append('limit', '20'); // Default limit - API fails with 500 error above 20 } if (filters.sort) { params.append('sort', filters.sort); } else { params.append('sort', '-created_at'); // Default to newest first } const queryString = params.toString(); const endpoint = `/connect/api/v1/cases?${queryString}`; const response = await this.makeRequest('GET', endpoint); if (!response.ok) { throw new Error(`Failed to search cases: ${response.status}`); } const result = await response.json(); // Apply client-side filtering if query or other filters are provided if (result.data && result.data.cases) { let cases = result.data.cases; // Apply query filter on case name, description, etc. if (query && query.trim()) { const searchTerm = query.toLowerCase(); cases = cases.filter((caseItem) => { return ((caseItem.name && caseItem.name.toLowerCase().includes(searchTerm)) || (caseItem.description && caseItem.description.toLowerCase().includes(searchTerm)) || (caseItem.severity && caseItem.severity.toLowerCase().includes(searchTerm)) || (caseItem.status && caseItem.status.toLowerCase().includes(searchTerm))); }); } // Apply status filter if (filters.status) { cases = cases.filter((caseItem) => caseItem.status && caseItem.status.toLowerCase() === filters.status.toLowerCase()); } // Apply severity filter if (filters.severity) { cases = cases.filter((caseItem) => caseItem.severity && caseItem.severity.toLowerCase() === filters.severity.toLowerCase()); } // Apply assignee filter if (filters.assignee) { cases = cases.filter((caseItem) => (caseItem.assignee_name && caseItem.assignee_name.toLowerCase().includes(filters.assignee.toLowerCase())) || (caseItem.assignee_email && caseItem.assignee_email.toLowerCase().includes(filters.assignee.toLowerCase()))); } // Return filtered results in same format return { ...result, data: { ...result.data, cases: cases, total: cases.length } }; } return result; } async getRelatedCases(caseId, method = 'observables') { try { const response = await this.makeRequest('GET', `/connect/api/v1/cases/${caseId}/related?method=${method}`); if (!response.ok) { // If related cases endpoint doesn't exist, return empty array instead of failing this.logger.warn(`Related cases endpoint returned ${response.status}, skipping related case lookup`); return []; } const data = await response.json(); return data.relatedCases || []; } catch (error) { this.logger.warn('Failed to fetch related cases, continuing without', { error }); return []; } } async updateCaseStatus(caseId, status, comment) { const payload = { status }; if (comment) payload.comment = comment; const response = await this.makeRequest('PUT', `/connect/api/v1/cases/${caseId}/status`, payload); if (!response.ok) { throw new Error(`Failed to update case status: ${response.status}`); } return await response.json(); } async addCaseComment(caseId, comment) { const payload = { content: comment }; const response = await this.makeRequest('POST', `/connect/api/v1/cases/${caseId}/comments`, payload); if (!response.ok) { throw new Error(`Failed to add case comment: ${response.status}`); } return await response.json(); } async refreshToken() { if (this.refreshPromise) { return this.refreshPromise; } this.refreshPromise = this.performTokenRefresh(); try { await this.refreshPromise; } finally { this.refreshPromise = null; } } async performTokenRefresh() { 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); } isTokenValid() { return this.accessToken !== null && Date.now() < this.tokenExpiresAt - 30000; // 30 second buffer } async getAccessToken() { if (this.isTokenValid()) { return this.accessToken; } await this.refreshToken(); return this.accessToken; } async makeRequest(method, endpoint, body) { const token = await this.getAccessToken(); const options = { method, headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', } }; if (body) { options.body = JSON.stringify(body); } const response = await fetch(`${this.config.apiUrl}${endpoint}`, options); return response; } startTokenRefreshMonitoring() { // Skip background timers in MCP mode to prevent EPIPE errors if (process.env.MCP_MODE === 'true') return; setInterval(() => { if (!this.isTokenValid()) { this.refreshToken().catch(error => { this.logger.error('Token refresh failed', { error }); }); } }, 60000); // Check every minute } getSettledValue(result) { return result.status === 'fulfilled' ? result.value : null; } combineInvestigationData(data) { // Combine all investigation data into a unified structure return { case: data.caseDetails, scores: data.caseScores, observables: data.observables || [], activities: data.activities || [], comments: data.comments || [], alerts: data.alerts || [], threatIntel: data.threatIntel }; } async analyzeFindings(caseData) { const findings = []; // Analyze observables if (caseData.observables && Array.isArray(caseData.observables) && caseData.observables.length > 0) { const observableFindings = this.analyzeObservables(caseData.observables); findings.push(...observableFindings); } // Analyze activities if (caseData.activities.length > 0) { const activityFindings = this.analyzeActivities(caseData.activities); findings.push(...activityFindings); } // Analyze alerts if (caseData.alerts.length > 0) { const alertFindings = this.analyzeAlerts(caseData.alerts); findings.push(...alertFindings); } return findings; } analyzeObservables(observables) { const findings = []; const maliciousObservables = observables.filter(o => o.tags && o.tags.includes('malicious')); const suspiciousObservables = observables.filter(o => o.tags && o.tags.includes('suspicious')); if (maliciousObservables.length > 0) { findings.push({ id: crypto.randomUUID(), category: 'Malicious Indicators', description: `Found ${maliciousObservables.length} malicious indicators`, severity: 'CRITICAL', confidence: 0.9, evidence: maliciousObservables.map(o => `${o.type}: ${o.value}`), indicators: maliciousObservables.map(o => o.value), mitre: { tactics: ['Initial Access', 'Execution'], techniques: ['T1566', 'T1204'] } }); } if (suspiciousObservables.length > 0) { findings.push({ id: crypto.randomUUID(), category: 'Suspicious Indicators', description: `Found ${suspiciousObservables.length} suspicious indicators`, severity: 'HIGH', confidence: 0.7, evidence: suspiciousObservables.map(o => `${o.type}: ${o.value}`), indicators: suspiciousObservables.map(o => o.value), mitre: { tactics: ['Reconnaissance', 'Discovery'], techniques: ['T1595', 'T1082'] } }); } return findings; } analyzeActivities(activities) { const findings = []; const criticalActivities = activities.filter(a => a.severity === 'CRITICAL'); const highActivities = activities.filter(a => a.severity === 'HIGH'); if (criticalActivities.length > 0) { findings.push({ id: crypto.randomUUID(), category: 'Critical Activities', description: `Found ${criticalActivities.length} critical activities`, severity: 'CRITICAL', confidence: 0.9, evidence: criticalActivities.map(a => `${a.timestamp}: ${a.action}`), indicators: criticalActivities.map(a => a.id), mitre: { tactics: ['Persistence', 'Privilege Escalation'], techniques: ['T1053', 'T1055'] } }); } return findings; } analyzeAlerts(alerts) { const findings = []; const criticalAlerts = alerts.filter(a => a.severity === 'CRITICAL'); if (criticalAlerts.length > 0) { findings.push({ id: crypto.randomUUID(), category: 'Critical Alerts', description: `Found ${criticalAlerts.length} critical alerts`, severity: 'CRITICAL', confidence: 0.95, evidence: criticalAlerts.map(a => `${a.timestamp}: ${a.name}`), indicators: criticalAlerts.map(a => a.id), mitre: { tactics: ['Defense Evasion', 'Impact'], techniques: ['T1562', 'T1485'] } }); } return findings; } async generateRecommendations(caseData, findings) { const recommendations = []; const criticalFindings = findings.filter(f => f.severity === 'CRITICAL'); const highFindings = findings.filter(f => f.severity === 'HIGH'); if (criticalFindings.length > 0) { recommendations.push({ id: crypto.randomUUID(), priority: 'IMMEDIATE', category: 'Incident Response', description: 'Activate incident response team immediately', rationale: `Found ${criticalFindings.length} critical findings requiring immediate attention`, effort: 'High', impact: 'High', resources: ['SOC Team', 'Incident Response Team', 'CISO'] }); } if (caseData.observables && Array.isArray(caseData.observables) && caseData.observables.some((o) => o.tags && o.tags.includes('malicious'))) { recommendations.push({ id: crypto.randomUUID(), priority: 'HIGH', category: 'Containment', description: 'Block malicious indicators at network perimeter', rationale: 'Malicious indicators detected that could be blocked', effort: 'Medium', impact: 'High', resources: ['Network Security Team', 'Firewall', 'Proxy'] }); } return recommendations; } async generateNextSteps(caseData, findings) { const nextSteps = []; if (findings.some(f => f.severity === 'CRITICAL')) { nextSteps.push('Escalate to incident response team'); nextSteps.push('Implement immediate containment measures'); } if (caseData.observables && Array.isArray(caseData.observables) && caseData.observables.length > 0) { nextSteps.push('Enrich observables with threat intelligence'); nextSteps.push('Check for additional IOCs in environment'); } if (caseData.activities.length > 0) { nextSteps.push('Analyze activity timeline for attack progression'); nextSteps.push('Identify affected systems and accounts'); } nextSteps.push('Document findings and evidence'); nextSteps.push('Update case status and notify stakeholders'); return nextSteps; } async findRelatedCases(caseId, caseData) { // Use existing correlation logic to find related cases const relatedCases = await this.getRelatedCases(caseId, 'observables'); return relatedCases; } async buildTimeline(caseData) { const timeline = []; // Add activities - ensure it's an array if (Array.isArray(caseData.activities)) { caseData.activities.forEach((activity) => { timeline.push({ timestamp: activity.timestamp, type: 'activity', description: `${activity.actor}: ${activity.action}`, severity: activity.severity, source: 'activities', details: activity }); }); } // Add alerts - ensure it's an array if (Array.isArray(caseData.alerts)) { caseData.alerts.forEach((alert) => { timeline.push({ timestamp: alert.timestamp, type: 'alert', description: alert.name, severity: alert.severity, source: alert.source, details: alert }); }); } // Sort chronologically timeline.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()); return timeline; } async extractArtifacts(caseData) { const artifacts = []; try { // Fetch observables from the dedicated API endpoint if case ID is available let observables = []; if (caseData.case && caseData.case._id) { observables = await this.getCaseObservables(caseData.case._id); } else if (caseData._id) { observables = await this.getCaseObservables(caseData._id); } else { // Fallback to observables in case data if available observables = caseData.observables || []; } // Extract artifacts from observables - handle cases where observables might not be an array if (Array.isArray(observables)) { observables.forEach((observable) => { if (observable.type === 'file' || observable.type === 'hash') { artifacts.push({ id: crypto.randomUUID(), type: observable.type, name: observable.value, hash: observable.type === 'hash' ? observable.value : undefined, extractedAt: new Date().toISOString(), analysis: { verdict: observable.tags && observable.tags.includes('malicious') ? 'MALICIOUS' : 'UNKNOWN', confidence: observable.confidence || 0, signatures: observable.tags ? observable.tags.filter(tag => tag.startsWith('sig:')) : [], behaviors: observable.tags ? observable.tags.filter(tag => tag.startsWith('behavior:')) : [], family: observable.tags ? observable.tags.find(tag => tag.startsWith('family:'))?.substring(7) : undefined, variant: observable.tags ? observable.tags.find(tag => tag.startsWith('variant:'))?.substring(8) : undefined } }); } }); } } catch (error) { this.logger.warn('Failed to fetch observables for artifact extraction, continuing without artifacts', { error }); } return artifacts; } generateInvestigationSummary(caseData, findings) { const threatType = this.identifyThreatType(caseData.case); const severity = this.calculateOverallSeverity(findings); const confidence = this.calculateOverallConfidence(findings); const riskScore = this.calculateRiskScore(caseData, findings); return { threatType, severity, confidence, status: findings.length > 0 ? 'ACTIVE' : 'RESOLVED', riskScore }; } identifyThreatType(caseDetails) { const name = caseDetails.name?.toLowerCase() || ''; const description = caseDetails.description?.toLowerCase() || ''; if (name.includes('malware') || description.includes('malware')) { return 'Malware'; } if (name.includes('phishing') || description.includes('phishing')) { return 'Phishing'; } if (name.includes('network') || description.includes('network')) { return 'Network Intrusion'; } if (name.includes('credential') || description.includes('credential')) { return 'Credential Compromise'; } return 'Unknown'; } calculateOverallSeverity(findings) { if (findings.some(f => f.severity === 'CRITICAL')) return 'CRITICAL'; if (findings.some(f => f.severity === 'HIGH')) return 'HIGH'; if (findings.some(f => f.severity === 'MEDIUM')) return 'MEDIUM'; return 'LOW'; } calculateOverallConfidence(findings) { if (findings.length === 0) return 0; const totalConfidence = findings.reduce((sum, f) => sum + f.confidence, 0); return totalConfidence / findings.length; } calculateRiskScore(caseData, findings) { let score = 0; // Base score from case score score += caseData.case?.score || 0; // Add points for critical findings score += findings.filter(f => f.severity === 'CRITICAL').length * 30; score += findings.filter(f => f.severity === 'HIGH').length * 20; score += findings.filter(f => f.severity === 'MEDIUM').length * 10; // Add points for malicious observables if (caseData.observables && Array.isArray(caseData.observables)) { const maliciousObservables = caseData.observables.filter((o) => o.tags && o.tags.includes('malicious')); score += maliciousObservables.length * 25; } return Math.min(score, 100); } groupObservablesByType(observables) { const groups = {}; observables.forEach(observable => { groups[observable.type] = (groups[observable.type] || 0) + 1; }); return groups; } generateObservableRecommendations(observables) { const recommendations = []; const maliciousObservables = observables.filter(o => o.tags && o.tags.includes('malicious')); if (maliciousObservables.length > 0) { recommendations.push('Block malicious indicators at network perimeter'); recommendations.push('Hunt for additional instances of malicious indicators'); } const suspiciousObservables = observables.filter(o => o.tags && o.tags.includes('suspicious')); if (suspiciousObservables.length > 0) { recommendations.push('Monitor suspicious indicators for additional activity'); recommendations.push('Enrich suspicious indicators with threat intelligence'); } return recommendations; } getWorkflowDefinitions() { return [ { type: 'malware', name: 'Malware Investigation Workflow', description: 'Comprehensive malware analysis and containment', steps: [ 'Isolate affected systems', 'Collect malware samples', 'Perform static analysis', 'Perform dynamic analysis', 'Identify indicators of compromise', 'Hunt for additional infections', 'Implement containment measures', 'Document findings and lessons learned' ], estimatedTime: '4-8 hours', requiredSkills: ['Malware Analysis', 'Incident Response', 'Forensics'], tools: ['Sandbox', 'Disassembler', 'Network Monitoring', 'EDR'] }, { type: 'network', name: 'Network Intrusion Investigation', description: 'Network-based threat investigation and response', steps: [ 'Analyze network traffic patterns', 'Identify lateral movement', 'Check for data exfiltration', 'Identify compromised systems', 'Implement network segmentation', 'Monitor for persistence mechanisms', 'Collect forensic evidence', 'Prepare incident report' ], estimatedTime: '6-12 hours', requiredSkills: ['Network Security', 'Incident Response', 'Forensics'], tools: ['SIEM', 'Network Monitoring', 'Packet Capture', 'Forensics Tools'] } ]; } selectWorkflow(workflows, threatType, severity) { const matchingWorkflow = workflows.find(w => w.type === threatType.toLowerCase()); return matchingWorkflow || workflows[0]; } } export function createInvestigationAgentMetadata() { const capabilities = [ { name: 'investigate_case', description: 'Perform comprehensive case investigation', inputSchema: { type: 'object', properties: { caseId: { type: 'string' }, options: { type: 'object' } }, required: ['caseId'] }, outputSchema: { type: 'object', properties: { investigationId: { type: 'string' }, summary: { type: 'object' }, findings: { type: 'array' }, recommendations: { type: 'array' } } } }, { name: 'get_case_details', description: 'Retrieve detailed case information', inputSchema: { type: 'object', properties: { caseId: { type: 'string' } }, required: ['caseId'] }, outputSchema: { type: 'object' } }, { name: 'analyze_case_observables', description: 'Analyze case observables and IOCs', inputSchema: { type: 'object', properties: { caseId: { type: 'string' } }, required: ['caseId'] }, outputSchema: { type: 'object', properties: { analysis: { type: 'object' }, recommendations: { type: 'array' } } } }, { name: 'search_cases', description: 'Search for cases based on criteria', inputSchema: { type: 'object', properties: { query: { type: 'string' }, filters: { type: 'object' } }, required: ['query'] }, outputSchema: { type: 'object', properties: { cases: { type: 'array' }, total: { type: 'number' } } } } ]; return { id: { type: 'investigation', instance: 'primary', uuid: crypto.randomUUID() }, name: 'Investigation Agent', description: 'Comprehensive case investigation and analysis agent', version: '1.0.0', capabilities, dependencies: [], resources: { memory: 256, cpu: 1 } }; } //# sourceMappingURL=investigation-agent.js.map