Skip to content

Support Sigstore bundle verification for cosign v3#3123

Merged
simonbaird merged 3 commits intoconforma:mainfrom
SequeI:bundleAware
Mar 2, 2026
Merged

Support Sigstore bundle verification for cosign v3#3123
simonbaird merged 3 commits intoconforma:mainfrom
SequeI:bundleAware

Conversation

@SequeI
Copy link
Contributor

@SequeI SequeI commented Feb 25, 2026

cosign v3 stores signatures as OCI referrer bundles by default, but ec
only supported the legacy tag-based format. Detect bundles via
cosign.GetBundles() and route verification through the bundle-aware
code path, matching what the cosign CLI already does internally.

Also fix typos and incorrect examples in CLI help text.

You can check this behaviour out by using Cosign V3, signing an image, attaching the predicate.json, and trying to use ec validate from main branch, then from this branch.

Ref: https://issues.redhat.com/browse/EC-1689

@qodo-code-review
Copy link
Contributor

Review Summary by Qodo

Support Sigstore bundle verification for cosign v3

✨ Enhancement 🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Add support for Sigstore bundle verification for cosign v3
  - Detect bundles via cosign.GetBundles() and route through bundle-aware code path
  - Implement ProvenanceFromBundlePayload() for parsing DSSE envelope payloads
  - Add parseAttestationsFromBundles() for extracting attestations from bundles
• Fix CLI help text typos and incorrect command examples
  - Correct "form" to "from" in sigstore initialize help
  - Fix "expresssion" to "expression" typo in validate image help
  - Update example commands to use correct ec sigstore initialize syntax
• Expose CreateRemoteOptions() as public function for bundle verification
Diagram
flowchart LR
  A["Image with Bundles"] -->|hasBundles check| B["Bundle Path"]
  A -->|no bundles| C["Legacy Tag Path"]
  B -->|VerifyImageAttestations| D["parseAttestationsFromBundles"]
  D -->|ProvenanceFromBundlePayload| E["Attestation Objects"]
  C -->|VerifyImageSignatures| F["ProvenanceFromSignature"]
  F --> E
Loading

Grey Divider

File Changes

1. cmd/sigstore/initialize.go 📝 Documentation +4/-8

Fix CLI help text and command examples

• Fix typo: "form" changed to "from" in help text
• Remove outdated statement about command being wrapper around cosign
• Correct example commands from ec initialize to ec sigstore initialize
• Update flag examples to use long-form flags (e.g., --root instead of -root)

cmd/sigstore/initialize.go


2. cmd/validate/image.go 📝 Documentation +2/-2

Fix typos in validate image help text

• Fix typo: "expresssion" changed to "expression" in flag help text
• Fix indentation issue in example documentation

cmd/validate/image.go


3. internal/attestation/attestation.go ✨ Enhancement +26/-0

Add bundle payload attestation parsing function

• Add new ProvenanceFromBundlePayload() function to parse attestations from raw DSSE envelope JSON
• Handles bundle-wrapped attestations with proper error handling for missing payload data
• Returns provenance object without signatures (signatures handled separately in bundle path)

internal/attestation/attestation.go


View more (4)
4. internal/evaluation_target/application_snapshot_image/application_snapshot_image.go ✨ Enhancement +70/-8

Implement bundle-aware signature and attestation verification

• Add imports for cosignOCI and ociremote packages
• Add hasBundles() method to detect Sigstore bundles using cosign.GetBundles()
• Refactor ValidateImageSignature() to route bundle images through VerifyImageAttestations()
 with IntotoSubjectClaimVerifier
• Refactor ValidateAttestationSignature() to detect bundles and parse via new
 parseAttestationsFromBundles() method
• Add parseAttestationsFromBundles() to extract attestations from DSSE envelopes in bundle format

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go


5. internal/utils/oci/client.go ✨ Enhancement +2/-2

Expose CreateRemoteOptions as public function

• Change createRemoteOptions() function from private to public by renaming to
 CreateRemoteOptions()
• Update internal call site to use new public function name

internal/utils/oci/client.go


