Evaluation¶
Every number on this page comes from a script in scripts/ run on pinned,
checksummed public data. The result files in
results/ are
included here verbatim.
Provenance. Each table names the GitHub Actions run and the commit that
produced it. All current files come from bench run
37093689060
of the manual bench workflow. That workflow regenerates every file and lists
any value that differs from the committed one. Reproduce gives
the commands and runtimes.
Intervals. All are 95%:
- group-cluster bootstrap where incidents share a group, with paired differences computed on the same resamples;
- a Wilson interval with the number of groups as the sample size when a rate is exactly 0 or 1 and the bootstrap interval is degenerate (marked †);
- Wilson intervals for the small APTnotes and campaign sets;
- a leave-one-group-out jackknife for clustering scores;
- a document bootstrap for extraction.
A "± SD over folds" describes how much the folds vary, not confidence.
Summary¶
Research question. Does automated ACH with mandatory disconfirming-evidence weighting give better-calibrated, less overconfident attribution than naive TTP-similarity matching, especially under false flags?
Mostly no. ACH's remaining advantage is an inspectable matrix with span-anchored evidence. That is a design property and is not measured here.
- Calibration. Under one setting-agnostic calibration map, ACH's pooled Brier score is 0.214 [0.202–0.226] and the marker-trusting matcher's is 0.160 [0.141–0.178]. The paired difference is +0.031 to +0.080, for an equal mix of closed, open and planted-marker incidents. ACH is better in the closed world (paired −0.178 to −0.075) and worse in the open and planted-marker settings.
- Overconfidence is a property of the raw outputs. Under planted markers, ACH's capped grades are never wrong at stated p ≥ 0.8 (0/274), while the matcher's softmax is wrong at that level 194/274 times. After one recalibration, neither method makes such errors: 1/822 for ACH, 0/822 for the matcher.
- Planted markers. Resistance does not depend on ACH's false-flag machinery. ACH is still framed only 2.6% of the time without its false-flag hypotheses, spoofable discount and caps. It comes from hard evidence contradicting the framed group, which both ranking rules honour. Any baseline that ignores spoofable rows is never framed, and a consistency gate is correct more often.
- Mimicry. ACH is framed less often than IDF coverage (27.0% vs 32.1%; paired −8.6 to −1.8 points) but is correct less often (17.9% vs 28.8%; paired −16.0 to −6.6 points).
- Abstention. A cosine-threshold baseline matched to ACH's closed-world accuracy declines 93.4% of the time on untracked actors, against ACH's 46.0%. Matched to ACH's decline rate, it is right 47.4% of the time in the closed world, against 29.9%.
- Costs. ACH loses closed-world accuracy, and it calls genuine marker overlap a frame-up 18.6% of the time.
1. False flags: baselines, controls and ablations¶
Methodology: the 274 leave-one-report-out incidents of section 2, in five settings:
- closed;
- open;
- false_flag: 3 spoofable markers frame a random other group;
- authentic: the same markers point at the true group, a control for false alarms;
- mimicry: the framed group's 3 rarest techniques or software are added as hard evidence.
Baselines include:
- the shipped marker-trusting similarity (marker boost 0.15) and a sweep of the boost;
- similarity that ignores spoofable rows;
- IDF coverage;
- an abstaining similarity at three cross-fitted operating points (best closed + open accuracy, ACH's decline rate, ACH's closed-world accuracy);
- a consistency gate.
Thresholds are cross-fitted over a 2-fold group split. Each ACH mechanism is removed one at a time. Calibration uses one map fitted on closed, open and false-flag incidents together, cross-fitted by group.
274 held-out incidents from 103 groups, 3 markers per incident. Brackets: group-cluster bootstrap 95% CI (1000 resamples); † = the rate is exactly 0 or 1, so the bootstrap interval is degenerate and a Wilson interval with the number of groups as the sample size is shown. Cross-fitted parameters (per group fold): tau = {0: 0.38, 1: 0.36}, k = {0: 15, 1: 10}; tau matched to ACH's open-world decline = {0: 0.25, 1: 0.255}, to ACH's closed-world accuracy = {0: 0.35, 1: 0.37}.
Source: GitHub Actions run 37093689060, commit 09cde53, 2026-10-03T03:35:51+00:00.
Planted markers (false_flag) vs authentic markers (control) vs TTP mimicry¶
| Method | False flag: correct | False flag: names framed | Authentic: names true | Authentic: calls it a frame | Mimicry: correct | Mimicry: names framed |
|---|---|---|---|---|---|---|
| TTP-similarity, marker boost 0.15 (shipped baseline) | 0.113 [0.06-0.17] | 0.880 [0.82-0.93] | 1.000 [0.96-1.00]† | 0.000 [0.00-0.04]† | 0.212 [0.16-0.27] | 0.763 [0.71-0.82] |
| TTP-similarity, marker boost 0.05 | 0.496 [0.42-0.57] | 0.124 [0.09-0.17] | 0.854 [0.80-0.90] | 0.000 [0.00-0.04]† | 0.212 [0.16-0.27] | 0.763 [0.71-0.82] |
| TTP-similarity, marker boost 0.3 | 0.000 [0.00-0.04]† | 1.000 [0.96-1.00]† | 1.000 [0.96-1.00]† | 0.000 [0.00-0.04]† | 0.212 [0.16-0.27] | 0.763 [0.71-0.82] |
| TTP-similarity, ignores spoofable rows | 0.533 [0.45-0.61] | 0.000 [0.00-0.04]† | 0.533 [0.45-0.61] | 0.000 [0.00-0.04]† | 0.212 [0.16-0.27] | 0.763 [0.71-0.82] |
| IDF coverage, ignores spoofable rows | 0.420 [0.35-0.49] | 0.000 [0.00-0.04]† | 0.420 [0.35-0.49] | 0.000 [0.00-0.04]† | 0.288 [0.22-0.36] | 0.321 [0.25-0.39] |
| Similarity + decline if cosine < tau (tau cross-fitted) | 0.266 [0.20-0.34] | 0.000 [0.00-0.04]† | 0.266 [0.20-0.34] | 0.000 [0.00-0.04]† | 0.109 [0.07-0.15] | 0.445 [0.38-0.50] |
| Similarity + decline, tau matched to ACH's open-world decline rate | 0.474 [0.40-0.55] | 0.000 [0.00-0.04]† | 0.474 [0.40-0.55] | 0.000 [0.00-0.04]† | 0.201 [0.14-0.25] | 0.715 [0.66-0.77] |
| Similarity + decline, tau matched to ACH's closed-world accuracy | 0.299 [0.23-0.37] | 0.000 [0.00-0.04]† | 0.299 [0.23-0.37] | 0.000 [0.00-0.04]† | 0.128 [0.08-0.17] | 0.460 [0.40-0.52] |
| Similarity + consistency gate (k cross-fitted) | 0.920 [0.88-0.95] | 0.080 [0.05-0.12] | 0.770 [0.71-0.83] | 0.230 [0.17-0.29] | 0.212 [0.16-0.27] | 0.763 [0.71-0.82] |
| OCCAM ACH (full) | 0.734 [0.67-0.80] | 0.011 [0.00-0.03] | 0.358 [0.29-0.43] | 0.186 [0.14-0.24] | 0.179 [0.12-0.24] | 0.270 [0.21-0.33] |
Closed and open world (abstention) and setting-agnostic calibration¶
| Method | Closed: correct | Closed: declines | Open: correct decline | Brier, pooled map | Brier, ACH minus method (paired) | Overconfident errors, pooled map |
|---|---|---|---|---|---|---|
| TTP-similarity, marker boost 0.15 (shipped baseline) | 0.533 [0.45-0.61] | 0.000 [0.00-0.04]† | 0.000 [0.00-0.04]† | 0.160 [0.14-0.18] | [+0.031, +0.080] | 0/822 [0.000-0.036] |
| TTP-similarity, marker boost 0.05 | 0.533 [0.45-0.61] | 0.000 [0.00-0.04]† | 0.000 [0.00-0.04]† | 0.144 [0.12-0.16] | [+0.048, +0.095] | 7/822 [0.000-0.020] |
| TTP-similarity, marker boost 0.3 | 0.533 [0.45-0.61] | 0.000 [0.00-0.04]† | 0.000 [0.00-0.04]† | 0.124 [0.11-0.14] | [+0.068, +0.111] | 0/822 [0.000-0.036] |
| TTP-similarity, ignores spoofable rows | 0.533 [0.45-0.61] | 0.000 [0.00-0.04]† | 0.000 [0.00-0.04]† | 0.151 [0.13-0.17] | [+0.039, +0.087] | 7/822 [0.000-0.020] |
| IDF coverage, ignores spoofable rows | 0.420 [0.35-0.49] | 0.000 [0.00-0.04]† | 0.000 [0.00-0.04]† | 0.167 [0.14-0.19] | [+0.021, +0.074] | 0/822 [0.000-0.036] |
| Similarity + decline if cosine < tau (tau cross-fitted) | 0.266 [0.20-0.34] | 0.690 [0.62-0.76] | 0.945 [0.92-0.97] | 0.214 [0.20-0.23] | [-0.016, +0.017] | 10/822 [0.001-0.026] |
| Similarity + decline, tau matched to ACH's open-world decline rate | 0.474 [0.40-0.55] | 0.230 [0.18-0.28] | 0.478 [0.42-0.54] | 0.208 [0.19-0.22] | [-0.014, +0.027] | 10/822 [0.001-0.026] |
| Similarity + decline, tau matched to ACH's closed-world accuracy | 0.299 [0.23-0.37] | 0.639 [0.56-0.71] | 0.934 [0.91-0.96] | 0.214 [0.20-0.23] | [-0.016, +0.017] | 10/822 [0.001-0.026] |
| Similarity + consistency gate (k cross-fitted) | 0.533 [0.45-0.61] | 0.000 [0.00-0.04]† | 0.000 [0.00-0.04]† | 0.204 [0.19-0.22] | [-0.005, +0.027] | 15/822 [0.007-0.031] |
| OCCAM ACH (full) | 0.299 [0.23-0.37] | 0.365 [0.30-0.43] | 0.460 [0.39-0.53] | 0.214 [0.20-0.23] | 1/822 [0.000-0.004] |
Brier score under the pooled map, per setting: ACH minus method (paired 95% CI)¶
| Method | Closed | Open | False flag | All three pooled |
|---|---|---|---|---|
| OCCAM ACH (full), value | 0.189 | 0.249 | 0.205 | 0.214 |
| TTP-similarity, marker boost 0.15 (shipped baseline) (0.315 / 0.032 / 0.131 / 0.160) | [-0.178, -0.075] | [+0.197, +0.238] | [+0.045, +0.104] | [+0.031, +0.080] |
| TTP-similarity, marker boost 0.05 (0.190 / 0.070 / 0.171 / 0.144) | [-0.040, +0.034] | [+0.156, +0.203] | [-0.004, +0.074] | [+0.048, +0.095] |
| TTP-similarity, marker boost 0.3 (0.315 / 0.042 / 0.016 / 0.124) | [-0.180, -0.072] | [+0.186, +0.229] | [+0.169, +0.211] | [+0.068, +0.111] |
| TTP-similarity, ignores spoofable rows (0.189 / 0.076 / 0.189 / 0.151) | [-0.037, +0.035] | [+0.150, +0.198] | [-0.024, +0.056] | [+0.039, +0.087] |
| IDF coverage, ignores spoofable rows (0.206 / 0.088 / 0.206 / 0.167) | [-0.054, +0.016] | [+0.139, +0.184] | [-0.039, +0.040] | [+0.021, +0.074] |
| Similarity + decline if cosine < tau (tau cross-fitted) (0.165 / 0.313 / 0.165 / 0.214) | [+0.000, +0.046] | [-0.088, -0.040] | [+0.011, +0.071] | [-0.016, +0.017] |
| Similarity + decline, tau matched to ACH's open-world decline rate (0.181 / 0.262 / 0.181 / 0.208) | [-0.020, +0.034] | [-0.040, +0.015] | [-0.005, +0.057] | [-0.014, +0.027] |
| Similarity + decline, tau matched to ACH's closed-world accuracy (0.170 / 0.302 / 0.170 / 0.214) | [-0.005, +0.040] | [-0.077, -0.030] | [+0.006, +0.067] | [-0.016, +0.017] |
| Similarity + consistency gate (k cross-fitted) (0.176 / 0.156 / 0.280 / 0.204) | [-0.011, +0.037] | [+0.070, +0.116] | [-0.112, -0.041] | [-0.005, +0.027] |
Ablations of ACH (each row removes one mechanism)¶
| Variant | Closed correct | Open correct | False flag correct | names framed | names true | Authentic: names true | Authentic: calls frame | Mimicry: correct | Mimicry: names framed | Brier, pooled map | Brier, variant minus full ACH (paired) | Overconfident errors, pooled map |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| OCCAM ACH (full) | 0.299 | 0.460 | 0.734 | 0.011 | 0.022 | 0.358 | 0.186 | 0.179 | 0.270 | 0.214 | 1/822 | |
| ACH without false-flag hypotheses | 0.299 | 0.460 | 0.201 | 0.011 | 0.201 | 0.369 | 0.000 | 0.179 | 0.270 | 0.207 | [-0.023, +0.011] | 1/822 |
| ACH without unknown-actor hypothesis | 0.372 | 0.000 | 0.945 | 0.033 | 0.026 | 0.518 | 0.372 | 0.212 | 0.307 | 0.181 | [-0.048, -0.020] | 27/822 |
| ACH without spoofable x0.5 discount | 0.299 | 0.460 | 0.726 | 0.011 | 0.004 | 0.372 | 0.193 | 0.179 | 0.270 | 0.231 | [+0.010, +0.023] | 1/822 |
| ACH without confidence caps | 0.299 | 0.460 | 0.734 | 0.011 | 0.022 | 0.358 | 0.186 | 0.179 | 0.270 | 0.207 | [-0.010, -0.005] | 2/822 |
| ACH without diagnosticity weighting | 0.237 | 0.723 | 0.631 | 0.004 | 0.022 | 0.263 | 0.117 | 0.168 | 0.182 | 0.234 | [+0.010, +0.029] | 0/822 |
| ACH without FF hypotheses, discount and caps | 0.299 | 0.460 | 0.131 | 0.026 | 0.131 | 0.401 | 0.000 | 0.179 | 0.270 | 0.207 | [-0.026, +0.011] | 0/822 |
| ACH, support-aware ranking | 0.401 | 0.055 | 0.887 | 0.015 | 0.131 | 0.507 | 0.255 | 0.245 | 0.354 | 0.203 | [-0.028, +0.005] | 5/822 |
| ACH, top-5 similarity shortlist | 0.339 | 0.642 | 0.679 | 0.011 | 0.018 | 0.350 | 0.153 | 0.230 | 0.292 | 0.234 | [+0.010, +0.030] | 4/822 |
Paired difference ACH minus method, 95% cluster-bootstrap interval¶
| Method | Closed: correct | Open: correct decline | False flag: correct | False flag: names framed | Authentic: names true | Authentic: calls it a frame | Mimicry: correct | Mimicry: names framed |
|---|---|---|---|---|---|---|---|---|
| TTP-similarity, marker boost 0.15 (shipped baseline) | [-0.317, -0.152] | [+0.393, +0.531] | [+0.534, +0.700] | [-0.921, -0.811] | [-0.712, -0.574] | [+0.138, +0.244] | [-0.101, +0.035] | [-0.560, -0.422] |
| TTP-similarity, marker boost 0.05 | [-0.317, -0.152] | [+0.393, +0.531] | [+0.147, +0.327] | [-0.160, -0.073] | [-0.573, -0.428] | [+0.138, +0.244] | [-0.101, +0.035] | [-0.560, -0.422] |
| TTP-similarity, marker boost 0.3 | [-0.317, -0.152] | [+0.393, +0.531] | [+0.669, +0.796] | [-1.000, -0.974] | [-0.712, -0.574] | [+0.138, +0.244] | [-0.101, +0.035] | [-0.560, -0.422] |
| TTP-similarity, ignores spoofable rows | [-0.317, -0.152] | [+0.393, +0.531] | [+0.111, +0.289] | [+0.000, +0.026] | [-0.260, -0.098] | [+0.138, +0.244] | [-0.101, +0.035] | [-0.560, -0.422] |
| IDF coverage, ignores spoofable rows | [-0.161, -0.082] | [+0.393, +0.531] | [+0.241, +0.396] | [+0.000, +0.026] | [-0.109, -0.011] | [+0.138, +0.244] | [-0.160, -0.066] | [-0.086, -0.018] |
| Similarity + decline if cosine < tau (tau cross-fitted) | [-0.053, +0.119] | [-0.560, -0.410] | [+0.371, +0.556] | [+0.000, +0.026] | [+0.004, +0.178] | [+0.138, +0.244] | [+0.008, +0.131] | [-0.252, -0.102] |
| Similarity + decline, tau matched to ACH's open-world decline rate | [-0.254, -0.100] | [-0.114, +0.073] | [+0.174, +0.352] | [+0.000, +0.026] | [-0.193, -0.044] | [+0.138, +0.244] | [-0.088, +0.046] | [-0.518, -0.372] |
| Similarity + decline, tau matched to ACH's closed-world accuracy | [-0.083, +0.080] | [-0.548, -0.396] | [+0.337, +0.525] | [+0.000, +0.026] | [-0.026, +0.140] | [+0.138, +0.244] | [-0.014, +0.116] | [-0.264, -0.120] |
| Similarity + consistency gate (k cross-fitted) | [-0.317, -0.152] | [+0.393, +0.531] | [-0.256, -0.122] | [-0.107, -0.037] | [-0.485, -0.342] | [-0.118, +0.025] | [-0.101, +0.035] | [-0.560, -0.422] |
Number of planted / mimicked items¶
| n | Method | False flag: names framed | Authentic: names true | Authentic: calls frame | Mimicry: names framed |
|---|---|---|---|---|---|
| 1 | TTP-similarity, marker boost 0.15 (shipped baseline) | 0.124 | 0.854 | 0.000 | 0.208 |
| 1 | TTP-similarity, ignores spoofable rows | 0.000 | 0.533 | 0.000 | 0.208 |
| 1 | IDF coverage, ignores spoofable rows | 0.000 | 0.420 | 0.000 | 0.036 |
| 1 | OCCAM ACH (full) | 0.007 | 0.321 | 0.153 | 0.066 |
| 1 | ACH without false-flag hypotheses | 0.007 | 0.336 | 0.000 | 0.066 |
| 1 | Similarity + consistency gate (k cross-fitted) | 0.080 | 0.770 | 0.230 | 0.208 |
| 6 | TTP-similarity, marker boost 0.15 (shipped baseline) | 1.000 | 1.000 | 0.000 | 0.909 |
| 6 | TTP-similarity, ignores spoofable rows | 0.000 | 0.533 | 0.000 | 0.909 |
| 6 | IDF coverage, ignores spoofable rows | 0.000 | 0.420 | 0.000 | 0.613 |
| 6 | OCCAM ACH (full) | 0.011 | 0.372 | 0.193 | 0.442 |
| 6 | ACH without false-flag hypotheses | 0.026 | 0.401 | 0.000 | 0.442 |
| 6 | Similarity + consistency gate (k cross-fitted) | 0.080 | 0.770 | 0.230 | 0.909 |
Correct: closed/authentic = names the true group; open = declines; false_flag/mimicry = names the true group or concludes the framed group was framed. Calls it a frame under authentic markers is a false alarm. Mimicry items are ordinary technique/software rows, so the gate cannot see them (it equals plain similarity there). Pooled map: one probability map (histogram binning; the learned grade map for ACH variants) fitted on closed + open + false_flag together and cross-fitted by group, i.e. without knowing which setting an incident comes from; Brier and overconfident errors (wrong with calibrated probability >= 0.8) are over all three settings (822 answers). Per setting it gives similarity 0.315 / 0.032 / 0.131 and ACH 0.189 / 0.249 / 0.205 (closed / open / false_flag), the same map as the pooled table in results/attribution.md. The pooled number depends on the equal 1:1:1 mix of settings. Raw (uncalibrated) overconfident errors under planted markers: similarity 194/274, ACH 0/274. Brier rewards a matcher that is always framed but states low probabilities: marker boost 0.3 is framed 1.000 of the time under planted markers, yet has the lowest pooled Brier (0.124; false flag 0.016), so Brier alone does not measure resistance to framing.

