Transforming global software distribution with Nixpkgs

Hi everyone,

I’m excited to announce that the Sovereign Tech Agency decided to invest 230 000 EUR into improving global IT supply chain security, as well increasing sustainability and reducing tech debt around the Nix ecosystem! We had applied with a development program intended to address a number of known attack surfaces and bottlenecks by end of February 2026:

This presents a unique opportunity to have experts spend quality time on resolving difficult, long-standing issues, and reinforces the role of NixOS as a critical piece of digital infrastructure.

The Sovereign Tech Agency invests globally in open software components that underpin our digital infrastructure. Their investments scale across many sectors and benefit a broad range of users, directly enhancing productivity, sustainability, and capacity for innovation. Check out their other funded projects to see the broader impact they’re making in strengthening the open source ecosystem.

Background

Back in July 2024, a group of contributors came together to prepare a project application for the Sovereign Tech Fund, following our participation in the Contribute Back Challenge 2023.
After multiple revisions, we went through two iterations with the Sovereign Tech Agency to answer questions, adjust the proposal, and sign a contract.

I would like to thank everyone involved in making this possible, with special thanks to the NixOS infrastructure team (@hexa) and the NixOS Foundation board (@ron @edolstra) at that time.
Great many thanks to the Sovereign Tech Agency team for the trust put into the project team, and also to the giant Nix ecosystem contributor community on the shoulders of which this effort builds.

This announcement comes a bit late since we were first waiting for the contract to be official before talking about the outcome of the application in public, and then conflicting priorities on my end that delayed the write-up.

Progress so far

Some of us already met in March at Ocean Sprint 2025 to coordinate and prepare for the first milestones:

@multivac61 iterated on the FOD tracker design with @Mic92, and refined a plan for rolling it out to Nixpkgs CI checks. The necessary API surface is now implemented, and some scalability issues were ironed out. Next up will be supporting incremental and periodic consistency checks.

@das_J and @Conni2461 together with @hexa and @mic92 collected requirements for rewriting Hydra’s queue runner, which is currently a bottleneck that prevents utilising our full build server capacity. There is a (to be published) prototype that connects to the Hydra database and manages the entire communication between the scheduler and workers. Next up would be constructing sensible build queues, after which the prototype should be ready for testing and open for further improvements.

In other news, the initial investment from the Sovereign Tech Challenge in the Nix ecosystem is bearing fruit: The prototype Nixpkgs vulnerability tracker received private follow-up funding to get it ready for productive use.

Stay up to date

Subscribe to this thread to be notified of updates as milestones are reached!

100 Likes

Amazing news !!! Looking forward for the outcome.

3 Likes

I’ve never subscribed to a thread faster. Amazing news and a great set of projects. If anyone wants to blast out how to help on any of these efforts, especially if it’s user testing or building or self-hosting, I know I’m not the only one eager to help.

2 Likes

I am glad to see efforts about improving nix evaluator performance. Just curious, what is the multi-evaluator support?


Really looking forward to an improved build farm. I hope rebuilds of a flat package set that consists of thousands of small packages (each takes a few seconds to build) can go directly into master instead of staging one day.

1 Like

Paying taxes has never been so fun. I’m happy to see this come to fruition!

8 Likes

230,000 amounts to 0.00002% of Germany’s tax revenue. Which is awesome. I wish my government in the UK would do something obvious and good like this. Or maybe I should be paying German taxes instead. The nix repl failed to calculate this, so I had to nix-shell a better calculator.

❯ nix repl
Nix 2.28.3
Type :? for help.
nix-repl> (230000 / 947700000000) * 100
0

❯ nix-shell -p calc
this path will be fetched (0.85 MiB download, 5.28 MiB unpacked):
  /nix/store/33hds0liqby9s2mn2gy16yz230kgw9qf-calc-2.15.0.2
copying path '/nix/store/33hds0liqby9s2mn2gy16yz230kgw9qf-calc-2.15.0.2' from 'https://cache.nixos.org'...

❯ calc
C-style arbitrary precision calculator (version 2.15.0.2)
Calc is open software. For license details type:  help copyright
[Type "exit" to exit, or "help" for help.]

; (230000 / 947700000000) * 100
	~0.00002426928352854279
4 Likes

@matthewcroughan Your numbers are off by factor 100.

