7. Build Thing
cargo install builds one product. The Build Thing builds everything Holger ships, including what needs signing, media or the ONE box: the estate's boxes, the front, the signed wasm plugins and the installer media. It runs as cargo xtask build in a clone, and as dwarves buildthing on the dwarves forge, which also signs.
Where the rows are declared
- The rows are Rust:
holger-recipe/src/variants.rs,rows()and the front, downstream and narrate rows, all throughvariants(). holger-build-thingis a cdylib with no logic of its own: it re-exportsholger_recipeand the wasm ABI the forge loads.holger-build-thing emitwrites.nornir/build-variants.toml; never edit that file by hand.- A row names its package, bin, features, target and kind (cargo, container, installer, ISO, keep medium, wasm guest), and whether the forge signs it.
The MVP suite
The MVP suite is Holger's one product list: the four products and what only an estate needs (.nornir/SUITES.md).
| part | rows on main |
| the four products | holger (the server's default), holger-downstream, holger-cli (the client's default), holger-ui (built from the holger-ui checkout beside) |
| the server, as the estate runs it | mvp-server-musl (bin holger-mvp, the static appliance payload) |
| metered (sold through the ONE box) | mvp-server-metered, mvp-server-musl-metered, mvp-server-musl-metered-narrate (a DEV boot log) |
| the estate front | estate-front-musl-metered (crate holger-estate-front) and its -narrate row |
| the three signed wasm plugins | plugin-allocation (holger-plugin-allocation-wasm), plugin-skidbladnir (holger-handler-skidbladnir-wasm), plugin-rust-toolchain (holger-handler-rust-toolchain-wasm) |
| installer media | holger-installer, holger-installer-metered, holger-front-installer-metered, holger-appliance-iso, holger-mvp-appliance (container), holger-keep, holger-front-keep |
| the downstream's media | downstream-musl (one-way, on holger-downstream-oneway-installer), downstream-fetch-musl (bin holger-downstream-fetch, on holger-downstream-installer) |
A medium never carries a scan worker staged from another tree: the forge door builds this tree's worker and stages it again ([sandbox-stage-stale]).
There are no other suites: ORIGINAL and OPEN were folded into this list on 2026-10-30 (.nornir/SUITES.md "History").
The one-way downstream appliance
The one-way downstream boots as an OS-less appliance without pairing (server/cli/src/pid1.rs, holger-downstream --features pid1). Its PID 1:
- reaps processes;
- brings the network up from the medium:
/run/korp/release/holger-net, thenHOLGER_NET, theneth0:dhcp(static or DHCP on the declared NIC, no resolver probe); - dials nothing;
- copies the top-level
*.holger.znippyfiles from a USB stick labelledHOLGERINBOX(FAT or ext4, mounted read-only,nosuid,nodev,noexec; hot-plug works) intobundle.inbox; - stops cleanly on the power button or SIGTERM.
Keys and trust come only from the medium, never from the stick.
- The rows.
downstream-muslisholger-downstreamwithpid1, on the mediumholger-downstream-oneway-installer. Its parameters live on the release:holger_ron,holger_net(optional),trust_policy,upstream_rootandupstream_signer(the attestation roots and signers may be PEM).downstream-fetch-muslis the fetching one (fetch,downstream-wg,paired; binholger-downstream-fetch), onholger-downstream-installer. - Its RON says
boundary: OneWay,domain:,trust: (first_import: Admin)andrules: (sandbox: required)with a crun sandbox block. Every bundle is verified in a crun job, so both downstream media carry the sandbox and the Sigstore trust root; server-lib featuresandbox-medialays them at boot. - The proof in a VM:
cargo xtask downstream-oneway-proof --fixture --work --door [--iso] [--compose-only] [--net] [--mem-mb]. It covers the install, the door, the admin's first-import grant, a stick hot-plug, the import, a pip download byte for byte,X-Holger-Domainand the clean stop.
The command
cargo xtask build --suite MVP --list # every row and its exact build line
cargo xtask build --suite MVP --variant mvp-server-musl # build one row
--list prints delivery 'MVP' — N variant(s):, then per row its name, role, kind and bin, and under it the exact cargo command, or (not a cargo build — built by the <kind> builder). A row that is not a cargo build is skipped by build, with the verb to use instead (for example cargo xtask installer-iso --variant <name> --kernel <bzImage>).
The full usage (the shared parser, nornir-build-thing suite_cli):
cargo xtask build [--suite <name>] [--variant <name>] [--list] [--attest] [--param k=v]...
[--opt name=on|off]... [--build <path|repo/variant>]...
[--features a,b] [--no-default-features] [--all-features]
--features with no --variant (for example cargo xtask build --suite MVP --features full) lists and builds the four products with those features. With a --variant, --attest and --opt, a row is built by its name with its own features.
A row is its cargo install line
A row adds the pinned container, remapped paths and a signature, never a feature (holger-recipe/src/twins.rs). On main the rule holds for the four products (twins::PRODUCTS): holger, holger-downstream, holger-ui and holger-cli, each held to one cargo install --locked --path … line.
holger-recipe/tests/rows_equal_cargo_install.rschecks the command lines.cargo xtask twin [--row <row>]builds a row and its line in the pinned toolchain image and compares the two binaries byte for byte.
The signed wasm plugins
- A row with
sign: trueis signed by the forge: it writes<stem>.wasm.p7s(a detached CMS signature) and<stem>.wasm.blake3beside the module. cargo xtask build-wasm-pluginsbuilds the modules in the toolchain container but does not sign. The signed module comes fromdwarves buildthing ./holger --suite MVP --variant <row>.- Before compiling a module, Holger checks the
.p7sover the module's BLAKE3-256, chained to the DER roots inron_plugin_trust_roots(plugin/host/src/verify.rs,znippy_common::sign). An environment variable is not a trust root: settingHOLGER_PLUGIN_TRUST_ROOTis refused (holger-server-13). - Refusals:
holger-plugin-6(unsigned),holger-plugin-7(bad or untrusted signature),holger-plugin-8(roots unreadable),holger-plugin-9(built withoutverify-modules),holger-plugin-13(a meter-guest build with no root). - Only the metered rows load wasm guests (
wasm-plugins,meter-guests). The open rows link the allow-meter natively and have no wasmtime.
How a release is built
Holger's crates.io releases are built by dwarves, the build server, and carry SLSA v1.0 provenance at Build L2: the isolation of L3 is built, its unforgeability is not (below). Each release has one signed statement that names every artifact by its sha256.
| pipeline | the Runa pipeline release-build on the dwarves forge's own step runner, run as the account relbuild |
| builder id | https://git.gunnar.rs/nordisk/dwarves/slsa/builders/dwarves-server/v1 |
| 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 over the DSSE PAE, on the owner's YubiKey (PIV slot 9c, PIN once, touch always); the key was made on the device and never leaves it |
| release key | slsa/release-signing-key.pub, key id d24d3785e8281bcd37e8f2c40a3a85e8152d3ce2200a5c929a1a5ca636e8249e (the sha256 of its SubjectPublicKeyInfo DER); the YubiKey's attestation of the key is beside it |
| attestations | attestations/<release>.intoto.jsonl |
The steps:
- Sources. Every repository the release reaches is checked out by its full commit id into a fresh run directory and made read-only. The commits are the release spec (the provenance's
externalParameters). - Fetch. In a sealed container with the network on and the sources read-only:
cargo vendor --lockedof everyCargo.lock. The forge checks every vendored crate against the lock's checksum. - Build. One fresh sealed ci4 container per step, by digest, never pulled:
--network=none, a read-only root, all capabilities dropped,no-new-privileges, no$HOME, no key, no shared cache, no shell; onecargocommand each.cargo package --locked --offline --no-verifyper crate in dependency order; binaries withcargo build --release --locked --offline. Outputs go toout/. - Provenance. dwarves writes the statement outside every container, from the run's record: the sha256 of every file in
out/, every repository at its commit, the image digest, every lock and crate checksum, the pipeline and its steps, and inrunDetails.builder.versionthe runner's account (relbuild), uid, binary and sha256. - Signature. The owner signs the statement on the YubiKey on his own machine, after reading what it names, and sends the bundle back with
dwarves release-build attach <run-id> <bundle>. The server verifies it against the release key and the run's statement before it stores it. - Publish. Each crate is packaged again from the same sources in the same image and uploaded only if its sha256 is the attested subject's; the crates.io index's checksum must then match too.
dwarves release-build request --name <release> --steps-from publish-<release>.sh --pins <pins> --lock <repo>/Cargo.toml
dwarves history # queued, building, waiting for signature, signed, failed
dwarves release-build show <run-id> # the statement, every artifact and its sha256
dwarves release-build attach <run-id> release.intoto.jsonl
The media, the app archives and the binaries
dwarves' run attests the crates it packs. A release ships more, and one more statement names all of it by sha256 (lane HOL-NCSC): every medium the forge composes (.iso-out/<variant>.iso: the four estate media, the installers, the appliance ISO), each app medium's app archive (korp's live app <variant>.live-app.znippy and its record <variant>.live.toml, what a box takes into its live slot) and every product binary (<out>/bin/<row>/<bin>):
cargo xtask release-attest --release holger-<version> --out <dwarves out/> # UNSIGNED, into target/release-attest/holger-<version>/
cargo xtask release-attest --check <statement.json> # before signing any statement: every shipped name is a subject
An artifact the release ships that is not there is refused by name ([release-attest-missing]); a statement that leaves one out is refused at the check ([release-attest-incomplete]). The rows not in the statement (the appliance container image, the wasm guests inside the app archives) are named with why in xtask/src/release_attest.rs (NOT_IN_STATEMENT); a row the declaration gains is attested or named, or cargo xtask test is red. The release manager signs this statement with the release key like dwarves' one, and it lands beside it in attestations/<release>-media.intoto.jsonl.
How to verify a release
You need the release key, the bundle and the artifact.
curl -fsSLO https://static.crates.io/crates/holger/holger-2.0.1.crate
# dwarves (checks the builder and the 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
# or cosign 2.x / 3.x (the releases are key-signed and not in Rekor)
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
# the key id
openssl pkey -pubin -in slsa/release-signing-key.pub -outform DER | sha256sum
slsa/README.md has the same check with plain openssl. Holger can require these releases on the way in: see chapter 8.
Why it is Build L2, not L3
Measured against SLSA v1.0's Build track (lane HOL-NCSC, 2026-10-31):
-
Build L1 (provenance exists): yes, from the next release on. Holger 2.0.0 was published before the runner existed and has no provenance at all, so no released Holger is attested yet.
-
Build L2 (a hosted build platform, authentic provenance): yes. The platform is the dwarves forge's release runner under its own account (
relbuild); it writes the statement from the run's record, outside every build container, and the statement is signed. The signature is the release key's: it says the release manager read the statement and vouches for it, which is how a verifier knows it was not altered. -
Build L3 (hardened builds): the isolation half is in place: every step runs in a fresh sealed container (no network, read-only root, no capabilities, no key, no shared cache), and a lane cannot change a run directory or the runner. The unforgeability half is not:
- the statement is signed by the release manager's key, not by a key only the build platform's control plane holds. The party who asks for a release could sign a statement the platform did not write. L3 wants a platform-held signing key that user-defined steps and requesters cannot reach (for example a key on the release runner's own HSM, used only by the runner after the last step, with the owner's YubiKey signature on top);
- the runner shares its host (dwarf) with the lanes' account; the separation is a Unix account, a root-owned binary pinned by sha256 and a write-protected run directory, not a separate machine or a hardened control plane;
- dwarves and the build image have no provenance of their own (dwarves' sha256 and commit are in every statement,
runDetails.builder.version; the ci4 image is pinned by digest); - there is no transparency log: bundles are key-signed and verified offline, and the release key's trust comes from this repository.
-
Crates a release does not rebuild are staged from crates.io by their index checksum; they are dependencies, not attested outputs.
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.
Moved from v1
- v1/SLSA.md, all of it except "The runner's own account" in full.
- "What is built through the Build Thing" of v1/HOLGER-V2.md.
- "The open rows" of v1/README-v1.md.
Still in v1
- v1/SLSA.md: the runner's account in detail.
.nornir/build-thing-design.md,.nornir/holger-tiers.md: the design of the rows and tiers..nornir/SUITES.md: the suites (stale: it still describes two suites).