ACRE Coverage Calculator
Calculate your organization’s ACRE (ATT&CK Coverage Ratio Evaluation) score by comparing your custom detection rules against the detectable ATT&CK technique baseline. Produces a weighted coverage score per OS platform, tracks history for trend analysis, and identifies coverage gaps with specific open-source rule recommendations.
Prerequisites
Before running this skill, verify:
platform_coverage.jsonexists in the working directory (generated by the platform-coverage skill)- Open-source detection repos are present at the paths below (cloned during the platform-coverage skill):
~/Security-Detections-MCP/detections/sigma/~/Security-Detections-MCP/detections/security_content/~/Security-Detections-MCP/detections/detection-rules/
- Your detection export file is ready (CSV or JSON — see format below)
If platform_coverage.json is missing, run the platform-coverage skill first, then return here.
Input Format
Your detection export must contain one row/object per rule with these fields. Column names are flexible; accepted variants are listed below.
| Field | Required | Accepted names | Notes |
|---|---|---|---|
| Rule identifier | Yes | rule_id, id, name, rule_name, title | Used in gap output only |
| Technique IDs | Yes | technique_ids, techniques, mitre_techniques, attack_techniques | Comma-separated string or JSON array; e.g. T1059.001,T1059.003 |
| Platform / OS | Yes | platform, os, operating_system | Must be Windows, Linux, or macOS (case-insensitive). If your export does not include this field, ask a GenAI tool to infer it from your data_source or log_source column before running this skill. |
| Creation date | Yes | created, created_date, created_at, date_created | ISO 8601 preferred (YYYY-MM-DD); also accepts MM/DD/YYYY and YYYY/MM/DD |
CSV example:
rule_id,technique_ids,platform,created
det-001,T1059.001,Windows,2025-01-15
det-002,"T1055,T1055.001",Windows,2025-03-20
det-003,T1003.007,Linux,2025-06-01JSON example:
[
{ "rule_id": "det-001", "technique_ids": ["T1059.001"], "platform": "Windows", "created": "2025-01-15" },
{ "rule_id": "det-002", "technique_ids": ["T1055", "T1055.001"], "platform": "Windows", "created": "2025-03-20" },
{ "rule_id": "det-003", "technique_ids": ["T1003.007"], "platform": "Linux", "created": "2025-06-01" }
]Step 1: Load Platform Coverage Data
Read platform_coverage.json. This file contains one record per technique × platform pair produced by the platform-coverage skill.
Expected record structure:
{
"technique_id": "T1059.004",
"technique_name": "Unix Shell",
"tactics": ["execution"],
"platform": "Linux",
"covered": true,
"coverage": {
"Sigma": 12,
"Splunk": 8,
"Elasticsearch": 6
}
}The tactics values are lowercase ATT&CK phase names (e.g., "persistence", "defense-evasion"). The coverage counts are per-library rule counts for that technique × platform pair.
If the file is absent or malformed, stop and tell the user to run the platform-coverage skill first.
Step 2: Build the Detectable Baseline (Denominator)
For each record in platform_coverage.json, apply the minimum threshold and tactic weight:
Minimum threshold: A technique × platform pair enters the denominator only if:
coverage.Sigma + coverage.Splunk + coverage.Elasticsearch >= 5
This filters out one-offs and ensures the community has meaningfully validated detectability. Techniques below this threshold are excluded entirely (neither covered nor uncovered in ACRE).
Tactic weight: Once a technique × platform pair passes the threshold, assign its weight:
- If any of the technique’s
tacticsare in the weighted set → weight = 1.5 - Otherwise → weight = 1.0
Weighted tactic set (the middle of the attack, where defenders can most disrupt adversaries):
| Tactic ID | Phase name in ATT&CK data |
|---|---|
| TA0003 | persistence |
| TA0004 | privilege-escalation |
| TA0005 | defense-evasion |
| TA0006 | credential-access |
| TA0007 | discovery |
| TA0008 | lateral-movement |
| TA0009 | collection |
WEIGHTED_TACTICS = {
"persistence", "privilege-escalation", "defense-evasion",
"credential-access", "discovery", "lateral-movement", "collection"
}
def get_weight(tactics: list[str]) -> float:
return 1.5 if any(t in WEIGHTED_TACTICS for t in tactics) else 1.0The weighted denominator for each platform is the sum of weights across all technique × platform pairs that pass the threshold.
Step 3: Parse User Detections
Write and run a Python script parse_detections.py that accepts the detection file path as a command-line argument.
Parsing logic:
- Auto-detect format by file extension (
.csv→ CSV reader,.json→json.load) - Normalize all column names: lowercase, strip whitespace
- Map column name variants to canonical names (first match wins per field):
rule_id←rule_id | id | name | rule_name | titletechnique_ids←technique_ids | techniques | mitre_techniques | attack_techniquesplatform←platform | os | operating_systemcreated←created | created_date | created_at | date_created
- For each row:
- Expand
technique_idsinto a list: if string, split on commas and strip whitespace; if array, use as-is. Uppercase all IDs (e.g.,t1059.001→T1059.001). - Normalize
platformto title case (windows→Windows). Accept onlyWindows,Linux,macOS— warn and skip rows with other values. - Parse
createdto a date. Try formats%Y-%m-%d,%m/%d/%Y,%Y/%m/%din order. - If any required field is missing or unparseable, print a warning identifying the row and skip it.
- Expand
- Print a summary: total rows read, rows skipped with reasons.
- Write parsed records to
parsed_detections.json:
[
{
"rule_id": "det-001",
"technique_ids": ["T1059.001"],
"platform": "Windows",
"created": "2025-01-15"
}
]Step 4: Calculate ACRE Scores
Write and run a Python script calculate_acre.py that loads the baseline from Step 2 and the parsed detections from Step 3.
Per platform (Windows, Linux, macOS):
- Build a set of covered
(technique_id, platform)pairs from user detections - Iterate over all technique × platform pairs in the denominator for this platform:
- Add
weighttoweighted_total - If
(technique_id, platform)is in the covered set: addweighttoweighted_covered, incrementcovered_count
- Add
acre_score = weighted_covered / weighted_total(return0.0ifweighted_total == 0)
Output structure (write to acre_current.json):
{
"date": "2026-04-12",
"scores": {
"Windows": {
"acre_score": 0.47,
"covered_count": 98,
"detectable_count": 215,
"weighted_covered": 132.5,
"weighted_total": 281.5
},
"Linux": {
"acre_score": 0.31,
"covered_count": 45,
"detectable_count": 145,
"weighted_covered": 55.5,
"weighted_total": 179.0
},
"macOS": {
"acre_score": 0.24,
"covered_count": 26,
"detectable_count": 110,
"weighted_covered": 36.0,
"weighted_total": 150.0
}
}
}Step 5: Update History
Load acre_history.json if it exists; otherwise start with { "entries": [] }.
Append today’s scores as a new entry using the structure from acre_current.json. If an entry for today’s date already exists, overwrite it (re-running the skill on the same day updates rather than duplicates).
Save back to acre_history.json:
{
"entries": [
{
"date": "2025-06-01",
"scores": {
"Windows": { "acre_score": 0.38, "covered_count": 79, "detectable_count": 215, "weighted_covered": 106.5, "weighted_total": 281.5 },
"Linux": { "acre_score": 0.22, "covered_count": 30, "detectable_count": 145, "weighted_covered": 39.5, "weighted_total": 179.0 },
"macOS": { "acre_score": 0.18, "covered_count": 19, "detectable_count": 110, "weighted_covered": 27.0, "weighted_total": 150.0 }
}
},
{
"date": "2026-04-12",
"scores": { ... }
}
]
}Note: because the denominator can shift as open-source libraries add or remove rules, store detectable_count and weighted_total alongside each entry. This keeps historical scores interpretable even when the denominator changes between runs.
Step 6: Identify Gaps and Find Open-Source Rule Recommendations
For each technique × platform pair in the denominator that is not covered by any user detection, find the specific open-source rules that address it.
Write and run a Python script find_gap_rules.py.
For each gap:
Search each of the three repos using the same parsing logic as the platform-coverage skill. Match rules to the gap’s technique ID and platform.
Sigma (~/Security-Detections-MCP/detections/sigma/):
- Scan
.ymlfiles underrules/andrules-threat-hunting/recursively - A rule matches if its
tags[]contains the technique ID (e.g.,attack.t1059.001, case-insensitive) AND itslogsourcemaps to the target platform (use the platform mapping table from the platform-coverage skill) - Report:
{ "file": "<relative path>", "title": "<rule title field>" }
Splunk (~/Security-Detections-MCP/detections/security_content/):
- Scan
.ymlfiles underdetections/recursively; skipdeprecated/ - A rule matches if
tags.mitre_attack_idcontains the technique ID AND the platform resolves to the target OS (viadata_sourcefirst, thentags.asset_type) - Report:
{ "file": "<relative path>", "name": "<name field>" }
Elastic (~/Security-Detections-MCP/detections/detection-rules/):
- Scan
.tomlfiles underrules/recursively; skiprules/_deprecated/ - A rule matches if any
rule.threat.technique[].id(or subtechnique id) equals the technique ID AND the directory path / OS tags match the target platform - Report:
{ "file": "<relative path>", "name": "<rule.name field>" }
Output — write acre_gaps.json:
{
"generated": "2026-04-12",
"gaps": [
{
"technique_id": "T1003.001",
"technique_name": "LSASS Memory",
"platform": "Windows",
"tactics": ["credential-access"],
"weight": 1.5,
"open_source_rules": {
"Sigma": [
{ "file": "rules/windows/credential_access/cred_dump_lsass_access.yml", "title": "LSASS Memory Access" },
{ "file": "rules/windows/credential_access/lsass_dump_comsvcs.yml", "title": "LSASS Dump via Comsvcs DLL" }
],
"Splunk": [
{ "file": "detections/endpoint/windows_lsass_memory_dump.yml", "name": "Windows LSASS Memory Dump" }
],
"Elastic": [
{ "file": "rules/windows/credential_access_lsass_memdump_handle_access.toml", "name": "LSASS Memory Dump Handle Access" }
]
}
}
]
}Sort gaps by:
- Weight descending (1.5× weighted-tactic gaps first)
- Total open-source rule count descending within each weight tier (most community coverage = highest-confidence, easiest-to-adopt rules first)
Step 7: Produce Output
Console Summary
====================================================
ACRE Score Report — 2026-04-12
====================================================
Windows
ACRE Score: 0.47 (47%)
Covered: 98 / 215 detectable techniques
Weighted: 132.5 / 281.5
Linux
ACRE Score: 0.31 (31%)
Covered: 45 / 145 detectable techniques
Weighted: 55.5 / 179.0
macOS
ACRE Score: 0.24 (24%)
Covered: 26 / 110 detectable techniques
Weighted: 36.0 / 150.0
Detectable threshold: ≥5 open-source rules (Sigma + Splunk + Elastic combined)
Tactic weight (1.5×): persistence, privilege-escalation, defense-evasion,
credential-access, discovery, lateral-movement, collection
====================================================
ASCII Trend Chart
If acre_history.json contains 2 or more entries, render a terminal line chart. Use W for Windows, L for Linux, M for macOS as data-point markers. For readability, scale the Y-axis to the actual data range rather than always 0.0–1.0. Label x-axis with entry dates (abbreviated YYYY-MM). Connect consecutive same-OS points with . characters when there are 3 or more data points.
Example shape (values are illustrative):
ACRE Score History (weighted)
0.6 | W
0.5 | W . . . .
0.4 | W . . L
0.3 | W . . L . . . . L
0.2 | L M . . . . . . . M
0.1 | M
+--+--------+--------+--------+--
2025-06 2025-09 2026-01 2026-04
W = Windows L = Linux M = macOS
If only one history entry exists (first run), skip the chart and print: Run this skill again after adding more detections to see your trend line.
Top Gaps Table
Print the top 20 gaps by priority (1.5× techniques first, then by total OSS rule count descending):
Top Coverage Gaps — Windows
──────────────────────────────────────────────────────────────────────────
Technique Name Weight Sigma Splunk Elastic
──────────────────────────────────────────────────────────────────────────
T1003.001 LSASS Memory 1.5× 14 8 6
T1055.012 Process Hollowing 1.5× 11 5 9
T1547.001 Registry Run Keys / Startup 1.5× 9 6 4
T1078 Valid Accounts 1.5× 7 11 3
...
Full gap list with rule file paths: acre_gaps.json
Repeat this table for Linux and macOS.
Output Files
| File | Contents |
|---|---|
acre_current.json | Scores from this run |
acre_history.json | Persistent dated history; append one entry per run |
acre_gaps.json | Prioritized gap list with open-source rule file paths |
parsed_detections.json | Normalized detection records (intermediate; safe to delete) |
Notes and Caveats
Denominator drift: The detectable baseline changes as open-source libraries add or remove rules. When the denominator shifts, ACRE scores can move even without any detection work. acre_history.json stores the weighted denominator with every entry so trend lines remain interpretable.
One detection = covered: ACRE counts a technique × platform pair as covered if at least one user rule exists for it. Depth of coverage (how many distinct procedures are addressed) is outside ACRE’s current scope — see the Technique Research Report (TRR) for procedure-level measurement.
Subtechniques vs. parents: A rule tagged to a parent technique (e.g., T1059) covers the parent only — it does not automatically propagate to subtechniques (T1059.001, etc.). Tag rules to the most specific applicable technique for accurate scoring.
Platform is required: ACRE requires an explicit OS field on every detection. If your SIEM export omits it, ask a GenAI tool to infer platform from your data_source or log_source field before running this skill.
Importing historical data: To backfill scores from before you started running this skill, manually add entries to acre_history.json following the entry structure in Step 5. Dates must be in YYYY-MM-DD format and entries should be sorted oldest-first for correct chart rendering.