Files
T
mukul975 c21af3347e Complete folder anatomy for all 649 cybersecurity skills + update LICENSE to Mahipal
- Add scripts/agent.py and references/api-reference.md to all remaining skills
- Update all 648 LICENSE files: copyright now reads 'Mahipal'
- Add implementing-security-monitoring-with-datadog (new skill with full anatomy)
- All 649 skills now have: SKILL.md, LICENSE, scripts/agent.py, references/api-reference.md
2026-03-11 00:22:12 +01:00

177 lines
6.8 KiB
Python

#!/usr/bin/env python3
"""MITRE ATT&CK Coverage Mapping Agent - maps detection rules to ATT&CK techniques and identifies gaps."""
import json
import argparse
import logging
from collections import defaultdict
from datetime import datetime
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
ENTERPRISE_TACTICS = [
"TA0001", "TA0002", "TA0003", "TA0004", "TA0005",
"TA0006", "TA0007", "TA0008", "TA0009", "TA0010",
"TA0011", "TA0040", "TA0042", "TA0043",
]
TACTIC_NAMES = {
"TA0001": "Initial Access", "TA0002": "Execution", "TA0003": "Persistence",
"TA0004": "Privilege Escalation", "TA0005": "Defense Evasion",
"TA0006": "Credential Access", "TA0007": "Discovery", "TA0008": "Lateral Movement",
"TA0009": "Collection", "TA0010": "Exfiltration", "TA0011": "Command and Control",
"TA0040": "Impact", "TA0042": "Resource Development", "TA0043": "Reconnaissance",
}
def load_detection_rules(filepath):
"""Load detection rules with ATT&CK mappings."""
with open(filepath) as f:
return json.load(f)
def load_attack_matrix(filepath):
"""Load ATT&CK enterprise matrix (techniques per tactic)."""
with open(filepath) as f:
return json.load(f)
def map_rules_to_techniques(rules):
"""Map detection rules to ATT&CK technique IDs."""
technique_rules = defaultdict(list)
unmapped = []
for rule in rules:
techniques = rule.get("mitre_attack", [])
if not techniques:
unmapped.append(rule.get("name", "unknown"))
continue
for tech in techniques:
technique_rules[tech].append({
"rule_name": rule.get("name", ""),
"severity": rule.get("severity", "medium"),
"data_source": rule.get("data_source", ""),
"enabled": rule.get("enabled", True),
})
return dict(technique_rules), unmapped
def calculate_coverage(technique_rules, attack_matrix):
"""Calculate coverage percentage per tactic."""
tactic_coverage = {}
for tactic_id, tactic_name in TACTIC_NAMES.items():
techniques_in_tactic = attack_matrix.get(tactic_id, [])
total = len(techniques_in_tactic)
covered = sum(1 for t in techniques_in_tactic if t in technique_rules)
tactic_coverage[tactic_id] = {
"tactic_name": tactic_name,
"total_techniques": total,
"covered": covered,
"coverage_percent": round(covered / max(total, 1) * 100, 1),
"gaps": [t for t in techniques_in_tactic if t not in technique_rules],
}
return tactic_coverage
def identify_priority_gaps(tactic_coverage, priority_techniques=None):
"""Identify high-priority coverage gaps."""
gaps = []
for tactic_id, data in tactic_coverage.items():
for tech in data["gaps"]:
priority = "high" if (priority_techniques and tech in priority_techniques) else "medium"
gaps.append({
"technique": tech,
"tactic": data["tactic_name"],
"tactic_id": tactic_id,
"priority": priority,
})
return sorted(gaps, key=lambda x: (0 if x["priority"] == "high" else 1, x["tactic"]))
def calculate_detection_depth(technique_rules):
"""Assess detection depth per technique (number of rules covering it)."""
depth = {}
for tech, rules in technique_rules.items():
enabled_rules = [r for r in rules if r["enabled"]]
data_sources = list(set(r["data_source"] for r in enabled_rules if r["data_source"]))
depth[tech] = {
"total_rules": len(rules),
"enabled_rules": len(enabled_rules),
"data_sources": data_sources,
"depth": "deep" if len(enabled_rules) >= 3 else "moderate" if len(enabled_rules) >= 2 else "shallow",
}
return depth
def generate_navigator_layer(technique_rules, tactic_coverage):
"""Generate ATT&CK Navigator layer JSON."""
techniques = []
for tech_id, rules in technique_rules.items():
score = min(len(rules), 4)
techniques.append({
"techniqueID": tech_id,
"score": score,
"comment": f"{len(rules)} detection rules",
"enabled": True,
})
layer = {
"name": "Detection Coverage",
"versions": {"attack": "14", "navigator": "4.9.1", "layer": "4.5"},
"domain": "enterprise-attack",
"techniques": techniques,
"gradient": {"colors": ["#ffffff", "#66b1ff", "#0044cc"], "minValue": 0, "maxValue": 4},
}
return layer
def generate_report(rules, technique_rules, unmapped, tactic_coverage, depth):
total_techniques_covered = len(technique_rules)
total_rules = len(rules)
report = {
"timestamp": datetime.utcnow().isoformat(),
"total_detection_rules": total_rules,
"mapped_rules": total_rules - len(unmapped),
"unmapped_rules": len(unmapped),
"techniques_covered": total_techniques_covered,
"tactic_coverage": tactic_coverage,
"detection_depth_summary": {
"deep": sum(1 for d in depth.values() if d["depth"] == "deep"),
"moderate": sum(1 for d in depth.values() if d["depth"] == "moderate"),
"shallow": sum(1 for d in depth.values() if d["depth"] == "shallow"),
},
"priority_gaps": identify_priority_gaps(tactic_coverage)[:20],
}
return report
def main():
parser = argparse.ArgumentParser(description="MITRE ATT&CK Coverage Mapping Agent")
parser.add_argument("--rules", required=True, help="JSON file with detection rules and ATT&CK mappings")
parser.add_argument("--matrix", help="ATT&CK matrix JSON (techniques per tactic)")
parser.add_argument("--navigator-output", help="Output ATT&CK Navigator layer JSON")
parser.add_argument("--output", default="attack_coverage_report.json")
args = parser.parse_args()
rules = load_detection_rules(args.rules)
attack_matrix = load_attack_matrix(args.matrix) if args.matrix else {t: [] for t in ENTERPRISE_TACTICS}
technique_rules, unmapped = map_rules_to_techniques(rules)
tactic_coverage = calculate_coverage(technique_rules, attack_matrix)
depth = calculate_detection_depth(technique_rules)
report = generate_report(rules, technique_rules, unmapped, tactic_coverage, depth)
if args.navigator_output:
layer = generate_navigator_layer(technique_rules, tactic_coverage)
with open(args.navigator_output, "w") as f:
json.dump(layer, f, indent=2)
logger.info("Navigator layer saved to %s", args.navigator_output)
with open(args.output, "w") as f:
json.dump(report, f, indent=2, default=str)
logger.info("Coverage: %d techniques covered by %d rules", len(technique_rules), len(rules))
print(json.dumps(report, indent=2, default=str))
if __name__ == "__main__":
main()