fix(ci): read cosign's attestation output in the framing it writes - #1751
Conversation
The release gate read `cosign verify-attestation` as a JSON array. Cosign writes its banner to stderr and one DSSE envelope per verified attestation to stdout, unwrapped and newline-delimited, so a subject with a single SBOM attestation is a bare object and the gate rejected it as malformed. That failed v0.75.0 in `tag-images`, and since this path had never run to completion in a real release, nothing had ever seen it. Read every framing cosign might use — one document, several newline-delimited ones, or an array of them — and decode each envelope strictly instead of skipping past one that cannot be read, so a broken capture stops a release rather than being silently ignored. The predicate comparison is unchanged: `cosign attest --type spdxjson` stores the predicate file verbatim, so the deep equality against the bundle's SBOM is what it claims to be. Verified against the real registry with cosign v3.0.6, the version the release installs: verifying the published webapp subject prints exactly one 1.4 MB envelope whose predicate is the syft document in the bundle key for key, which the fixed gate accepts and a re-generated SBOM does not. The two other release-only checks in this file, the index signature and its GitHub attestation, were run against all four published indexes and pass unchanged. The `node:test` cases carry captured cosign output as their fixture. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Warning Review limit reachedNext included review available in 37 seconds. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Approved automatically: @FelixTJDietrich is listed in the REVIEW_POLICY_MAINTAINERS repository variable, which the repository treats as satisfying the review requirement. See the review policy in docs/contributor/ci-cd.mdx.
📚 Documentation Preview
|
🧩 Storybook Preview
|
What changed and why
Releaserun 33691274046 failed intag-images→ Verify evidence from registry subjects withError: Cosign attestation result is malformed, thrown because the parsedcosign verify-attestationoutput was not an array. This path had never completed in a realrelease — v0.74.0 predates the evidence bundle and every attempt since stopped earlier — so it was
unproven code rather than a regression.
Cosign writes its verification banner to stderr and its result to stdout as a stream of JSON
documents:
verify-attestationprints one DSSE envelope per verified attestation, unwrapped andnewline-delimited. A subject carrying a single SBOM attestation is therefore a bare object, which
the gate rejected. (
cosign verifyprints an array instead, which is where the assumption camefrom; that call site never parses its output.)
array of them — so a cosign upgrade that reframes its output cannot fail a release.
try/catchreturnedfalsefor an envelope it could notread, silently skipping past it; an envelope cosign has already verified cryptographically and
still cannot be decoded is a broken capture, and now stops the release with a named error.
cosign verified nothingrather than a quiet mismatch.cosign attest --type spdxjsonstores the predicate file verbatim, so the deep equality against<image>-<platform>.spdx.jsonasserts exactly what it means to.
Fixes the v0.75.0 release.
How to test
Reproduced against the real registry with cosign v3.0.6 — the version
setup-release-security-toolsinstalls, not the v2.6.1 the images predate — using the v0.75.0candidate digests from run 33691274046.
exits
0, prints the banner on stderr and one 1 476 210-byte line on stdout, whose payloaddecodes to an in-toto statement (
predicateType: https://spdx.dev/Document, subjectghcr.io/hephaestus-build/webapp@sha256:ff26dd20…). Re-runningsyft --from registry … --platform linux/amd64 --scope squashed -o spdx-jsonover the same digest reproduces that predicate key forkey, differing only in
creationInfo.createdand thedocumentNamespaceUUID — the publishedattestation carries the syft document the bundle records, verbatim.
Feeding that captured 1.4 MB stdout to the fixed code parses one document, returns
truefor thepublished predicate and
falsefor the re-generated SBOM: the gate reads the real output and stillbites.
The
node:testcases use captured cosign v3.0.6 output as their fixture — realattest --type spdxjson/verify-attestationround trips over a local registry, one attestationand two — so the framing is proven by tests rather than by a release.
pnpm run formatandpnpm run checkpass.Release impact
No changeset.
verify-changesets.ymlscopesSHIPPED_PATHStoserver,webappanddocker;this changes
scripts/only, which is repository tooling and not image contents.Notes for reviewers
verifyIndexSignaturesis the other release-only check in this file and does not share thedefect: it never parses cosign's output, only its exit code. It was run for real against all four
published indexes (
webapp,application-server,agent-pi,postgres) —cosign verifyagainstthe
reusable-docker-build.yml@refs/heads/mainidentity andgh attestation verify --signer-workflowboth exit
0, and a deliberately wrong--signer-workflowexits1. It is left unchanged.Worth knowing for the next release run: because
cosign attestalready ran against these digests,a re-run will add a second attestation to each subject and
verify-attestationwill then printtwo newline-delimited envelopes. That is precisely the framing the fallback path handles, and the
TWO_ATTESTATIONSfixture covers it.Made by Claude Fable 5 in Claude Code.