diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8d7f82b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +# Keep the docker build context lean. Only the pre-staged release +# binaries are needed; everything else (source tree, target/, docs/, +# tests/, .git/, etc.) is excluded so: +# +# 1. Image rebuilds are fast (small context = small upload to buildx). +# 2. There's no risk of accidentally baking a stale `target/` artifact +# or test fixture into the image. +** +!dist/ +!Dockerfile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 692d840..350943a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -122,6 +122,21 @@ jobs: with: command: check + # Catch crate-metadata regressions (missing fields, oversized package, + # `exclude` slipping such that build-time `include_str!`'d files are + # dropped from the crate) BEFORE they reach a release tag. Mirrors what + # `cargo publish` will do at release time, but never actually uploads. + # Path-filtered to keep PR runs cheap. + publish-dry-run: + name: cargo publish --dry-run + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@1.88 + - uses: Swatinem/rust-cache@v2 + - run: cargo publish --dry-run --locked + shell-bridges: name: shell-bridges (parser tests + regex sync) runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f2d498d..0f32fce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,9 @@ on: permissions: contents: write # create release, upload assets - id-token: write # cosign keyless OIDC + id-token: write # cosign keyless OIDC + SLSA attestation + attestations: write # actions/attest-build-provenance + packages: write # ghcr.io image push (gated if:false today) # A second push of the same tag (rare; e.g. a force-push after a fixup) will # cancel the in-flight run rather than racing it. Different tags can release @@ -201,6 +203,19 @@ jobs: if-no-files-found: error retention-days: 7 + # SLSA build provenance for the per-target archive. Complementary + # to the cosign-keyless signature uploaded just above: cosign + # proves "the bomdrift maintainer's GitHub OIDC identity signed + # this blob"; SLSA proves "this blob was produced by the public + # release.yml workflow on tag ${{ github.ref_name }} in this + # repo". Verify with `gh attestation verify --owner Metbcy + # ` or `slsa-verifier verify-artifact --source-uri + # github.com/Metbcy/bomdrift --source-tag `. + - name: Attest build provenance + uses: actions/attest-build-provenance@v2 + with: + subject-path: dist/${{ steps.stage.outputs.stem }}.${{ matrix.archive }} + publish: name: publish release needs: [preflight, build] @@ -265,3 +280,146 @@ jobs: dist/*.sha256 dist/*.sig dist/*.pem + + # Multi-arch (linux/amd64, linux/arm64) ghcr.io image. Consumes the + # per-arch binaries already cosign-signed by the build matrix; no + # cargo build runs here. Tag matrix follows the standard convention: + # the full version, the major.minor, the major, and `latest`. + docker: + name: docker (ghcr.io) + needs: [preflight, build, publish] + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + steps: + - uses: actions/checkout@v4 + + - name: Compute version tag components + id: ver + shell: bash + run: | + version="${{ needs.preflight.outputs.version }}" + major="${version%%.*}" + rest="${version#*.}" + minor="${rest%%.*}" + echo "major=v${major}" >> "$GITHUB_OUTPUT" + echo "minor=v${major}.${minor}" >> "$GITHUB_OUTPUT" + + - name: Download all build artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + merge-multiple: true + + - name: Stage Linux binaries by docker arch + shell: bash + run: | + tag="${{ needs.preflight.outputs.tag }}" + mkdir -p dist/linux-amd64 dist/linux-arm64 + tar -xzf "artifacts/bomdrift-${tag}-x86_64-unknown-linux-gnu.tar.gz" \ + -C dist/linux-amd64 --strip-components=1 \ + --wildcards '*/bomdrift' + tar -xzf "artifacts/bomdrift-${tag}-aarch64-unknown-linux-gnu.tar.gz" \ + -C dist/linux-arm64 --strip-components=1 \ + --wildcards '*/bomdrift' + ls -la dist/linux-amd64 dist/linux-arm64 + + - uses: docker/setup-qemu-action@v3 + - uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push multi-arch image + id: build + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + provenance: true + sbom: true + tags: | + ghcr.io/metbcy/bomdrift:${{ needs.preflight.outputs.tag }} + ghcr.io/metbcy/bomdrift:${{ steps.ver.outputs.minor }} + ghcr.io/metbcy/bomdrift:${{ steps.ver.outputs.major }} + ghcr.io/metbcy/bomdrift:latest + + - name: Install cosign + uses: sigstore/cosign-installer@v3 + + - name: Sign image with cosign (keyless) + env: + COSIGN_EXPERIMENTAL: "1" + run: | + cosign sign --yes \ + "ghcr.io/metbcy/bomdrift@${{ steps.build.outputs.digest }}" + + # SLSA build provenance for the multi-arch image. See the + # equivalent step on the per-target archives for the cosign-vs-SLSA + # rationale. + - name: Attest build provenance (image) + uses: actions/attest-build-provenance@v2 + with: + subject-name: ghcr.io/metbcy/bomdrift + subject-digest: ${{ steps.build.outputs.digest }} + push-to-registry: true + + # Publish to crates.io. Independent of `docker` and `publish` so a + # crates.io transient (network, rate-limit, already-published-this-tag) + # doesn't fail the rest of the release. The CARGO_REGISTRY_TOKEN secret + # must be set on the repo before the first v0.9.9 tag push. + cargo-publish: + name: cargo publish (crates.io) + needs: [preflight, build, publish] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@1.88 + - uses: Swatinem/rust-cache@v2 + - name: cargo publish + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo publish --locked + + # Force-update the major-version tag to point at this release. + # GitHub Marketplace + sloppy adopters consume `Metbcy/bomdrift@v1` and + # expect it to track the latest stable release. During the v0.x line we + # track v1 (because that's what Marketplace currently references); once + # v1.0.0 ships, the logic switches to v${major} (so v2 will track v2.x.y). + retag-major: + name: retag major version + needs: [preflight, publish] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Force-update major-version tag to current release + shell: bash + run: | + version="${{ needs.preflight.outputs.version }}" + tag="${{ needs.preflight.outputs.tag }}" + major="${version%%.*}" + + # During v0.x, track v1 (Marketplace already pins it). + # From v1.0.0 onward, track v${major}. + if [ "$major" -ge 1 ]; then + major_tag="v${major}" + else + major_tag="v1" + fi + + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git tag -f "$major_tag" "$tag" + git push --force origin "$major_tag" + echo "Pointed $major_tag at $tag" diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e0e9ad..68dba65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,65 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [0.9.9] - 2026-04-30 + +The "distribution release." Phase 1 of the bomdrift adoption plan: every +plausible install path now works in one command. The release pipeline +gains four new outputs — crates.io, ghcr.io, SLSA build provenance, and +an automated `v1` major-tag retag — alongside the existing GitHub +Release + cosign-signed archives. No source-code feature work; the +v0.9.9 binary is functionally identical to the v0.9.8 binary. + +### Added + +- **`cargo install bomdrift` works.** The crate is now published to + crates.io. Cargo.toml gains `documentation = "https://docs.rs/bomdrift"` + and a `[package.metadata.docs.rs]` block so the auto-built docs.rs + page renders cleanly. The published-crate footprint is 54 files / + 220 KiB compressed, achieved via an `exclude` list trimming `tests/` + (2.7 MB of fixtures), `docs/` (mdbook source), `examples/`, + `benches/`, `fuzz/`, `comment-suppress/`, `scripts/`, `.github/`, the + GitHub Action manifests, and the CONTRIBUTING / CODE_OF_CONDUCT / + STATUS files. `data/` (typosquat reference lists, `include_str!`'d + at compile time) stays in. +- **`docker run ghcr.io/metbcy/bomdrift:latest` works.** A new + multi-arch (linux/amd64, linux/arm64) image is published to + GitHub Container Registry on every release. Single-stage Dockerfile; + the image consumes the cosign-signed binaries already produced by the + release matrix — no `cargo build` runs in the image. Distroless cc + base, runs as the distroless `nonroot` user, ~28 MB. Tag matrix: + `:vX.Y.Z`, `:vX.Y`, `:vX`, `:latest`. The image carries an inline + SLSA build provenance attestation (verify with + `gh attestation verify --owner Metbcy oci://ghcr.io/metbcy/bomdrift:vX.Y.Z`). +- **SLSA build provenance attestations.** Every release archive AND + the multi-arch ghcr.io image are now covered by + `actions/attest-build-provenance@v2`. SLSA proves *"this artifact + was produced by the public release.yml workflow on tag X in this + repo"*; the existing cosign signatures continue to prove *"the + bomdrift maintainer's GitHub OIDC identity signed this artifact."* + Both verifications must pass for the release to be trustworthy. See + [docs/src/release-signing.md](docs/src/release-signing.md) for the + full threat-model framing and the + `gh attestation verify` / `slsa-verifier` recipes. +- **`publish-dry-run` PR-time CI guard.** New `ci.yml` job runs + `cargo publish --dry-run --locked` on every PR. Catches + crate-metadata regressions (oversized package, missing required + fields, an `exclude` list change that drops a build-time + `include_str!` source) before they reach a release tag. + ### Changed +- **README install section ships three new install paths.** `cargo + install --locked bomdrift`, `docker run ghcr.io/metbcy/bomdrift`, + and the existing release-archive curl path are now equally + prominent. The from-source `cargo install --git` path stays as a + fourth option for adopters who want to track main. +- **README badges expanded.** crates.io, docs.rs, and GitHub + Marketplace badges added at the top of the README. The CI / Release + / Docs / License badges are kept. +- **GitHub Marketplace listing description rewritten.** Lead with the + axios narrative and the maintainer-age heuristic, drop generic + copy. (Marketplace dashboard edit, not a repo-file change.) - **Workflows opt into the Node.js 24 runner ahead of GitHub's 2026-06-02 forced-default.** Every workflow now sets `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'` at the top-level @@ -18,6 +75,11 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). `ci.yml`, `docs.yml`, `fuzz.yml`, `release.yml`, and `sbom-diff.yml`. Remove the env var once Node.js 24 is the default runtime (after 2026-09-16 per GitHub's deprecation timeline). +- **`v1` major-version tag is now automated.** A new `retag-major` + job in `release.yml` force-pushes `v1` to point at the latest + release on every tag. Until v1.0.0 ships, the floating tag stays + `v1` (Marketplace already references it); from v1.0.0 onward, the + job switches to `v${major}`. ### Fixed @@ -34,6 +96,52 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). work unchanged), and the comment links directly to the workflow run's artifacts tab so users have a real destination to follow. +### Documentation + +- **`docs/src/release-signing.md` gains a SLSA provenance section.** + Frames cosign + SLSA as complementary, with the threat model where + each catches what the other misses. Includes + `gh attestation verify` (recommended) and `slsa-verifier` + (air-gapped) recipes, and the ghcr.io image attestation verification + path. +- **Rustdoc cleanup.** Eight broken intra-doc links in module-level + docstrings (`MAX_QUERIES_PER_BATCH`, `SUFFIX_BOOST_SCORE`, + `run_with`, `eval_leaf`, `LeafOutcome`, `VulnRef`, `Cache::with_root`, + `crate::run_diff`) were either demoted to backtick-quoted code spans + (private items not visible to public docs) or fixed to use the + correct path after the v0.9.8 lib.rs split. Plus one redundant + explicit link target. `RUSTDOCFLAGS=-D warnings cargo doc --no-deps + --all-features` now passes; docs.rs auto-build is clean. No public + API change. +- **README and STATUS.md drift fixed.** Removed STATUS.md's stale + Marketplace-publication-pending line (the listing has been live + since v0.9.8). README from-source bumped from v0.9.8 to v0.9.9. + +### Tests + +- 444 → 444 (no net change). Distribution-release plumbing only. + +### Scope notes — what's in v0.9.9 vs deferred + +In v0.9.9: crates.io, ghcr.io, SLSA, docs.rs, Marketplace polish, +README badges + new install paths. + +Deferred (separate plans, not version-coupled): + +- Homebrew tap (`Metbcy/homebrew-tap` + `bomdrift.rb` formula). +- nix flake, AUR PKGBUILD, winget + Scoop manifests. +- README diet (the wall-of-text comparison table moves to + `docs/src/compare.md`). +- Asciinema demo recorded against `examples/axios-incident/`. + +Deferred from v0.9.8 (held for the next code-hygiene release): + +- File splits for `vex.rs`, `render/markdown.rs`, `render/sarif.rs`, + `baseline.rs`, `enrich/typosquat.rs`, `enrich/license.rs`. +- Mutation-testing audit via `cargo-mutants`. +- Coverage `--fail-under-lines` ratchet (planned for "after 2-3 + releases of visibility" — v0.9.9 is the second). + ## [0.9.8] - 2026-04-30 The "code-review-driven hardening" milestone. External agent review surfaced diff --git a/Cargo.lock b/Cargo.lock index 1d3cf10..b456b95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -123,7 +123,7 @@ dependencies = [ [[package]] name = "bomdrift" -version = "0.9.8" +version = "0.9.9" dependencies = [ "anyhow", "base64", diff --git a/Cargo.toml b/Cargo.toml index 3b8c2c2..cc14a29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,15 +1,50 @@ [package] name = "bomdrift" -version = "0.9.8" +version = "0.9.9" edition = "2024" rust-version = "1.88" description = "SBOM diff with supply-chain risk signals (CVEs, typosquats, maintainer-age)." license = "Apache-2.0" repository = "https://github.com/Metbcy/bomdrift" homepage = "https://metbcy.github.io/bomdrift/" +documentation = "https://docs.rs/bomdrift" readme = "README.md" keywords = ["sbom", "security", "supply-chain", "cyclonedx", "spdx"] categories = ["command-line-utilities", "development-tools"] +# Trim the published crate to source + runtime data + project meta. +# - tests/ (2.7 MB of fixtures): fixtures pulled from upstream projects; +# downstream `cargo install` users don't run `cargo test`. +# - docs/ (2.6 MB mdbook source): published separately at +# https://metbcy.github.io/bomdrift/. +# - examples/, benches/, fuzz/: not needed for `cargo install`. +# - comment-suppress/, action.yml, entrypoint.sh: GitHub Action +# surface, distributed via the Marketplace and cosign-signed +# release archives, not the crate. +# - .github/, scripts/: repo-only meta. +# - data/ stays IN — `src/enrich/typosquat.rs` `include_str!`s its +# contents at build time. +exclude = [ + "tests/", + "docs/", + "examples/", + "benches/", + "fuzz/", + "comment-suppress/", + "scripts/", + ".github/", + "action.yml", + "entrypoint.sh", + "STATUS.md", + "CODE_OF_CONDUCT.md", + "CONTRIBUTING.md", +] + +[package.metadata.docs.rs] +# Make every conditionally compiled item visible in the rendered docs +# (no-op today since bomdrift has no `[features]` block, but +# future-proofs the feature-flag story). +all-features = true +rustdoc-args = ["--cfg", "docsrs"] [lib] name = "bomdrift" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ff49f06 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +# syntax=docker/dockerfile:1.7 + +# bomdrift container image — distroless cc, multi-arch (linux/amd64, linux/arm64). +# +# Single-stage by design: this Dockerfile consumes pre-built per-arch +# binaries that the release pipeline (.github/workflows/release.yml) +# stages under dist/linux-${TARGETARCH}/bomdrift. There is no `cargo +# build` in the image; the binaries baked into ghcr.io are exactly the +# cosign-signed artifacts attached to the corresponding GitHub Release. +# +# Image base is gcr.io/distroless/cc-debian12 (supports glibc; ~22 MB +# base + ~6 MB stripped bomdrift binary). Runs as the distroless +# `nonroot` user — every production read of an SBOM in this image is +# intentional and uncovered by privileged-process side effects. +# +# Local development: +# +# cargo build --release +# mkdir -p dist/linux-amd64 +# cp target/release/bomdrift dist/linux-amd64/ +# docker buildx build --platform linux/amd64 -t bomdrift:local --load . +# docker run --rm bomdrift:local --version + +ARG TARGETARCH + +FROM gcr.io/distroless/cc-debian12:nonroot +ARG TARGETARCH +COPY dist/linux-${TARGETARCH}/bomdrift /bomdrift +USER nonroot +ENTRYPOINT ["/bomdrift"] diff --git a/README.md b/README.md index 34f4384..c52c0c9 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ > **SBOM diff with supply-chain risk signals.** Flags new CVEs (with EPSS + CISA KEV signal), typosquats across 8 ecosystems, multi-major version jumps, young-maintainer takeovers, recently-published / deprecated / maintainer-set-changed registry signals, and license-policy violations on every changed dependency — posted as a comment on GitHub, GitLab, Bitbucket, or Azure DevOps PRs. [![CI](https://github.com/Metbcy/bomdrift/actions/workflows/ci.yml/badge.svg)](https://github.com/Metbcy/bomdrift/actions/workflows/ci.yml) +[![crates.io](https://img.shields.io/crates/v/bomdrift.svg)](https://crates.io/crates/bomdrift) +[![docs.rs](https://img.shields.io/docsrs/bomdrift)](https://docs.rs/bomdrift) +[![GitHub Marketplace](https://img.shields.io/badge/marketplace-bomdrift-blue?logo=github)](https://github.com/marketplace/actions/bomdrift) [![Release](https://img.shields.io/github/v/release/Metbcy/bomdrift?sort=semver&display_name=tag)](https://github.com/Metbcy/bomdrift/releases/latest) [![Docs](https://img.shields.io/badge/docs-mdbook-blue)](https://metbcy.github.io/bomdrift/) [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) @@ -124,10 +127,29 @@ Comment `/bomdrift suppress GHSA-xxxx` on any PR; the sub-action appends to `.bo ### As a binary (local / CI) -Pre-built binaries cover Linux x86_64 + aarch64, macOS aarch64, and Windows x86_64. Each archive is cosign-signed via Sigstore + GitHub OIDC. +Pre-built binaries cover Linux x86_64 + aarch64, macOS aarch64, and Windows x86_64. Each archive is cosign-signed via Sigstore + GitHub OIDC, and (v0.9.9+) carries a SLSA build provenance attestation. + +**Install via `cargo` (v0.9.9+):** ```bash -VERSION=v0.9.8 +cargo install --locked bomdrift +bomdrift --version +``` + +**Install via Docker / OCI (v0.9.9+):** + +```bash +docker run --rm ghcr.io/metbcy/bomdrift:latest --version +# Pin to a specific version for reproducible CI: +docker run --rm ghcr.io/metbcy/bomdrift:v0.9.9 --version +``` + +The image is multi-arch (`linux/amd64`, `linux/arm64`), distroless, runs as a non-root user, and ships with an inline SLSA attestation (verify with `gh attestation verify --owner Metbcy oci://ghcr.io/metbcy/bomdrift:v0.9.9`). + +**Install from a release archive:** + +```bash +VERSION=v0.9.9 TARGET=x86_64-unknown-linux-gnu curl -sSL -o bomdrift.tar.gz \ "https://github.com/Metbcy/bomdrift/releases/download/${VERSION}/bomdrift-${VERSION}-${TARGET}.tar.gz" @@ -143,7 +165,7 @@ Verify the archive's signature before you trust the binary — see [Release sign ### From source ```bash -cargo install --locked --git https://github.com/Metbcy/bomdrift --tag v0.9.8 bomdrift +cargo install --locked --git https://github.com/Metbcy/bomdrift --tag v0.9.9 bomdrift ``` Requires Rust 1.85+ (the project uses edition 2024). diff --git a/STATUS.md b/STATUS.md index fdb5c96..af8c034 100644 --- a/STATUS.md +++ b/STATUS.md @@ -111,8 +111,6 @@ for the rationale. - The comment-suppress companion action currently suppresses an advisory ID across all components. Use a hand-curated baseline entry when you need per-component suppression. -- GitHub Marketplace publication is a repository setting. The action metadata - is ready, but a maintainer must enable the listing in GitHub settings. ## Feedback wanted diff --git a/docs/src/release-signing.md b/docs/src/release-signing.md index d80cfc5..ffa0518 100644 --- a/docs/src/release-signing.md +++ b/docs/src/release-signing.md @@ -112,3 +112,73 @@ Checksums alone don't authenticate the archive (an attacker who can modify the `.tar.gz` can also modify the `.sha256`); cosign is the authoritative verification path. The checksums exist for older toolchains and for quick local-rerun checks. + +## SLSA build provenance (v0.9.9+) + +In addition to the cosign-keyless signature on each archive, the +release pipeline produces a **SLSA build provenance attestation** +covering both the per-target archives and the multi-arch ghcr.io +image. The two are complementary, not redundant: + +- **cosign** proves *"the bomdrift maintainer's GitHub OIDC identity + signed this artifact."* It binds the artifact to the human (or + workflow run) holding the signing identity at sign time. +- **SLSA provenance** proves *"this artifact was produced by the + public `release.yml` workflow on tag `v0.9.9` in this repo, against + this commit SHA."* It binds the artifact to the build itself — + including the source ref, the workflow file, and the ephemeral + runner identity. + +Both verifications must pass for the release to be trustworthy. An +attacker who compromised the maintainer's signing identity (cosign +verifies) but couldn't push to `Metbcy/bomdrift` (SLSA fails) would +trip SLSA. Conversely, an attacker who pushed a malicious workflow +to a fork (SLSA verifies for the fork) wouldn't have the +maintainer's OIDC identity (cosign fails). + +### Verifying SLSA provenance — `gh` (recommended) + +The simplest path uses `gh`, which calls into the SLSA verifier with +the right defaults for GitHub-hosted attestations: + +```bash +VERSION=v0.9.9 +TARGET=x86_64-unknown-linux-gnu +ARCHIVE=bomdrift-${VERSION}-${TARGET}.tar.gz +BASE="https://github.com/Metbcy/bomdrift/releases/download/${VERSION}" + +curl -fsSL -O "${BASE}/${ARCHIVE}" +gh attestation verify --owner Metbcy "${ARCHIVE}" +``` + +A successful verification prints +`Loaded ... attestation(s) ... verified`. Pin the source ref by +adding `--source-ref refs/tags/${VERSION}` if you want to reject +attestations from other tags. + +### Verifying SLSA provenance — `slsa-verifier` + +For air-gapped or non-GitHub environments where `gh` isn't +available: + +```bash +slsa-verifier verify-artifact \ + --provenance-path "${ARCHIVE}.intoto.jsonl" \ + --source-uri github.com/Metbcy/bomdrift \ + --source-tag ${VERSION} \ + "${ARCHIVE}" +``` + +The `.intoto.jsonl` file is downloaded automatically by `gh +attestation download`, or you can fetch it directly from the +release's attestation manifest at +`https://github.com/Metbcy/bomdrift/attestations`. + +### Verifying the ghcr.io image attestation + +The multi-arch image carries an inline attestation (pushed by the +build job's `push-to-registry: true`): + +```bash +gh attestation verify --owner Metbcy oci://ghcr.io/metbcy/bomdrift:${VERSION} +``` diff --git a/src/enrich/cache.rs b/src/enrich/cache.rs index a531036..344d871 100644 --- a/src/enrich/cache.rs +++ b/src/enrich/cache.rs @@ -80,7 +80,7 @@ struct CacheEntry { } /// Filesystem-backed severity cache. Construct via [`Cache::open`] (production) -/// or [`Cache::with_root`] (tests, when an explicit root is needed). +/// or via the test-only `with_root` constructor (when an explicit root is needed). pub struct Cache { root: PathBuf, now_secs: fn() -> u64, diff --git a/src/enrich/epss.rs b/src/enrich/epss.rs index cad99d9..a280b1a 100644 --- a/src/enrich/epss.rs +++ b/src/enrich/epss.rs @@ -2,7 +2,7 @@ //! //! EPSS publishes a per-CVE probability of exploitation in the next 30 days, //! refreshed daily. We query -//! in batches and surface the score on every [`VulnRef`] whose primary id +//! in batches and surface the score on every [`crate::enrich::VulnRef`] whose primary id //! or aliases include a CVE-prefixed identifier. //! //! Best-effort: a network failure or parse error logs to stderr at @@ -35,7 +35,7 @@ struct CacheEntry { score: Option, } -/// Apply EPSS scores to every [`VulnRef`] in `e.vulns`. Updates in place; +/// Apply EPSS scores to every [`crate::enrich::VulnRef`] in `e.vulns`. Updates in place; /// `--no-epss` callers should skip calling this entirely. Best-effort. pub fn enrich(e: &mut Enrichment) -> Result<()> { enrich_with_ttl(e, None) diff --git a/src/enrich/kev.rs b/src/enrich/kev.rs index 5084a8b..4694cef 100644 --- a/src/enrich/kev.rs +++ b/src/enrich/kev.rs @@ -3,7 +3,7 @@ //! Single bulk feed at //! , //! refreshed daily. We download the catalog once per 24h, parse the -//! `vulnerabilities[].cveID` field, and flip [`VulnRef::kev`] to true on +//! `vulnerabilities[].cveID` field, and flip [`crate::enrich::VulnRef::kev`] to true on //! every reference whose primary id or aliases include a KEV CVE. //! //! Best-effort: network failure logs at `BOMDRIFT_DEBUG=1` and returns Ok @@ -37,7 +37,7 @@ struct KevEntry { cve_id: String, } -/// Apply KEV flags to every [`VulnRef`] in `e.vulns`. `--no-kev` callers +/// Apply KEV flags to every [`crate::enrich::VulnRef`] in `e.vulns`. `--no-kev` callers /// should skip calling this entirely. pub fn enrich(e: &mut Enrichment) -> Result<()> { enrich_with_ttl(e, None) diff --git a/src/enrich/license.rs b/src/enrich/license.rs index 0dee114..e9b7c57 100644 --- a/src/enrich/license.rs +++ b/src/enrich/license.rs @@ -26,8 +26,8 @@ //! //! ## WITH-chain inheritance through compound expressions (v0.9.7+) //! -//! Each leaf of an SPDX expression is evaluated by [`eval_leaf`] which -//! produces a [`LeafOutcome`] reflecting BOTH the base license check +//! Each leaf of an SPDX expression is evaluated by `eval_leaf` which +//! produces a `LeafOutcome` reflecting BOTH the base license check //! AND the exception check. Those per-leaf outcomes are then combined //! by the standard SPDX expression semantics: //! diff --git a/src/enrich/osv.rs b/src/enrich/osv.rs index 794f7f5..7ae4c1a 100644 --- a/src/enrich/osv.rs +++ b/src/enrich/osv.rs @@ -4,7 +4,7 @@ //! //! 1. **`/v1/querybatch`** — POST every purl of every component in //! `ChangeSet.added` and the after-side of `ChangeSet.version_changed`, -//! chunked by [`MAX_QUERIES_PER_BATCH`]. Returns advisory IDs only. +//! chunked by `MAX_QUERIES_PER_BATCH`. Returns advisory IDs only. //! 2. **`/v1/vulns/{id}`** — GET each unique advisory ID returned in step 1 //! and parse `database_specific.severity` (GHSA's text label) to populate //! [`crate::enrich::Severity`]. @@ -18,7 +18,7 @@ //! Both stages are best-effort: callers should surface errors as warnings //! and continue rendering the diff. OSV being unreachable is not a reason to //! block a PR review. A failed /vulns/{id} lookup yields -//! [`Severity::None`](crate::enrich::Severity::None) for that advisory and +//! [`Severity::None`] for that advisory and //! a single stderr warning per run. use std::collections::{BTreeSet, HashMap}; diff --git a/src/enrich/typosquat.rs b/src/enrich/typosquat.rs index 29bc3cc..1d17ec7 100644 --- a/src/enrich/typosquat.rs +++ b/src/enrich/typosquat.rs @@ -35,7 +35,7 @@ //! 3. **Suffix containment with a substantial added prefix → boost**. When //! the candidate ends with a legit name (≥ 5 chars) AND the added prefix //! is longer than 3 characters, the score is boosted to at least -//! [`SUFFIX_BOOST_SCORE`]. The textbook typosquat pattern: +//! `SUFFIX_BOOST_SCORE`. The textbook typosquat pattern: //! `plain-crypto-js`, `safe-axios`, `secure-lodash`. The base //! Jaro-Winkler similarity for these is low (the prefix kills it) but the //! deceptive intent is unmistakable. diff --git a/src/refresh.rs b/src/refresh.rs index ba02827..44d7c2a 100644 --- a/src/refresh.rs +++ b/src/refresh.rs @@ -39,7 +39,7 @@ //! ## Testability //! //! Network and filesystem are split off the public [`run`] entry point via -//! [`run_with`], which accepts an injected fetcher closure and an explicit +//! `run_with`, which accepts an injected fetcher closure and an explicit //! cache root. Tests use a fake fetcher returning canned anvaka markdown plus //! a tempdir cache root, so the test suite stays fully offline. diff --git a/src/render/term.rs b/src/render/term.rs index f9072ba..47c155f 100644 --- a/src/render/term.rs +++ b/src/render/term.rs @@ -7,7 +7,7 @@ //! `CLICOLOR_FORCE` environment variables. //! //! The actual TTY check (so that piped output stays plain) lives in -//! [`crate::run_diff`]; this module assumes the caller has already decided +//! [`mod@crate::run`]; this module assumes the caller has already decided //! that ANSI is appropriate. Color emission is still further gated by //! `if_supports_color(Stdout, ...)` so even if invoked directly, output //! degrades gracefully when stdout cannot render escapes.