mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
fix(backend): cap vulnerability_report severity at Warning
A completed scan is a finding about the software inventory, not an enrollment failure. Mapping the CVE risk onto Error/Critical made the report read as a failed enrollment: the diagnosis page collected it as failure evidence, MCP search lifted it on failure-intent queries, and the timeline's Error filter filled up with clean sessions. critical/high now map to Warning, everything else to Info; the CVE severity itself stays in scan_summary and on the report panel's own scale. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+30
-1
@@ -115,7 +115,7 @@ public class VulnerabilityReportEventSequenceTests
|
||||
Assert.Equal("vulnerability_report", evt.EventType);
|
||||
Assert.Equal(AutopilotMonitor.Shared.Constants.EventTypes.VulnerabilityReport, evt.EventType);
|
||||
Assert.Equal("VulnerabilityCorrelationService", evt.Source);
|
||||
Assert.Equal(EventSeverity.Error, evt.Severity); // risk=high → Error
|
||||
Assert.Equal(EventSeverity.Warning, evt.Severity); // risk=high → Warning (severity is capped)
|
||||
// Non-phase event: must stay Unknown so the UI timeline doesn't render a phantom phase boundary.
|
||||
Assert.Equal(EnrollmentPhase.Unknown, evt.Phase);
|
||||
Assert.Equal(NowUtc, evt.Timestamp);
|
||||
@@ -127,6 +127,35 @@ public class VulnerabilityReportEventSequenceTests
|
||||
Assert.IsType<Dictionary<string, object>>(evt.Data["scan_summary"]);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
// The scan reports on the software inventory, not on the enrollment: a finding is never an
|
||||
// enrollment failure, so severity is capped at Warning. Error/Critical would put the report
|
||||
// into the diagnosis page's failure evidence and into MCP's failure-intent ranking.
|
||||
[InlineData("critical", EventSeverity.Warning)]
|
||||
[InlineData("high", EventSeverity.Warning)]
|
||||
[InlineData("medium", EventSeverity.Info)]
|
||||
[InlineData("low", EventSeverity.Info)]
|
||||
[InlineData("none", EventSeverity.Info)]
|
||||
[InlineData("HIGH", EventSeverity.Warning)] // case-insensitive
|
||||
[InlineData("", EventSeverity.Info)]
|
||||
public void Severity_IsCappedAtWarning(string risk, EventSeverity expected)
|
||||
{
|
||||
var evt = Build(null, new List<EnrollmentEvent>(), Report(risk));
|
||||
|
||||
Assert.Equal(expected, evt.Severity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Severity_IsInfo_WhenRiskIsMissing()
|
||||
{
|
||||
var report = new Dictionary<string, object>
|
||||
{
|
||||
["scan_summary"] = new Dictionary<string, object>(),
|
||||
};
|
||||
|
||||
Assert.Equal(EventSeverity.Info, Build(null, new List<EnrollmentEvent>(), report).Severity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Data_Phase_CarriesTheLabel()
|
||||
{
|
||||
|
||||
+17
-3
@@ -541,12 +541,26 @@ namespace AutopilotMonitor.Functions.Services.Vulnerability
|
||||
string.Equals(phase, "user_enrollment", StringComparison.Ordinal)
|
||||
|| string.Equals(phase, "both", StringComparison.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// Maps the scan's overall CVE risk onto the event severity, capped at
|
||||
/// <see cref="EventSeverity.Warning"/>.
|
||||
/// <para>
|
||||
/// <b>Why the cap:</b> <c>EventSeverity</c> on the timeline states how badly the
|
||||
/// <i>enrollment</i> went, not how severe a CVE is. A completed scan is a finding about
|
||||
/// the software inventory — nothing failed. Emitting Error/Critical made the report read
|
||||
/// as an enrollment failure: the diagnosis page collects Error/Critical events as failure
|
||||
/// evidence, MCP search lifts high-severity events on failure-intent queries, and the
|
||||
/// timeline's Error filter — the one used to hunt for problems — filled up with clean
|
||||
/// sessions. The CVE severity itself is not lost: it rides in <c>scan_summary</c>
|
||||
/// (<c>overall_risk</c>, <c>critical_cves</c>, …) and the report panel renders it on its
|
||||
/// own colour scale. Warning keeps the report visible without claiming a failure.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
private static EventSeverity MapVulnerabilityRiskToSeverity(string? overallRisk) =>
|
||||
(overallRisk ?? string.Empty).ToLowerInvariant() switch
|
||||
{
|
||||
"critical" => EventSeverity.Critical,
|
||||
"high" => EventSeverity.Error,
|
||||
"medium" => EventSeverity.Warning,
|
||||
"critical" => EventSeverity.Warning,
|
||||
"high" => EventSeverity.Warning,
|
||||
_ => EventSeverity.Info,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user