fix(cli): support cosign v3 bundle format for signature verification#546
Conversation
cosign v3 (installed by cosign-installer v4.1.0) stores signatures as OCI referrer artifacts using the new bundle format (application/vnd.dev.sigstore.bundle.v0.3+json) with the bundle as raw layer content -- not as .sig tags or annotation-based bundles. CLI changes: - Match both v3 bundle artifact type and v2 simplesigning artifact type in OCI referrer discovery - Extract v3 bundles from layer content (not annotations) - Keep v2 annotation-based extraction as fallback - Add isCosignSignatureArtifact helper for clean type matching CI changes: - Remove --registry-referrers-mode=oci-1-1 and COSIGN_EXPERIMENTAL from cosign sign commands -- cosign v3 already pushes to the referrers fallback tag by default, no flags needed - Plain `cosign sign --yes` is the correct invocation for cosign v3 Closes #532
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
📜 Recent review details⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
🧰 Additional context used📓 Path-based instructions (1)cli/**/*.go📄 CodeRabbit inference engine (CLAUDE.md)
Files:
🔇 Additional comments (5)
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds OCI-referrer-based cosign signature discovery and verification, adding explicit support for cosign v3 bundles (layer) and cosign v2 bundles (annotations), updates tests, and removes COSIGN_EXPERIMENTAL / registry-referrers flag usage from CI signing steps. Changes
Sequence DiagramsequenceDiagram
participant CLI as Verifier (CLI)
participant Referrers as OCI Referrers API
participant Registry as Registry (image / manifest)
participant V3 as V3 Bundle Handler
participant V2 as V2 Bundle Handler
CLI->>Referrers: Request referrers for image digest
Referrers-->>CLI: Return referrer descriptors
loop each descriptor
CLI->>CLI: isCosignSignatureArtifact(desc)?
alt desc.type == cosignV3BundleArtifactType
CLI->>Registry: Fetch referrer image (layers)
Registry-->>CLI: Image + layer content
CLI->>V3: Read first layer (bundle) and verify against digest
V3-->>CLI: verification result
else desc.type == cosignV2ArtifactType
CLI->>Registry: Fetch manifest / annotations
Registry-->>CLI: Manifest + annotations
CLI->>V2: Extract per-source bundle from annotations and verify
V2-->>CLI: verification result(s)
else
CLI-->>CLI: skip non-cosign artifact
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
📝 Coding Plan
Comment |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses a critical issue where the CLI failed to verify image signatures generated by Cosign v3 due to changes in its signature storage format. By updating the verification logic, the CLI can now correctly identify and process signatures from both Cosign v2 and v3, ensuring robust and future-proof image integrity checks. This enhancement resolves the "no cosign signatures found" error, improving the reliability and security of synthorg start and synthorg update operations. Highlights
Ignored Files
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request correctly adds support for the cosign v3 bundle format, which is a necessary fix for compatibility with newer versions of cosign. The changes are well-implemented: new constants clearly define the v2 and v3 artifact types, and the logic is cleanly separated into verifyCosignV3Bundle and verifyCosignV2Bundle functions. The addition of the isCosignSignatureArtifact helper and its corresponding test case is a good practice. My review includes one minor suggestion to make the code slightly more concise. Overall, this is a solid improvement.
| switch desc.ArtifactType { | ||
| case cosignV3BundleArtifactType: | ||
| return true | ||
| case cosignV2ArtifactType: | ||
| return true | ||
| default: | ||
| return false | ||
| } |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
cli/internal/verify/cosign_test.go (1)
129-218: 🧹 Nitpick | 🔵 TrivialConsider adding a test for v3 bundle format verification.
TestVerifyCosignSignatureInvalidBundleonly exercises the v2 code path (annotation-based bundles). Since this PR adds v3 support where the bundle is stored as layer content, a corresponding test for v3 format would improve coverage of the newverifyCosignV3Bundlefunction.🧪 Sketch for v3 bundle test
func TestVerifyCosignSignatureV3InvalidBundle(t *testing.T) { repo := "test/image" sigDigest := "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" // v3 stores bundle as raw layer content, not in annotations invalidBundleContent := []byte(`{"invalid": "v3 bundle"}`) configJSON := `{}` sigManifest := ociManifest{ SchemaVersion: 2, MediaType: "application/vnd.oci.image.manifest.v1+json", Config: ociDescriptor{ MediaType: "application/vnd.oci.image.config.v1+json", Digest: "sha256:44136fa355b311bfa616a15e4e5e6d84e4f455ce82fb1ed83b0a7f9e2c3d4a5b", Size: len(configJSON), }, Layers: []ociLayerDescriptor{ { MediaType: "application/vnd.dev.sigstore.bundle.v0.3+json", Digest: "sha256:0000000000000000000000000000000000000000000000000000000000000002", Size: len(invalidBundleContent), }, }, } // ... setup mock server to return layer blob content ... // ... assert verification fails with appropriate error ... }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cli/internal/verify/cosign_test.go` around lines 129 - 218, Add a new unit test exercising the v3 bundle code path: create TestVerifyCosignSignatureV3InvalidBundle that mirrors TestVerifyCosignSignatureInvalidBundle but uses a layer with MediaType "application/vnd.dev.sigstore.bundle.v0.3+json" and places the invalid bundle JSON in the layer blob (not in annotations); build the ociManifest (using ociManifest and ociLayerDescriptor) and referrer index, mock the HTTP server to return the manifest and the layer blob for the v3 digest, construct an ImageRef and call VerifyCosignSignature expecting a non-nil error (assert the error message contains "cosign signature"), so verifyCosignV3Bundle is exercised and failure for invalid v3 bundle is covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@cli/internal/verify/cosign.go`:
- Around line 38-49: The isCosignSignatureArtifact helper uses a switch
returning true for two artifact types; simplify it by returning a boolean
expression that checks whether desc.ArtifactType equals
cosignV3BundleArtifactType or cosignV2ArtifactType (reference function
isCosignSignatureArtifact(desc v1.Descriptor) and constants
cosignV3BundleArtifactType, cosignV2ArtifactType) so the function becomes a
single-line boolean return instead of a switch.
- Around line 129-136: The dispatch currently assumes descriptors reaching this
block are filtered by isCosignSignatureArtifact in findCosignSignatures so it
treats any non-v3 ArtifactType as v2; add a brief defensive comment above the
dispatch clarifying that invariant and either return an explicit error if
desc.ArtifactType is neither cosignV3BundleArtifactType nor the expected v2 type
or assert the invariant (e.g., panic/log and return error) before calling
verifyCosignV3Bundle/verifyCosignV2Bundle; reference desc.ArtifactType,
cosignV3BundleArtifactType, verifyCosignV3Bundle, verifyCosignV2Bundle,
isCosignSignatureArtifact, and findCosignSignatures when making the change.
---
Outside diff comments:
In `@cli/internal/verify/cosign_test.go`:
- Around line 129-218: Add a new unit test exercising the v3 bundle code path:
create TestVerifyCosignSignatureV3InvalidBundle that mirrors
TestVerifyCosignSignatureInvalidBundle but uses a layer with MediaType
"application/vnd.dev.sigstore.bundle.v0.3+json" and places the invalid bundle
JSON in the layer blob (not in annotations); build the ociManifest (using
ociManifest and ociLayerDescriptor) and referrer index, mock the HTTP server to
return the manifest and the layer blob for the v3 digest, construct an ImageRef
and call VerifyCosignSignature expecting a non-nil error (assert the error
message contains "cosign signature"), so verifyCosignV3Bundle is exercised and
failure for invalid v3 bundle is covered.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 9dfacb9c-b8ea-46ec-a7aa-5c31ed14d485
📒 Files selected for processing (3)
.github/workflows/docker.ymlcli/internal/verify/cosign.gocli/internal/verify/cosign_test.go
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: CLI Build (darwin, arm64)
- GitHub Check: CLI Build (linux, arm64)
- GitHub Check: CLI Build (windows, amd64)
- GitHub Check: CLI Test (ubuntu-latest)
- GitHub Check: CLI Test (windows-latest)
- GitHub Check: CLI Test (macos-latest)
- GitHub Check: Build Web
- GitHub Check: Build Sandbox
- GitHub Check: Build Backend
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
cli/**/*.go
📄 CodeRabbit inference engine (CLAUDE.md)
cli/**/*.go: Go: runcd cli && golangci-lint runbefore committing CLI code
Go: runcd cli && go vet ./...to check for common Go mistakes
Files:
cli/internal/verify/cosign_test.gocli/internal/verify/cosign.go
cli/**/*_test.go
📄 CodeRabbit inference engine (CLAUDE.md)
Go: use
testing.Ffor fuzz testing; organize fuzz targets in test files withFuzzTargetnaming
Files:
cli/internal/verify/cosign_test.go
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to .github/workflows/docker.yml : CI Docker: build → scan → push to GHCR + cosign sign + SLSA L3 provenance via attest-build-provenance (images only pushed after Trivy/Grype scans pass).
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to .github/workflows/docker.yml : CI Docker: build → scan → push to GHCR + cosign sign + SLSA L3 provenance via attest-build-provenance (images only pushed after Trivy/Grype scans pass).
Applied to files:
.github/workflows/docker.yml
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Dependabot: auto-updates Docker image digests and versions daily.
Applied to files:
.github/workflows/docker.yml
🔇 Additional comments (6)
.github/workflows/docker.yml (1)
217-217: Good update: signing commands now match cosign v3 behavior.The switch to plain
cosign sign --yes <image>@${DIGEST}is correct here, and keeping the digest guard preserves safety before signing.Also applies to: 384-384, 551-551
cli/internal/verify/cosign.go (3)
17-30: LGTM! Well-documented constants for both bundle formats.The artifact type constants correctly reflect cosign's documented media types for v3 bundles and v2 simplesigning payloads.
138-162: LGTM! Proper resource handling for v3 bundle extraction.The deferred close on the reader prevents resource leaks, and the error handling is comprehensive. Reading the entire bundle into memory is acceptable since Sigstore bundles are small JSON documents.
164-197: LGTM! Clear separation of v2 bundle verification logic.The function correctly checks manifest-level annotations first, then falls back to layer annotations, with proper error aggregation when bundles exist but fail verification.
cli/internal/verify/cosign_test.go (2)
145-154: LGTM! Correct usage of v2 artifact type constant.The layer MediaType correctly uses
cosignV2ArtifactTypesince this test validates the v2 annotation-based bundle format.
220-237: LGTM! Good test coverage for the helper function.The test covers both positive cases (v3 and v2 artifact types) and negative cases (empty, unrelated types). Table-driven approach is idiomatic.
| // Try v3 bundle format first (bundle is raw layer content). | ||
| if desc.ArtifactType == cosignV3BundleArtifactType { | ||
| return verifyCosignV3Bundle(img, ref.Digest, sev, certID) | ||
| } | ||
|
|
||
| // Fall back to v2 format (bundle in annotations). | ||
| return verifyCosignV2Bundle(img, ref.Digest, sev, certID) | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Verify that the v3/v2 dispatch logic is correct given filtered descriptors.
The function assumes descriptors are pre-filtered by isCosignSignatureArtifact, so only v3 or v2 types reach here. If ArtifactType is not v3, it falls through to v2 verification. This is correct given the current filtering in findCosignSignatures, but consider adding a defensive check or comment to clarify this invariant.
🛡️ Optional: Add defensive comment
// Try v3 bundle format first (bundle is raw layer content).
if desc.ArtifactType == cosignV3BundleArtifactType {
return verifyCosignV3Bundle(img, ref.Digest, sev, certID)
}
- // Fall back to v2 format (bundle in annotations).
+ // Fall back to v2 format (bundle in annotations).
+ // Note: Only v2 or v3 artifact types reach here due to filtering in findCosignSignatures.
return verifyCosignV2Bundle(img, ref.Digest, sev, certID)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Try v3 bundle format first (bundle is raw layer content). | |
| if desc.ArtifactType == cosignV3BundleArtifactType { | |
| return verifyCosignV3Bundle(img, ref.Digest, sev, certID) | |
| } | |
| // Fall back to v2 format (bundle in annotations). | |
| return verifyCosignV2Bundle(img, ref.Digest, sev, certID) | |
| } | |
| // Try v3 bundle format first (bundle is raw layer content). | |
| if desc.ArtifactType == cosignV3BundleArtifactType { | |
| return verifyCosignV3Bundle(img, ref.Digest, sev, certID) | |
| } | |
| // Fall back to v2 format (bundle in annotations). | |
| // Note: Only v2 or v3 artifact types reach here due to filtering in findCosignSignatures. | |
| return verifyCosignV2Bundle(img, ref.Digest, sev, certID) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cli/internal/verify/cosign.go` around lines 129 - 136, The dispatch currently
assumes descriptors reaching this block are filtered by
isCosignSignatureArtifact in findCosignSignatures so it treats any non-v3
ArtifactType as v2; add a brief defensive comment above the dispatch clarifying
that invariant and either return an explicit error if desc.ArtifactType is
neither cosignV3BundleArtifactType nor the expected v2 type or assert the
invariant (e.g., panic/log and return error) before calling
verifyCosignV3Bundle/verifyCosignV2Bundle; reference desc.ArtifactType,
cosignV3BundleArtifactType, verifyCosignV3Bundle, verifyCosignV2Bundle,
isCosignSignatureArtifact, and findCosignSignatures when making the change.
- Simplify isCosignSignatureArtifact from switch to single return - Add TestVerifyCosignSignatureV3InvalidBundle covering the v3 code path (bundle as layer content, not annotations) Addresses Gemini and CodeRabbit review feedback.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@cli/internal/verify/cosign.go`:
- Around line 149-152: The current io.ReadAll(reader) call that produces
bundleJSON can exhaust memory for large malicious layers; replace it by reading
via io.LimitReader to impose a maximum bundle size (e.g., define const
maxBundleSize = 1<<20 or suitable value) and use
io.ReadAll(io.LimitReader(reader, maxBundleSize)); after reading, detect if the
stream was truncated/limit reached (e.g., attempt to read one extra byte or
check if len(bundleJSON) == maxBundleSize) and return a clear error like "bundle
too large" if exceeded. Update the code around bundleJSON and reader in
cosign.go to use the limit and an explicit error path.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 86a84f37-2143-40e9-a7f7-2a50945fa673
📒 Files selected for processing (2)
cli/internal/verify/cosign.gocli/internal/verify/cosign_test.go
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: CLI Test (macos-latest)
- GitHub Check: CLI Test (windows-latest)
- GitHub Check: Build Web
- GitHub Check: Build Backend
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
cli/**/*.go
📄 CodeRabbit inference engine (CLAUDE.md)
cli/**/*.go: Go: runcd cli && golangci-lint runbefore committing CLI code
Go: runcd cli && go vet ./...to check for common Go mistakes
Files:
cli/internal/verify/cosign_test.gocli/internal/verify/cosign.go
cli/**/*_test.go
📄 CodeRabbit inference engine (CLAUDE.md)
Go: use
testing.Ffor fuzz testing; organize fuzz targets in test files withFuzzTargetnaming
Files:
cli/internal/verify/cosign_test.go
🧬 Code graph analysis (1)
cli/internal/verify/cosign_test.go (2)
cli/internal/verify/verify.go (1)
ImageRef(28-33)cli/internal/verify/cosign.go (1)
VerifyCosignSignature(53-73)
🔇 Additional comments (7)
cli/internal/verify/cosign.go (4)
122-129: LGTM!The v3/v2 dispatch correctly handles both formats. Since
findCosignSignaturespre-filters descriptors viaisCosignSignatureArtifact, only valid artifact types reach this point.
17-30: LGTM! Constants align with cosign v3 and v2 formats.The artifact type constants correctly distinguish between cosign v3 bundle format (
application/vnd.dev.sigstore.bundle.v0.3+json) and the legacy v2 simplesigning format. Documentation is clear.
38-42: LGTM!The helper is concise and correctly identifies both v3 and v2 cosign signature artifact types.
157-190: LGTM!The v2 bundle extraction correctly tries manifest-level annotations first, then layer annotations, with proper error aggregation. The fallback error message is clear.
cli/internal/verify/cosign_test.go (3)
147-171: LGTM!Test correctly updated to use the
cosignV2ArtifactTypeconstant, ensuring the test exercises the v2 annotation-based verification path.
220-237: LGTM!Good table-driven test covering both positive cases (v3 and v2 artifact types) and negative cases (empty OCI, in-toto, empty string). Test is concise and idiomatic.
239-329: LGTM!Excellent test for the v3 bundle extraction path. The mock registry correctly simulates:
- A referrer index with
cosignV3BundleArtifactType- Layer content containing the bundle (not in annotations)
- Invalid bundle JSON to trigger the error path
This complements
TestVerifyCosignSignatureInvalidBundleand ensures both v3 and v2 bundle extraction paths are tested.
Limit io.ReadAll to 1MB+1 via io.LimitReader when reading cosign v3 bundle from registry layer. Prevents a malicious registry from serving an arbitrarily large blob. Typical bundles are ~10KB.
🤖 I have created a release *beep* *boop* --- ## [0.3.4](v0.3.3...v0.3.4) (2026-03-18) ### Bug Fixes * **cli:** support cosign v3 bundle format for signature verification ([#546](#546)) ([6115eff](6115eff)), closes [#532](#532) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
Summary
Fixes
synthorg start/synthorg updatefailing with "no cosign signatures found" because the CLI only looked for cosign v2 signature artifacts, but cosign-installer v4.1.0 installs cosign v3 which uses a different storage format.Root cause
cosign v3 stores signatures as OCI referrer artifacts with:
application/vnd.dev.sigstore.bundle.v0.3+json(notapplication/vnd.dev.cosign.simplesigning.v1+json)dev.sigstore.cosign/bundleannotation)sha256-<digest>(not.sigtag)The signatures were being pushed to GHCR correctly all along -- the CLI just wasn't looking for the right format.
CLI changes
application/vnd.dev.sigstore.bundle.v0.3+json) and v2 (application/vnd.dev.cosign.simplesigning.v1+json) artifact types in OCI referrer discoveryisCosignSignatureArtifacthelper for clean type matchingremote.Referrers()will use it automaticallyCI changes
--registry-referrers-mode=oci-1-1andCOSIGN_EXPERIMENTALfrom all 3cosign signcommands -- cosign v3 already pushes to the referrers fallback tag by defaultcosign sign --yesis the correct invocationTest plan
go vet ./...passesgo test ./...passesgo build ./...passesgolangci-lint runpassessynthorg startverifies cosign signatures successfullysha256-0230fa...with artifact typeapplication/vnd.dev.sigstore.bundle.v0.3+jsonCloses #532