feat(techniques): status hover tooltips + min 2 tests for validated
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled

1. Status logic (v3): require ≥2 validated tests with 'detected' result
   to reach 'validated' status. With only 1 validated+detected test the
   technique stays 'partial' (single test is insufficient evidence).
   Backfilled existing data: T1012 and T1059.001 downgraded to 'partial'.

2. Hover tooltips on status badges in TechniquesPage and TechniqueDetailPage:
   - validated: ≥2 tests executed and detected
   - partial: some tests done but incomplete coverage
   - in_progress: tests exist but none validated yet
   - not_covered: tests run but Blue Team didn't detect
   - not_evaluated: no tests created yet
   - review_required: recent update needs acknowledgment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
kitos
2026-06-02 10:32:52 +02:00
parent ba75baeb7d
commit 7e4a44bbde
3 changed files with 44 additions and 10 deletions

View File

@@ -115,17 +115,26 @@ class TechniqueEntity:
) -> TechniqueStatus: ) -> TechniqueStatus:
"""Recompute ``status_global`` from a list of (state, detection_result) pairs. """Recompute ``status_global`` from a list of (state, detection_result) pairs.
Rules (v2): Rules (v3):
1. No tests -> not_evaluated 1. No tests -> not_evaluated
2. All validated -> inspect detection results: 2. All tests validated -> inspect detection results:
- All detected -> validated a. All detected AND ≥ 2 validated tests -> validated
- Any partially_detected -> partial b. All detected but only 1 validated test -> partial
- Otherwise -> not_covered (single test is not enough evidence for full coverage)
3. Some validated, others in progress -> partial c. Any partially_detected -> partial
4. All in intermediate states -> in_progress d. Otherwise (no detected results) -> not_covered
3. Some validated, others in intermediate states -> partial
4. All tests in intermediate states (draft/executing/evaluating/review/rejected)
-> in_progress
Minimum validated count for "validated": 2 tests.
With only 1 validated+detected test the technique is "partial" to
signal that more testing is recommended.
Returns the new status (also set on the entity). Returns the new status (also set on the entity).
""" """
_MIN_VALIDATED_FOR_FULL = 2 # require ≥ N validated tests for "validated"
tests = [ tests = [
_TestSnapshot( _TestSnapshot(
state=s if isinstance(s, TestState) else TestState(s), state=s if isinstance(s, TestState) else TestState(s),
@@ -137,9 +146,14 @@ class TechniqueEntity:
if not tests: if not tests:
self.status_global = TechniqueStatus.not_evaluated self.status_global = TechniqueStatus.not_evaluated
elif all(t.state == TestState.validated for t in tests): elif all(t.state == TestState.validated for t in tests):
validated_count = len(tests)
results = [t.detection_result for t in tests if t.detection_result] results = [t.detection_result for t in tests if t.detection_result]
if results and all(r == TestResult.detected or r == "detected" for r in results): if results and all(r == TestResult.detected or r == "detected" for r in results):
# Need at least _MIN_VALIDATED_FOR_FULL tests for "validated"
if validated_count >= _MIN_VALIDATED_FOR_FULL:
self.status_global = TechniqueStatus.validated self.status_global = TechniqueStatus.validated
else:
self.status_global = TechniqueStatus.partial
elif any( elif any(
r == TestResult.partially_detected or r == "partially_detected" r == TestResult.partially_detected or r == "partially_detected"
for r in results for r in results

View File

@@ -27,6 +27,15 @@ import { useAuth } from "../context/AuthContext";
import TestFromTemplateForm from "../components/TestFromTemplateForm"; import TestFromTemplateForm from "../components/TestFromTemplateForm";
import type { TechniqueStatus, TestState, TestResult } from "../types/models"; import type { TechniqueStatus, TestState, TestResult } from "../types/models";
const STATUS_TOOLTIPS: Record<TechniqueStatus, string> = {
validated: "✅ Validated — ≥2 tests executed and detected by Blue Team. Technique is covered.",
partial: "🟡 Partial — Some tests done but not all detected, only 1 validated test, or some tests still pending. More testing needed.",
in_progress: "🔵 In Progress — Tests exist but none validated yet (draft, executing, or under review).",
not_covered: "🔴 Not Covered — Tests were run but Blue Team did not detect the attack. Coverage gap.",
not_evaluated: "⚫ Not Evaluated — No tests created for this technique yet.",
review_required:"🟠 Review Required — Technique was recently updated or new intel/rules detected. Needs review.",
};
const statusBadgeColors: Record<TechniqueStatus, string> = { const statusBadgeColors: Record<TechniqueStatus, string> = {
validated: "bg-green-900/50 text-green-400 border-green-500/30", validated: "bg-green-900/50 text-green-400 border-green-500/30",
partial: "bg-yellow-900/50 text-yellow-400 border-yellow-500/30", partial: "bg-yellow-900/50 text-yellow-400 border-yellow-500/30",
@@ -243,9 +252,10 @@ export default function TechniqueDetailPage() {
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<h1 className="text-2xl font-bold text-white">{technique.mitre_id}</h1> <h1 className="text-2xl font-bold text-white">{technique.mitre_id}</h1>
<span <span
className={`inline-flex rounded-full border px-2.5 py-0.5 text-xs font-medium ${ className={`inline-flex rounded-full border px-2.5 py-0.5 text-xs font-medium cursor-help ${
statusBadgeColors[technique.status_global] statusBadgeColors[technique.status_global]
}`} }`}
title={STATUS_TOOLTIPS[technique.status_global]}
> >
{technique.status_global.replace(/_/g, " ")} {technique.status_global.replace(/_/g, " ")}
</span> </span>

View File

@@ -26,6 +26,15 @@ const statusBadgeColors: Record<TechniqueStatus, string> = {
review_required: "bg-orange-900/50 text-orange-400 border-orange-500/30", review_required: "bg-orange-900/50 text-orange-400 border-orange-500/30",
}; };
const STATUS_TOOLTIPS: Record<TechniqueStatus, string> = {
validated: "✅ Validated — ≥2 tests executed and detected by Blue Team. Technique is covered.",
partial: "🟡 Partial — Some tests done but not all detected, only 1 validated test, or some tests still pending. More testing needed.",
in_progress: "🔵 In Progress — Tests exist but none validated yet (draft, executing, or under review).",
not_covered: "🔴 Not Covered — Tests were run but Blue Team did not detect the attack. Coverage gap.",
not_evaluated: "⚫ Not Evaluated — No tests created for this technique yet.",
review_required:"🟠 Review Required — Technique was recently updated or new intel/rules detected. Needs review.",
};
export default function TechniquesPage() { export default function TechniquesPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const [viewMode, setViewMode] = useState<"matrix" | "list">("matrix"); const [viewMode, setViewMode] = useState<"matrix" | "list">("matrix");
@@ -240,9 +249,10 @@ export default function TechniquesPage() {
</td> </td>
<td className="px-4 py-3"> <td className="px-4 py-3">
<span <span
className={`inline-flex rounded-full border px-2 py-0.5 text-xs font-medium ${ className={`inline-flex rounded-full border px-2 py-0.5 text-xs font-medium cursor-help ${
statusBadgeColors[tech.status_global] statusBadgeColors[tech.status_global]
}`} }`}
title={STATUS_TOOLTIPS[tech.status_global]}
> >
{tech.status_global.replace(/_/g, " ")} {tech.status_global.replace(/_/g, " ")}
</span> </span>