feat: add posture reasons and review queue to Security overview (#1462)

Add structured posture reasons derived alongside the posture verb in
securityPosture.ts so the masthead and Overview tab can answer why the
page is red, what to do first, and what clears it.

Backend:
- derivePostureReasons() returns blocker, review, and info reasons from
  the same SecurityPostureFacts used by deriveSecurityPosture()
- deriveSecurityPosture() depends on derivePostureReasons() internally
- Exposure split: public exposure with KEV, fixable, or EPSS >= 0.1 is a
  blocker; exposure without any of those is a review item
- Fully dismissed exposed images produce no posture reason
- postureReasons and primaryAction returned by the overview endpoint

Frontend:
- ReviewQueueCard on the Overview tab with per-row CTAs for blockers
- Action summary in masthead subtitle and desktop primary CTA button
- Card gated on posture not being Unknown
- Backward compatible with older remote nodes
This commit is contained in:
Anso
2026-06-25 21:56:25 -04:00
committed by GitHub
parent e9c262ae6a
commit 0384c47d1e
7 changed files with 504 additions and 26 deletions
+34
View File
@@ -1,3 +1,5 @@
import type { SecurityTab } from '@/lib/events';
export type VulnSeverity = 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW' | 'UNKNOWN';
export type VulnScanStatus = 'in_progress' | 'completed' | 'failed';
export type VulnScanTrigger = 'manual' | 'scheduled' | 'deploy' | 'deploy-preflight';
@@ -230,6 +232,33 @@ export interface ScanCompareResult {
* backend `SecurityPostureState`. */
export type SecurityPostureState = 'Action needed' | 'Monitoring' | 'Secure' | 'Unknown';
/** Kinds of posture reason the backend can report. */
export type PostureReasonKind =
| 'fixable_cve'
| 'known_exploited'
| 'secret'
| 'dangerous_compose'
| 'public_exposure'
| 'stale_scan'
| 'failed_scan'
| 'needs_review';
/** One structured reason explaining why the security posture is what it is. */
export interface PostureReason {
kind: PostureReasonKind;
count: number;
severity: 'blocker' | 'review' | 'info';
label: string;
description: string;
targetTab: SecurityTab;
}
/** Highest-priority action for the masthead CTA. */
export interface PostureAction {
label: string;
targetTab: SecurityTab;
}
/** Node-scoped security posture rollup for the Security page Overview. */
export interface SecurityOverview {
scannedImages: number;
@@ -269,6 +298,11 @@ export interface SecurityOverview {
posture?: SecurityPostureState;
/** True when the bounded posture pass hit its row cap on this node. */
posturePartial?: boolean;
/** Structured reasons for the posture (blockers, review, info). Optional for
* older remote nodes that do not report them. */
postureReasons?: PostureReason[];
/** Highest-priority action for the masthead CTA, or null when no blockers. */
primaryAction?: PostureAction | null;
}
/** Which detail tab the scan sheet opens on. Matches VulnerabilityScanSheet's tabs. */