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-l3the component emits$CI_SERVER_URL/$CI_PROJECT_PATH, which is the consuming project rather than the build platform. Underslsa-l1GitLab Runner emitshttps://<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-l3signs 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.