947.7 billion is not

9 477 000 000

(that is ~9 billion).

2 Likes

That only makes it more incredible. Thanks for catching, edited above.

1 Like

That makes it more credible in my opinion – a much smaller fraction of Germany’s tax money is spent on Nix.

Not only do I fail at math, but also at English. Though google defines “incredible” as “difficult to believe; extraordinary”. I’m shocked at how small a number this is. Compare it to the GDP which is 4.5 trillion, and wow again. Open source gets so little, and deserved more recognition long ago. I now wonder how much other projects or things get from tax revenue so we can add it all up.

2 Likes

On that note, I just found Sovereign Tech Agency - Wikipedia, which details some of the other funding that the agency has given. I calculated the total funding to be around 16669152 EUR, which comes to 0.18% of German tax revenue.

1 Like

This is wonderful news!

2 Likes

A bit lost in translation, probably to keep the proposal brief.
It refers to the possibility to have multiple “implementations” of an abstract evaluator interface, more so than multiple instances of the current implementation, which already mostly works¹.
Currently when code talks to the evaluation cache, it is quite different from other code that talks directly to the evaluator, yet it has to fall back on the uncached operations for some things.

This item is more of an architectural change and an intermediate step than a user-facing improvement.


¹ Multiple instances works in unit tests. Probably not useful in practice, and not tested in practice. Usefulness might improve after other changes which are not part of any current plans.

3 Likes

I’m not sure if this is what you refer to as “Assert reproducibility of Nixpkgs source retrieval”, but in case, this seems like a great place to mention (thanks @RaitoBezarius for pointing me here) this CVE I raised a year ago NVD - CVE-2024-36050 that I also discussed with the security team (I had no recent answer after I said I would create the CVE), and it is discussed in re-fetch source when url changes · Issue #969 · NixOS/nix · GitHub and Provide a binary cache for builds · Issue #68 · NixOS/ofborg · GitHub.

This describes an attack that is almost impossible to detect (for a PR reviewer) allowing a malicious PR to inject arbitrarily malicious code in basically any software, by simply changing the hash. The attack is very easy to run and can have devastating effects (trapdoor, viruses…). Yet, as far as I know, one year later, it is still effective, even if I proposed some proposals for potential fixes (the best fixes are not completely trivial as they require nix & caches to also maintain a list of verified couple hash/url, but as far as I see this is the only really robust solution to this issue).

@RaitoBezarius mentionned https://fod-oracle.org/, but this is a very hacky & expensive way to check nixpkgs using external tools, and would not apply outside of nixpkgs. A cleaner solution would require directly nix & caches to maintain (and distribute) a list of verified (hash, url) tupples, but this require some transformation on the nix APIs, but at least it is clean and should be reasonably efficient (i.e. avoid re-downloading all sources when fetching an untrusted derivation).

Hey @tobiasBora, I’m Óli,the maintainer of fod-oracle.

Thank you for your detailed feedback on the CVE-2024-36050 vulnerability and the broader security concerns around Nixpkgs source retrieval. I appreciate you taking the time to explain the attack vector and its potential impact.

You’re absolutely right that the current situation represents a significant security risk. The ability for malicious PRs to inject arbitrary code simply by changing hashes is indeed concerning, especially given how difficult it would be for reviewers to detect such attacks.

I’d love to learn more about your thoughts on how fod-oracle could be improved or what a more elegant solution might look like. While I understand your concerns about it being “hacky & expensive,” I believe it serves an important role in the current ecosystem as we work toward better solutions.

Today, given a nix expression, you can use fod-oracle to detect all direct and transitive Fixed-Output Derivations (FODs). As far as I understand, this capability hasn’t been easily achievable until now and forms a solid foundation for building other security tools and services for the community.

For example, in nixpkgs CI, fod-oracle will help with:

Hash validation: When someone changes a hash, we can confirm that this is the correct hash in the right place by verifying the actual content matches the expected hash

Infrastructure change detection: When somebody modifies the underlying infrastructure that creates FODs but keeps the hash the same, we can verify whether all those hashes actually remain the same or whether they would change under the new infrastructure

I completely agree that the ideal solution would involve Nix and binary caches maintaining verified (hash, URL) tuples as you suggested. This would provide a much more robust and systematic approach to preventing these attacks. However, until such fundamental changes are implemented in Nix itself, tools like fod-oracle can serve as important interim security measures.

