From e7df69efe4e1d4bc9252e739483426ac93e5b215 Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Sun, 6 Sep 2026 22:30:36 +0200 Subject: [PATCH] backend+web: vulnerability summary carries a priority breakdown (act/attend/track); the exposure panel shows Act and Attend tiles and every severity or priority chip filters the top-CVE list (D-218) Act equals the KEV set by construction (CvePriority: KEV => act), so the KEV tile becomes the Act tile and Attend joins it instead of a duplicate count. The breakdown is counted in the backend over every CVE in the window, like the severity breakdown, because the list is top-N. Both panel consumers read the generated CveExposureSummary type; the MCP tool description names the new block. Wire types regenerated for web and MCP. Co-Authored-By: Claude Fable 5.1 --- internal | 2 +- .../VulnerabilitySummaryAggregatorTests.cs | 18 ++ .../VulnerabilityWireParityTests.cs | 6 + .../VulnerabilitySummaryAggregator.cs | 8 + .../src/generated/wire-types.generated.ts | 8 + .../autopilot-monitor-mcp/src/tools/admin.ts | 3 +- .../Models/VulnerabilityApiModels.cs | 15 ++ .../components/VulnerabilityDataSection.tsx | 25 +-- .../apps/components/VulnerabilitiesTab.tsx | 7 +- .../components/VulnerabilityExposurePanel.tsx | 172 +++++++++++++----- .../__tests__/vulnerabilitySeverity.test.ts | 10 +- .../lib/vulnerabilitySeverity.ts | 10 + .../utils/shared-manifests.json | 34 ++++ .../utils/wire-types.generated.ts | 8 + 14 files changed, 257 insertions(+), 69 deletions(-) diff --git a/internal b/internal index bc834c33..2b113df6 160000 --- a/internal +++ b/internal @@ -1 +1 @@ -Subproject commit bc834c33b3a7fb4b0ba1aa6ab8251b0e2beba380 +Subproject commit 2b113df6a146d119f5ad84109c526cd4d9412b37 diff --git a/src/Backend/AutopilotMonitor.Functions.Tests/VulnerabilitySummaryAggregatorTests.cs b/src/Backend/AutopilotMonitor.Functions.Tests/VulnerabilitySummaryAggregatorTests.cs index 02af53dc..30666e9e 100644 --- a/src/Backend/AutopilotMonitor.Functions.Tests/VulnerabilitySummaryAggregatorTests.cs +++ b/src/Backend/AutopilotMonitor.Functions.Tests/VulnerabilitySummaryAggregatorTests.cs @@ -88,6 +88,24 @@ public class VulnerabilitySummaryAggregatorTests Assert.Equal(4, r.DistinctCves); } + [Fact] + public void BuildsPriorityBreakdownFromTheMergedBand_legacyRowsCountInNone() + { + var act = Row("CVE-A", "s1", "Critical", 9.8, kev: true); act.Priority = "act"; + var attendHigh = Row("CVE-B", "s1", "Medium", 5.0); attendHigh.Priority = "attend"; + var attendLow = Row("CVE-B", "s2", "Medium", 5.0); attendLow.Priority = "track"; // merged band = attend + var track = Row("CVE-C", "s2", "Low", 2.0); track.Priority = "TRACK"; // case-insensitive + var legacy = Row("CVE-D", "s3", "High", 7.5); // empty priority + + var r = VulnerabilitySummaryAggregator.Aggregate(new[] { act, attendHigh, attendLow, track, legacy }, false, 20, 30, false, Now); + + Assert.Equal(1, r.PriorityBreakdown.Act); + Assert.Equal(1, r.PriorityBreakdown.Attend); + Assert.Equal(1, r.PriorityBreakdown.Track); + Assert.Equal(4, r.DistinctCves); + Assert.Equal(r.KevCves, r.PriorityBreakdown.Act); + } + [Fact] public void RanksTopCvesByAffectedSessionsThenScore_andRespectsTopN() { diff --git a/src/Backend/AutopilotMonitor.Functions.Tests/VulnerabilityWireParityTests.cs b/src/Backend/AutopilotMonitor.Functions.Tests/VulnerabilityWireParityTests.cs index 81e8a0b1..af56ad31 100644 --- a/src/Backend/AutopilotMonitor.Functions.Tests/VulnerabilityWireParityTests.cs +++ b/src/Backend/AutopilotMonitor.Functions.Tests/VulnerabilityWireParityTests.cs @@ -663,6 +663,7 @@ public class VulnerabilityWireParityTests public void CveExposureSummary_wire_shape_is_unchanged_by_the_IApiResponse_marker() { var breakdown = new SeverityBreakdown { Critical = 1, High = 2, Medium = 0, Low = 0 }; + var priorities = new PriorityBreakdown { Act = 1, Attend = 1, Track = 1 }; var topCves = new List { new TopCve @@ -687,6 +688,7 @@ public class VulnerabilityWireParityTests distinctCves = 3, kevCves = 1, severityBreakdown = breakdown, + priorityBreakdown = priorities, topCves, truncated = false, }, @@ -698,6 +700,7 @@ public class VulnerabilityWireParityTests DistinctCves = 3, KevCves = 1, SeverityBreakdown = breakdown, + PriorityBreakdown = priorities, TopCves = topCves, Truncated = false, }); @@ -707,6 +710,7 @@ public class VulnerabilityWireParityTests public void CveExposureSummary_omits_totalAffectedTenants_on_a_tenant_scoped_read() { var breakdown = new SeverityBreakdown { Critical = 0, High = 0, Medium = 0, Low = 0 }; + var priorities = new PriorityBreakdown { Act = 0, Attend = 0, Track = 0 }; var topCves = new List(); AssertParity( @@ -718,6 +722,7 @@ public class VulnerabilityWireParityTests distinctCves = 0, kevCves = 0, severityBreakdown = breakdown, + priorityBreakdown = priorities, topCves, truncated = true, }, @@ -729,6 +734,7 @@ public class VulnerabilityWireParityTests DistinctCves = 0, KevCves = 0, SeverityBreakdown = breakdown, + PriorityBreakdown = priorities, TopCves = topCves, Truncated = true, }); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Vulnerability/VulnerabilitySummaryAggregator.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Vulnerability/VulnerabilitySummaryAggregator.cs index dce927a6..2a29e100 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Vulnerability/VulnerabilitySummaryAggregator.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Vulnerability/VulnerabilitySummaryAggregator.cs @@ -100,6 +100,13 @@ namespace AutopilotMonitor.Functions.Functions.Vulnerability Low = groups.Count(c => c.SeverityRank == 1), }; + var priorities = new PriorityBreakdown + { + Act = groups.Count(c => CvePriority.Rank(c.Priority) == CvePriority.Rank(CvePriority.Act)), + Attend = groups.Count(c => CvePriority.Rank(c.Priority) == CvePriority.Rank(CvePriority.Attend)), + Track = groups.Count(c => CvePriority.Rank(c.Priority) == CvePriority.Rank(CvePriority.Track)), + }; + var totalAffectedSessions = inWindow.Select(r => r.SessionId) .Where(s => !string.IsNullOrEmpty(s)) .Distinct(StringComparer.OrdinalIgnoreCase).Count(); @@ -118,6 +125,7 @@ namespace AutopilotMonitor.Functions.Functions.Vulnerability DistinctCves = groups.Count, KevCves = groups.Count(c => c.IsKev), SeverityBreakdown = breakdown, + PriorityBreakdown = priorities, TopCves = topCves, Truncated = truncated, }; diff --git a/src/McpServer/autopilot-monitor-mcp/src/generated/wire-types.generated.ts b/src/McpServer/autopilot-monitor-mcp/src/generated/wire-types.generated.ts index 19457fe9..86a7e4d4 100644 --- a/src/McpServer/autopilot-monitor-mcp/src/generated/wire-types.generated.ts +++ b/src/McpServer/autopilot-monitor-mcp/src/generated/wire-types.generated.ts @@ -1206,6 +1206,7 @@ export interface CveExposureSummary { distinctCves: number; kevCves: number; severityBreakdown: SeverityBreakdown; + priorityBreakdown: PriorityBreakdown; topCves: TopCve[]; truncated: boolean; } @@ -3087,6 +3088,13 @@ export interface PreviewWhitelistTenantEntry { tenantId: string; } +/** Distinct-CVE counts grouped by their highest remediation priority (act/attend/track). Rows written before priorities existed carry no band and are counted in none - the three add up to at most DistinctCves. act equals the KEV set by construction (CvePriority.Compute: KEV ⇒ act). */ +export interface PriorityBreakdown { + act: number; + attend: number; + track: number; +} + /** Response of GET /api/progress/sessions/{sessionId}/events — the session's event stream after the serial knowledge proof passed. */ export interface ProgressGetSessionEventsResponse { success: boolean; diff --git a/src/McpServer/autopilot-monitor-mcp/src/tools/admin.ts b/src/McpServer/autopilot-monitor-mcp/src/tools/admin.ts index 6632b334..c0f24e20 100644 --- a/src/McpServer/autopilot-monitor-mcp/src/tools/admin.ts +++ b/src/McpServer/autopilot-monitor-mcp/src/tools/admin.ts @@ -1823,7 +1823,8 @@ export function registerAdminTools(server: McpServer, ga: boolean, strictGa: boo title: 'Vulnerability Summary', description: 'Get a fleet-wide vulnerability exposure summary aggregated from detected CVEs: total affected devices, ' + - 'distinct CVE count, KEV (CISA Known-Exploited) count, a severity breakdown, and the top CVEs ranked by how ' + + 'distinct CVE count, KEV (CISA Known-Exploited) count, a severity breakdown, a priority breakdown ' + + '(act/attend/track counts over every CVE in the window), and the top CVEs ranked by how ' + 'many devices they affect. Each top CVE carries cvssScore/cvssSeverity, isKev, epssScore (FIRST EPSS ' + 'probability of exploitation within 30 days, 0-1, null when unscored) and priority ("act" = in CISA KEV, ' + '"attend" = EPSS >= 0.1 or CVSS >= 9.0, "track" = everything else) — rank remediation by priority, then EPSS, ' + diff --git a/src/Shared/AutopilotMonitor.Shared/Models/VulnerabilityApiModels.cs b/src/Shared/AutopilotMonitor.Shared/Models/VulnerabilityApiModels.cs index 8b70e7e8..4d04b2fc 100644 --- a/src/Shared/AutopilotMonitor.Shared/Models/VulnerabilityApiModels.cs +++ b/src/Shared/AutopilotMonitor.Shared/Models/VulnerabilityApiModels.cs @@ -336,6 +336,7 @@ namespace AutopilotMonitor.Shared.Models public int DistinctCves { get; set; } public int KevCves { get; set; } public SeverityBreakdown SeverityBreakdown { get; set; } = default!; + public PriorityBreakdown PriorityBreakdown { get; set; } = default!; public IReadOnlyList TopCves { get; set; } = default!; public bool Truncated { get; set; } } @@ -350,6 +351,20 @@ namespace AutopilotMonitor.Shared.Models public int Low { get; set; } } + /// + /// Distinct-CVE counts grouped by their highest remediation priority (act/attend/track). Rows + /// written before priorities existed carry no band and are counted in none - the three add up + /// to at most . act equals the KEV set by + /// construction (CvePriority.Compute: KEV ⇒ act). + /// + // Declaration order == wire order. + public class PriorityBreakdown + { + public int Act { get; set; } + public int Attend { get; set; } + public int Track { get; set; } + } + /// /// A single CVE ranked by how many devices it affects in scope. /// is the highest EPSS probability seen across the rows (null = unscored / legacy rows); diff --git a/src/Web/autopilot-monitor-web/app/admin/components/VulnerabilityDataSection.tsx b/src/Web/autopilot-monitor-web/app/admin/components/VulnerabilityDataSection.tsx index 1918fd73..5947bf56 100644 --- a/src/Web/autopilot-monitor-web/app/admin/components/VulnerabilityDataSection.tsx +++ b/src/Web/autopilot-monitor-web/app/admin/components/VulnerabilityDataSection.tsx @@ -10,6 +10,7 @@ import { useCanMutatePlatform } from "@/hooks/useCanMutatePlatform"; import { SectionCardHeader } from "@/components/SectionCardHeader"; import type { AdminConfiguration as WireAdminConfiguration, + CveExposureSummary, GetVulnerabilitySyncStatusResponse, TriggerEpssSyncResponse, TriggerMsrcSyncResponse, @@ -18,26 +19,6 @@ import type { UpdateAdminConfigurationResponse, } from "@/utils/wire-types.generated"; -interface VulnerabilityTopCve { - cveId: string; - cvssSeverity: string; - cvssScore: number; - isKev: boolean; - affectedSessions: number; - sampleSoftware: string[]; -} - -interface VulnerabilitySummary { - windowDays: number; - totalAffectedSessions: number; - totalAffectedTenants: number | null; - distinctCves: number; - kevCves: number; - severityBreakdown: { critical: number; high: number; medium: number; low: number }; - topCves: VulnerabilityTopCve[]; - truncated: boolean; -} - interface VulnerabilityDataSectionProps { adminConfig: AdminConfiguration | null; loadingConfig: boolean; @@ -94,7 +75,7 @@ export function VulnerabilityDataSection({ } | null>(null); // Fleet exposure overview (read-only; cross-tenant via /api/global/metrics/vulnerability) - const [summary, setSummary] = useState(null); + const [summary, setSummary] = useState(null); const [summaryDays, setSummaryDays] = useState(30); const [loadingSummary, setLoadingSummary] = useState(false); @@ -144,7 +125,7 @@ export function VulnerabilityDataSection({ const refreshSummary = useCallback(async () => { setLoadingSummary(true); try { - setSummary(await fetchJson(api.metrics.globalVulnerability(summaryDays, 10), getAccessToken)); + setSummary(await fetchJson(api.metrics.globalVulnerability(summaryDays, 10), getAccessToken)); } catch { // Non-critical: the overview is auxiliary; config + sync still render. setSummary(null); diff --git a/src/Web/autopilot-monitor-web/app/apps/components/VulnerabilitiesTab.tsx b/src/Web/autopilot-monitor-web/app/apps/components/VulnerabilitiesTab.tsx index ee9a9186..2d8734d4 100644 --- a/src/Web/autopilot-monitor-web/app/apps/components/VulnerabilitiesTab.tsx +++ b/src/Web/autopilot-monitor-web/app/apps/components/VulnerabilitiesTab.tsx @@ -4,7 +4,8 @@ import { useEffect, useState } from "react"; import { useAuth } from "../../../contexts/AuthContext"; import { useNotifications } from "../../../contexts/NotificationContext"; import { scopedApi } from "@/lib/scopedApi"; -import VulnerabilityExposurePanel, { type VulnerabilitySummary } from "@/components/VulnerabilityExposurePanel"; +import VulnerabilityExposurePanel from "@/components/VulnerabilityExposurePanel"; +import type { CveExposureSummary } from "@/utils/wire-types.generated"; import type { SoftwareTabScope, TimeRange } from "./types"; import { rangeToDays } from "./types"; import { ApiError, fetchJson } from "@/lib/apiClient"; @@ -17,7 +18,7 @@ export default function VulnerabilitiesTab({ scope, timeRange }: { scope: Softwa const { addNotification } = useNotifications(); const { isGlobalAdmin, selectedTenantId, scopeInitialized, scopeKey } = scope; - const [summary, setSummary] = useState(null); + const [summary, setSummary] = useState(null); const [loading, setLoading] = useState(true); // GA without a specific tenant selected sees the cross-tenant aggregate (incl. affected tenants). @@ -32,7 +33,7 @@ export default function VulnerabilitiesTab({ scope, timeRange }: { scope: Softwa try { setLoading(true); const url = scopedApi.vulnerability(scope, days, TOP_N); - const summary = await fetchJson(url, getAccessToken); + const summary = await fetchJson(url, getAccessToken); if (cancelled) return; setSummary(summary); } catch (err) { diff --git a/src/Web/autopilot-monitor-web/components/VulnerabilityExposurePanel.tsx b/src/Web/autopilot-monitor-web/components/VulnerabilityExposurePanel.tsx index 11c35254..752a8c3e 100644 --- a/src/Web/autopilot-monitor-web/components/VulnerabilityExposurePanel.tsx +++ b/src/Web/autopilot-monitor-web/components/VulnerabilityExposurePanel.tsx @@ -1,34 +1,21 @@ "use client"; -import { severityChipClass, KEV_BADGE_CLASS, CONTEXT_PILL_CLASS, formatEpss, epssTitle, priorityLabel, PRIORITY_TITLE } from "@/lib/vulnerabilitySeverity"; +import { useState } from "react"; +import type { CveExposureSummary, TopCve } from "@/utils/wire-types.generated"; +import { + severityChipClass, + priorityChipClass, + KEV_BADGE_CLASS, + CONTEXT_PILL_CLASS, + formatEpss, + epssTitle, + priorityLabel, + PRIORITY_TITLE, +} from "@/lib/vulnerabilitySeverity"; import TruncatedLabel from "@/components/TruncatedLabel"; -export interface VulnerabilityTopCve { - cveId: string; - cvssSeverity: string; - cvssScore: number; - isKev: boolean; - /** FIRST EPSS probability (0..1); null/absent when unscored or for rows written before EPSS. */ - epssScore?: number | null; - /** act / attend / track; empty for rows written before priorities existed. */ - priority?: string; - affectedSessions: number; - sampleSoftware: string[]; -} - -export interface VulnerabilitySummary { - windowDays: number; - totalAffectedSessions: number; - totalAffectedTenants: number | null; - distinctCves: number; - kevCves: number; - severityBreakdown: { critical: number; high: number; medium: number; low: number }; - topCves: VulnerabilityTopCve[]; - truncated: boolean; -} - interface Props { - summary: VulnerabilitySummary | null; + summary: CveExposureSummary | null; loading?: boolean; /** Show the "Affected tenants" KPI (cross-tenant / Global Admin views only). */ showTenantCount?: boolean; @@ -41,9 +28,23 @@ interface Props { } /** - * Read-only fleet/tenant vulnerability exposure: affected-device + CVE/KEV KPIs, a severity - * breakdown, and the top CVEs ranked by affected devices. Shared by the GA admin section and the - * tenant-facing Software › Vulnerabilities tab so both render identical chips/CVE rows. + * One active list filter: a severity band or a priority band, both lower-case wire values. + * Chips toggle it; clicking the active chip again clears it. + */ +type ListFilter = { kind: "severity" | "priority"; value: string } | null; + +function matchesFilter(c: TopCve, filter: ListFilter): boolean { + if (!filter) return true; + const actual = filter.kind === "severity" ? c.cvssSeverity : c.priority; + return (actual || "").toLowerCase() === filter.value; +} + +/** + * Read-only fleet/tenant vulnerability exposure: affected-device, CVE and priority KPIs, a + * severity and a priority breakdown (both filter the list), and the top CVEs ranked by affected + * devices. Shared by the GA admin section and the tenant-facing Software › Vulnerabilities tab so + * both render identical chips/CVE rows. The breakdown counts cover every CVE in the window while + * the list is top-N, so a filter can legitimately leave the list empty. */ export default function VulnerabilityExposurePanel({ summary, @@ -56,6 +57,20 @@ export default function VulnerabilityExposurePanel({ title = "Vulnerability Exposure", }: Props) { const hasFindings = summary != null && summary.distinctCves > 0; + const [filter, setFilter] = useState(null); + + const toggleFilter = (kind: "severity" | "priority", value: string) => + setFilter((prev) => (prev && prev.kind === kind && prev.value === value ? null : { kind, value })); + + const isActive = (kind: "severity" | "priority", value: string) => + filter != null && filter.kind === kind && filter.value === value; + + const visibleCves = hasFindings ? summary!.topCves.filter((c) => matchesFilter(c, filter)) : []; + const filterLabel = filter + ? filter.kind === "priority" + ? priorityLabel(filter.value) ?? filter.value + : filter.value.charAt(0).toUpperCase() + filter.value.slice(1) + : null; return (
@@ -99,33 +114,76 @@ export default function VulnerabilityExposurePanel({
) : (
- {/* KPI tiles */} -
+ {/* KPI tiles — the priority ladder (Act = KEV by definition, Attend = next tier) sits next to the totals */} +
- + + {showTenantCount && }
- {/* Severity breakdown */} -
+ {/* Severity + priority breakdowns — every chip filters the list below */} +
{([ ["Critical", summary!.severityBreakdown.critical], ["High", summary!.severityBreakdown.high], ["Medium", summary!.severityBreakdown.medium], ["Low", summary!.severityBreakdown.low], ] as Array<[string, number]>).map(([label, count]) => ( - - {label}: {count.toLocaleString()} - + toggleFilter("severity", label.toLowerCase())} + title={`Show only ${label} CVEs`} + /> + ))} +
{/* Top CVEs */}
-
Top CVEs by affected devices
+
+ Top CVEs by affected devices + {filterLabel && ( + <> + · {filterLabel} only + + + )} +
+ {visibleCves.length === 0 ? ( +
+ None of the top {summary!.topCves.length} CVEs is {filterLabel}. The chip counts cover every CVE in the + window; the list shows only the CVEs affecting the most devices. +
+ ) : (
- {summary!.topCves.map((c) => ( + {visibleCves.map((c) => (
@@ -157,6 +215,7 @@ export default function VulnerabilityExposurePanel({
))}
+ )}
{summary!.truncated && ( @@ -170,11 +229,42 @@ export default function VulnerabilityExposurePanel({ ); } -function Kpi({ value, label }: { value: number; label: string }) { +function Kpi({ value, label, title }: { value: number; label: string; title?: string }) { return ( -
+
{value.toLocaleString()}
{label}
); } + +/** A breakdown chip that doubles as a list-filter toggle; the active one carries a ring. */ +function FilterChip({ + label, + count, + className, + active, + onClick, + title, +}: { + label: string; + count: number; + className: string; + active: boolean; + onClick: () => void; + title: string; +}) { + return ( + + ); +} diff --git a/src/Web/autopilot-monitor-web/lib/__tests__/vulnerabilitySeverity.test.ts b/src/Web/autopilot-monitor-web/lib/__tests__/vulnerabilitySeverity.test.ts index 5f48474b..a8ed2dda 100644 --- a/src/Web/autopilot-monitor-web/lib/__tests__/vulnerabilitySeverity.test.ts +++ b/src/Web/autopilot-monitor-web/lib/__tests__/vulnerabilitySeverity.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { severityChipClass, KEV_BADGE_CLASS } from "../vulnerabilitySeverity"; +import { severityChipClass, priorityChipClass, KEV_BADGE_CLASS } from "../vulnerabilitySeverity"; describe("severityChipClass", () => { it("maps known severities case-insensitively", () => { @@ -14,6 +14,14 @@ describe("severityChipClass", () => { }); }); +describe("priorityChipClass", () => { + it("paints Act in the KEV red and the other bands neutral", () => { + expect(priorityChipClass("Act")).toBe(KEV_BADGE_CLASS); + expect(priorityChipClass("attend")).toContain("gray"); + expect(priorityChipClass("track")).toContain("gray"); + }); +}); + describe("KEV_BADGE_CLASS", () => { it("is a red-toned chip", () => { expect(KEV_BADGE_CLASS).toContain("red"); diff --git a/src/Web/autopilot-monitor-web/lib/vulnerabilitySeverity.ts b/src/Web/autopilot-monitor-web/lib/vulnerabilitySeverity.ts index b1f05ad4..f64398df 100644 --- a/src/Web/autopilot-monitor-web/lib/vulnerabilitySeverity.ts +++ b/src/Web/autopilot-monitor-web/lib/vulnerabilitySeverity.ts @@ -42,6 +42,16 @@ export function epssTitle(score: number, percentile?: number | null): string { return percentile != null ? `${base} — higher than ${(percentile * 100).toFixed(1)}% of all scored CVEs` : base; } +/** + * Chip classes for a priority band. Act reuses the KEV red (it IS the KEV set by definition); + * Attend and Track stay on the neutral outline so priority never reads as a second severity scale. + */ +export function priorityChipClass(priority: string): string { + return priority.toLowerCase() === "act" + ? KEV_BADGE_CLASS + : "bg-white text-gray-700 dark:bg-gray-800 dark:text-gray-300 border-gray-300 dark:border-gray-600"; +} + /** Human label for the remediation priority band. */ export function priorityLabel(priority?: string | null): string | null { switch ((priority || "").toLowerCase()) { diff --git a/src/Web/autopilot-monitor-web/utils/shared-manifests.json b/src/Web/autopilot-monitor-web/utils/shared-manifests.json index c2ab4741..abd4baab 100644 --- a/src/Web/autopilot-monitor-web/utils/shared-manifests.json +++ b/src/Web/autopilot-monitor-web/utils/shared-manifests.json @@ -6110,6 +6110,13 @@ "name": "SeverityBreakdown" } }, + { + "name": "priorityBreakdown", + "type": { + "kind": "ref", + "name": "PriorityBreakdown" + } + }, { "name": "topCves", "type": { @@ -14466,6 +14473,33 @@ } ] }, + "PriorityBreakdown": { + "kind": "object", + "doc": "Distinct-CVE counts grouped by their highest remediation priority (act/attend/track). Rows written before priorities existed carry no band and are counted in none - the three add up to at most DistinctCves. act equals the KEV set by construction (CvePriority.Compute: KEV ⇒ act).", + "fields": [ + { + "name": "act", + "type": { + "kind": "primitive", + "name": "number" + } + }, + { + "name": "attend", + "type": { + "kind": "primitive", + "name": "number" + } + }, + { + "name": "track", + "type": { + "kind": "primitive", + "name": "number" + } + } + ] + }, "ProgressGetSessionEventsResponse": { "kind": "object", "doc": "Response of GET /api/progress/sessions/{sessionId}/events — the session's event stream after the serial knowledge proof passed.", diff --git a/src/Web/autopilot-monitor-web/utils/wire-types.generated.ts b/src/Web/autopilot-monitor-web/utils/wire-types.generated.ts index 95bfd9b1..8257bf3a 100644 --- a/src/Web/autopilot-monitor-web/utils/wire-types.generated.ts +++ b/src/Web/autopilot-monitor-web/utils/wire-types.generated.ts @@ -1206,6 +1206,7 @@ export interface CveExposureSummary { distinctCves: number; kevCves: number; severityBreakdown: SeverityBreakdown; + priorityBreakdown: PriorityBreakdown; topCves: TopCve[]; truncated: boolean; } @@ -3087,6 +3088,13 @@ export interface PreviewWhitelistTenantEntry { tenantId: string; } +/** Distinct-CVE counts grouped by their highest remediation priority (act/attend/track). Rows written before priorities existed carry no band and are counted in none - the three add up to at most DistinctCves. act equals the KEV set by construction (CvePriority.Compute: KEV ⇒ act). */ +export interface PriorityBreakdown { + act: number; + attend: number; + track: number; +} + /** Response of GET /api/progress/sessions/{sessionId}/events — the session's event stream after the serial knowledge proof passed. */ export interface ProgressGetSessionEventsResponse { success: boolean;