feat(techniques): status hover tooltips + min 2 tests for validated
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
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:
@@ -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):
|
||||||
self.status_global = TechniqueStatus.validated
|
# Need at least _MIN_VALIDATED_FOR_FULL tests for "validated"
|
||||||
|
if validated_count >= _MIN_VALIDATED_FOR_FULL:
|
||||||
|
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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user