The public hardened-artifact API (ADR-0039), generated from the doc-comments in lib/api.nix.
lib.api.mkHardenedImage
Derivation-first core (ADR-0039, FR-092..095): wrap an ALREADY-BUILT
derivation in a hardened OCI image (via buildGitlabImage) and, by default,
attach + build its CycloneDX SBOM (via lib/sbom). This is the headline
“I have a derivation, give me a hardened image + SBOM” entry point, and the
one mkSecureArtifact funnels into.
Inputs (attrset):
name(string, required) — image name, e.g. “my-service”version(string, required) — image tag, e.g. “1.2.3”package(derivation, required) — the built artifact to bundleentrypoint/cmd(list|null) — OCI entrypoint/cmd (default null)env(attrset) — extra env vars (default {})extraDeps(list) /extraLabels(attrset) — extra runtime deps / labelsuser/workDir/debug— passthrough tobuildGitlabImage(debug = false⇒ no shell in the image, FR-002)fips(bool, default false) — build the image under the FIPS package set so it carries FIPS labels. TRUTHFULNESS CONTRACT (FR-095): the caller MUST have builtpackageunder the FIPS set; this flag does NOT convert a non-FIPS build into a FIPS one.sbom(bool, default true) — attach + build a CycloneDX SBOMsbomEcosystems(list|null) — overridelib/sbomauto-detection (e.g. ruby/yarn lock paths that cannot be inferred from the derivation)
Returns the nix2container image derivation, with passthru:
sbom (only when sbom = true), package, labels, fips.
Type
mkHardenedImage :: AttrSet -> Derivation
Example
lib.${system}.mkHardenedImage {
name = "my-service";
version = "1.0.0";
package = self.packages.${system}.my-service;
cmd = [ "/bin/my-service" ];
}
lib.api.mkSecureArtifact
Src-first umbrella (ADR-0039, FR-096..098): detect → build → mkHardenedImage, every stage overridable. Point it at a source tree; it
detects the ecosystem (unless forced), builds a derivation with the matching
emitter, and hands that to mkHardenedImage.
Inputs (attrset):
name,version(required)src(derivation/path, required unlessoverrideDerivation) — the sourcerev(string) — full commit SHA (used for Go ldflags etc.)ecosystem(string|null) — force “go”|“python”|“ruby”|“rust”; null ⇒ detect fromsrc(FR-097)ecosystemArgs(attrset) — per-ecosystem knobs: go{ vendorHash; goVersion?; goreleaser?; subPackages?; }, rust{ cargoLock; outputHashes?; }, ruby{ gemset; ruby?; gemdir?; }, python{ lock; python?; overrides?; exclude?; format?; }for a./poetry.lock, or{ lock; python?; overrides?; sourcePreference?; }for a./uv.lockoverrideDerivation(derivation|null) — bypass detect+build entirely and image THIS derivation (FR-097)image(attrset) — extra args forwarded tomkHardenedImagefips,sbom,sbomEcosystems— as inmkHardenedImage
Returns the mkHardenedImage result, with an additional passthru.ecosystem
= the detected/forced IR (or { ecosystem = "custom"; } when
overrideDerivation is used).
If src matches no known marker and neither ecosystem nor
overrideDerivation is given, detect throws at eval time naming the
markers searched and the escape hatches (FR-098) — no silent empty image.
Type
mkSecureArtifact :: AttrSet -> Derivation
Example
lib.${system}.mkSecureArtifact {
name = "my-service";
version = "1.0.0";
src = ./.;
rev = self.rev or "0000000000000000000000000000000000000000";
ecosystemArgs = { vendorHash = "sha256-…"; }; # Go
}
lib.api.buildEcosystemDerivation
Internal dispatch from a detected/forced ecosystem to its emitter, producing
the build derivation mkSecureArtifact then images. Exposed (rather than
hidden in a let) so tests and docs can exercise a single ecosystem’s
build-arg assembly directly.
Takes { name; version; src; rev; ecosystem; ecosystemArgs; buildPkgs; }
where ecosystem is one of “go” | “rust” | “ruby” | “python” and buildPkgs
is the plain or FIPS package set. Throws for an unsupported ecosystem.
Type
buildEcosystemDerivation :: AttrSet -> Derivation
Ecosystem detector (lib/ecosystem/detect.nix).
lib.detect.detect
Detect a source tree’s ecosystem by file-presence and return the normalized IR:
{
ecosystem = "go" | "rust" | "python" | "ruby";
toolchainVersion = string | null; # best-effort, from the marker
primaryMarker = string; # e.g. "go.mod"
buildConfigFile = string | null; # e.g. ".goreleaser.yml", relative
}
Throws (rather than returning a sentinel) when no marker is present, so a
misconfigured mkSecureArtifact fails at eval time with an actionable
message instead of silently producing an empty derivation.
Type
detect :: Path -> AttrSet
Example
detect ./my-go-service
# => { ecosystem = "go"; toolchainVersion = "1.22.3"; primaryMarker = "go.mod"; buildConfigFile = null; }
lib.detect.tryDetect
Like detect, but returns null instead of
throwing when no ecosystem marker is present.
Use this for introspection — deciding whether a tree is buildable at all, or
branching on the result — where a throw would be inconvenient. When you
already know the tree has a marker, call detect directly so a missing one
surfaces as an error rather than a silent null.
Type
tryDetect :: Path -> AttrSet | null
Example
tryDetect ./maybe-empty-dir # => null
tryDetect ./my-go-service # => { ecosystem = "go"; … }
lib.detect.markers
The marker table that drives detection, in priority order.
Each entry pairs an ecosystem with its primary marker file (the presence
test) and the buildConfig files worth noting when that ecosystem wins. The
first ecosystem whose primary marker exists takes precedence, so order only
matters for a tree that carries several (say, a Go tool vendoring a small
Ruby helper) — detection picks the top-level build, which is unambiguous for
the gitlab-org projects this serves. Exposed for the docs site and tests.
Type
markers :: [ { ecosystem :: String; primary :: String; buildConfig :: [ String ]; } ]
Ruby / Bundler gemset enrichment (lib/ecosystem/ruby.nix).
lib.ruby.enrichGemset
Enrich a bundix gemset by swapping gems for their nixpkgs pre-built
equivalents where that’s safe, so bundlerEnv fetches and builds fewer gems
from rubygems.org.
For each gem in the gemset, this checks whether nixpkgs already ships it and,
depending on matchMode, substitutes the entry’s source to point at the
nixpkgs store path. The gem stays in the gemset either way, so bundlerEnv’s
dependency-graph resolution is unaffected — only the fetch/build source
changes.
matchMode controls how careful the substitution is:
"exact"(default) substitutes only when the nixpkgs version equals the gemset-pinned version. Bundler enforces lockfile versions strictly, so a mismatch (nixpkgs 7.2.3 vs a pinned 7.2.3.1) would break the build; exact matching can’t introduce that."name-only"substitutes whenever nixpkgs has a gem by that name and rewrites the entry’sversionto the nixpkgs one. This reuses more pre-built closure but can pull in a different version, so reach for it when you want maximum reuse and the test suite covers the gems in play.
exclude is a list of gem names to never substitute, even when nixpkgs has
them — the escape hatch for a name-only build where one gem’s nixpkgs version
diverges from the lockfile in a way Bundler rejects.
Returns the enriched gemset plus introspection: gemset (the enriched map,
the one to hand to bundlerEnv), enrichedGemset (the same value), the
substituted nixpkgsGems, and stats (total, fromNixpkgs, fromBundlerEnv,
matchMode).
Type
enrichGemset :: { gemset :: AttrSet; ruby :: Derivation; matchMode ? String; exclude ? [ String ]; } -> AttrSet
Example
enriched = rubyEcosystemLib.enrichGemset {
inherit gemset ruby;
matchMode = "exact";
};
gems = bundlerEnv { gemset = enriched.gemset; inherit ruby; };
CycloneDX SBOM helpers (lib/sbom).
lib.sbom.detect
Detect which language ecosystems a finished derivation vendors, from reliable build-system markers.
Reads goModules (set by buildGoModule) and cargoDeps (set by
buildRustPackage / rustPlatform) on the built package and returns one entry
per marker present. Both are safe to auto-detect because the emitters consume
the finished output rather than rebuilding, so emitting Rust components for a
package that only carries cargoDeps incidentally (nixpkgs ruby bundles
YJIT crates) is correct, not a false positive. Ruby and yarn aren’t
auto-detected here; their inventory lives in a gemset.nix or yarn.lock that
isn’t discoverable from the built drv, so they need an explicit
sbomEcosystems entry.
Type
detect :: Derivation -> [ { kind :: String; } ]
Example
detect goPackage # => [ { kind = "go"; } ]
lib.sbom.ecosystemsFor
Resolve the ecosystem list for a package: an explicit
passthru.sbomEcosystems if the package sets one, otherwise the
auto-detected list from detect.
An explicit list wins outright, so a package can both add path-bearing ecosystems (ruby’s gemset, yarn’s lockfiles) and suppress a false-positive auto-detection in the same declaration.
Type
ecosystemsFor :: Derivation -> [ AttrSet ]
Example
ecosystemsFor rails
# => [ { kind = "ruby"; gemset = ./gemset.nix; } { kind = "yarn"; yarnLock = [ ./yarn.lock ]; } ]
lib.sbom.attach
Attach an aggregated vendored SBOM to a package as its bombonVendoredSbom
passthru, covering every detected or declared ecosystem.
bombon’s transformer reads every *.cdx.json under that derivation’s $out,
so combining ecosystems is just collecting their files into one directory
(prefixed by kind so a package with more than one ecosystem doesn’t get
identically-named files clobbered on copy). Meant for stdenv packages
(rails, workhorse); it uses overrideAttrs to add the passthru, keeping
pkg a real derivation, which matters because buildBom hands attached
packages to bombon as extraPaths that go through exportReferencesGraph
and must be derivations. Since the emitters consume pkg rather than
rebuilding it, the package’s own output hash is unchanged. Passing a package
with no ecosystems returns it untouched; passing a non-derivation throws
rather than silently yielding a plain attrset that fails far from the cause.
Type
attach :: Derivation -> Derivation
Example
attach workhorse # => workhorse with passthru.bombonVendoredSbom
lib.sbom.buildBom
Build the ecosystem-complete CycloneDX SBOM derivation for a package.
Folds every vendored ecosystem SBOM into the Nix-closure SBOM so gem, npm, Go
module, and Rust crate components surface for CVE matching. For our images,
the bundled app package and its runtime deps are surfaced as
gitlabSbomPackages; a nix2container image references those as copied store
paths rather than derivation inputs, so bombon’s dependency walk sees bare
paths and can’t read their bombonVendoredSbom. This attaches each surfaced
package (safe, since the emitters consume rather than rebuild) and adds the
ones that gain a vendored SBOM to extraPaths, entering them into the graph
as real derivations the transformer reads from. Without it an image BOM would
list only the Nix closure. extraPaths, includeBuildtimeDependencies, and
excludes pass straight through to bombon.
Type
buildBom :: Derivation -> { extraPaths :: [ Derivation ]; includeBuildtimeDependencies :: Bool; excludes :: [ ... ]; } -> Derivation
Example
buildBom gitlabImage { } # => the image's CycloneDX SBOM derivation
OCI image layer construction (lib/oci-layers.nix).
lib.ociLayers.appMaxLayers
The layer budget for the one heavy application group in an image.
Set to 80, which leaves about 45 layers of headroom under the ~125-layer OCI
ceiling for the base, /etc, and extra groups (one layer each) plus any
future group, even across the largest closures. Only the application group
uses this split; every other group stays a single layer.
Type
appMaxLayers :: Int
lib.ociLayers.foldImageLayers
Build an image’s layers from an ordered list of group specs, deduplicating shared store paths across the groups.
Each spec is an attrset passed to nix2container.buildLayer
({ deps ? []; copyToRoot ? null; maxLayers ? … }). The specs fold in order,
and each built layer excludes paths already present in the layers before it,
so a store path lands in exactly one layer. A spec that omits maxLayers
gets maxLayers = 1 (the whole group in one layer); only a group that sets a
higher maxLayers (typically the application group, via appMaxLayers) is
popularity-split. That keeps the total layer count across all groups well
under the OCI ceiling.
Type
foldImageLayers :: [ AttrSet ] -> [ Layer ]
Example
foldImageLayers [
{ deps = [ glibc cacert ]; } # base, one layer
{ deps = [ app ]; maxLayers = appMaxLayers; } # split for cache reuse
{ copyToRoot = etcFiles; } # /etc at the image root
]
GitLab release version registry (lib/gitlab-versions.nix).
lib.gitlabVersions.releases
The latest patch of each maintained GitLab minor, keyed by MAJOR.MINOR.
Tracks the release train’s active window (N, N-1, N-2). Each key holds the full component set for that series (gitlab-rails, gitaly, workhorse, and the rest) with real harvested source and vendor hashes, so a build pinned to a minor is fully reproducible. Refresh with the gitlab-versions Rust CLI, which realigns every release to CNG and re-harvests hashes; lib/gitlab-release.nix resolves a selector against this set.
Type
releases :: AttrSet # { "19.2" = { gitlab-rails = { … }; … }; … }
lib.gitlabVersions.shared
Components pinned once and shared across every GitLab release.
These don’t follow the GitLab release train (the auth services iam / glaz /
sts, orbit-knowledge-graph, glab-cli, ai-assist, and friends), so they live
outside the per-minor releases set and get merged in by the resolver. Pin
values here and every release picks them up.
Type
shared :: AttrSet # { iam = { … }; glaz = { … }; … }
lib.gitlabVersions.latest
The default GitLab minor when a caller selects no release.
A MAJOR.MINOR key into releases (the newest tracked series). The resolver
falls back to this when handed null.
Type
latest :: String # e.g. "19.2"
GitLab release resolution helpers (lib/gitlab-release.nix).
lib.gitlabRelease.toMinorKey
Normalise a user-supplied release selector to a registry key
(MAJOR.MINOR).
Accepts null (resolves to the registry’s latest tracked release), a bare
"19.1", a full patch like "19.1.3" (reduced to its minor series), and
tolerates a leading v or trailing -ee. It only trims to the minor; the
patch component isn’t validated against the pin, so "19.1.0" resolves to
whatever 19.1 patch is tracked. Callers needing an exact-patch guarantee
should compare against patchVersion themselves.
Type
toMinorKey :: Registry -> (String | null) -> String
Example
toMinorKey registry "v19.1.3-ee" # => "19.1"
toMinorKey registry null # => registry.latest
lib.gitlabRelease.releaseKeys
List every tracked release key (MAJOR.MINOR) in the registry.
Order isn’t guaranteed, so sort if you need one; the flake uses this to enumerate the per-release alias outputs.
Type
releaseKeys :: Registry -> [ String ]
Example
releaseKeys registry # => [ "19.2" "19.1" "19.0" ]
lib.gitlabRelease.patchVersion
The exact patch version string for a resolved release, taken from that series’ gitlab-rails pin.
Used to build alias suffixes; resolves the selector through toMinorKey
first, so a null or partial selector works the same way resolve does.
Type
patchVersion :: Registry -> (String | null) -> String
Example
patchVersion registry "19.1" # => "19.1.3"
lib.gitlabRelease.resolve
Resolve a release selector to the flat component attrset every package
derivation consumes (gitlabVersions.<component>).
Picks the release for the selector’s minor key, then merges the shared, release-independent components under the release-specific ones (the two sets are disjoint by construction, so it’s really a union). Throws with the list of known keys when the selector names a release that isn’t tracked.
Type
resolve :: Registry -> (String | null) -> AttrSet
Example
(resolve registry "19.1").gitaly.version # => "19.1.3"
FIPS toolchain and mode configuration (lib/fips.nix).
lib.fips.modes
BoringSSL FIPS mode definitions, keyed by mode name.
Each mode fixes a BoringSSL branch, the exact commit and its sha256, the
NIST CMVP certificate number, and whether it’s validated. legacy is the
fully validated Certificate #5104; update-stream tracks a real pending CMVP
submission (FedRAMP FR-R7); main is development-only and unpinned. These
pins must track the certificate, never “latest”.
Type
modes :: AttrSet # { legacy = { branch; commit; sha256; certificateNumber; validated; systems; sbomSystems; }; … }
lib.fips.toolchainVersions
The exact build-toolchain versions NIST CMVP Certificate #5104 requires.
Any deviation invalidates FIPS certification, so this is a permanent
requirement rather than a floor; fipsAssertToolchainVersions enforces it at
build time. Covers clang, go, cmake, and ninja.
Type
toolchainVersions :: { clang :: String; go :: String; cmake :: String; ninja :: String; }
lib.fips.integrityHashes
Expected FIPS integrity hash checksums (from bcm.o), per architecture.
amd64 holds the BORINGSSL_bcm_text_hash a validated build must reproduce;
arm64 is null because ARM64 isn’t validated.
Type
integrityHashes :: { amd64 :: String; arm64 :: String | null; }
lib.fips.getArch
Map a Nix system string to the FIPS architecture label.
Returns “amd64”, “arm64”, “amd64-darwin”, “arm64-darwin”, or “unknown” for
anything unrecognised. This is the label used in image tags and provenance,
distinct from whether the arch is actually validated (see
isFipsValidated).
Type
getArch :: String -> String
Example
getArch "aarch64-linux" # => "arm64"
lib.fips.isFipsValidated
Whether a Nix system is FIPS 140-3 validated.
Only x86_64-linux is covered by Certificate #5104, so this is true there
and false everywhere else. Use it to gate FIPS-only build paths.
Type
isFipsValidated :: String -> Bool
Example
isFipsValidated "x86_64-linux" # => true
lib.fips.generateConstantsJson
Write the FIPS constants to a JSON file for non-Nix tools (Python, shell).
Keeps a single source of truth: it emits the toolchain versions and per-mode
branch, commit, certificate number, and validated flag, omitting the
Nix-specific sha256. Takes writeText explicitly so the pure config file
stays free of a pkgs dependency.
Type
generateConstantsJson :: { writeText :: ... } -> Derivation
Example
generateConstantsJson { inherit (pkgs) writeText; }
lib.fips.getShortCommit
The first 12 characters of a mode’s pinned commit.
Used as the short-commit suffix in image tags, SBOM and provenance version strings, and SPDX document namespaces; centralised here so every lateral builder derives it the same way.
Type
getShortCommit :: { commit :: String; … } -> String
Example
getShortCommit fipsLib.modes.legacy # => "85897d07196b"
lib.fips.getVersionString
The {mode}-{shortCommit} version string shared across all four lateral
builders.
Used identically as the image tag, the SBOM and provenance version, and the
SPDX versionInfo. Centralised so a change to the format updates every
builder’s output together; otherwise one build’s version strings would stop
matching across its own SBOM, provenance, and tag.
Type
getVersionString :: String -> { commit :: String; … } -> String
Example
getVersionString "legacy" fipsLib.modes.legacy # => "legacy-85897d07196b"
lib.fips.rustFips
Rust FIPS configuration for the aws-lc-rs / AWS-LC module (ADR-0028).
Separate from the BoringSSL module the Go services use: aws-lc-rs with the
fips feature builds AWS-LC from a pinned commit and branch, which carry
their own NIST CMVP certificates (dynamic #5298, static #5314). Holds the
pinned commit, branch, certificate numbers, module name, cargo feature flag,
the build-time Go minimum, and the validated architecture scope.
Type
rustFips :: AttrSet
lib.fips.getModeConfig
Look up the configuration for a named FIPS mode.
Returns the mode’s entry from modes, or
throws when the name isn’t one of legacy, update-stream, or main; throwing
keeps a typo from silently selecting a non-compliant build.
Type
getModeConfig :: String -> AttrSet
Example
(getModeConfig "legacy").certificateNumber # => "5104"
lib.fips.buildToolchain
Build the FIPS toolchain package set with every tool pinned to its certificate-required version.
Assembles Clang 17.0.6 (from the llvm-of-yore backport) plus Go 1.22.3, CMake
3.29.3, and Ninja 1.12.1 overridden to the exact sources Certificate #5104
requires, alongside the ordinary build tools. Takes llvm-of-yore and pkgs
explicitly so this config file keeps no implicit pkgs dependency. Returns an
attrset of the individual tools plus an allPackages list ready for
environment.systemPackages.
Type
buildToolchain :: { llvm-of-yore :: AttrSet; pkgs :: AttrSet; } -> AttrSet
Example
(fipsLib.buildToolchain { inherit llvm-of-yore pkgs; }).go # => go 1.22.3
lib.fips.getBoringSSLSource
Fetch the BoringSSL source pinned for a given FIPS mode.
Resolves the mode’s commit and sha256 from modes
and fetches from google/boringssl; throws for an unknown mode or one with no
pinned hash (main). The commit is the FIPS-validated source for the
certificate, so it must track the certificate rather than a moving branch.
Takes fetchFromGitHub explicitly to stay pkgs-free.
Type
getBoringSSLSource :: { mode :: String; fetchFromGitHub :: ...; } -> Derivation
Example
getBoringSSLSource { mode = "legacy"; inherit (pkgs) fetchFromGitHub; }
lib.fips.fipsAssertToolchainVersions
Assert that a toolchain matches the FIPS-required versions, failing the build on any mismatch.
Reads the version of each supplied clang, go, cmake, and ninja and compares
against toolchainVersions. On a
match it returns an empty attrset you can ignore; on a mismatch it throws with
the offending tools and the expected versions, so a non-compliant toolchain
fails loudly rather than producing a silently uncertified binary. Wire it into
a derivation to gate the build.
Type
fipsAssertToolchainVersions :: { clang :: Derivation; go :: Derivation; cmake :: Derivation; ninja :: Derivation; } -> AttrSet
Example
fipsAssertToolchainVersions { inherit clang go cmake ninja; } # => { } or throws