What specific improvements would you suggest for fod-oracle in the meantime? And do you have thoughts on how we might advocate for or contribute to the more comprehensive Nix-level solutions you mentioned?

4 Likes

@multivac61 Thanks a lot for joining this discussion, and for your time implementing fod-oracle. I’m really happy to see that someone takes this seriously, and my comment was not at all a criticism against your work but just a clarification about its scope, in the sense that the only clean way to fix this issue is to fix nix itself as I can easily come up with more complex (but targetted) attack vectors that are just impossible to detect by just watching nixpkgs.

That being said, I’m really curious to understand how you implemented fod-oracle, as knowing this may help to see potential issues. Can you maybe explain a bit more how it works internally + how you implement it? To be honest I would expect your solution to be more complex to code than fixing nix itself ^^

I’m confused… do you mean that you hash the content of the nix store /nix/store/XX-foo and check that you find XX (if so this helps to prevent the recently fixed race condition attack on nix, but not my CVE), or do you re-download the url and check that this precise url gives XX? (If so that would indeed help to detect issues about my CVE, but would be a bit costly since you need to redownload all sources of nixpkgs… but no other way to fix this without fixing nix itself) Do you also manage to do it either for the full nixpkgs and for the diff between the current version and previous one? (In practice no-one wants to redownload all sources of nixpkgs for each PR ^^) If so really curious to know how you can implement this without changing/reimplementing nix!

Btw, note that checking for duplicate hashes in nixpkgs itself is NOT enough to insure security since one can for instance use a hash injected in a previous version of nixpkgs to be more discrete since caches are persistent, use a hash present in a corrupted cache like cachix, or maybe (never tried) use the hash of an output derivation instead of the hash of an existing source.

To advocate for change, one can maybe run an actual attack to show that the current system is broken (at the risk of being banned) :stuck_out_tongue:

Regarding how to fix nix, I don’t know enough about its internal to say “modify this function”, but at a high level I can think of a smooth and modular change that would let hydra implement a new API first, and let clients opt-in if they wish latter, with a first a warning if the cache does not implement the API and in the longer term an error. I explain in Provide a binary cache for builds · Issue #68 · NixOS/ofborg · GitHub how this may be implemented in term of functionality, in practice I would certainly implement it by doing a change in nix so that every time it computes a FOD, it stores in a database the tuple (hash—to save space—of derivation including url etc, hash of FOD) if it is a new FOD and check before this same database to see if the FOD was not already created before, here or in a cache (needs the new API for cache but this can be at the beginning only an “if the cache implements it use it otherwise print a warning”, and since hydra uses no cache it would still detects many (not all, only the derivations built by hydra!) attacks even without an API). Then in a second time we can implement an API for caches to allow querying this database in a remote cache to allow the verification described above also for derivation not built by hydra, but this is rather in the cache/remote-related code. Not sure if it is clear, feel free to ask if not! I can also provide MWE I gave to nix’s security team to test attacks if needed.

Thanks for your work
!

3 Likes

@tobiasBora the idea is simply to run fod-oracle as a service next to ofborg and Hydra, but other than that do what you described. This is primarily to circumvent a bunch of planning uncertainties for the project, but gets the job done for Nixpkgs CI — at the cost of lesser performance and portability than building it directly into something that exists. It’s similar to what’s being prepared for the hash collection infrastructure to track build reproducibility — there are some synergies but no guarantees they can be leveraged. The thing is, Nix, Hydra, and ofborg aren’t easy to maintain, and there’s an entire milestone to do something about that, it’s just we had to decouple those components to be able to complete everything within the available time frame of the STF investment program. Ironing everything out to have it all in one box would be a worthy follow-up project though, and that becomes much more predictable given a working reference implementation.

2 Likes

Hi everyone, a big update almost 4 months into the project:

  • 3 out of 7 milestones currently in progress
  • New Hydra queue runner in staging, getting ready for production
  • Bashless activation setup being prototyped
  • FOD tracker not possible as planned, but pivot may end up better than the original design

New Hydra queue runner

Context: Nixpkgs is claimed to be the largest open source software distribution in the world, and Hydra is the continuous delivery (CD) tool to coordinate the corresponding build farm. But for a while now it has run into a bottleneck: build machines running idle because the queue runner did not catch up with scheduling new builds. Not only is this a waste of available resources, it precludes hardware donations to improve turnaround times on updates and security fixes.

