vulnerability matching improvements, including confidence levels and data freshness indicators. These changes enhance the accuracy and transparency of vulnerability reporting in the Autopilot Monitor application.

This commit is contained in:
Oliver Kieselbach
2026-04-02 22:55:28 +02:00
parent b2871b9a9d
commit cc42a22fcb
5 changed files with 164 additions and 7 deletions
@@ -1 +1 @@
801
802
@@ -56,6 +56,45 @@ public class VulnerabilityCorrelationServiceTests
"cpe:2.3:a:cisco:anyconnect:*:*:*:*:*:*:android:*"));
}
// -----------------------------------------------------------------------
// IsDescriptionNonWindowsOnly
// -----------------------------------------------------------------------
[Theory]
[InlineData("A buffer overflow vulnerability in TA for Linux and TA for MacOS prior to 5.8.1", true)]
[InlineData("Privilege escalation in Android agent version 3.0", true)]
[InlineData("Remote code execution in Linux kernel module", true)]
[InlineData("Vulnerability in agent for iOS devices", true)]
public void IsDescriptionNonWindowsOnly_OnlyNonWindowsPlatforms_ReturnsTrue(string description, bool expected)
{
Assert.Equal(expected, VulnerabilityCorrelationService.IsDescriptionNonWindowsOnly(description));
}
[Theory]
[InlineData("Vulnerability in agent for Linux, macOS, and Windows")]
[InlineData("Buffer overflow in Windows service prior to 5.8.1")]
[InlineData("Cross-platform issue affecting Windows and Linux")]
public void IsDescriptionNonWindowsOnly_MentionsWindows_ReturnsFalse(string description)
{
Assert.False(VulnerabilityCorrelationService.IsDescriptionNonWindowsOnly(description));
}
[Theory]
[InlineData("Buffer overflow in processing module")]
[InlineData("Remote code execution via crafted input")]
public void IsDescriptionNonWindowsOnly_NoPlatformMentioned_ReturnsFalse(string description)
{
Assert.False(VulnerabilityCorrelationService.IsDescriptionNonWindowsOnly(description));
}
[Fact]
public void IsDescriptionNonWindowsOnly_NullOrEmpty_ReturnsFalse()
{
Assert.False(VulnerabilityCorrelationService.IsDescriptionNonWindowsOnly(null));
Assert.False(VulnerabilityCorrelationService.IsDescriptionNonWindowsOnly(""));
Assert.False(VulnerabilityCorrelationService.IsDescriptionNonWindowsOnly(" "));
}
// -----------------------------------------------------------------------
// FilterByVersion
// -----------------------------------------------------------------------
@@ -305,6 +344,65 @@ public class VulnerabilityCorrelationServiceTests
Assert.Equal("CVE-2021-1258", results[0]["cveId"]);
}
[Fact]
public void FilterByVersion_DescriptionNonWindowsOnly_SkipsCve()
{
var svc = CreateServiceForFilterTests();
var cves = new List<CachedCve>
{
new CachedCve
{
CveId = "CVE-2024-0213",
CvssScore = 8.2,
CvssSeverity = "HIGH",
Description = "A buffer overflow vulnerability in TA for Linux and TA for MacOS prior to 5.8.1 allows a local user to gain elevated permissions.",
PublishedDate = "2024-01-09",
AffectedVersions = new List<CachedVersionRange>
{
new CachedVersionRange
{
// Wildcard platform — CPE filter won't catch it
Criteria = "cpe:2.3:a:trellix:agent:*:*:*:*:*:*:*:*",
VersionStartIncluding = "5.0",
VersionEndExcluding = "5.8.1"
}
}
}
};
var results = svc.FilterByVersion(cves, "cpe:2.3:a:trellix:agent", "5.8.0.161");
Assert.Empty(results); // Description says Linux/macOS only → filtered out
}
[Fact]
public void FilterByVersion_DescriptionMentionsWindows_KeepsCve()
{
var svc = CreateServiceForFilterTests();
var cves = new List<CachedCve>
{
new CachedCve
{
CveId = "CVE-2024-9999",
CvssScore = 7.0,
CvssSeverity = "HIGH",
Description = "Vulnerability in agent for Linux, macOS, and Windows prior to 5.8.1.",
PublishedDate = "2024-01-09",
AffectedVersions = new List<CachedVersionRange>
{
new CachedVersionRange
{
Criteria = "cpe:2.3:a:trellix:agent:*:*:*:*:*:*:*:*",
VersionStartIncluding = "5.0",
VersionEndExcluding = "5.8.1"
}
}
}
};
var results = svc.FilterByVersion(cves, "cpe:2.3:a:trellix:agent", "5.8.0.161");
Assert.Single(results); // Mentions Windows → keep it
}
[Fact]
public void FilterByVersion_EmptyAffectedVersions_ReturnsEmpty()
{
@@ -563,6 +563,51 @@ namespace AutopilotMonitor.Functions.Services.Vulnerability
return false;
}
// Keywords that indicate a non-Windows platform in CVE descriptions.
private static readonly string[] DescriptionNonWindowsKeywords = new[]
{
"linux", "macos", "mac os", "mac_os", "mac_os_x", "macintosh",
"android", "ios", "iphone", "ipad",
"freebsd", "openbsd", "netbsd", "solaris", "aix", "hp-ux", "hpux",
"chrome os", "chromeos", "tvos", "watchos", "ipados", "unix"
};
private static readonly string[] DescriptionWindowsKeywords = new[]
{
"windows", "win32", "win64", "microsoft windows"
};
/// <summary>
/// Secondary defense: check if the CVE description explicitly mentions ONLY
/// non-Windows platforms. Returns true when the description names at least one
/// non-Windows OS and does NOT mention Windows at all.
/// Conservative: if no platform is mentioned, returns false (pass through).
/// </summary>
internal static bool IsDescriptionNonWindowsOnly(string? description)
{
if (string.IsNullOrWhiteSpace(description)) return false;
bool hasNonWindows = false;
foreach (var kw in DescriptionNonWindowsKeywords)
{
if (description.Contains(kw, StringComparison.OrdinalIgnoreCase))
{
hasNonWindows = true;
break;
}
}
if (!hasNonWindows) return false;
foreach (var kw in DescriptionWindowsKeywords)
{
if (description.Contains(kw, StringComparison.OrdinalIgnoreCase))
return false; // Mentions Windows too — keep it
}
return true; // Only non-Windows platforms mentioned
}
/// <summary>
/// Filter cached CVEs by installed version. Applies version-range logic,
/// platform filtering (skip non-Windows CVEs), and flags wildcard matches
@@ -640,6 +685,11 @@ namespace AutopilotMonitor.Functions.Services.Vulnerability
if (!isAffected)
continue;
// Secondary defense: if the CVE description explicitly mentions only
// non-Windows platforms (e.g. "TA for Linux and MacOS"), filter it out.
if (IsDescriptionNonWindowsOnly(cve.Description))
continue;
var entry = new Dictionary<string, object>
{
{ "cveId", cve.CveId },
@@ -21,6 +21,7 @@ interface CveDetail {
kev?: boolean;
publishedDate?: string;
references?: string[];
versionMatchConfidence?: string;
}
interface Finding {
@@ -141,6 +142,7 @@ export default function VulnerabilityReportSection({
kev: (c.kev ?? c.isKev ?? c.is_kev ?? false) as boolean,
publishedDate: (c.publishedDate || c.published_date || "") as string,
references: (c.references || []) as string[],
versionMatchConfidence: (c.versionMatchConfidence || c.version_match_confidence) as string | undefined,
}));
const maxCvss = cves.reduce((max: number, c: CveDetail) => Math.max(max, c.cvss ?? 0), 0);
@@ -620,11 +622,10 @@ export default function VulnerabilityReportSection({
)}
{/* Data Freshness */}
{dataFreshness && (
<div className="mt-3 text-xs text-gray-400">
Data Freshness: {dataFreshness}
</div>
)}
<div className="mt-3 text-xs text-gray-400">
{dataFreshness && <>Data Freshness: {dataFreshness} &middot; </>}
<span className="italic">Experimental &mdash; best-effort CVE matching</span>
</div>
</>
)}
</div>
@@ -863,6 +864,14 @@ function FindingRow({ finding, isExpanded, onToggle }: {
<span>KEV</span>
</span>
)}
{cve.versionMatchConfidence === "low" && (
<span
className="px-2 py-0.5 rounded text-xs font-medium bg-amber-50 text-amber-700 cursor-help"
title="Version match is based on a wildcard CPE entry — the installed version may not actually be affected"
>
Uncertain match
</span>
)}
{cve.publishedDate && (
<span className="text-xs text-gray-400">{cve.publishedDate}</span>
)}
File diff suppressed because one or more lines are too long