Skip to content

Improve signature and attestation detail output for sigstore bundles#3162

Open
simonbaird wants to merge 2 commits intoconforma:mainfrom
simonbaird:v3-sig-output
Open

Improve signature and attestation detail output for sigstore bundles#3162
simonbaird wants to merge 2 commits intoconforma:mainfrom
simonbaird:v3-sig-output

Conversation

@simonbaird
Copy link
Member

@qodo-code-review
Copy link
Contributor

Review Summary by Qodo

Handle v3 sigstore bundle signatures and attestations filtering

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Handle v3 sigstore bundle signatures and attestations separately
  - Filter image signature attestations from provenance attestations
  - Extract signature details from bundle image signature attestations
• Add helper functions for bundle signature processing
  - isImageSignatureAttestation() identifies signature type by predicate
  - extractSignaturesFromBundle() extracts signatures from DSSE envelopes
• Improve code clarity with detailed comments explaining v3 bundle handling
• Minor code style fix for octal file permission notation
Diagram
flowchart LR
  A["v3 Bundle Signatures"] --> B["VerifyImageAttestations"]
  B --> C["Filter by Predicate Type"]
  C --> D["Image Signatures"]
  C --> E["Provenance Attestations"]
  D --> F["extractSignaturesFromBundle"]
  E --> G["parseAttestationsFromBundles"]
  F --> H["EntitySignature List"]
  G --> I["Attestation List"]
Loading

Grey Divider

File Changes

1. internal/evaluation_target/application_snapshot_image/application_snapshot_image.go ✨ Enhancement +129/-7

Add v3 bundle signature and attestation filtering logic

• Added filtering logic to separate image signature attestations from provenance attestations in v3
 bundles
• Implemented isImageSignatureAttestation() helper to identify attestation types by predicate type
• Implemented extractSignaturesFromBundle() to extract signature details from bundle DSSE
 envelopes
• Updated ValidateImageSignature() to handle v3 bundles differently from v2 signatures
• Updated parseAttestationsFromBundles() to skip image signature attestations
• Added comprehensive comments explaining v3 bundle handling and known limitations
• Fixed octal file permission notation from 0644 to 0o644

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Mar 4, 2026

Code Review by Qodo

🐞 Bugs (5) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Missing predicate constant 🐞 Bug ✓ Correctness
Description
The PR references attestation.PredicateCosignSign but the local internal/attestation package does
not define it, causing a compile-time failure. This blocks merging and also prevents the new bundle
filtering logic from working at all.
Code

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[R532-552]

