@nahisaho/musubix-security 1.8.0 → 1.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/analyzers/ai/index.d.ts +6 -0
- package/dist/analyzers/ai/index.d.ts.map +1 -0
- package/dist/analyzers/ai/index.js +6 -0
- package/dist/analyzers/ai/index.js.map +1 -0
- package/dist/analyzers/ai/prompt-injection-detector.d.ts +152 -0
- package/dist/analyzers/ai/prompt-injection-detector.d.ts.map +1 -0
- package/dist/analyzers/ai/prompt-injection-detector.js +468 -0
- package/dist/analyzers/ai/prompt-injection-detector.js.map +1 -0
- package/dist/analyzers/api/api-security-analyzer.d.ts +263 -0
- package/dist/analyzers/api/api-security-analyzer.d.ts.map +1 -0
- package/dist/analyzers/api/api-security-analyzer.js +581 -0
- package/dist/analyzers/api/api-security-analyzer.js.map +1 -0
- package/dist/analyzers/compliance/compliance-checker.d.ts +201 -0
- package/dist/analyzers/compliance/compliance-checker.d.ts.map +1 -0
- package/dist/analyzers/compliance/compliance-checker.js +772 -0
- package/dist/analyzers/compliance/compliance-checker.js.map +1 -0
- package/dist/analyzers/container/image-scanner.d.ts +163 -0
- package/dist/analyzers/container/image-scanner.d.ts.map +1 -0
- package/dist/analyzers/container/image-scanner.js +459 -0
- package/dist/analyzers/container/image-scanner.js.map +1 -0
- package/dist/analyzers/container/index.d.ts +6 -0
- package/dist/analyzers/container/index.d.ts.map +1 -0
- package/dist/analyzers/container/index.js +6 -0
- package/dist/analyzers/container/index.js.map +1 -0
- package/dist/analyzers/dashboard/security-dashboard.d.ts +286 -0
- package/dist/analyzers/dashboard/security-dashboard.d.ts.map +1 -0
- package/dist/analyzers/dashboard/security-dashboard.js +796 -0
- package/dist/analyzers/dashboard/security-dashboard.js.map +1 -0
- package/dist/analyzers/iac/iac-checker.d.ts +124 -0
- package/dist/analyzers/iac/iac-checker.d.ts.map +1 -0
- package/dist/analyzers/iac/iac-checker.js +755 -0
- package/dist/analyzers/iac/iac-checker.js.map +1 -0
- package/dist/analyzers/iac/index.d.ts +6 -0
- package/dist/analyzers/iac/index.d.ts.map +1 -0
- package/dist/analyzers/iac/index.js +6 -0
- package/dist/analyzers/iac/index.js.map +1 -0
- package/dist/analyzers/index.d.ts +9 -0
- package/dist/analyzers/index.d.ts.map +1 -0
- package/dist/analyzers/index.js +13 -0
- package/dist/analyzers/index.js.map +1 -0
- package/dist/analyzers/monitor/realtime-monitor.d.ts +216 -0
- package/dist/analyzers/monitor/realtime-monitor.d.ts.map +1 -0
- package/dist/analyzers/monitor/realtime-monitor.js +601 -0
- package/dist/analyzers/monitor/realtime-monitor.js.map +1 -0
- package/dist/analyzers/sast/index.d.ts +7 -0
- package/dist/analyzers/sast/index.d.ts.map +1 -0
- package/dist/analyzers/sast/index.js +7 -0
- package/dist/analyzers/sast/index.js.map +1 -0
- package/dist/analyzers/sast/interprocedural-analyzer.d.ts +276 -0
- package/dist/analyzers/sast/interprocedural-analyzer.d.ts.map +1 -0
- package/dist/analyzers/sast/interprocedural-analyzer.js +635 -0
- package/dist/analyzers/sast/interprocedural-analyzer.js.map +1 -0
- package/dist/analyzers/sast/zero-day-detector.d.ts +183 -0
- package/dist/analyzers/sast/zero-day-detector.d.ts.map +1 -0
- package/dist/analyzers/sast/zero-day-detector.js +593 -0
- package/dist/analyzers/sast/zero-day-detector.js.map +1 -0
- package/dist/analyzers/sca/dependency-scanner.d.ts +275 -0
- package/dist/analyzers/sca/dependency-scanner.d.ts.map +1 -0
- package/dist/analyzers/sca/dependency-scanner.js +642 -0
- package/dist/analyzers/sca/dependency-scanner.js.map +1 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +10 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/pipeline-manager.d.ts +105 -0
- package/dist/core/pipeline-manager.d.ts.map +1 -0
- package/dist/core/pipeline-manager.js +449 -0
- package/dist/core/pipeline-manager.js.map +1 -0
- package/dist/core/result-aggregator.d.ts +96 -0
- package/dist/core/result-aggregator.d.ts.map +1 -0
- package/dist/core/result-aggregator.js +462 -0
- package/dist/core/result-aggregator.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -1
- package/dist/integrations/ci-integration.d.ts +227 -0
- package/dist/integrations/ci-integration.d.ts.map +1 -0
- package/dist/integrations/ci-integration.js +472 -0
- package/dist/integrations/ci-integration.js.map +1 -0
- package/dist/integrations/git-hooks.d.ts +155 -0
- package/dist/integrations/git-hooks.d.ts.map +1 -0
- package/dist/integrations/git-hooks.js +425 -0
- package/dist/integrations/git-hooks.js.map +1 -0
- package/dist/integrations/index.d.ts +9 -0
- package/dist/integrations/index.d.ts.map +1 -0
- package/dist/integrations/index.js +9 -0
- package/dist/integrations/index.js.map +1 -0
- package/dist/integrations/report-aggregator.d.ts +250 -0
- package/dist/integrations/report-aggregator.d.ts.map +1 -0
- package/dist/integrations/report-aggregator.js +488 -0
- package/dist/integrations/report-aggregator.js.map +1 -0
- package/dist/integrations/vscode-integration.d.ts +245 -0
- package/dist/integrations/vscode-integration.d.ts.map +1 -0
- package/dist/integrations/vscode-integration.js +449 -0
- package/dist/integrations/vscode-integration.js.map +1 -0
- package/dist/intelligence/attack-pattern-matcher.d.ts +217 -0
- package/dist/intelligence/attack-pattern-matcher.d.ts.map +1 -0
- package/dist/intelligence/attack-pattern-matcher.js +887 -0
- package/dist/intelligence/attack-pattern-matcher.js.map +1 -0
- package/dist/intelligence/index.d.ts +12 -0
- package/dist/intelligence/index.d.ts.map +1 -0
- package/dist/intelligence/index.js +18 -0
- package/dist/intelligence/index.js.map +1 -0
- package/dist/intelligence/neuro-symbolic-core.d.ts +88 -0
- package/dist/intelligence/neuro-symbolic-core.d.ts.map +1 -0
- package/dist/intelligence/neuro-symbolic-core.js +403 -0
- package/dist/intelligence/neuro-symbolic-core.js.map +1 -0
- package/dist/intelligence/predictive-analyzer.d.ts +317 -0
- package/dist/intelligence/predictive-analyzer.d.ts.map +1 -0
- package/dist/intelligence/predictive-analyzer.js +714 -0
- package/dist/intelligence/predictive-analyzer.js.map +1 -0
- package/dist/intelligence/risk-scorer.d.ts +333 -0
- package/dist/intelligence/risk-scorer.d.ts.map +1 -0
- package/dist/intelligence/risk-scorer.js +824 -0
- package/dist/intelligence/risk-scorer.js.map +1 -0
- package/dist/intelligence/security-analytics.d.ts +349 -0
- package/dist/intelligence/security-analytics.d.ts.map +1 -0
- package/dist/intelligence/security-analytics.js +813 -0
- package/dist/intelligence/security-analytics.js.map +1 -0
- package/dist/intelligence/threat-intelligence.d.ts +288 -0
- package/dist/intelligence/threat-intelligence.d.ts.map +1 -0
- package/dist/intelligence/threat-intelligence.js +639 -0
- package/dist/intelligence/threat-intelligence.js.map +1 -0
- package/dist/policy/index.d.ts +6 -0
- package/dist/policy/index.d.ts.map +1 -0
- package/dist/policy/index.js +6 -0
- package/dist/policy/index.js.map +1 -0
- package/dist/policy/policy-engine.d.ts +254 -0
- package/dist/policy/policy-engine.d.ts.map +1 -0
- package/dist/policy/policy-engine.js +651 -0
- package/dist/policy/policy-engine.js.map +1 -0
- package/dist/remediation/auto-fixer.d.ts +179 -0
- package/dist/remediation/auto-fixer.d.ts.map +1 -0
- package/dist/remediation/auto-fixer.js +540 -0
- package/dist/remediation/auto-fixer.js.map +1 -0
- package/dist/remediation/fix-validator.d.ts +195 -0
- package/dist/remediation/fix-validator.d.ts.map +1 -0
- package/dist/remediation/fix-validator.js +462 -0
- package/dist/remediation/fix-validator.js.map +1 -0
- package/dist/remediation/index.d.ts +10 -0
- package/dist/remediation/index.d.ts.map +1 -0
- package/dist/remediation/index.js +15 -0
- package/dist/remediation/index.js.map +1 -0
- package/dist/remediation/patch-generator.d.ts +203 -0
- package/dist/remediation/patch-generator.d.ts.map +1 -0
- package/dist/remediation/patch-generator.js +533 -0
- package/dist/remediation/patch-generator.js.map +1 -0
- package/dist/remediation/remediation-planner.d.ts +262 -0
- package/dist/remediation/remediation-planner.d.ts.map +1 -0
- package/dist/remediation/remediation-planner.js +531 -0
- package/dist/remediation/remediation-planner.js.map +1 -0
- package/dist/remediation/secure-code-transformer.d.ts +222 -0
- package/dist/remediation/secure-code-transformer.d.ts.map +1 -0
- package/dist/remediation/secure-code-transformer.js +625 -0
- package/dist/remediation/secure-code-transformer.js.map +1 -0
- package/dist/types/fix.d.ts +3 -1
- package/dist/types/fix.d.ts.map +1 -1
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/interprocedural.d.ts +203 -0
- package/dist/types/interprocedural.d.ts.map +1 -0
- package/dist/types/interprocedural.js +7 -0
- package/dist/types/interprocedural.js.map +1 -0
- package/dist/types/neuro-symbolic.d.ts +179 -0
- package/dist/types/neuro-symbolic.d.ts.map +1 -0
- package/dist/types/neuro-symbolic.js +7 -0
- package/dist/types/neuro-symbolic.js.map +1 -0
- package/dist/types/pipeline.d.ts +173 -0
- package/dist/types/pipeline.d.ts.map +1 -0
- package/dist/types/pipeline.js +7 -0
- package/dist/types/pipeline.js.map +1 -0
- package/dist/types/result.d.ts +134 -0
- package/dist/types/result.d.ts.map +1 -0
- package/dist/types/result.js +25 -0
- package/dist/types/result.js.map +1 -0
- package/dist/types/vulnerability.d.ts +2 -2
- package/dist/types/vulnerability.d.ts.map +1 -1
- package/dist/types/zero-day.d.ts +146 -0
- package/dist/types/zero-day.d.ts.map +1 -0
- package/dist/types/zero-day.js +7 -0
- package/dist/types/zero-day.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Security Analytics Engine
|
|
3
|
+
* @module @nahisaho/musubix-security/intelligence/security-analytics
|
|
4
|
+
*
|
|
5
|
+
* Provides trend analysis, metrics collection, statistical reporting,
|
|
6
|
+
* and security insights for comprehensive security management.
|
|
7
|
+
*/
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// SecurityAnalytics Class
|
|
10
|
+
// ============================================================================
|
|
11
|
+
/**
|
|
12
|
+
* Security Analytics Engine
|
|
13
|
+
*/
|
|
14
|
+
export class SecurityAnalytics {
|
|
15
|
+
options;
|
|
16
|
+
events = [];
|
|
17
|
+
metricsHistory = new Map();
|
|
18
|
+
vulnerabilityHistory = new Map();
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.options = {
|
|
21
|
+
retentionDays: options.retentionDays ?? 90,
|
|
22
|
+
enableRealtime: options.enableRealtime ?? true,
|
|
23
|
+
industryBenchmark: options.industryBenchmark ?? {
|
|
24
|
+
'vulnerability-count': 50,
|
|
25
|
+
'average-risk-score': 45,
|
|
26
|
+
'mean-time-to-remediate': 30,
|
|
27
|
+
'security-debt': 100,
|
|
28
|
+
'fix-rate': 70,
|
|
29
|
+
'detection-rate': 85,
|
|
30
|
+
'false-positive-rate': 15,
|
|
31
|
+
'coverage': 80,
|
|
32
|
+
},
|
|
33
|
+
customMetrics: options.customMetrics ?? new Map(),
|
|
34
|
+
};
|
|
35
|
+
// Initialize metrics history
|
|
36
|
+
const metricTypes = [
|
|
37
|
+
'vulnerability-count',
|
|
38
|
+
'average-risk-score',
|
|
39
|
+
'mean-time-to-remediate',
|
|
40
|
+
'security-debt',
|
|
41
|
+
'fix-rate',
|
|
42
|
+
'detection-rate',
|
|
43
|
+
'false-positive-rate',
|
|
44
|
+
'coverage',
|
|
45
|
+
];
|
|
46
|
+
for (const type of metricTypes) {
|
|
47
|
+
this.metricsHistory.set(type, []);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Record an analytics event
|
|
52
|
+
*/
|
|
53
|
+
recordEvent(event) {
|
|
54
|
+
const fullEvent = {
|
|
55
|
+
id: `EVT-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
56
|
+
...event,
|
|
57
|
+
};
|
|
58
|
+
this.events.push(fullEvent);
|
|
59
|
+
// Prune old events
|
|
60
|
+
this.pruneOldEvents();
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Record vulnerability discovery
|
|
64
|
+
*/
|
|
65
|
+
recordVulnerability(vulnerability) {
|
|
66
|
+
this.vulnerabilityHistory.set(vulnerability.id, {
|
|
67
|
+
found: new Date(),
|
|
68
|
+
});
|
|
69
|
+
this.recordEvent({
|
|
70
|
+
type: 'vulnerability',
|
|
71
|
+
timestamp: new Date(),
|
|
72
|
+
data: {
|
|
73
|
+
vulnerabilityId: vulnerability.id,
|
|
74
|
+
type: vulnerability.type,
|
|
75
|
+
severity: vulnerability.severity,
|
|
76
|
+
file: vulnerability.location.file,
|
|
77
|
+
},
|
|
78
|
+
tags: ['discovery', vulnerability.severity, vulnerability.type],
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Record vulnerability fix
|
|
83
|
+
*/
|
|
84
|
+
recordFix(vulnerabilityId) {
|
|
85
|
+
const record = this.vulnerabilityHistory.get(vulnerabilityId);
|
|
86
|
+
if (record) {
|
|
87
|
+
record.fixed = new Date();
|
|
88
|
+
}
|
|
89
|
+
this.recordEvent({
|
|
90
|
+
type: 'fix',
|
|
91
|
+
timestamp: new Date(),
|
|
92
|
+
data: { vulnerabilityId },
|
|
93
|
+
tags: ['remediation'],
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Record scan completion
|
|
98
|
+
*/
|
|
99
|
+
recordScan(scanResults) {
|
|
100
|
+
this.recordEvent({
|
|
101
|
+
type: 'scan',
|
|
102
|
+
timestamp: new Date(),
|
|
103
|
+
data: scanResults,
|
|
104
|
+
tags: ['scan'],
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Prune old events
|
|
109
|
+
*/
|
|
110
|
+
pruneOldEvents() {
|
|
111
|
+
const cutoff = new Date();
|
|
112
|
+
cutoff.setDate(cutoff.getDate() - this.options.retentionDays);
|
|
113
|
+
this.events = this.events.filter(e => e.timestamp >= cutoff);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get events by type
|
|
117
|
+
*/
|
|
118
|
+
getEventsByType(type, since) {
|
|
119
|
+
return this.events.filter(e => e.type === type && (!since || e.timestamp >= since));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Calculate metric for a period
|
|
123
|
+
*/
|
|
124
|
+
calculateMetric(type, period = 'day') {
|
|
125
|
+
const now = new Date();
|
|
126
|
+
const periodStart = this.getPeriodStart(now, period);
|
|
127
|
+
const prevPeriodStart = this.getPreviousPeriodStart(periodStart, period);
|
|
128
|
+
let value;
|
|
129
|
+
let unit = '';
|
|
130
|
+
const breakdown = {};
|
|
131
|
+
const periodEvents = this.events.filter(e => e.timestamp >= periodStart);
|
|
132
|
+
const prevPeriodEvents = this.events.filter(e => e.timestamp >= prevPeriodStart && e.timestamp < periodStart);
|
|
133
|
+
switch (type) {
|
|
134
|
+
case 'vulnerability-count': {
|
|
135
|
+
value = periodEvents.filter(e => e.type === 'vulnerability').length;
|
|
136
|
+
unit = 'count';
|
|
137
|
+
// Breakdown by severity
|
|
138
|
+
for (const event of periodEvents.filter(e => e.type === 'vulnerability')) {
|
|
139
|
+
const severity = event.data.severity;
|
|
140
|
+
breakdown[severity] = (breakdown[severity] || 0) + 1;
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case 'average-risk-score': {
|
|
145
|
+
const vulnEvents = periodEvents.filter(e => e.type === 'vulnerability');
|
|
146
|
+
value = vulnEvents.length > 0
|
|
147
|
+
? this.calculateAverageRisk(vulnEvents)
|
|
148
|
+
: 0;
|
|
149
|
+
unit = 'score';
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case 'mean-time-to-remediate': {
|
|
153
|
+
value = this.calculateMTTR();
|
|
154
|
+
unit = 'days';
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case 'security-debt': {
|
|
158
|
+
value = this.calculateSecurityDebt();
|
|
159
|
+
unit = 'hours';
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case 'fix-rate': {
|
|
163
|
+
const fixed = periodEvents.filter(e => e.type === 'fix').length;
|
|
164
|
+
const found = periodEvents.filter(e => e.type === 'vulnerability').length;
|
|
165
|
+
value = found > 0 ? Math.round((fixed / found) * 100) : 100;
|
|
166
|
+
unit = '%';
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
case 'detection-rate': {
|
|
170
|
+
// Simulated - would need baseline for real implementation
|
|
171
|
+
value = 85;
|
|
172
|
+
unit = '%';
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
case 'false-positive-rate': {
|
|
176
|
+
// Simulated - would need feedback data
|
|
177
|
+
value = 10;
|
|
178
|
+
unit = '%';
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
case 'coverage': {
|
|
182
|
+
const scans = periodEvents.filter(e => e.type === 'scan');
|
|
183
|
+
value = scans.length > 0
|
|
184
|
+
? Math.round(scans.reduce((sum, e) => sum + (e.data.filesScanned || 0), 0) / scans.length)
|
|
185
|
+
: 0;
|
|
186
|
+
unit = 'files';
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
default:
|
|
190
|
+
value = 0;
|
|
191
|
+
unit = '';
|
|
192
|
+
}
|
|
193
|
+
// Calculate change
|
|
194
|
+
const prevValue = this.calculatePreviousPeriodValue(type, prevPeriodEvents);
|
|
195
|
+
const changeValue = value - prevValue;
|
|
196
|
+
const changePercentage = prevValue !== 0
|
|
197
|
+
? Math.round((changeValue / prevValue) * 100)
|
|
198
|
+
: 0;
|
|
199
|
+
const metric = {
|
|
200
|
+
type,
|
|
201
|
+
value,
|
|
202
|
+
unit,
|
|
203
|
+
timestamp: now,
|
|
204
|
+
period,
|
|
205
|
+
change: {
|
|
206
|
+
value: changeValue,
|
|
207
|
+
percentage: changePercentage,
|
|
208
|
+
direction: changeValue > 0 ? 'up' : changeValue < 0 ? 'down' : 'stable',
|
|
209
|
+
},
|
|
210
|
+
breakdown: Object.keys(breakdown).length > 0 ? breakdown : undefined,
|
|
211
|
+
};
|
|
212
|
+
// Store in history
|
|
213
|
+
this.metricsHistory.get(type)?.push(metric);
|
|
214
|
+
return metric;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get period start date
|
|
218
|
+
*/
|
|
219
|
+
getPeriodStart(date, period) {
|
|
220
|
+
const start = new Date(date);
|
|
221
|
+
switch (period) {
|
|
222
|
+
case 'hour':
|
|
223
|
+
start.setMinutes(0, 0, 0);
|
|
224
|
+
break;
|
|
225
|
+
case 'day':
|
|
226
|
+
start.setHours(0, 0, 0, 0);
|
|
227
|
+
break;
|
|
228
|
+
case 'week':
|
|
229
|
+
start.setDate(start.getDate() - start.getDay());
|
|
230
|
+
start.setHours(0, 0, 0, 0);
|
|
231
|
+
break;
|
|
232
|
+
case 'month':
|
|
233
|
+
start.setDate(1);
|
|
234
|
+
start.setHours(0, 0, 0, 0);
|
|
235
|
+
break;
|
|
236
|
+
case 'quarter':
|
|
237
|
+
start.setMonth(Math.floor(start.getMonth() / 3) * 3, 1);
|
|
238
|
+
start.setHours(0, 0, 0, 0);
|
|
239
|
+
break;
|
|
240
|
+
case 'year':
|
|
241
|
+
start.setMonth(0, 1);
|
|
242
|
+
start.setHours(0, 0, 0, 0);
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
return start;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Get previous period start date
|
|
249
|
+
*/
|
|
250
|
+
getPreviousPeriodStart(currentStart, period) {
|
|
251
|
+
const prev = new Date(currentStart);
|
|
252
|
+
switch (period) {
|
|
253
|
+
case 'hour':
|
|
254
|
+
prev.setHours(prev.getHours() - 1);
|
|
255
|
+
break;
|
|
256
|
+
case 'day':
|
|
257
|
+
prev.setDate(prev.getDate() - 1);
|
|
258
|
+
break;
|
|
259
|
+
case 'week':
|
|
260
|
+
prev.setDate(prev.getDate() - 7);
|
|
261
|
+
break;
|
|
262
|
+
case 'month':
|
|
263
|
+
prev.setMonth(prev.getMonth() - 1);
|
|
264
|
+
break;
|
|
265
|
+
case 'quarter':
|
|
266
|
+
prev.setMonth(prev.getMonth() - 3);
|
|
267
|
+
break;
|
|
268
|
+
case 'year':
|
|
269
|
+
prev.setFullYear(prev.getFullYear() - 1);
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
return prev;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Calculate average risk from events
|
|
276
|
+
*/
|
|
277
|
+
calculateAverageRisk(events) {
|
|
278
|
+
const severityScores = {
|
|
279
|
+
critical: 100,
|
|
280
|
+
high: 75,
|
|
281
|
+
medium: 50,
|
|
282
|
+
low: 25,
|
|
283
|
+
info: 10,
|
|
284
|
+
};
|
|
285
|
+
const total = events.reduce((sum, e) => {
|
|
286
|
+
const severity = e.data.severity;
|
|
287
|
+
return sum + (severityScores[severity] || 0);
|
|
288
|
+
}, 0);
|
|
289
|
+
return Math.round(total / events.length);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Calculate previous period value
|
|
293
|
+
*/
|
|
294
|
+
calculatePreviousPeriodValue(type, events) {
|
|
295
|
+
switch (type) {
|
|
296
|
+
case 'vulnerability-count':
|
|
297
|
+
return events.filter(e => e.type === 'vulnerability').length;
|
|
298
|
+
case 'average-risk-score':
|
|
299
|
+
return this.calculateAverageRisk(events.filter(e => e.type === 'vulnerability'));
|
|
300
|
+
case 'fix-rate': {
|
|
301
|
+
const fixed = events.filter(e => e.type === 'fix').length;
|
|
302
|
+
const found = events.filter(e => e.type === 'vulnerability').length;
|
|
303
|
+
return found > 0 ? Math.round((fixed / found) * 100) : 100;
|
|
304
|
+
}
|
|
305
|
+
default:
|
|
306
|
+
return 0;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Calculate mean time to remediate
|
|
311
|
+
*/
|
|
312
|
+
calculateMTTR() {
|
|
313
|
+
const fixedVulns = Array.from(this.vulnerabilityHistory.entries())
|
|
314
|
+
.filter(([, record]) => record.fixed);
|
|
315
|
+
if (fixedVulns.length === 0)
|
|
316
|
+
return 0;
|
|
317
|
+
const totalDays = fixedVulns.reduce((sum, [, record]) => {
|
|
318
|
+
const diffTime = (record.fixed.getTime() - record.found.getTime());
|
|
319
|
+
return sum + (diffTime / (1000 * 60 * 60 * 24));
|
|
320
|
+
}, 0);
|
|
321
|
+
return Math.round(totalDays / fixedVulns.length);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Calculate security debt
|
|
325
|
+
*/
|
|
326
|
+
calculateSecurityDebt() {
|
|
327
|
+
const openVulns = Array.from(this.vulnerabilityHistory.entries())
|
|
328
|
+
.filter(([, record]) => !record.fixed);
|
|
329
|
+
// Estimate hours to fix based on count (simplified)
|
|
330
|
+
return openVulns.length * 4; // 4 hours per vulnerability average
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Calculate trend
|
|
334
|
+
*/
|
|
335
|
+
calculateTrend(metricType, dataPoints = 10) {
|
|
336
|
+
const history = this.metricsHistory.get(metricType) || [];
|
|
337
|
+
const recentHistory = history.slice(-dataPoints);
|
|
338
|
+
const points = recentHistory.map(m => ({
|
|
339
|
+
timestamp: m.timestamp,
|
|
340
|
+
value: m.value,
|
|
341
|
+
}));
|
|
342
|
+
// Calculate trend direction using linear regression
|
|
343
|
+
let direction = 'stable';
|
|
344
|
+
let strength = 0;
|
|
345
|
+
let forecast;
|
|
346
|
+
if (points.length >= 2) {
|
|
347
|
+
const regression = this.linearRegression(points.map((p, i) => [i, p.value]));
|
|
348
|
+
const slope = regression.slope;
|
|
349
|
+
if (Math.abs(slope) < 0.5) {
|
|
350
|
+
direction = 'stable';
|
|
351
|
+
}
|
|
352
|
+
else if (metricType === 'vulnerability-count' || metricType === 'average-risk-score') {
|
|
353
|
+
// Lower is better for these metrics
|
|
354
|
+
direction = slope < 0 ? 'improving' : 'declining';
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
// Higher is better for fix-rate, coverage, etc.
|
|
358
|
+
direction = slope > 0 ? 'improving' : 'declining';
|
|
359
|
+
}
|
|
360
|
+
strength = Math.min(1, Math.abs(slope) / 10);
|
|
361
|
+
forecast = Math.round(regression.intercept + regression.slope * points.length);
|
|
362
|
+
}
|
|
363
|
+
// Generate insights
|
|
364
|
+
const insights = this.generateTrendInsights(metricType, direction, points);
|
|
365
|
+
return {
|
|
366
|
+
name: `${metricType} trend`,
|
|
367
|
+
metricType,
|
|
368
|
+
dataPoints: points,
|
|
369
|
+
direction,
|
|
370
|
+
strength,
|
|
371
|
+
forecast,
|
|
372
|
+
insights,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Simple linear regression
|
|
377
|
+
*/
|
|
378
|
+
linearRegression(points) {
|
|
379
|
+
const n = points.length;
|
|
380
|
+
if (n === 0)
|
|
381
|
+
return { slope: 0, intercept: 0 };
|
|
382
|
+
let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
|
|
383
|
+
for (const [x, y] of points) {
|
|
384
|
+
sumX += x;
|
|
385
|
+
sumY += y;
|
|
386
|
+
sumXY += x * y;
|
|
387
|
+
sumX2 += x * x;
|
|
388
|
+
}
|
|
389
|
+
const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX) || 0;
|
|
390
|
+
const intercept = (sumY - slope * sumX) / n;
|
|
391
|
+
return { slope, intercept };
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Generate trend insights
|
|
395
|
+
*/
|
|
396
|
+
generateTrendInsights(metricType, direction, points) {
|
|
397
|
+
const insights = [];
|
|
398
|
+
const latest = points[points.length - 1]?.value ?? 0;
|
|
399
|
+
const benchmark = this.options.industryBenchmark[metricType];
|
|
400
|
+
// Direction insight
|
|
401
|
+
if (direction === 'improving') {
|
|
402
|
+
insights.push(`${metricType} is showing positive improvement`);
|
|
403
|
+
}
|
|
404
|
+
else if (direction === 'declining') {
|
|
405
|
+
insights.push(`${metricType} trend is concerning and needs attention`);
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
insights.push(`${metricType} has remained stable`);
|
|
409
|
+
}
|
|
410
|
+
// Benchmark comparison
|
|
411
|
+
if (benchmark) {
|
|
412
|
+
if (metricType === 'vulnerability-count' || metricType === 'average-risk-score') {
|
|
413
|
+
if (latest < benchmark) {
|
|
414
|
+
insights.push(`Performance is better than industry average (${benchmark})`);
|
|
415
|
+
}
|
|
416
|
+
else if (latest > benchmark * 1.5) {
|
|
417
|
+
insights.push(`Significantly above industry average of ${benchmark}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
if (latest > benchmark) {
|
|
422
|
+
insights.push(`Exceeding industry benchmark of ${benchmark}`);
|
|
423
|
+
}
|
|
424
|
+
else if (latest < benchmark * 0.7) {
|
|
425
|
+
insights.push(`Below industry benchmark of ${benchmark}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return insights;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Get vulnerability statistics
|
|
433
|
+
*/
|
|
434
|
+
getVulnerabilityStats(since) {
|
|
435
|
+
const vulnEvents = this.getEventsByType('vulnerability', since);
|
|
436
|
+
const fixEvents = this.getEventsByType('fix', since);
|
|
437
|
+
const bySeverity = {
|
|
438
|
+
critical: 0,
|
|
439
|
+
high: 0,
|
|
440
|
+
medium: 0,
|
|
441
|
+
low: 0,
|
|
442
|
+
info: 0,
|
|
443
|
+
};
|
|
444
|
+
const byType = {};
|
|
445
|
+
const byFile = {};
|
|
446
|
+
for (const event of vulnEvents) {
|
|
447
|
+
const severity = event.data.severity;
|
|
448
|
+
const type = event.data.type;
|
|
449
|
+
const file = event.data.file;
|
|
450
|
+
if (severity in bySeverity) {
|
|
451
|
+
bySeverity[severity]++;
|
|
452
|
+
}
|
|
453
|
+
byType[type] = (byType[type] || 0) + 1;
|
|
454
|
+
byFile[file] = (byFile[file] || 0) + 1;
|
|
455
|
+
}
|
|
456
|
+
// Calculate ages
|
|
457
|
+
const now = new Date();
|
|
458
|
+
const openVulns = Array.from(this.vulnerabilityHistory.entries())
|
|
459
|
+
.filter(([, record]) => !record.fixed);
|
|
460
|
+
let totalAge = 0;
|
|
461
|
+
let oldestAge = 0;
|
|
462
|
+
for (const [, record] of openVulns) {
|
|
463
|
+
const age = (now.getTime() - record.found.getTime()) / (1000 * 60 * 60 * 24);
|
|
464
|
+
totalAge += age;
|
|
465
|
+
oldestAge = Math.max(oldestAge, age);
|
|
466
|
+
}
|
|
467
|
+
return {
|
|
468
|
+
total: vulnEvents.length,
|
|
469
|
+
bySeverity,
|
|
470
|
+
byType,
|
|
471
|
+
byFile,
|
|
472
|
+
newInPeriod: vulnEvents.length,
|
|
473
|
+
fixedInPeriod: fixEvents.length,
|
|
474
|
+
averageAge: openVulns.length > 0 ? Math.round(totalAge / openVulns.length) : 0,
|
|
475
|
+
oldestAge: Math.round(oldestAge),
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Generate security scorecard
|
|
480
|
+
*/
|
|
481
|
+
generateScorecard() {
|
|
482
|
+
const categoryScores = [];
|
|
483
|
+
// Vulnerability Management Score
|
|
484
|
+
const vulnCount = this.calculateMetric('vulnerability-count', 'month');
|
|
485
|
+
const vulnScore = Math.max(0, 100 - vulnCount.value * 2);
|
|
486
|
+
categoryScores.push({
|
|
487
|
+
category: 'Vulnerability Management',
|
|
488
|
+
score: vulnScore,
|
|
489
|
+
weight: 0.3,
|
|
490
|
+
});
|
|
491
|
+
// Remediation Velocity Score
|
|
492
|
+
const mttr = this.calculateMetric('mean-time-to-remediate', 'month');
|
|
493
|
+
const remediationScore = Math.max(0, 100 - mttr.value * 3);
|
|
494
|
+
categoryScores.push({
|
|
495
|
+
category: 'Remediation Velocity',
|
|
496
|
+
score: remediationScore,
|
|
497
|
+
weight: 0.25,
|
|
498
|
+
});
|
|
499
|
+
// Fix Rate Score
|
|
500
|
+
const fixRate = this.calculateMetric('fix-rate', 'month');
|
|
501
|
+
categoryScores.push({
|
|
502
|
+
category: 'Fix Rate',
|
|
503
|
+
score: fixRate.value,
|
|
504
|
+
weight: 0.25,
|
|
505
|
+
});
|
|
506
|
+
// Detection Coverage Score
|
|
507
|
+
const coverage = this.calculateMetric('coverage', 'month');
|
|
508
|
+
const coverageScore = Math.min(100, coverage.value);
|
|
509
|
+
categoryScores.push({
|
|
510
|
+
category: 'Detection Coverage',
|
|
511
|
+
score: coverageScore,
|
|
512
|
+
weight: 0.2,
|
|
513
|
+
});
|
|
514
|
+
// Calculate overall score
|
|
515
|
+
const overallScore = Math.round(categoryScores.reduce((sum, cat) => sum + cat.score * cat.weight, 0));
|
|
516
|
+
// Determine grade
|
|
517
|
+
const grade = this.scoreToGrade(overallScore);
|
|
518
|
+
// Key findings
|
|
519
|
+
const keyFindings = [];
|
|
520
|
+
if (vulnCount.value > 10) {
|
|
521
|
+
keyFindings.push(`${vulnCount.value} vulnerabilities found this month`);
|
|
522
|
+
}
|
|
523
|
+
if (mttr.value > 7) {
|
|
524
|
+
keyFindings.push(`Average remediation time is ${mttr.value} days`);
|
|
525
|
+
}
|
|
526
|
+
// Improvements
|
|
527
|
+
const improvements = [];
|
|
528
|
+
for (const cat of categoryScores) {
|
|
529
|
+
if (cat.score < 50) {
|
|
530
|
+
improvements.push(`Improve ${cat.category} (current score: ${cat.score})`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
// Industry comparison
|
|
534
|
+
const industryComparison = {
|
|
535
|
+
percentile: Math.round(overallScore * 0.9), // Simplified
|
|
536
|
+
average: 55,
|
|
537
|
+
};
|
|
538
|
+
// Get historical scores
|
|
539
|
+
const history = this.getHistoricalScores();
|
|
540
|
+
return {
|
|
541
|
+
overallScore,
|
|
542
|
+
grade,
|
|
543
|
+
categoryScores,
|
|
544
|
+
keyFindings,
|
|
545
|
+
improvements,
|
|
546
|
+
industryComparison,
|
|
547
|
+
history,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Convert score to grade
|
|
552
|
+
*/
|
|
553
|
+
scoreToGrade(score) {
|
|
554
|
+
if (score >= 90)
|
|
555
|
+
return 'A';
|
|
556
|
+
if (score >= 80)
|
|
557
|
+
return 'B';
|
|
558
|
+
if (score >= 70)
|
|
559
|
+
return 'C';
|
|
560
|
+
if (score >= 60)
|
|
561
|
+
return 'D';
|
|
562
|
+
return 'F';
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Get historical scores
|
|
566
|
+
*/
|
|
567
|
+
getHistoricalScores() {
|
|
568
|
+
// Simplified - would aggregate from metricsHistory
|
|
569
|
+
const history = [];
|
|
570
|
+
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'];
|
|
571
|
+
for (let i = 0; i < months.length; i++) {
|
|
572
|
+
history.push({
|
|
573
|
+
period: months[i],
|
|
574
|
+
score: Math.round(60 + Math.random() * 30),
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
return history;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Generate dashboard data
|
|
581
|
+
*/
|
|
582
|
+
generateDashboard() {
|
|
583
|
+
const vulnStats = this.getVulnerabilityStats();
|
|
584
|
+
const scorecard = this.generateScorecard();
|
|
585
|
+
// Calculate metrics
|
|
586
|
+
const metrics = [
|
|
587
|
+
this.calculateMetric('vulnerability-count', 'day'),
|
|
588
|
+
this.calculateMetric('average-risk-score', 'day'),
|
|
589
|
+
this.calculateMetric('fix-rate', 'week'),
|
|
590
|
+
this.calculateMetric('mean-time-to-remediate', 'month'),
|
|
591
|
+
];
|
|
592
|
+
// Calculate trends
|
|
593
|
+
const trends = [
|
|
594
|
+
this.calculateTrend('vulnerability-count'),
|
|
595
|
+
this.calculateTrend('average-risk-score'),
|
|
596
|
+
];
|
|
597
|
+
// Get recent activity
|
|
598
|
+
const recentActivity = this.events
|
|
599
|
+
.slice(-10)
|
|
600
|
+
.map(e => ({
|
|
601
|
+
timestamp: e.timestamp,
|
|
602
|
+
type: e.type,
|
|
603
|
+
description: this.formatEventDescription(e),
|
|
604
|
+
}));
|
|
605
|
+
// Get top risks (simulated)
|
|
606
|
+
const topRisks = this.getEventsByType('vulnerability')
|
|
607
|
+
.filter(e => e.data.severity === 'critical' || e.data.severity === 'high')
|
|
608
|
+
.slice(-5)
|
|
609
|
+
.map(e => ({
|
|
610
|
+
vulnerability: {
|
|
611
|
+
id: e.data.vulnerabilityId,
|
|
612
|
+
type: e.data.type,
|
|
613
|
+
severity: e.data.severity,
|
|
614
|
+
description: `${e.data.type} vulnerability`,
|
|
615
|
+
recommendation: 'Review and fix the vulnerability',
|
|
616
|
+
confidence: 0.9,
|
|
617
|
+
ruleId: 'analytics',
|
|
618
|
+
detectedAt: e.timestamp,
|
|
619
|
+
message: `${e.data.type} vulnerability`,
|
|
620
|
+
location: {
|
|
621
|
+
file: e.data.file,
|
|
622
|
+
startLine: 1,
|
|
623
|
+
endLine: 1,
|
|
624
|
+
startColumn: 0,
|
|
625
|
+
endColumn: 0,
|
|
626
|
+
},
|
|
627
|
+
cwes: [],
|
|
628
|
+
fix: { description: '', code: '' },
|
|
629
|
+
},
|
|
630
|
+
riskScore: e.data.severity === 'critical' ? 95 : 75,
|
|
631
|
+
daysOpen: Math.round((new Date().getTime() - e.timestamp.getTime()) / (1000 * 60 * 60 * 24)),
|
|
632
|
+
}));
|
|
633
|
+
// Determine trend
|
|
634
|
+
const vulnTrend = trends.find(t => t.metricType === 'vulnerability-count');
|
|
635
|
+
const trend = vulnTrend?.direction || 'stable';
|
|
636
|
+
return {
|
|
637
|
+
summary: {
|
|
638
|
+
totalVulnerabilities: vulnStats.total,
|
|
639
|
+
criticalCount: vulnStats.bySeverity.critical,
|
|
640
|
+
overallRisk: metrics.find(m => m.type === 'average-risk-score')?.value || 0,
|
|
641
|
+
securityScore: scorecard.overallScore,
|
|
642
|
+
trend,
|
|
643
|
+
},
|
|
644
|
+
recentActivity,
|
|
645
|
+
topRisks,
|
|
646
|
+
metrics,
|
|
647
|
+
trends,
|
|
648
|
+
scorecard,
|
|
649
|
+
generatedAt: new Date(),
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Format event description
|
|
654
|
+
*/
|
|
655
|
+
formatEventDescription(event) {
|
|
656
|
+
switch (event.type) {
|
|
657
|
+
case 'vulnerability':
|
|
658
|
+
return `Found ${event.data.severity} severity ${event.data.type} vulnerability`;
|
|
659
|
+
case 'fix':
|
|
660
|
+
return `Fixed vulnerability ${event.data.vulnerabilityId}`;
|
|
661
|
+
case 'scan':
|
|
662
|
+
return `Scan completed: ${event.data.filesScanned} files, ${event.data.vulnerabilitiesFound} issues`;
|
|
663
|
+
default:
|
|
664
|
+
return `Event: ${event.type}`;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Generate analytics report
|
|
669
|
+
*/
|
|
670
|
+
generateReport(startDate, endDate) {
|
|
671
|
+
const vulnStats = this.getVulnerabilityStats(startDate);
|
|
672
|
+
const scorecard = this.generateScorecard();
|
|
673
|
+
// Calculate key metrics for period
|
|
674
|
+
const keyMetrics = [
|
|
675
|
+
this.calculateMetric('vulnerability-count', 'month'),
|
|
676
|
+
this.calculateMetric('average-risk-score', 'month'),
|
|
677
|
+
this.calculateMetric('fix-rate', 'month'),
|
|
678
|
+
this.calculateMetric('mean-time-to-remediate', 'month'),
|
|
679
|
+
this.calculateMetric('security-debt', 'month'),
|
|
680
|
+
];
|
|
681
|
+
// Generate executive summary
|
|
682
|
+
const executiveSummary = this.generateExecutiveSummary(vulnStats, scorecard);
|
|
683
|
+
// Generate recommendations
|
|
684
|
+
const recommendations = this.generateReportRecommendations(vulnStats, scorecard);
|
|
685
|
+
return {
|
|
686
|
+
id: `RPT-${Date.now()}`,
|
|
687
|
+
title: 'Security Analytics Report',
|
|
688
|
+
period: { start: startDate, end: endDate },
|
|
689
|
+
executiveSummary,
|
|
690
|
+
keyMetrics,
|
|
691
|
+
trends: [
|
|
692
|
+
this.calculateTrend('vulnerability-count'),
|
|
693
|
+
this.calculateTrend('average-risk-score'),
|
|
694
|
+
this.calculateTrend('fix-rate'),
|
|
695
|
+
],
|
|
696
|
+
vulnerabilityStats: vulnStats,
|
|
697
|
+
riskSummary: {
|
|
698
|
+
totalVulnerabilities: vulnStats.total,
|
|
699
|
+
averageRiskScore: keyMetrics.find(m => m.type === 'average-risk-score')?.value || 0,
|
|
700
|
+
distribution: {
|
|
701
|
+
critical: vulnStats.bySeverity.critical,
|
|
702
|
+
high: vulnStats.bySeverity.high,
|
|
703
|
+
medium: vulnStats.bySeverity.medium,
|
|
704
|
+
low: vulnStats.bySeverity.low,
|
|
705
|
+
informational: vulnStats.bySeverity.info,
|
|
706
|
+
},
|
|
707
|
+
topRisks: [],
|
|
708
|
+
totalBusinessImpact: [],
|
|
709
|
+
securityPosture: scorecard.overallScore,
|
|
710
|
+
trend: 'stable',
|
|
711
|
+
},
|
|
712
|
+
recommendations,
|
|
713
|
+
generatedAt: new Date(),
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Generate executive summary
|
|
718
|
+
*/
|
|
719
|
+
generateExecutiveSummary(stats, scorecard) {
|
|
720
|
+
const parts = [];
|
|
721
|
+
parts.push(`Security Score: ${scorecard.overallScore}/100 (Grade: ${scorecard.grade})`);
|
|
722
|
+
parts.push(`Total vulnerabilities identified: ${stats.total}`);
|
|
723
|
+
parts.push(`Critical/High severity: ${stats.bySeverity.critical + stats.bySeverity.high}`);
|
|
724
|
+
if (stats.fixedInPeriod > 0) {
|
|
725
|
+
parts.push(`Vulnerabilities fixed this period: ${stats.fixedInPeriod}`);
|
|
726
|
+
}
|
|
727
|
+
if (stats.averageAge > 7) {
|
|
728
|
+
parts.push(`Average vulnerability age: ${stats.averageAge} days (attention needed)`);
|
|
729
|
+
}
|
|
730
|
+
return parts.join('. ') + '.';
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Generate report recommendations
|
|
734
|
+
*/
|
|
735
|
+
generateReportRecommendations(stats, scorecard) {
|
|
736
|
+
const recommendations = [];
|
|
737
|
+
if (stats.bySeverity.critical > 0) {
|
|
738
|
+
recommendations.push({
|
|
739
|
+
priority: 'high',
|
|
740
|
+
description: `Address ${stats.bySeverity.critical} critical vulnerabilities immediately`,
|
|
741
|
+
impact: 'Reduces critical risk exposure',
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
if (stats.averageAge > 14) {
|
|
745
|
+
recommendations.push({
|
|
746
|
+
priority: 'high',
|
|
747
|
+
description: 'Improve vulnerability remediation velocity',
|
|
748
|
+
impact: 'Reduces exposure window',
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
for (const cat of scorecard.categoryScores.filter(c => c.score < 60)) {
|
|
752
|
+
recommendations.push({
|
|
753
|
+
priority: cat.score < 40 ? 'high' : 'medium',
|
|
754
|
+
description: `Improve ${cat.category}`,
|
|
755
|
+
impact: `Current score: ${cat.score}/100`,
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
if (recommendations.length === 0) {
|
|
759
|
+
recommendations.push({
|
|
760
|
+
priority: 'low',
|
|
761
|
+
description: 'Continue current security practices',
|
|
762
|
+
impact: 'Maintain security posture',
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
return recommendations;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Get all metrics
|
|
769
|
+
*/
|
|
770
|
+
getAllMetrics(period = 'day') {
|
|
771
|
+
const metricTypes = [
|
|
772
|
+
'vulnerability-count',
|
|
773
|
+
'average-risk-score',
|
|
774
|
+
'mean-time-to-remediate',
|
|
775
|
+
'security-debt',
|
|
776
|
+
'fix-rate',
|
|
777
|
+
'detection-rate',
|
|
778
|
+
'false-positive-rate',
|
|
779
|
+
'coverage',
|
|
780
|
+
];
|
|
781
|
+
return metricTypes.map(type => this.calculateMetric(type, period));
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Export analytics data
|
|
785
|
+
*/
|
|
786
|
+
exportData() {
|
|
787
|
+
return {
|
|
788
|
+
events: this.events,
|
|
789
|
+
metricsHistory: Object.fromEntries(this.metricsHistory),
|
|
790
|
+
vulnerabilityHistory: Object.fromEntries(this.vulnerabilityHistory),
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
// ============================================================================
|
|
795
|
+
// Factory Functions
|
|
796
|
+
// ============================================================================
|
|
797
|
+
/**
|
|
798
|
+
* Create a SecurityAnalytics instance
|
|
799
|
+
*/
|
|
800
|
+
export function createSecurityAnalytics(options) {
|
|
801
|
+
return new SecurityAnalytics(options);
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Quick dashboard generation
|
|
805
|
+
*/
|
|
806
|
+
export function generateDashboard(events) {
|
|
807
|
+
const analytics = createSecurityAnalytics();
|
|
808
|
+
for (const event of events) {
|
|
809
|
+
analytics.recordEvent(event);
|
|
810
|
+
}
|
|
811
|
+
return analytics.generateDashboard();
|
|
812
|
+
}
|
|
813
|
+
//# sourceMappingURL=security-analytics.js.map
|