better namings for vulnerability report

This commit is contained in:
Oliver Kieselbach
2026-04-07 00:35:50 +02:00
parent 257a8f7b52
commit 95b6ed0734
4 changed files with 44 additions and 12 deletions
@@ -1 +1 @@
880
881
@@ -69,6 +69,7 @@ namespace AutopilotMonitor.Functions.Services.Vulnerability
var findings = new List<Dictionary<string, object>>();
var unmatchedSoftware = new List<Dictionary<string, object>>();
var matchedCleanSoftware = new List<Dictionary<string, object>>();
int matchedCount = 0;
int totalCves = 0;
int criticalCves = 0;
@@ -128,7 +129,15 @@ namespace AutopilotMonitor.Functions.Services.Vulnerability
}
if (vulns.Count == 0)
{
matchedCleanSoftware.Add(new Dictionary<string, object>
{
{ "softwareName", displayName },
{ "installedVersion", installedVersion },
{ "cpeUri", cpeUri }
});
continue;
}
// Check KEV for each CVE
foreach (var vuln in vulns)
@@ -253,6 +262,7 @@ namespace AutopilotMonitor.Functions.Services.Vulnerability
},
{ "findings", findings },
{ "unmatched_software", unmatchedSoftware.Take(100).ToList() },
{ "matched_clean_software", matchedCleanSoftware.Take(200).ToList() },
{ "data_freshness", new Dictionary<string, object>
{
{ "cpe_cache_entries", _cpeMappings?.Count ?? 0 },
@@ -122,11 +122,12 @@ export default function VulnerabilityReportSection({
}, [events]);
// --- Findings data (wrapped in try/catch to prevent page crash on malformed report data) ---
const { findings, unmatchedSoftware, mappedNames, unmappedNames, totalSoftware, kevTotal, dataFreshness } = useMemo(() => { try {
const { findings, unmatchedSoftware, mappedNames, unmappedNames, cleanNames, totalSoftware, kevTotal, dataFreshness } = useMemo(() => { try {
const findingsMap = new Map<string, Finding>();
let unmatched: string[] = [];
const mapped = new Set<string>();
const unmapped = new Set<string>();
const clean = new Set<string>();
let total = 0;
let freshness = "";
let kevCount = 0;
@@ -195,6 +196,18 @@ export default function VulnerabilityReportSection({
}
}
}
// Parse matched_clean_software (CPE mapped, no CVEs found)
const rawClean = d.matched_clean_software;
if (rawClean && Array.isArray(rawClean)) {
for (const entry of rawClean) {
if (entry && typeof entry === "object") {
const e = entry as Record<string, unknown>;
const label = (e.softwareName || e.software || e.name || "") as string;
if (label) clean.add(label.toLowerCase().trim());
}
}
}
total = (d.totalSoftware ?? d.total_software ?? d.totalCount ?? total) as number;
const rawFreshness = d.dataFreshness || d.data_freshness || d.analyzedAt || d.analyzed_at || freshness;
if (rawFreshness && typeof rawFreshness === "object") {
@@ -214,11 +227,12 @@ export default function VulnerabilityReportSection({
unmatchedSoftware: uniqueUnmatched,
mappedNames: mapped,
unmappedNames: unmapped,
cleanNames: clean,
totalSoftware: total,
kevTotal: kevCount,
dataFreshness: freshness,
};
} catch (e) { console.error("[VulnerabilityReportSection] Failed to parse findings", e); return { findings: [], unmatchedSoftware: [], mappedNames: new Set<string>(), unmappedNames: new Set<string>(), totalSoftware: 0, kevTotal: 0, dataFreshness: "" }; }
} catch (e) { console.error("[VulnerabilityReportSection] Failed to parse findings", e); return { findings: [], unmatchedSoftware: [], mappedNames: new Set<string>(), unmappedNames: new Set<string>(), cleanNames: new Set<string>(), totalSoftware: 0, kevTotal: 0, dataFreshness: "" }; }
}, [vulnReportData]);
// Severity counts for header badges
@@ -672,7 +686,8 @@ export default function VulnerabilityReportSection({
<tbody className="divide-y divide-gray-100">
{filteredInventory.map((item, i) => {
const nameLower = item.name.toLowerCase().trim();
const isMapped = mappedNames.has(nameLower);
const hasFindings = mappedNames.has(nameLower);
const isClean = cleanNames.has(nameLower);
const isUnmapped = unmappedNames.has(nameLower);
return (
<tr key={`${item.name}-${item.version}-${i}`} className="hover:bg-gray-50">
@@ -691,8 +706,10 @@ export default function VulnerabilityReportSection({
)}
</td>
<td className="py-2 px-3">
{isMapped ? (
<span className="px-2 py-0.5 rounded text-xs font-medium bg-indigo-100 text-indigo-700 cursor-help" title="Matched to CPE entry — CVE lookup available">mapped</span>
{hasFindings ? (
<span className="px-2 py-0.5 rounded text-xs font-medium bg-red-100 text-red-700 cursor-help" title="CVEs found for this version — action needed">vulnerable</span>
) : isClean ? (
<span className="px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-700 cursor-help" title="Checked against NVD — no known vulnerabilities for this version">clean</span>
) : isUnmapped ? (
<span className="px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-500 cursor-help" title="No CPE mapping — not checked against CVE database">unmapped</span>
) : (
@@ -722,7 +739,8 @@ export default function VulnerabilityReportSection({
<span><span className="inline-block px-1.5 py-0.5 rounded bg-red-100 text-red-700 font-medium mr-1">low</span> Unidentified</span>
<span className="basis-full h-0" />
<span className="font-medium text-gray-500 whitespace-nowrap">CVE Mapping:</span>
<span><span className="inline-block px-1.5 py-0.5 rounded bg-indigo-100 text-indigo-700 font-medium mr-1">mapped</span> Matched to CPE — CVE lookup available</span>
<span><span className="inline-block px-1.5 py-0.5 rounded bg-red-100 text-red-700 font-medium mr-1">vulnerable</span> CVEs found — action needed</span>
<span><span className="inline-block px-1.5 py-0.5 rounded bg-green-100 text-green-700 font-medium mr-1">clean</span> Checked — no known vulnerabilities</span>
<span><span className="inline-block px-1.5 py-0.5 rounded bg-gray-100 text-gray-500 font-medium mr-1">unmapped</span> No CPE mapping configured</span>
</div>
</>
@@ -760,7 +778,8 @@ export default function VulnerabilityReportSection({
<tbody className="divide-y divide-gray-100">
{deltaItems.map((item, i) => {
const nameLower = item.name.toLowerCase().trim();
const isMapped = mappedNames.has(nameLower);
const hasFindings = mappedNames.has(nameLower);
const isClean = cleanNames.has(nameLower);
const isUnmapped = unmappedNames.has(nameLower);
return (
<tr key={`${item.name}-${item.version}-${i}`} className="hover:bg-gray-50">
@@ -779,8 +798,10 @@ export default function VulnerabilityReportSection({
)}
</td>
<td className="py-2 px-3">
{isMapped ? (
<span className="px-2 py-0.5 rounded text-xs font-medium bg-indigo-100 text-indigo-700 cursor-help" title="Matched to CPE entry — CVE lookup available">mapped</span>
{hasFindings ? (
<span className="px-2 py-0.5 rounded text-xs font-medium bg-red-100 text-red-700 cursor-help" title="CVEs found for this version — action needed">vulnerable</span>
) : isClean ? (
<span className="px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-700 cursor-help" title="Checked against NVD — no known vulnerabilities for this version">clean</span>
) : isUnmapped ? (
<span className="px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-500 cursor-help" title="No CPE mapping — not checked against CVE database">unmapped</span>
) : (
@@ -800,7 +821,8 @@ export default function VulnerabilityReportSection({
<span><span className="inline-block px-1.5 py-0.5 rounded bg-red-100 text-red-700 font-medium mr-1">low</span> Unidentified</span>
<span className="basis-full h-0" />
<span className="font-medium text-gray-500 whitespace-nowrap">CVE Mapping:</span>
<span><span className="inline-block px-1.5 py-0.5 rounded bg-indigo-100 text-indigo-700 font-medium mr-1">mapped</span> Matched to CPE — CVE lookup available</span>
<span><span className="inline-block px-1.5 py-0.5 rounded bg-red-100 text-red-700 font-medium mr-1">vulnerable</span> CVEs found — action needed</span>
<span><span className="inline-block px-1.5 py-0.5 rounded bg-green-100 text-green-700 font-medium mr-1">clean</span> Checked — no known vulnerabilities</span>
<span><span className="inline-block px-1.5 py-0.5 rounded bg-gray-100 text-gray-500 font-medium mr-1">unmapped</span> No CPE mapping configured</span>
</div>
</>
File diff suppressed because one or more lines are too long