+// isImageSignatureAttestation checks if a signature from a bundle represents
+// an image signature attestation (vs. a provenance attestation).
+func isImageSignatureAttestation(sig cosignOCI.Signature) bool {
+	payload, err := sig.Payload()
+	if err != nil {
+		log.Debugf("Cannot read signature payload: %v", err)
+		return false
+	}
+
+	att, err := attestation.ProvenanceFromBundlePayload(sig, payload)
+	if err != nil {
+		log.Debugf("Cannot parse bundle attestation: %v", err)
+		return false
+	}
+
+	predicateType := att.PredicateType()
+	// Image signature attestations use the cosign/sign predicate type
+	isImageSig := predicateType == attestation.PredicateCosignSign
+	log.Debugf("Attestation predicateType: %s, isImageSignature: %t", predicateType, isImageSig)
+
+	return isImageSig
Evidence
ApplicationSnapshotImage uses attestation.PredicateCosignSign when classifying bundle signatures,
but internal/attestation only defines other predicate constants (e.g., SLSA, SPDX) and does not
define PredicateCosignSign, so the identifier is undefined.

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[532-552]
internal/attestation/slsa_provenance_02.go[31-34]
internal/attestation/spdx_sbom.go[19-21]

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

## Issue description
`internal/evaluation_target/application_snapshot_image/application_snapshot_image.go` compares attestation predicate types to `attestation.PredicateCosignSign`, but that identifier is not defined in the local `internal/attestation` package. This will fail compilation.
### Issue Context
The new bundle filtering logic relies on identifying cosign image signature attestations by predicate type.
### Fix Focus Areas
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[532-552]
- internal/attestation/spdx_sbom.go[17-25]
- internal/attestation/slsa_provenance_02.go[31-35]
### Suggested approach
- Add `const PredicateCosignSign = "https://sigstore.dev/cosign/sign/v1"` to the `internal/attestation` package (new file or an existing predicates/constants file).
- Alternatively, avoid cross-package dependency by using a local constant in application_snapshot_image.go (but prefer a single canonical definition in `internal/attestation`).

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


2. Repeated bundle lookup 🐞 Bug ⛯ Reliability
Description
ValidateImageSignature calls hasBundles(ctx) once to select verification mode and again inside the
per-signature loop; hasBundles performs a remote cosign.GetBundles call. This can cause N extra
registry calls, and worse, a transient failure can flip the branch mid-function and process bundle
attestations using the non-bundle signature path.
Code

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[R183-206]

for _, s := range sigs {
-		es, err := signature.NewEntitySignature(s)
-		if err != nil {
-			return err
+		if a.hasBundles(ctx) {
+			// This will appears in the output under "signatures" so filter out
+			// the sigs that are provenance attestations leaving only the sigs
+			// that are image signatures. Note: This does seems confusing and
+			// I'm not 100% sure we're doing the right thing here. Maybe revisit
+			// once we have a better idea about sigstore bundles and how they're
+			// handled by cosign itself.
+			if !isImageSignatureAttestation(s) {
+				log.Debugf("Skipping non-image signature attestation")
+				continue
+			}
+
+			// For bundle image signatures produced by cosign v3, the old
+			// method of accessing the signatures doesn't work. Instead we have
+			// to extract them from the bundle. And the bundle actually has
+			// signatures for both the image itself and the attestation.
+			signatures, err := extractSignaturesFromBundle(s)
+			if err != nil {
+				return fmt.Errorf("cannot extract signatures from bundle: %w", err)
+			}
+			a.signatures = append(a.signatures, signatures...)
+		} else {
+			// For older non-bundle image signatures produced by cosign v2.
Evidence
hasBundles does a remote bundle listing via cosign.GetBundles, and ValidateImageSignature invokes it
both before verification and again inside the loop. That can add extra network calls and make
behavior inconsistent if the result differs between calls.

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[125-129]
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[164-178]
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[183-206]

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` calls `a.hasBundles(ctx)` multiple times, including inside a loop. `hasBundles` performs a remote `cosign.GetBundles` call, which can be slow and can fail transiently, leading to inconsistent code paths.
### Issue Context
The first `hasBundles` result determines whether signatures are fetched via `VerifyImageAttestations` (bundle mode) or `VerifyImageSignatures` (legacy). Re-checking inside the loop is unnecessary and risky.
### Fix Focus Areas
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[125-129]
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[157-215]
### Suggested approach
- Set `useBundles := a.hasBundles(ctx)` once at the start of `ValidateImageSignature`.
- Use `useBundles` for both selecting the verification call and for choosing the per-signature processing branch.
- (Optional) consider caching `useBundles` on the struct if other methods also need it, to avoid multiple remote calls across validation steps.

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



Remediation recommended

3. Double attestation verification 🐞 Bug ➹ Performance
Description
For bundle images, ValidateImageSignature uses VerifyImageAttestations to obtain image signature
attestations, and ValidateAttestationSignature also calls VerifyImageAttestations to obtain
attestations. Since ValidateImage runs both checks sequentially, bundle images pay for two remote
verification passes, increasing latency and flakiness.
Code

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

if a.hasBundles(ctx) {
+		// For v3 bundles, both image signatures and attestations are stored as
+		// "attestations" in the unified bundle format. So we use VerifyImageAttestations
+		// to extract image signatures from the bundle, even though it seems unintuitive.
+		// This is different from v2 where image signatures and attestations were separate.
+		//
+		// The certificate extraction requires different handling for bundles and
+		// should be addressed in future work to achieve full v2 parity.
  opts.NewBundleFormat = true
  opts.ClaimVerifier = cosign.IntotoSubjectClaimVerifier
  sigs, _, err = client.VerifyImageAttestations(a.reference, &opts)
Evidence
ValidateImageSignature calls VerifyImageAttestations when bundles are present;
ValidateAttestationSignature always calls VerifyImageAttestations; and ValidateImage invokes both
validations sequentially. This yields two similar remote verification operations per bundle image.

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[164-175]
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[224-232]
internal/image/validate.go[77-85]

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

## Issue description
For bundle images, both image signature validation and attestation validation invoke `VerifyImageAttestations`, leading to duplicated remote verification work.
### Issue Context
`ValidateImage` calls `ValidateImageSignature` (unless skipped) and then `ValidateAttestationSignature`. In bundle mode, both can end up hitting `VerifyImageAttestations`.
### Fix Focus Areas
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[157-216]
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[220-241]
- internal/image/validate.go[77-92]
### Suggested approach
- Introduce a shared method on `ApplicationSnapshotImage` like `verifyAttestationsOnce(ctx) ([]cosignOCI.Signature, error)` that caches results for the lifetime of the struct.
- In bundle mode, have `ValidateImageSignature` reuse those layers and filter/extract image signatures from them instead of re-calling verification.
- Keep legacy v2 behavior unchanged.

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


4. Filter swallows parse errors 🐞 Bug ⛯ Reliability
Description
isImageSignatureAttestation converts any ProvenanceFromBundlePayload error into 'false' and only
logs at debug level. Because ProvenanceFromBundlePayload also extracts signatures/cert info, errors
reading Base64Signature/Cert/Chain can silently cause image signatures to be skipped or attestations
to be misclassified.
Code

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[R534-545]

+func isImageSignatureAttestation(sig cosignOCI.Signature) bool {
+	payload, err := sig.Payload()
+	if err != nil {
+		log.Debugf("Cannot read signature payload: %v", err)
+		return false
+	}
+
+	att, err := attestation.ProvenanceFromBundlePayload(sig, payload)
+	if err != nil {
+		log.Debugf("Cannot parse bundle attestation: %v", err)
+		return false
+	}
Evidence
Predicate classification currently depends on ProvenanceFromBundlePayload, which calls
createEntitySignatures and therefore signature.NewEntitySignature. NewEntitySignature can error
while reading signature/cert/chain. isImageSignatureAttestation suppresses these errors by returning
false, which drives filtering decisions in both ValidateImageSignature and
parseAttestationsFromBundles.

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[534-545]
internal/attestation/attestation.go[91-115]
internal/attestation/attestation.go[146-174]
internal/signature/signature.go[34-65]

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

## Issue description
`isImageSignatureAttestation` uses `attestation.ProvenanceFromBundlePayload` to determine predicateType, but that function also tries to build signature/cert metadata and can fail for reasons unrelated to predicate parsing. The current code treats any failure as "not an image signature", which can silently drop/misclassify entries.
### Issue Context
This function is used for filtering in both `ValidateImageSignature` and `parseAttestationsFromBundles`, so misclassification affects output correctness.
### Fix Focus Areas
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[532-553]
- internal/attestation/attestation.go[91-175]
- internal/signature/signature.go[34-75]
### Suggested approach
- Add a lightweight helper that:
- unmarshals the DSSE envelope,
- base64-decodes the payload,
- unmarshals into an `in_toto.Statement` (or just reads `predicateType`),
- returns predicateType without touching signature/cert fields.
- Change `isImageSignatureAttestation` to use that helper.
- If predicate parsing fails, consider returning an error up the stack (or at minimum log at warn/error and avoid silently filtering).

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



Advisory comments

5. No bundle sig tests🐞 Bug ⛯ Reliability
Description
The new bundle image signature path (VerifyImageAttestations + isImageSignatureAttestation +
extractSignaturesFromBundle) is not covered by existing ValidateImageSignature tests, increasing the
chance of regressions in this complex filtering/extraction logic.
Code

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

if a.hasBundles(ctx) {
+		// For v3 bundles, both image signatures and attestations are stored as
+		// "attestations" in the unified bundle format. So we use VerifyImageAttestations
+		// to extract image signatures from the bundle, even though it seems unintuitive.
+		// This is different from v2 where image signatures and attestations were separate.
+		//
+		// The certificate extraction requires different handling for bundles and
+		// should be addressed in future work to achieve full v2 parity.
  opts.NewBundleFormat = true
  opts.ClaimVerifier = cosign.IntotoSubjectClaimVerifier
  sigs, _, err = client.VerifyImageAttestations(a.reference, &opts)
Evidence
Existing tests for ValidateImageSignature mock only VerifyImageSignatures (legacy v2 path) and do
not set up bundle mode, so the newly added v3 bundle-specific logic is not exercised.

internal/evaluation_target/application_snapshot_image/application_snapshot_image_test.go[469-482]
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[164-205]

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 new v3 bundle signature handling logic is complex and currently untested at the `ValidateImageSignature` level.
### Issue Context
Current tests only mock `VerifyImageSignatures`, so they don't execute the bundle branch that calls `VerifyImageAttestations`, filters by predicate type, and extracts signatures from the DSSE envelope.
### Fix Focus Areas
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[157-216]
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[532-604]
- internal/evaluation_target/application_snapshot_image/application_snapshot_image_test.go[469-510]
### Suggested approach
- Refactor `hasBundles` decision to be injectable/cacheable so tests can force bundle mode without real registry calls.
- Add tests that:
- return a mix of bundle attestations (cosign/sign + provenance) from `VerifyImageAttestations` and assert correct filtering.
- ensure `extractSignaturesFromBundle` returns expected signatures when Base64Signature is empty vs populated.
- validate behavior when predicate parsing fails (should not silently misclassify).

ⓘ 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

@coderabbitai
Copy link

coderabbitai bot commented Mar 4, 2026

📝 Walkthrough

Walkthrough

ValidateImageSignature gains bundle-aware processing: when bundles exist it enables NewBundleFormat and routes verification through VerifyImageAttestations (Cosign in‑toto verifier), extracts image signatures from bundles, and preserves the legacy non-bundle signature path.

Changes

Cohort / File(s) Summary
Bundle-aware image signature validation
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go
Adds bundle-mode flag in ValidateImageSignature, uses VerifyImageAttestations with NewBundleFormat for bundles, skips image-signature attestations in parseAttestationsFromBundles, and adds helpers: extractPredicateType, hasPredicateType, isImageSignatureAttestation, extractSignaturesFromBundle. Retains legacy non-bundle signature handling and updates logging and file-permission literals.

Sequence Diagram(s)

sequenceDiagram
    participant Validator as ValidateImageSignature
    participant Verifier as VerifyImageAttestations
    participant Cosign as Cosign In-toto Verifier
    participant Bundle as Bundle Parser
    participant SigBuilder as extractSignaturesFromBundle

    Validator->>Verifier: Request verification (NewBundleFormat when bundles present)
    Verifier->>Cosign: Verify attestations (in‑toto)
    Cosign-->>Verifier: Verified attestations (DSSE envelopes)
    Verifier->>Bundle: Provide bundle attestations
    Bundle-->>Verifier: Filter attestations (skip image-signature vs others)
    alt image-signature attestations present
        Bundle->>SigBuilder: extractSignaturesFromBundle(DSSE envelope)
        SigBuilder-->>Validator: EntitySignature objects
    else other attestations
        Verifier-->>Validator: Parsed attestations (provenance, etc.)
    end
    Validator->>Validator: Fall back to legacy non-bundle path if applicable
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Improve signature and attestation detail output for sigstore bundles' directly aligns with the changeset which adds bundle-based signature processing and attestation filtering for sigstore v3 bundles.
Description check ✅ Passed The description references issue EC-1690 which is related to the changeset objective of improving signature and attestation detail output for sigstore bundles, though minimal detail is provided.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.5.0)

Error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions
The command is terminated due to an error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions


Comment @coderabbitai help to get the list of available commands and usage tips.

@simonbaird
Copy link
Member Author

@SequeI FYI, wdyt?

{
"keyid": "",
"sig": ""
"sig": "MEUCIBZc+dmgTn8SCx30h9yvCOjsBwj1+aZX0gW53c7TeyuSAiEAp4zWGNHMrjql9NFl/fCmFXnJkgDkOqbN5n7H7mw6aqI="
Copy link
Member Author

Choose a reason for hiding this comment

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

See line 366 in this file for the equivalent output for v2 style sigs.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go (1)

164-205: ⚠️ Potential issue | 🟠 Major

Cache bundle detection once per call; avoid re-querying in the loop.

Line 164 already decides bundle mode, but Line 184 calls a.hasBundles(ctx) again for every signature. Since hasBundles performs a remote lookup, this adds unnecessary network I/O and can flip behavior mid-loop on transient errors.

Suggested fix
 func (a *ApplicationSnapshotImage) ValidateImageSignature(ctx context.Context) error {
 	opts := a.checkOpts
 	client := oci.NewClient(ctx)
+	useBundles := a.hasBundles(ctx)

 	var sigs []cosignOCI.Signature
 	var err error

-	if a.hasBundles(ctx) {
+	if useBundles {
 		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)
 	}
@@
 	for _, s := range sigs {
-		if a.hasBundles(ctx) {
+		if useBundles {
 			...
 		} else {
 			...
 		}
 	}

As per coding guidelines, "Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@internal/evaluation_target/application_snapshot_image/application_snapshot_image.go`
around lines 164 - 205, Compute and cache the bundle-mode result once (e.g.,
isBundle := a.hasBundles(ctx)) before calling
client.VerifyImageAttestations/VerifyImageSignatures and use that cached boolean
inside the for _, s := range sigs loop instead of calling a.hasBundles(ctx)
repeatedly; update the initial branch that chooses VerifyImageAttestations vs
VerifyImageSignatures to use the cached value and remove the extra remote
lookups in the loop (ensure any error handling for the initial hasBundles call
is preserved).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@internal/evaluation_target/application_snapshot_image/application_snapshot_image.go`:
- Around line 534-553: isImageSignatureAttestation currently calls
attestation.ProvenanceFromBundlePayload which performs full signature/material
extraction and can fail even when predicateType indicates an image-signature;
change it to parse only the predicate type from the signature payload bytes
returned by sig.Payload() (e.g., decode the JSON payload and read the
"predicateType" field) and base the boolean result solely on that value; remove
or avoid calling attestation.ProvenanceFromBundlePayload in
isImageSignatureAttestation so metadata extraction errors do not cause false
negatives, but keep debug logging for any payload parse errors.

---

Outside diff comments:
In
`@internal/evaluation_target/application_snapshot_image/application_snapshot_image.go`:
- Around line 164-205: Compute and cache the bundle-mode result once (e.g.,
isBundle := a.hasBundles(ctx)) before calling
client.VerifyImageAttestations/VerifyImageSignatures and use that cached boolean
inside the for _, s := range sigs loop instead of calling a.hasBundles(ctx)
repeatedly; update the initial branch that chooses VerifyImageAttestations vs
VerifyImageSignatures to use the cached value and remove the extra remote
lookups in the loop (ensure any error handling for the initial hasBundles call
is preserved).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1eb6f5b7-2584-4200-8e07-a21f30521173

📥 Commits

Reviewing files that changed from the base of the PR and between d70aaba and b250838.

⛔ Files ignored due to path filters (1)
  • features/__snapshots__/task_validate_image.snap is excluded by !**/*.snap
📒 Files selected for processing (1)
  • internal/evaluation_target/application_snapshot_image/application_snapshot_image.go

@simonbaird simonbaird force-pushed the v3-sig-output branch 2 times, most recently from c2b7dd7 to a7ce061 Compare March 4, 2026 22:48
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go (1)

541-549: ⚠️ Potential issue | 🟠 Major

Decouple image-signature type detection from full attestation material extraction.

isImageSignatureAttestation currently depends on attestation.ProvenanceFromBundlePayload, so any extraction failure can misclassify a valid image-signature attestation. Parse only predicateType from the DSSE payload for classification.

Proposed fix
 import (
 	"bytes"
 	"context"
+	"encoding/base64"
 	"encoding/json"
@@
 func isImageSignatureAttestation(sig cosignOCI.Signature) bool {
 	payload, err := sig.Payload()
 	if err != nil {
 		log.Debugf("Cannot read signature payload: %v", err)
 		return false
 	}

-	att, err := attestation.ProvenanceFromBundlePayload(sig, payload)
-	if err != nil {
-		log.Debugf("Cannot parse bundle attestation: %v", err)
-		return false
-	}
-
-	predicateType := att.PredicateType()
+	var envelope struct {
+		Payload string `json:"payload"`
+	}
+	if err := json.Unmarshal(payload, &envelope); err != nil || envelope.Payload == "" {
+		log.Debugf("Cannot parse bundle envelope payload: %v", err)
+		return false
+	}
+
+	decoded, err := base64.StdEncoding.DecodeString(envelope.Payload)
+	if err != nil {
+		log.Debugf("Cannot decode bundle payload: %v", err)
+		return false
+	}
+
+	var stmt struct {
+		PredicateType string `json:"predicateType"`
+	}
+	if err := json.Unmarshal(decoded, &stmt); err != nil {
+		log.Debugf("Cannot parse in-toto statement: %v", err)
+		return false
+	}
+
+	predicateType := stmt.PredicateType
 	// Image signature attestations use the cosign/sign predicate type
 	isImageSig := predicateType == "https://sigstore.dev/cosign/sign/v1"
 	log.Debugf("Attestation predicateType: %s, isImageSignature: %t", predicateType, isImageSig)

 	return isImageSig
 }

As per coding guidelines, "Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@internal/evaluation_target/application_snapshot_image/application_snapshot_image.go`
around lines 541 - 549, The image-signature detection currently calls
attestation.ProvenanceFromBundlePayload and uses att.PredicateType(), which
misclassifies when full attestation parsing fails; change
isImageSignatureAttestation logic to extract only the DSSE envelope's
predicateType from the raw payload (do not call ProvenanceFromBundlePayload) and
compare that string to "https://sigstore.dev/cosign/sign/v1" (adjust where
isImageSig/isImageSignatureAttestation is computed), falling back to full
parsing only when predicateType indicates a non-image attestation; this keeps
attestation.ProvenanceFromBundlePayload for when you need full material but
ensures classification is based solely on the DSSE predicateType.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@internal/evaluation_target/application_snapshot_image/application_snapshot_image.go`:
- Around line 184-205: Compute the bundle-mode once before iterating signatures
instead of calling a.hasBundles(ctx) inside the loop: call useBundles :=
a.hasBundles(ctx) (or if hasBundles returns (bool, error) call useBundles, err
:= a.hasBundles(ctx) and handle the error) and then replace the in-loop
conditionals that currently call a.hasBundles(ctx) with checks against
useBundles for both verification and per-signature handling (the blocks using
isImageSignatureAttestation(s), extractSignaturesFromBundle(s) and appending to
a.signatures should use useBundles). This avoids repeated remote lookups and
transient flips in behavior.

---

Duplicate comments:
In
`@internal/evaluation_target/application_snapshot_image/application_snapshot_image.go`:
- Around line 541-549: The image-signature detection currently calls
attestation.ProvenanceFromBundlePayload and uses att.PredicateType(), which
misclassifies when full attestation parsing fails; change
isImageSignatureAttestation logic to extract only the DSSE envelope's
predicateType from the raw payload (do not call ProvenanceFromBundlePayload) and
compare that string to "https://sigstore.dev/cosign/sign/v1" (adjust where
isImageSig/isImageSignatureAttestation is computed), falling back to full
parsing only when predicateType indicates a non-image attestation; this keeps
attestation.ProvenanceFromBundlePayload for when you need full material but
ensures classification is based solely on the DSSE predicateType.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fb182018-0a76-4dc2-8b5e-bcfe5265c33f

📥 Commits

Reviewing files that changed from the base of the PR and between b250838 and a7ce061.

⛔ Files ignored due to path filters (1)
  • features/__snapshots__/task_validate_image.snap is excluded by !**/*.snap
📒 Files selected for processing (1)
  • internal/evaluation_target/application_snapshot_image/application_snapshot_image.go

@simonbaird
Copy link
Member Author

There's some work to do here. Moving to draft.

@simonbaird simonbaird marked this pull request as draft March 5, 2026 15:13
simonbaird and others added 2 commits March 5, 2026 11:35
If we compare the output for a cosign v2 signed image versus a
cosign v3 signed image, there is a lot of detail missing. I'm
expecting we will be able to extract the signature info from the
bundle, but for now Claude and I are having some difficulty.

I think we'll need to gain a better understanding of the way the new
sigstore bundles work and try again in the future.

For now I think it's okay if we just show the sig field and add some
todos in the code. I suspect no one is actually consuming this data,
so it won't have an immediate impact if it's missing.

Ref: https://issues.redhat.com/browse/EC-1690
Co-authored-by: Claude Code <noreply@anthropic.com>
In v3 signature bundles, the signature is actually just another type
of attestation. So when we list attestations (in v3) we see both the
sig and the provenance. Similarly, when we list the image signatures
we see the image signature and the attestation signature.

In the detailed output for ec validate image we show details about
the signatures and attestations. This changes attempts to avoid
listing provenance atts in the image sig list, and imag sigs in the
provenance att list.

As mentioned in the comments, there might be other ways to do this.

Ref: https://issues.redhat.com/browse/EC-1690
Co-authored-by: Claude Code <noreply@anthropic.com>
@simonbaird simonbaird marked this pull request as ready for review March 5, 2026 18:33
@qodo-code-review
Copy link
Contributor

Review Summary by Qodo

Extract and filter v3 sigstore bundle signatures and attestations

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Handle v3 sigstore bundle signature and attestation extraction separately
• Filter image signature attestations from provenance attestations in v3 bundles
• Extract signature details from bundle image signature attestations
• Add helper functions for predicate type detection and filtering
Diagram
flowchart LR
  A["v3 Bundle Processing"] --> B["ValidateImageSignature"]
  A --> C["ValidateAttestationSignature"]
  B --> D["Extract Image Sigs<br/>from Bundle"]
  C --> E["Filter & Extract<br/>Provenance Atts"]
  D --> F["isImageSignatureAttestation<br/>Check"]
  E --> F
  F --> G["Separate Output:<br/>Signatures vs Attestations"]
Loading

Grey Divider

File Changes

1. internal/evaluation_target/application_snapshot_image/application_snapshot_image.go ✨ Enhancement +145/-8

Implement v3 bundle signature and attestation filtering

• Added filtering logic to distinguish image signature attestations from provenance attestations in
 v3 bundles
• Implemented extractSignaturesFromBundle() to extract signature details from bundle image
 signature attestations
• Added helper functions isImageSignatureAttestation(), hasPredicateType(), and
 extractPredicateType() for predicate type detection
• Modified ValidateImageSignature() to use VerifyImageAttestations for bundles and filter
 results appropriately
• Updated parseAttestationsFromBundles() to skip image signature attestations and only process
 provenance attestations
• Fixed octal literal notation from 0644 to 0o644 for Go 1.13+ compliance
• Added comprehensive comments explaining v3 bundle handling differences and TODO items for future
 work

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Mar 5, 2026

Code Review by Qodo

🐞 Bugs (6) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Bundle signature filtering broken 🐞 Bug ✓ Correctness ⭐ New
Description
isImageSignatureAttestation() looks for predicateType directly in sig.Payload(), but for
bundles sig.Payload() is a DSSE envelope and predicateType exists only in the base64-decoded
embedded statement. This likely results in skipping all bundle image signatures (empty
image.signatures) and incorrectly including cosign/sign/v1 entries in attestations.
Code

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[R192-195]

+			if !isImageSignatureAttestation(s) {
+				log.Debugf("Skipping non-image signature attestation")
+				continue
+			}
Evidence
In the bundle path, the code explicitly treats sig.Payload() as a DSSE envelope (payloadType +
base64 payload) and then passes that DSSE JSON to ProvenanceFromBundlePayload, which
base64-decodes the embedded statement before accessing predicateType. However,
isImageSignatureAttestation()/extractPredicateType() attempt to read predicateType from the
*outer* DSSE JSON; since the DSSE envelope does not contain a predicateType field, unmarshalling
succeeds but returns an empty string (no error), making isImageSignatureAttestation() return false
and breaking the filtering in both ValidateImageSignature and parseAttestationsFromBundles.

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[184-206]
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[289-324]
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[533-568]
internal/evaluation_target/application_snapshot_image/application_snapshot_image_test.go[1046-1065]
internal/attestation/attestation.go[146-167]

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

### Issue description
`isImageSignatureAttestation()` currently checks for `predicateType` by JSON-unmarshalling the raw bytes returned from `sig.Payload()`. For bundle entries, `sig.Payload()` is a DSSE envelope JSON containing `payloadType` and a base64-encoded `payload` (the embedded in-toto statement). The DSSE envelope itself does **not** contain `predicateType`, so the check misclassifies bundle entries and breaks signature/attestation filtering.

### Issue Context
- Bundle parsing already assumes `sig.Payload()` is DSSE JSON and uses `attestation.ProvenanceFromBundlePayload()` which base64-decodes the embedded statement.
- Filtering should follow the same model: decode the DSSE envelope, base64-decode its `payload`, then read `predicateType` from the embedded statement.

### Fix Focus Areas
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[533-568]
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[184-206]
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[289-304]
- internal/evaluation_target/application_snapshot_image/application_snapshot_image_test.go[1046-1078]

### Suggested approach
1. Replace `extractPredicateType(payload []byte)` usage for bundle filtering with logic that:
  - Unmarshals `sig.Payload()` into `cosign.AttestationPayload` (or DSSE envelope struct).
  - Base64-decodes `payload.PayLoad`.
  - Extracts `predicateType` from the decoded embedded JSON (your existing `extractPredicateType` can be used *on the decoded payload bytes*).
2. If the DSSE unmarshal/base64 decode fails, return false and emit a debug log that indicates the payload isn’t a DSSE envelope / can’t be decoded.
3. Add a unit test ensuring `isImageSignatureAttestation()` returns true for a bundle DSSE signature whose embedded statement’s predicateType is `https://sigstore.dev/cosign/sign/v1` (you can reuse `createBundleDSSESignature`).
4. Optionally add a higher-level test to ensure ValidateImageSignature(bundle path) populates `a.signatures` (may require controllable bundle detection).

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


2. Missing predicate constant 🐞 Bug ✓ Correctness
Description
The PR references attestation.PredicateCosignSign but the local internal/attestation package does
not define it, causing a compile-time failure. This blocks merging and also prevents the new bundle
filtering logic from working at all.
Code

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[R532-552]

+// isImageSignatureAttestation checks if a signature from a bundle represents
+// an image signature attestation (vs. a provenance attestation).
+func isImageSignatureAttestation(sig cosignOCI.Signature) bool {
+	payload, err := sig.Payload()
+	if err != nil {
+		log.Debugf("Cannot read signature payload: %v", err)
+		return false
+	}
+
+	att, err := attestation.ProvenanceFromBundlePayload(sig, payload)
+	if err != nil {
+		log.Debugf("Cannot parse bundle attestation: %v", err)
+		return false
+	}
+
+	predicateType := att.PredicateType()
+	// Image signature attestations use the cosign/sign predicate type
+	isImageSig := predicateType == attestation.PredicateCosignSign
+	log.Debugf("Attestation predicateType: %s, isImageSignature: %t", predicateType, isImageSig)
+
+	return isImageSig
Evidence
ApplicationSnapshotImage uses attestation.PredicateCosignSign when classifying bundle signatures,
but internal/attestation only defines other predicate constants (e.g., SLSA, SPDX) and does not
define PredicateCosignSign, so the identifier is undefined.

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[532-552]
internal/attestation/slsa_provenance_02.go[31-34]
internal/attestation/spdx_sbom.go[19-21]

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

## Issue description
`internal/evaluation_target/application_snapshot_image/application_snapshot_image.go` compares attestation predicate types to `attestation.PredicateCosignSign`, but that identifier is not defined in the local `internal/attestation` package. This will fail compilation.
### Issue Context
The new bundle filtering logic relies on identifying cosign image signature attestations by predicate type.
### Fix Focus Areas
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[532-552]
- internal/attestation/spdx_sbom.go[17-25]
- internal/attestation/slsa_provenance_02.go[31-35]
### Suggested approach
- Add `const PredicateCosignSign = &amp;amp;amp;quot;https://sigstore.dev/cosign/sign/v1&amp;amp;amp;quot;` to the `internal/attestation` package (new file or an existing predicates/constants file).
- Alternatively, avoid cross-package dependency by using a local constant in application_snapshot_image.go (but prefer a single canonical definition in `internal/attestation`).

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


3. Repeated bundle lookup 🐞 Bug ⛯ Reliability
Description
ValidateImageSignature calls hasBundles(ctx) once to select verification mode and again inside the
per-signature loop; hasBundles performs a remote cosign.GetBundles call. This can cause N extra
registry calls, and worse, a transient failure can flip the branch mid-function and process bundle
attestations using the non-bundle signature path.
Code

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[R183-206]

for _, s := range sigs {
-		es, err := signature.NewEntitySignature(s)
-		if err != nil {
-			return err
+		if a.hasBundles(ctx) {
+			// This will appears in the output under "signatures" so filter out
+			// the sigs that are provenance attestations leaving only the sigs
+			// that are image signatures. Note: This does seems confusing and
+			// I'm not 100% sure we're doing the right thing here. Maybe revisit
+			// once we have a better idea about sigstore bundles and how they're
+			// handled by cosign itself.
+			if !isImageSignatureAttestation(s) {
+				log.Debugf("Skipping non-image signature attestation")
+				continue
+			}
+
+			// For bundle image signatures produced by cosign v3, the old
+			// method of accessing the signatures doesn't work. Instead we have
+			// to extract them from the bundle. And the bundle actually has
+			// signatures for both the image itself and the attestation.
+			signatures, err := extractSignaturesFromBundle(s)
+			if err != nil {
+				return fmt.Errorf("cannot extract signatures from bundle: %w", err)
+			}
+			a.signatures = append(a.signatures, signatures...)
+		} else {
+			// For older non-bundle image signatures produced by cosign v2.
Evidence
hasBundles does a remote bundle listing via cosign.GetBundles, and ValidateImageSignature invokes it
both before verification and again inside the loop. That can add extra network calls and make
behavior inconsistent if the result differs between calls.

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[125-129]
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[164-178]
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[183-206]

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` calls `a.hasBundles(ctx)` multiple times, including inside a loop. `hasBundles` performs a remote `cosign.GetBundles` call, which can be slow and can fail transiently, leading to inconsistent code paths.
### Issue Context
The first `hasBundles` result determines whether signatures are fetched via `VerifyImageAttestations` (bundle mode) or `VerifyImageSignatures` (legacy). Re-checking inside the loop is unnecessary and risky.
### Fix Focus Areas
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[125-129]
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[157-215]
### Suggested approach
- Set `useBundles := a.hasBundles(ctx)` once at the start of `ValidateImageSignature`.
- Use `useBundles` for both selecting the verification call and for choosing the per-signature processing branch.
- (Optional) consider caching `useBundles` on the struct if other methods also need it, to avoid multiple remote calls across validation steps.

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



Remediation recommended

4. Double attestation verification 🐞 Bug ➹ Performance
Description
For bundle images, ValidateImageSignature uses VerifyImageAttestations to obtain image signature
attestations, and ValidateAttestationSignature also calls VerifyImageAttestations to obtain
attestations. Since ValidateImage runs both checks sequentially, bundle images pay for two remote
verification passes, increasing latency and flakiness.
Code

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

if a.hasBundles(ctx) {
+		// For v3 bundles, both image signatures and attestations are stored as
+		// "attestations" in the unified bundle format. So we use VerifyImageAttestations
+		// to extract image signatures from the bundle, even though it seems unintuitive.
+		// This is different from v2 where image signatures and attestations were separate.
+		//
+		// The certificate extraction requires different handling for bundles and
+		// should be addressed in future work to achieve full v2 parity.
opts.NewBundleFormat = true
opts.ClaimVerifier = cosign.IntotoSubjectClaimVerifier
sigs, _, err = client.VerifyImageAttestations(a.reference, &opts)
Evidence
ValidateImageSignature calls VerifyImageAttestations when bundles are present;
ValidateAttestationSignature always calls VerifyImageAttestations; and ValidateImage invokes both
validations sequentially. This yields two similar remote verification operations per bundle image.

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[164-175]
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[224-232]
internal/image/validate.go[77-85]

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

## Issue description
For bundle images, both image signature validation and attestation validation invoke `VerifyImageAttestations`, leading to duplicated remote verification work.
### Issue Context
`ValidateImage` calls `ValidateImageSignature` (unless skipped) and then `ValidateAttestationSignature`. In bundle mode, both can end up hitting `VerifyImageAttestations`.
### Fix Focus Areas
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[157-216]
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[220-241]
- internal/image/validate.go[77-92]
### Suggested approach
- Introduce a shared method on `ApplicationSnapshotImage` like `verifyAttestationsOnce(ctx) ([]cosignOCI.Signature, error)` that caches results for the lifetime of the struct.
- In bundle mode, have `ValidateImageSignature` reuse those layers and filter/extract image signatures from them instead of re-calling verification.
- Keep legacy v2 behavior unchanged.

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


5. Filter swallows parse errors 🐞 Bug ⛯ Reliability
Description
isImageSignatureAttestation converts any ProvenanceFromBundlePayload error into 'false' and only
logs at debug level. Because ProvenanceFromBundlePayload also extracts signatures/cert info, errors
reading Base64Signature/Cert/Chain can silently cause image signatures to be skipped or attestations
to be misclassified.
Code

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[R534-545]

+func isImageSignatureAttestation(sig cosignOCI.Signature) bool {
+	payload, err := sig.Payload()
+	if err != nil {
+		log.Debugf("Cannot read signature payload: %v", err)
+		return false
+	}
+
+	att, err := attestation.ProvenanceFromBundlePayload(sig, payload)
+	if err != nil {
+		log.Debugf("Cannot parse bundle attestation: %v", err)
+		return false
+	}
Evidence
Predicate classification currently depends on ProvenanceFromBundlePayload, which calls
createEntitySignatures and therefore signature.NewEntitySignature. NewEntitySignature can error
while reading signature/cert/chain. isImageSignatureAttestation suppresses these errors by returning
false, which drives filtering decisions in both ValidateImageSignature and
parseAttestationsFromBundles.

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[534-545]
internal/attestation/attestation.go[91-115]
internal/attestation/attestation.go[146-174]
internal/signature/signature.go[34-65]

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

## Issue description
`isImageSignatureAttestation` uses `attestation.ProvenanceFromBundlePayload` to determine predicateType, but that function also tries to build signature/cert metadata and can fail for reasons unrelated to predicate parsing. The current code treats any failure as &amp;amp;amp;quot;not an image signature&amp;amp;amp;quot;, which can silently drop/misclassify entries.
### Issue Context
This function is used for filtering in both `ValidateImageSignature` and `parseAttestationsFromBundles`, so misclassification affects output correctness.
### Fix Focus Areas
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[532-553]
- internal/attestation/attestation.go[91-175]
- internal/signature/signature.go[34-75]
### Suggested approach
- Add a lightweight helper that:
- unmarshals the DSSE envelope,
- base64-decodes the payload,
- unmarshals into an `in_toto.Statement` (or just reads `predicateType`),
- returns predicateType without touching signature/cert fields.
- Change `isImageSignatureAttestation` to use that helper.
- If predicate parsing fails, consider returning an error up the stack (or at minimum log at warn/error and avoid silently filtering).

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



Advisory comments

6. No bundle sig tests🐞 Bug ⛯ Reliability
Description
The new bundle image signature path (VerifyImageAttestations + isImageSignatureAttestation +
extractSignaturesFromBundle) is not covered by existing ValidateImageSignature tests, increasing the
chance of regressions in this complex filtering/extraction logic.
Code

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

if a.hasBundles(ctx) {
+		// For v3 bundles, both image signatures and attestations are stored as
+		// "attestations" in the unified bundle format. So we use VerifyImageAttestations
+		// to extract image signatures from the bundle, even though it seems unintuitive.
+		// This is different from v2 where image signatures and attestations were separate.
+		//
+		// The certificate extraction requires different handling for bundles and
+		// should be addressed in future work to achieve full v2 parity.
opts.NewBundleFormat = true
opts.ClaimVerifier = cosign.IntotoSubjectClaimVerifier
sigs, _, err = client.VerifyImageAttestations(a.reference, &opts)
Evidence
Existing tests for ValidateImageSignature mock only VerifyImageSignatures (legacy v2 path) and do
not set up bundle mode, so the newly added v3 bundle-specific logic is not exercised.

internal/evaluation_target/application_snapshot_image/application_snapshot_image_test.go[469-482]
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[164-205]

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 new v3 bundle signature handling logic is complex and currently untested at the `ValidateImageSignature` level.
### Issue Context
Current tests only mock `VerifyImageSignatures`, so they don&amp;amp;amp;#x27;t execute the bundle branch that calls `VerifyImageAttestations`, filters by predicate type, and extracts signatures from the DSSE envelope.
### Fix Focus Areas
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[157-216]
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[532-604]
- internal/evaluation_target/application_snapshot_image/application_snapshot_image_test.go[469-510]
### Suggested approach
- Refactor `hasBundles` decision to be injectable/cacheable so tests can force bundle mode without real registry calls.
- Add tests that:
- return a mix of bundle attestations (cosign/sign + provenance) from `VerifyImageAttestations` and assert correct filtering.
- ensure `extractSignaturesFromBundle` returns expected signatures when Base64Signature is empty vs populated.
- validate behavior when predicate parsing fails (should not silently misclassify).

ⓘ 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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go (1)

533-568: ⚠️ Potential issue | 🔴 Critical

Predicate type extraction parses wrong JSON level.

The DSSE envelope structure returned by sig.Payload() has predicateType inside the base64-decoded payload field, not at the top level. The current implementation will always return an empty string, causing isImageSignatureAttestation to return false for all attestations.

See parseAttestationsFromBundles (lines 310-321) for the correct structure handling.

Proposed fix
+import "encoding/base64"
+
 // extractPredicateType extracts the predicateType field from a JSON
-// payload lazily, i.e. without unmarshalling all the other fields
+// payload lazily from the base64-encoded in-toto statement inside the DSSE envelope
 func extractPredicateType(payload []byte) (string, error) {
-	var attestation struct {
-		PredicateType string `json:"predicateType"`
+	var envelope struct {
+		Payload string `json:"payload"`
 	}
-	if err := json.Unmarshal(payload, &attestation); err != nil {
+	if err := json.Unmarshal(payload, &envelope); err != nil {
 		return "", err
 	}
-	return attestation.PredicateType, nil
+	if envelope.Payload == "" {
+		return "", nil
+	}
+
+	decoded, err := base64.StdEncoding.DecodeString(envelope.Payload)
+	if err != nil {
+		return "", fmt.Errorf("decode base64 payload: %w", err)
+	}
+
+	var stmt struct {
+		PredicateType string `json:"predicateType"`
+	}
+	if err := json.Unmarshal(decoded, &stmt); err != nil {
+		return "", fmt.Errorf("parse in-toto statement: %w", err)
+	}
+	return stmt.PredicateType, nil
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@internal/evaluation_target/application_snapshot_image/application_snapshot_image.go`
around lines 533 - 568, The predicate extraction currently unmarshals the
top-level DSSE envelope instead of the inner base64-encoded payload; update
extractPredicateType to first unmarshal the DSSE envelope (matching the
structure returned by sig.Payload(), e.g. a struct with a "payload" string),
base64-decode that envelope.payload, then unmarshal the decoded bytes into a
struct containing PredicateType (`json:"predicateType"`) and return it; keep
hasPredicateType and isImageSignatureAttestation (which calls sig.Payload() and
uses cosignSignPredicateType) unchanged aside from relying on the corrected
extractPredicateType implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@internal/evaluation_target/application_snapshot_image/application_snapshot_image.go`:
- Around line 533-568: The predicate extraction currently unmarshals the
top-level DSSE envelope instead of the inner base64-encoded payload; update
extractPredicateType to first unmarshal the DSSE envelope (matching the
structure returned by sig.Payload(), e.g. a struct with a "payload" string),
base64-decode that envelope.payload, then unmarshal the decoded bytes into a
struct containing PredicateType (`json:"predicateType"`) and return it; keep
hasPredicateType and isImageSignatureAttestation (which calls sig.Payload() and
uses cosignSignPredicateType) unchanged aside from relying on the corrected
extractPredicateType implementation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 59cb6a2c-0b6f-4d4f-bb84-1a109826fd61

📥 Commits

Reviewing files that changed from the base of the PR and between a7ce061 and 8e59a62.

⛔ Files ignored due to path filters (1)
  • features/__snapshots__/task_validate_image.snap is excluded by !**/*.snap
📒 Files selected for processing (1)
  • internal/evaluation_target/application_snapshot_image/application_snapshot_image.go

Comment on lines +192 to +195
if !isImageSignatureAttestation(s) {
log.Debugf("Skipping non-image signature attestation")
continue
}
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. Bundle signature filtering broken 🐞 Bug ✓ Correctness

isImageSignatureAttestation() looks for predicateType directly in sig.Payload(), but for
bundles sig.Payload() is a DSSE envelope and predicateType exists only in the base64-decoded
embedded statement. This likely results in skipping all bundle image signatures (empty
image.signatures) and incorrectly including cosign/sign/v1 entries in attestations.
Agent Prompt
### Issue description
`isImageSignatureAttestation()` currently checks for `predicateType` by JSON-unmarshalling the raw bytes returned from `sig.Payload()`. For bundle entries, `sig.Payload()` is a DSSE envelope JSON containing `payloadType` and a base64-encoded `payload` (the embedded in-toto statement). The DSSE envelope itself does **not** contain `predicateType`, so the check misclassifies bundle entries and breaks signature/attestation filtering.

### Issue Context
- Bundle parsing already assumes `sig.Payload()` is DSSE JSON and uses `attestation.ProvenanceFromBundlePayload()` which base64-decodes the embedded statement.
- Filtering should follow the same model: decode the DSSE envelope, base64-decode its `payload`, then read `predicateType` from the embedded statement.

### Fix Focus Areas
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[533-568]
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[184-206]
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[289-304]
- internal/evaluation_target/application_snapshot_image/application_snapshot_image_test.go[1046-1078]

### Suggested approach
1. Replace `extractPredicateType(payload []byte)` usage for bundle filtering with logic that:
   - Unmarshals `sig.Payload()` into `cosign.AttestationPayload` (or DSSE envelope struct).
   - Base64-decodes `payload.PayLoad`.
   - Extracts `predicateType` from the decoded embedded JSON (your existing `extractPredicateType` can be used *on the decoded payload bytes*).
2. If the DSSE unmarshal/base64 decode fails, return false and emit a debug log that indicates the payload isn’t a DSSE envelope / can’t be decoded.
3. Add a unit test ensuring `isImageSignatureAttestation()` returns true for a bundle DSSE signature whose embedded statement’s predicateType is `https://sigstore.dev/cosign/sign/v1` (you can reuse `createBundleDSSESignature`).
4. Optionally add a higher-level test to ensure ValidateImageSignature(bundle path) populates `a.signatures` (may require controllable bundle detection).

ⓘ 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