Skip to content

Evaluation

Every number on this page is rendered from committed JSON by scripts/render_tables.py (the tables below are results/TABLES.md; each table names its source file and the run that wrote it). How to regenerate each file is on Reproduce. The short version of the findings:

  • Reconstruction (ATLAS). On the four scenarios the traversal was designed on, the full method beats naive reachability on every log (event F1 0.66 vs 0.25; ΔF1 +0.41, t(3) 95 % CI [0.24, 0.58]). On the 12 held-out M1-M6 host logs it is 2.4x more precise (0.62 vs 0.26), but recall drops to 0.72 and the F1 gain is not significant (higher on 9 of 12 logs, sign-flip p = 0.28). Session-root stops are the component that transfers: precision is higher on 12 of 12 held-out logs (+0.10 [0.07, 0.14], exact sign test p = 0.0005) with recall essentially unchanged (-0.0045 [-0.012, -0.0003]). The held-out logs are unseen host logs from the same testbed, and 8 of 12 replay an S1-S4 exploit; on the four logs with a new exploit (M2, M4) the stops add +0.12 precision, higher on 4 of 4.
  • Comparison with the paper. ROOTLINE's naive reachability lands where ATLAS's own published graph-traversal baseline does (P 0.15-0.26 vs 0.18). Our PyTorch reproduction of the ATLAS LSTM does not reach the published entity-level numbers, nor ATLAS's own cleaned list scored the same way (0.86-1.00); ATLAS's shipped raw model output does not either.
  • Rules. v0.3 detects 23/27 in-sample dev2 captures but 4/64 parsed sealed captures (Wilson [0.02, 0.15]). The rules do not generalise; 96 of 160 sealed captures are not even parsed.
  • Unsupervised ranking. IsolationForest does not outperform a one-line "user-dir image first" heuristic on the held-out hosts (recall@10 0.78 vs 0.93; the heuristic is higher on 8, lower on 1 and tied on 3 of 12 logs, sign test p = 0.039) and falls to 0.49 without that single feature.
  • Live kernel. The probe recovers the scripted chain on a real kernel in CI in one query, with the download socket as a root cause, in 72 of 72 completed jobs over 15 pushes (71 of 72 jobs pass every check; one row per job in results/live_ebpf.json).

Methodology

Data. ATLAS (Alsaheel et al., USENIX Security 2021) ships pre-processed Windows audit, DNS and browser logs where every line is labelled + when it involves a ground-truth attack entity. ROOTLINE uses the audit and DNS rows (browser rows have no pid); a raw event is an attack event when its line is +. Events are predicted when both endpoints of their provenance edge are in the story. DNS events are not scored.

Pivots. One run per (log, pivot): ATLAS's own starting IOC (user_artifact.txt) and every ground-truth entity that maps to a vertex (the paper's random-symptom protocol). Entity recall counts ground-truth entities found other than the ones the pivot itself covers. Root-cause hit@3 asks whether any of the three top-ranked entry points is a ground-truth entity.

Design vs held-out data. The v0.2 traversal heuristics (Windows session roots, the browser-profile penalty, the user-dir anomaly feature) were written while looking at S1-S4, so S1-S4 results are in-sample. The per-host logs of M1-M6 were never used for design. See ADR 0010.

Statistics. Means are means of per-log means, and the log is the unit of every test, because pivots in one log share a graph and are not independent. For the 12 held-out logs, intervals are 95 % percentile intervals from a log-cluster bootstrap (2,000 resamples), and paired differences against naive get exact two-sided sign and sign-flip tests over logs; the smallest attainable p with 12 logs is 2/2^12 = 0.00049. For the four S logs, a bootstrap cannot extend past the extreme logs and no distribution-free test can go below p = 0.125, so the tables give the range over the four logs and a t(3) interval for the difference. Pivot counts are reported as description only. p-values are stored to 4 significant digits and never printed as 0. Rule coverage uses Wilson intervals computed from the counts. IsolationForest's per-log value is the mean over 10 seeds.

Variants. Each switches components of the same reconstructor off:

