Reproduce¶
Every published number comes from a script in benchmarks/ and a JSON file in
results/; each file records the
GitHub Actions run (or, for local files, the command and commit) that produced it, and the
Evaluation page lists them. The full-data runs execute in the
benchmarks workflow
on GitHub-hosted ubuntu-latest runners (4 vCPU, 16 GB RAM). Its inputs choose the legs, which
run in parallel; each leg downloads its own data and uploads results/ plus its log:
gh workflow run benchmarks.yml -f lineage=true -f legs='["lineage", "oracle", "materialized"]' \
-f typosquat=true -f images=true
Locally¶
Python 3.10+; about 1 GB of disk for OSV dumps, popularity lists and blobless clones (plus about 1.7 GB if you also fetch Syft, Trivy and the Trivy DB for the image benchmark).
pip install -e ".[bench,osv,crypto]" numpy
export TRACEGATE_DATA=$HOME/tracegate-data # anywhere outside the repo
python scripts/download_data.py osv # OSV dumps: PyPI, npm, Alpine, Go, crates.io, RubyGems, NuGet
python scripts/download_data.py popular # top-N name lists per ecosystem (--only pypi npm for lineage)
python scripts/download_data.py repos --pin-from results/data_manifest.json # the 11 repos at the committed run's commits
| Step | Command | Writes | Key expected output | Runtime on the runner |
|---|---|---|---|---|
| Backtracking, multi-version readers | python benchmarks/lineage_eval.py --snapshots 12 |
results/lineage_real_repos.json |
TOTAL {"tracegate": ..., "pickaxe-package-specific": ...} |
40 min 10 s |
| Backtracking, old one-version readers (same data) | python benchmarks/lineage_eval.py --snapshots 12 --single-version |
results/lineage_real_repos_single_version.json |
same, tracegate and baselines without pickaxes | 24 min 6 s |
| Adjudication evidence | python benchmarks/adjudicate.py --evidence, then verdicts by hand, then --score |
results/lineage_adjudication_evidence.json, lineage_adjudication.json |
one record per sampled disagreement | 7 s |
| Commit-message oracle | python benchmarks/lineage_eval.py --oracle-only |
results/lineage_bot_bump_oracle.json |
one [oracle <repo>] line per repository, then ORACLE {...} |
8 min 49 s |
| Reachability, per-snapshot sources | python benchmarks/lineage_eval.py --snapshots 12 --materialize --repos healthchecks netbox warehouse |
results/lineage_real_repos_materialized.json |
high+=... -> actionable=... per repo |
2 min 46 s |
| Reachability audit | python benchmarks/reach_audit.py --evidence, verdicts by hand, --score (needs pypi.org for release metadata) |
results/reachability_audit.json |
false-unreached rate with exact interval |
a few minutes locally |
| Typosquat, hash split | python benchmarks/typosquat_eval.py --eco PyPI npm RubyGems crates.io NuGet --dump ../dump |
results/typosquat_<eco>.json, typosquat_pr.png |
P / R / F1 / FPR per detector with bootstrap CIs | 7 min 26 s (run 37003433022) |
| Typosquat, time split | add --split time |
results/typosquat_<eco>_time.json |
same table, tuned on MAL records published before 2025-01-01 | 7 min 34 s |
| Original TypoGard / typomania | fetch typogard_npm.py at 9c10636, build typomania registry at 10e27e8 (see benchmarks.yml), then python benchmarks/originals_eval.py --dump ../dump --typogard ... --typomania ... |
results/typosquat_originals.json |
flag agreement port vs original per split | 3 min 27 s |
| Redraw the PR figure from committed JSON | python benchmarks/typosquat_eval.py --plot-only |
results/typosquat_pr.png |
the figure, labelled with its source run | seconds |
| Images | python scripts/download_data.py tools && trivy image --download-db-only --cache-dir $TRACEGATE_DATA/trivy-cache && python scripts/scan_real.py images && python benchmarks/images_eval.py |
results/images_real.json |
per-image Syft/Trivy counts, identity convergence | 1 min 19 s (pull + scan 61 s, benchmark 18 s, downloads 11 s) |
| Scale (synthetic) | python benchmarks/scale_eval.py |
results/scale_synthetic.json |
gate median per graph size | 3 s |
| Sigstore | sigstore workflow (needs GitHub OIDC) |
results/sigstore_evidence.json |
Rekor logIndex per artefact | - |
| Admission | kind-admission workflow |
results/kind_admission.json |
signed allowed, unsigned and incomplete-provenance denied | - |
| Result tables in README and docs | python scripts/render_results.py --write (CI runs --check) |
marked blocks in README.md, docs/*.md |
rendered result blocks in ... |
seconds |
| Docs site | python scripts/build_static_demo.py && mkdocs build --strict |
site/ |
the demo needs the first step (it writes docs/demo/data/, git-ignored) |
about 1 min |
Cloning the 11 repositories at their pinned commits takes about 2.5 minutes per leg. Runtimes are wall-clock step times in run 37091106360 (lineage, oracle and reachability legs), run
37088867445 (images and scale) and run 37003433022 (typosquat). The longest step is the
multi-version backtracking leg: mastodon's yarn.lock history makes thousands of git log -S
pickaxe calls over a 15,000-line file.
Live feeds¶
OSV dumps, popularity lists and the Trivy DB change daily, so a later re-run can shift finding
counts and typosquat numbers. Every leg stamps the dataset manifest it used into results/
(data_manifest.json, data_manifest_typosquat.json, data_manifest_images.json) with the
sha256 and fetch time of each file and the commit of each clone.