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

Glossary

Canonical vocabulary for GitLab Hardened Builds. When several words exist for one concept, this file picks one and lists the rest under Avoid.

Definitions only. Decisions belong in ADRs, requirements in the spec.

Where a term comes from SLSA, this file follows SLSA v1.2. Our own terms are mapped onto SLSA’s in the section below, because the two vocabularies overlap awkwardly and getting the mapping wrong changes what we are allowed to claim.

Project components

The project offers two halves. The hardened-artifact API is pure Nix and produces the artifact; the hardened-artifact CI component does the parts a Nix function cannot - signing, attestation, scanning, and choosing a runner tier. A consuming project normally uses both.

hardened-artifact API (lib.${system}): The pure-Nix half, at lib/api.nix. A consuming project adds this repository as a flake input and calls mkHardenedImage (derivation-first) or mkSecureArtifact (source-first) to turn a package into a hardened OCI image with a CycloneDX SBOM. Also exposes detect, the ecosystem detector. Linux only. It does not sign, attest, scan or run an L3 build - those need keys, network and runners. See ADR-0039. Avoid: “the Nix library”. This repository has several; name the one you mean.

hardened-artifact CI component: The reusable CI/CD configuration this project publishes at ci/components/hardened-artifact.yml. A consuming project adds it to its own pipeline with include: and passes it a few inputs; GitLab then merges its jobs into that pipeline, where they build the artifact, produce an SBOM, and scan it. Under slsa-l3 they also sign it. Avoid: “the component” on its own, as GitLab’s components/slsa is also relevant. Say which one.

SBOM library (lib.sbom.${system}): Ecosystem-aware CycloneDX SBOM generation, built on a fork of bombon. It itemises the vendored language dependencies that a pkg:nix/-only SBOM misses. Used by the API’s passthru.sbom and available to consumers directly. See ADR-0036.

Overlays (overlays.default, overlays.overrides): nixpkgs overlays exported so a consuming project can apply them to its own nixpkgs. default adds this project’s builders and libraries - the image builders, fipsLib, the per-ecosystem emitters and the version registry. overrides carries security overrides, which rebuild a package’s reverse dependency closure from source. A project that builds its own packages uses the package set instead, which also carries the pin and the nixpkgs config.

Package set (legacyPackages.${system}): The curated nixpkgs package set: a pinned nixpkgs with this project’s additions, overrides and nixpkgs configuration, the same set this project’s own outputs build with. A project that builds its own software builds it on this set instead of its own nixpkgs. legacyPackages is the output name nixpkgs uses for a set too large for packages; it does not mean deprecated. See ADR-0039. Avoid: “the overlaid nixpkgs”, or pkgs on its own.

Flake template (templates.default, alias templates.hardened): Scaffolding run with nix flake init -t. Produces a starter consumer flake that imports the API.

Terms that are easy to get wrong

These three are ambiguous in ordinary usage and have burned us. Use the canonical term.

Consuming project: An external GitLab project that includes the hardened-artifact component into its own pipeline to get a hardened artifact built. It is where the source and the pipeline definition live; the team behind it is SLSA’s producer. Avoid: consumer. SLSA reserves that for “a party who uses software provided by a producer”, which is the far end of the supply chain from this.

Build tier: The assurance level a build runs at, chosen by the consuming project. One of slsa-l3 or slsa-l1. A build tier is not a runner tier: one build tier can span two runner tiers, which is exactly what slsa-l3 does. Avoid: using “tier” unqualified.

Runner tier: A class of CI runner identified by a tag (or by being untagged). See the three named below. Avoid: using “tier” unqualified.

Build tiers

slsa-l3 tier: The build tier that realises the derivation on the offline SLSA L3 VM and runs everything else on the shared Attic runner. Achieves SLSA Build L3 isolation. See ADR-0035.

slsa-l1 tier: The build tier that runs every job on any untagged runner, and takes its SLSA provenance from Runner-generated provenance, and signs nothing. Achieves SLSA Build L1. Decided in ADR-0044.

Runner tiers

Offline build VM (nix-slsa-l3-amd64 / nix-slsa-l3-arm64): An ephemeral GCE VM booted per job from a pre-baked offline NixOS image, with zero network egress, no secrets and no signing key. The only place the slsa-l3 tier realises a derivation from upstream source.

Shared Attic runner (nix-attic-shared): The persistent, networked runner-manager host carrying the shared Nix store, the Attic cache, and the secrets (Sigstore OIDC, registry credentials, the Nix signing key). Everything in the slsa-l3 tier that is not a from-source build runs here. Avoid: “shared runner” on its own. It collides with GitLab.com’s shared runner fleet, which is a completely different trust posture.