variant time-respecting session-root stops causal spine accessed expansion
naive
naive+stops yes
time yes
time+stops yes yes
time+spine yes yes
time+stops+spine yes yes yes
full (ROOTLINE) yes yes yes yes

Ablation: one dot per log

Session-root stops per held-out log

Generated by python scripts/render_tables.py from the committed JSON in results/. Do not edit by hand.

Headline results

Result Number Source (results/)
ATLAS S1-S4 (design data): full method vs naive reachability, event F1 0.66 vs 0.25, higher on 4 of 4 logs (per-log 0.42-0.78 vs 0.15-0.39; ΔF1 +0.41, t(3) 95 % CI [+0.24, +0.58]) ablation.json, bench run 37092501921
ATLAS M1-M6 host logs (held out): precision / recall / F1, full vs naive 0.62 / 0.72 / 0.46 vs 0.26 / 0.98 / 0.33; F1 higher on 9 of 12 logs, ΔF1 +0.14 [-0.11, +0.35] (sign-flip p = 0.28, not significant) ablation.json, bench run 37092501921
Session-root stops alone, held out precision +0.10 [+0.07, +0.14], higher on 12 of 12 logs (exact sign test p = 0.00049); recall -0.0045 [-0.0119, -0.0003] ablation.json, bench run 37092501921
Same, only the 4 held-out logs whose exploit is not in S1-S4 (M2, M4) precision +0.12, higher on 4 of 4 logs (descriptive) ablation.json, bench run 37092501921
ATLAS paper's graph-traversal baseline vs ROOTLINE naive reachability, event precision 0.18 vs 0.15 / 0.26: consistent paper Table 5; ablation.json, bench run 37092501921
ATLAS LSTM, our reproduction (entity F1, S1-S4, mean of 5 seeds) 0.24-0.75 vs ATLAS's cleaned list under our scorer 0.86-1.00 (paper 0.89-1.00): not reproduced atlas_repro.json, atlas-repro run 36997669262
Rule tagger v0.3: in-sample dev2 vs sealed, captures detected 23/27 [0.68, 0.94] vs 4/64 [0.02, 0.15] coverage.json, sealed-coverage run 36994398382
Live bpftrace probe on a GitHub-hosted kernel 71/72 CI jobs pass over 15 pushes [0.925, 0.998]; chain recovered in one query in 72/72 [0.95, 1.00] live_ebpf.json, ci runs 36996901332..37003827535
Graph reduction (16 ATLAS logs) 3.8-5.8x fewer edges, lossless by design atlas.json, bench run 37092501921

Ablation (ATLAS, every ground-truth entity as a pivot)

ATLAS S1-S4, design data (in-sample): 4 logs, 15 pivots (results/ablation.json, bench run 37092501921). Mean of per-log means with the range over the four logs in brackets. Four logs are too few for a useful interval per variant (a 4-log bootstrap cannot extend past the extreme logs) or for a distribution-free test (the smallest attainable exact p is 0.125); the ΔF1 column gives a t(3) 95 % interval.

variant precision (range) recall (range) F1 (range) root-cause hit@3 story vertices ΔF1 vs naive [t(3) 95 % CI] logs with higher precision
naive 0.15 (0.08-0.25) 0.94 (0.78-1.00) 0.25 (0.15-0.39) 0.50 5,494
naive+stops 0.26 (0.13-0.41) 0.94 (0.770-0.9999) 0.39 (0.22-0.58) 0.50 4,416 +0.14 [+0.01, +0.26] 4/4
time 0.24 (0.12-0.33) 0.86 (0.69-1.00) 0.37 (0.20-0.49) 0.45 3,520 +0.11 [+0.01, +0.22] 4/4
time+stops 0.31 (0.16-0.46) 0.86 (0.677-0.9999) 0.45 (0.24-0.63) 0.45 3,291 +0.19 [+0.05, +0.33] 4/4
time+spine 0.24 (0.07-0.45) 0.23 (0.06-0.67) 0.20 (0.06-0.53) 0.45 919 -0.06 [-0.32, +0.20] 2/4
time+stops+spine 0.46 (0.22-0.84) 0.22 (0.05-0.67) 0.26 (0.07-0.74) 0.45 400 +0.002 [-0.39, +0.39] 4/4
full 0.54 (0.31-0.66) 0.98 (0.931-0.9995) 0.66 (0.42-0.78) 0.45 3,139 +0.41 [+0.24, +0.58] 4/4

