Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

SLSA Level 3 builds

The from-source build runs on an ephemeral, offline VM with no secrets and no network. Everything downstream substitutes that build’s output rather than rebuilding it. That combination is what earns SLSA Level 3, and it’s a security boundary the component enforces (ADR-0035).

Choosing this tier

Include /ci/components/hardened-artifact-slsa-l3.yml. This requires access to specific tagged runners, including an offline builder runner. The alternative, hardened-artifact-slsa-l1.yml, runs the same build, SBOM and scan steps on any available runner, but does not provide offline isolation or signing. See Integrate an external project.

The two runner roles

CI uses two runner roles, and which one a job lands on is a security decision:

  • nix-slsa-l3-amd64 / nix-slsa-l3-arm64 — ephemeral, offline VMs that realise derivations from upstream source under isolation. No network egress, no Attic client, no secrets, no signing key. The runner-manager powers the VM off after the build and extracts the outputs out-of-band. This is the only place a from-source build runs.
  • nix-attic-shared — the persistent, networked host: shared Nix store, Attic substituter/push, network, and the secrets (Sigstore OIDC, registry creds, signing key). Everything that is not a from-source build runs here.

How the component uses them

In hardened-artifact-slsa-l3.yml, the hardened:build job carries the slsa-build-tag (nix-slsa-l3-amd64 by default) and does exactly one thing:

script:
  - nix build ".#$[[ inputs.flake-target ]]" --print-out-paths --no-link

It runs offline. Nothing secret happens. The runner-manager extracts the closure and pushes it to the shared Attic afterward.

Every downstream job — SBOM, scan, sign, attest — runs on nix-attic-shared and resolves the artifact with:

- nix build ".#$[[ inputs.flake-target ]]" --print-out-paths --no-link --max-jobs 0

--max-jobs 0 means “do not build anything; get it from a substituter.” A cache miss fails the job rather than quietly rebuilding on the shared runner. So the SBOM’d, scanned, and signed artifact is provably the one the offline VM produced — no off-VM rebuild can slip in.

Why this is SLSA L3

SLSA Level 3 asks for a hardened, isolated build platform whose provenance is non-forgeable. Here:

  • the build is hermetic (every input is a pinned, hash-checked fetcher);
  • it runs on an ephemeral, isolated VM with no network and no secrets;
  • the VM cannot sign anything, so provenance is generated afterward on a separate, credentialed host over the exact substituted output.

The security invariant

Signing, secrets, and network must not touch the offline VM. The component never adds a signing step, an Attic push, a registry login, or a secret to hardened:build. Signing happens post-build on nix-attic-shared, where the key lives and the transparency-log upload can reach the network. See Signing & provenance.

Reference

The runner-role model, the offline-build invariant, and the substitute-only downstream discipline are ADR-0035.