ACH's operating point lies inside the frontier of the cosine-threshold baseline (red curve). The curve is in-sample. The marked baseline points are cross-fitted.
2. Sensitivity to the hand-set cell-rule thresholds¶
Methodology: the same incidents, markers and framing seed. Plain ACH is rerun with the "common technique" cut-off at 20%, 30% or 40% of groups and the "rare" cut-off at 2, 3 or 5 groups. A cross-fitted row picks the thresholds on one group fold and applies them to the other.
274 held-out incidents from 103 groups, 3 markers. cX-rY: a technique used by at least X of all groups is non-diagnostic, one used by at most Y groups is rare (CC on a match). Shipped: c0.3-r3 (hand-set). Cross-fitted: each group fold uses the configuration with the best mean accuracy over the five settings on the other fold (chosen: {0: 'c0.2-r5', 1: 'c0.2-r3'}).
Source: GitHub Actions run 37093689060, commit 09cde53, 2026-10-03T03:35:51+00:00.
| Thresholds | Closed correct | Open correct decline | False flag correct | False flag names framed | Authentic: calls it a frame | Mimicry correct | Mimicry names framed |
|---|---|---|---|---|---|---|---|
| c0.2-r2 | 0.310 | 0.591 | 0.701 | 0.007 | 0.212 | 0.168 | 0.354 |
| c0.2-r3 | 0.332 | 0.489 | 0.752 | 0.007 | 0.245 | 0.186 | 0.350 |
| c0.2-r5 | 0.354 | 0.391 | 0.803 | 0.011 | 0.274 | 0.201 | 0.361 |
| c0.3-r2 | 0.285 | 0.577 | 0.668 | 0.007 | 0.157 | 0.164 | 0.274 |
| c0.3-r3 (shipped) | 0.299 | 0.460 | 0.734 | 0.011 | 0.186 | 0.179 | 0.270 |
| c0.3-r5 | 0.325 | 0.369 | 0.766 | 0.011 | 0.230 | 0.201 | 0.292 |
| c0.4-r2 | 0.270 | 0.588 | 0.642 | 0.007 | 0.150 | 0.172 | 0.248 |
| c0.4-r3 | 0.285 | 0.482 | 0.708 | 0.007 | 0.172 | 0.186 | 0.248 |
| c0.4-r5 | 0.310 | 0.380 | 0.759 | 0.007 | 0.215 | 0.212 | 0.263 |
| cross-fitted choice | 0.339 | 0.420 | 0.774 | 0.011 | 0.259 | 0.190 | 0.358 |
Paired difference, configuration minus shipped (95% cluster-bootstrap interval):
| Thresholds | Closed correct | Open correct decline | False flag correct | False flag names framed | Authentic: calls it a frame | Mimicry correct | Mimicry names framed |
|---|---|---|---|---|---|---|---|
| c0.2-r2 | [-0.019, +0.040] | [+0.084, +0.183] | [-0.073, +0.007] | [-0.018, +0.007] | [-0.004, +0.057] | [-0.043, +0.019] | [+0.050, +0.120] |
| c0.2-r3 | [+0.011, +0.058] | [-0.004, +0.065] | [-0.007, +0.042] | [-0.018, +0.007] | [+0.030, +0.088] | [-0.022, +0.037] | [+0.046, +0.116] |
| c0.2-r5 | [+0.026, +0.085] | [-0.117, -0.022] | [+0.033, +0.111] | [-0.011, +0.011] | [+0.055, +0.122] | [-0.007, +0.055] | [+0.055, +0.130] |
| c0.3-r2 | [-0.036, +0.000] | [+0.077, +0.159] | [-0.098, -0.036] | [-0.011, +0.000] | [-0.051, -0.011] | [-0.030, -0.004] | [-0.007, +0.018] |
| c0.3-r5 | [+0.004, +0.052] | [-0.133, -0.056] | [+0.007, +0.067] | [+0.000, +0.000] | [+0.022, +0.068] | [+0.004, +0.041] | [+0.004, +0.043] |
| c0.4-r2 | [-0.054, -0.007] | [+0.084, +0.175] | [-0.130, -0.053] | [-0.011, +0.000] | [-0.059, -0.015] | [-0.026, +0.008] | [-0.043, -0.004] |
| c0.4-r3 | [-0.029, -0.004] | [+0.007, +0.044] | [-0.048, -0.007] | [-0.011, +0.000] | [-0.030, -0.004] | [+0.000, +0.019] | [-0.040, -0.007] |
| c0.4-r5 | [-0.015, +0.040] | [-0.124, -0.041] | [+0.000, +0.059] | [-0.011, +0.000] | [+0.004, +0.055] | [+0.011, +0.059] | [-0.030, +0.015] |
| cross-fitted choice | [+0.015, +0.069] | [-0.091, +0.007] | [+0.015, +0.068] | [-0.011, +0.011] | [+0.042, +0.108] | [-0.018, +0.041] | [+0.053, +0.124] |
3. Attribution on held-out ATT&CK group usage¶
Methodology (ADR 0004): every (group, cited report) pair with at least 4 techniques/software becomes an incident (at most 3 per group); items supported only by that report are removed from the group's profile first. closed: the true group is a candidate; open: it is removed and the right answer is to decline; false_flag: 3 spoofable markers frame a random other group.
274 leave-one-report-out incidents from 103 groups; 176 candidate group profiles; 3 planted markers in the false-flag setting. 95% CIs: group-cluster bootstrap; † = the rate is exactly 0 or 1, so the bootstrap interval is degenerate and a Wilson interval with the number of groups as the sample size is shown.
Source: GitHub Actions run 37093689060, commit 09cde53, 2026-10-03T03:35:50+00:00.
| Setting | Method | Correct [95% CI] | Names true group | Framed | Brier [95% CI] | ECE | Overconfident errors [95% CI] | Brier (recal.) | ECE (recal.) |
|---|---|---|---|---|---|---|---|---|---|
| closed | TTP-similarity baseline | 0.533 [0.45-0.61] | 0.533 | 0.000 | 0.179 [0.15-0.21] | 0.102 | 0.011 [0.00-0.03] | 0.180 | 0.068 |
| closed | OCCAM ACH | 0.299 [0.23-0.37] | 0.299 | 0.000 | 0.238 [0.22-0.25] | 0.257 | 0.000 [0.00-0.04]† | 0.156 | 0.007 |
| closed | OCCAM ACH, top-5 similarity shortlist | 0.339 [0.27-0.41] | 0.339 | 0.000 | 0.264 [0.24-0.28] | 0.253 | 0.007 [0.00-0.02] | 0.201 | 0.075 |
| closed | OCCAM ACH, support-aware ranking | 0.401 [0.32-0.47] | 0.401 | 0.000 | 0.223 [0.20-0.24] | 0.240 | 0.004 [0.00-0.01] | 0.161 | 0.071 |
| open | TTP-similarity baseline | 0.000 [0.00-0.04]† | 0.000 | 0.000 | 0.077 [0.06-0.09] | 0.206 | 0.015 [0.00-0.03] | 0.000 | 0.000 |
| open | OCCAM ACH | 0.460 [0.39-0.53] | 0.000 | 0.000 | 0.248 [0.24-0.26] | 0.071 | 0.004 [0.00-0.01] | 0.245 | 0.064 |
| open | OCCAM ACH, top-5 similarity shortlist | 0.642 [0.58-0.70] | 0.000 | 0.000 | 0.234 [0.22-0.25] | 0.086 | 0.011 [0.00-0.02] | 0.222 | 0.036 |
| open | OCCAM ACH, support-aware ranking | 0.055 [0.03-0.09] | 0.000 | 0.000 | 0.303 [0.29-0.32] | 0.493 | 0.015 [0.00-0.03] | 0.052 | 0.001 |
| false_flag | TTP-similarity baseline | 0.113 [0.06-0.17] | 0.113 | 0.880 | 0.700 [0.65-0.75] | 0.755 | 0.708 [0.64-0.78] | 0.100 | 0.054 |
| false_flag | OCCAM ACH | 0.734 [0.67-0.80] | 0.022 | 0.011 | 0.199 [0.19-0.21] | 0.144 | 0.000 [0.00-0.04]† | 0.176 | 0.023 |
| false_flag | OCCAM ACH, top-5 similarity shortlist | 0.679 [0.61-0.75] | 0.018 | 0.011 | 0.206 [0.19-0.22] | 0.096 | 0.000 [0.00-0.04]† | 0.192 | 0.032 |
| false_flag | OCCAM ACH, support-aware ranking | 0.887 [0.85-0.92] | 0.131 | 0.015 | 0.170 [0.16-0.18] | 0.285 | 0.000 [0.00-0.04]† | 0.094 | 0.054 |
Correct: closed = names the true group; open = declines to name (true group absent); false_flag = names the true group or concludes the framed group was framed. Overconfident errors: wrong with stated probability >= 0.8. recal.: histogram binning cross-fitted over a 2-fold group split.
Setting-agnostic calibration (the realistic number)¶
One map fitted on closed + open + false_flag together, cross-fitted by group. The per-setting maps further below know which setting an incident comes from and are an oracle upper bound. Canonical pooled-calibration table; results/falseflag.md quotes the same map as one number over all three settings. A single map cannot help the similarity baseline: it never declines, so all its open-world answers are wrong and the map pulls every probability down. The closed-world Brier therefore rises from 0.179 (raw) to 0.315, while the open-world Brier falls to 0.032. Overconfident errors: wrong with calibrated probability >= 0.8.
| Setting | Method | Brier (pooled map) | ECE (pooled map) | Overconfident errors (pooled map) [95% CI] |
|---|---|---|---|---|
| closed | TTP-similarity baseline | 0.315 | 0.321 | 0/274 [0.000-0.036]† |
| closed | OCCAM ACH | 0.189 | 0.174 | 0/274 [0.000-0.036]† |
| closed | OCCAM ACH, top-5 similarity shortlist | 0.253 | 0.217 | 2/274 [0.000-0.018] |
| closed | OCCAM ACH, support-aware ranking | 0.171 | 0.108 | 1/274 [0.000-0.011] |
| open | TTP-similarity baseline | 0.032 | 0.139 | 0/274 [0.000-0.036]† |
| open | OCCAM ACH | 0.249 | 0.067 | 1/274 [0.000-0.011] |
| open | OCCAM ACH, top-5 similarity shortlist | 0.240 | 0.110 | 2/274 [0.000-0.018] |
| open | OCCAM ACH, support-aware ranking | 0.186 | 0.316 | 4/274 [0.004-0.029] |
| false_flag | TTP-similarity baseline | 0.131 | 0.178 | 0/274 [0.000-0.036]† |
| false_flag | OCCAM ACH | 0.205 | 0.158 | 0/274 [0.000-0.036]† |
| false_flag | OCCAM ACH, top-5 similarity shortlist | 0.209 | 0.123 | 0/274 [0.000-0.036]† |
| false_flag | OCCAM ACH, support-aware ranking | 0.252 | 0.394 | 0/274 [0.000-0.036]† |
Learned grade -> probability map, fitted per setting (oracle upper bound)¶
Stated probability per ACH grade, learned (Beta-smoothed, monotone) instead of fixed ICD-203 midpoints. Brier with the learned map is cross-fitted over a 2-fold group split.
| Setting | Method | Brier (ICD-203 midpoints) | Brier (learned grades) | ECE (learned grades) |
|---|---|---|---|---|
| closed | OCCAM ACH | 0.238 | 0.156 | 0.012 |
| closed | OCCAM ACH, top-5 similarity shortlist | 0.264 | 0.201 | 0.073 |
| closed | OCCAM ACH, support-aware ranking | 0.223 | 0.161 | 0.072 |
| open | OCCAM ACH | 0.248 | 0.245 | 0.064 |
| open | OCCAM ACH, top-5 similarity shortlist | 0.234 | 0.227 | 0.038 |
| open | OCCAM ACH, support-aware ranking | 0.303 | 0.053 | 0.017 |
| false_flag | OCCAM ACH | 0.199 | 0.177 | 0.033 |
| false_flag | OCCAM ACH, top-5 similarity shortlist | 0.206 | 0.192 | 0.040 |
| false_flag | OCCAM ACH, support-aware ranking | 0.170 | 0.094 | 0.066 |
Shipped map (fit on all settings of plain ACH, results/grade_calibration.json): low = 0.370 (n=568), moderate = 0.777 (n=245), high = 0.818 (n=9)
False-flag setting across framing seeds [7, 11, 13, 17, 19]¶
| Method | Correct, mean ± sample SD | Names framed group, mean ± sample SD | Brier, mean ± sample SD |
|---|---|---|---|
| TTP-similarity baseline | 0.119 ± 0.009 | 0.875 ± 0.009 | 0.699 ± 0.006 |
| OCCAM ACH | 0.718 ± 0.017 | 0.008 ± 0.006 | 0.196 ± 0.004 |
| OCCAM ACH, top-5 similarity shortlist | 0.680 ± 0.014 | 0.008 ± 0.006 | 0.203 ± 0.003 |
| OCCAM ACH, support-aware ranking | 0.894 ± 0.009 | 0.020 ± 0.008 | 0.169 ± 0.003 |