Per-log F1, full vs naive: S1 0.68 vs 0.15; S2 0.77 vs 0.39; S3 0.42 vs 0.15; S4 0.78 vs 0.32 (higher on 4 of 4 logs, paired t(3) = 7.48, p = 0.0049).

ATLAS M1-M6 host logs, held out: 12 logs, 40 pivots (results/ablation.json, bench run 37092501921). Mean of per-log means, 95 % log-cluster bootstrap. Paired tests are over logs (pivots in one log share a graph); with 12 logs the smallest attainable exact p is 0.00049.

variant precision recall F1 entity recall root-cause hit@3 story vertices ΔF1 vs naive logs with higher precision
naive 0.26 [0.12, 0.43] 0.98 [0.950, 0.998] 0.33 [0.19, 0.50] 0.98 [0.93, 1.00] 0.42 5,869
naive+stops 0.36 [0.21, 0.53] 0.98 [0.938, 0.997] 0.46 [0.32, 0.62] 0.98 [0.93, 1.00] 0.50 4,550 +0.14 [+0.09, +0.18]; 12/12 logs, sign-flip p = 0.00049 12/12 (sign test p = 0.00049)
time 0.31 [0.16, 0.48] 0.93 [0.87, 0.99] 0.39 [0.24, 0.56] 0.97 [0.92, 1.00] 0.40 4,730 +0.07 [+0.04, +0.09]; 12/12 logs, sign-flip p = 0.00049 12/12 (sign test p = 0.00049)
time+stops 0.40 [0.25, 0.57] 0.93 [0.86, 0.98] 0.51 [0.37, 0.66] 0.97 [0.92, 1.00] 0.48 3,921 +0.18 [+0.12, +0.24]; 12/12 logs, sign-flip p = 0.00049 12/12 (sign test p = 0.00049)
time+spine 0.28 [0.15, 0.45] 0.06 [0.05, 0.08] 0.06 [0.06, 0.07] 0.97 [0.92, 1.00] 0.40 321 -0.26 [-0.44, -0.12]; 0/12 logs, sign-flip p = 0.00049 5/12 (sign test p = 0.77)
time+stops+spine 0.50 [0.35, 0.65] 0.06 [0.05, 0.07] 0.08 [0.07, 0.10] 0.97 [0.92, 1.00] 0.48 183 -0.25 [-0.43, -0.10]; 2/12 logs, sign-flip p = 0.0063 8/12 (sign test p = 0.39)
full 0.62 [0.48, 0.76] 0.72 [0.56, 0.88] 0.46 [0.35, 0.60] 0.97 [0.92, 1.00] 0.48 2,576 +0.14 [-0.11, +0.35]; 9/12 logs, sign-flip p = 0.28 12/12 (sign test p = 0.00049)

Session-root stops alone (naive+stops vs naive), held out: precision +0.10 [+0.07, +0.14], higher on 12 of 12 logs (exact sign test p = 0.00049; 33 of 40 pivots higher, 1 lower); recall -0.0045 [-0.0119, -0.0003], lower on 11 of 12 logs (1 tied).

Highest held-out F1 point estimate: time+stops 0.51 vs full 0.46; difference +0.04 [-0.14, +0.26], higher on 5 of 12 logs (sign-flip p = 0.69), so it is the highest point estimate, not a significant winner. Root-cause hit@3 ranges 0.40-0.50 over all variants and both groups.

