Fixed-Output Derivations for Auditable External Fetches
Nix's fixed-output derivations gate network access with cryptographic hashes.

Reproducible builds rest on a simple promise: given identical inputs, any machine, anywhere, produces an identical output. That promise is what makes build-time tampering detectable and makes a compiled artifact trustworthy without having to trust the machine that compiled it. But real software is not self-contained. It depends on source tarballs, vendored trees, and pre-built binaries that live somewhere on the internet, and fetching them means doing something a reproducible build is supposed to forbid: letting the network decide what happens during the build.
Monzo's engineering team, building reproducible builds for AWS Nitro Enclaves, named the three properties a serious reproducible build has to deliver. It needs dependency pinning, the ability to say "this is the exact version." And it needs offline sandboxing, the discipline of refusing to download anything without explicit, visible permission. A pinned dependency is only pinned if the build can guarantee it got the exact bytes it asked for, and an offline sandbox cannot fetch anything. Something has to give.
Without a principled way to resolve that conflict, any system calling itself reproducible is reproducible only for as long as nothing upstream moves. A mirror changes, a CDN swaps a file behind a stable-looking URL, or an attacker gains control of an upstream host, and the guarantee is gone. The question a reproducible build system has to answer is how it lets the network in without letting it in everywhere.
Fixed-output derivations: resolving the tension with a declared, hash-gated commitment
Nix answers that question with fixed-output derivations, commonly abbreviated FODs. They are the one designated exception to Nix's normal model of building software, and the exception is narrow and strict on purpose. An FOD is permitted to reach out to the network during its build, but only on the condition that its output matches a cryptographic hash the package author declared in advance.
This inversion means a build's identity is the hash of what comes out, not the recipe that produced it, so trust shifts from process to output. A build is identified by what went into it, and whatever comes out is simply accepted as the product of that fixed recipe. FODs flip this specifically for the case of external fetches: the identity of an FOD is the hash of what comes out, not the recipe that produced it. The package author writes down, ahead of time, what the fetched content should hash to. If the network hands back anything else, byte for byte, the build fails. There is no step where that hash can be revised after the fact to match whatever happened to arrive.
This is what shifts the trust model. A download mirror can be controlled by a party nobody fully trusts, and the hash check still enforces that the content delivered is the content the package author originally committed to. Every other derivation in a Nix build graph is fully sandboxed: no network reachability, no access to the host filesystem, nothing but the explicit inputs it was given. That means every impurity the outside world could introduce into a build has exactly one gate it can pass through. Garnix's engineering analysis puts the architecture directly: impurities must pass through FODs, and those outputs must match a pre-recorded cryptographic hash, or the build fails. Nothing upstream of that gate is trusted by default, and nothing downstream of it needs to touch the network.
Verifying that an FOD actually behaves this way has a defined procedure. Nix's deterministic references and sandboxing are called an excellent starting point for reproducibility, and this four-pass check is the method given for confirming that a fixed-output derivation is genuinely stable across runs.
What FOD leaves in the derivation graph represent
A Nix derivation graph describes a build as a tree, with each node depending on the outputs of the nodes beneath it. Somewhere near the bottom of that tree sit the FODs, the only nodes that know their own output hash before anything is built. Everything above them is constructed hermetically, assembled entirely from those pinned, hash-verified inputs with no further contact with the outside world. Pulling every FOD leaf out of that tree leaves the complete, minimal list of every point where the build touched the network.
That list is worth more than it looks. Most dependency trees can tell you what a package manager thinks it installed. Few can tell you, with cryptographic certainty, every external byte that made it into the final artifact and where each one came from.
That distinction is the basis of a workflow Nixcademy developed to satisfy government auditors evaluating software supply chain integrity. The approach strips every intermediate derivation out of a build closure, leaving only the downloadable, content-verifiable source packages, the FODs themselves. What remains is an artifact set that proves a specific, enumerable collection of sources produced a specific product binary, and that proof was presented to regulators as direct supply chain integrity evidence.
This is, in effect, a principled method for generating a software bill of materials. It is a list of what was cryptographically proven to have been fetched, not what a build manager believes it installed.
The hash-reuse vulnerability: how FODs can be weaponized against the reviewers who trust them
The strength of the FOD model, its reliance on the hash rather than the source, is also where it can be turned against a reviewer. Nix reuses a cached store path whenever it encounters a matching output hash, regardless of the URL or the recipe that produced the fetch. That reuse rule is what makes FODs efficient, since identical content never needs to be fetched twice. A hash match can be engineered by someone who controls neither the trusted URL nor the trusted server, which is what an attacker can exploit.
Garnix's security analysis demonstrates the mechanics directly. A reviewer scanning a pull request sees the trusted domain name. Nix, meanwhile, sees only the hash, and if that hash already exists in a cache, it serves the cached content regardless of which URL the recipe claims to point to.
The attack unfolds in two steps. Second, the attacker submits a pull request that uses a trusted-looking URL but specifies the hash of the previously staged malicious content. A human reviewer checks the URL and sees nothing wrong, because the URL is not where the danger lives.
A related variant needs no insertion step. It simply pins a package to an older hash, one the attacker knows is already cached, corresponding to a version with a known and unpatched vulnerability. Neither variant requires compromising an upstream server. Both only require a hash already present in a cache the target system trusts, and that cache can be the local /nix/store just as easily as a remote one.
A milder version of the same structural gap appears constantly without any adversary involved. A developer updates a source URL or bumps a version string in a fetcher and forgets to update the corresponding hash, and the build quietly succeeds against the old, cached content. The common thread in both the deliberate attack and the accidental bug is the same divergence: Nix cares about the hash, and the human reviewer cares about the URL. Everything about this vulnerability lives in that gap.
Implications for code review and CI
URL-based code review cannot catch the hash-reuse attack, because the attack is specifically engineered to leave the URL looking normal. Teams that want a real defense need something automated that checks whether a declared hash actually corresponds to a fresh fetch of the declared URL, not merely whether the hash happens to be present somewhere in a cache.
This is a gap in standard practice, not a failure of any individual reviewer's diligence. Checking that a URL looks legitimate and that a hash field is populated is the normal review process for a dependency change, and it tells a reviewer nothing about whether that hash was actually produced by fetching that URL. The attack exploits precisely the part of the process nobody is checking.
Garnix's response to this is a CI feature called fodCheck, which validates each fixed-output derivation by rebuilding it while deliberately bypassing the cached output, so any mismatch between the declared hash and a fresh fetch of the declared URL surfaces immediately. If the hash in a pull request was generated from different content or a different source entirely, the fresh rebuild fails and the check catches it before merge. The feature is available on Garnix's paid plan, configured through garnix.yaml.
Teams not using that particular tool can still apply the same underlying principle. A CI step that forces a fresh rebuild of FODs, bypassing whatever is cached, and compares the result against the hash declared in the pull request will catch both the deliberate hash-reuse attack and the far more common accidental mismatch from a forgotten hash update. Only a fresh-fetch check catches an error that otherwise produces no warning.
The deeper trust boundary FODs cannot address on their own
A matching output hash is a strong claim, but it is a narrower claim than it sounds. It proves that the cached content is consistent with what the package author declared at some point in the past. It does not prove that the build host, the evaluator, or the local toolchain executing the build did so honestly. The hash check for a fixed-output derivation runs after the sandbox completes, and a build daemon compromised at exactly that window, after evaluation but before verification, can still alter an output and nothing in the FOD mechanism itself would catch it.
This is not a flaw specific to Nix. Bit-for-bit reproducibility across independently operated infrastructure gives real confidence that the build infrastructure was not compromised, but even Nix builds can leak timestamps or other sources of nondeterminism, and reproducibility is described as a necessary condition.
The Guix project's work on supply chain security frames where the answer actually lies. Rather than leaning on attestation, certifying each link in the chain and trusting the certificate, Guix and Nix both aim to make every step independently verifiable. Reproducible builds, bootstrappable builds, and provenance tracking are presented as a supply chain strategy taken together, not as a single mechanism that substitutes for the others. FODs are one essential piece of that strategy: they establish a verifiable, cryptographic commitment at the exact point where a build touches the outside world. A complete audit practice also requires independent rebuilds across infrastructure nobody shares control over, authenticated updates to source material, and, for the highest-assurance cases, hardware-rooted evidence that the build actually executed the way it claims to have.
Building FOD-based provenance into the development workflow rather than bolting it on at audit time
The properties FODs make possible, hash-verified fetches, an enumerable list of external trust assumptions, a content-addressable audit trail, only hold up reliably when they are part of the environment definition from the moment development begins. Reconstructing them after the fact from build logs or container image layers does not produce the same guarantee, because logs and layers record what happened to occur, not what was cryptographically committed to in advance.
The Nixcademy demonstration built for government auditors illustrates the difference concretely. A command like nix build .#iso already produces the same artifact on any machine that runs it, which is reproducibility in its ordinary sense. Satisfying a government auditor required an additional, deliberate step: filtering the dependency tree down to its FOD-leaf derivations and rebuilding the entire system from source on a machine with no network access at all, proving that nothing in the final artifact depended on an unverified fetch.
Monzo's own earlier approach, built around standard Dockerfiles, shows what gets lost without that structure. It achieved consistent hashes for its images, but it could not stop a build step from reaching out and downloading an arbitrary binary at runtime, could not enumerate the full set of dependencies that went into an image, and could not trace which version of a transitive dependency actually ended up inside it. Those are exactly the gaps that FOD-structured builds close, because every external fetch is forced through a single, declared, hash-checked gate rather than scattered across however many build steps a Dockerfile happens to contain.
A declarative environment manifest, one that captures every input to a build including its FOD-pinned external fetches, is what turns this from a one-time audit exercise into something durable. It can be versioned like code, shared across a team by name, rolled back atomically if something goes wrong, and used to generate a deterministic SBOM at any point in a project's lifecycle without re-running the build. When a vulnerability is disclosed in some upstream source, teams working this way can query which deployed environments include a fetch of that exact content hash, identifying every affected deployment without grepping through logs or rescanning container images after the fact.
The platform engineering consequence follows directly. Teams that define golden-path environments with FOD-pinned inputs give every developer and every CI runner access to the same verifiable artifact, and the familiar defense of "it works on my machine" loses its meaning, because the machine's environment is itself a content-addressed, auditable object rather than an informal accumulation of whatever happened to get installed on it. A declarative, FOD-rooted environment manifest, used as the actual unit of promotion from development through to production, is what closes that gap.
Sources
- NixOS Reproducible Builds
- Building a Secure Software Supply Chain with GNU Guix
- Glossary - Nix 2.28.7 Reference Manual
- Content-addressing derivation outputs - Nix 2.28.6 Reference Manual
- Reproducibility checking with new CLI and fixed output derivations - Guides - NixOS Discourse
- Security fix: Nix fixed-output derivation sandbox bypass - Announcements - NixOS Discourse
- Input for Supply Chain Security Panel at NixCon - NixCon - NixOS Discourse
- Nix: A Solution With Problems


