Reproduce¶
Every published number comes from a script that writes a committed file under results/, and
every such file records where it was made: a provenance block (script, rootline version,
commit, GitHub workflow and run id, Python, platform) or, for the older CI files, run_id and
commit fields. Set ROOTLINE_DATA to choose where datasets go (default ~/.cache/rootline);
nothing is downloaded into the repository.
Setup¶
git clone https://github.com/rakshit-737/rootline-provenance-forensics && cd rootline-provenance-forensics
pip install -e ".[dev,bench]"
export ROOTLINE_DATA=~/rootline-data
python scripts/download_data.py --list # 78 files, 736.5 MB (the default set)
python scripts/download_data.py # ATLAS S1 zip, OTRF, Splunk dev + dev2; SHA-256 and size checked
python scripts/download_data.py --only atlas/S1.zip atlas/M1.zip # what the ATLAS benchmarks need (76 MB)
What regenerates what¶
| Result file | Command | Runtime | Where the committed file was made |
|---|---|---|---|
results/atlas.json, figures/atlas_prf.png |
python scripts/bench.py --only atlas |
85 s in CI (16 logs, IsolationForest x10 seeds x2 variants) | manual workflow bench, run 37092501921 at cde2a39 |
results/log4shell.json, log4shell_story.* |
python scripts/bench.py --only log4shell |
< 1 s | bench run 37092501921 |
results/ablation.json, figures/ablation*.png |
python scripts/ablation.py |
100 s in CI | bench run 37092501921 |
results/coverage_head.json (dev/dev2 at HEAD, in-sample) |
python scripts/bench.py --only coverage --allow-unfrozen |
about 2 min in CI, including the 0.72 GB download | bench run 37092501921 |
results/coverage.json, figures/tagger_coverage.png |
manual workflow sealed-coverage (runs scripts/verify_freeze.py, downloads the pinned captures, scripts/bench.py --only coverage) |
about 3 min (2 min 34 s) | CI run 36994398382 at 60e3561 (scored once) |
results/live_ebpf.json (run1_detail), live_ebpf_story.mmd |
live-ebpf job in ci.yml (scripts/live/run_chain.sh + assert_chain.py) |
about 2 min per job | CI run 36996901332 |
results/live_ebpf.json (repeatability) |
python scripts/live/collect_repeatability.py --first 36996901332 --last 37003827535 --dependabot-window 2026-10-01 2026-10-02 (downloads the live-ebpf artefacts with gh) |
about 2 min | collected 2026-10-03 from 15 ci runs |
results/atlas_repro.json |
manual workflow atlas-repro (repro/atlas_repro.py, 4 scenarios x 5 seeds, then --merge) |
about 8 min (20 parallel jobs, 1-5 min each) | CI run 36997669262 |
results/RESULTS.md |
python scripts/bench.py --render-only |
< 1 s | anywhere |
results/TABLES.md |
python scripts/render_tables.py (--check verifies it is current) |
< 1 s | anywhere |
python scripts/bench.py with no arguments runs atlas and log4shell and reuses the other
results, printing reused results/X.json (not re-run) for them. It exits with an error,
without writing anything, when a dataset it needs is missing.
The sealed coverage must not be re-scored with changed sources. scripts/bench.py --only
coverage runs scripts/verify_freeze.py first and refuses to write results/coverage.json
unless it passes, which at HEAD it does not: the loaders and normaliser were hardened after
scoring (state after scoring). Re-running the
sealed-coverage workflow at its default ref (the scored commit 60e3561) reproduces the
published numbers exactly. To confirm the freeze yourself:
python scripts/verify_freeze.py --ref 153eade # freeze commit: "freeze intact at 153eade"
python scripts/verify_freeze.py --ref 60e3561 # scored commit: "freeze intact at 60e3561"
python scripts/verify_freeze.py --ast 153eade --files src/rootline/rules_v03.py src/rootline/rules_linux.py src/rootline/detect.py
Expected headline numbers¶
After the commands above, results/TABLES.md should show (see Evaluation):
- Ablation, S1-S4:
fullprecision 0.54, recall 0.98, F1 0.66;naiveF1 0.25. - Ablation, M hosts (held out):
fullprecision 0.62, recall 0.72, F1 0.46;naiveF1 0.33; session-root stops +0.10 precision, higher on 12 of 12 logs. - Coverage, sealed: v0.3 detected 4/64 parsed captures.
- Live eBPF: each push's five live-ebpf jobs normally all pass; over the window above, 71 of 72 completed jobs passed and the chain was recovered in all 72.
The traversals are deterministic, and the CI runs set PYTHONHASHSEED=0. Regenerating the
ATLAS, ablation and Log4Shell files in CI reproduced every row of the earlier laptop files
exactly. IsolationForest uses seeds 0-9 and the LSTM reproduction seeds 0-4; their results can
vary within the reported spread across hardware and library versions.
Tests and docs¶
python -m pytest -q # unit + fixture tests; real-data tests skip without $ROOTLINE_DATA
python -m pytest -q -m realdata # the tests that need the downloads
pip install -r requirements-docs.txt && mkdocs build --strict # docs/hooks.py builds the demo and CLI page