Skip to content

ci(supply-chain): sign runtime manifest, validate scan refs, asset SBOMs + train provenance gate (FIX-SUPPLY) - #135

Merged
vanlongme merged 6 commits into
mainfrom
v10/fix-supply
Oct 6, 2026
Merged

vanlongme merged 6 commits into
mainfrom
v10/fix-supply

Conversation

@vanlongme

Copy link
Copy Markdown
Contributor

Summary

Supply-chain hardening on the three publishing workflows, closing the confirmed review findings on the release/train path:

  • Unsigned runtime manifest — runtime-images.json (the deployment contract deployments pin digests from) was the only unsigned artifact in the release chain. The train's publish job now cosign sign-blobs it and attaches runtime-images.json.sigstore.json next to it on every runtime-* release. The exact cosign verify-blob command (pinned certificate-identity runtime-images.yml@refs/heads/main + issuer) is documented where the manifest is consumed: .github/README.md, docs/images.md, docs/runbooks/upgrade.md.
  • Unvalidated SCAN_REF env writes (SUPF-10) — the scan jobs in images.yml, release.yml and runtime-images.yml wrote artifact-supplied ref content into $GITHUB_ENV unchecked; a newline in a forged ref would inject arbitrary env vars. The ref is now validated against the strict ghcr.io/tinyorbitvn/tinycdi-<img>@sha256:<64hex> form before the env write; anything else fails the job.
  • Train build provenance — the train publish job gains the same actions/attest-build-provenance legs release.yml already ships, gated on a dedicated TRAIN_ATTESTATIONS_ENABLED repo variable (defaults OFF — deliberately not set by setup-repo-protection.sh; set it only once verified on a tag build). attestations: write + id-token: write are scoped to that one job; the existing cosign sign/attest steps are unchanged.
  • Missing asset SBOMs (SEC-17) — the packaged Helm chart and the static release binaries now get dedicated SPDX SBOMs (sbom-chart.spdx.json from the packaged tgz contents — per-file inventory with sha256 — and sbom-binaries.spdx.json from the Go binaries' embedded module lists), produced by the jobs that build them, validated by collect-publish-inputs.sh, and carried through bundle/ so checksums + cosign sign-blob + the release cover them like every other asset.
  • Docs — docs/security/provenance.md now 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.md got the per-item status lines.

No workflow triggers, events, concurrency or the release environment gate were touched; every added action is pinned by full SHA (reusing the existing attest-build-provenance@4d10147… v4.2.2 and upload-artifact pins).

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.sh additionally gained poisoned/missing SBOM cases for the two new artifacts.

How each change was tested without cutting a release

  • SCAN_REF guard: the regex is unit-tested in supply-chain-hardening.test.sh (runs in workflow-policy on every PR) — valid ref accepted; newline injection, foreign registry, wrong image, short/uppercase digest, tag-form and local all rejected. Step ordering (guard before env write, fail-closed) is asserted per workflow.
  • Chart/binaries SBOMs: the exact step commands were run locally with the pinned syft 1.52.0 binary (sha256-verified download): dir: on the extracted packaged chart → valid SPDX-2.3 with a 54-file sha256 inventory; dir:dist on a freshly built tinycdi-backend binary → valid SPDX-2.3 with 69 Go-module packages. Both jobs run in the workflow_dispatch dry-run path, so the next rehearsal exercises them end-to-end.
  • Manifest sign-blob + train provenance: structural mirrors of the release.yml steps 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 on main — the guard steps fail closed and the provenance legs are inert while TRAIN_ATTESTATIONS_ENABLED is unset.
  • collect-publish-inputs.sh changes are covered by the extended publish-inputs.test.sh happy-path + two new fail cases.

Diffstat vs origin/main

 .github/README.md                            |  19 ++-
 .github/scripts/collect-publish-inputs.sh    |   6 +-
 .github/tests/publish-inputs.test.sh         |  16 ++-
 .github/tests/supply-chain-hardening.test.sh | 179 +++++++++++++++++++++++++++
 .github/workflows/images.yml                 |   8 +-
 .github/workflows/release.yml                |  68 +++++++++-
 .github/workflows/runtime-images.yml         |  85 +++++++++++--
 docs/images.md                               |  17 ++-
 docs/runbooks/upgrade.md                     |   5 +-
 docs/security/provenance.md                  |  37 +++++-
 docs/security/test-inventory.md              |  12 +-
 docs/security/threat-model.md                |  15 +++
 12 files changed, 440 insertions(+), 27 deletions(-)

v1.0 fix task (FIX-SUPPLY), requested by the project orchestrator; the advisor reviews and merges.

Generated with Devin

…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.
Copilot AI balanced review requested due to automatic review settings October 6, 2026 05:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@vanlongme
vanlongme merged commit 873e530 into main Oct 6, 2026
14 checks passed
@vanlongme
vanlongme deleted the v10/fix-supply branch October 6, 2026 06:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants