Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Enrichers overview

An enricher runs over the ChangeSet produced by the diff core and adds risk-signal metadata to the rendered output without modifying the ChangeSet itself. Each is independent, has its own opt-out flag, and follows a best-effort contract: any failure (network, rate-limit, upstream API change) is logged once to stderr and the diff renders without that enricher’s findings.

Shipping enrichers

EnricherSourceNetwork?DefaultOpt-out flagCalibration
OSV.dev CVE lookupOSV.dev /v1/querybatch + /v1/vulns/{id}yeson--no-osv--cache-ttl-hours (v0.9.6)
EPSSFIRST.org /api/v1/epssyeson--no-epss--cache-ttl-hours; --fail-on-epss <0.0–1.0>
CISA KEVCISA known-exploited catalogyeson--no-kev--cache-ttl-hours; --fail-on kev
TyposquatEmbedded top-N lists, optional XDG cachenoon(none — pure compute)--typosquat-similarity-threshold (v0.9.6)
Multi-major version jumpThe diff itselfnoon(none — pure compute)(hard-coded MIN_MAJOR_DELTA = 2 — see chapter for rationale)
Maintainer ageGitHub REST /repos/.../contributors + /commitsyeson--no-maintainer-age--young-maintainer-days (v0.9.6)
Registry metadatanpm / PyPI / crates.io public APIsyeson (v0.9+)--no-registry--recently-published-days; --cache-ttl-hours
License policySBOM licenses field + SPDX expression evalnoon(configured by allow/deny lists)--allow-licenses, --deny-licenses, --allow-exception, --deny-exception
PluginsExternal-process plugins (v0.9.6+)variesoff (opt-in)(don’t pass --plugin)(per-plugin manifest)

Best-effort contract

Every enricher that touches the network honors the same contract:

  1. Per-request timeout (15s for OSV, 15s for GitHub) so a misbehaving upstream can’t hang a CI job.
  2. Errors warn, never block. A failed enricher logs one line to stderr (the warning is the same key every time, so it dedupes reasonably) and the diff renders without that enricher’s contributions.
  3. Rate-limit awareness. OSV’s /v1/querybatch is unauthenticated; the GitHub REST API honors GITHUB_TOKEN for the 5000/hr cap. On a 403 + X-RateLimit-Remaining: 0, the maintainer-age enricher returns whatever was already collected and warns once.
  4. Per-component caching within a single run. Repeated cs.added entries from the same project (e.g. monorepo subpackages sharing a GitHub repo) don’t multiply HTTP requests.

Determinism

Each enricher’s output is structured into the Enrichment graph (vulns: HashMap<...>, typosquats: Vec<...>, version_jumps: Vec<...>, maintainer_age: Vec<...>). Renderers iterate these in deterministic order — Vecs in their natural BTreeMap-derived order from the ChangeSet, the vulns HashMap with its keys sorted before emission.

This is the contract that lets peter-evans/create-or-update-comment upsert PR comments in place: identical inputs render to byte-identical output, so the comment body is patched only when the diff genuinely changes.

Why these signals?

The enricher set was chosen because each maps to a real, recent, high-impact incident class:

  • OSV.dev CVE lookup: published advisories, the broadest signal.
  • EPSS: probability of exploitation in next 30 days; dampens false-urgency on Critical-CVSS-but-low-exploitation advisories.
  • CISA KEV: known-exploited; the highest-confidence “act now” filter.
  • Typosquat: malicious packages mimicking popular ones (the plain-crypto-js axios dropper, the PyPI campaigns 2024–2026).
  • Multi-major version jump: takeover swaps, namespace reuse.
  • Maintainer age: long-game social-engineering campaigns (xz / Jia Tan).
  • Registry metadata: recently-published, deprecated, maintainer-set-changed — the npm Shai-Hulud-style worm precursors.
  • License policy: not a malicious-code signal but a policy gate that the same diff-time reviewer is best positioned to enforce.

For organizations with environment-specific rules outside this list, the v0.9.6 Plugins protocol lets you layer custom enrichers on top without forking bomdrift.

See also