BLA
BLA (Blind LLM Agents) is the open-source Blue Defense MCP and CLI built to protect software artifacts against automated AI reverse-engineering workflows.
翻譯準備中
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Provenance-aware CI/CD security gate: attributes each scanner finding to the lock-file commit that introduced the vulnerable version (version diff, not line blame), inside a signed, fail-closed gate with blast radius and reachability triage
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
Which commit, and which PR, put this CVE in production? TRACEGATE answers from the lock-file history, inside a signed, fail-closed CI gate.
Contribution. TRACEGATE applies version-aware attribution to lock-file history: it credits each scanner finding to the commit whose diff introduced that exact (package, version) pair, not to whoever last rewrote the line, and it does this inside a fail-closed gate that verifies signed provenance for every pipeline stage. (How it works · Evaluation)檢視完整 README ↗Which commit, and which PR, put this CVE in production? TRACEGATE answers from the lock-file history, inside a signed, fail-closed CI gate.
Contribution. TRACEGATE applies version-aware attribution to lock-file history: it credits each scanner finding to the commit whose diff introduced that exact (package, version) pair, not to whoever last rewrote the line, and it does this inside a fail-closed gate that verifies signed provenance for every pipeline stage. (How it works · Evaluation)
Agreement with git blame | 92.4% of 3,969 vulnerable (snapshot, pin) pairs in 11 repos and 4 ecosystems (one-version-per-name readers on the same data: 88.5%); a package-specific git log -S pickaxe: 89.6%. Blame shares the lock-file reader, so this is agreement, not correctness. |
| Commit-message oracle | At the labelled commit TRACEGATE is right on 458/458 by construction, as is git blame (458/458). At the last later snapshot still pinning the version: TRACEGATE 436/436 (one-sided 95% exact lower bound 99.3%), git blame 419/436 (96.1%). The oracle tests version tracking under line rewrites, not attribution independent of the lock-file reader. |
| Typosquat (PyPI, supporting signal) | F1 0.133 at the dev-tuned threshold, 0.153 FPR-matched, vs Damerau-1 0.147; recall is low for every detector. |
Backtracking: run 37091106360; oracle: run 37091106360; typosquat: run 37003433022.
Docs: https://rakshit-737.github.io/tracegate-cicd-security-gate/ · live demo · how it works
Zero install: open the live demo. The cve-origin scenario loads with a BLOCK verdict; type CVE-2020-14343 and press Enter to see the origin story (PR #42) and the blast radius.
From source (v1.1.0 or later; do not pip install tracegate: that PyPI name belongs to an unrelated project):
python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate (Git Bash: . .venv/Scripts/activate)
pip install "git+https://github.com/rakshit-737/[email protected]"
tracegate demo # six scenarios, about 2 seconds
tracegate synth cve-origin ev.json
tracegate --demo backtrack ev.json CVE-2020-14343 # "introduced_by": {"pr": 42, ...}
tracegate --demo gate ev.json --comment # "## TRACEGATE: BLOCK", exit 1
--demo (accepted before or after the subcommand) trusts the public demo key. Without a configured key the gate refuses to run (exit 2). The v1.0.0 release (wheel and :1.0.0 image) predates both: it trusts the demo key implicitly (fail-open) and is superseded by v1.1.0.
Container (build from source): docker build -t tracegate . && docker run --rm -p 127.0.0.1:8080:8080 tracegate, then open http://127.0.0.1:8080 and run a scenario. Or pull ghcr.io/rakshit-737/tracegate-cicd-security-gate:1.1.2 (verify it as shown in SECURITY.md); the :1.0.0 image is the superseded fail-open build.
TRACEGATE is a provenance-aware CI/CD security gate. It merges real Syft SBOMs, Trivy scans, git history and OSV data into one signed, content-addressed provenance graph, then answers the question most scanners leave open: which commit, and which PR, put this CVE in production, and what else inherits it?
# via); an audit of the earlier downgrades is in the Evaluation.Every number below is rendered from the committed JSON in results/ by scripts/render_results.py, and CI fails if a table and its file disagree. Each file names the Actions run that produced it; the Evaluation page lists which run produced which file, with the methodology, every table and interval. Commands: Reproduce.
| Question | Data | TRACEGATE | Baselines and ablations |
|---|---|---|---|
Finding -> introducing commit, agreement with git blame --first-parent | 3,969 vulnerable (snapshot, pin) pairs, 132 snapshots of 11 repos, 4 ecosystems | 92.4% [Wilson 91.5-93.2; repo-clustered 82.7-99.5]; old one-version readers on the same data 88.5% | package-specific git log -S 89.6%; version-line git log -S 72.2%; last manifest commit 13.8%; first mention 13.1% |
| ... per ecosystem | pip 362 / Go 328 / Cargo 523 / npm (yarn, pnpm) 2,756 pairs | 97.8% / 99.7% / 99.2% / 89.5% | package-specific git log -S: 100.0% / 100.0% / 99.0% / 85.2% |
| Finding -> introducing commit, commit-message oracle (4,532 labelled bumps, reverts and re-bumps; 458 sampled) | at the labelled commit / at the last later snapshot still pinning it | 458/458 (by construction) / 436/436 (one-sided 95% exact >= 99.3%) | git blame 458/458 / 419/436 (96.1%); package-specific git log -S 403/436 later; old one-version readers 13/67 on the multi-version stratum; no recency rule 0/40 on reverts and re-bumps |
| Reachability: high/critical findings left actionable | 887 high+ OSV findings, 36 snapshots of 3 Python repos, own sources | 887 (0 downgraded): the previous rule's downgrades were audited and 17/17 pins were loaded (exact 95% 80.5-100.0), so without recorded dependency edges nothing is downgraded now | 887 (raw scanner output) |
| Typosquat, PyPI (hash split, test half) | 5,964 OSV MAL-* names vs 4,973 packages ranked 5k-15k | F1 0.133 at the dev-tuned default (FPR 1.7%); 0.153 FPR-matched to Damerau-1 (paired F1 difference [+0.002, +0.011]) | Damerau-1 0.147; typomania/TypoGard port 0.125; our pypi-scan port 0.112 |
| Keyless signing | dev build cfd215e wheel + sdist (evidence); v1.1.0 release bundles on the release page | signed with GitHub OIDC, verified, Rekor entries checked | - |
| Admission | kind cluster, 3 images (decisions) |
What the numbers mean, stated plainly:
git blame is not correctness, and the sampled disagreements are blame's. Blame and the pickaxes credit whoever last wrote the pin line. On pip and Go TRACEGATE disagrees with them on 9 pairs; those, plus a uniform random sample of 50 of the 293 Cargo and npm disagreements, were read against the diffs. In 59 of 59 the commit blame names already pinned the version at its parent: merges, pip-compile rewrites that lower-cased names, the Yarn 4 and pnpm format migrations, unrelated bumps that re-sorted lines. TRACEGATE's commit made the version appear in 59/59 (Wilson 95% 94-100%). The reading rule is TRACEGATE's own definition of "introducing commit" (newest commit whose diff makes the pair appear), so this shows the disagreements are line rewrites, not that the lock-file reader is right where all methods agree.git log -S is a strong baseline. With a token that names the package (Cargo's name/version pair, yarn's block header plus version), the pickaxe ties TRACEGATE on Cargo (519/523 vs 518/523; exact McNemar p = 1.00) and on Go (327/328 vs 328/328; p = 1.00), and agrees with blame more often on pip (362/362 vs 354/362; p = 0.008). On npm (yarn, pnpm) TRACEGATE agrees more often (89.5% vs 85.2%; 360 : 242 discordant, p = 1.7e-06), but the repo-clustered interval of that difference includes 0. The v1.1.0 headline gap on Cargo (95.0% vs 54.7%) came from a version-line token that matches every crate at that version; it is kept as the version line column and is not a fair baseline.main into minor and lock-file format migrations that blame credits.| Image | Syft pkgs | Layers | Trivy findings | matched, naive name@version | matched, raw purl string | matched, canonical purl | Syft/Trivy SBOM Jaccard |
|---|---|---|---|---|---|---|---|
| alpine:3.14.2 | 14 | 1 | 43 | 43 | 0 | 43 | 1.00 |
| httpd:2.4.49-alpine3.14 | 49 | 5 | 99 | 99 | 0 | 99 | 0.97 |
| memcached:1.6.10-alpine3.14 | 33 | 6 | 44 | 44 | 0 | 44 | 0.95 |
| nginx:1.21.3-alpine | 56 | 6 | 105 | 105 | 0 | 105 | 1.00 |
| node:14.17.6-alpine3.14 | 430 | 4 | 94 | 94 | 51 | 94 | 1.00 |
| python:3.9.7-alpine3.14 | 64 | 5 | 83 | 83 | 12 | 83 | 0.97 |
| redis:6.2.5-alpine3.14 | 32 | 6 | 43 | 43 | 0 | 43 | 0.94 |
All 511 Trivy rows: naive 100.0%, raw purl 12.3%, canonical 100.0%. Merged graph: 535 nodes, 637 edges; 219 unique finding nodes (146 CVEs), a 2.33x de-duplication; verdict block; 0 unattributed findings. Source: results/images_real.json, run 37088867445.
Warden (dependency-risk) scoring of the 401 language packages in these images flags 3. One is npm-cli-docs: OSV has an all-versions MAL record for that public name, and npm bundles an internal package with the same name. The other two are typosquat-heuristic false positives on legitimate packages (ansistyles, uid-number). The cause of an earlier batch of 13 false MAL flags (Sept-2025 npm hijack records that list only the trojanised versions) is covered in ADR 0006.
| Services | Deps | Events | Nodes | Edges | Gate median |
|---|---|---|---|---|---|
| 10 | 50 | 41 | 104 | 562 | 0.004 s |
| 50 | 100 | 201 | 354 | 5,302 | 0.038 s |
| 100 | 200 | 401 | 704 | 20,602 | 0.132 s |
| 200 | 400 | 801 | 1,404 | 81,202 | 0.622 s |
Synthetic data; source results/scale_synthetic.json, run 37088867445.
1. Ingest, sign, verify
flowchart TB
subgraph SRC["Real pipeline inputs"]
GIT["git history of lock files: pip, poetry, uv, npm, yarn, pnpm, Go, Cargo"]
SY["Syft JSON / CycloneDX"]
TV["Trivy JSON"]
SA["SARIF: Semgrep / Bandit / CodeQL"]
DEP["deploy / runtime facts"]
end
GIT --> LIN["gitlineage: commit events"]
SY --> ING["ingest adapters"]
TV --> ING
SA --> ING
LIN --> ENV["DSSE envelopes: Ed25519, HMAC, or keyless (Sigstore-bound key)"]
ING --> ENV
DEP --> ENV
ENV --> COL["collector: verify, fail closed"]
COL --> G[("provenance DAG, purl + digest ids")]
2. Enrich, decide, query
flowchart TB
G[("provenance DAG")] --> EN["enrich"]
OSV[("OSV bulk dumps")] --> IDX["OsvIndex"] --> W["HeuristicWarden: MAL records + typosquat"] --> EN
RE["static / runtime reachability"] --> EN
EN --> POL["policy: Python DSL = Rego port"] --> OUT["PR comment + exit code"]
G --> BT["backtrack + blast radius"]
G --> EXP["exports: Cypher/Neo4j, in-toto SLSA, OPA input"]
G --> API["FastAPI + lineage explorer UI"]
Graph shape: commit -introduced-> dependency -installed_in-> layer -layer_of-> image -> deployment -> container, plus commit -> build -> image. Node ids are content-addressed. Dependencies are keyed by a canonical purl, so a package seen in the manifest, by Syft and by Trivy becomes one node, and a shared base layer is one node across every image (ADR 0001).
| Module | File | Notes |
|---|---|---|
| Contracts, content addressing | models.py, ids.py | canonical purls, memoised |
| Signing | signing.py | DSSE PAE; Ed25519 (cryptography) or HMAC; in-toto v1 / SLSA statements |
| Collector | collector.py | verifies every envelope; missing or invalid provenance blocks |
| Real-tool ingest | ingest.py | Syft JSON, CycloneDX, Trivy JSON (unchanged tool output) |
| Git lineage | gitlineage.py | first-parent manifest walk, follows renames, PR numbers from subjects |
| OSV index | osv.py | offline, streams official zip dumps; ECOSYSTEM/SEMVER ranges; CVSS v3 |
| Typosquat / Warden | typosquat.py, warden.py | deletion-index edit distance, transposition, separator, homoglyph, suffix, combosquat; MultiWarden per ecosystem; WardenApiClient for the real Warden service |
| Reachability | reach.py, enrich.py | runtime facts first, static fallback (ADR 0004) |
| Policy | policy.py, policies/tracegate.rego | deterministic; Rego parity checked in CI (ADR 0003) |
| Backtrack | backtrack.py | origin story + blast radius |
| Exports | export.py | Neo4j Cypher, JSON, OPA input, in-toto |
| Service | api.py, ui/index.html | FastAPI + dependency-free lineage explorer |
Reachability tiers: imported (AST imports and dotted strings), entrypoint (named in the Dockerfile, Procfile, CI or config), transitive (pip-compile # via edges, and implied framework dependencies such as django.db.backends.postgresql -> psycopg), referenced (bare string constants such as passlib's "argon2"), unreached (none of these, in a manifest that records dependency edges) and unknown (none of these, but the manifest records no edges). Only unreached findings are downgraded.
git clone https://github.com/rakshit-737/tracegate-cicd-security-gate && cd tracegate-cicd-security-gate
pip install -e ".[dev]" # the core gate is stdlib-only; extras add crypto/osv/api
python -m pytest -q # about 100 tests; real-data tests skip without datasets
python -m tracegate.cli demo # the six spec scenarios
Gate a real image scan with Ed25519-signed provenance:
tracegate keygen ~/.tracegate/ci # keep keys outside the repo; the .key is written 0600
export TRACEGATE_KEYID=ci TRACEGATE_SIGNING_KEY=~/.tracegate/ci.key TRACEGATE_PUBKEY=~/.tracegate/ci.pub
syft <image> -o syft-json=sbom.json
trivy image --format json -o trivy.json <image>
tracegate ingest --syft sbom.json --trivy trivy.json --commit $(git rev-parse HEAD) -o img.json
tracegate lineage . requirements.txt -o commits.json # run inside an app repo with a pinned manifest
tracegate merge commits.json img.json -o events.json
tracegate gate events.json --comment # exit 1 on block
tracegate backtrack events.json CVE-2023-0465
tracegate export events.json --format intoto > provenance.intoto.json
tracegate export events.json --format cypher | cypher-shell -u neo4j -p ...
Service and UI: tracegate --demo serve starts the API with the lineage explorer at http://127.0.0.1:8080 and the built-in scenarios (public demo key). For a real gate give it a trust root, for example TRACEGATE_KEYID=ci TRACEGATE_PUBKEY=~/.tracegate/ci.pub tracegate serve; with no trust root it refuses to start (exit 2). Set TRACEGATE_API_TOKEN before exposing it beyond localhost. NEO4J_PASSWORD=... TRACEGATE_PUBKEY_FILE=~/.tracegate/ci.pub docker compose up --build adds Neo4j (localhost-only ports); compose refuses to start without NEO4J_PASSWORD.
Demo scenarios (these follow the spec):
| Scenario | Outcome |
|---|---|
clean | pass; also prints the base-layer blast radius |
malicious-dep | typosquat blocked, with the commit -> dependency path |
cve-origin | reachable CVE blocked; the origin story names PR #42 and 3 services |
unreachable | critical CVE in a module that is never loaded -> warn |
tampered | forged scan attestation -> block (fail closed) |
unsigned-missing | missing stage -> block |
Nothing large is committed. scripts/download_data.py fetches everything into $TRACEGATE_DATA (default: a sibling ../../datasets/tracegate if present, else ./data/, which is git-ignored) and records a sha256, size and fetch time for each file, and the head commit of each clone, in MANIFEST.json; each benchmark leg copies that manifest into results/ stamped with its run. download_data.py repos --pin-from results/data_manifest.json checks out the exact commits a committed run used. Tool binaries are verified against the vendors' checksum files.
| Data | Used by | Source | Size | Fetched (UTC) | sha256 / commit | Licence or terms |
|---|---|---|---|---|---|---|
osv/Alpine-all.zip | backtracking, oracle (run 37091106360) | osv-vulnerabilities.storage.googleapis.com/Alpine/all.zip | 4.0 MB | 2026-10-03T02:49 | c3a745816d2c | per source, mostly CC-BY-4.0 (OSV, GHSA, PyPA, Go); MAL-* records from ossf/malicious-packages, Apache-2.0 |
osv/Go-all.zip | backtracking, oracle (run 37091106360) | osv-vulnerabilities.storage.googleapis.com/Go/all.zip | 12.0 MB | 2026-10-03T02:49 | 77bb6a0cb7bc | per source, mostly CC-BY-4.0 (OSV, GHSA, PyPA, Go); MAL-* records from ossf/malicious-packages, Apache-2.0 |
osv/NuGet-all.zip | backtracking, oracle (run 37091106360) | osv-vulnerabilities.storage.googleapis.com/NuGet/all.zip | 2.5 MB | 2026-10-03T02:49 | a58050f8574f | per source, mostly CC-BY-4.0 (OSV, GHSA, PyPA, Go); MAL-* records from ossf/malicious-packages, Apache-2.0 |
osv/PyPI-all.zip | backtracking, oracle (run 37091106360) | osv-vulnerabilities.storage.googleapis.com/PyPI/all.zip | 35.4 MB | 2026-10-03T02:49 | 7c3a5ff9e05d | per source, mostly CC-BY-4.0 (OSV, GHSA, PyPA, Go); MAL-* records from ossf/malicious-packages, Apache-2.0 |
osv/RubyGems-all.zip | backtracking, oracle (run 37091106360) | osv-vulnerabilities.storage.googleapis.com/RubyGems/all.zip | 5.0 MB | 2026-10-03T02:49 | 11454ea786ae | per source, mostly CC-BY-4.0 (OSV, GHSA, PyPA, Go); MAL-* records from ossf/malicious-packages, Apache-2.0 |
osv/crates.io-all.zip | backtracking, oracle (run 37091106360) | osv-vulnerabilities.storage.googleapis.com/crates.io/all.zip | 3.5 MB | 2026-10-03T02:49 | a461c7e74e7a | per source, mostly CC-BY-4.0 (OSV, GHSA, PyPA, Go); MAL-* records from ossf/malicious-packages, Apache-2.0 |
Rendered from results/data_manifest.json, data_manifest_typosquat.json and data_manifest_images.json; the 7 Docker images are pulled by tag and sha256-verified by scripts/pull_image.py and never run. The Trivy vulnerability DB is a live feed and its version is not pinned.
Small fixtures derived from real tool output (tests/fixtures/alpine.syft.json, alpine.trivy.json, osv_sample.json) keep CI independent of the downloads.
The full-data runs execute in the benchmarks workflow (gh workflow run benchmarks.yml, inputs choose the legs); the Reproduce page lists every command, its output file and its runtime on the runner. Locally, each step is a single command:
python scripts/download_data.py osv && python scripts/download_data.py popular
python scripts/download_data.py repos --pin-from results/data_manifest.json # the committed run's commits
python benchmarks/lineage_eval.py --snapshots 12 # -> results/lineage_real_repos.json
python benchmarks/lineage_eval.py --snapshots 12 --single-version # -> ..._single_version.json (old readers)
python benchmarks/lineage_eval.py --oracle-only # -> results/lineage_bot_bump_oracle.json
python benchmarks/lineage_eval.py --snapshots 12 --materialize --repos healthchecks netbox warehouse
python benchmarks/adjudicate.py --evidence && python benchmarks/reach_audit.py --evidence
python benchmarks/typosquat_eval.py --eco PyPI npm RubyGems crates.io NuGet # add --split time
python scripts/download_data.py tools && python scripts/scan_real.py images && python benchmarks/images_eval.py
python benchmarks/scale_eval.py
python scripts/render_results.py --write # re-render every results table in README.md and docs/
OSV dumps, popularity lists and the Trivy DB are live feeds, so a re-run on a later date can shift finding counts and typosquat numbers; the sha256 of what each run used is in its results/data_manifest*.json.
Evaluation design, including the splits, the references and why download counts are not used as a typosquat feature, is in ADR 0005.
| Work | What it does | TRACEGATE |
|---|---|---|
| Syft | SBOM generation | consumes its unmodified JSON as the build/layer stage |
| Trivy / Grype | per-artifact vuln scanning | attaches findings to shared graph nodes and adds commit lineage and blast radius |
| Sigstore / SLSA / in-toto | attestation formats and verification | emits DSSE + in-toto SLSA statements; its contribution is the queryable graph on top |
| GUAC (OpenSSF) | supply-chain metadata graph | GUAC is a large multi-service aggregator; TRACEGATE is a stdlib-only CI gate with deterministic verdicts and commit-level backtracking |
| Snyk / GitHub Advanced Security | commercial suites | open source, and gives one graph you can query across stages |
| SZZ (Śliwerski, Zimmermann and Zeller, MSR 2005) and its variants: AG-SZZ (Kim et al., ASE 2006) skips cosmetic changes, MA-SZZ (da Costa et al., IEEE TSE 2017) skips meta-changes such as merges | find bug-introducing commits by blaming the lines a fix changed | a different question (which commit introduced a dependency version) on lock files, where line blame suffers the same cosmetic and meta-change failures; TRACEGATE compares (package, version) pairs instead of lines. git blame and two git log -S pickaxes are its line baselines |
| Rosa et al., developer-informed SZZ oracle (ICSE 2021); Lyu et al., SZZ on the Linux kernel (IEEE TSE; arXiv 2308.05060) | evaluate SZZ implementations against labels from developers / commit messages | the bot-bump oracle takes the same approach: labels from commit messages |
| typomania / TypoGard (Taylor et al., NSS 2020), pypi-scan | name similarity | original typomania and TypoGard code run on the same splits in CI (port agreement 98.99-100%); pypi-scan is our port (see Evaluation) |
SBOMs, scanning, attestation formats and line-based attribution are not novel. The contribution is applying version-aware attribution, diffing (package, version) pairs rather than lines, to lock-file history, inside a fail-closed, signature-verified CI gate. Cross-tool identity, reachability and typosquat scoring are supporting components.
git blame is not correctness; the commit-message oracle is right for TRACEGATE by construction at the labelled commit and tests version tracking under rewrites at later snapshots, not attribution independent of the lock-file reader. Disagreements were adjudicated for a sample only (Evaluation).# via annotations the gate cannot tell a transitive dependency of an imported package from an unused one, so it no longer downgrades there. An audit of every earlier downgrade found all of them loaded (Evaluation). Dynamic imports, plugins loaded by name from settings and C-extension loading stay invisible, and the sources are written by the PR under review, so static downgrades must not be trusted on untrusted PRs. No eBPF or /proc/*/maps runtime collector ships.cosign; envelopes themselves are Ed25519. The kind admission demo is a gate step before kubectl apply, not an in-cluster validating webhook.WardenApiClient follows the real Warden POST /api/v1/scans schema, but no end-to-end run against a live Warden is in CI; offline results use HeuristicWarden.tracegate ingest --sarif, tested on Bandit-style fixtures). SAST findings are attached to files, not to dependencies, so reachability does not apply to them.sys.modules runtime collector (needs a Linux runtime; not feasible on the Windows dev machine).# via annotations.TRACEGATE is a defensive, lab-only tool. It reads pipeline metadata that you produce. Images are pulled as tarballs, unpacked with path and link sanitisation, and catalogued; they are never executed. No malware is downloaded. Malicious packages are known only by name and OSV record. Nothing in this repo scans third-party systems. See THREAT_MODEL.md and SECURITY.md.
See CONTRIBUTING.md and CHANGELOG.md. Design decisions are in docs/adr. MIT licensed; see LICENSE.
| admits only the image with a valid signature and complete signed provenance |
| cosign alone would admit the third image |
| Gate latency (median per repo) | real lineage graphs | pip 7-70 ms, Go 283-304 ms, Cargo 40-129 ms, npm (yarn, pnpm) 126-483 ms; max 1619 ms (per snapshot graph) | - |
unreached# viaMAL-* names are spam or dependency confusion). The test sets are mostly malicious (PyPI 55%, npm 96%), so precision there is not deployment precision: at a 1% base rate TRACEGATE's precision would be about 3-4%. On a time split (tune before 2025, test after) the top-5k detectors roughly halve (TRACEGATE F1 0.059 at the dev-tuned default, 0.083 FPR-matched, vs Damerau-1 0.066). On RubyGems the 14-name difflib check beats every top-5k detector, TRACEGATE included (F1 0.213 vs 0.075), because the MAL-* names there are floods of variants of a few top gems; on crates.io and NuGet no detector is useful.osv/npm-all.zip | backtracking, oracle (run 37091106360) | osv-vulnerabilities.storage.googleapis.com/npm/all.zip | 217.4 MB | 2026-10-03T02:49 | 303a96ffacce | per source, mostly CC-BY-4.0 (OSV, GHSA, PyPA, Go); MAL-* records from ossf/malicious-packages, Apache-2.0 |
popular/npm-high-impact-top.js | backtracking, oracle (run 37091106360) | raw.githubusercontent.com/wooorm/npm-high-impact/main/lib/top.js | 0.4 MB | 2026-10-03T02:49 | bbc16e783283 | wooorm/npm-high-impact, MIT |
popular/top-pypi-packages.min.json | backtracking, oracle (run 37091106360) | hugovk.dev/top-pypi-packages/top-pypi-packages.min.json | 0.8 MB | 2026-10-03T02:49 | 55fee05ed02b | hugovk/top-pypi-packages (no licence file; public BigQuery download counts) |
repos/alacritty | backtracking, oracle (run 37091106360) | github.com/alacritty/alacritty.git | blobless clone | 2026-10-03T02:50 | d692748d3f61 | Apache-2.0 |
repos/bat | backtracking, oracle (run 37091106360) | github.com/sharkdp/bat.git | blobless clone | 2026-10-03T02:50 | 4608fc959aa8 | MIT OR Apache-2.0 |
repos/caddy | backtracking, oracle (run 37091106360) | github.com/caddyserver/caddy.git | blobless clone | 2026-10-03T02:50 | ac834b5dc70a | Apache-2.0 |
repos/excalidraw | backtracking, oracle (run 37091106360) | github.com/excalidraw/excalidraw.git | blobless clone | 2026-10-03T02:50 | ed10ac7dca7e | MIT |
repos/healthchecks | backtracking, oracle (run 37091106360) | github.com/healthchecks/healthchecks.git | blobless clone | 2026-10-03T02:49 | e566e1c40099 | BSD-3-Clause |
repos/hugo | backtracking, oracle (run 37091106360) | github.com/gohugoio/hugo.git | blobless clone | 2026-10-03T02:50 | 6b3ba3a7e22a | Apache-2.0 |
repos/mastodon | backtracking, oracle (run 37091106360) | github.com/mastodon/mastodon.git | blobless clone | 2026-10-03T02:51 | 73fe2b73467a | AGPL-3.0 |
repos/netbox | backtracking, oracle (run 37091106360) | github.com/netbox-community/netbox.git | blobless clone | 2026-10-03T02:49 | 251458b89a5e | Apache-2.0 |
repos/ripgrep | backtracking, oracle (run 37091106360) | github.com/BurntSushi/ripgrep.git | blobless clone | 2026-10-03T02:50 | 3fce3b5bb023 | Unlicense OR MIT |
repos/vue-core | backtracking, oracle (run 37091106360) | github.com/vuejs/core.git | blobless clone | 2026-10-03T02:51 | 4ab865a848a1 | MIT |
repos/warehouse | backtracking, oracle (run 37091106360) | github.com/pypi/warehouse.git | blobless clone | 2026-10-03T02:50 | 81b91d6b8199 | Apache-2.0 |
osv/Go-all.zip | typosquat (run 37003433022) | osv-vulnerabilities.storage.googleapis.com/Go/all.zip | 12.0 MB | 2026-10-02T11:52 | daba95c43d1d | per source, mostly CC-BY-4.0 (OSV, GHSA, PyPA, Go); MAL-* records from ossf/malicious-packages, Apache-2.0 |
osv/PyPI-all.zip | typosquat (run 37003433022) | osv-vulnerabilities.storage.googleapis.com/PyPI/all.zip | 35.3 MB | 2026-10-02T11:52 | f2248ff61726 | per source, mostly CC-BY-4.0 (OSV, GHSA, PyPA, Go); MAL-* records from ossf/malicious-packages, Apache-2.0 |
osv/RubyGems-all.zip | typosquat (run 37003433022) | osv-vulnerabilities.storage.googleapis.com/RubyGems/all.zip | 5.0 MB | 2026-10-02T11:52 | b4f202bd02af | per source, mostly CC-BY-4.0 (OSV, GHSA, PyPA, Go); MAL-* records from ossf/malicious-packages, Apache-2.0 |
osv/crates.io-all.zip | typosquat (run 37003433022) | osv-vulnerabilities.storage.googleapis.com/crates.io/all.zip | 3.5 MB | 2026-10-02T11:52 | cd0cdc315668 | per source, mostly CC-BY-4.0 (OSV, GHSA, PyPA, Go); MAL-* records from ossf/malicious-packages, Apache-2.0 |
osv/npm-all.zip | typosquat (run 37003433022) | osv-vulnerabilities.storage.googleapis.com/npm/all.zip | 217.3 MB | 2026-10-02T11:52 | d5432a4ae379 | per source, mostly CC-BY-4.0 (OSV, GHSA, PyPA, Go); MAL-* records from ossf/malicious-packages, Apache-2.0 |
popular/crates-top.json | typosquat (run 37003433022) | crates.io/api/v1/crates | 0.5 MB | 2026-10-02T11:54 | 3e53adbbe87d | crates.io API (names and download counts; crates.io data access policy) |
popular/nuget-top.json | typosquat (run 37003433022) | azuresearch-usnc.nuget.org/query | 0.2 MB | 2026-10-02T11:58 | e85f38327093 | NuGet search API (names and download counts; nuget.org terms of use) |
popular/rubygems-top.json | typosquat (run 37003433022) | packages.ecosyste.ms/api/v1/registries/rubygems.org/packages | 0.5 MB | 2026-10-02T11:58 | a0212eace429 | packages.ecosyste.ms (data CC BY-SA 4.0) |
bin/syft_1.52.0_linux_amd64.tar.gz | images (run 37088867445) | github.com/anchore/syft/releases/download/v1.52.0/syft_1.52.0_linux_amd64.tar.gz | 29.3 MB | 2026-10-03T02:10 | caeedb81fb04 | anchore/syft release, Apache-2.0 |
bin/trivy_0.74.0_Linux-64bit.tar.gz | images (run 37088867445) | github.com/aquasecurity/trivy/releases/download/v0.74.0/trivy_0.74.0_Linux-64bit.tar.gz | 50.4 MB | 2026-10-03T02:10 | 2ae6fe3ee734 | aquasecurity/trivy release, Apache-2.0 |