Untagged runner: Whichever runner picks up an untagged job in the consuming project: a GitLab.com hosted runner, an instance runner or a project runner. The slsa-l1 tier runs all its jobs on one. Avoid: shared runner, SaaS runner.

SLSA model terms

Quoted verbatim and in full from SLSA v1.2 - terminology and tracks - each followed by what it maps to here. Definitions are reproduced complete, never abridged. Where our usage and SLSA’s could drift, the SLSA wording wins.

Tracks

Build track: SLSA: “The SLSA build track describes increasing levels of trustworthiness and completeness in a package artifact’s provenance. Provenance describes what entity built the artifact, what process they used, and what the inputs were. The lowest level only requires the provenance to exist, while higher levels provide increasing protection against tampering of the build, the provenance, or the artifact.

The primary purpose of the build track is to enable verification that the artifact was built as expected. Consumers have some way of knowing what the expected provenance should look like for a given package and then compare each package artifact’s actual provenance to those expectations. Doing so prevents several classes of supply chain threats.

Each ecosystem (for open source) or organization (for closed source) defines exactly how this is implemented, including: means of defining expectations, what provenance format is accepted, whether reproducible builds are used, how provenance is distributed, when verification happens, and what happens on failure. […]“

hardened-builds: The only track this project addresses. The slsa-l1 and slsa-l3 build tiers are Build track levels, and wherever these docs say “SLSA L1” or “SLSA L3” unqualified they mean Build L1 or Build L3.

Source track: SLSA: “The SLSA source track provides producers and consumers with increasing levels of trust in the source code they produce and consume. It describes increasing levels of trustworthiness and completeness of how a source revision was created.

The expected process for creating a new revision is determined solely by that repository’s owner (the organization) who also determines the intent of the software in the repository and administers technical controls to enforce the process.

Consumers can review attestations to verify whether a particular revision meets their standards.“

hardened-builds: Out of scope. This project addresses the Build track only, and makes no claim about how a consuming project’s source was authored, reviewed or protected.

Build Track: Roles

Producer: SLSA: “A party who creates software and provides it to others. Producers are often also consumers.”

hardened-builds: The team behind a consuming project.

Verifier: SLSA: “A party who inspect an artifact’s provenance to determine the artifact’s authenticity.”

hardened-builds: The downstream party our attestations have to convince.

Avoid: relying party. Note a consumer and a verifier are distinct SLSA roles - a consumer may verify, or delegate verifying to someone else.

Consumer: SLSA: “A party who uses software provided by a producer. The consumer may verify provenance for software they consume or delegate that responsibility to a separate verifier.”

hardened-builds: Whoever runs the hardened image downstream - not the project that includes our component.

Infrastructure provider: SLSA: “A party who provides software or services to other roles.”

hardened-builds: GitLab, or the operator of a self-managed instance, for both build tiers. Under slsa-l3 this project is one too, since it operates the runner tiers.

Build Track: Build model

Platform: SLSA: “System that allows tenants to run builds. Technically, it is the transitive closure of software and services that must be trusted to faithfully execute the build. It includes software, hardware, people, and organizations.”

hardened-builds: GitLab’s control plane and the runner fleet, the hardware they run on, and the organization operating them. Which organization that is depends on where the instance runs: GitLab on GitLab.com, the operator of a self-managed instance otherwise. Under slsa-l3 this project is part of the platform too, because it operates the runner tiers - as runner operator.

Neither the consuming project nor the contents of this repository are part of it: the CI component is Steps, the Nix libraries are Dependencies.

Avoid: builder. SLSA lists it as ambiguous: “Usually means build platform, but might be used for build environment, the user who invoked the build, or a build tool from dependencies. To avoid confusion, we always use ‘build platform’. The only exception is in the provenance, where builder is used as a more concise field name.” That exception is why builder.id below keeps the name.

Admin: SLSA: “A privileged user with administrative access to the platform, potentially allowing them to tamper with builds or the control plane.”

hardened-builds: On GitLab.com, GitLab’s own operators. On a self-managed instance, the organization running it.

Avoid: reading this as a GitLab project administrator. A project Owner or Maintainer has administrative rights over a project, not over the build platform - in SLSA terms they are a tenant, however privileged they are in GitLab’s own permission model. A SLSA admin can tamper with the control plane itself; a project Owner cannot. Avoid: treating admin and tenant as the same party because they work for the same organization. SLSA’s roles are defined by what someone controls, not by who employs them. Control plane is required to be outside the tenant’s control, not outside the producing organization’s - so a self-managed instance whose administrators are distinct from its project developers satisfies that, and the spec separately accepts that a malicious platform operator is out of scope at every level.