The goal of this milestone was to investigate whether Hydra is still the correct solution to the problem, and fixing performance and maintainability issues if needed or otherwise coming up with a different setup.

Early technical discussion confirmed that there is no reasonable alternative to Hydra at Nixpkgs scale. A possible contender would have been buildbot-nix, but there were similar reservations about performance and maintenance effort; and more importantly swapping out such a crucial component would impose a lot of work on the volunteer infrastructure team.

@conni2461 and @das_j published GitHub - helsinki-systems/hydra-queue-runner: Hydra queue runner rewrite end of June 2025. They have been continuously meeting with @hexa @lassulus @mic92 to discuss design and progress. The service is written in Rust, and scheduler and workers communicate over gRPC (instead of the infamous Nix daemon protocol) over a bi-directional channel used the send commands, status updates, logs, and build results, and also allows clients do not need a public IP.

Recently the infrastructure team recently deployed a staging Hydra available at https://staging-hydra.nixos.org, which finally enables testing changes before rolling them out. This is a big deal! The new queue runner is part of that staging deployment, and already features the following:

  • A new machine view is integrated in Hydra, querying the queue runner via REST API; build logs are working
  • Some database queries were optimised along the way
  • Stopping the queue-runner gracefully aborts active steps, and builder crashes are handled gracefully
  • Configurable retry limits (max, interval, backoff), decision based on steps results (e.g. on connection reset); various scheduling functions, e.g. based on PSI

More testing and small corrections are still needed, but production deployment is in sight. After that the team will turn their attention to ofborg, the Nixpkgs continuous integration (CI) service that runs builds and tests on pull requests, and its deployment configuration.

Bashless activation prototype in progress

@nikstur is prototyping a hardened boot setup that does not require any interpreted scripts. This is now conceptually understood, and the next step is to write an integration test that demonstrates a system that activates without Bash.

The deliverable is supposed to be a foundation for people to build their specific use cases on top. This is a continuation of and follows the same pattern as the STF 2023 Contribute Back Challenge with removing Perl from the boot process. That had allowed an interactive (largely) Perl-less system, and ended up seeing much more uptake than originally anticipated, with a new, complied implementation of switch-to-configuration by @jmbaur:

Specifically, the goal here is to build an appliance system, which is primarily relevant for high-security applications — both as a validation that the setup generally works and a showcase for how to do it.

There’s more to unpack on why this is important and where it fits into the big picture of NixOS. Most of the boot process is rather obscure knowledge that is barely documented. But since this post is really long already, we’ll leave that for another time. Until then, you can read up on the details of work in progress in the planning document and the development branch.

FOD tracker delayed, pivoting

As noted recently, one milestone currently can’t be delivered as intended:

This writeup, which goes into quite some detail, comes only now since we’ve been busy corresponding internally and with the Sovereign Tech Agency (STA) on how to proceed.

The original plan entailed building a tool that would evaluate the source specifications for all packages and store the pairs of input and output hashes in a database. Then on each pull request, it would diff the hash pairs against the master branch, check for the conditions in the cache poisoning attack, and rebuild changed fixed-output derivations (FODs) to verify their hash pair validity. Additionally the tool was envisioned to periodically re-download all sources asynchronously to check for liveness and consistency.

The design with a separate FOD tracking service was acknowledged during planning to be less efficient than the alternative approach of patching the Nix evaluator and Hydra job scheduler to log FODs when encountering them in expressions and CI builds respectively, such that the work would happen only once. This was discussed with @picnoir @julienmalka and a few others, who had investigated closing the cache poisoning attack vector and related issues. The decision for the external service was taken to de-risk the timeline, as that design was more well-understood, and already had an (unmaintained but working) prototype implementation.

The alternative approach would have required touching brittle but depended-upon code in Hydra — for which we have an entire milestone to make it more maintainable, scheduled later in the year, so requiring that as a dependency would have been impractical — and unfinished code in Nix, which has very limited maintainer capacity and code review throughput we couldn’t rely on in planning. And keeping a patched Nix just for CI seemed like an undesirable long-term maintenance cost.

