Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

docs/v1/SLSA.md

SLSA: Holger's release provenance

Holger's crates.io releases are built by dwarves, the build server, in its release-build mode (dwarves release-build, dwarves crates/dwarves-slsa) and carry SLSA v1.0 provenance at Build L2 (the isolation of L3 is built, its unforgeability is not: see "Why this is Build L2, not L3" at the end). Each release has ONE signed statement that names every artifact of the release by its sha256.

Level SLSA v1.0 Build L2 (see "Why this is Build L2, not L3")
Builder id https://git.gunnar.rs/nordisk/dwarves/slsa/builders/dwarves-server/v1 (dwarves' one builder id: its Build Thing provenance names it too, and runDetails.builder.version says which runner and account; the release key is what vouches for a release)
Build type https://git.gunnar.rs/nordisk/dwarves/slsa/buildtypes/cargo-release/v1
Format in-toto Statement v1, predicate SLSA Provenance v1, in a DSSE envelope (.intoto.jsonl, one line)
Signature ECDSA P-256 (SHA-256) over the DSSE PAE, made on the owner's YubiKey (PIV slot 9c, PIN once, touch always); the key was generated on the device and never leaves it
Release key slsa/release-signing-key.pub, key id d24d3785e8281bcd37e8f2c40a3a85e8152d3ce2200a5c929a1a5ca636e8249e (sha256 of its SubjectPublicKeyInfo DER); the YubiKey's attestation of the key is beside it (slsa/README.md)
Attestations attestations/<release>.intoto.jsonl

How a release is built

A release build is a Runa pipeline (release-build), run by the dwarves forge's own step runner, the one its CI pipelines run on: the same container engine, the same caps (the run directory under 40 GB, the disk over 110 GB free) and the same cancel. Its run is in the forge's history like any other run (dwarves history, the dwarves UI's Pipeline tab).

  1. Sources (the forge, slsa.checkout): every repository the release reaches is checked out by its full commit id (git-lfs objects checked with git lfs fsck --objects) into a fresh run directory and made read-only. The commits are the release spec, which is the provenance's externalParameters.
  2. Fetch (a sealed container, network on, sources read-only): cargo vendor --locked of every Cargo.lock of the release. Only cargo runs there; no build script or proc macro of the release. The forge then checks every vendored crate against the lock's checksum.
  3. Build (one fresh sealed ci4 container by digest per step, never pulled, --network=none, read-only root filesystem, all capabilities dropped, no-new-privileges, no $HOME, no key, no sccache, no shared registry, no shared target, no shell: one cargo command each): cargo package --locked --offline --no-verify per crate in dependency order; the forge stages each packaged crate for the next. Binaries, when a release has them, are built from the packaged crates with cargo build --release --locked --offline. Outputs go to one out/ directory. Nothing the build writes outlives the run, and no other build sees it.
  4. Provenance, written by dwarves OUTSIDE every container, from the Runa run's record: subject = the sha256 of every file in out/; resolvedDependencies = every repository at its commit, the build image digest, every Cargo.lock's sha256 and every locked crate with its checksum; internalParameters.runa = the pipeline, the run id and every step's result; runDetails = the builder id, the runner's own sha256, the account it ran as (relbuild) and dwarves' own version and commit, the invocation id, start and finish; the pipeline as it ran and each step's log are byproducts by sha256.
  5. Signature: the owner signs the statement on the YubiKey on his own machine (script-sign-release.sh), after reading what it names. The signed bundle comes back through the dwarves server (dwarves release-build attach <run-id> <bundle>, or the upload on the run's page in the dwarves UI), which verifies it against the release key and the run's statement before it stores it; the release runner verifies it again and marks the run signed.
  6. Publish: each crate is packaged again from the same read-only sources in the same image; it is uploaded only if its sha256 is the attested subject's (cargo publish --no-verify --locked, so no build script runs beside the token), and the crates.io index's checksum must then be the subject's too.

Per release, from the command line (or the "Request release" form on the dwarves UI's Pipeline tab, release-build; both call the same server API):

dwarves release-build request --name <release> --steps-from publish-<release>.sh --pins <pins> --lock <repo>/Cargo.toml
dwarves history                               # the run and its state: queued, building, waiting for signature, signed, failed
dwarves release-build show <run-id>           # the statement, every artifact and its sha256, the signing command
dwarves release-build attach <run-id> release.intoto.jsonl

How to verify a release

You need the release key (slsa/release-signing-key.pub), the bundle (attestations/holger-<version>.intoto.jsonl) and the artifact.

curl -fsSLO https://static.crates.io/crates/holger/holger-2.0.1.crate

# cosign (2.x or 3.x). --insecure-ignore-tlog: the releases are key-signed and not in
# Rekor; the committed release key is the trust anchor.
cosign verify-blob-attestation --key slsa/release-signing-key.pub \
  --signature attestations/holger-2.0.1.intoto.jsonl \
  --type https://slsa.dev/provenance/v1 --insecure-ignore-tlog=true holger-2.0.1.crate

# or dwarves (checks the builder and build type too)
dwarves release-build verify --key slsa/release-signing-key.pub \
  --bundle attestations/holger-2.0.1.intoto.jsonl --artifact holger-2.0.1.crate

# what was built, from which commits
sed 's/.*"payload":"\([^"]*\)".*/\1/' attestations/holger-2.0.1.intoto.jsonl | base64 -d \
  | jq '.predicate.buildDefinition.externalParameters.repositories, .predicate.runDetails'

The crates.io index's cksum of each version is the sha256 of the .crate it serves, so curl https://index.crates.io/ho/lg/holger shows the same digests as the subjects.

With Holger itself

Holger's provenance scanner verifies these bundles offline (provenance/tests/slsa_dwarf_release.rs). Pin the release key on the floor and require the dwarves builder at level 3 on the source that brings the crates in:

rules: (
    require_provenance: true,
    keys: [(id: "d24d3785e8281bcd37e8f2c40a3a85e8152d3ce2200a5c929a1a5ca636e8249e", alg: ecdsa_p256, public_key: "<slsa/release-signing-key.pub, PEM>")],
),
// on the source:
gates: (provenance: require(
    builders: [(id: "https://git.gunnar.rs/nordisk/dwarves/slsa/builders/dwarves-server/v1", level: 2)],
    keys: ["d24d3785e8281bcd37e8f2c40a3a85e8152d3ce2200a5c929a1a5ca636e8249e"],
    slsa_min: 2,
)),

A builder named without a level counts as SLSA build level 2. Until the gaps at the end close, name this builder at level 2 (slsa_min: 2); slsa_min: 3 refuses Holger's own releases.

The runner's own account

The release runner does not run as the user that the people and agents who write the code use. It runs as relbuild, its own account on dwarf's host:

Every statement names the account and the binary in runDetails.builder.version: runnerAccount (relbuild), runnerUid, releaseMode (dedicated-account), runnerBinary, runnerSha256 and requestSha256.

jq -r .payload attestations/holger-2.0.1.intoto.jsonl | base64 -d \
  | jq '.predicate.runDetails.builder.version | {runnerAccount, releaseMode, runnerSha256}'

How it is set up: dwarves crates/dwarves-slsa, "Release mode", and scripts/relbuild-user.sh.

Why this is Build L2, not L3

Measured against SLSA v1.0's Build track (lane HOL-NCSC, 2026-10-31):

A verifier's policy should therefore ask for level 2 of this builder (builders: [(id: "https://git.gunnar.rs/nordisk/dwarves/slsa/builders/dwarves-server/v1", level: 2)]) until the gaps above close.