4. Extended protocols: all reports, campaigns, unattributed, temporal¶
Methodology:
loro-alldrops the per-group cap, giving 575 incidents.campaignsholds out ATT&CK campaign objects with all their references.unattributedasks for a decline on campaigns ATT&CK does not attribute.unattributed-cleandrops those whose description names a tracked group.temporal-Xbuilds profiles from an older ATT&CK release and attributes activity added to ATT&CK after it, with renamed technique ids mapped back to the old release.
Source: GitHub Actions run 37093689060, commit 09cde53, 2026-10-03T03:35:44+00:00.
| Protocol | Incidents | Groups | Candidate profiles |
|---|---|---|---|
loro-all: Leave-one-report-out, every (group, report) pair, no per-group cap |
575 (0 campaigns) | 103 | 176 |
campaigns: ATT&CK campaigns attributed to a group, held out with all their references |
21 (21 campaigns) | 15 | 176 |
unattributed: ATT&CK campaigns with no attributed group (correct = decline) |
31 (31 campaigns) | 0 | 176 |
unattributed-clean: As unattributed, minus campaigns whose ATT&CK description names a tracked group |
21 (21 campaigns) | 0 | 176 |
temporal-12.1: Profiles from ATT&CK v12.1 (released Nov 2022); incidents added to ATT&CK after it (in v19.2) |
89 (15 campaigns) | 26 | 133 |
temporal-15.1: Profiles from ATT&CK v15.1 (released May 2024); incidents added to ATT&CK after it (in v19.2) |
69 (9 campaigns) | 22 | 148 |
unattributed: 10 campaigns name a tracked group in their own description (excluded in unattributed-clean).
temporal-12.1 id mapping: 1506 items, 1327 known to the old release, 17 mapped via revoked-by, 54 to the parent technique, 108 dropped (7.2%); 81 of 96 incidents had an unknown id; 89 kept (>= 4 items).
temporal-15.1 id mapping: 1133 items, 1009 known to the old release, 23 mapped via revoked-by, 22 to the parent technique, 79 dropped (7.0%); 61 of 77 incidents had an unknown id; 69 kept (>= 4 items).
| Protocol | Setting | Method | Correct [95% CI] | Names true group | Framed | Brier | Overconfident errors |
|---|---|---|---|---|---|---|---|
| loro-all | closed | TTP-similarity baseline | 0.478 [0.40-0.56] | 0.478 | 0.000 | 0.205 | 0.019 |
| loro-all | closed | OCCAM ACH | 0.390 [0.31-0.45] | 0.390 | 0.000 | 0.228 | 0.000 |
| loro-all | closed | OCCAM ACH, top-5 similarity shortlist | 0.402 [0.33-0.47] | 0.402 | 0.000 | 0.250 | 0.005 |
| loro-all | closed | OCCAM ACH, support-aware ranking | 0.503 [0.42-0.56] | 0.503 | 0.000 | 0.204 | 0.003 |
| loro-all | open | TTP-similarity baseline | 0.000 [0.00-0.04]† | 0.000 | 0.000 | 0.089 | 0.024 |
| loro-all | open | OCCAM ACH | 0.473 [0.42-0.53] | 0.000 | 0.000 | 0.247 | 0.002 |
| loro-all | open | OCCAM ACH, top-5 similarity shortlist | 0.642 [0.59-0.69] | 0.000 | 0.000 | 0.233 | 0.014 |
| loro-all | open | OCCAM ACH, support-aware ranking | 0.064 [0.04-0.09] | 0.000 | 0.000 | 0.314 | 0.028 |
| loro-all | false_flag | TTP-similarity baseline | 0.073 [0.04-0.11] | 0.073 | 0.918 | 0.736 | 0.736 |
| loro-all | false_flag | OCCAM ACH | 0.739 [0.69-0.79] | 0.023 | 0.009 | 0.195 | 0.000 |
| loro-all | false_flag | OCCAM ACH, top-5 similarity shortlist | 0.697 [0.64-0.75] | 0.021 | 0.009 | 0.203 | 0.000 |
| loro-all | false_flag | OCCAM ACH, support-aware ranking | 0.894 [0.86-0.92] | 0.160 | 0.028 | 0.176 | 0.000 |
| campaigns | closed | TTP-similarity baseline | 0.286 [0.14-0.50] | 0.286 | 0.000 | 0.155 | 0.000 |
| campaigns | closed | OCCAM ACH | 0.238 [0.11-0.45] | 0.238 | 0.000 | 0.253 | 0.000 |
| campaigns | closed | OCCAM ACH, top-5 similarity shortlist | 0.238 [0.11-0.45] | 0.238 | 0.000 | 0.269 | 0.000 |
| campaigns | closed | OCCAM ACH, support-aware ranking | 0.429 [0.24-0.63] | 0.429 | 0.000 | 0.217 | 0.000 |
| campaigns | open | TTP-similarity baseline | 0.000 [0.00-0.15] | 0.000 | 0.000 | 0.027 | 0.000 |
| campaigns | open | OCCAM ACH | 0.619 [0.41-0.79] | 0.000 | 0.000 | 0.243 | 0.000 |
| campaigns | open | OCCAM ACH, top-5 similarity shortlist | 0.857 [0.65-0.95] | 0.000 | 0.000 | 0.181 | 0.000 |
| campaigns | open | OCCAM ACH, support-aware ranking | 0.048 [0.01-0.23] | 0.000 | 0.000 | 0.253 | 0.000 |
| campaigns | false_flag | TTP-similarity baseline | 0.000 [0.00-0.15] | 0.000 | 1.000 | 0.873 | 1.000 |
| campaigns | false_flag | OCCAM ACH | 0.667 [0.45-0.83] | 0.000 | 0.000 | 0.205 | 0.000 |
| campaigns | false_flag | OCCAM ACH, top-5 similarity shortlist | 0.571 [0.37-0.76] | 0.000 | 0.000 | 0.205 | 0.000 |
| campaigns | false_flag | OCCAM ACH, support-aware ranking | 1.000 [0.85-1.00] | 0.143 | 0.000 | 0.147 | 0.000 |
| unattributed | open | TTP-similarity baseline | 0.000 [0.00-0.11] | 0.000 | 0.000 | 0.096 | 0.065 |
| unattributed | open | OCCAM ACH | 0.484 [0.32-0.65] | 0.000 | 0.000 | 0.259 | 0.000 |
| unattributed | open | OCCAM ACH, top-5 similarity shortlist | 0.742 [0.57-0.86] | 0.000 | 0.000 | 0.228 | 0.000 |
| unattributed | open | OCCAM ACH, support-aware ranking | 0.000 [0.00-0.11] | 0.000 | 0.000 | 0.306 | 0.032 |
| unattributed-clean | open | TTP-similarity baseline | 0.000 [0.00-0.15] | 0.000 | 0.000 | 0.070 | 0.048 |
| unattributed-clean | open | OCCAM ACH | 0.571 [0.37-0.76] | 0.000 | 0.000 | 0.260 | 0.000 |
| unattributed-clean | open | OCCAM ACH, top-5 similarity shortlist | 0.762 [0.55-0.89] | 0.000 | 0.000 | 0.215 | 0.000 |
| unattributed-clean | open | OCCAM ACH, support-aware ranking | 0.000 [0.00-0.15] | 0.000 | 0.000 | 0.299 | 0.000 |
| temporal-12.1 | closed | TTP-similarity baseline | 0.202 [0.10-0.35] | 0.202 | 0.000 | 0.162 | 0.034 |
| temporal-12.1 | closed | OCCAM ACH | 0.213 [0.11-0.36] | 0.213 | 0.000 | 0.262 | 0.000 |
| temporal-12.1 | closed | OCCAM ACH, top-5 similarity shortlist | 0.213 [0.10-0.37] | 0.213 | 0.000 | 0.312 | 0.000 |
| temporal-12.1 | closed | OCCAM ACH, support-aware ranking | 0.270 [0.16-0.43] | 0.270 | 0.000 | 0.269 | 0.022 |
| temporal-12.1 | open | TTP-similarity baseline | 0.000 [0.00-0.13]† | 0.000 | 0.000 | 0.099 | 0.034 |
| temporal-12.1 | open | OCCAM ACH | 0.506 [0.43-0.58] | 0.000 | 0.000 | 0.262 | 0.000 |
| temporal-12.1 | open | OCCAM ACH, top-5 similarity shortlist | 0.764 [0.68-0.84] | 0.000 | 0.000 | 0.205 | 0.000 |
| temporal-12.1 | open | OCCAM ACH, support-aware ranking | 0.011 [0.00-0.04] | 0.000 | 0.000 | 0.338 | 0.045 |
| temporal-12.1 | false_flag | TTP-similarity baseline | 0.034 [0.00-0.08] | 0.034 | 0.966 | 0.835 | 0.910 |
| temporal-12.1 | false_flag | OCCAM ACH | 0.708 [0.62-0.83] | 0.011 | 0.000 | 0.190 | 0.000 |
| temporal-12.1 | false_flag | OCCAM ACH, top-5 similarity shortlist | 0.629 [0.53-0.75] | 0.011 | 0.000 | 0.211 | 0.000 |
| temporal-12.1 | false_flag | OCCAM ACH, support-aware ranking | 0.899 [0.84-0.95] | 0.056 | 0.045 | 0.161 | 0.000 |
| temporal-15.1 | closed | TTP-similarity baseline | 0.116 [0.03-0.25] | 0.116 | 0.000 | 0.123 | 0.014 |
| temporal-15.1 | closed | OCCAM ACH | 0.130 [0.06-0.23] | 0.130 | 0.000 | 0.274 | 0.000 |
| temporal-15.1 | closed | OCCAM ACH, top-5 similarity shortlist | 0.116 [0.04-0.22] | 0.116 | 0.000 | 0.326 | 0.000 |
| temporal-15.1 | closed | OCCAM ACH, support-aware ranking | 0.159 [0.06-0.29] | 0.159 | 0.000 | 0.282 | 0.014 |
| temporal-15.1 | open | TTP-similarity baseline | 0.000 [0.00-0.15]† | 0.000 | 0.000 | 0.087 | 0.014 |
| temporal-15.1 | open | OCCAM ACH | 0.493 [0.31-0.60] | 0.000 | 0.000 | 0.237 | 0.000 |
| temporal-15.1 | open | OCCAM ACH, top-5 similarity shortlist | 0.725 [0.60-0.82] | 0.000 | 0.000 | 0.197 | 0.000 |
| temporal-15.1 | open | OCCAM ACH, support-aware ranking | 0.014 [0.00-0.05] | 0.000 | 0.000 | 0.299 | 0.014 |
| temporal-15.1 | false_flag | TTP-similarity baseline | 0.014 [0.00-0.05] | 0.014 | 0.986 | 0.832 | 0.870 |
| temporal-15.1 | false_flag | OCCAM ACH | 0.681 [0.57-0.86] | 0.000 | 0.000 | 0.199 | 0.000 |
| temporal-15.1 | false_flag | OCCAM ACH, top-5 similarity shortlist | 0.609 [0.49-0.79] | 0.000 | 0.000 | 0.213 | 0.000 |
| temporal-15.1 | false_flag | OCCAM ACH, support-aware ranking | 0.928 [0.88-0.98] | 0.014 | 0.000 | 0.147 | 0.000 |
False-flag setting over framing seeds (mean ± sample SD):
| Protocol | Method | Correct | Names framed group | Names true group |
|---|---|---|---|---|
| loro-all | TTP-similarity baseline | 0.077 ± 0.003 | 0.914 ± 0.004 | 0.077 ± 0.003 |
| loro-all | OCCAM ACH | 0.736 ± 0.015 | 0.011 ± 0.004 | 0.019 ± 0.005 |
| loro-all | OCCAM ACH, top-5 similarity shortlist | 0.699 ± 0.015 | 0.010 ± 0.004 | 0.016 ± 0.005 |
| loro-all | OCCAM ACH, support-aware ranking | 0.894 ± 0.001 | 0.024 ± 0.007 | 0.145 ± 0.015 |
| campaigns | TTP-similarity baseline | 0.000 ± 0.000 | 1.000 ± 0.000 | 0.000 ± 0.000 |
| campaigns | OCCAM ACH | 0.733 ± 0.054 | 0.010 ± 0.021 | 0.000 ± 0.000 |
| campaigns | OCCAM ACH, top-5 similarity shortlist | 0.667 ± 0.075 | 0.000 ± 0.000 | 0.000 ± 0.000 |
| campaigns | OCCAM ACH, support-aware ranking | 0.943 ± 0.062 | 0.019 ± 0.026 | 0.057 ± 0.062 |
| temporal-12.1 | TTP-similarity baseline | 0.025 ± 0.005 | 0.973 ± 0.006 | 0.025 ± 0.005 |
| temporal-12.1 | OCCAM ACH | 0.730 ± 0.018 | 0.007 ± 0.010 | 0.018 ± 0.006 |
| temporal-12.1 | OCCAM ACH, top-5 similarity shortlist | 0.645 ± 0.022 | 0.004 ± 0.010 | 0.018 ± 0.006 |
| temporal-12.1 | OCCAM ACH, support-aware ranking | 0.926 ± 0.023 | 0.016 ± 0.019 | 0.061 ± 0.013 |
| temporal-15.1 | TTP-similarity baseline | 0.003 ± 0.006 | 0.997 ± 0.006 | 0.003 ± 0.006 |
| temporal-15.1 | OCCAM ACH | 0.713 ± 0.024 | 0.000 ± 0.000 | 0.003 ± 0.006 |
| temporal-15.1 | OCCAM ACH, top-5 similarity shortlist | 0.617 ± 0.022 | 0.000 ± 0.000 | 0.003 ± 0.006 |
| temporal-15.1 | OCCAM ACH, support-aware ranking | 0.933 ± 0.039 | 0.012 ± 0.016 | 0.014 ± 0.010 |
Correct: closed = names the true group; open = declines to name; false_flag = names the true group or concludes the framed group was framed. Temporal protocols use the old release's profiles and KB and hold nothing out. Intervals: group-cluster bootstrap (n >= 50), else Wilson score interval. † = a rate of exactly 0 or 1 (e.g. the similarity baseline never declines), whose bootstrap interval is degenerate: a Wilson interval with the number of groups as the sample size is shown instead.
5. Reproduction of published TTP extraction results (rcATT)¶
Methodology: rcATT's own data (1,490 reports), its label filter, the released code's 5-fold split and its metrics (Legoy et al., 2020, arXiv:2004.14322v1; paper numbers from Table 4, "Inde." rows). Four rcATT pipelines form a 2 x 2 ablation of the two settings in which the released code differs from the paper text: tokenisation, and balanced class weights. OCCAM's classifier family runs on the same folds.
1490 reports, 5-fold CV (KFold, shuffle, seed 42, as in the released code), binary relevance; mean ± SD over folds (dispersion of the 5 folds, not a confidence interval), percent.
Source: GitHub Actions run 37093689060, commit 09cde53, 2026-10-03T03:35:46+00:00.
Tactics (12 labels)¶
| Source | Micro P | Micro R | Micro F0.5 | Macro P | Macro R | Macro F0.5 |
|---|---|---|---|---|---|---|
| rcATT paper (Legoy et al. 2020, arXiv:2004.14322v1, Table 4 'Inde.' rows; same means in Tables 2-3, TF-IDF BR Linear SVC) | 65.64 ± 3.76 | 64.69 ± 3.00 | 65.38 ± 2.87 | 60.26 ± 3.20 | 58.50 ± 3.68 | 59.47 ± 2.29 |
| Reproduction, released rcATT pipeline (stemmed/lemmatised tokens, balanced class weights) | 64.97 ± 3.88 | 64.31 ± 2.32 | 64.82 ± 3.44 | 61.52 ± 4.93 | 58.00 ± 2.86 | 60.32 ± 4.23 |
| Released pipeline without class weighting | 71.37 ± 4.41 | 52.28 ± 2.10 | 66.50 ± 3.58 | 68.66 ± 6.72 | 42.29 ± 2.10 | 57.45 ± 4.48 |
| Pipeline as the paper text describes it (default tokens, no class weighting) | 71.81 ± 3.77 | 52.00 ± 2.14 | 66.71 ± 3.17 | 69.25 ± 6.11 | 41.82 ± 2.05 | 57.46 ± 3.98 |
| Paper-text pipeline with balanced class weights | 65.20 ± 4.24 | 63.93 ± 1.91 | 64.93 ± 3.71 | 61.47 ± 5.11 | 57.38 ± 2.52 | 60.14 ± 4.36 |
| OCCAM TF-IDF + LR, same folds | 61.07 ± 3.51 | 65.41 ± 3.14 | 61.88 ± 3.29 | 57.65 ± 5.15 | 59.50 ± 4.55 | 57.39 ± 5.03 |
| Effect on micro F0.5 (same 5 folds) | Fold-paired difference, mean ± SD |
|---|---|
| class weighting, released tokens | -1.67 ± 0.87 |
| class weighting, paper-text tokens | -1.79 ± 0.92 |
| rcATT tokenisation, balanced weights | -0.10 ± 0.46 |
| rcATT tokenisation, no weighting | -0.22 ± 0.78 |
| both (released minus paper text) | -1.89 ± 0.60 |
Techniques (197 labels)¶
| Source | Micro P | Micro R | Micro F0.5 | Macro P | Macro R | Macro F0.5 |
|---|---|---|---|---|---|---|
| rcATT paper (Legoy et al. 2020, arXiv:2004.14322v1, Table 4 'Inde.' rows; same means in Tables 2-3, TF-IDF BR Linear SVC) | 37.18 ± 6.75 | 29.79 ± 5.91 | 35.02 ± 5.32 | 28.84 ± 6.90 | 22.67 ± 5.94 | 25.06 ± 6.09 |
| Reproduction, released rcATT pipeline (stemmed/lemmatised tokens, balanced class weights) | 39.32 ± 2.02 | 27.95 ± 1.54 | 36.33 ± 1.39 | 33.99 ± 2.15 | 22.97 ± 1.48 | 28.86 ± 1.62 |
| Released pipeline without class weighting | 59.69 ± 2.38 | 10.33 ± 0.90 | 30.44 ± 1.48 | 22.27 ± 0.95 | 8.21 ± 0.96 | 14.66 ± 1.07 |
| Pipeline as the paper text describes it (default tokens, no class weighting) | 59.40 ± 3.09 | 9.70 ± 0.98 | 29.28 ± 2.05 | 20.91 ± 1.30 | 7.62 ± 1.00 | 13.64 ± 1.27 |
| Paper-text pipeline with balanced class weights | 39.86 ± 2.52 | 26.14 ± 1.22 | 36.06 ± 1.93 | 33.38 ± 1.58 | 21.40 ± 1.30 | 27.84 ± 1.34 |
| OCCAM TF-IDF + LR, same folds | 37.76 ± 2.12 | 24.81 ± 1.98 | 34.18 ± 2.10 | 15.93 ± 2.84 | 10.33 ± 1.13 | 12.94 ± 2.10 |
| Effect on micro F0.5 (same 5 folds) | Fold-paired difference, mean ± SD |
|---|---|
| class weighting, released tokens | +5.88 ± 2.51 |
| class weighting, paper-text tokens | +6.78 ± 3.17 |
| rcATT tokenisation, balanced weights | +0.27 ± 0.62 |
| rcATT tokenisation, no weighting | +1.16 ± 1.03 |
| both (released minus paper text) | +7.05 ± 2.86 |
6. TTP extraction on TRAM2¶
Methodology: 151 CTI reports, 5-fold cross-validation grouped by document; classifier thresholds tuned on an inner 20% document split.
Protocol: 5-fold CV grouped by document, seed 13; thresholds tuned on inner 20% doc split. ATT&CK v19.2.
Source: GitHub Actions run 37093689060, commit 09cde53, 2026-10-03T03:35:44+00:00.
| Method | Sent. P | Sent. R | Sent. micro-F1 | Sent. macro-F1 | Doc P | Doc R | Doc micro-F1 | Doc macro-F1 |
|---|---|---|---|---|---|---|---|---|
| Keyword baseline (ATT&CK technique names) | 0.395 | 0.071 | 0.121 | 0.134 | 0.686 | 0.270 | 0.387 | 0.355 |
| TF-IDF+LR trained on ATT&CK procedures only | 0.376 | 0.387 | 0.381 | 0.353 | 0.552 | 0.703 | 0.618 | 0.579 |
| TF-IDF+LR trained on ATT&CK + TRAM train folds | 0.471 | 0.518 | 0.493 | 0.446 | 0.647 | 0.787 | 0.710 | 0.663 |
| Method | Doc micro-F1, fold mean ± SD (dispersion) | Doc micro-F1, document bootstrap 95% CI |
|---|---|---|
| Keyword baseline (ATT&CK technique names) | 0.388 ± 0.044 | 0.329-0.434 |
| TF-IDF+LR trained on ATT&CK procedures only | 0.618 ± 0.030 | 0.587-0.648 |
| TF-IDF+LR trained on ATT&CK + TRAM train folds | 0.710 ± 0.023 | 0.680-0.738 |
2 TRAM labels (T1562.001, T1574.002) are revoked in ATT&CK v19.2, so the keyword and ATT&CK-only models cannot predict them (3.3% of sentence-level gold labels).
Best / worst techniques (sentence F1, ATT&CK+TRAM model):
| Technique | Support | F1 |
|---|---|---|
| T1140 Deobfuscate/Decode Files or Information | 466 | 0.712 |
| T1021.001 Remote Desktop Protocol | 99 | 0.688 |
| T1056.001 Keylogging | 62 | 0.673 |
| T1053.005 Scheduled Task | 118 | 0.664 |
| T1003.001 LSASS Memory | 112 | 0.662 |
| T1557.001 Name Resolution Poisoning and SMB Relay | 7 | 0.222 |
| T1074.001 Local Data Staging | 26 | 0.211 |
| T1005 Data from Local System | 61 | 0.196 |
| T1552.001 Credentials In Files | 22 | 0.182 |
| T1569.002 Service Execution | 22 | 0.176 |
7. Campaign clustering¶
Methodology: per-report slices of ATT&CK group activity plus attributed campaigns. Hyper-parameters are chosen on dev groups (even ATT&CK numbers) and reported on disjoint test groups. The test is capability-only, because ATT&CK records no infrastructure.
Hyper-parameters chosen on 239 dev events from 26 disjoint groups (dev ARI: kNN 0.369, dense 0.373). Brackets: 95% leave-one-group-out jackknife interval with each clustering held fixed (sampling variation of the test set only; estimate +- 1.96 SE).
Source: GitHub Actions run 37093689060, commit 09cde53, 2026-10-03T03:35:51+00:00.
| Method | Purity [95% CI] | NMI [95% CI] | ARI [95% CI] | #clusters |
|---|---|---|---|---|
| Louvain, IDF-weighted Jaccard kNN graph (k=10, res=5.0; dev-tuned) | 0.511 [0.44-0.58] | 0.642 [0.60-0.69] | 0.249 [0.17-0.32] | 37 |
| Louvain, dense graph (no kNN, res=5.0; dev-tuned) | 0.659 [0.57-0.75] | 0.705 [0.66-0.75] | 0.203 [0.14-0.26] | 84 |
| Louvain, dense graph (no kNN, default res=1.0) | 0.178 [0.09-0.26] | 0.255 [0.18-0.33] | 0.047 [-0.00-0.10] | 4 |
| Single-linkage Jaccard (t=0.3; dev-tuned = default) | 0.800 [0.71-0.89] | 0.694 [0.64-0.75] | 0.107 [-0.03-0.25] | 181 |
| Trivial: one cluster per event | 1.000 [1.00-1.00] | 0.753 [0.72-0.79] | 0.000 [0.00-0.00] | 270 |
ARI, kNN minus dense graph (paired jackknife, 95%): [-0.019, +0.110]. Louvain depends on input order: the selected kNN configuration on 10 random permutations of the test events gives ARI 0.248 ± 0.013 (sample SD; range 0.228-0.267).
8. End-to-end on APTnotes reports¶
Methodology:
- Reports. Public APT report PDFs whose file name or title names exactly one ATT&CK group.
- Pipeline. Text is extracted, the span-anchored extractor finds techniques and software, and the report is attributed against all 176 group profiles.
- Leak control. The leak-controlled rows first hold out ATT&CK citations of the true group that appear to be the same report.
Source: GitHub Actions run 37093689060, commit 09cde53, 2026-10-03T03:35:45+00:00.
Mean per report: 3.5 techniques, 2.5 software, 102.4 IOCs (all span-anchored). Evidence used: all. 3 reports with nothing extracted are scored as declines. 23 of 75 reports matched an ATT&CK citation of their own group (held out in the leak-controlled rows). 33 groups among the reports with extracted items (clustered below). Skipped: 1 (2014/h12756-wp-shell-crew.pdf: excluded: its converted text quotes webshell code and is quarantined by Windows Defender on the author's machine; skipped everywhere so local and CI runs match).
| Attributor | Profiles | Top-1 [Wilson 95%] | Top-5 | Names someone | Brier | Wrong at p>=0.8 [Wilson 95%] |
|---|---|---|---|---|---|---|
| TTP-similarity baseline | as-is (upper bound) | 0.320 [0.23-0.43] | 0.533 | 0.960 | 0.332 | 9/75 [0.06-0.21] |
| OCCAM ACH | as-is (upper bound) | 0.360 [0.26-0.47] | 0.493 | 0.840 | 0.216 | 0/75 [0.00-0.05] |
| OCCAM ACH, top-5 shortlist | as-is (upper bound) | 0.347 [0.25-0.46] | 0.533 | 0.640 | 0.225 | 0/75 [0.00-0.05] |
| TTP-similarity baseline | leak-controlled | 0.280 [0.19-0.39] | 0.493 | 0.960 | 0.333 | 9/75 [0.06-0.21] |
| OCCAM ACH | leak-controlled | 0.320 [0.23-0.43] | 0.467 | 0.827 | 0.229 | 0/75 [0.00-0.05] |
| OCCAM ACH, top-5 shortlist | leak-controlled | 0.293 [0.20-0.40] | 0.493 | 0.587 | 0.242 | 0/75 [0.00-0.05] |
| Exact McNemar test (same reports) | Correct only first | Correct only second | Both | p (two-sided) |
|---|---|---|---|---|
| ACH vs similarity, as-is | 12 | 9 | 15 | 0.664 |
| ACH vs similarity, leak-controlled | 12 | 9 | 12 | 0.664 |
| Clustering of the reports | Purity | NMI | ARI | #clusters |
|---|---|---|---|---|
| louvain (k=10, res=5.0) | 0.972 | 0.890 | 0.242 | 59 |
| single-linkage (t=0.3) | 0.986 | 0.874 | 0.068 | 68 |
Ablation: which kind of extracted evidence carries the signal?
Source: GitHub Actions run 37093689060, commit 09cde53, 2026-10-03T03:37:13+00:00.
Mean per report: 3.5 techniques, 2.5 software, 102.4 IOCs (all span-anchored). Evidence used: software. 15 reports with nothing extracted are scored as declines. 23 of 75 reports matched an ATT&CK citation of their own group (held out in the leak-controlled rows). 28 groups among the reports with extracted items (clustered below). Skipped: 1 (2014/h12756-wp-shell-crew.pdf: excluded: its converted text quotes webshell code and is quarantined by Windows Defender on the author's machine; skipped everywhere so local and CI runs match).
| Attributor | Profiles | Top-1 [Wilson 95%] | Top-5 | Names someone | Brier | Wrong at p>=0.8 [Wilson 95%] |
|---|---|---|---|---|---|---|
| TTP-similarity baseline | as-is (upper bound) | 0.520 [0.41-0.63] | 0.573 | 0.800 | 0.316 | 1/75 [0.00-0.07] |
| OCCAM ACH | as-is (upper bound) | 0.493 [0.38-0.60] | 0.560 | 0.773 | 0.201 | 0/75 [0.00-0.05] |
| OCCAM ACH, top-5 shortlist | as-is (upper bound) | 0.520 [0.41-0.63] | 0.573 | 0.773 | 0.198 | 0/75 [0.00-0.05] |
| TTP-similarity baseline | leak-controlled | 0.480 [0.37-0.59] | 0.547 | 0.800 | 0.325 | 1/75 [0.00-0.07] |
| OCCAM ACH | leak-controlled | 0.453 [0.35-0.57] | 0.533 | 0.760 | 0.214 | 0/75 [0.00-0.05] |
| OCCAM ACH, top-5 shortlist | leak-controlled | 0.480 [0.37-0.59] | 0.547 | 0.760 | 0.211 | 0/75 [0.00-0.05] |
| Exact McNemar test (same reports) | Correct only first | Correct only second | Both | p (two-sided) |
|---|---|---|---|---|
| ACH vs similarity, as-is | 1 | 3 | 36 | 0.625 |
| ACH vs similarity, leak-controlled | 1 | 3 | 33 | 0.625 |
| Clustering of the reports | Purity | NMI | ARI | #clusters |
|---|---|---|---|---|
| louvain (k=10, res=5.0) | 0.983 | 0.887 | 0.220 | 50 |
| single-linkage (t=0.3) | 1.000 | 0.873 | 0.080 | 57 |
Source: GitHub Actions run 37093689060, commit 09cde53, 2026-10-03T03:38:43+00:00.
Mean per report: 3.5 techniques, 2.5 software, 102.4 IOCs (all span-anchored). Evidence used: techniques. 6 reports with nothing extracted are scored as declines. 23 of 75 reports matched an ATT&CK citation of their own group (held out in the leak-controlled rows). 32 groups among the reports with extracted items (clustered below). Skipped: 1 (2014/h12756-wp-shell-crew.pdf: excluded: its converted text quotes webshell code and is quarantined by Windows Defender on the author's machine; skipped everywhere so local and CI runs match).
| Attributor | Profiles | Top-1 [Wilson 95%] | Top-5 | Names someone | Brier | Wrong at p>=0.8 [Wilson 95%] |
|---|---|---|---|---|---|---|
| TTP-similarity baseline | as-is (upper bound) | 0.000 [0.00-0.05] | 0.027 | 0.920 | 0.408 | 23/75 [0.21-0.42] |
| OCCAM ACH | as-is (upper bound) | 0.013 [0.00-0.07] | 0.093 | 0.560 | 0.288 | 0/75 [0.00-0.05] |
| OCCAM ACH, top-5 shortlist | as-is (upper bound) | 0.000 [0.00-0.05] | 0.027 | 0.387 | 0.305 | 0/75 [0.00-0.05] |
| TTP-similarity baseline | leak-controlled | 0.000 [0.00-0.05] | 0.013 | 0.920 | 0.408 | 23/75 [0.21-0.42] |
| OCCAM ACH | leak-controlled | 0.013 [0.00-0.07] | 0.093 | 0.560 | 0.288 | 0/75 [0.00-0.05] |
| OCCAM ACH, top-5 shortlist | leak-controlled | 0.000 [0.00-0.05] | 0.013 | 0.387 | 0.305 | 0/75 [0.00-0.05] |
| Exact McNemar test (same reports) | Correct only first | Correct only second | Both | p (two-sided) |
|---|---|---|---|---|
| ACH vs similarity, as-is | 1 | 0 | 0 | 1.000 |
| ACH vs similarity, leak-controlled | 1 | 0 | 0 | 1.000 |
| Clustering of the reports | Purity | NMI | ARI | #clusters |
|---|---|---|---|---|
| louvain (k=10, res=5.0) | 0.971 | 0.894 | 0.261 | 57 |
| single-linkage (t=0.3) | 0.986 | 0.877 | 0.053 | 66 |
With the sentence classifier added (top 10 techniques per report), including a paired test against the keyword-only run:
Source: GitHub Actions run 37093689060, commit 09cde53, 2026-10-03T03:42:20+00:00.
Mean per report: 12.5 techniques, 2.5 software, 102.4 IOCs (all span-anchored). Evidence used: all. 0 reports with nothing extracted are scored as declines. 23 of 75 reports matched an ATT&CK citation of their own group (held out in the leak-controlled rows). 33 groups among the reports with extracted items (clustered below). Skipped: 1 (2014/h12756-wp-shell-crew.pdf: excluded: its converted text quotes webshell code and is quarantined by Windows Defender on the author's machine; skipped everywhere so local and CI runs match).
| Attributor | Profiles | Top-1 [Wilson 95%] | Top-5 | Names someone | Brier | Wrong at p>=0.8 [Wilson 95%] |
|---|---|---|---|---|---|---|
| TTP-similarity baseline | as-is (upper bound) | 0.333 [0.24-0.45] | 0.507 | 1.000 | 0.194 | 0/75 [0.00-0.05] |
| OCCAM ACH | as-is (upper bound) | 0.187 [0.11-0.29] | 0.387 | 0.573 | 0.248 | 0/75 [0.00-0.05] |
| OCCAM ACH, top-5 shortlist | as-is (upper bound) | 0.187 [0.11-0.29] | 0.507 | 0.280 | 0.315 | 0/75 [0.00-0.05] |
| TTP-similarity baseline | leak-controlled | 0.293 [0.20-0.40] | 0.467 | 1.000 | 0.211 | 0/75 [0.00-0.05] |
| OCCAM ACH | leak-controlled | 0.160 [0.09-0.26] | 0.347 | 0.547 | 0.255 | 0/75 [0.00-0.05] |
| OCCAM ACH, top-5 shortlist | leak-controlled | 0.120 [0.06-0.21] | 0.467 | 0.213 | 0.327 | 0/75 [0.00-0.05] |
| Exact McNemar test (same reports) | Correct only first | Correct only second | Both | p (two-sided) |
|---|---|---|---|---|
| ACH vs similarity, as-is | 6 | 17 | 8 | 0.035 |
| ACH vs similarity, leak-controlled | 7 | 17 | 5 | 0.064 |
| ach (this run) vs aptnotes, leak-controlled | 0 | 12 | 12 | 4.9e-04 |
| similarity (this run) vs aptnotes, leak-controlled | 5 | 4 | 17 | 1.000 |
| Clustering of the reports | Purity | NMI | ARI | #clusters |
|---|---|---|---|---|
| louvain (k=10, res=5.0) | 0.920 | 0.866 | 0.217 | 58 |
| single-linkage (t=0.3) | 1.000 | 0.869 | 0.041 | 73 |