6. docs/modules/ROOT/pages/ec_sigstore_initialize.adoc 📝 Documentation +4/-8

Update sigstore initialize documentation

• Fix typo: "form" changed to "from" in documentation
• Remove outdated statement about command being wrapper around cosign
• Correct example commands from ec initialize to ec sigstore initialize
• Update flag examples to use long-form flags with proper syntax

docs/modules/ROOT/pages/ec_sigstore_initialize.adoc


7. docs/modules/ROOT/pages/ec_validate_image.adoc 📝 Documentation +2/-2

Fix typos in validate image documentation

• Fix typo: "expresssion" changed to "expression" in options documentation
• Fix indentation in example section

docs/modules/ROOT/pages/ec_validate_image.adoc


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Feb 25, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (2) 📎 Requirement gaps (0)

Grey Divider


Action required

1. hasBundles() ignores GetBundles error 📘 Rule violation ⛯ Reliability
Description
hasBundles() silently converts cosign.GetBundles() failures into false, which can route
verification down the legacy path without surfacing the underlying failure. This can mask
registry/network/auth problems and produce misleading verification behavior.
Code

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[R125-129]

+func (a *ApplicationSnapshotImage) hasBundles(ctx context.Context) bool {
+	regOpts := []ociremote.Option{ociremote.WithRemoteOptions(oci.CreateRemoteOptions(ctx)...)}
+	bundles, _, err := cosign.GetBundles(ctx, a.reference, regOpts)
+	return err == nil && len(bundles) > 0
+}
Evidence
Compliance requires potential failure points to be handled with meaningful context; here the error
from an external dependency (cosign.GetBundles) is discarded and not logged or returned, making
failures hard to diagnose and potentially changing control flow silently.

Rule 3: Generic: Robust Error Handling and Edge Case Management
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[125-129]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`hasBundles()` discards errors from `cosign.GetBundles()` and returns `false`, which can silently trigger the legacy verification path and hide real registry/network/auth failures.
## Issue Context
Bundle detection depends on an external call. Per compliance, failure points should be handled explicitly with actionable context (return error and/or log at an appropriate level).
## Fix Focus Areas
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[125-171]
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[192-206]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Signature check verifies attestations 🐞 Bug ⛨ Security
Description
For bundle images, ValidateImageSignature verifies attestations (and uses the in-toto claim
verifier) instead of verifying image signatures, so the ImageSignatureCheck can become semantically
incorrect. This risks passing the “image signature” gate based on attestations rather than actual
image signatures.
Code

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[R164-170]

