ci(supply-chain): sign runtime manifest, validate scan refs, asset SBOMs + train provenance gate (FIX-SUPPLY) - #135
Merged
Merged
Conversation
…UPPLY) The scan jobs in all three publishing workflows wrote the artifact-supplied image ref into $GITHUB_ENV unvalidated; a newline in a forged ref file could inject arbitrary env vars into the job. The ref is now checked against ghcr.io/tinyorbitvn/tinycdi-<img>@sha256:<64hex> first — anything else fails the job (SUPF-10).
…PPLY) runtime-images.json is the deployment contract consumers pin digests from and was the only unsigned artifact in the chain: the publish job now cosign sign-blobs it and attaches the Sigstore bundle next to it on the runtime-* release (SEC-17). The train publish also gains the same var-gated attest-build-provenance legs release.yml already has, behind the dedicated TRAIN_ATTESTATIONS_ENABLED repo variable (off by default) with attestations:write scoped to the publish job only (SEC-I12).
The packaged Helm chart and the static release binaries shipped signed but without SBOMs. The chart and binaries jobs now emit sbom-chart.spdx.json (per-file inventory of the packaged tgz) and sbom-binaries.spdx.json (go-binary module list) with the pinned, sha256-verified syft; collect-publish-inputs.sh requires and validates both artifacts, and they join bundle/ so checksums, sign-blob and the GitHub Release cover them like every other asset.
…X-SUPPLY) supply-chain-hardening.test.sh pins the new controls: the strict repo@sha256 guard before every SCAN_REF env write (plus a unit test of the regex against injection/malformed refs), manifest sign-blob wiring and ordering, the TRAIN_ATTESTATIONS_ENABLED-gated provenance legs, the sbom-chart/sbom-binaries pipeline plumbing, and the digest-addressability documentation anchor. publish-inputs.test.sh gains the two new artifact folders and poisoned-SBOM cases.
…s (FIX-SUPPLY) Document the cosign verify-blob command for runtime-images.json where the manifest is consumed (.github/README.md, docs/images.md, docs/runbooks/upgrade.md), the TRAIN_ATTESTATIONS_ENABLED variable, the dedicated chart/binaries SBOMs, and why gate-failed digest-only pushes staying pullable-by-digest is an accepted residual (never tagged, never signed, verify-by-signature) in provenance.md + the threat-model supply chain bullets.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Supply-chain hardening on the three publishing workflows, closing the confirmed review findings on the release/train path:
runtime-images.json(the deployment contract deployments pin digests from) was the only unsigned artifact in the release chain. The train'spublishjob nowcosign sign-blobs it and attachesruntime-images.json.sigstore.jsonnext to it on everyruntime-*release. The exactcosign verify-blobcommand (pinned certificate-identityruntime-images.yml@refs/heads/main+ issuer) is documented where the manifest is consumed:.github/README.md,docs/images.md,docs/runbooks/upgrade.md.SCAN_REFenv writes (SUPF-10) — the scan jobs inimages.yml,release.ymlandruntime-images.ymlwrote artifact-supplied ref content into$GITHUB_ENVunchecked; a newline in a forged ref would inject arbitrary env vars. The ref is now validated against the strictghcr.io/tinyorbitvn/tinycdi-<img>@sha256:<64hex>form before the env write; anything else fails the job.actions/attest-build-provenancelegsrelease.ymlalready ships, gated on a dedicatedTRAIN_ATTESTATIONS_ENABLEDrepo variable (defaults OFF — deliberately not set bysetup-repo-protection.sh; set it only once verified on a tag build).attestations: write+id-token: writeare scoped to that one job; the existing cosign sign/attest steps are unchanged.sbom-chart.spdx.jsonfrom the packaged tgz contents — per-file inventory with sha256 — andsbom-binaries.spdx.jsonfrom the Go binaries' embedded module lists), produced by the jobs that build them, validated bycollect-publish-inputs.sh, and carried throughbundle/so checksums +cosign sign-blob+ the release cover them like every other asset.docs/security/provenance.mdnow states why gate-failed digest-only pushes staying pullable-by-digest is an accepted residual (never tagged, never signed, verify-by-signature);docs/security/threat-model.mdgot the per-item status lines.No workflow triggers, events, concurrency or the
releaseenvironment gate were touched; every added action is pinned by full SHA (reusing the existingattest-build-provenance@4d10147…v4.2.2 andupload-artifactpins).Regression test
bash .github/tests/supply-chain-hardening.test.sh— fails on v0.5.0 (37 FAILs on the unmodified tree: no ref guard, no sign-blob step, no train provenance legs, no asset SBOM plumbing, no docs anchors) and passes on this branch.publish-inputs.test.shadditionally gained poisoned/missing SBOM cases for the two new artifacts.How each change was tested without cutting a release
supply-chain-hardening.test.sh(runs inworkflow-policyon every PR) — valid ref accepted; newline injection, foreign registry, wrong image, short/uppercase digest, tag-form andlocalall rejected. Step ordering (guard before env write, fail-closed) is asserted per workflow.dir:on the extracted packaged chart → valid SPDX-2.3 with a 54-file sha256 inventory;dir:diston a freshly builttinycdi-backendbinary → valid SPDX-2.3 with 69 Go-module packages. Both jobs run in theworkflow_dispatchdry-run path, so the next rehearsal exercises them end-to-end.release.ymlsteps that produced real signatures and SLSA attestations on v0.5.0; asserted by the meta-guard (presence, gating, ordering before tag promotion) + actionlint. The publish path itself can only run onmain— the guard steps fail closed and the provenance legs are inert whileTRAIN_ATTESTATIONS_ENABLEDis unset.collect-publish-inputs.shchanges are covered by the extendedpublish-inputs.test.shhappy-path + two new fail cases.Diffstat vs origin/main
v1.0 fix task (FIX-SUPPLY), requested by the project orchestrator; the advisor reviews and merges.
Generated with Devin