The FOD tracker architecture was implemented in Go as a CLI command and REST service at https://fod-oracle.org (source) by the beginning of June 2025. The demo deployment tracks FOD hash pairs for various Nixpkgs releases as intended. It managed to drastically improve performance compared to the Python prototype by heavily leaning on asynchronous operations, and was also able to leverage recently added facilities in Nixpkgs to parallelise evaluation. Work on the web UI envisioned for the out-of-band bulk-rebuild mode was paused in favor of first enabling the pull-request-based deployment the team deemed the more important, contributor-facing feature. The tool appeared to fulfill functional requirements and seemed ready for beginning to prepare for deployment.

During June 2025 @wolfgangwalther from the NixOS CI team brought up concerns with performance in the production environment. The email correspondence went on for more than two weeks, those involved trying to find a satisfying solution. Despite exhaustive analysis of possible workarounds, the outcome is that the tool will be an order of magnitude too slow for per-PR checks.

A full Nixpkgs evaluation with fod-oracle currently takes 20 min on a 16-core machine with 64 GB RAM, and a full re-download of all sources requires on the order of 10 hours, respecting GitHub’s rate limits. While the latter would only be needed once in a while, the former is far too much for the GitHub Actions runners which have only 2 cores. Also a full evaluation is heavily IO-bound since it has to write .drv files to disk for further processing, and placing the Nix store on a tmpfs would raise memory requirements to about 128 GB. A full evaluation would always be required to compute the FODs that changed in a PR. And simply throwing more hardware at the problem is not something the NixOS Foundation can (or should have to) afford short- or long-term.

At the end of June we had laid out an alternative strategy, detailed below. But as a result, we had to give up on the original plan. I see the reasons for this breakdown in these mutually confounding factors:

  • The overall system’s complexity would have required developers with comprehensive domain knowledge and merge authority. During the planning phase, those weren’t available for the sustained time periods needed, which led us to take a suboptimal architectural approach. We were aware of the general problem and added buffers, but still underestimated the resulting risks.
  • Competing commitments by everyone involved reduced communication quality, with written correspondence causing further delays, despite volunteers’ extraordinary dedication to resolve issues as they arised.

Alternative plan, requested extension of timeline

The problem analysis yielded that the ultimate security boundary is the release channel (the final artefacts built by Hydra), as that affects consumers and manifests in the cache that is susceptible to poisoning. Since the turnaround time for a release is on the order of a day or more, re-downloading even all sources in parallel wouldn’t affect CI performance at that stage, but still provide the desired security properties. In practice we’d only need to download sources for FODs that changed relative to the last released commit, which is substantially less. A full download could be scheduled at a much larger interval.

It was also noted that some fraction of sources is simply broken. While in PR mode this would not immediately block all work as we’d only check changed FODs, it would need time to stabilise that for a full successful rebuild of all sources. This is why the FOD check must not immediately block releases, and getting there will inevitably have to rely on volunteer efforts spread over time. In order to avoid loose ends, we should add a check in the release script with a flag that can be turned on once all sources are stable.

@multivac61 and I tried to refine and estimate the changes agreed on with the other contributors. In that session, we could not convince ourselves that the new timeline we were about to propose to the STA would work out as intended, primarily due to other obligations already planned. Rearranging the code to get more clarity on the new setup made it evident that the changes would overall amount to a rewrite, in the sense of moving and re-testing a lot of code. We concluded that although the insights gained so far would lead to the desired results, it would take more time than would be available to deliver them even with an extended timeline. @multivac61 decided to stepped down from the project; we can keep using the work under a free license.

As a last attempt, I asked Nix and Hydra maintainers @Ericson2314 @roberth @Mic92 @Conni2461 @das_j for their assessment on whether the more efficient solution (as mentioned earlier by @tobiasBora) could be done in reasonable time: Logging FODs during evaluation directly in Nix to use in nix-eval-jobs, adding a table for input/output hash pairs in Hydra, scheduling FOD rebuilds with the new queue runner, and displaying the status in a new Hydra UI view. My idea was that since the main reasons for failure so far were lack of aggregate domain expertise and lack of access to the essential components — with Hydra improvements well underway and the small required Nix changes being in principle possible with good-enough coordination, there was a chance to get the optimal solution in terms of performance and architectural complexity. And the sentiment from maintainers was that it should work.

