Skip to content

Commit 8c6d0f0

Browse files
committed
πŸ”§ CRITICAL: Fix notification system severity filtering and content
MAJOR NOTIFICATION FIXES: - βœ… Fix severity filtering - now properly filters vulnerabilities by min_severity - βœ… Enhanced CVSS parsing with proper base score extraction (9.0+ = Critical, 7.0+ = High, etc.) - βœ… Rich notification content with vulnerability details and severity breakdown - βœ… Show top 5 most severe vulnerabilities with titles in notifications - βœ… Improved Discord/Slack message formatting with better colors and emojis - βœ… Fixed severity determination logic with proper CVSS impact analysis BEFORE: 🟒 LOW severity with 31 vulnerabilities (all shown regardless of filter) AFTER: πŸ”₯ HIGH severity with only high/critical vulnerabilities (properly filtered) NOTIFICATION IMPROVEMENTS: - Detailed vulnerability titles and summaries - Severity breakdown (Critical: X, High: Y, Medium: Z) - Better emoji usage (πŸ”₯ Critical, 🟠 High, 🟑 Medium, 🟒 Low) - Truncation handling for long messages - Rich formatting for Discord and Slack The min_severity filter now actually works and notifications contain useful details!
1 parent 004abf1 commit 8c6d0f0

File tree

2 files changed

+274
-107
lines changed

2 files changed

+274
-107
lines changed

β€Žsrc/automation/scheduler.rsβ€Ž

Lines changed: 69 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,23 @@ async fn run_scheduled_scan(
180180
Ok(results) => {
181181
for (result, packages) in results {
182182
// Apply policies to filter results
183-
let filtered_result = policy_engine.apply_policies(&result, &packages);
183+
let mut filtered_result = policy_engine.apply_policies(&result, &packages);
184+
185+
// CRITICAL FIX: Filter vulnerabilities by minimum severity BEFORE creating notification
186+
if let Some(min_severity) = &config.notifications.filters.min_severity {
187+
let min_level = severity_level(min_severity);
188+
filtered_result.scan_result.vulnerabilities.retain(|v| {
189+
if let Some(severity) = &v.severity {
190+
let level = severity_level(severity);
191+
level >= min_level
192+
} else {
193+
false // Exclude vulnerabilities with no severity info
194+
}
195+
});
196+
197+
info!("Filtered vulnerabilities: {} remaining after applying minimum severity '{}'",
198+
filtered_result.scan_result.vulnerabilities.len(), min_severity);
199+
}
184200

185201
// Send notifications if enabled and conditions are met
186202
if config.notifications.enabled && should_notify(&filtered_result, &config.notifications.filters) {
@@ -227,23 +243,27 @@ fn should_notify(
227243
return false;
228244
}
229245

230-
// Check minimum severity
246+
// Check minimum severity - FIXED: Better CVSS and severity parsing
231247
if let Some(min_severity) = &filters.min_severity {
232-
let has_qualifying_severity = result.vulnerabilities.iter().any(|v| {
233-
if let Some(severity) = &v.severity {
234-
let level = severity_level(severity);
235-
let min_level = severity_level(min_severity);
236-
info!("Checking severity: '{}' (level {}) >= '{}' (level {})",
237-
severity, level, min_severity, min_level);
238-
level >= min_level
239-
} else {
240-
false
241-
}
242-
});
248+
let qualifying_vulnerabilities: Vec<_> = result.vulnerabilities.iter()
249+
.filter(|v| {
250+
if let Some(severity) = &v.severity {
251+
let level = severity_level(severity);
252+
let min_level = severity_level(min_severity);
253+
level >= min_level
254+
} else {
255+
false
256+
}
257+
})
258+
.collect();
243259

244-
if !has_qualifying_severity {
245-
info!("No vulnerabilities meet minimum severity requirement of '{}'", min_severity);
260+
if qualifying_vulnerabilities.is_empty() {
261+
info!("No vulnerabilities meet minimum severity requirement of '{}'. Found {} total vulnerabilities but none qualify.",
262+
min_severity, result.vulnerabilities.len());
246263
return false;
264+
} else {
265+
info!("Found {} vulnerabilities meeting minimum severity '{}' out of {} total",
266+
qualifying_vulnerabilities.len(), min_severity, result.vulnerabilities.len());
247267
}
248268
}
249269

@@ -269,30 +289,52 @@ fn should_notify(
269289
true
270290
}
271291

272-
/// Convert severity string to numeric level for comparison
292+
/// Convert severity string to numeric level for comparison - IMPROVED CVSS parsing
273293
fn severity_level(severity: &str) -> u8 {
274294
let severity_lower = severity.to_lowercase();
275295

276296
// Handle CVSS format (e.g., "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H")
277297
if severity_lower.starts_with("cvss:") {
278-
// For CVSS format, determine severity based on the score components
279-
// C:H/I:H/A:H indicates High impact across all three categories
280-
if severity.contains("C:H") && severity.contains("I:H") && severity.contains("A:H") {
281-
return 4; // Critical - High impact on all three (Confidentiality, Integrity, Availability)
282-
} else if severity.contains("C:H") || severity.contains("I:H") || severity.contains("A:H") {
283-
return 3; // High - High impact on at least one category
284-
} else if severity.contains("C:M") || severity.contains("I:M") || severity.contains("A:M") {
285-
return 2; // Medium - Medium impact
286-
} else {
287-
return 1; // Low - Low or no significant impact
298+
// Extract base score if present (e.g., "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/E:P/RL:O/RC:C/CR:X/IR:X/AR:X/MAV:X/MAC:X/MPR:X/MUI:X/MS:X/MC:L/MI:L/MA:L")
299+
if let Some(score_start) = severity.find("/AV:") {
300+
let score_part = &severity[..score_start];
301+
if let Some(version_end) = score_part.rfind('/') {
302+
if let Ok(base_score) = score_part[version_end + 1..].parse::<f32>() {
303+
return match base_score {
304+
s if s >= 9.0 => 4, // Critical (9.0-10.0)
305+
s if s >= 7.0 => 3, // High (7.0-8.9)
306+
s if s >= 4.0 => 2, // Medium (4.0-6.9)
307+
s if s >= 0.1 => 1, // Low (0.1-3.9)
308+
_ => 0,
309+
};
310+
}
311+
}
288312
}
313+
314+
// Fallback: analyze impact scores for CVSS without base score
315+
let high_impact_count = ["C:H", "I:H", "A:H"].iter()
316+
.filter(|&impact| severity.contains(impact))
317+
.count();
318+
319+
let medium_impact_count = ["C:M", "I:M", "A:M"].iter()
320+
.filter(|&impact| severity.contains(impact))
321+
.count();
322+
323+
return match high_impact_count {
324+
3 => 4, // Critical - High impact on all three (Confidentiality, Integrity, Availability)
325+
2 => 3, // High - High impact on two categories
326+
1 => 3, // High - High impact on at least one category
327+
0 if medium_impact_count >= 2 => 2, // Medium - Medium impact on multiple categories
328+
0 if medium_impact_count >= 1 => 2, // Medium - Medium impact on at least one category
329+
_ => 1, // Low - Low or no significant impact
330+
};
289331
}
290332

291333
// Handle simple severity strings
292334
match severity_lower.as_str() {
293335
s if s.contains("critical") => 4,
294336
s if s.contains("high") => 3,
295-
s if s.contains("medium") => 2,
337+
s if s.contains("medium") || s.contains("moderate") => 2,
296338
s if s.contains("low") => 1,
297339
_ => 0,
298340
}

0 commit comments

Comments
Β (0)