feat(exec-dashboard): split threat actors into exposure vs detection strength
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled

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.
This commit is contained in:
kitos
2026-06-03 10:01:22 +02:00
parent b33562a34e
commit 9e36b683fa

View File

@@ -336,53 +336,73 @@ export default function ExecutiveDashboardPage() {
</div> </div>
</div> </div>
{/* Section 3: Top Threat Actors */} {/* Section 3: Threat Actor Exposure vs Detection Strength */}
<div className="rounded-xl border border-gray-800 bg-gray-900 p-4"> {(() => {
<h2 className="mb-1 text-sm font-semibold text-gray-300 flex items-center gap-1"> const allActors = [...(threatActors?.items || [])].filter(
Top Threat Actors (a) => a.technique_count > 0,
<MetricTooltip title="Top Threat Actors" description="The 5 most dangerous known adversary groups ranked by how many of their attack techniques we cannot currently detect. Higher uncovered count = greater exposure to that group." context="These are real APT groups (e.g. APT28, FIN7) with documented attack playbooks. Focus testing on their techniques." /> );
</h2>
<p className="mb-3 text-[10px] text-gray-500"> // Most vulnerable: highest uncovered technique count
Ranked by uncovered techniques (most exposure first) const mostVulnerable = [...allActors]
</p> .sort((a, b) => {
<div className="space-y-2"> const ua = a.technique_count * (1 - a.coverage_pct / 100);
{[...(threatActors?.items || [])] const ub = b.technique_count * (1 - b.coverage_pct / 100);
// Sort by uncovered technique count DESC (= technique_count × gap%) return ub !== ua ? ub - ua : b.technique_count - a.technique_count;
// Tiebreak: higher technique_count = broader attack surface })
.sort((a, b) => { .slice(0, 5);
const uncoveredA = a.technique_count * (1 - a.coverage_pct / 100);
const uncoveredB = b.technique_count * (1 - b.coverage_pct / 100); // Best covered: highest coverage_pct with ≥1 technique
if (uncoveredB !== uncoveredA) return uncoveredB - uncoveredA; const bestCovered = [...allActors]
return b.technique_count - a.technique_count; .filter((a) => a.coverage_pct > 0)
}) .sort((a, b) =>
.slice(0, 5) b.coverage_pct !== a.coverage_pct
.map((actor, idx) => ( ? 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 (
<div <div
key={actor.id} className="flex items-center gap-3 rounded-lg bg-gray-800/50 p-2.5 cursor-pointer hover:bg-gray-800 transition-colors"
className="flex items-center gap-3 rounded-lg bg-gray-800/50 p-3 cursor-pointer hover:bg-gray-800"
onClick={() => navigate(`/threat-actors/${actor.id}`)} onClick={() => navigate(`/threat-actors/${actor.id}`)}
> >
{/* Rank badge */} <div
<div className={`flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-[10px] font-bold ${ className={`flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-[10px] font-bold ${
idx === 0 ? "bg-red-500/20 text-red-400" side === "risk"
: idx === 1 ? "bg-orange-500/20 text-orange-400" ? idx === 0
: idx === 2 ? "bg-yellow-500/20 text-yellow-400" ? "bg-red-500/20 text-red-400"
: "bg-gray-700 text-gray-400" : idx === 1
}`}> ? "bg-orange-500/20 text-orange-400"
: "bg-yellow-500/20 text-yellow-400"
: "bg-green-500/20 text-green-400"
}`}
>
{idx + 1} {idx + 1}
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-sm font-medium text-white truncate"> <p className="text-xs font-medium text-white truncate">
{actor.name} {actor.name}
</p> </p>
<p className="text-[10px] text-gray-500 truncate"> <p className="text-[10px] text-gray-500 truncate">
{Math.round(actor.technique_count * (1 - actor.coverage_pct / 100))} uncovered {side === "risk"
{" / "} ? `${uncovered} uncovered / ${actor.technique_count} techniques`
{actor.technique_count} techniques : `${actor.technique_count} techniques covered`}
</p> </p>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-1.5 shrink-0">
<div className="w-20 h-1.5 rounded-full bg-gray-700 overflow-hidden"> <div className="w-16 h-1.5 rounded-full bg-gray-700 overflow-hidden">
<div <div
className="h-full rounded-full transition-all" className="h-full rounded-full transition-all"
style={{ style={{
@@ -396,14 +416,66 @@ export default function ExecutiveDashboardPage() {
}} }}
/> />
</div> </div>
<span className="w-10 text-right text-xs font-medium text-gray-300 tabular-nums"> <span className="w-8 text-right text-[10px] font-medium text-gray-300 tabular-nums">
{actor.coverage_pct.toFixed(0)}% {actor.coverage_pct.toFixed(0)}%
</span> </span>
</div> </div>
</div> </div>
))} );
</div> };
</div>
return (
<div className="grid gap-4 lg:grid-cols-2">
{/* Left: highest exposure */}
<div className="rounded-xl border border-red-900/30 bg-gray-900 p-4">
<h2 className="mb-1 text-sm font-semibold text-red-400 flex items-center gap-1">
Highest Exposure
<MetricTooltip
title="Highest Exposure Threat Actors"
description="Adversary groups whose attack techniques we are LEAST prepared to detect. We have the most uncovered techniques for these groups."
context="We are most vulnerable to these groups. Prioritise testing their TTPs urgently."
/>
</h2>
<p className="mb-3 text-[10px] text-gray-500 leading-snug">
These threat actors use techniques we <span className="text-red-400 font-medium">cannot currently detect</span>.
If they targeted us today, most of their attacks would go unnoticed.
</p>
<div className="space-y-1.5">
{mostVulnerable.map((actor, idx) => (
<ActorRow key={actor.id} actor={actor} idx={idx} side="risk" />
))}
{mostVulnerable.length === 0 && (
<p className="py-4 text-center text-xs text-gray-500">No data available</p>
)}
</div>
</div>
{/* Right: best detection capability */}
<div className="rounded-xl border border-green-900/30 bg-gray-900 p-4">
<h2 className="mb-1 text-sm font-semibold text-green-400 flex items-center gap-1">
Strongest Detection
<MetricTooltip
title="Strongest Detection Capability"
description="Adversary groups whose attack techniques we are BEST prepared to detect. High coverage means our Blue Team can identify most of their TTPs."
context="These are our strengths. If these groups attacked us, we would likely detect them."
/>
</h2>
<p className="mb-3 text-[10px] text-gray-500 leading-snug">
For these threat actors we have <span className="text-green-400 font-medium">strong detection coverage</span>.
Their techniques are well tested and our Blue Team would likely identify an intrusion.
</p>
<div className="space-y-1.5">
{bestCovered.map((actor, idx) => (
<ActorRow key={actor.id} actor={actor} idx={idx} side="strength" />
))}
{bestCovered.length === 0 && (
<p className="py-4 text-center text-xs text-gray-500">No coverage data yet run tests to populate</p>
)}
</div>
</div>
</div>
);
})()}
{/* Section 4: Operational KPIs */} {/* Section 4: Operational KPIs */}
<div className="grid grid-cols-2 gap-4 lg:grid-cols-4"> <div className="grid grid-cols-2 gap-4 lg:grid-cols-4">