Build release binaries with cargo auditable to embed dependency data - #937
Build release binaries with cargo auditable to embed dependency data#937shashial wants to merge 1 commit into
Conversation
|
Claude finished @shashial's task in 3m 37s —— View job Review: cargo auditable release buildsReviewed the diff against Blocking-ish1. 2. The Worth addressing3. No verification that 4. Reproducible builds not re-verified — 5. Version pinned in four independent places — 6. README sections now conflict — the "Reproducible builds" section (lines 157–176) still instructs plain Minor
Not run: |
| .PHONY: audit-bin | ||
| audit-bin: install-cargo-audit ## Scan built binaries for vulnerable dependencies (uses the audit data embedded by "make build") | ||
| @fail=0; \ | ||
| for bin in $(BUILD_OUTPUT_DIR)/*; do \ | ||
| if [ -f "$$bin" ] && [ -x "$$bin" ]; then \ | ||
| echo "==> $$bin"; \ | ||
| cargo audit bin "$$bin" || fail=1; \ | ||
| fi; \ | ||
| done; \ | ||
| exit $$fail |
There was a problem hiding this comment.
audit-bin passes vacuously when there is nothing to scan.
The target doesn't depend on build, and if $(BUILD_OUTPUT_DIR) doesn't exist (or is empty) the glob stays unexpanded as the literal string target/.../*, [ -f ... ] is false, the loop body never runs, fail stays 0 and the target exits 0. A fresh checkout — or someone running make audit-bin before make build, or with a different BUILD_PROFILE/BUILD_TARGET than the one that was built — gets a green "audit passed" having scanned zero binaries. That's the worst failure mode for a security-scanning target.
Also worth handling: cargo audit bin on a binary with no .dep-v0 section falls back to guessing versions from panic strings, so stale non-auditable artifacts left in target/ produce low-confidence results silently mixed in with the real ones.
Suggest counting what was scanned and failing if it's zero:
| .PHONY: audit-bin | |
| audit-bin: install-cargo-audit ## Scan built binaries for vulnerable dependencies (uses the audit data embedded by "make build") | |
| @fail=0; \ | |
| for bin in $(BUILD_OUTPUT_DIR)/*; do \ | |
| if [ -f "$$bin" ] && [ -x "$$bin" ]; then \ | |
| echo "==> $$bin"; \ | |
| cargo audit bin "$$bin" || fail=1; \ | |
| fi; \ | |
| done; \ | |
| exit $$fail | |
| .PHONY: audit-bin | |
| audit-bin: install-cargo-audit ## Scan built binaries for vulnerable dependencies (uses the audit data embedded by "make build") | |
| @fail=0; count=0; \ | |
| for bin in $(BUILD_OUTPUT_DIR)/*; do \ | |
| if [ -f "$$bin" ] && [ -x "$$bin" ]; then \ | |
| echo "==> $$bin"; \ | |
| count=$$((count + 1)); \ | |
| cargo audit bin "$$bin" || fail=1; \ | |
| fi; \ | |
| done; \ | |
| if [ "$$count" -eq 0 ]; then \ | |
| echo "error: no binaries found in $(BUILD_OUTPUT_DIR); run 'make build' first" >&2; \ | |
| exit 1; \ | |
| fi; \ | |
| exit $$fail |
| # The cargo-auditable embedded data is sorted and timestamp-free | ||
| .PHONY: install-cargo-auditable | ||
| install-cargo-auditable: | ||
| @command -v cargo-auditable >/dev/null 2>&1 || cargo install cargo-auditable@0.7.5 --locked |
There was a problem hiding this comment.
The command -v guard defeats the version pin that the PR is built around.
If any cargo-auditable is already on PATH — an older 0.6.x on a dev machine, a preinstalled one on a CI runner, whatever a future base image ships — the install is skipped and the build silently uses that version instead of 0.7.5. So the claim that "cargo-auditable is pinned to 0.7.5 in all build paths so every artifact embeds identically formatted data" doesn't actually hold for the make path.
Gate on the version rather than on presence:
| # The cargo-auditable embedded data is sorted and timestamp-free | |
| .PHONY: install-cargo-auditable | |
| install-cargo-auditable: | |
| @command -v cargo-auditable >/dev/null 2>&1 || cargo install cargo-auditable@0.7.5 --locked | |
| CARGO_AUDITABLE_VERSION := 0.7.5 | |
| .PHONY: install-cargo-auditable | |
| install-cargo-auditable: | |
| @cargo install --list | grep -q '^cargo-auditable v$(CARGO_AUDITABLE_VERSION)' \ | |
| || cargo install cargo-auditable@$(CARGO_AUDITABLE_VERSION) --locked |
(The dangling # The cargo-auditable embedded data is sorted and timestamp-free comment sits above the install target where it reads as a claim about the installer; it belongs next to the build target or in the reproducibility section.)
Same pattern applies to install-cargo-audit below. I realize install-cargo-deb already uses command -v, so this is pre-existing repo style — but the pin matters more here, since the whole point is byte-identical embedded metadata across build paths.
| --mount=type=cache,target=/usr/local/cargo/git \ | ||
| --mount=type=cache,target=$SCCACHE_DIR,sharing=locked \ | ||
| cargo build --release --features="$FEATURES" --package=${RBUILDER_BIN} | ||
| cargo auditable build --release --features="$FEATURES" --package=${RBUILDER_BIN} |
There was a problem hiding this comment.
Worth verifying once that the .dep-v0 section actually survives this build path. cargo-auditable injects via RUSTC_WORKSPACE_WRAPPER, and this stage inherits RUSTC_WRAPPER=sccache from base (line 43), so cargo ends up chaining cargo-auditable → sccache → rustc. That chaining is supposed to work, but combined with the cargo chef cook layer (line 66) building deps without the wrapper and the persistent sccache mount, a cache hit silently returning a non-instrumented artifact would produce a released image with no audit data and no error.
A cargo audit bin /app/target/release/${RBUILDER_BIN} (or a readelf -S | grep dep-v0) right after the build would turn that into a hard failure instead of a silent one.
| libclang-dev \ | ||
| protobuf-compiler \ | ||
| cmake | ||
| RUN cargo install cargo-auditable@0.7.5 --locked |
There was a problem hiding this comment.
This is the image that backs the reproducible Linux release artifacts, and the PR changes what it emits without re-verifying determinism. Two things to confirm before merging:
cargo-auditablelinks an extra generated object into the binary. It's documented as sorted/deterministic, but this repo goes to unusual lengths for bit-identical output (-C metadata='',--build-id=none,--remap-path-prefix,SOURCE_DATE_EPOCH) — the object file is produced in a temp dir, so it's worth an actual two-run hash comparison rather than taking the docs' word for it.- Note that everything above this line is pinned to a Debian snapshot repo specifically so the toolchain doesn't drift.
cargo install ... --lockedfrom crates.io is now the one un-snapshotted network fetch in the reproducible builder. It's pinned by version+lockfile so it should resolve identically, but it does mean the reproducible image's inputs are no longer sourced from a single snapshot.
Also a minor cost: this compiles cargo-auditable and its dependency tree from source on every cold build of this image, with no registry cache mount.
| ### Auditable builds | ||
|
|
||
| Release builds (`make build`, the Docker images, and the released binaries) go through | ||
| [`cargo auditable`](https://github.com/rust-secure-code/cargo-auditable), which embeds the full | ||
| dependency list into each binary. To scan the built binaries against the RustSec advisory database: | ||
|
|
||
| ```bash | ||
| make audit-bin | ||
| ``` | ||
|
|
||
| The embedded data can also be picked up by scanners such as `trivy`, `grype`, `syft`. Meaning that container | ||
| images built from these binaries can be scanned for Rust dependencies as well. |
There was a problem hiding this comment.
This section says release builds go through cargo auditable, but the "Reproducible builds" section immediately above (lines 157–176) tells users to run plain cargo build --release and shows a specific expected sha256sum. Those two now describe divergent binaries: following the reproducible-builds instructions produces an artifact without the .dep-v0 section, and one whose hash won't match anything make build produces. Worth reconciling the two sections so it's clear which command corresponds to the released artifact.
Nit: "such as trivy, grype, syft. Meaning that container images…" — sentence fragment; syft, meaning that container images…`.
There was a problem hiding this comment.
Pull request overview
This PR updates the release build pipeline to run through cargo auditable so produced binaries embed a resolved dependency list (enabling downstream vulnerability/dep scanning), and adds a Makefile target to scan built binaries with cargo audit bin.
Changes:
- Switch release-oriented
make build*targets tocargo auditable buildand add helper install targets forcargo-auditable/cargo-audit. - Update Docker build paths and the macOS release workflow path to build via
cargo auditable. - Document auditable builds and add a
make audit-bintarget for RustSec scanning of built binaries.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Adds documentation for auditable release builds and make audit-bin. |
| Makefile | Routes release builds through cargo auditable and introduces audit-bin scanning. |
| docker/Dockerfile.reproducible | Installs cargo-auditable so reproducible Docker builds embed audit data. |
| docker/Dockerfile.rbuilder | Installs cargo-auditable and builds packages via cargo auditable. |
| .github/workflows/release.yaml | Installs cargo-auditable on macOS and builds macOS release binaries via cargo auditable. |
Suppressed comments (1)
Makefile:161
install-cargo-auditpins 0.22.2 in the install command, but thecommand -vguard means any preinstalledcargo-auditversion will be used (which may not behave consistently across environments). Consider checking the installed version and reinstalling when it differs.
@command -v cargo-audit >/dev/null 2>&1 || cargo install cargo-audit@0.22.2 --locked
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| # The cargo-auditable embedded data is sorted and timestamp-free | ||
| .PHONY: install-cargo-auditable | ||
| install-cargo-auditable: | ||
| @command -v cargo-auditable >/dev/null 2>&1 || cargo install cargo-auditable@0.7.5 --locked |
| @fail=0; \ | ||
| for bin in $(BUILD_OUTPUT_DIR)/*; do \ | ||
| if [ -f "$$bin" ] && [ -x "$$bin" ]; then \ | ||
| echo "==> $$bin"; \ | ||
| cargo audit bin "$$bin" || fail=1; \ | ||
| fi; \ | ||
| done; \ | ||
| exit $$fail |
| The embedded data can also be picked up by scanners such as `trivy`, `grype`, `syft`. Meaning that container | ||
| images built from these binaries can be scanned for Rust dependencies as well. |
| FEATURE_FLAG="--features $FEATURES" | ||
| fi | ||
| cargo build --profile ${{ matrix.profile }} $FEATURE_FLAG \ | ||
| cargo auditable build --profile ${{ matrix.profile }} $FEATURE_FLAG \ |
📝 Summary
Release builds now go through
cargo auditable,which embeds the resolved dependency list into a
.dep-v0section of each binary.make build*targets,docker/Dockerfile.rbuilder,docker/Dockerfile.reproducible, and theMac path of the release workflow build with
cargo auditable buildmake audit-bintarget scans built binaries against the RustSec advisory databasecargo-auditableis pinned to0.7.5in all build paths so every artifact embeds identicallyformatted data
build-dev, CI test builds) are unchanged💡 Motivation and Context
Makes binaries — and container images built from them — scannable for Rust dependencies and known
vulnerabilities with
cargo audit bin, trivy, grype, syft, etc.✅ I have completed the following steps:
make lintmake test