Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
160 changes: 159 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
# <archive>` or `slsa-verifier verify-artifact --source-uri
# github.com/Metbcy/bomdrift --source-tag <tag> <archive>`.
- 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]
Expand Down Expand Up @@ -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"
108 changes: 108 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

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

Loading
Loading