ctokx commited on
Commit
56f2d54
·
verified ·
1 Parent(s): 23cdae8

Add README.md

Browse files
Files changed (1) hide show
  1. README.md +332 -0
README.md ADDED
@@ -0,0 +1,332 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ language:
4
+ - en
5
+ base_model: answerdotai/ModernBERT-base
6
+ pipeline_tag: text-classification
7
+ tags:
8
+ - cyber-threat-intelligence
9
+ - mitre-attack
10
+ - multi-label-classification
11
+ - defensive-security
12
+ - blue-team
13
+ - threat-intelligence
14
+ datasets:
15
+ - center-for-threat-informed-defense/tram
16
+ metrics:
17
+ - f1
18
+ library_name: transformers
19
+ ---
20
+
21
+ # cti-attack-mapper
22
+
23
+ Maps sentences from cyber threat intelligence reports to MITRE ATT&CK technique
24
+ IDs. 49 techniques, multi-label, sentence level.
25
+
26
+ > **Read this before you trust the numbers.** On the leak-free split, a
27
+ > fine-tuned ModernBERT scores 0.445 macro-F1. A plain TF-IDF baseline scores
28
+ > 0.454. The transformer does not beat it; they are tied inside the noise of a
29
+ > 151-report corpus. What does beat both is a blend of the two: 0.474 macro-F1
30
+ > (5-fold mean, standard deviation 0.022), about 0.04 ahead of each base model
31
+ > at the same threshold setting, and ahead in all five folds. One caveat:
32
+ > against TF-IDF at its own best threshold setting the blend's lead shrinks to
33
+ > 0.02 and is no longer outside the noise. Every number here is written by a
34
+ > script in the repo. See [Results](#results).
35
+
36
+ **Defensive use only.** It labels adversary behavior that has already been
37
+ written up in public threat reports. It produces no offensive capability.
38
+
39
+ ---
40
+
41
+ ## What this repo is for
42
+
43
+ There are already several ATT&CK classifiers on the Hub. This one exists for
44
+ three reasons those usually leave out.
45
+
46
+ 1. **The baselines are published.** Most cyber-ML papers never say what a TF-IDF
47
+ plus logistic regression floor scores. This one does. On the honest split
48
+ that floor ties the fine-tuned transformer, and only a blend of the two gets
49
+ past it. The repo shows by how much and with what spread.
50
+ 2. **The test split is leak-free, and the naive alternative is measured.** The
51
+ corpus is 19k sentences drawn from only 151 reports. The sentence-level
52
+ random split that most work uses scatters near-identical prose from one
53
+ report across train and test. That inflates macro-F1 by about 12 percent.
54
+ Both splits ship here.
55
+ 3. **Everything regenerates.** Every number below is written by
56
+ `scripts/04_report.py` from JSON that the training and eval scripts produce.
57
+ None of it is typed by hand.
58
+
59
+ ## Quick start
60
+
61
+ ```python
62
+ from transformers import AutoModelForSequenceClassification, AutoTokenizer
63
+ import torch
64
+
65
+ model_id = "ctokx/cti-attack-mapper-modernbert"
66
+ tok = AutoTokenizer.from_pretrained(model_id)
67
+ model = AutoModelForSequenceClassification.from_pretrained(model_id).eval()
68
+
69
+ text = "The implant establishes persistence by creating a scheduled task that runs at logon."
70
+ with torch.no_grad():
71
+ probs = torch.sigmoid(model(**tok(text, return_tensors="pt", truncation=True)).logits)[0]
72
+
73
+ for i, p in enumerate(probs):
74
+ if p > 0.5:
75
+ print(model.config.id2label[i], round(float(p), 3))
76
+ # T1053.005 0.94
77
+ ```
78
+
79
+ Or use the repo's wrapper, which loads the tuned per-class thresholds and
80
+ resolves technique names:
81
+
82
+ ```python
83
+ from cti_attack.predict import AttackMapper
84
+
85
+ mapper = AttackMapper("models/modernbert__document")
86
+ mapper.predict("The dropper base64-encodes its configuration before writing it to disk.")
87
+ # [Prediction(T1027, 'Obfuscated Files or Information', 0.912)]
88
+ ```
89
+
90
+ ## Results
91
+
92
+ <!-- BEGIN GENERATED RESULTS - scripts/04_report.py -->
93
+ ### Test-set results
94
+
95
+ Per-class thresholds tuned on dev. Macro-F1 is the headline metric. It weights all 49 techniques equally, so a handful of common ones cannot cover for a weak long tail.
96
+
97
+ | Model | doc macro-F1 | doc micro-F1 | random macro-F1 | random micro-F1 | inflation |
98
+ |---|---|---|---|---|---|
99
+ | Frequency prior | 0.000 | 0.000 | 0.000 | 0.000 | n/a |
100
+ | ATT&CK keyword match | 0.167 | 0.170 | 0.171 | 0.153 | +2.4% |
101
+ | TF-IDF + one-vs-rest LR | 0.454 | 0.500 | 0.506 | 0.545 | +11.5% |
102
+ | **ModernBERT-base (fine-tuned)** | 0.445 | 0.478 | 0.482 | 0.540 | +8.3% |
103
+ | SecureBERT (fine-tuned) | 0.369 | 0.252 | 0.415 | 0.373 | +12.5% |
104
+
105
+ ### Same table, single global threshold
106
+
107
+ | Model | doc macro-F1 | doc micro-F1 | random macro-F1 | random micro-F1 | inflation |
108
+ |---|---|---|---|---|---|
109
+ | Frequency prior | 0.000 | 0.000 | 0.000 | 0.000 | n/a |
110
+ | ATT&CK keyword match | 0.167 | 0.170 | 0.171 | 0.153 | +2.4% |
111
+ | TF-IDF + one-vs-rest LR | 0.449 | 0.472 | 0.515 | 0.552 | +14.7% |
112
+ | **ModernBERT-base (fine-tuned)** | 0.425 | 0.494 | 0.504 | 0.557 | +18.6% |
113
+ | SecureBERT (fine-tuned) | 0.352 | 0.425 | 0.388 | 0.470 | +10.2% |
114
+
115
+ ### Ensemble + cross-validated results
116
+
117
+ One 70/15/15 split of a 151-document corpus is one draw, so the gap between models can be noise. To check it, here is 5-fold cross-validation grouped by document. Each report lands in exactly one test fold. The blend weight and thresholds are tuned on each fold's own dev set, never on its test set.
118
+
119
+ | Model | doc macro-F1 (5-fold CV, mean +/- std) |
120
+ |---|---|
121
+ | TF-IDF + LR (per-class) | 0.4326 +/- 0.0349 |
122
+ | TF-IDF + LR (global) | 0.4521 +/- 0.0420 |
123
+ | ModernBERT (per-class) | 0.4263 +/- 0.0256 |
124
+ | **Ensemble TF-IDF+ModernBERT (per-class)** | **0.4738 +/- 0.0223** |
125
+
126
+ Paired per-fold test, per-class thresholds on both sides:
127
+ - ensemble vs TF-IDF, same per-class regime: **+0.0412**, 5/5 folds positive, p = 0.005
128
+ - ensemble vs ModernBERT, same per-class regime: **+0.0475**, 5/5 folds positive, p = 0.010
129
+ - ensemble vs TF-IDF at its stronger global regime: **+0.0217**, 4/5 folds positive, p = 0.100
130
+
131
+ On the original single 70/15/15 document split, the same ensemble (70% TF-IDF, 30% ModernBERT) scores **0.4965** macro-F1, against 0.4536 for TF-IDF and 0.4454 for ModernBERT.
132
+
133
+ How to read this: the ensemble has the highest mean and the lowest variance of everything tested. Compared against each base model at the same threshold setting, it wins in all five folds. The one comparison it does not clearly win is against TF-IDF at its own best threshold setting, where the lead is small enough to be noise over five folds. That row is in the table on purpose.
134
+
135
+ ### ModernBERT per-technique, document split (12 most frequent)
136
+
137
+ | Technique | Name | Support | P | R | F1 |
138
+ |---|---|---|---|---|---|
139
+ | `T1027` | Obfuscated Files or Information | 102 | 0.57 | 0.54 | 0.55 |
140
+ | `T1140` | Deobfuscate/Decode Files or Information | 68 | 0.73 | 0.84 | 0.78 |
141
+ | `T1105` | Ingress Tool Transfer | 57 | 0.51 | 0.44 | 0.47 |
142
+ | `T1059.003` | Command and Scripting Interpreter: Windows … | 51 | 0.39 | 0.59 | 0.47 |
143
+ | `T1055` | Process Injection | 40 | 0.68 | 0.53 | 0.59 |
144
+ | `T1106` | Native API | 34 | 0.64 | 0.47 | 0.54 |
145
+ | `T1047` | Windows Management Instrumentation | 28 | 0.69 | 0.64 | 0.67 |
146
+ | `T1053.005` | Scheduled Task/Job: Scheduled Task | 26 | 0.71 | 0.92 | 0.80 |
147
+ | `T1562.001` | Impair Defenses: Disable or Modify Tools ⚠️*revoked* | 26 | 0.92 | 0.46 | 0.62 |
148
+ | `T1574.002` | Hijack Execution Flow: DLL Side-Loading ⚠️*revoked* | 26 | 0.83 | 0.38 | 0.53 |
149
+ | `T1082` | System Information Discovery | 24 | 0.83 | 0.21 | 0.33 |
150
+ | `T1078` | Valid Accounts | 23 | 0.23 | 0.48 | 0.31 |
151
+
152
+ ### Head vs tail, document split
153
+
154
+ | Bucket | Techniques | Mean F1 |
155
+ |---|---|---|
156
+ | head (>=20 test examples) | 15 | 0.570 |
157
+ | tail (<20 test examples) | 34 | 0.391 |
158
+
159
+ ### Corpus
160
+
161
+ - 19,178 raw sentences, 18,437 after cleaning and dedup
162
+ - 4,024 carry at least one technique (21.8%)
163
+ - 49 techniques across 151 source documents
164
+ - 714 duplicate sentences removed (370 of them appeared in more than one document)
165
+ - dropped for having too few documents to split: T1557.001
166
+ <!-- END GENERATED RESULTS -->
167
+
168
+ ### How to read this
169
+
170
+ - **No single model beats the linear baseline; a blend of two does.** On the
171
+ leak-free split TF-IDF scores 0.454 and ModernBERT 0.445. The 0.009 gap is
172
+ inside the noise you get from a 151-document corpus, so they are tied, not
173
+ that TF-IDF wins. Blending their probabilities (about 70 percent TF-IDF)
174
+ reaches 0.474 in 5-fold cross-validation and beats each base model at the same
175
+ threshold setting in all five folds. The one place it does not clearly win is
176
+ against TF-IDF at its own best threshold setting, where the 0.02 lead is
177
+ within noise. The blend is also the steadiest model tested, with the smallest
178
+ fold-to-fold spread.
179
+ - **The `random` column is the number you would have reported by accident.** It
180
+ runs about 12 percent higher for the same models on the same data. If you are
181
+ comparing against work that split at sentence level, that column is the
182
+ comparable one, and it is not the true one.
183
+ - **The keyword row is the control.** It never trains, so leaked phrasing cannot
184
+ help it. It moves 2.4 percent between splits, which is the size of plain
185
+ test-set-composition noise. The trained models move 8 to 19 percent. That
186
+ difference is what makes the leakage claim a measurement, not an assertion.
187
+ - **One claim this data does not support:** that leakage inflates high-capacity
188
+ models more than linear ones. Under per-class thresholds TF-IDF inflates more
189
+ (11.5 percent versus 8.3 percent); under a global threshold the order flips
190
+ (14.7 percent versus 18.6 percent). A result that changes sign with the
191
+ threshold setting is not a result, and it is not claimed here.
192
+ - **The tail is where this model is weak.** 0.570 mean F1 on the 15 techniques
193
+ with at least 20 test examples, 0.391 on the 34 rarer ones. Micro-F1 hides
194
+ this, which is why macro-F1 leads.
195
+
196
+ ## Intended use
197
+
198
+ **In scope.** A triage aid for threat-intelligence and detection-engineering
199
+ work:
200
+
201
+ - First-pass ATT&CK tagging of a report, for an analyst to correct
202
+ - Deciding which reports to read first when clearing a backlog
203
+ - Rough coverage analysis: which techniques a body of reporting talks about
204
+ - A baseline to beat, with a published harness for beating it
205
+
206
+ **Out of scope.**
207
+
208
+ - Unreviewed labeling. Output is candidates, not conclusions. At macro-F1 around
209
+ 0.45, roughly half the predictions on rarer techniques are wrong.
210
+ - Compliance, audit, or attestation evidence.
211
+ - Detecting techniques outside the 49 covered.
212
+ - Anything but English prose describing adversary behavior. It is not a malware
213
+ classifier; it does not read binaries, logs, or code.
214
+
215
+ ## Limitations
216
+
217
+ - **49 techniques, not the full ATT&CK matrix.** Anything outside the label set
218
+ is invisible, including techniques the text plainly describes. No prediction is
219
+ not evidence of absence.
220
+ - **2 of the 49 labels are revoked in current ATT&CK.** `T1562.001` and
221
+ `T1574.002` were valid when the TRAM corpus was annotated and MITRE has since
222
+ revoked them. Map them forward before comparing output to current ATT&CK.
223
+ - **Sentence-level context only.** Techniques you can only infer from
224
+ surrounding paragraphs are under-represented.
225
+ - **Long-tailed.** `T1027` has 678 training instances; the rarest retained
226
+ techniques have about 20.
227
+ - **151 source documents.** Even the leak-free split is one draw from a small
228
+ pool. Treat a point or two between models as noise, including the
229
+ ModernBERT/TF-IDF gap above.
230
+ - **Annotation is not exhaustive.** Some unlabelled sentences do describe
231
+ techniques, so measured recall is lower than the truth.
232
+ - **Domain shift is untested.** The training text is vendor threat-report prose.
233
+ Behavior on incident tickets, chat logs, or non-native-English reporting is
234
+ unknown and probably worse.
235
+ - **Per-class thresholds are fitted.** 49 thresholds tuned on a dev set with few
236
+ positives per class can overfit, so the single-threshold numbers are reported
237
+ next to them and you can see the size of the effect.
238
+
239
+ ## Training details
240
+
241
+ | | |
242
+ |---|---|
243
+ | Base model | `answerdotai/ModernBERT-base` (149M) |
244
+ | Objective | Multi-label BCE, per-class `pos_weight`, capped at 50 |
245
+ | Max sequence length | 256 tokens |
246
+ | Batch size | 16, gradient accumulation 2 (effective 32) |
247
+ | Learning rate | 3e-5, linear schedule, 10% warmup |
248
+ | Weight decay | 0.01 (excluding bias and norm parameters) |
249
+ | Epochs | 6, best checkpoint by dev macro-F1 (epoch 5) |
250
+ | Precision | bf16 autocast |
251
+ | Hardware | 1x RTX 4060 Laptop, 8 GB |
252
+ | Peak VRAM | ~5.5 GB |
253
+ | Wall clock | ~30 min per split scheme |
254
+ | Seed | 20260802 |
255
+
256
+ The `pos_weight` is doing real work. 78 percent of sentences carry no label, and
257
+ without it the model learns to predict nothing.
258
+
259
+ Training loss reached 0.010 while dev macro-F1 peaked at 0.414. The model
260
+ memorised the training set instead of generalising from it. With 2,684 labelled
261
+ training sentences across 49 techniques, that is what you would expect, and it is
262
+ the likely reason a single transformer does not pull ahead of the linear
263
+ baseline. A blend of the two does, which points at data, not architecture, as
264
+ the limit.
265
+
266
+ ## Dataset
267
+
268
+ See [DATASET_CARD.md](DATASET_CARD.md). In short:
269
+
270
+ - 19,178 raw sentences, 18,437 after cleaning and dedup
271
+ - 4,024 carry at least one technique (21.8%)
272
+ - 49 techniques across 151 source documents
273
+ - 714 duplicate sentences removed. 370 of them appeared in more than one
274
+ document, which is the leakage the `random` split exposes.
275
+ - `T1557.001` dropped: it appears in exactly one document and cannot be split
276
+ leak-free
277
+
278
+ ## Reproducing
279
+
280
+ ```bash
281
+ pip install -r requirements.txt
282
+ python scripts/reproduce_all.py # ModernBERT only, ~40 min on an RTX 4060
283
+ python scripts/reproduce_all.py --all-models # adds DeBERTa-v3 and SecureBERT
284
+ ```
285
+
286
+ Individual steps:
287
+
288
+ ```bash
289
+ python scripts/01_build_dataset.py # build both splits
290
+ python scripts/02_run_baselines.py # frequency, keyword, TF-IDF
291
+ python scripts/03_train.py --model modernbert --scheme document # fine-tune
292
+ python scripts/05_ensemble.py --scheme document # blend TF-IDF + ModernBERT
293
+ python scripts/06_cv.py --folds 5 # 5-fold document CV + error bars
294
+ python scripts/04_report.py # regenerate the tables above
295
+ pytest tests/ -q # invariants + smoke test
296
+ ```
297
+
298
+ The published weights are the ModernBERT model. The blend is those weights plus
299
+ a TF-IDF and logistic regression model, which `scripts/05_ensemble.py` refits
300
+ from the included dataset in a few seconds, so the best-scoring setup reproduces
301
+ without shipping a second binary. The blend weight and every threshold are tuned
302
+ on dev, never on test.
303
+
304
+ The test suite checks the claims this card makes: that no document spans two
305
+ splits, that all 49 techniques reach every split, that no duplicate sentences
306
+ survive, and that the model loads and returns well-formed output.
307
+
308
+ ## Licence and attribution
309
+
310
+ Apache-2.0. Derived from
311
+ [MITRE CTID TRAM](https://github.com/center-for-threat-informed-defense/tram)
312
+ (Apache-2.0). Technique names come from
313
+ [MITRE ATT&CK STIX data](https://github.com/mitre-attack/attack-stix-data)
314
+ under the ATT&CK Terms of Use.
315
+
316
+ ATT&CK® is a registered trademark of The MITRE Corporation. This project is not
317
+ affiliated with, endorsed by, or sponsored by The MITRE Corporation.
318
+
319
+ See [NOTICE](NOTICE) for full attribution.
320
+
321
+ ## Citation
322
+
323
+ ```bibtex
324
+ @misc{cti_attack_mapper,
325
+ title = {cti-attack-mapper: sentence-level MITRE ATT&CK classification
326
+ with leak-free evaluation},
327
+ author = {Varol Cagdas Tok},
328
+ year = {2026},
329
+ url = {https://huggingface.co/ctokx/cti-attack-mapper-modernbert},
330
+ note = {Derived from MITRE CTID TRAM, Apache-2.0}
331
+ }
332
+ ```