+	if a.hasBundles(ctx) {
+		opts.NewBundleFormat = true
+		opts.ClaimVerifier = cosign.IntotoSubjectClaimVerifier
+		sigs, _, err = client.VerifyImageAttestations(a.reference, &opts)
+	} else {
+		opts.ClaimVerifier = cosign.SimpleClaimVerifier
+		sigs, _, err = client.VerifyImageSignatures(a.reference, &opts)
Evidence
The code path for bundles uses VerifyImageAttestations with IntotoSubjectClaimVerifier inside
ValidateImageSignature, while other signature-verification code paths and documentation
distinguish image signatures from attestation signatures and use VerifyImageSignatures +
SimpleClaimVerifier for image signatures.

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[154-171]
internal/rego/sigstore/sigstore.go[124-136]
docs/modules/ROOT/pages/types-of-attestations-and-manifests.adoc[111-120]
internal/image/validate.go[77-81]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ValidateImageSignature` switches to attestation verification for bundle images, which changes the meaning of the “image signature check” and can incorrectly pass it based on attestations.
### Issue Context
The system treats image signatures and attestation signatures as separate checks and documents them as distinct concepts.
### Fix Focus Areas
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[154-171]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Bundle attestations drop signatures🐞 Bug ✓ Correctness
Description
Bundle attestation parsing returns attestations without populating .Signatures(), so policy input
.attestations[].signatures becomes empty/omitted for bundle format. This violates the documented
policy-input contract and can break policies that expect attestation signatures to be present.
Code

internal/attestation/attestation.go[R146-170]

+// ProvenanceFromBundlePayload parses an attestation from a raw DSSE envelope
+// JSON payload as returned by the Sigstore bundle verification path.
+func ProvenanceFromBundlePayload(dsseJSON []byte) (Attestation, error) {
+	var payload cosign.AttestationPayload
+	if err := json.Unmarshal(dsseJSON, &payload); err != nil {
+		return nil, fmt.Errorf("malformed bundle attestation: %w", err)
+	}
+
+	if payload.PayLoad == "" {
+		return nil, errors.New("no `payload` data found in bundle attestation")
+	}
+
+	embedded, err := decodedPayload(payload)
+	if err != nil {
+		return nil, err
+	}
+
+	//nolint:staticcheck
+	var statement in_toto.Statement
+	if err := json.Unmarshal(embedded, &statement); err != nil {
+		return nil, fmt.Errorf("malformed bundle attestation: %w", err)
+	}
+
+	return provenance{statement: statement, data: embedded}, nil
+}
Evidence
Legacy attestation parsing (ProvenanceFromSignature) extracts and stores signatures, and the
policy-input docs explicitly state attestations contain .statement and .signatures. The new
bundle parsing (ProvenanceFromBundlePayload) does not set signatures, and the input writer
includes whatever Attestation.Signatures() returns—so signatures are silently lost for
bundle-derived attestations.

internal/attestation/attestation.go[118-144]
internal/attestation/attestation.go[146-170]
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[422-432]
docs/modules/ROOT/pages/policy_input.adoc[73-76]
acceptance/examples/sigstore.rego[64-69]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Bundle-derived attestations are added without `.signatures`, violating the documented policy-input schema and breaking policies that expect signature metadata.
### Issue Context
Legacy path uses `ProvenanceFromSignature` which calls `createEntitySignatures`. The bundle path parses a DSSE envelope but never populates `provenance.signatures`.
### Fix Focus Areas
- internal/attestation/attestation.go[146-170]
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[251-281]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Bundle parsing errors are skipped 📘 Rule violation ⛯ Reliability
Description
Bundle attestation parsing errors are only logged at debug level and then skipped, which can lead to
silently missing attestations in typical non-debug runs. This weakens diagnosability and can cause
unexpected successful execution with incomplete inputs.
Code

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[R255-272]

+	for _, sig := range layers {
+		payload, err := sig.Payload()
+		if err != nil {
+			log.Debugf("Skipping bundle entry: cannot read payload: %v", err)
+			continue
+		}
+		var dsseEnvelope struct {
+			PayloadType string `json:"payloadType"`
+			Payload     string `json:"payload"`
+		}
+		if err := json.Unmarshal(payload, &dsseEnvelope); err != nil {
+			log.Debugf("Skipping bundle entry: not a valid DSSE envelope: %v", err)
+			continue
+		}
+		if dsseEnvelope.PayloadType != "application/vnd.in-toto+json" {
+			log.Debugf("Skipping bundle entry with payloadType: %s", dsseEnvelope.PayloadType)
+			continue
+		}
Evidence
The checklist forbids silent/ignored errors; here multiple error cases (sig.Payload() and DSSE
unmarshal) are handled by Debugf + continue, which is effectively silent in many production
configurations and provides no enforcement that at least one valid attestation was collected.

Rule 3: Generic: Robust Error Handling and Edge Case Management
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[255-272]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`parseAttestationsFromBundles()` skips multiple error conditions with `Debugf` + `continue`, which can hide failures and result in missing attestations without an actionable signal.
## Issue Context
In typical runs, debug logs may be disabled. Robust error handling should either return an error with context or clearly log at an appropriate level and enforce required invariants (e.g., at least one attestation parsed if bundle mode is in use).
## Fix Focus Areas
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[251-283]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Rego sigstore not bundle-aware 🐞 Bug ✓ Correctness
Description
Bundle-aware routing was added in the image validation path, but the documented/user-facing Rego
builtins ec.sigstore.verify_image and ec.sigstore.verify_attestation still call
VerifyImageSignatures/VerifyImageAttestations without enabling NewBundleFormat or detecting
bundles. Policies using these builtins may still fail on cosign v3 default bundle storage.
Code

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[R164-167]

+	if a.hasBundles(ctx) {
+		opts.NewBundleFormat = true
+		opts.ClaimVerifier = cosign.IntotoSubjectClaimVerifier
+		sigs, _, err = client.VerifyImageAttestations(a.reference, &opts)
Evidence
The PR introduces explicit bundle detection + NewBundleFormat toggling in
ApplicationSnapshotImage, implying the default verification path is not sufficient for bundle
storage. However, the Rego builtins remain unchanged and are documented/used in example policies, so
bundle images may continue to break there.

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[164-167]
internal/rego/sigstore/sigstore.go[124-136]
internal/rego/sigstore/sigstore.go[185-196]
acceptance/examples/sigstore.rego[23-33]
docs/modules/ROOT/pages/ec_sigstore_verify_image.adoc[1-22]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Rego sigstore builtins are documented and used by example policies, but they don’t participate in the new bundle-aware routing.
### Issue Context
This PR added explicit bundle routing in `ApplicationSnapshotImage`; similar handling is likely needed for builtins.
### Fix Focus Areas
- internal/rego/sigstore/sigstore.go[111-136]
- internal/rego/sigstore/sigstore.go[172-197]
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[125-129]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@simonbaird
Copy link
Member

/ok-to-test

@simonbaird
Copy link
Member

@SequeI Did you look at the Qodo suggestions? Are any of them worthwhile fixing before merge in your opinion?

I do think the "Rego sigstore not bundle-aware" is something we need to fix, but it could have it's own separate PR and Jira.

Related question: Is there a Jira open for this one?

@simonbaird
Copy link
Member

simonbaird commented Mar 2, 2026

FYI I've tested this with a v2 and v3 signed image in a version of #3140 rebased on this PR, and it seems to be working nicely.

@simonbaird
Copy link
Member

For the enterprise contract failure, it's a known issue. Will aim to get it fixed in main branch, then a rebase should get this green.

@SequeI
Copy link
Contributor Author

SequeI commented Mar 2, 2026

@simonbaird

1 and 2 are by design, if we cannot detect sigstore bundle via referrers API, we fallback to checking for tags so it's fine. Any network/auth failures will get caught by the tag check.

Fixed 3rd issue, sig field is populated correctly now. One quick run through to check correctness and should be good

@codecov
Copy link

codecov bot commented Mar 2, 2026

Codecov Report

❌ Patch coverage is 86.36364% with 9 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ation_snapshot_image/application_snapshot_image.go 80.95% 8 Missing ⚠️
internal/attestation/attestation.go 93.75% 1 Missing ⚠️
Flag Coverage Δ
acceptance 54.85% <34.84%> (-0.16%) ⬇️
generative 18.16% <0.00%> (-0.09%) ⬇️
integration 27.00% <3.03%> (-0.13%) ⬇️
unit 68.65% <83.33%> (+0.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
cmd/sigstore/initialize.go 100.00% <100.00%> (ø)
cmd/validate/image.go 91.32% <100.00%> (ø)
internal/utils/oci/client.go 56.91% <100.00%> (ø)
internal/attestation/attestation.go 90.90% <93.75%> (+0.74%) ⬆️
...ation_snapshot_image/application_snapshot_image.go 82.68% <80.95%> (-0.65%) ⬇️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions github-actions bot added size: XL and removed size: L labels Mar 2, 2026
SequeI added 3 commits March 2, 2026 12:45
cosign v3 stores signatures as OCI referrer bundles by default, but ec
only supported the legacy tag-based format. Detect bundles via
cosign.GetBundles() and route verification through the bundle-aware
code path, matching what the cosign CLI already does internally.

Also fix typos and incorrect examples in CLI help text.

Signed-off-by: SequeI <[email protected]>
Signed-off-by: SequeI <[email protected]>
@simonbaird
Copy link
Member

/ok-to-test

@simonbaird simonbaird merged commit bb47538 into conforma:main Mar 2, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants