Demonstrably Secure Software Supply Chains with Nix

Maintaining secure software development environments, especially those that require high levels of integrity guarantees, often comes with significant overhead. Organizations frequently resort to air-gapped environments, extensive employee vetting, and local forks of all software sources. While these methods aim to enhance security, they can drastically slow down development, introduce vulnerabilities due to outdated packages, and thus increase costs. What if you could achieve ironclad supply chain integrity without these burdens?
Government authorities worldwide are demanding stronger proof that software used in critical systems is secure. Agencies like the USA’s CISA/DISA, Germany’s BSI/DeuMilSAA, France’s ANSSI/DGSE, Italy’s ACN, Spain’s CCN, etc. are setting strict requirements. Meeting these rules is not optional for many organizations.
This article explores how Nix, a powerful package and depencency manager, offers a unique and robust solution to meet these security standards while simultaneously saving costs, freeing up developers to work with practically unconstrained software, and eliminating the need for restrictive, air-gapped environments. Nix allows you to prove the exact origin and integrity of your software before delivery without impeding your development workflow. Let developers use the latest and greatest toolchains and provide the supply chain integrity proof for the release later!
Using Nix, you can achieve the high levels of transparency and verifiability required by regulators. It allows you to:
- Prove Integrity: Guarantee that this exact set of sources produced this image without third-party interference, satisfying regulatory requirements.
- Track Sources: Include all application sources and toolchains (e.g., compilers and their compilers) for complete transparency and fully hermetic offline rebuilds.
- Audit Outputs: Export and archive the exact sources (typically a few GB of tarballs) of each release for third-party audits, ensuring trust and accountability for compliance.
While organizations are just starting to use Nix to address these requirements, we believe that as regulators become more familiar with its capabilities, demonstrating verifiable supply chain integrity with Nix will become a significant competitive advantage. This article explains how Nix makes this possible.
Who Benefits?
- Developers and DevOps Teams: Ensure reproducible, secure builds without maintaining forks of open source packages and patch lists on top. Use the latest tools to meet your tight deadlines.
- Compliance Officers: Provide verifiable proof for audits required by government agencies.
- Security Professionals: Mitigate supply chain attacks with full transparency to meet regulatory demands.
What’s Included in the Example
Our example GitHub project applicative-systems/secure-supply-chain shows this vision in action. It provides a relatively minimal bootable NixOS live CD/USB image with halfway realistic demo applications:
- C++ Database Writer: Listens on a TCP port, pipes the user input to a PostgreSQL database.
- Rust Database Reader: Serves database content over HTTP.
The booted ISO runs these services.
We use this image as a halfway realistic demonstrator: This could be your product image and we show how to rebuild everything completely from scratch to demonstrate your supply chain integrity.
The project’s README.md explains the full steps without much detail. Follow the rest of this article for the explanation of what it does.
The Nix Deep Dive: From Package Derivations to Verifiable Closures
The bootable ISO/USB image in the repository can be built by a simple nix build .#iso
call.
This is great as it will work on any machine/computer the same, but it doesn’t explain where the sources really come from.
Also, it will not rebuild everything from source bacause it can download most of the packages from the typically
trusted cache.nixos.org
- but that won’t give us the government authories’ acceptance.
When we run nix derivation show
on the output of our system image Nix recipe, we get output like this:
$ nix derivation show $(nix build --print-out-paths .#iso)
{
"/nix/store/sd3cmxsjpjpl298v88csr96ywph1s39g-nixos-supply-chain-25.05pre-git-x86_64-linux.iso.drv": {
# ...
"inputDrvs": {
# list of derivations that this derivation depends on
"/nix/store/abc123-bla.drv": {
# ...
},
},
"inputSrcs": [
# list of source folders, scripts, and other directly
# referenced files
],
"name": "nixos-supply-chain-25.05pre-git-x86_64-linux.iso",
"outputs": {
"path": "/nix/store/xvrwkzlz8a1jbvn910kvc0k27j6qhmjx-nixos-supply-chain-25.05pre-git-x86_64-linux.iso"
}
},
}
This is the JSON-structured content of the Nix derivation. (Nix expressions that describe packages, evaluate to derivations. Derivations are more machine-readable data structures that describe a complete build environment) This one describes the inputs to our system image:
inputSrcs
: These are files and folders that are directly referenced from e.g. a git repo or some download URL - this is typically the source code of a project, or scripts or patches, etc. If they are not there, they cannot be generated.inputDrvs
: These are the derivations of the compilers, build systems, and all kinds of other tools. If they are not there, yet, they can be built from their own sources, or downloaded from a Nix binary cache (we aren’t allowed to do the latter if we want to prove supply chain integrity).
(By the way: In our Nix & NixOS 101 trainings, we cover this topic thoroughly on the second day!)
For our system image, this data structure practically describes the first level dependency list. In the following image these are the light blue boxes:
(The dependencies are vastly simplified as the diagram’s complexity would increase a lot if we included everything)
Building the Dependency Tree
As our derivation depends on other derivations, we could nest down into each and every derivation and obtain the next list of dependencies.
The --recursive
flag of the last command does this:
$ nix derivation show --recursive $(nix build --print-out-paths .#iso)
The output of this command is a list of derivation data structures. The following diagram shows the graph structure (again, a bit simplified) that this output describes. Here, we can see that our build systems also need to be compiled from some source if they are not available already. And the compiler needs to be bootstrapped from some other compiler, and so on:
After following all the dependencies’ dependencies, we arrive at source packages and the bootstrap compiler. Nixpkgs defines the so-called bootstrap tarball which contains the bootstrap compiler, a minimal bootstrap shell, and minimized coreutils. It is roughly 30MB small (which makes it itself nicely auditable) and is the only binary piece (which we can also build from source if we like) that we need to start with to build everything else from source.
To prove that this set of sources led to our product binary, we can do this:
- Strip all intermediate derivations and only keep downloadable and content-verifyable source packages.
- Export these to an offline system. This system must have been set up (once) beforehand in such a way that the verifying authorities accept its integrity.
- Rebuild everything from scratch on the offline system to produce the binary.
In some sense, we are extracting the leafs from the tree. But how do we do that?
Stripping the Sources from the Dependency Tree
This is simple with Nix: All source packages and the bootstrap tarball are so-called fixed-output derivations (FOD) and can be easily filtered out.
Compared to regular derivations, the builder process of a fixed-output derivation has access to the internet, but only if it provides the promise of an output hash:
# example fixed-output derivation
builtins.fetchurl {
url = "http://some/download/source.tar.gz";
sha256 = "1md7jsf123a45z73bz1kszpp01yw6x5ljkjk2hx7wl800any6465";
}
Fixed-output derivations fail if the promished hash does not hold true for the output. And if it doesn’t fail, we don’t even care if the download mirror is owned by some untrusted third party - as the hash proves that the content is what we expected. This kind of derivation is necessary for all source downloads - if we cannot access the source, we cannot generate it or magically let it appear.
The part that we are aiming for are all the packages in the “Fixed-output Derivations” box in this diagram:
We already had that information, it was just unfiltered. This command extracts all the FOD leafs:
$ nix derivation show -r "$(nix build .#iso --print-out-paths)" |
jq -r 'to_entries[] | select(.value.outputs.out.hash != null) | .key'
jq
is a useful JSON processor app that helps us filter all the derivations which have an output hash.
Every derivation that knows the hash of its own output is a fixed-output derivation (FOD).
Once we have all the fixed-output derivations in a list, we can export them from the Nix store into a single file:
$ nix-store --export "${fixedOutputDerivations[@]}" > source-export.closure
This new file source-export.closure
weighs 5.5 GB in our example project.
This is not small but might could realistically be audited by third parties.
(We can even easily append more meta information like the origin URLs etc.
to make that process easier)
The Final Rebuild
Someone with security clearance could now take the source-export.closure
file,
copy it on a USB stick or similar, carry it to a verified air-gapped system and
completely rebuild everything there from scratch.
This proves the integrity of the binary files created in the process
(assuming the system on which the offline build runs has been verified in a separate process).
On that offline system, we simply run:
$ nix-store --import < source-export.closure
$ nix-build path/to/nix/expression.nix
This system cannot download anything as it can’t access the internet, so it rebuilds eeeeeeverything from scratch. For the example repository, this took another ~12 hours on a regular laptop. The result must have been created from the sources in the tarball and nothing else!
Because we not only demonstrate the integrity of our payload binaries, but also the integrity of our compilers’ compilers, this demonstration goes arguably deeper than what parties do who start with binary distributions (e.g. Debian, Ubuntu, Fedora, etc.).
Conclusion
In essence, we’ve shown how Nix enables definitive proof that your system image is derived solely from a specific, trusted set of sources—including all dependencies and build tools. By gathering all necessary source materials into a single “source closure”, a complete offline rebuild is feasible, providing verifiable supply chain integrity.
This capability transforms compliance from a development roadblock into a final verification step. By decoupling development from stringent, upfront security protocols, teams can utilize the latest tools, collaborate more effectively, and contribute to open source projects seamlessly. The complex task of supply chain proof for regulatory bodies is streamlined to a manageable and cheap operation, realistically achievable by a single individual with a single machine.
Nix’s power stems from its core features: reproducible builds, fixed-output derivations, and the ability to create offline source closures. While using Nix for the first time involves navigating unique challenges and adapting existing workflows, it offers integrity and reproducibility like no other tool (btw. guix also exists). Nix makes software development with robust supply chain guarantees a realistic and cheap post-development process, rather than a costly and restrictive constraint from the outset.
Please note that using the famous Nix patterns IFD and dynamic derivations might reduce the simplicity of this process. Of course, there are also solutions.
We at Nixcademy have extensive experience in guiding companies through this adoption process. We help organizations understand the opportunities and risks at the management level and work alongside engineering teams to implement these solutions effectively. If you’re looking to leverage Nix to strengthen your supply chain security, streamline compliance, and empower your development teams, we’re here to help. Contact us to explore how we can tailor these powerful Nix-based solutions to fit your specific needs and processes.