attack-mapper 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,72 @@
1
+ """Extra output plugins: portfolio badge (SVG) and machine-readable JSON."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import List
6
+
7
+ from .coverage import CoverageReport
8
+
9
+
10
+ def render_badge(report: CoverageReport) -> str:
11
+ """Return an SVG coverage badge string (e.g. for a README or portfolio)."""
12
+ pct = report.coverage_ratio * 100
13
+ label = "ATT&CK"
14
+ value = f"{pct:.0f}%"
15
+ color = "#e74c3c" if pct < 25 else "#e67e22" if pct < 60 else "#27ae60"
16
+
17
+ # Simple two-segment badge, shields.io style.
18
+ label_w, value_w = 56, 46
19
+ total = label_w + value_w
20
+ return (
21
+ f'<svg xmlns="http://www.w3.org/2000/svg" width="{total}" height="20" '
22
+ f'role="img" aria-label="{label}: {value}">'
23
+ f'<linearGradient id="b" x2="0" y2="100%">'
24
+ f'<stop offset="0" stop-color="#444"/><stop offset="1" stop-color="#333"/>'
25
+ f'</linearGradient>'
26
+ f'<clipPath id="a"><rect width="{total}" height="20" rx="3"/></clipPath>'
27
+ f'<g clip-path="url(#a)">'
28
+ f'<rect width="{label_w}" height="20" fill="#444"/>'
29
+ f'<rect x="{label_w}" width="{value_w}" height="20" fill="{color}"/>'
30
+ f'<g fill="#fff" text-anchor="middle" '
31
+ f'font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11">'
32
+ f'<text x="{label_w/2}" y="14">{label}</text>'
33
+ f'<text x="{label_w + value_w/2}" y="14">{value}</text>'
34
+ f"</g></g></svg>"
35
+ )
36
+
37
+
38
+ def render_json(report: CoverageReport) -> str:
39
+ """Return a JSON summary suitable for dashboards / CI artifacts."""
40
+ import json as _json
41
+
42
+ out = {
43
+ "version": report.db.version,
44
+ "rules_analyzed": report.rule_count,
45
+ "coverage": {
46
+ "covered": len(report.covered),
47
+ "covered_in_scope": len(report.covered_in_scope),
48
+ "in_scope": report.total_techniques,
49
+ "ratio": round(report.coverage_ratio, 4),
50
+ },
51
+ "scope": {
52
+ "include": sorted(report.include),
53
+ "ignore": sorted(report.ignore),
54
+ "ignored_count": len(report.ignored),
55
+ "out_of_scope_count": len(report.out_of_scope),
56
+ },
57
+ "unknown_technique_ids": report.unknown,
58
+ "unmapped_rules": [r.path for r in report.unmapped_rules],
59
+ "covered_techniques": [
60
+ {
61
+ "id": tid,
62
+ "name": report.db.technique_name(tid),
63
+ "rules": report.covered[tid],
64
+ }
65
+ for tid in report.covered_techniques
66
+ ],
67
+ "tactics": [
68
+ {"tactic": t, **info}
69
+ for t, info in report.tactics_coverage().items()
70
+ ],
71
+ }
72
+ return _json.dumps(out, indent=2)