The held-out logs are unseen host logs from the same ATLAS testbed; 8 of the 12 replay an exploit that one of S1-S4 also uses (the CVE is in each log's name). The strictest check of the claim is the two multi-host attacks with a new exploit (descriptive, 4 logs):

held-out logs logs Δ precision (mean) Δ precision (range) logs higher Δ recall (mean)
exploit not in S1-S4 (M2, M4) 4 +0.118 +0.036 to +0.226 4/4 -0.0008
exploit also used in S1-S4 (M1, M3, M5, M6) 8 +0.091 +0.038 to +0.234 8/8 -0.0062

ATLAS from the analyst IOC (one pivot per log)

scenarios method logs precision recall F1 story vertices
M (held out) ioc-grep 12 0.999 [0.998, 1.000] 0.034 [0.027, 0.042] 0.065 [0.053, 0.080] 5
M (held out) naive-bfs 12 0.154 [0.095, 0.228] 0.998 [0.996, 0.999] 0.250 [0.166, 0.350] 6,308
M (held out) rootline 12 0.673 [0.479, 0.857] 0.597 [0.353, 0.837] 0.360 [0.196, 0.541] 2,149
S1-S4 (in-sample) ioc-grep 4 0.998 (0.992-1.000) 0.040 (0.017-0.079) 0.077 (0.033-0.146) 8
S1-S4 (in-sample) naive-bfs 4 0.161 (0.079-0.244) 0.9995 (0.999-1.000) 0.269 (0.146-0.392) 5,902
S1-S4 (in-sample) rootline 4 0.408 (0.161-0.590) 0.999 (0.9957-0.9999) 0.559 (0.277-0.742) 3,424

Source: results/atlas.json, bench run 37092501921. Means over logs; held-out rows give a 95 % log bootstrap, in-sample rows the range over the four logs. rootline-noreduce rows in the JSON are identical to rootline (reduction is lossless by design) and are not shown.

Comparison with published ATLAS results

system level trained on labels precision recall F1
ATLAS paper, Table 5: graph traversal (10 attacks) event no 0.178 1.000 0.303
ROOTLINE naive reachability (S1-S4 / M hosts, all pivots) event no 0.153 / 0.261 0.943 / 0.981 0.254 / 0.327
ROOTLINE full (S1-S4 / M hosts, all pivots) event no 0.542 / 0.625 0.982 / 0.722 0.663 / 0.463
ATLAS paper, Table 4: LSTM (10 attacks) event yes 0.999 0.999 0.999

ATLAS LSTM reproduction, entity level (results/atlas_repro.json, atlas-repro run 36997669262). Our scorer counts ROOTLINE's abstracted entity set, which is about a tenth of the paper's; the like-for-like reference is therefore ATLAS's own cleaned list scored by our scorer, not the paper's number. ATLAS's released evaluate.py scores a manually entered cleaned list (the script stops until one is filled in); for S1 the shipped list equals the ground truth.

scenario entities in paper Table 4 (malicious) entities under our scorer (malicious) paper F1 ATLAS cleaned list, our scorer ATLAS shipped raw output, our scorer our LSTM, regenerated training set: mean (5-seed range) our LSTM, ATLAS's shipped training set: mean (5-seed range)
S1 7,467 (22) 652 (11) 1.00 1.00 0.71 0.65 (0.48-0.80) 0.75 (0.43-0.95)
S2 34,022 (12) 2,162 (7) 0.92 0.88 0.70 0.50 (0.22-0.74) 0.60 (0.33-0.93)
S3 8,998 (26) 752 (19) 0.96 0.94 0.39 0.30 (0.05-0.54) 0.54 (0.40-0.81)
S4 13,037 (21) 897 (16) 0.89 0.86 0.33 0.40 (0.17-0.58) 0.24 (0.14-0.40)

Event universe: what ROOTLINE can score vs what ATLAS labels

log ROOTLINE events ATLAS '+' lines scored attack events ratio
M1 h1 (CVE-2015-5122) 68,307 4,011 2,942 0.73
M1 h2 (CVE-2015-5122) 76,490 4,157 3,039 0.73
M2 h1 (CVE-2015-5119) 81,766 17,875 11,514 0.64
M2 h2 (CVE-2015-5119) 81,624 17,081 11,152 0.65
M3 h1 (CVE-2015-3105) 76,081 18,094 11,700 0.65
M3 h2 (CVE-2015-3105) 89,799 17,092 11,146 0.65
M4 h1 (CVE-2018-8174) 77,635 4,091 2,984 0.73
M4 h2 (CVE-2018-8174) 67,792 4,145 3,026 0.73
M5 h1 (CVE-2017-0199) 214,035 17,126 11,146 0.65
M5 h2 (CVE-2017-0199) 75,423 17,049 11,125 0.65
M6 h1 (CVE-2017-11882) 84,581 5,803 4,097 0.71
M6 h2 (CVE-2017-11882) 89,866 4,191 3,048 0.73
S1 (CVE-2015-5122) 53,538 4,598 3,363 0.73
S2 (CVE-2015-3105) 50,390 15,073 9,519 0.63
S3 (CVE-2017-11882) 68,835 5,165 3,689 0.71
S4 (CVE-2017-0199) 71,044 18,062 11,624 0.64

Unsupervised process ranking (ATLAS)

logs ranking n first malicious process at rank recall@10
M (held out) iforest 12 1.87 [1.76, 1.99] 0.776 [0.644, 0.893]
M (held out) iforest-no-userdir 12 2.33 [2.10, 2.61] 0.493 [0.436, 0.547]
M (held out) user-dir image first (heuristic) 12 1.00 [1.00, 1.00] 0.929 [0.786, 1.000]
M (held out) degree 12 5.08 [2.92, 7.00] 0.327 [0.261, 0.397]
S1-S4 (in-sample) iforest 4 1.75 (1.10-2.00) 0.863 (0.667-1.000)
S1-S4 (in-sample) iforest-no-userdir 4 2.40 (1.90-3.30) 0.667 (0.500-1.000)
S1-S4 (in-sample) user-dir image first (heuristic) 4 1.00 (1.00-1.00) 0.833 (0.333-1.000)
S1-S4 (in-sample) degree 4 4.25 (1.00-8.00) 0.500 (0.167-1.000)

Source: results/atlas.json, bench run 37092501921. IsolationForest per log is the mean over 10 seeds. Held-out rows give a 95 % log bootstrap, in-sample rows the range over the four logs.

held-out recall@10, paired over 12 logs mean difference [t(11) 95 % CI] logs higher / lower / tied exact sign test
user-dir heuristic minus IsolationForest +0.15 [-0.02, +0.33] 8 / 1 / 3 p = 0.039
IsolationForest minus IsolationForest without exec_user_dir +0.28 [+0.14, +0.42] 10 / 0 / 2 p = 0.002

Rule coverage, Splunk attack_data (v0.3 rules)

split captures unparsed v0.3 detected [Wilson 95 %] v0.3 same technique [Wilson 95 %] alert precision proxy: on-technique / all alerts
dev 46 1 34/45 [0.61, 0.86] 27/45 [0.45, 0.73] 84/105 [0.71, 0.87]
dev2 27 0 23/27 [0.68, 0.94] 14/27 [0.34, 0.69] 61/166 [0.30, 0.44]
sealed 160 96 4/64 [0.02, 0.15] 1/64 [0.003, 0.083] 1/5 [0.04, 0.62]
sealed-unseen-technique 73 45 2/28 [0.02, 0.23] 0/28 [0.00, 0.12] 0/2 [0.00, 0.66]
sealed-excluding-v03-target-techniques (derived, see note) 117 66 3/51 [0.02, 0.16] 1/51 [0.003, 0.103] -

Source: results/coverage.json, sealed-coverage run 36994398382 at 60e3561 (scored once; never re-scored). Wilson intervals are computed here from the counts. The alert-precision interval treats alerts as independent, but alerts cluster within captures, so it is too narrow. The derived row excludes sealed captures whose parent technique a v0.3 rule names (rootline.bench.sealed_excluding_v03_targets); it was added after the scoring run, computed from that run's committed rows, and does not appear in the run's own artefact.

Live eBPF in CI

CI evidence jobs passed [Wilson 95 %] chain recovered [Wilson 95 %] root causes (one query) story vertices alerts lost events
run 36996901332 (0e86829; run conclusion failure: lint failed, live-ebpf 5/5) 5/5 [0.57, 1.00] 5/5 /tmp/rootline-lab-stage.sh, 198.51.100.7:8081 28 RL-002, RL-003, RL-004, RL-005, RL-006, RL-007 0
repeatability: 15 push runs on main, 36996901332..37003827535 (0e86829..2aad2d0) 71/72 [0.925, 0.998] 72/72 [0.95, 1.00] /tmp/rootline-lab-stage.sh, 198.51.100.7:8081 28-54 same in every job 0

Source: results/live_ebpf.json; one row per job in repeatability.jobs, collected from the CI artefacts by scripts/live/collect_repeatability.py. Cancelled jobs (3) are excluded. The one failure: run 36998054457 matrix 4 (fork-parent check, since corrected; chain recovered). Dependabot-branch runs in the same period (not in the window): 10 runs (36998921068, 36998925449, 36998932197, 36998938464, 36999002032, 36999006264, 36999007966, 36999011841, 36999025961, 36999031176), 50/50 live-ebpf jobs passed.

Reading the comparison with published work

  • Event universe. ROOTLINE scores 63-73 % of the lines ATLAS labels as attack (table above): browser rows, pid-less rows and audit lines that carry no file or network operation have no syscall-level event. A recall measured against all + lines would be lower by that factor. ATLAS's own attack-event counts (Table 3) equal the + line counts for 9 of the 10 attacks; for M-3 the + lines number 35,186 against Table 3's 34,979.
  • Starting entity. The paper's Table 4 starts S-1, S-3, M-5 and M-6 from a malicious host, S-2, S-4, M-1 and M-2 from a leaked file, and M-3 and M-4 from a malicious file. ROOTLINE's IOC runs start from the attacker address, and the ablation uses every entity.
  • Same campaigns. The paper's Table 2 builds S-1/M-1, S-2/M-3, S-3/M-6 and S-4/M-5 from the same APT reports and exploits; only M-2 (CVE-2015-5119) and M-4 (CVE-2018-8174) are new. The ablation therefore reports the M2/M4 logs separately.
  • Training. ATLAS trains leave-one-attack-out on labelled sequences; ROOTLINE uses no labels. ATLAS's released evaluate.py scores a manually entered cleaned entity list (the script stops until one is filled in); the shipped eval_*.json files contain such a list, and for S1 it equals the ground truth exactly. Scored without that list, ATLAS's own raw model output gives entity F1 0.71 / 0.70 / 0.39 / 0.33 on S1-S4.
  • Entity universe. Our scorer counts ROOTLINE's abstracted entities (652 for S1, 11 of them malicious), the paper's Table 4 counts 7,467 (22 malicious). The like-for-like reference for our reproduction is therefore ATLAS's own cleaned list under our scorer (F1 0.86-1.00), not the paper's 0.89-1.00.
  • Our LSTM reproduction (repro/atlas_repro.py, PyTorch, 5 seeds, run in the manual atlas-repro workflow) follows ATLAS's released code (batch 1 and 8 epochs as in its atlas.py, sequence length 400). Mean entity F1 per scenario is 0.30-0.65 with a regenerated training set and 0.24-0.75 with ATLAS's shipped training set; the 5-seed ranges are in the table above. Differences that may explain the gap: Keras vs PyTorch initialisation, rapidfuzz vs fuzzywuzzy in undersampling, and the manual cleaning step. The paper's numbers are not reproduced.
  • Graph-traversal baseline. The paper's Table 5 traversal baseline (P 0.18, R 1.00) and ROOTLINE's naive reachability (P 0.15 on S1-S4, 0.26 on M hosts) agree, which is a useful check that the event universes are comparable at least for traversal methods.

Reduction

The reduction is lossless for causality by construction: it merges repeated edges only when no new information reached the source in between, and keeps the first edge of every (source, target, relation) key. "100 % of attack edges preserved" is therefore a property of the design, not an empirical finding. The empirical check is that stories with and without reduction are identical (rootline vs rootline-noreduce in the raw tables). On ATLAS it removes 3.8-4.35x of the edges on S1-S4 and 3.8-5.81x across all 16 logs; vertex counts do not change.

Raw tables

The full per-scenario tables (including IsolationForest ranking and the per-capture coverage rows) are on Raw benchmark tables.