fix(ci): sign the image even when the CVE scan fails - #358
Conversation
There was a problem hiding this comment.
Correct. cosign reads nothing from trivy (only needs.build.outputs.digest), the image is pushed in build before the scan runs, and if: inputs.push carries no status function so the implicit "needs must succeed" rule still holds against build. A failing scan still fails the workflow via its own job. sbom was already needs: build, so this makes the two post-push jobs consistent.
Nit: the reasoning in the description covers consumers who receive an unsigned image, but not consumers who reject one. If anything downstream gates on the signature (a Kyverno / policy-controller rule, or a cosign verify step in a deploy job), the old needs: [build, trivy] was an accidental CVE gate for them: a failing scan meant no signature meant no admission. After this, that same image verifies and is admitted. That is probably the right call given trivy-exit-code defaults to '1' and a single unfixable upstream CVE would otherwise wedge a deploy, but it is a behaviour change at the consumer end rather than a no-op, so it is worth confirming no cluster policy is currently leaning on it. Does not block: the invariant this restores is the more defensible one, and gating the push is the proper fix as you say.
Nit: worth carrying the trivy-exit-code point from the description into an issue rather than leaving it in the PR body, since it outlives this merge and FerrTrack still does not override the default.
Addresses the durable half of FerrLabs/FerrTrack-Cloud#712.
cosignwasneeds: [build, trivy]. It reads nothing fromtrivy, so that dependency was a pure gate: a failing CVE scan skipped signing.The image is pushed in
build, beforetrivyruns. Withholding the signature therefore does not withhold the artefact. It ships either way, and a failing scan is the one case where it ships unsigned and unattested. That is backwards: the moment an image is most worth identifying is the moment we stop identifying it.A cosign signature attests provenance, not the absence of CVEs. The scan keeps its own red check and its SARIF, and
sbomwas alreadyneeds: build, so the SBOM already published on a failing scan while the signature did not.How #712 happened, and what is left
FerrTrack's
app@v2.25.0shipped unsigned on 2026-08-29 because the SARIF upload died onResource not accessible by integration, failing thetrivyjob and skippingcosign. That specific hole is already closed here:Upload SARIFnow carriescontinue-on-error: trueandif: always().What is not closed is the structural one.
trivy-exit-codedefaults to'1', so any repo that does not override it still turns a genuine CVE finding into a published-but-unsigned image. FerrTrack does not override it.The alternative, and why not now
If the intent is that a vulnerable image must not reach consumers, the fix is to gate the push, not the signature: build, scan, then push. That is a real change to this workflow's shape and to every caller's expectations, and it belongs in its own PR. This one restores the invariant that everything published is signed.
Blast radius
Org-wide, every caller of
reusable-docker-build.yml. It only ever adds signatures; no path that signs today stops signing.Not verifiable here
The change only shows on a run where
trivyfails. The proof is the next image built from a repo with a fixable HIGH or CRITICAL CVE: thetrivycheck should stay red and a.sigshould appear for the digest anyway.