Reproducible builds audit¶
Overview¶
A reproducible builds audit checks whether a derivation’s outputs are deterministic: rebuilding with the same declared inputs should yield the same bytes. Nix’s sandbox, fixed inputs, and fixed-output derivations make builds repeatable far more often than on conventional distros, but they do not guarantee bit-for-bit identical artifacts everywhere. Timestamps, parallelism, toolchain drift, and upstream build systems that ignore SOURCE_DATE_EPOCH can still change file contents even when the store path identity is stable.
Auditing matters for supply chain confidence: you want to know whether a substituted binary cache path could differ from a local rebuild, and whether packaging fixes actually remove non-determinism. The Reproducible Builds effort tracks nixpkgs progress; content-addressed derivations push identity toward output hashes rather than input hashes.
Details¶
Repeatable vs bit-identical¶
Nix optimizes for repeatable builds—same derivation graph → same store paths—so substitution and rollback work. Bit-identical reproducibility is stricter: every file in the output matches byte-for-byte across rebuilds, hosts, and (often) time. Packaging and upstream tooling must cooperate for the second property.
| Property | What it means | What Nix gives you |
|---|---|---|
| Repeatable / input-addressed | Same .drv + same input closure → same /nix/store/<hash>-… path |
Default model; sandbox + pinned inputs enforce this strongly |
| Bit-identical / deterministic | Rebuilding the same .drv locally produces identical NAR contents |
Not automatic; must be verified per package |
| Cross-machine identical | Two builders with the same pin produce identical bytes | Requires deterministic upstream + pinned toolchains; platform may still differ |
| Cross-time identical | Rebuild months later matches today | Needs SOURCE_DATE_EPOCH, no embedding of build timestamps, stable archives |
Purity and reproducibility explains why input-addressed identity and byte-level reproducibility are related but not the same goal.
Common non-determinism sources¶
Even with sandboxing and locked nixpkgs, builders may still vary:
- Timestamps and dates — archives, object files, or docs embedding
$SOURCE_DATE_EPOCH-unaware build time - Parallelism — unordered iteration written into outputs (map iteration order, race-y file lists)
- Randomness — reading
/dev/random,$RANDOM, or UUIDs baked into artifacts - Host leakage — impure env vars,
-march=native, or undeclared paths when sandbox is off or relaxed - Upstream fetch drift — caught by FOD hash mismatch, not by
--check
Fixes usually land in nixpkgs (patches, postPatch, SOURCE_DATE_EPOCH, doCheck for upstream test regressions) and are tracked via reproducible-builds issue tags in nixpkgs; see reproducible.nixos.org for project status and reports.
Spot-checking with --check / --rebuild¶
Nix can rebuild an already-realised derivation and compare the new output to the store copy. The derivation must exist locally first; otherwise Nix reports that checking is not possible.
| CLI | Command | Notes |
|---|---|---|
| Classic | nix-build expr.nix -A attr --check |
Documented in the Nix manual; exit code 104 on non-determinism |
| Classic + preserve diff | nix-build expr.nix -A attr --check --keep-failed |
Keeps the second build at a .check sibling path |
| Modern | nix build .#attr --rebuild |
--rebuild compares to existing store paths (Nix 2.34+) |
| Modern + preserve diff | nix build .#attr --rebuild --keep-failed |
Same .check path behaviour on failure |
On mismatch, Nix errors with “may not be deterministic” and names both paths, e.g. /nix/store/…-hello vs /nix/store/…-hello.check. The .check path is a copy of the second build for inspection; it is not GC-protected and may disappear after the command unless you copy it elsewhere.
Comparing outputs: diffoscope and diff-hook¶
For human-readable diffs, use diffoscope from the Reproducible Builds project:
For automated CI, configure a diff-hook in nix.conf (run-diff-hook = true). Nix invokes the hook only when outputs differ; it does not run on successful matches. The hook receives the two output paths and derivation name; typical setups shell out to diffoscope or diff -r. See the Nix manual section Verifying build reproducibility.
Where auditing fits in security work¶
Deterministic builds reduce “works on the builder” surprises and make cache substitution safer to reason about, but they do not prove benign intent—only stable bytes. Combine audits with hash review on FOD bumps, substituter trust, and broader supply chain practices. CA derivations change the trust model when outputs themselves become the address; reproducibility audits remain relevant for verifying that builders are well-behaved before content addresses stabilize.
Examples¶
Minimal workflow (commands illustrative; full verification needs a built package and network for nixpkgs):
# 1. Build once (classic or flakes)
nix-build nixpkgs -A hello
# nix build nixpkgs#hello
# 2. Rebuild and compare
nix-build nixpkgs -A hello --check --keep-failed
# nix build nixpkgs#hello --rebuild --keep-failed
# 3. If non-deterministic, inspect
diffoscope /nix/store/…-hello /nix/store/…-hello.check
These steps cannot be fully verified offline in this wiki pass—they assume an installed Nix, a realised hello (or your target attr), and optionally diffoscope in PATH.
Example nix.conf fragment for a diff hook (paths are site-specific):
See also¶
- Purity and reproducibility
- Hermetic builds
- Fixed-output derivation
- Supply chain
- Content-addressed derivations
- Debugging builds —
--keep-failed, exit codes, logs
References¶
- Reproducible Builds for NixOS — project status, metrics, and nixpkgs reproducibility tracking
- Nix manual: Verifying build reproducibility (
diff-hook,--check) - diffoscope — Reproducible Builds project tool for deep output comparison