Health Score: cap MITRE Coverage at 100% by canonicalising tactics

STAR rules sometimes label tactics with non-canonical names
(observed: "Stealth", "Defense Impairment") which were counted as
distinct tactics on top of the 14 canonical ATT&CK Enterprise ones,
producing percentages > 100% (e.g. 15/14 = 107.1% on a busy tenant).

Fix in get_health_score():
  - Restrict covered_tactics to the 14 canonical ATT&CK Enterprise tactics.
  - Map known STAR aliases ("Stealth", "Defense Impairment") -> "Defense Evasion".
  - Derive TOTAL_TACTICS from the canonical set (single source of truth).

Result: tactics_covered = 14, mitre_pct = 100.0 (was 15 / 107.1).
This commit is contained in:
marc
2026-05-22 19:41:48 +02:00
parent 70f3f83db3
commit 8c4298ca2a
+19 -2
View File
@@ -1098,7 +1098,21 @@ def _compute_health(db) -> dict:
parser_pct = round((covered_sources / total_sources * 100) if total_sources else 0.0, 1)
# --- MITRE coverage ---
TOTAL_TACTICS = 14 # standard ATT&CK Enterprise tactic count
# Standard ATT&CK Enterprise tactics (14).
CANONICAL_TACTICS = frozenset({
"Reconnaissance", "Resource Development", "Initial Access", "Execution",
"Persistence", "Privilege Escalation", "Defense Evasion", "Credential Access",
"Discovery", "Lateral Movement", "Collection", "Command and Control",
"Exfiltration", "Impact",
})
# SentinelOne STAR rules sometimes label tactics with non-canonical names.
# Map them to canonical ATT&CK so we don't over-count and exceed 100%.
TACTIC_ALIASES = {
"Stealth": "Defense Evasion",
"Defense Impairment": "Defense Evasion",
}
TOTAL_TACTICS = len(CANONICAL_TACTICS)
rules = db.query(ParsedRule).filter_by(rule_type="library").all()
total_rules = len(rules)
covered_tactics: set = set()
@@ -1114,7 +1128,10 @@ def _compute_health(db) -> dict:
if tactics or techniques:
rules_with_mitre += 1
for t in tactics:
if t and t != "Uncategorized":
if not t or t == "Uncategorized":
continue
t = TACTIC_ALIASES.get(t, t)
if t in CANONICAL_TACTICS:
covered_tactics.add(t)
for tech in techniques:
k = tech.get("id") or tech.get("name")