Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
40a60ca
docs(roadmap): close out future candidates with v0.9.6 dispositions
Apr 30, 2026
8c7b24e
refactor(cache): single source of truth for CACHE_TTL_SECS
Metbcy Apr 30, 2026
74ead20
feat(examples/plugins): banned-packages worked example
Apr 30, 2026
d966c60
docs(attestation): chapter on cosign-verified SBOM attestations
Apr 30, 2026
c0c6f9e
docs(plugins): chapter on external-process plugin protocol
Apr 30, 2026
40c54fb
feat(calibration): configure typosquat similarity, young-maintainer d…
Metbcy Apr 30, 2026
e86e121
feat(attestation): verify cosign-signed SBOM attestations from OCI re…
Metbcy Apr 30, 2026
9e5900d
feat(plugin): external-process plugin system with TOML manifests
Metbcy Apr 30, 2026
c6dbde6
merge: v0.9.6 rust (cache-TTL unify, calibration knobs, OCI attestati…
Metbcy Apr 30, 2026
b658556
merge: v0.9.6 platform (roadmap cleanup, plugin example, attestation …
Metbcy Apr 30, 2026
2ddcddf
docs(readme,status): refresh feature list, comparison, non-goals + v0…
Apr 30, 2026
fbe1732
docs(intro): refresh pitch for v0.9.6 + add navigation footer
Apr 30, 2026
96c9929
docs(quickstart,github-action): refresh examples + document upload-to…
Apr 30, 2026
25df9c5
docs(cli-reference): full flag audit for v0.9.6 — group by purpose, a…
Apr 30, 2026
b390151
docs(architecture, contributing): module map + best-effort + byte-det…
Apr 30, 2026
e668b27
docs(baseline): schema reference for unified BaselineEntry
Apr 30, 2026
4a7ec6e
docs(enrichers): per-enricher chapter audit — calibration knobs, disa…
Apr 30, 2026
1b30a6d
docs(summary): reorganize navigation into Output / Enrichers / Suppre…
Apr 30, 2026
fc9888c
docs: version-pin sweep + cross-link cleanup
Apr 30, 2026
035207b
chore(release): prepare v0.9.6
Metbcy Apr 30, 2026
1d2401a
fix(plugin): cfg-gate test helper to match its callers (windows dead-…
Metbcy Apr 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,145 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.9.6] - 2026-04-29

The "finish the roadmap" milestone. v0.9.6 closes out every entry in the
prior roadmap's "Future candidates (not committed)" section — by shipping
it, by closing it as a non-goal, or by documenting the explicit upstream
blocker. Headline new features: **OCI attestation verification** via
`cosign verify-attestation`, an **external-process plugin system** for
custom rules, full **CLI calibration knobs** for the remaining hardcoded
thresholds, and a **comprehensive documentation refresh** across every
chapter to reflect v0.7 → v0.9.6 reality.

### Added

- **OCI attestation verification.** `bomdrift diff
--before-attestation <oci-ref> --after-attestation <oci-ref>` shells
out to `cosign verify-attestation --type=cyclonedx`, parses the
in-toto envelope, and feeds the verified SBOM payload into the
standard parser. New `--cosign-identity <regex>` and
`--cosign-issuer <url>` flags pass through to cosign's
`--certificate-identity-regexp` / `--certificate-oidc-issuer`. New
`--require-attestation` boolean refuses falling back to the
`--before` / `--after` file flags so production CI gates can enforce
attested SBOMs only. Documented in `docs/src/attestation.md`. New
`attestation` row in `--debug-calibration` so users can confirm
cosign accepted the right cert.
- **External-process plugin system.** New `--plugin
<path-to-plugin.toml>` flag (repeatable). Plugin manifest in TOML
(`name` / `description` / `exec` / `timeout_ms` / `invoke_on`).
bomdrift invokes the plugin once per Added or VersionChanged
component with JSON on stdin (`{component, event, before}`) and
parses JSON from stdout (`{findings: [...]}`). Best-effort: timeout,
non-zero exit, or malformed JSON drops the offending plugin's
findings and logs a warning at `BOMDRIFT_DEBUG=1`; the rest of the
diff renders. New `bomdrift.plugin` SARIF rule with stable
`partialFingerprints` per `(plugin_name, purl, rule_id)`. Worked
example shipped under `examples/plugins/banned-packages/`.
Documented in `docs/src/plugins.md`. Protocol carries
`protocol_version: 1` for forward-compat.
- **CLI calibration knobs** for the three previously-hardcoded
thresholds:
- `--typosquat-similarity-threshold <FLOAT>` (default `0.92`,
validated 0.0..=1.0).
- `--young-maintainer-days <i64>` (default `90`, validated >= 1).
- `--cache-ttl-hours <u64>` (default `24`, validated >= 1; applies
uniformly to OSV / EPSS / KEV / Registry caches).
- Matching `[diff]` config keys: `typosquat_similarity_threshold`,
`young_maintainer_days`, `cache_ttl_hours`.
- `--debug-calibration` rows now emit the *active* threshold rather
than the hardcoded default, so calibration data collection
reflects the real run.

### Changed

- **`CACHE_TTL_SECS` unified.** Previously duplicated in four
modules (`src/enrich/{cache,epss,kev,registry}.rs`). Now a single
source of truth in `src/enrich/cache.rs` with `effective_ttl_secs`
helper that honors per-run overrides without globals.
- **Comprehensive documentation refresh** across every chapter.
Notable updates:
- `README.md` rewritten with capability-grouped feature list
(Ingest / Enrichers / Suppression / Output / Forge /
Extensibility / Packaging) and a 5-column comparison table
against Socket / Snyk / Trivy / OSV-Scanner / Grype with 11
feature-row dimensions sourced from the v0.7-v0.9 competitor
research doc.
- `docs/src/cli-reference.md` rewritten end-to-end. Every CLI flag
now documented and grouped by purpose (Output / Suppression /
Enrichment / Calibration / License / Failure thresholds /
Forge / Attestation / Plugins / Diagnostics) with each entry
annotated with introduced-in version.
- `docs/src/architecture.md` module map expanded to cover the 8
modules added across v0.7-v0.9.6 (`config`, `clock`,
`attestation`, `plugin`, `vex`, `epss`, `kev`, `registry`,
`license`); new "Best-effort enricher contract" and
"Byte-determinism contract" subsections; approved-deps table
including `base64 = "0.22"` (v0.9.6) and `spdx = "=0.10.9"`
exact pin (v0.9.5).
- `docs/src/baseline.md` 6-row schema-reference table for the
unified `BaselineEntry` (id / purl / expires / reason /
vex_status / vex_justification).
- Per-enricher chapters (`docs/src/enrichers/{typosquat,
maintainer-age,version-jump,kev,epss,registry}.md`) gained
consistent Calibration + Disabling + See-also subsections;
overview table grew from 4 to 9 rows.
- `docs/src/SUMMARY.md` reorganized into Output / Enrichers /
Suppressions / Advanced groups for new-reader navigation.
- `CONTRIBUTING.md` "Test conventions (v0.9.5+)" subsection added
documenting the `clock::test_env_lock()` recipe; "Adding a new
enricher" and "Adding a new finding kind" worked recipes.
- Stale content rewritten across multiple chapters
(`gitlab-ci.md`'s v0.7-deferred section now covers what v0.9
actually shipped; release-signing pins refreshed; etc.).

### Roadmap

- Closed out every "Future candidates" entry from the v0.9.5
roadmap with explicit dispositions:
- **Reachability** → moved to Non-goals (pair with Endor / Snyk).
- **GraphQL maintainer-age** → decided: REST stays
(cursor-pagination-cost analysis lifted into the maintainer
enricher's module doc).
- **VEX vocabulary beyond OpenVEX 8 justifications** →
spec-bound; documented in `docs/src/vex.md` that bomdrift
follows the OpenVEX 0.2.0 vocab verbatim.
- **PyPI / crates.io maintainer-set-changed** → moved to a new
"Blocked on upstream" subsection with the precise API gap
documented (PyPI lacks per-version maintainers; crates.io
lacks per-version `published_by` history).
- Calibration backlog section removed entirely — every threshold
(similarity, young-maintainer-days, recently-published-days,
cache-ttl-hours) is now CLI/config-configurable.

### Deps

- Added `base64 = "0.22"` for the cosign in-toto envelope payload
decode in `src/attestation.rs`. Already a transitive dep via
`ureq`; promoted to direct so we own the pin.

### Tests

- 389 → 420 (+31). Plugin manifest parse, plugin success/timeout/
non-zero-exit/malformed-output paths, attestation envelope parse,
fake-cosign integration test (PATH-injection with serialized env
lock), calibration knobs override default + reflect in
`--debug-calibration`, cache-TTL override per enricher.

### Scope notes

What stayed deferred to v1.0 candidates (carried to roadmap "Blocked
on upstream" or new "Future candidates"):

- PyPI / crates.io maintainer-set-changed (upstream API blockers).
- WASM / sandboxed plugin model (current external-process model
works; revisit if demand materializes).
- Bitbucket / Azure DevOps action-side `vex:` / `emit-vex:` /
`plugin:` inputs (CLI surface is broader than action surface).
- Multi-major version-jump `MIN_MAJOR_DELTA` calibration knob
(only remaining hardcoded threshold; revisit with calibration data).

## [0.9.5] - 2026-04-29

The "polish + multi-SCM parity" milestone. v0.9.5 ships the v0.9 follow-up
Expand Down
74 changes: 70 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ git clone https://github.com/Metbcy/bomdrift
cd bomdrift

cargo check --all-targets # fast feedback while editing
cargo test --release # full test suite (~270+ tests as of v0.5)
cargo clippy --all-targets --all-features -- -D warnings
cargo fmt --all --check # MUST pass; run `cargo fmt --all` to fix
cargo test --release # full test suite (~420 tests as of v0.9.6)
rustup run 1.88 cargo clippy --all-targets --all-features -- -D warnings
cargo fmt --all -- --check # MUST pass; run `cargo fmt --all` to fix
```

Rust 1.85+ required (the project uses edition 2024).
Rust 1.88+ required (the project uses edition 2024; CI is pinned to
1.88 to keep clippy lints stable across releases — see
`Cargo.toml`'s `rust-version` field).

## Project conventions

Expand Down Expand Up @@ -101,6 +103,70 @@ Network-touching enrichers should have a unit test for the network-
failure path (fake fetcher returns `Err`) — the best-effort contract
matters and silently breaking it would be an easy regression.

### Test conventions (v0.9.5+)

Tests that mutate `SOURCE_DATE_EPOCH` (directly or indirectly via
`bomdrift::clock::*`) MUST acquire `clock::test_env_lock()` to serialize
across the crate's parallel test threads. Without the lock, two tests
running in parallel can read each other's mutated env var and
intermittently fail in ways that look format-deterministic but aren't.

```rust
#[test]
fn baseline_expiry_relative_to_source_date_epoch() {
let _lock = bomdrift::clock::test_env_lock();
// SAFETY: serialized by _lock above.
unsafe { std::env::set_var("SOURCE_DATE_EPOCH", "1735689600") }; // 2025-01-01
// ... test body ...
}
```

The lock is a `std::sync::Mutex<()>` — re-entrant calls within a single
test thread are fine, but a panic without the guard will poison it. If
you see "PoisonError" in CI but not locally, a previous test panicked
without releasing — fix the panicking test, not the poison handling.

### Adding a new enricher

The shortest viable PR shape, mirroring how `enrich::epss` was added in
v0.8 and `enrich::registry` in v0.9:

1. **`src/enrich/<name>.rs`** — pure `enrich(cs: &ChangeSet, ...) ->
Vec<<Name>Finding>` with a fail-soft fetcher boundary. Mirror the
shape of `src/enrich/osv.rs`.
2. **Wire into `Enrichment`** — add a field to the
`bomdrift::enrich::Enrichment` struct in `src/enrich/mod.rs`; have
`lib.rs::run_diff` populate it.
3. **Add a `--no-<name>` flag** to `src/cli.rs::DiffArgs`, plumb
through the `[diff] no_<name>` config key.
4. **Renderers** — add a section to `render::markdown`,
`render::term`, `render::json`. For SARIF, add a stable rule ID
(`bomdrift.<name>`), a `partialFingerprints.primaryHash/v1`
identity tuple, and a fingerprint-stability test.
5. **`--debug-calibration` row** — emit one
`<kind>|<key>|<score>|<threshold>` line per finding considered.
6. **Docs** — add `docs/src/enrichers/<name>.md` and link it from
`docs/src/SUMMARY.md` and `docs/src/enrichers/overview.md`.
7. **CHANGELOG** — `## [Unreleased]` entry under `### Added`.

### Adding a new finding kind

When a new finding kind is purely a rendering layer (e.g., a new
synthetic ID for VEX export or a new SARIF rule for an existing
enricher), the recipe is shorter:

1. **Synthetic-id grammar** — extend
`bomdrift::vex::SyntheticFindingKind` and the
`parse_synthetic_id` parser. Round-trip must be exact.
2. **SARIF rule** — add the rule descriptor to
`render::sarif::ALL_RULES` so it appears in `tool.driver.rules`
even with zero results, then a `partialFingerprints` identity
tuple for the new rule.
3. **Markdown / terminal / JSON sections** — mirror the existing
per-finding sections.
4. **Determinism test** — round-trip the rendered SARIF / VEX through
the parser and assert byte-for-byte equality with the input.

## Documentation

When you add a CLI flag / action input / enricher, update:
Expand Down
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bomdrift"
version = "0.9.5"
version = "0.9.6"
edition = "2024"
rust-version = "1.88"
description = "SBOM diff with supply-chain risk signals (CVEs, typosquats, maintainer-age)."
Expand Down Expand Up @@ -35,6 +35,7 @@ time = { version = "0.3", default-features = false, features = ["serde", "parsin
sha2 = { version = "0.10", default-features = false }
# Exact-pinned: SPDX list updates can shift LicenseId.is_gnu() / is_osi_approved membership and silently change license-policy semantics. Bump deliberately.
spdx = { version = "=0.10.9", default-features = false }
base64 = { version = "0.22", default-features = false, features = ["std"] }

[dev-dependencies]
criterion = { version = "0.5", default-features = false, features = ["html_reports"] }
Expand Down
Loading
Loading