Skip to content

Support Sigstore bundle verification for cosign v3#3123

Open
SequeI wants to merge 2 commits intoconforma:mainfrom
SequeI:bundleAware
Open

Support Sigstore bundle verification for cosign v3#3123
SequeI wants to merge 2 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.

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 <asiek@redhat.com>
Signed-off-by: SequeI <asiek@redhat.com>
@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

Comment on lines +125 to +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
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. hasbundles() ignores getbundles error 📘 Rule violation ⛯ Reliability

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.
Agent Prompt
## 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

Comment on lines +164 to +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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Signature check verifies attestations 🐞 Bug ⛨ Security

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.
Agent Prompt
### 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

Comment on lines +146 to +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
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Bundle attestations drop signatures 🐞 Bug ✓ Correctness

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.
Agent Prompt
### 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

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.

1 participant