Security researchers have found three security issues in Lix. This issue has not been assigned CVEs yet.
We have released updates to Lix 2.94 and 2.95 that contain fixes for these issues. All Lix users are advised to update immediately.
Fixed versions are Lix ≥ 2.94.3 and ≥ 2.95.4.
Context
Heap out-of-bounds write
A Nix list is a length-prefixed array of Value *. When we allocate a new list, the heap allocation size is sizeof(Value::List) + length * sizeof(Value *) in size_t arithmetic but the length can come from untrusted evaluation.
Large enough length will wrap the multiplication causing the evaluator to allocate a tiny buffer and then write length elements into it.
The immediate neighbours of that allocation are other 8-byte allocations, e.g. possibly Value::List headers, other small objects.
Each write will advance of one Value per iteration and may run into the evaluator’s whole value graph: Value, Value::List, Value::App, Value::{Thunk, Lambda, Int, Float, String, External}, Attr, Bindings, Env.
We were not able to figure out a chain for further exploitation.
Unprivileged repairs can lead to local privilege escalation
The daemon already refused repair from untrusted clients for the build and verify operations on the grounds that repair is not atomic. That same reasoning was never applied to the operations that add paths.
The repair layer works approximately like this:
if (repair || !isValidPath(info.path)) {
deletePath(realPath); // 1. it destroys a legitimate path
restorePath(realPath, wrapperSource); // 2. unpack the client's NAR
auto hashResult = hashSink.finish();
if (hashResult.first != info.narHash)
throw Error("hash mismatch ..."); // too late
// [snip]
registerValidPath(info);
}
Seatbelt sandbox escape on macOS
On macOS, Lix implements the build sandbox using the Apple’s Seatbelt APIs (sandbox_init_with_parameters(3)). The daemon generates a policy from the derivation and the paths the build is allowed to touch. The builder compiles that policy before execing the build.
Because the sandbox interpreter refuses overly long policies, Lix split the path list across several (allow file-read* file-write* process-exec …) forms once the profile passes a size threshold.
The threshold check ran before Lix decided whether the current path actually exists and contributed a piece of sandbox policy.
Optional sandbox paths that do not exist are normal on macOS (e.g. __impureHostDeps). When the threshold was crossed exactly on an optional path, the new (allow …) was opened and closed without any subpath constraint.
As a result, (allow file-read* file-write* process-exec) which doesn’t mean “allow nothing”… but “allow everything”.
Because store closures are large, these can happen by accident.
As a reminder, the Lix daemon is NOT a security boundary as per our security policy.
Am I affected?
The suspicious unsigned integer overflow has an easy reproducer:
builtins.genList (x: x) 2305843009213693952
in a Nix evaluation.
The local privilege escalation reproducer will be released later.
Our response
All our release branches that are part of a maintained NixOS release (2.94, 2.95) have received fixes by banning unprivileged repairs, bounding the allocations for lists and preventing “allow any” forms to occur.
What to expect?
We expect users that relied on unprivileged repairs to require manual privilege escalation to repair paths from now on.
Other than that, we do not expect any functional regression or stability issues.
Protecting yourselves
On NixOS
On NixOS, you have two scenarios.
I’m using lix-module
Upgrade your Lix pin and rebuild Lix as always. Please know that lix-module should be preferred only for HEAD builds these days and released builds should rather use Nixpkgs to benefit from the maintenance work going into Nixpkgs.
I’m using Lix from Nixpkgs
First of all, Nixpkgs moves slower when you are subscribing to one of its channel (unstable, 26.05, etc.).
The fix will not be built by Hydra instantly.
Multiple tools are available for you:
- https://nixpk.gs/pr-tracker.html — this tells you when a PR lands in the channel you might be interested
- https://status.nixos.org/ — this tells when is the last time a channel was updated
If you cannot wait, you have the option to apply the patch:
- apply the nixpkgs PR as a patch to your nixpkgs source tree → will cause a Lix rebuild
- apply the Lix patches as a patch to Lix as an overlay → will cause a Lix rebuild
- temporarily adopt lix-module → will cause a Lix rebuild
You can find Nixpkgs PRs here:
- 26.05: https://github.com/NixOS/nixpkgs/pull/573612
- unstable: https://github.com/NixOS/nixpkgs/pull/573613
On non-NixOS Linux
I have used the Lix’s installer (or Determinate Systems’ installer)
On non-NixOS Linux, you may have installed Lix via our installer or Determinate Systems’ installer with a Lix’s URL.
Lix 2.94
You can use nix upgrade-nix to upgrade and download the new binaries.
Lix 2.95
Due to a change in our service management units, we have switched to a script where we can also migrate those:
curl -sSf -L "https://git.lix.systems/lix-project/lix/raw/tag/2.95.4/misc/upgrade-lix.sh" | sudo --preserve-env=PATH bash -s -- 2.95.4
I have used my Linux distribution’s package manager
To the best of our knowledge, Lix is only packaged in Nixpkgs and Pacstall (without maintainer).
This means you have a package in a distribution we do not have knowledge of, please contact your distribution’s security team.
On macOS
I have used the Lix’s installer (or Determinate Systems’ installer)
On macOS, you may have installed Lix via our installer or Determinate Systems’ installer with a Lix’s URL.
Lix 2.94
You can use nix upgrade-nix to upgrade and download the new binaries.
Lix 2.95
Due to a change in our service management units, we have switched to a script where we can also migrate those:
curl -sSf -L "https://git.lix.systems/lix-project/lix/raw/tag/2.95.4/misc/upgrade-lix.sh" | sudo --preserve-env=PATH bash -s -- 2.95.4
I am using nix-darwin
On macOS, you may be using nix-darwin and therefore you are depending on Nixpkgs or lix-module, thus, the section on On NixOS applies to you as well.
I have used a macOS package manager
To the best of our knowledge, Lix is only packaged in Nixpkgs and Pacstall (without maintainer).
This means you have a package in a distribution we do not have knowledge of, please contact your distribution’s security team.
Additional guidance
How to deal with Lix daemon not being a security boundary?
There’s rare usages where Lix daemon as a multi-user needs to run as root, let’s go through the usual scenarios where a Lix daemon might be used:
- Build farm system (CI/CD)
- Deployed NixOS system (server/embedded/appliance use-case)
- Development machine
The first one can run the Lix daemon as a unprivileged user and make the ownership of the build farm Nix store be someone less privileged than root, yet still more privileged than anybody on the system. You can use AFNix’s build-nix-daemon NixOS module to implement this architecture: https://git.afnix.fr/afnix/infra/src/branch/main/services/build-nix-daemon licensed under MIT. Lix infrastructure mostly runs using this scheme, except on macOS.
The second one can run without Lix daemon at all, except if your usecase involve calling the Lix binaries to deploy on the machine. It’s highly recommended to use a deployment method that retrieves artifacts from a trusted build source (a CD build farm for example) and apply these artifacts without the use of the Nix CLI. NixOS offer this mode of deployment in its “image based mode” where updates are distributed via systemd-sysupdate and are full disk images. If you want to keep using a writeable store model, building a smaller utility around the nix copy feature is better than keeping Lix entirely.
The third one is the thorny one, there’s two subcases:
- you have root (via
sudoor something else) - you don’t have root
In the first subcase, the user can already get to root and you are now vulnerable to attackers accessing your user account to become root, this scenario is less impacted by this vulnerability.
In the second subcase, you are relying on the Lix daemon as a security boundary, the same way you are relying on any privileged piece of code the user can interact with to avoid giving out root. For this case, it is recommended to carefully jmonitor the endpoint w.r.t. crash logs and Lix daemon logs to catch exploitation attempts as the method is not silent.
Timeline
2026-07-29: Bug about unsigned integer overflow disclosed to Lix team via security@lix.systems.
2026-08-29: Unsigned integer overflow fixes ready.
2026-09-12: Local privilege escalation chain on macOS disclosed to Lix team via security@lix.systems.
2026-09-25 - 2026-09-28: NixCon
2026-10-09: Bug about Seatbelt sandbox escape disclosed to Lix team via the NixOS security team.
2026-10-09: Seatbelt sandbox escape fixes ready.
2026-10-13: Disclosure deadline.
2026-10-13: Release day for Lix.
2026-10-13: Blog post published.
Coordination with the CppNix team has started from day 1.
Credits
Credits to the Nixpkgs security team — hexa and tgerbet (@LeSuisse) — for coordinating and dealing with the MITRE paperwork.
Credits to the xokdvium for handling the coordination on the CppNix side.
Credits to Guthrie McAfee Armstrong (@gmarmstrong) for reporting the issue on the macOS local privilege escalation and discussing the scope of this vulnerability. Over the next month, additional reports arrived from @faukah, @joaoviictorti, Moritz Theile.
Credits to rootile for the security response on the unsigned integer overflow.
Credits to eldritch horrors for the thorough analysis and security response.
Credits to Raito Bezarius for handling the coordination on the Lix side.