Infrastructure worth keeping — and kept patched.

How our release pipeline maps to NIST SP 800-204C, 800-204D and SLSA

This page takes the release pipeline that built FosterStack Cache v0.2.2 and puts each stage next to the NIST practice it maps to. It says where a stage only partly implements a practice, and it says which SLSA Build level we assess v0.2.2 at: Level 2, not Level 3.

This is guidance we followed when designing the pipeline. It is not a certification.

Words used here:

The two NIST documents

Parts of both documents cover running services in your own cluster, such as service mesh and GitOps. That is your environment, not our release pipeline, so this page does not map it.

Stage by stage: what runs, and which practice it maps to

This describes the workflow files as they were at the v0.2.2 tag. What changed since v0.2.1 is listed after the table. "Signs" means the stage records what it did and signs it, with a short-lived key and no stored signing key. The signature is tied to that stage's own workflow and logged in a public transparency log.

StageWhat it does in v0.2.2NIST practiceHow much
Source admission The tag must be annotated, signed and strictly named: signed by the owner's key or, for an automatic patch tag only, by the release workflow's own keyless identity. The commit must be signed (by the owner's key, or GitHub's own key for merges made on github.com) and be on main. Every required check must pass on that exact commit or, for the checks that run only on pull requests, on the pull request that produced it. An owner-approved requirements list must exist for the version. The lists of allowed signers and required checks are read from main, not from the tagged commit. Signs. 800-204D 5.1.4 "Secure Code Commits" (commit-signing enforcement, COMMIT-REQ-2); 5.1.2 "Secure Pull-Push Operations on Repositories" (automated checks before code is trusted) Partly. There is one maintainer, so no second person reviews or approves a change.
Build Builds the binaries once on a fresh GitHub-hosted machine with the toolchain pinned, and checks that the dependency files did not change. Writes checksums and an SBOM per archive. Signs a build statement and SLSA provenance. 800-204D 5.1.1 "Secure Build"; 800-204C 3.3.3 (SBOM generation and code signing as pipeline tasks) Partly. The signing runs in the same job as the build steps, not in a separate, more trusted process as 5.1.1 asks.
Image assembly Checks every archive against the signed build statement before opening it. Adds one binary to a base image pinned by digest, with no commands run inside the image, using a BuildKit image that is also pinned by digest. Pushes by digest only, with no version tags, to a separate candidates package. Signs, recording the base digest and binary hashes, plus SLSA provenance. 800-204D 4.2: "The inputs and outputs of each build step should be verified" Matches the practice as we read it.
Reproducibility check Assembles the images again on a separate machine. The digests must match, or the release fails. Signs. No direct match in either document. Not mapped. It repeats the image assembly; it does not rebuild the binaries from source.
Scans Two scanners with different vulnerability databases run, each on its own machine. They first check the image-assembly and reproducibility statements, then scan every platform image of every variant. One also scans the archives, the other the Go module files. Any finding blocks the release, even the lowest-rated one. For one of the two scanners, a published VEX statement can clear a finding. Each scanner signs its verdict for the exact digests. 800-204C 4.8 and 4.8.1 (container image scans, software composition analysis); 800-204D 5.2, DEPLOY_REQ-4 (evidence the image was scanned) Partly. The release pipeline runs no static or dynamic application security testing itself. The signed verdict records a pass, not the full scan report.
Acceptance tests Run against the exact candidate images: the Linux archives and binaries (macOS archives are checked, not run), the images on both CPU architectures, the image variants, the Gradle and Maven test suites, the Kubernetes setup from our docs, and a test that the server makes no outbound connections. A final job checks that every release-blocking acceptance test in the approved list passed, then signs one statement. 800-204C 3.3.1 (tests run in the pipeline; an organisation may choose to stop the pipeline when a test fails); 4.6 (stop further pipeline tasks if a test fails) Matches the practice as we read it. The individual test jobs do not sign their own results.
Release authorization Verifies every earlier statement: signed by the expected stage, naming this commit and these digests, scan verdicts pass, acceptance results complete. Anything missing stops the release. It also stops the release if a VEX statement says a flaw affects us and no owner acceptance of that risk is recorded. Records each statement's transparency-log entry. Signs the approval. 800-204C 4.6: "verify that all required signatures … are present to ensure that no one bypasses the pipeline"; 800-204D 5.1.1 (policy checks that the required attestations are found) Partly. The policy is code in the workflow file, not a separate signed policy document as 5.1.1 describes.
Promotion and publish Will not start without the signed approval. Copies the approved digests to GitHub's registry and the Docker Hub mirror with no rebuild, and checks the digests did not change. Signs the public images and the checksums file. Publishes the VEX statements in three forms: OpenVEX, scanner suppression rules and CSAF 2.0. Uploads the release files, re-checks the image signatures, the signed checksums and every tag the way a customer would, without credentials, then makes the release page public. 800-204D 5.2, DEPLOY-REQ-3 (only an artifact made by the approved build process is cleared) Partly. Promotion only copies the digests named in the signed approval. Enforcing that at deploy time is up to your own environment.
Signing, across all stages Keyless signing: a short-lived signing identity per stage, no stored signing key, every signature logged publicly. 800-204C 4.6: "signing the release artifact during each required CI/CD stage (preferably multi-party signing)"; 800-204D 5.1.1 (attestations signed, stored tamper-proof) Partly. Not multi-party: every signature is made by automation in one repository, run by one maintainer.
Evidence bundle release-manifest.json lists every digest, the hash of the approved requirements list, per-test results, and the transparency-log entry of every statement the authorization verified. It is attached to each image in GitHub's registry as a signed statement and published with the SBOMs, VEX file and signed checksums. 800-204D 4 (record provenance, with a way to verify and authenticate it) Matches the practice as we read it. Neither document has a section on VEX.

What changed from v0.2.1 to v0.2.2

The v0.2.2 tag changed 31 workflow files. The build stage and the scan stage did not change. These did:

Which SLSA Build level do we assess v0.2.2 at?

We assess v0.2.2 at SLSA Build Level 2, against the SLSA v1.0 requirements and levels. We do not claim Build Level 3.

Level 3 is not claimed. It asks the build platform to "prevent runs from influencing one another, even within the same project" and to "prevent secret material used to sign the provenance from being accessible to the user-defined build steps". We have not shown either. In v0.2.2 the build job can ask for a signing identity for the whole job, and it also runs this repository's build configuration and compiles its code: the goreleaser hooks and the Go build. The image job can ask for a signing identity too, and it builds this repository's Dockerfiles. So the build steps could reach the signing, and Level 3 needs that kept apart. The build job also restored a GitHub Actions cache written by earlier runs, and we have not shown that other workflows or branches cannot write to it. The spec calls that "cache poisoning" under its "Isolated" requirement. Build Level 3 is not claimed for v0.2.2.

What we do not meet

Check it yourself

This checks the SLSA provenance of the v0.2.2 image and the workflow that signed it:

$ gh attestation verify oci://ghcr.io/fosterstack/cache:0.2.2 \
  --repo fosterstack/cache \
  --signer-workflow fosterstack/cache/.github/workflows/stage-image.yml

Section numbers refer to the final published versions of NIST SP 800-204C and NIST SP 800-204D. This is guidance we followed when designing the pipeline. It is not a certification.

Verify a release How we build