Tenant: SLSA: “An untrusted user that builds an artifact on the platform. The tenant defines the build steps and external parameters.”

hardened-builds: A user, not a project. “Untrusted” describes the platform’s posture toward them, not a judgement about the team.

Control plane: SLSA: “Build platform component that orchestrates each independent build execution and produces provenance. The control plane is managed by an admin and trusted to be outside the tenant’s control.”

hardened-builds: GitLab’s scheduling services, gitlab runner, gitlab-runner-helper - the GitLab components that decides what runs, sets up the container, and writes artifact metadata. It is not the job script, which is tenant territory.

Steps: SLSA: “The set of actions that comprise a build, defined by the tenant.”

hardened-builds: the .gitlab-ci.yml in the consuming project, plus the hardened-artifact CI component from this repository (assuming the consuming project imports it).

Build environment: SLSA: “The independent execution context in which the build runs, initialized by the control plane. In the case of a distributed build, this is the collection of all such machines/containers/VMs that run steps.”

hardened-builds: The runner’s job container.

External parameters: SLSA: “The set of top-level, independent inputs to the build, specified by a tenant and used by the control plane to initialize the build.”

hardened-builds: Our component inputs - flake-target and the rest. SLSA specifies that these should be recorded completely at Build L2+.

Avoid: build recipe. SLSA lists it as ambiguous: “Could mean external parameters, but may include concrete steps of how to perform a build. To avoid implementation details, we don’t define this term, but always use ‘external parameters’ which is the interface to a build platform. Similar terms are build configuration source and build definition.” So avoid build definition too - say external parameters for the inputs and Steps for the actions.

Dependencies: SLSA: “Artifacts fetched during initialization or execution of the build process, such as configuration files, source artifacts, or build tools.”

hardened-builds: Everything the consumer’s flake pins - including this project’s own Nix libraries, which the consumer fetches as a flake input.

Outputs: SLSA: “Collection of artifacts produced by the build.”

hardened-builds: The hardened image, and the SBOM derivation.

Build caches: SLSA: “An intermediate artifact storage managed by the platform that maps intermediate artifacts to their explicit inputs. A build may share build caches with any subsequent build running on the platform.”

hardened-builds: Attic, under slsa-l3. The GitLab CI cache: that slsa-l1 uses to pass its closure to later jobs is not one in SLSA’s sense - it is tenant-writable rather than platform-managed.

Provenance: SLSA: “Attestation (metadata) describing how the outputs were produced, including identification of the platform and external parameters.”

hardened-builds: Differs by build tier. Under slsa-l1 it is Runner-generated provenance, unsigned. Under slsa-l3 it is a document the hardened-artifact CI component writes and signs; a Step writes it, so it is tenant-generated. Avoid: saying “the provenance” without saying which build tier you mean.

Build Track: Isolation Requirements

Hosted (SLSA requirement, Build L2 and up): SLSA: “All build steps ran using a hosted build platform on shared or dedicated infrastructure, not on an individual’s workstation.”

hardened-builds: Met by slsa-l3, whose runner tiers are hosted infrastructure. slsa-l1 makes no claim: an Untagged runner may be a project runner on an individual’s workstation.

Isolated (SLSA requirement, Build L3 only): SLSA: “The build platform ensured that the build steps ran in an isolated environment, free of unintended external influence. In other words, any external influence on the build was specifically requested by the build itself. This MUST hold true even between builds within the same tenant project.” Full definition: https://slsa.dev/spec/v1.2/build-requirements#isolation-strength

hardened-builds: This is the property the slsa-l3 tier’s offline VM provides and the slsa-l1 tier does not.

Build Track: Provenance Requirements

Provenance Exists (SLSA requirement, Build L1 and up): SLSA: “The build process MUST generate provenance that unambiguously identifies the output package by cryptographic digest and describes how that package was produced. The format MUST be acceptable to the package ecosystem and/or consumer. It is RECOMMENDED to use the SLSA Provenance format and associated suite because it is designed to be interoperable, universal, and unambiguous when used for SLSA. […]” SLSA, Completeness: “Best effort. The provenance at L1 SHOULD contain sufficient information to catch mistakes and simulate the user experience at higher levels in the absence of tampering.” SLSA, Authenticity: “No requirements.” SLSA, Accuracy: “No requirements.” Full definition: https://slsa.dev/spec/v1.2/build-requirements#provenance-exists