We met to explore detailed options and possible risks. It turned out that most of the work would be needed in Hydra and would touch parts that are being worked on already or will be in upcoming weeks (that is, code that can now be dealt with), as well as a much smaller change in Nix that is deemed uncontroversial. Specifically, we converged on the following:

  • In the new queue runner, make sure to traverse the entire derivation closure, cutting off at existing store paths. Cutoff must be decided at each derivation, and the old pruning behavior would be conditional on changed FOD hash pairs.
  • To access the hash pairs directly from the Nix database, stabilise just the necessary interfaces from the long-experimental content-addressed (CA) derivations. (The hash pairs are currently called realisations in Nix CA terminology.)
    • To further reduce risks, for simplicity Hydra could keep its own table for realisations, and we would only switch to Nix-native realisations if they land soon enough.
  • When encountering an FOD in the queue runner, perform the scheduling logic as usual, and additionally query the hash-pairs table in order to decide whether to create an extra build step which is the same, except:
    • It’s a reproducibility check (similar to existing repro check functionality, needs investigation)
    • Nothing depends on this task (dependants use the build step as is)
    • The build step has an extra requiredSystemFeatures += fod-check which does not exist in the original derivation. It is only added for scheduling purposes.
  • Have the infrastructure team provide a build machine with supportedFeatures = fod-check, mandatoryFeatures = fod-check, for each system that needs it (only x86_64-linux for now, as already discussed), such that jobs will naturally be dispatched correctly. The queue for those will have limited concurrency to avoid rate limits.
  • Extend the queue runner’s worker protocol to account for FOD rebuild status.
  • Add a new UI view to Hydra that shows rebuild status for a jobset (needs refinement).

Notably, while the CA feature as a whole is decidedly not stable yet, FOD checks would not require full roll-out. Realisations add little surface and are well-understood, and are arguably ready for stabilisation. This design would allow not changing anything about ingesting Nix expressions into the build queue, but instead do the additional work downstream when processing the build graph. It would also re-use existing mechanisms for job scheduling, minimising the number of new moving parts to deal with for maintainers and the infra team.

Important for this milestone is that the queue runner is the crucial component needed for efficient FOD checking, and it’s getting ready for adding features. The new situation and revised plan allow capturing synergies that were out of reach in the original proposal: The changes to Nix could be made by Nix maintainers who are involved in the broader development program, now soon available to contribute. This would allow us to make FOD verification much more efficient and easier to maintain long-term than originally anticipated. We requested an extension of the due date to end of November 2025, in order to make sure not to affect any of the other milestones.

Observations

A few personal notes, since these few past weeks were not easy for some of us. I’m very impressed by everyone’s dedication to do the right thing and do it well, against all odds. And I’m grateful to participate in the process. Thank you very much @multivac61 @Mic92 @wolfgangwalther @roberth @Ericson2314 @Conni2461 @das_j — and everyone else who helped out here and there — for your patience, endurance, and expertise. Thank you also @ryantrinkle @lassulus from the NixOS Foundation board for your indispensable moral support.

Reality is that we had to make many compromises on the way, so from the outside the result may rightfully look dissatisfying. I already noted this in various opportunities for feedback: the STA’s support enables us to grow organisational capacity, and is a chance to demonstrate we can get things done as a community working on a shared resource; this is not self-evident and to me feels like a great leap for NixOS. Everyone I work with is committed to getting this to production and has put in substantial effort so far, with a high degree of professionalism, building a spirit of “we can do this”.

We’re now waiting for formal feedback from the STA, that we have been in close touch with. I’ll post notes here as we go.

Coming soon

We’ve used the opportunity of various emergency meetings for warming up with @roberth and @Ericson2314 about their milestones to investigate Nix evaluator performance improvements and enable installations with an unprivileged Nix daemon. These are planned for the last quarter of 2025 and the first months of 2026. @raboof and @julienmalka will also start out in October 2025 with further improving the collection of build result hashes. The goal of that milestone is to automatically track and report on the degree of build reproducibility – which is already pretty good as you can read up in Is NixOS truly reproducible? to pass the time until the next update.

If you have any questions, feel free to ask them here or in direct messages!

26 Likes

I also created a tracking issue for bashless activation now: Bashless Activation - Tracking Issue · Issue #428908 · NixOS/nixpkgs · GitHub

6 Likes