From 9e36b683faa542892230ebadad35e5c62b14cba4 Mon Sep 17 00:00:00 2001 From: kitos Date: Wed, 3 Jun 2026 10:01:22 +0200 Subject: [PATCH] feat(exec-dashboard): split threat actors into exposure vs detection strength MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace single list with two-column layout: - LEFT '⚠ Highest Exposure': top 5 actors by uncovered technique count, red border, text explaining 'these attacks would go unnoticed today' - RIGHT '✅ Strongest Detection': top 5 actors by coverage %, green border, text explaining 'Blue Team would likely detect an intrusion from these' Shows both the risks (where to focus testing) and the strengths (what's already well protected) to give executives a balanced view. --- frontend/src/pages/ExecutiveDashboardPage.tsx | 152 +++++++++++++----- 1 file changed, 112 insertions(+), 40 deletions(-) diff --git a/frontend/src/pages/ExecutiveDashboardPage.tsx b/frontend/src/pages/ExecutiveDashboardPage.tsx index 3ab7cbf..1e6f5ff 100644 --- a/frontend/src/pages/ExecutiveDashboardPage.tsx +++ b/frontend/src/pages/ExecutiveDashboardPage.tsx @@ -336,53 +336,73 @@ export default function ExecutiveDashboardPage() { - {/* Section 3: Top Threat Actors */} -
-

- Top Threat Actors - -

-

- Ranked by uncovered techniques (most exposure first) -

-
- {[...(threatActors?.items || [])] - // Sort by uncovered technique count DESC (= technique_count × gap%) - // Tiebreak: higher technique_count = broader attack surface - .sort((a, b) => { - const uncoveredA = a.technique_count * (1 - a.coverage_pct / 100); - const uncoveredB = b.technique_count * (1 - b.coverage_pct / 100); - if (uncoveredB !== uncoveredA) return uncoveredB - uncoveredA; - return b.technique_count - a.technique_count; - }) - .slice(0, 5) - .map((actor, idx) => ( + {/* Section 3: Threat Actor Exposure vs Detection Strength */} + {(() => { + const allActors = [...(threatActors?.items || [])].filter( + (a) => a.technique_count > 0, + ); + + // Most vulnerable: highest uncovered technique count + const mostVulnerable = [...allActors] + .sort((a, b) => { + const ua = a.technique_count * (1 - a.coverage_pct / 100); + const ub = b.technique_count * (1 - b.coverage_pct / 100); + return ub !== ua ? ub - ua : b.technique_count - a.technique_count; + }) + .slice(0, 5); + + // Best covered: highest coverage_pct with ≥1 technique + const bestCovered = [...allActors] + .filter((a) => a.coverage_pct > 0) + .sort((a, b) => + b.coverage_pct !== a.coverage_pct + ? b.coverage_pct - a.coverage_pct + : b.technique_count - a.technique_count, + ) + .slice(0, 5); + + const ActorRow = ({ + actor, + idx, + side, + }: { + actor: (typeof allActors)[0]; + idx: number; + side: "risk" | "strength"; + }) => { + const uncovered = Math.round( + actor.technique_count * (1 - actor.coverage_pct / 100), + ); + return (
navigate(`/threat-actors/${actor.id}`)} > - {/* Rank badge */} -
+
{idx + 1}
-

+

{actor.name}

- {Math.round(actor.technique_count * (1 - actor.coverage_pct / 100))} uncovered - {" / "} - {actor.technique_count} techniques + {side === "risk" + ? `${uncovered} uncovered / ${actor.technique_count} techniques` + : `${actor.technique_count} techniques covered`}

-
-
+
+
- + {actor.coverage_pct.toFixed(0)}%
- ))} -
-
+ ); + }; + + return ( +
+ {/* Left: highest exposure */} +
+

+ ⚠ Highest Exposure + +

+

+ These threat actors use techniques we cannot currently detect. + If they targeted us today, most of their attacks would go unnoticed. +

+
+ {mostVulnerable.map((actor, idx) => ( + + ))} + {mostVulnerable.length === 0 && ( +

No data available

+ )} +
+
+ + {/* Right: best detection capability */} +
+

+ ✅ Strongest Detection + +

+

+ For these threat actors we have strong detection coverage. + Their techniques are well tested and our Blue Team would likely identify an intrusion. +

+
+ {bestCovered.map((actor, idx) => ( + + ))} + {bestCovered.length === 0 && ( +

No coverage data yet — run tests to populate

+ )} +
+
+
+ ); + })()} {/* Section 4: Operational KPIs */}