Skip to content

Build release binaries with cargo auditable to embed dependency data - #937

Draft
shashial wants to merge 1 commit into
developfrom
cargo-auditable-builds
Draft

Build release binaries with cargo auditable to embed dependency data#937
shashial wants to merge 1 commit into
developfrom
cargo-auditable-builds

Conversation

@shashial

Copy link
Copy Markdown
Contributor

📝 Summary

Release builds now go through cargo auditable,
which embeds the resolved dependency list into a .dep-v0 section of each binary.

  • make build* targets, docker/Dockerfile.rbuilder, docker/Dockerfile.reproducible, and the
    Mac path of the release workflow build with cargo auditable build
  • New make audit-bin target scans built binaries against the RustSec advisory database
  • cargo-auditable is pinned to 0.7.5 in all build paths so every artifact embeds identically
    formatted data
  • Dev builds (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:

  • Run make lint
  • Run make test
  • [n/a] Added tests (if applicable)

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Claude finished @shashial's task in 3m 37s —— View job


Review: cargo auditable release builds

Reviewed the diff against origin/develop, plus Makefile, both Dockerfiles, release.yaml, and the surrounding build paths. The approach is sound; findings below are about the guarantees the PR claims versus what the code enforces. Five inline comments posted.

Blocking-ish

1. make audit-bin passes vacuouslyMakefile:163-172. The target doesn't depend on build, and an unmatched glob leaves the literal target/.../*, so -f fails, the loop never runs, and it exits 0. Running it on a fresh checkout, before make build, or with a BUILD_PROFILE/BUILD_TARGET different from the one built, reports success having scanned nothing. Suggested a count guard inline. (Fix this →)

2. The 0.7.5 pin isn't actually enforcedMakefile:74-77. command -v cargo-auditable >/dev/null || cargo install ...@0.7.5 skips the install if any version is already on PATH. The PR's stated goal ("pinned to 0.7.5 in all build paths so every artifact embeds identically formatted data") doesn't hold for the make path on a machine or runner that already has 0.6.x. Gate on version instead. Same applies to install-cargo-audit. (Fix this →)

Worth addressing

3. No verification that .dep-v0 is actually present in any artifact. This is the structural gap behind 1 and 2: nothing in CI fails if a binary ships without embedded data. The Dockerfile.rbuilder path is the most exposed (docker/Dockerfile.rbuilder:76) — it inherits RUSTC_WRAPPER=sccache from the base stage while cargo-auditable injects via RUSTC_WORKSPACE_WRAPPER, and cargo chef cook (line 66) builds deps unwrapped against a persistent sccache mount. That chain is supposed to work, but if it doesn't, the result is a released image with no audit data and a green build. A cargo audit bin / readelf -S | grep dep-v0 assertion after each build turns a silent regression into a hard failure.

4. Reproducible builds not re-verifieddocker/Dockerfile.reproducible:13. This image backs the Linux release artifacts and the repo works hard for bit-identical output (-C metadata='', --build-id=none, --remap-path-prefix, SOURCE_DATE_EPOCH). cargo-auditable links a generated object produced in a temp dir; worth an actual two-run hash comparison rather than trusting the "sorted and timestamp-free" claim in the new Makefile comment. Secondary: everything above that line is deliberately pinned to a Debian snapshot repo, and cargo install from crates.io is now the one un-snapshotted network fetch in that builder.

5. Version pinned in four independent placesMakefile:77, docker/Dockerfile.rbuilder:39, docker/Dockerfile.reproducible:13, .github/workflows/release.yaml:151. Given the goal is identical embedded data across paths, silent drift between these is the exact failure the PR is trying to prevent. At minimum a Makefile variable plus Docker ARGs.

6. README sections now conflict — the "Reproducible builds" section (lines 157–176) still instructs plain cargo build --release with a fixed sha256sum, which produces a binary without audit data and a hash matching nothing make build emits.

Minor

  • docker/Dockerfile.rbuilder-operator:16 builds via make build-reproducible, which isn't a target in the Makefile — so that path is already broken and isn't covered by this PR. Pre-existing, flagging only because it's another release-ish build path that won't get audit data.
  • cargo install cargo-auditable --locked compiles from source with no registry cache mount in both Dockerfiles and on every Mac release run; cargo-binstall or taiki-e/install-action would cut that.
  • Dangling comment at Makefile:74 reads as a claim about the installer rather than about the build.

Not run: make lint / make test — Bash is restricted in this environment, so I reviewed statically. The shell-semantics claim in finding 1 (unmatched glob → exit 0) is standard POSIX behavior and doesn't depend on execution.
· branch cargo-auditable-builds

Comment thread Makefile
Comment on lines +163 to +172
.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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Suggested change
.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

Comment thread Makefile
Comment on lines +74 to +77
# 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Suggested change
# 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}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. cargo-auditable links 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.
  2. Note that everything above this line is pinned to a Debian snapshot repo specifically so the toolchain doesn't drift. cargo install ... --locked from 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.

Comment thread README.md
Comment on lines +178 to +189
### 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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…`.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 to cargo auditable build and add helper install targets for cargo-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-bin target 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-audit pins 0.22.2 in the install command, but the command -v guard means any preinstalled cargo-audit version 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.

Comment thread Makefile
# 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
Comment thread Makefile
Comment on lines +165 to +172
@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
Comment thread README.md
Comment on lines +188 to +189
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 \
@shashial
shashial marked this pull request as draft August 21, 2026 12:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants