security: the Helm chart is cosign-signed but the images it pulls are not, and nothing ships an SBOM

Sourced from reviewing TruePPM's release hardening (~/repos/trueppm-suite/trueppm), re-checked against Visiban's .gitlab-ci.yml.

An inconsistent trust boundary

helm-publish signs the chart, keyless via Sigstore, so an operator can cosign verify the chart they pull. The job comment says so explicitly: "so an operator can cosign verify the chart they pull rather than trusting the registry."

But backend-docker-push, frontend-docker-push, ghcr-push-backend and ghcr-push-frontend sign nothing. grep -riE "sbom|cosign|attestation|provenance|syft" .gitlab-ci.yml matches only inside the helm-publish block.

So the chart is verifiable and the images that chart pulls are not — the artifact carrying almost none of the attack surface is signed, and the two carrying nearly all of it are not. An operator who follows the chart's own verification advice ends up with a verified pointer to unverified images.

Why this is worth doing here specifically

  • Visiban publishes to two registries (GitLab + ghcr.io/visiban/visiban) and mirrors to GitHub. More publication surface, more places a tag can be replaced.
  • The cosign toolchain is already wired: helm-publish installs a SHA-pinned cosign 2.4.3 and authenticates keyless with SIGSTORE_ID_TOKEN. This is extending an existing, working mechanism to two more artifacts, not introducing one.
  • #1074 (closed) is making released images verifiable as present. Signing makes them verifiable as ours. The two are complementary and neither substitutes for the other.

Scope

  • Generate a CycloneDX SBOM per image with syft at publish time, naming it so metadata.component.name is the image rather than a temporary build path (--source-name / --source-version).
  • cosign attest --type cyclonedx the SBOM to the image digest, not to a tag — a tag can be moved, a digest cannot.
  • cosign verify-attestation in the same job, and assert the SBOM names its own architecture. An attestation step that silently no-ops is the failure mode worth designing against.
  • Document the verification commands in docs/administration/, next to wherever the chart verification lands.

Watch out

  • Multi-arch. #1084 (closed) is restoring linux/arm64, so each release has a manifest list with per-arch digests. Attesting only the index, or only amd64, produces a green job and a half-covered release. Attest each platform digest.
  • Keyless identity is the job. The certificate identity is whatever CI job runs the attest step, so this must never run from a workstation — TruePPM's equivalent script refuses to execute outside CI for exactly that reason.
  • Backfill. TruePPM has scripts/backfill-sbom-attestation.sh for images already published without attestations, run from a manual CI job. Worth porting if 1.2 ships before this lands, so already-released images can be brought up to the same bar without a re-cut.

Out of scope

Signing the source or adding SLSA provenance beyond the SBOM attestation. Those are a larger conversation; this issue is about closing the gap between a signed chart and unsigned images.