Skip to Content
Determinate NixProvenance

Provenance in Determinate Nix

Determinate Nix has a provenance command that enables you to:

For more on provenance in Determinate Nix, check out our Provenance and SBOMs with Nix webinar.

Enabling provenance

Provenance tracking is gated behind the provenance experimental feature. To use the nix provenance commands, enable that feature in your nix.custom.conf file:

/etc/nix/nix.custom.conf
extra-experimental-features = provenance

If you’re on macOS and manage your system with nix-darwin, you can instead set extra-experimental-features using the determinateNix.customSettings attribute in the determinate nix-darwin module:

customSettings = { extra-experimental-features = "provenance"; };

Showing provenance

Available since Determinate Nix 3.16.0.

The nix provenance show command in Determinate Nix displays the provenance chain behind a store path (you can also supply a flake reference that resolves to a store path).

Here’s an example:

nix provenance show \ $(nix build --print-out-paths "https://flakehub.com/f/NixOS/nixpkgs/=0.2511.909025#ponysay")

You should see output like this (if you’re using cache.nixos.org as a cache, which Determinate Nix does by default):

/nix/store/c0fnfby2hvzkbf768hcryvj4yy9xahpa-ponysay-unstable-2021-03-27 ← copied from https://cache.nixos.org

nix provenance show displays a summary of the provenance chain. To see the full provenance data for a store path, check the provenance field in the output of nix path-info --json. Each step in the chain is a JSON object with a type field, and its next field contains the step that came before it. Here’s the provenance for a build of Determinate Nix that was copied from FlakeHub Cache:

{ "provenance": { "from": "https://edge.cache.flakehub.com", "next": { "buildHost": "pt36iu1eciih4", "drv": "22d5fy6lcn1gjh2j4gikv6hqkiz277g4-determinate-nix-3.23.2.drv", "next": { "flakeOutput": "packages.x86_64-linux.default", "next": { "next": { "attrs": { "lastModified": 1791922994, "ref": "refs/heads/gh-readonly-queue/main/pr-688-f8aee05268a97df9b139a120bb8bee9b9c6d8313", "rev": "b9ca37a0d89afc5a8cf4ee56c6702374aa9c9c87", "shallow": true, "type": "git", "url": "file:///home/runner/work/nix-src/nix-src" }, "type": "tree" }, "subpath": "/flake.nix", "type": "subpath" }, "pure": true, "type": "flake" }, "output": "out", "system": "x86_64-linux", "tags": {}, "type": "build" }, "type": "copied" }, // other attributes }

Reading from the outside in, this says that the store path was copied from FlakeHub Cache, that it was built from the out output of a derivation on an x86_64-linux build host, that the derivation was instantiated from the packages.x86_64-linux.default output of a flake, and that the flake’s flake.nix came from a specific revision of a Git repository.

Verifying provenance

Available since Determinate Nix 3.16.1.

The nix provenance verify command in Determinate Nix verifies two things about a store path (you can also supply a flake reference that resolves to a store path):

  1. That all of the derivation’s sources are currently available.
  2. That evaluating the derivation using the fetched sources results in the same .drv file as the derivation currently in the Nix store.

This command, for example, would verify the provenance of a NixOS system closure:

nix provenance verify /run/current-system

The output could look something like this:

/nix/store/bwmc8dmbf01b07sgzfa48f5y5dgj48am-nixos-system-machine-25.11.20260214.3aadb7c ✅ fetched tree 'git+file:///my-repo?ref=refs/heads/master&rev=f6ba198ac4c8010cb0879978541189f201b734f1' ✅ evaluated 'git+file:///my-repo?ref=refs/heads/master&rev=f6ba198ac4c8010cb0879978541189f201b734f1#nixosConfigurations.machine.config.system.build.toplevel' ✅ re-instantiated path '/nix/store/qi4z9bg89sxjrs5ar11627hgwjiv42m3-nixos-system-machine-25.11.20260214.3aadb7c.drv' ✅ rebuilt derivation '/nix/store/qi4z9bg89sxjrs5ar11627hgwjiv42m3-nixos-system-machine-25.11.20260214.3aadb7c.drv^out'

In this case, Determinate Nix confirms that the sources are all available and that the resulting .drv file is the same as the one currently in the Nix store; if the sources weren’t available or the .drv files differed, verification would fail.

The nix provenance show command only works with derivations that have already been realised in the local Nix store.

You can also use the command with any store path or flake reference. Here’s an example:

nix provenance verify \ $(nix build --print-out-paths "https://flakehub.com/f/NixOS/nixpkgs/=0.2511.909025#ponysay")

Recording provenance

Determinate Nix records provenance data as metadata alongside each store path, accompanying information like the path’s references and signatures. Because provenance data is metadata and by definition not part of a derivation, it never affects derivation hashes or output paths in the Nix store.

With provenance enabled, Determinate Nix records most of the provenance chain automatically, including flake evaluation, fetching, building, and copying. See the provenance JSON format in the Determinate Nix manual for the full set of fields that Determinate Nix records.

You can also add custom data to the provenance chain in two ways:

Derivation metadata

When provenance is enabled, Determinate Nix provides a builtins.derivationWithMeta function. It works just like builtins.derivation but it accepts an additional __meta attribute set, which Determinate Nix records in the derivation’s provenance data. Unlike other derivation attributes, Determinate Nix doesn’t pass __meta to the derivation’s builder, which means that it doesn’t affect the derivation’s hash or output path.

Because builtins.derivationWithMeta only exists when the feature is enabled, we recommend falling back to builtins.derivation when it’s missing. Here’s an example mkDerivation function that does that:

flake.nix
{ outputs = { self }: { packages.x86_64-linux.default = let mkDerivation = if builtins ? derivationWithMeta then builtins.derivationWithMeta else args: builtins.derivation (removeAttrs args [ "__meta" ]); in mkDerivation { name = "hello"; system = "x86_64-linux"; builder = "/bin/sh"; args = [ "-c" "echo hello > $out" ]; __meta = { description = "A friendly greeting"; license = "MIT"; maintainer = "platform-team@my-org.com"; }; }; }; }

The __meta attribute set can contain any JSON-compatible values, but it can’t contain string context, such as references to store paths. If it does, evaluation fails.

Determinate Nix only records derivation metadata when it first instantiates a derivation, which is when it writes the .drv file to the Nix store. Because __meta doesn’t affect the derivation’s hash, changing it doesn’t produce a new .drv file, so re-evaluating after you change __meta doesn’t record the new metadata.

Derivation metadata in Determinate Secure Packages

Determinate Secure Packages uses derivation metadata to attach security-related information to every package it builds. Its stdenv.mkDerivation calls builtins.derivationWithMeta whenever it’s available and passes a subset of each package’s meta attribute as __meta:

FieldWhat it contains
description, homepage, changelog, downloadPageGeneral information about the package
licenseThe package’s license
identifiers.possibleCPEsCandidate Common Platform Enumeration (CPE) identifiers used to match the package against vulnerability databases
cyclonedx_1_5.vulnerabilitiesCurated Vulnerability Exploitability eXchange (VEX) statements, in CycloneDX 1.5 format

The VEX statements come from each package’s meta.vulns attribute, which records whether known vulnerabilities actually affect the package. Here’s an example:

meta = { # ... vulns = { CVE-2021-36230 = { analysis = { justification = lib.vulns.justification.codeNotPresent "Only affects Terraform Enterprise"; state = lib.vulns.state.falsePositive; }; }; }; };

At evaluation time, Determinate Secure Packages adds the affected version range and a link to the advisory source, converts each entry to CycloneDX format, and passes the result to Determinate Nix as part of __meta. Determinate Nix then records it in the provenance of the resulting store path, so the vulnerability analysis travels with the package through builds and binary caches. Tools that inspect store paths, like FlakeBOM and FlakeAudit, can then read provenance data from nix path-info --json without re-evaluating the package.

See Adding your own vulnerability metadata for more on meta.vulns and the lib.vulns helpers.

Build provenance tags

The build-provenance-tags setting enables you to attach arbitrary name/value pairs to the provenance data of every store path that a machine builds. This is useful for recording things like the URL of the CI job that ran the build or the name of the build cluster.

You can set the tags in the nix.custom.conf file as a JSON object. Here’s an example:

/etc/nix/nix.custom.conf
build-provenance-tags = {"ci-job": "https://github.com/my-org/my-repo/actions/runs/123456789", "cluster": "prod-builders"}

You can also set the tags for a single command, which can handy in environments like CI where the values change with every run. Like any Nix setting, build-provenance-tags is available as a CLI flag:

nix build \ --build-provenance-tags '{"ci-job": "https://github.com/my-org/my-repo/actions/runs/123456789"}' \ .#my-package

Or you can set it through the NIX_CONFIG environment variable:

export NIX_CONFIG='build-provenance-tags = {"ci-job": "https://github.com/my-org/my-repo/actions/runs/123456789"}' nix build .#my-package

Note that tags that you pass this way replace the tags in nix.custom.conf. To add to the configured tags instead, use --extra-build-provenance-tags:

nix build \ --extra-build-provenance-tags '{"ci-job": "https://github.com/my-org/my-repo/actions/runs/123456789"}' \ .#my-package

With the nix.custom.conf example above, this build records both cluster and ci-job.

On a multi-user installation, the Nix daemon performs the build and records its provenance. The daemon only accepts build-provenance-tags from trusted users, so if you aren’t one, it ignores your tags and prints a warning like this:

warning: ignoring the client-specified setting 'build-provenance-tags', because it is a restricted setting and you are not a trusted user

Determinate Nix only records tags on store paths that it builds. Store paths that are already present or that it copies from a binary cache keep the provenance they already have.

In the JSON output of nix path-info --json, the tags appear in the tags field of the entry with "type": "build".

Last updated on