hardened-builds: Met by both build tiers. Each emits a SLSA Provenance predicate naming the artifact by SHA-256 digest - the Runner-generated provenance under slsa-l1, the component’s tenant-generated document under slsa-l3.

Neither tier uses the full RECOMMENDED suite, a DSSE envelope around an in-toto Statement carrying a SLSA Provenance predicate. Both use the Statement and predicate formats. slsa-l1 leaves the Statement unsigned. slsa-l3 signs it with cosign sign-blob, which is a detached signature over the file rather than a DSSE envelope.

Provenance is Authentic (SLSA requirement, Build L2 and up): SLSA, Authenticity: “Consumers MUST be able to validate the authenticity of the provenance attestation […]” SLSA, Accuracy: “The provenance MUST be generated by the control plane (i.e. within the trust boundary identified in the provenance) and not by a tenant of the build platform (i.e. outside the trust boundary), except as noted below.” SLSA, Completeness: “SHOULD be complete […]” Full definition: https://slsa.dev/spec/v1.2/build-requirements#provenance-authentic

hardened-builds: Neither build tier meets it today. Runner-generated provenance is generated by the control plane, which the Accuracy clause requires, but under slsa-l1 it is unsigned, so consumers have nothing to validate. Under slsa-l3 the document is tenant-generated.

Provenance is Unforgeable (SLSA requirement, Build L3 only): SLSA, Accuracy: “Provenance MUST be strongly resistant to forgery by tenants.” Secret material used to authenticate it “MUST NOT be accessible to the environment running the user-defined build steps”, and “every field in the provenance MUST be generated or verified by the build platform in a trusted control plane.” SLSA, Completeness: “SHOULD be complete […]” Full definition: https://slsa.dev/spec/v1.2/build-requirements#provenance-unforgeable

hardened-builds: The Build L3 form of the above. Neither build tier meets it today. slsa-l1 signs nothing. Under slsa-l3 the signing capability is accessible inside a job, exposing it to the untrusted build steps.

Build: Provenance format

builder.id: SLSA: “URI indicating the transitive closure of the trusted build platform. This is intended to be the sole determiner of the SLSA Build level. If a build platform has multiple modes of operations that have differing security attributes or SLSA Build levels, each mode MUST have a different builder.id and SHOULD have a different signer identity. This is to minimize the risk that a less secure mode compromises a more secure one. The builder.id URI SHOULD resolve to documentation explaining:

  • The scope of what this ID represents.
  • The claimed SLSA Build level.
  • The accuracy and completeness guarantees of the fields in the provenance.
  • Any fields that are generated by the tenant-controlled build process and not verified by the trusted control plane, except for the subject.
  • The interpretation of any extension fields. “ Full definition: https://slsa.dev/spec/v1.2/build-provenance#builder

hardened-builds: Not met today, on any of the three counts.

  • Names the platform: under slsa-l3 the component emits $CI_SERVER_URL/$CI_PROJECT_PATH, which is the consuming project rather than the build platform. Under slsa-l1 GitLab Runner emits https://<host>/<project>/-/runners/<runner-id>, also project-specific and varying per runner. That second format is GitLab’s, not this project’s.
  • Differs per mode: the two build tiers are modes with differing security attributes, so they require different values. Neither tier emits a stable identifier for its mode. The SHOULD on distinct signer identities does not apply to slsa-l1, which signs nothing; slsa-l3 signs with the consuming project’s OIDC identity.
  • Resolves to documentation: neither URI does.

GitLab features

Runner-generated provenance: A JSON document GitLab Runner writes describing what a job produced, switched on with RUNNER_GENERATE_ARTIFACTS_METADATA. GitLab’s own runner process writes it rather than the job’s script, and the runner hashes the artifact files itself. It is unsigned. It is the slsa-l1 tier’s SLSA provenance. GitLab docs: https://docs.gitlab.com/ci/runners/configure_runners/#artifact-provenance-metadata (field reference) and https://docs.gitlab.com/ci/pipeline_security/slsa/ (GitLab’s own account of the SLSA level it targets).

Mechanics

Nix substituter: A server Nix downloads finished build results from instead of building them locally. Before building anything, Nix asks each configured substituter whether it already has the result; if one does, Nix fetches it. Nix accepts a downloaded result only if it is signed by a key listed in trusted-public-keys, so a substituter is only as trustworthy as the key you configure for it.

Attic: The shared Nix binary cache that carries closures from the offline build VM to the jobs that consume them. Not used by the slsa-l1 tier.