{"vulnerability": "cve-2024-27297", "sightings": [{"uuid": "f1331e95-f9d8-49bd-8ef8-c28187a16bd4", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27297", "type": "seen", "source": "https://bsky.app/profile/euvd-bot.bsky.social/post/3mizc6vmm6z27", "content": "", "creation_timestamp": "2026-04-08T22:01:12.954430Z"}, {"uuid": "79bc23ed-96e3-4b3a-8f87-4a5702b47a74", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27297", "type": "seen", "source": "https://bsky.app/profile/thehackerwire.bsky.social/post/3mj25m5bkc527", "content": "", "creation_timestamp": "2026-04-09T06:11:49.617575Z"}, {"uuid": "525993b0-42a5-4de9-b281-48f362ed0487", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27297", "type": "seen", "source": "https://bsky.app/profile/cyberhub.blog/post/3mjkezhvr362y", "content": "", "creation_timestamp": "2026-04-15T17:07:07.905448Z"}, {"uuid": "c8f84fd1-e7d4-4afd-8081-0ff500fb283a", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27297", "type": "published-proof-of-concept", "source": "Telegram/viJYT7gg8S3gJ-1aMGboAArF-0q_OTu7PWcGdGBWd_FKwXc", "content": "", "creation_timestamp": "2026-04-09T01:27:18.000000Z"}, {"uuid": "5d7ace92-1ab7-4d5f-a199-678905f67e8c", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27297", "type": "seen", "source": "https://t.me/arpsyndicate/4188", "content": "#ExploitObserverAlert\n\nCVE-2024-27297\n\nDESCRIPTION: Exploit Observer has 3 entries in 2 file formats related to CVE-2024-27297.\n\nFIRST-EPSS: 0.000450000", "creation_timestamp": "2024-03-13T08:53:07.000000Z"}, {"uuid": "9a70ad4b-3866-4c86-88fb-1f8cbd335780", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27297", "type": "seen", "source": "https://t.me/ctinow/205143", "content": "https://ift.tt/OQw2iFj\nCVE-2024-27297", "creation_timestamp": "2024-03-11T23:27:18.000000Z"}, {"uuid": "c43ea014-5a5e-49a1-a090-555692fe113c", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27297", "type": "seen", "source": "https://t.me/ctinow/205135", "content": "https://ift.tt/OQw2iFj\nCVE-2024-27297", "creation_timestamp": "2024-03-11T23:27:07.000000Z"}, {"uuid": "5c6f6593-e176-4f91-bd9b-84b9e9804620", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27297", "type": "seen", "source": "https://gist.github.com/wyattgill9/990529c04d4b62e11a1f63fb789c30e4", "content": "# Advanced Nix: Derivations, Evaluation, and the Store Model \u2014 A Technical Reference\n\n## TL;DR\n- Nix's advanced feature set splits cleanly along the **evaluation layer / store layer** boundary. Fixed-output derivations (FODs), multiple outputs, `structuredAttrs`, and closure checks are **stable** today; the ambitious rebuild-avoidance and plan-generation features \u2014 **content-addressed (CA) derivations** (RFC 62), **dynamic derivations** (RFC 92), **impure derivations**, and **recursive Nix** \u2014 remain **experimental and unstabilized in 2026**, and the Lix fork is actively removing all four.\n- The recurring theme is **impurity escape hatches** (FODs, `__impure`, `__noChroot`, IFD) versus **purity/reproducibility guarantees**, and the recurring problem is that each escape hatch (especially FODs, which \"can produce arbitrary content\") weakens Nix's trust model \u2014 as CVE-2024-27297 concretely demonstrated.\n- Practical guidance: use stable features freely; treat CA/dynamic/impure/recursive-nix as research-grade (fine for private experiments, unwise for anything you need to keep working); prefer checked-in generated code over IFD in nixpkgs-style projects; and pick your Nix implementation (CppNix / Lix / Determinate) deliberately because they now diverge on exactly these features.\n\n## Key Findings\n\n1. **Two layers.** Nix is really two systems: an **evaluation layer** (the lazy functional language producing `.drv` files) and a **store layer** (which builds `.drv`s into store paths and manages the content-addressed-ish store). Nearly every \"advanced\" feature is best understood by which layer it touches. IFD and string contexts are evaluation-layer; CA derivations, FODs, recursive Nix, and dynamic derivations are store-layer (or bridge the two).\n\n2. **FODs are the load-bearing impurity.** Fixed-output derivations are the *only* stable way to get network access in a build, and they underpin every fetcher and every `vendorHash`/`cargoHash`/`npmDepsHash` pattern. They are also a genuine trust hole: the output hash is the *only* thing checked, so a FOD can run arbitrary code and produce arbitrary content as long as the hash matches \u2014 and CVE-2024-27297 showed the sandbox could be bypassed to mutate outputs after registration.\n\n3. **CA derivations are the \"big idea\" that hasn't shipped.** RFC 62 content-addressing enables early cutoff, deduplication, and a multi-user trust model, but since first shipping experimentally in **Nix 2.4** (Tweag: \"the release of Nix 2.4 marks the entry of content-addressed derivations in a released Nix version\"), it remains experimental \u2014 its stabilization milestone (NixOS/nix Milestone #35) was **~65% complete, last updated March 23, 2026** \u2014 and blocked on hard problems (realisation trust, cache copying, scheduler over-fetching, IFD/recursive-nix incompatibility). The ecosystem has split: **upstream CppNix** keeps trying to stabilize it, **Lix** is deleting it, and **Determinate** ignores it in favor of parallel evaluation.\n\n4. **Dynamic derivations (RFC 92) are the \"real fix\" for IFD** \u2014 letting a build produce more `.drv`s that Nix then builds \u2014 but depend on CA derivations and recursive-nix, so they inherit all their instability.\n\n## Details\n\n### 1. The store model, `.drv` files, and input-addressing (the foundation)\n\nA **derivation** is the low-level build recipe the evaluator emits: a data structure (serialized as a `.drv` file in **ATerm** format) describing `builder`, `args`, `env`, `inputDrvs`, `inputSrcs`, `system`, and `outputs`. You can inspect it with `nix derivation show` (JSON) and it's produced by `builtins.derivation`; `stdenv.mkDerivation` is a large nixpkgs wrapper over that primitive, and `derivationStrict` is the internal primop that actually forces it.\n\nClassic Nix is **input-addressed**: an output's store path is computed by hashing the *derivation graph* (the `.drv` and, transitively, all its inputs) \u2014 **not** the output content. The mechanism (well documented by Farid Zakaria's \"What's in a Nix store path\" and Max Bernstein's \"Nix derivations by hand\"): take the `.drv`'s ATerm, replace each `inputDrv` reference with a hash of that input, clear the output paths to empty strings, SHA-256 the result, then build a fingerprint string like `output:out:sha256::/nix/store:`, hash *that*, take the first 20 bytes, and base32-encode (\"nix32\"). The consequence is the defining pain of classic Nix: **any change to any input \u2014 even a comment in a build script \u2014 changes every downstream store path**, triggering \"rebuild the world.\"\n\nA **store object** is verified by the SHA-256 of its **NAR** (Nix ARchive) serialization \u2014 a canonical, sorted, reproducible archive format. `nix-store --dump`/`--restore` produce/consume NARs; NAR hashes are what `nix-store --query --hash` returns. The newer **deriving path** syntax `^out` (and nested `^foo.drv^out`) lets you name outputs of not-yet-built derivations, which is the syntactic foundation for dynamic derivations.\n\n### 2. Fixed-output derivations (FODs)\n\n**What/why:** A FOD declares its output hash *in advance* via `outputHash`, `outputHashAlgo`, and `outputHashMode`. Because the result is pledged up front, Nix relaxes the sandbox and **grants network access** \u2014 making FODs the escape hatch that every fetcher (`fetchurl`, `fetchgit`, `fetchFromGitHub`, `fetchzip`, `fetchClosure`) relies on.\n\n**How to use:**\n```nix\nstdenv.mkDerivation {\n  name = \"foo-src\";\n  outputHashMode = \"flat\";        # or \"recursive\"/\"nar\", \"text\", \"git\"\n  outputHashAlgo = \"sha256\";\n  outputHash = \"sha256-AAAA...\";  # SRI form preferred\n  # builder downloads from the network\n}\n```\n`outputHashMode` values:\n- **`flat`** (default): hash of a single file's bytes; no references to other store objects allowed. Used by `fetchurl`.\n- **`recursive`** / **`nar`** (synonyms; `nar` added in Nix 2.21 as the clearer name, `recursive` supported since 2005): hash of the NAR of the whole output tree. Used by `fetchzip`, `fetchFromGitHub`, and the vendored-dependency FODs. `fetchzip` uses this because zip files are non-deterministic but their *contents* are stable.\n- **`text`**: like flat but permits references (except self-references); used for `.drv` outputs under **dynamic-derivations**.\n- **`git`**: hash via Git's tree/blob algorithm; part of the **git-hashing** experimental feature (RFC 133).\n\n**SRI hashes** (`sha256-`) are the modern encoding; `nix-hash --to-sri` converts. `lib.fakeHash`/`lib.fakeSha256` (or an empty string, or all-zeros) are the standard \"build it and read the correct hash from the mismatch error\" placeholders. `nix-prefetch-url` / `nix-prefetch-git` compute hashes ahead of time. A **hash mismatch** yields the familiar `hash mismatch in fixed-output derivation ... specified: ... got: ...`.\n\n**Footguns and trust:**\n- **FODs can lie.** Only the hash is checked; a FOD may run arbitrary code and produce arbitrary content so long as the final hash matches. This is why FODs are described as an impurity/trust hole.\n- **CVE-2024-27297** (reported by jade and puckipedia, patched March 2024 in Nix 2.20.5/2.19.4/2.18.2/2.3.18): the FOD sandbox permitted sharing an abstract-namespace Unix-domain socket, over which a writable file descriptor to the output could be passed *out* of the sandbox and used to mutate the store path **after** Nix registered it \u2014 a time-of-check/time-of-use bug that could contaminate downstream input-addressed derivations. The GitHub advisory describes it as: \"A fixed-output derivations on Linux can send file descriptors to files in the Nix store to another program running on the host ... via Unix domain sockets in the abstract namespace.\" The same class of bug hit Guix.\n- **The fix itself later needed fixing.** A follow-up flaw, **CVE-2026-39860** (reported by edef, fixed by Sergei Zimmerman in Determinate Nix 3.17.3 and upstream Nix 2.34.5/2.33.4/2.32.7/2.31.4/2.30.4/2.29.3/2.28.6), \"allowed for arbitrary overwrites of files writable by the Nix process orchestrating Nix builds ... by following symlinks during fixed-output derivation output registration.\" Guix independently moved to put even FODs in a fresh network namespace with slirp4netns userspace networking to close the socket hole entirely.\n- **The self-reference restriction:** `flat` mode forbids references entirely; even in recursive mode, FODs referencing themselves or other FODs have historically produced broken/empty store paths (NixOS/nix #7148).\n- **`nix-build --check` does not re-validate FOD hashes** correctly if the hash is unchanged but content differs (issue #3369) \u2014 a reproducibility blind spot.\n\n**The vendored-dependency pattern (and why hashes are brittle):** nixpkgs' `buildRustPackage` (`cargoHash`), `buildGoModule` (`vendorHash`), `buildNpmPackage` (`npmDepsHash`), `buildComposerProject`/`buildMavenPackage` (`vendorHash`/`mvnHash`), `mixRelease` (`mixFodDeps`), and `fetchYarnDeps` (`offlineCache`) all package a language's dependency tree into a single recursive-mode FOD. These hashes are brittle for two reasons: (1) the vendored tarball's top-level directory is named from `pname`+`version`, so the hash changes when the version changes (mitigate with `cargoDepsName = pname`); and (2) **if you bump a version but leave the old hash, Nix silently reuses the old FOD** (same name/hash \u21d2 \"already cached\"), so the build proceeds with stale dependencies and fails confusingly downstream. The nixpkgs-recommended fix is to set the hash to `lib.fakeHash` first, forcing a fresh FOD and surfacing the correct hash. Tools like `nix-update` and `nixpkgs-update` automate this. Go modules can even want *different* hashes on different platforms (nixpkgs #205842), further showing the fragility.\n\n### 3. Content-addressed (CA) derivations \u2014 RFC 62\n\n**What/why:** In the CA model (the \"intensional\" store, from Eelco Dolstra's thesis), an output's store path is computed from **its own content** rather than from its inputs. Enable with the `ca-derivations` experimental feature and mark a derivation with `__contentAddressed = true` plus `outputHashMode`/`outputHashAlgo` (as with FODs, but the hash is *not* fixed in advance \u2014 hence \"floating\"):\n\n```nix\nstdenv.mkDerivation {\n  name = \"foo\";\n  __contentAddressed = true;\n  outputHashMode = \"recursive\";   # or \"nar\"\n  outputHashAlgo = \"sha256\";\n}\n```\nIn nix.conf: `experimental-features = ca-derivations`.\n\n**Floating vs fixed CA outputs:** A FOD is a *fixed* content-addressed output (address known up front, grants network access). A CA derivation is a *floating* content-addressed output \u2014 content-addressed but the address is discovered at build time, and the builder gets **no** impure capabilities (still sandboxed). Floating CA outputs implicitly require the `ca-derivations` *system feature* so they aren't scheduled onto builders that don't understand them.\n\n**The realisation concept:** CA derivations split two things that input-addressing conflates: the **output path** (an opaque content-addressed blob) and the **realisation** \u2014 the signed fact that \"building drv output `(drvPath, outputName)` produced *this* store path.\" For input-addressed derivations this mapping is implicit in the path itself; for CA derivations it must be tracked and transmitted separately. A store path without its realisation is meaningless, so binary-cache protocols must copy realisations too (and, per issue #6623, historically failed to). You can confirm a path is CA with `nix path-info --sigs`, which shows a `ca:fixed:r:\u2026` line \u2014 e.g. `/nix/store/988jq9bj7s336q48bzdaamcl90k5g1yw-vim-8.2.2567 ca:fixed:r:sha256:0z37vk3ndszn3p2in3li6rk3kln1lfqd9b6vl6w0qhkn7bixqibc`. `nix store make-content-addressed` rewrites an existing input-addressed path into a CA one.\n\n**Under the hood \u2014 `.drv` paths become unknown until build time:** Because the output path depends on content, Nix literally cannot know a floating CA derivation's output path before building it. It uses **placeholders** (`builtins.placeholder`, computed as base32 of `sha256(\"nix-output:\")`; CA variants use `nix-upstream-output:...`) in the build environment, then rewrites them once the real content-addressed path is known (\"rewriting hashes ... cross fingers\").\n\n**Early cutoff (the payoff):** If a change to `openssl` produces a byte-identical `curl` output, the CA model detects that the content-address is unchanged and **stops the rebuild there** \u2014 everything downstream of `curl` need not be rebuilt (only references updated). This is the \"early cutoff\" that could dramatically cut Hydra's rebuild load and storage (Tweag's \"Towards a content-addressed model for Nix\").\n\n**Trust model:** Because trust (the signed realisation) is separated from storage (the content-addressed path), **multiple mutually-distrusting users can share one store**. But this is also where the current design is naive: if your machine has one realisation for a drv output and a substituter offers a *different* one, the current implementation simply refuses to fetch. Per the Haskell.nix \"Content addressed derivations\" docs: \"The current implementation has a naive approach that just forbids fetching a path if the local system has a different realisation for the same drv output ... it can result in a totally useless binary cache in some pathological cases.\" Signing output mappings (issue #4248: \"the link between the symbolic derivation output ... and the output store path has to be trusted, so we should sign it\") is still unfinished.\n\n**Why it hasn't stabilized (as of 2026):** The \"ca-derivations stabilisation\" milestone (NixOS/nix Milestone #35) is still open at ~65% complete (last updated March 23, 2026; 29 open / 56 closed issues). Concrete blockers:\n- **Realisation trust / conflicting realisations** \u2014 signing and conflict resolution unfinished (#4248; naive \"forbid\" logic).\n- **Cache/realisation copying** \u2014 \"there's no way on the command-line to access a realisation without having the .drv file available\" (#6769), which breaks remote builds (builders don't instantiate `.drv`s).\n- **Scheduler over-fetching** \u2014 to know whether a cache has a resolved CA derivation, Nix must first resolve it, which requires fetching all its inputs (#11928); \"deep vs shallow\" realisation semantics for store-to-store communication are unresolved (#11896).\n- **IFD incompatibility** \u2014 IFD relies on realising a string's context, but CA context strings carry only placeholders, not resolved paths (#5805).\n- **recursive-nix incompatibility** \u2014 `queryRealisation`/`registerDrvOutput` are forbidden in recursive-nix, so CA errors out (#4353).\n- **Non-determinism/self-references** \u2014 Tweag's \"Self-references in content-addressed Nix\" notes there's no guarantee of catching self-references hidden inside e.g. compressed files, \"the main reason why content-addressability will not be the default in Nix.\" CA still crashes on trivial cases as recently as Nix 2.34.x.\n\nYou can build all of nixpkgs CA with `import  { config.contentAddressedByDefault = true; }`, but there's no populated binary cache, so you rebuild almost everything yourself.\n\n### 4. Impure derivations\n\n**What/why:** The `impure-derivations` experimental feature lets a derivation marked `__impure = true` produce a **different result every build** \u2014 e.g. fetching a \"latest\" version, hardware-dependent builds, or non-hermetic CI tests.\n\n```nix\nstdenv.mkDerivation {\n  name = \"impure\";\n  __impure = true;              # rebuilt every time\n  buildCommand = \"date &gt; $out\"; # or curl the network, etc.\n}\n```\nIntroduced in Nix 2.8 (April 2022).\n\n**How they differ from FODs:** No hash is required (a FOD needs one); they have network access (like FODs); they are **content-addressed floating outputs** so they cannot be substituted from a cache and are **always rebuilt**; and crucially **only FODs or other impure derivations may depend on an impure derivation** \u2014 a downstream consumer must itself be CA or impure. Consequently **impure derivations depend on the `ca-derivations` machinery** (they're \"floating CA outputs that run unsandboxed\" and are deliberately not recorded in the build trace). Nix 2.12's `` gained an `impure` argument that flips it into an impure derivation. Config: `extra-experimental-features = impure-derivations ca-derivations`. Status: experimental, and (see \u00a714) slated for removal in Lix because it rides on the CA infrastructure.\n\n### 5. Import From Derivation (IFD)\n\n**What/why:** IFD is when the **evaluator** consumes a value that requires **building** a derivation first \u2014 e.g. `builtins.readFile drv`, `import drv`, or `builtins.fromJSON (builtins.readFile drv)` where `drv` is a store path that must be realised. Evaluation *blocks*, the store object is built, then evaluation resumes with the result.\n\n```nix\n# IFD.nix\nlet drv = derivation { name = \"hello\"; builder = \"/bin/sh\";\n  args = [ \"-c\" \"echo -n hello &gt; $out\" ]; system = builtins.currentSystem; };\nin \"${builtins.readFile drv} world\"   # blocks to build `drv`, then continues\n```\n\n**Under the hood / why it's costly:** The Nix manual's data-flow diagram shows the CLI \u2192 evaluator \u2192 store round trip: the evaluator hits the store path, pauses, the store realises the `.drv`, and only then does evaluation continue. Because the evaluator is **sequential**, it discovers required paths one at a time, so builds can't be parallelized the way a normal build plan is; evaluation can't complete until builds finish. This is why it \"breaks\" eval-only workflows: `nix flake show`/`nix search` want to enumerate outputs *without building*, and IFD forces arbitrary builds mid-evaluation. (Note: a builtin *fetcher* like `builtins.fetchurl` producing a store path is **not** IFD \u2014 only derivation-produced paths count.)\n\n**Control:** `allow-import-from-derivation` (default **true** in plain Nix and in flakes; setting it false makes IFD throw `cannot build '...drv' during evaluation because the option 'allow-import-from-derivation' is disabled`). **Hydra** historically forced `--no-allow-import-from-derivation` for `hydra.nixos.org` evaluation (later made configurable, PR hydra#769), and **nixpkgs bans IFD** by policy \u2014 because, as edolstra put it in PR #5253, \"it can cause arbitrary amounts of building when running commands like `nix flake show`.\" Outside nixpkgs, IFD is widely defended as making Haskell/yarn/etc. integrations \"much more pleasant.\"\n\n**Use cases:** the `*2nix` / `lang2nix` tools \u2014 `cabal2nix`/haskell.nix, `node2nix`/`npmlock2nix`, `poetry2nix`, `crate2nix`/crane, `yarn2nix`, `dream2nix` \u2014 traditionally read a generated Nix expression via IFD. The nixpkgs-approved alternative is **checking the generated code into the repo** (pre-generating with a separate tool) rather than generating during evaluation.\n\n**Relationship to recursive Nix and dynamic derivations:** IFD makes the *Nix language* a monadic build system (build results feed back into evaluation). **Dynamic derivations** instead push that dynamism down into the *derivation layer* so the evaluator needn't stay alive during builds \u2014 the \"real fix\" for IFD's pain (see \u00a77). Determinate's `builtins.parallel` is a different mitigation, parallelizing IFD-heavy evaluation.\n\n### 6. Recursive Nix\n\n**What/why:** The `recursive-nix` experimental feature lets a **builder call Nix** \u2014 instantiating and building derivations from *inside* a running build \u2014 by exposing the Nix daemon socket inside the sandbox (`NIX_REMOTE=daemon`). Eelco Dolstra motivated it (and uses it in projects like `nix-ccache`, \"a flake to remotely build and/or cache C/C++ compilation, using recursive Nix\") for cases where a build wants to compute and build more work dynamically.\n\n```nix\nwith import  {};\nrunCommand \"foo\" {\n  requiredSystemFeatures = [ \"recursive-nix\" ];\n  buildInputs = [ nix jq ];\n  NIX_PATH = \"nixpkgs=${}\";\n} ''\n  hello=$(nix-build -E '(import  {}).hello.overrideDerivation (a: { name = \"recursive-hello\"; })')\n  mkdir -p $out/bin; ln -s $hello/bin/hello $out/bin/hello\n''\n```\n\n**Under the hood / restrictions:** The build gets a restricted daemon socket. Crucially, **arbitrary `nix-store -r` substitutions are disallowed** \u2014 you can't realise a path that wasn't already a build input or built by a prior recursive call, because that \"could lead to derivations with hidden dependencies or breaking reproducibility.\" Dynamic derivations use a \"very restricted recursive nix socket in the sandbox\" (issue #8602) to add `.drv`s to the host store from within a build.\n\n**Comparison to IFD:** recursive-nix is the \"nuclear option\" \u2014 it can build things IFD can build and more, but with rough edges: less clean caching/parallelism/dry-run than returning derivations, non-portability, and (per Lix) it hardcodes the legacy daemon protocol.\n\n### 7. Dynamic derivations \u2014 RFC 92\n\n**What/why:** The `dynamic-derivations` experimental feature (John Ericson's RFC 92, \"plan-dynamism\") lets the **unit of build generate more build graph at build time**. Three primitives:\n1. Derivations whose output *is itself a `.drv`* (via `outputHashMode = \"text\"`).\n2. A derivation depending on the output of a derivation that is *itself produced by another derivation* (the nested `^` deriving paths).\n3. `builtins.outputOf` to reference such not-yet-built outputs from the language.\n\n```nix\nbuiltins.outputOf producingDrv.outPath \"out\"\n```\n\nThis enables **IFD-less `lang2nix`** (a derivation parses `Cargo.lock`/`package-lock.json` and emits per-crate `.drv`s that Nix then builds) and **fine-grained incremental compilation** (one derivation per object file for Make/Cargo/Bazel-style graphs), splitting huge builds (LLVM, Chromium, the Linux kernel) into cacheable pieces without checking generated code into nixpkgs. Farid Zakaria's `MakeNix` demo shows a C project where changing one `.c` file rebuilds only the affected `.o` derivation.\n\n**Under the hood / relationship to CA:** Dynamic derivations **require `ca-derivations`** (identical outputs must share a store path regardless of inputs, essential for incremental caching) and work best with **recursive-nix** (to add the generated `.drv`s to the host store). The `text` output mode writes an ATerm- (or the newer JSON-) serialized derivation to `$out`. The ergonomics are still rough: `builtins.outputOf` currently needs `builtins.unsafeDiscardOutputDependency` on the `.drvPath` because the `DrvDeep` string context (referring to the whole build closure) isn't yet supported. RFC 92's own text notes it's \"not an alternative to IFD\" for cases needing build results *at eval time*, and that the `text` output mode is a leaky detail they'd like to remove.\n\n**Status (2026):** Experimental and \"somewhat brittle\" \u2014 Farid Zakaria's early-2025 walkthrough pins a specific Nix commit and warns \"here be dragons.\" The tracking issue is NixOS/nix #6316; John Ericson gave a demo (project \"sandstone\") at PlanetNix 2025 as a \"call to arms.\" It is *not* stabilized.\n\n### 8. String contexts (the underdocumented core)\n\nA Nix string is not just characters \u2014 it's a pair of **(characters, string context)**, where the context is an unordered set of elements tracking which store paths/derivation outputs the string depends on. This is how Nix automatically threads dependencies: when you interpolate `${drv}` into a build script, the context records that the output must be built and present. Idiomatic Nix never touches contexts explicitly, but advanced code sometimes must:\n- `builtins.hasContext` / `builtins.getContext` \u2014 inspect the context (returns an attrset like `{ \"/nix/store/...-hello.drv\" = { allOutputs = true; }; }`).\n- `builtins.appendContext` \u2014 attach context.\n- `builtins.unsafeDiscardStringContext` \u2014 strip context (needed to use a string where empty context is required); \"unsafe\" because Nix normally guarantees dependency tracking and this can silently drop dependencies. Safe operations \"grow\" contexts; discarding \"shrinks.\"\n- `builtins.addDrvOutputDependencies` \u2014 turn a *constant* context element (a plain `.drv` path) into a \"derivation deep\" element referring to the underlying derivation and all its outputs. Safe (only enlarges). Its inverse `builtins.unsafeDiscardOutputDependency` is unsafe and is exactly what's needed to feed a `.drvPath` to `builtins.outputOf`.\n\n### 9. Advanced `mkDerivation` attributes (mostly stable)\n\n- **`__structuredAttrs = true`**: serializes all attributes as JSON to `.attrs.json` (pointed to by `NIX_ATTRS_JSON_FILE`), plus a Bash helper `.attrs.sh` (`NIX_ATTRS_SH_FILE`). Avoids env-var size limits and `passAsFile`, and supports nested/typed data (e.g. `hardening.format = true` \u2192 `${hardening[format]}`). Becoming the default in modern nixpkgs.\n- **Runtime closure checks**: `allowedReferences`, `disallowedReferences`, `allowedRequisites` (whole transitive closure), `disallowedRequisites`. Used e.g. in NixOS to ensure an initrd has no accidental store dependencies. To allow a self-reference, list `\"out\"`.\n- **`outputChecks`** (under `structuredAttrs`): per-output `maxSize`, `maxClosureSize`, `ignoreSelfRefs`, and the reference/requisite lists. Example: cap `dev` at 128 KiB, forbid `out` from referencing the C compiler.\n- **`unsafeDiscardReferences. = true`** (structuredAttrs): disables runtime dependency scanning \u2014 useful for self-contained filesystem images with an embedded store.\n- **`exportReferencesGraph`**: writes the reference graph of given inputs into build-dir files; used by NixOS ISO/initrd builders that need a path's whole closure.\n- **Multiple outputs**: `outputs = [ \"out\" \"dev\" \"lib\" \"doc\" ]`; consumers select with `pkg.dev` or CLI `drv^dev`; `meta.outputsToInstall` controls default installs; separate outputs enable independent GC/download.\n- **`preferLocalBuild = true`**: build locally rather than dispatch to a remote builder (for cheap derivations). **`allowSubstitutes = false`**: never fetch from a cache, always build (for derivations cheaper to build than download); overridable via `always-allow-substitutes`.\n- **`impureEnvVars`**: whitelist env vars passed into a FOD's builder (e.g. `http_proxy`); only meaningful for FODs. The `configurable-impure-env` feature exposes this via the `impure-env` setting.\n\n### 10. System features, remote/distributed builds, and sandbox relaxation\n\n- **`requiredSystemFeatures`**: a derivation is only scheduled on a machine whose `system-features` include all listed features. Common ones: **`big-parallel`** (large parallel builds like Chromium, the Linux kernel, compilers), **`kvm`** (needs `/dev/kvm`), **`nixos-test`** (VM integration tests), **`benchmark`**. Missing features cause silent non-selection (issue #2238: Chromium needs `big-parallel`).\n- **Remote builders** are configured via `builders` / `nix.buildMachines` with `supportedFeatures`, a speed factor, and a host key. **`builders-use-substitutes = true`** tells remote builders to pull dependencies from their own caches. `preferLocalBuild`/`allowSubstitutes` interact here.\n- **Sandbox relaxation**: `__noChroot = true` (only honored when `sandbox = relaxed`) disables the sandbox for that derivation; `sandbox-paths`/`extra-sandbox-paths` inject specific host paths into the sandbox. These are impurity holes gated by `sandbox` mode.\n- **Trust model**: only **trusted users** may set security-sensitive settings; `trusted-substituters` are caches untrusted users may opt into; impure/FOD/`__noChroot` builds all interact with this because they can introduce unverified content. Garnix's \"stop trusting Nix caches\" writeup underscores that adding a substituter means trusting everything it serves.\n\n### 11. Purity, evaluation-time impurities, fetching, and eval caching\n\n- **Eval-time impurities**: `builtins.currentSystem`, `builtins.currentTime`, `builtins.getEnv`, `` lookups. **`--pure-eval`** (and flakes' pure evaluation) forbids these; **`restrict-eval`** limits filesystem access; flakes give **reproducible/hermetic evaluation** with locked inputs (`flake.lock`, `narHash`).\n- **`builtins.fetchTree`** (the generic fetcher behind flake inputs; `fetch-tree` feature, always on with `flakes`) and `builtins.fetchClosure` (`fetch-closure` feature).\n- **Source-path builtins**: `builtins.path { path; filter; name; }` and `builtins.filterSource` (filtered copies into the store, controlling the input hash), `builtins.storePath` (assert an existing path is a store path), and `nix-store --add-fixed` / `nix-store --add` for manual insertion.\n- **Eval caching**: flakes cache evaluation results (`--eval-cache`); `nix-eval-jobs` parallelizes evaluation of large jobsets and offers `--check-cache-status` (useful for CA where `nix realisation info` bails on unbuilt drvs, #12128); `builtins.trace`/`builtins.warn` for debugging.\n\n### 12. Binary cache internals\n\nA binary cache serves **NAR** files plus **`.narinfo`** metadata (StorePath, URL, Compression, NarHash, NarSize, References, Deriver, and a **`Sig:`** signature), optional **`.ls`** listings, and (for CA) **realisations**. Keypairs come from `nix-store --generate-binary-cache-key`; clients trust caches via `substituters` + `trusted-public-keys`. Push with `nix copy --to`, sign with `nix store sign`, and automate cache population with a **`post-build-hook`**. Wrong/garbage-collected keys can brick rebuilds (NixOS/nix #8271), and **negative caching** can require two `nixos-rebuild switch` passes for a new substituter to take effect.\n\n### 13. `git-hashing` and Software Heritage\n\nThe `git-hashing` experimental feature (RFC 133) lets Nix content-address store objects using **Git's** blob/tree hashing instead of NAR hashing. Motivations: reuse hashes already known to Git (no recomputation for huge repos), deduplicate at tree granularity, and interoperate with content-addressed archives like **Software Heritage** for anti-bitrot. It does not (initially) encode Nix \"references.\" `outputHashMode = \"git\"` selects it. Related friction: NAR hashes for git inputs have been unstable across Nix versions due to CRLF normalization (#11428) and `export-subst` (#7596).\n\n### 14. The fork landscape (this is now decision-relevant)\n\nThe advanced features above are exactly where CppNix, Lix, Determinate, and Tvix/Snix **diverge**:\n\n- **CppNix (NixOS/nix)** \u2014 the upstream reference. Still actively pursuing CA-derivations stabilization (Milestone #35, ~65%, last touched March 2026) and driving dynamic-derivations development. Flakes and `nix-command` remain experimental but there was a roadmap discussed at PlanetNix to de-experiment them.\n- **Lix** \u2014 a CppNix fork focused on \"correctness, usability, and growth.\" It is **removing this entire family**: `recursive-nix` (RFD #767, closed/removed April 2025 \u2014 \"puts the legacy nix daemon protocol as part of stable derivation ABI ... it will *never* be stabilized\"); `ca-derivations` (RFD #815, author raito \u2014 \"no significant adoption ... fragile in the face of minor system changes ... blocks simplifications in the store, evaluator, and derivation codebase ... the current implementation is not sustainable\"); and, as a forced cascade, `dynamic-derivations` and `impure-derivations` \u2014 per Lix maintainer pennae, \"we *also* have to remove impure derivations though since the impure derivation rewriting machinery *also* does not work without ca derivations ... since they build on the ca infrastructure and inherit all its problems we will not get around removing them too.\" (A `dynamic-derivations` removal Gerrit change, #3139 \"libutil: remove dynamic derivations feature,\" carries the work in code.) Lix also has the extra `pipe-operator` feature (`|&gt;`), stable since v2.91. **If you rely on CA/dynamic/impure/recursive-nix, you must use CppNix, not Lix.**\n- **Determinate Nix** \u2014 Determinate Systems' validated downstream of CppNix, staying upstream-compatible (\"retains full compatibility with upstream Nix\"). It does **not** promote CA/dynamic derivations; instead it attacks the same eval-speed/IFD problems with **`builtins.parallel`** + parallel evaluation (the `parallel-eval` feature) and **lazy trees**. Per Determinate's \"Parallel evaluation comes to Determinate Nix\" (v3.11.1), `builtins.parallel` \"reduced the evaluation time of `github:edolstra/parallel-eval-test#ifd`, for example, from 50 seconds to 10 seconds,\" and `nix flake check` on Hydra \"used to take 20 seconds and now takes less than 5.5 seconds (a speedup of 3.7x)\"; separately, its parallel garbage collector \"cut over five seconds from `nix search` on nixpkgs, from 24.3 seconds to 18.9 seconds.\" Eelco Dolstra (Nix's creator) is a Determinate co-founder. Note these are vendor-reported figures.\n- **Tvix / Snix** \u2014 Tvix is a Rust reimplementation (TVL); development slowed and **Snix** is its active fork. Relevant here because Snix's rethink of the storage layer is explicitly cited (in Lix's RFD #815) as a possible future basis for a cleaner content-addressed store.\n\n## Summary table: feature \u2192 flag \u2192 stability (2026)\n\n| Feature | Experimental-feature flag | Attribute / builtin | Stability (upstream CppNix, 2026) | Lix |\n|---|---|---|---|---|\n| Fixed-output derivations | \u2014 (stable) | `outputHash`/`outputHashMode`/`outputHashAlgo` | **Stable** | kept |\n| Multiple outputs | \u2014 | `outputs = [...]`, `drv^out` | **Stable** | kept |\n| structuredAttrs / outputChecks | \u2014 | `__structuredAttrs`, `outputChecks` | **Stable** | kept |\n| Closure checks | \u2014 | `allowed/disallowedReferences/Requisites` | **Stable** | kept |\n| preferLocalBuild / allowSubstitutes | \u2014 | those attrs | **Stable** | kept |\n| requiredSystemFeatures / remote builds | \u2014 | `requiredSystemFeatures`, `builders` | **Stable** | kept |\n| exportReferencesGraph | \u2014 | `exportReferencesGraph` | **Stable** | kept |\n| String contexts | \u2014 | `getContext`/`addDrvOutputDependencies`/`unsafeDiscard*` | **Stable** (advanced) | kept |\n| IFD | `allow-import-from-derivation` (setting, default on) | `import`/`readFile` on drv output | **Stable but discouraged**; banned in nixpkgs | kept (default on) |\n| Content-addressed derivations | `ca-derivations` | `__contentAddressed = true` | **Experimental**, ~65% to stabilization (Milestone #35) | **being removed** (#815) |\n| Impure derivations | `impure-derivations` (+`ca-derivations`) | `__impure = true` | **Experimental** (since 2.8) | **being removed** (cascade) |\n| Dynamic derivations | `dynamic-derivations` (+`ca-derivations`, `recursive-nix`) | `outputHashMode=\"text\"`, `builtins.outputOf` | **Experimental**, brittle (RFC 92, #6316) | **being removed** (#3139) |\n| Recursive Nix | `recursive-nix` | `requiredSystemFeatures=[\"recursive-nix\"]` | **Experimental** | **removed** (#767, Apr 2025) |\n| Git hashing | `git-hashing` | `outputHashMode=\"git\"` | **Experimental** (RFC 133) | \u2014 |\n| fetchTree / fetchClosure | `fetch-tree` / `fetch-closure` | those builtins | fetch-tree on with flakes | kept |\n| configurable impure env | `configurable-impure-env` | `impure-env` setting | **Experimental** | \u2014 |\n| Parallel eval / builtins.parallel | `parallel-eval` (Determinate) | `builtins.parallel` | Determinate developer-preview only | n/a |\n\n## Key links for further reading\n- **RFC 62** (content-addressed paths): `github.com/NixOS/rfcs/blob/master/rfcs/0062-content-addressed-paths.md`; CA stabilization milestone: `github.com/NixOS/nix/milestone/35`; Tweag series \"Towards a content-addressed model for Nix,\" \"Self-references in content-addressed Nix,\" \"Implementing a content-addressed Nix.\"\n- **RFC 92** (dynamic derivations): `github.com/NixOS/rfcs/blob/master/rfcs/0092-plan-dynamism.md`; tracking issue `NixOS/nix#6316`; `builtins.outputOf` PR `NixOS/nix#8813`; Farid Zakaria's \"An early look at Nix Dynamic Derivations\" and `MakeNix`.\n- **RFC 133** (git-hashing): `github.com/NixOS/rfcs/blob/master/rfcs/0133-git-hashing.md`.\n- **FOD security**: CVE-2024-27297 advisory `GHSA-2ffj-w4mj-pg37` and NixOS Discourse \"Security fix: Nix fixed-output derivation sandbox bypass\"; Guix's write-up of the same CVE.\n- **IFD**: Nix manual \"Import From Derivation\"; PR `NixOS/nix#5253`; nixos.wiki \"Import From Derivation.\"\n- **String context / advanced attributes**: Nix manual \"String context\" and \"Advanced Attributes\"; \"Store Derivation and Deriving Path\"; \"Derivation 'ATerm' file format.\"\n- **Store-path internals**: Farid Zakaria \"What's in a Nix store path\"; Max Bernstein \"Nix derivations by hand\"; Eelco Dolstra's PhD thesis \"The Purely Functional Software Deployment Model.\"\n- **Forks**: Lix RFDs `git.lix.systems/lix-project/lix/issues/815` (remove ca-derivations) and `/767` (remove recursive-nix); Determinate Systems blog \"Parallel evaluation comes to Determinate Nix\" and \"Determinate Nix: the recent past and the shining future\"; Snix `snix.dev`.\n\n## Recommendations\n\n**Use now (stable, low-risk):**\n- FODs and all nixpkgs fetchers; multiple outputs; `__structuredAttrs` + `outputChecks`; `allowed/disallowedReferences/Requisites`; `preferLocalBuild`/`allowSubstitutes`; `requiredSystemFeatures` + remote builders; `exportReferencesGraph`; string-context builtins when you genuinely need them.\n- For vendored-dependency hashes, always reset to `lib.fakeHash` on version bumps and let the build tell you the real hash; use `cargoDepsName = pname` to reduce churn; drive updates with `nix-update`.\n\n**Use with caution (experimental, private/CI only, expect breakage):**\n- **CA derivations**: worth experimenting for early-cutoff/dedup on a private Hydra+cache, but do not build anything load-bearing on it, and **do not adopt it on Lix** (being removed). Verify with `nix path-info --sigs` (look for a `ca:` line).\n- **Dynamic derivations / recursive-nix**: research-grade. Pin an exact Nix commit, enable `dynamic-derivations ca-derivations recursive-nix` together, and treat any working setup as fragile.\n- **Impure derivations**: fine for non-hermetic CI checks (`__impure = true` in a `nix flake check`), understanding outputs are never cached and consumers must be impure/CA.\n\n**Avoid / prefer alternatives:**\n- **IFD in nixpkgs-style projects**: prefer checked-in generated code. Where IFD is convenient (private flakes, Haskell/JS dev), keep it but know it disables the eval cache and can stall `nix flake show`. On Determinate, try `builtins.parallel` to blunt the cost.\n- **`__noChroot` / FOD network abuse**: only when unavoidable; document the impurity.\n\n**Choosing an implementation (decision thresholds):**\n- Need CA/dynamic/impure/recursive-nix \u2192 **CppNix** (or wait; watch Milestone #35).\n- Prioritize correctness/stability and don't use those features \u2192 **Lix** is a reasonable default.\n- Want fastest evaluation on big/monorepo/IFD-heavy codebases \u2192 **Determinate** (`parallel-eval`, lazy trees).\n- **Re-evaluate** if: Milestone #35 closes and CA ships on production Hydra (then CA becomes safe to adopt, and Lix said it would reconsider); or flakes/`nix-command` are de-experimented upstream.\n\n## Caveats\n- **Rapidly moving target.** These are experimental features whose semantics and CLIs can change or be removed without notice; version-specific behavior (e.g. `nar` mode since 2.21, impure derivations since 2.8, CA since 2.4) matters. Treat all forward-looking items as of mid-2026.\n- **Fork divergence is now permanent enough to matter.** The same `experimental-features` line does *not* behave identically across CppNix, Lix, and Determinate; Lix has actively deleted several features described here.\n- **Vendor benchmarks.** Determinate's IFD speedup (50s\u219210s), `nix flake check` (20s\u21925.5s), and `nix search` GC (24.3s\u219218.9s) figures are vendor-reported; independent numbers are scarce.\n- **Security.** FODs remain a trust boundary even after CVE-2024-27297 and its follow-up CVE-2026-39860; adding third-party substituters is a supply-chain trust decision. CA's multi-user-trust promise is not fully realized because realisation signing is incomplete.\n- **Milestone #35 counts** (~65%, 29 open/56 closed) are a March 2026 snapshot and will drift.\n- Where sources conflicted (e.g. exact defaults of `allow-import-from-derivation` across contexts), I favored the current Nix manual and primary issue threads; flakes enable IFD by default, which is why crane/haskell.nix \"just work\" under flakes but need `--allow-import-from-derivation` otherwise.", "creation_timestamp": "2026-07-12T22:58:01.476487Z"}]}