Skip to content

Improve signature and attestation detail output for v3 style sigstore bundles#3162

Open
simonbaird wants to merge 5 commits into
conforma:mainfrom
simonbaird:v3-sig-output
Open

Improve signature and attestation detail output for v3 style sigstore bundles#3162
simonbaird wants to merge 5 commits into
conforma:mainfrom
simonbaird:v3-sig-output

Conversation

@simonbaird
Copy link
Copy Markdown
Member

@qodo-code-review
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown

coderabbitai Bot commented Mar 4, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: e369c289-501c-4703-a2e3-1215004b11e9

📥 Commits

Reviewing files that changed from the base of the PR and between bde8b84 and 05e06c1.

📒 Files selected for processing (1)
  • internal/evaluation_target/application_snapshot_image/application_snapshot_image.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/evaluation_target/application_snapshot_image/application_snapshot_image.go

📝 Walkthrough

Walkthrough

ValidateImageSignature becomes bundle-aware: it detects Sigstore bundles, uses bundle-mode verification, extracts image signatures from DSSE envelopes into EntitySignature objects, skips image-signature attestations during attestation parsing, updates logging, adds tests, and tweaks input.json file mode.

Changes

Bundle-aware image signature validation

Layer / File(s) Summary
Imports and bundle detection wiring
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go
Adds encoding/base64 import and detects bundle presence in ValidateImageSignature, switching to VerifyImageAttestations with NewBundleFormat when bundles are present.
Bundle signature handling and extraction
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go
In bundle mode, filters bundle entries with isImageSignatureAttestation, uses extractSignaturesFromBundle to produce EntitySignature objects from DSSE envelopes, and retains legacy non-bundle signature construction otherwise.
Attestation parsing and logging updates
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go
parseAttestationsFromBundles skips image-signature attestations and accumulates other attestations; adds TODO comments and updates debug logging to include discovered predicateType.
File permission tweak and tests
internal/evaluation_target/application_snapshot_image/application_snapshot_image.go, internal/evaluation_target/application_snapshot_image/application_snapshot_image_test.go
Updates input.json creation mode to 0o644 and adds unit tests: TestIsImageSignatureAttestation, TestExtractSignaturesFromBundle, plus helpers makeBundleSig* to build DSSE-backed signatures.

Sequence Diagram(s)

sequenceDiagram
  participant Validator as ValidateImageSignature
  participant Verifier as VerifyImageAttestations
  participant Cosign as Cosign In-toto Verifier
  participant BundleParser as parseAttestationsFromBundles
  participant SigExtractor as extractSignaturesFromBundle

  Validator->>Verifier: request verification (opts.NewBundleFormat when bundles)
  Verifier->>Cosign: verify attestations (in‑toto)
  Cosign-->>Verifier: return verified DSSE envelopes
  Verifier->>BundleParser: provide bundle entries
  BundleParser-->>Verifier: filter entries (skip image-signature attestations)
  alt image-signature attestations present
    BundleParser->>SigExtractor: pass DSSE envelope
    SigExtractor-->>Validator: EntitySignature objects (from DSSE signatures)
  else other attestations
    BundleParser-->>Validator: parsed attestations (provenance, etc.)
  end
  Validator->>Validator: fallback to legacy VerifyImageSignatures when not bundles
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: improving signature and attestation output specifically for v3 style sigstore bundles, which is reflected throughout the code changes.
Description check ✅ Passed The description provides a reference to the related issue (EC-1690) which connects the PR to its requirements, meeting the minimal expectation for the description check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@simonbaird
Copy link
Copy Markdown
Member Author

@SequeI FYI, wdyt?

{
"keyid": "",
"sig": ""
"sig": "MEUCIBZc+dmgTn8SCx30h9yvCOjsBwj1+aZX0gW53c7TeyuSAiEAp4zWGNHMrjql9NFl/fCmFXnJkgDkOqbN5n7H7mw6aqI="
Copy link
Copy Markdown
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
Copy Markdown

@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
Copy Markdown

@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
Copy Markdown
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 simonbaird marked this pull request as ready for review March 5, 2026 18:33
@qodo-code-review
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown

@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

Copy link
Copy Markdown
Contributor

@st3penta st3penta left a comment

Choose a reason for hiding this comment

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

To be honest, this PR seems a bit experimental and with several TODOs left.
Maybe we should aim to gain a better understanding of the changes in cosign v3 before merging this?

It would be nice to have an acceptance test specifically for these changes.

Maybe add some references about the changes in the v3 in the PR description?

I also find the naming a bit confusing, but it might be me.

@simonbaird
Copy link
Copy Markdown
Member Author

I'm planning to come back to this and fix it up a bit. Moving to draft in the meantime.

@simonbaird simonbaird marked this pull request as draft March 18, 2026 03:40
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>
@fullsend-ai-review
Copy link
Copy Markdown

fullsend-ai-review Bot commented Jun 3, 2026

Review

Findings

Low

  • [logic-error] internal/evaluation_target/application_snapshot_image/application_snapshot_image.go — In isImageSignatureAttestation, when JSON unmarshalling fails or payloadType is empty, the function returns true (treating the entry as an image signature). This is documented design intent for simple-signing payloads, and upstream cosign verification provides cryptographic guarantees on payload integrity. However, if a valid attestation had a malformed DSSE envelope that still parsed as JSON, it would be silently filtered out of parseAttestationsFromBundles. The practical risk is low given upstream verification, but consider returning false for the ambiguous cases to let downstream parsing decide.

  • [pattern-violation] internal/evaluation_target/application_snapshot_image/application_snapshot_image.goextractSignaturesFromBundle duplicates the signature-extraction logic from createEntitySignatures in internal/attestation/attestation.go. Both iterate over attestationPayload.Signatures, copy the base EntitySignature, and conditionally fill Signature and KeyID. If one copy is patched (e.g., to handle a new field), the other may be forgotten. Consider reusing or extracting the shared logic.

Info

  • [sub-agent-failure] N/A — The intent-coherence and style-conventions sub-agents could not be dispatched (model unavailable on deployment). These are non-critical review dimensions and do not affect the safety assessment of this change.
Previous run

Review

Findings

Medium

  • [missing-test] internal/evaluation_target/application_snapshot_image/application_snapshot_image_test.go — No integration-level test exercises the bundle path of ValidateImageSignature. All existing tests for this method mock HasBundles to return false. The new bundle-specific logic (filtering with isImageSignatureAttestation, extracting via extractSignaturesFromBundle) is only tested at the unit level for the helper functions. There is no test that verifies the overall ValidateImageSignature method correctly collects signatures when hasBundles returns true, nor a test verifying the interplay between ValidateImageSignature and parseAttestationsFromBundles — specifically that image signature attestations appear in a.signatures and NOT in a.attestations.

Low

  • [pattern-violation] internal/evaluation_target/application_snapshot_image/application_snapshot_image.goextractSignaturesFromBundle near-duplicates createEntitySignatures in internal/attestation/attestation.go (lines 92-116). Both create a base EntitySignature via NewEntitySignature, then iterate over cosign.Signatures to fill in Signature and KeyID when empty. If one is updated and the other is not, they will diverge. The TODO comment acknowledges this, but the duplication is a drift risk.

  • [edge-case] internal/evaluation_target/application_snapshot_image/application_snapshot_image.go — In extractSignaturesFromBundle, esNew := es performs a shallow copy of EntitySignature. The Metadata (map[string]string) and Chain ([]string) fields share underlying memory across all copies in the results slice. No current code path mutates these fields after extraction, but this is a fragile invariant. Consider maps.Clone(es.Metadata) if the struct evolves.

  • [naming-convention] internal/evaluation_target/application_snapshot_image/application_snapshot_image.go — Minor grammar issues in new comments: "This will appears" (should be "will appear") and "This does seems confusing" (should be "does seem"). Also, some comments use casual uncertain language ("I'm not 100% sure we're doing the right thing here") which is atypical of the codebase's generally professional comment style.

Previous run (2)

Review

Findings

Low

  • [missing-test] internal/evaluation_target/application_snapshot_image/application_snapshot_image.go — The new functions isImageSignatureAttestation and extractSignaturesFromBundle lack dedicated unit tests. The existing TestParseAttestationsFromBundles only uses SLSA provenance predicate types and does not exercise the new filtering logic for cosign/sign/v1. TestValidateImageSignatureClaims only tests the non-bundle path. Key scenarios to cover: DSSE envelope with cosign/sign/v1 predicate, DSSE envelope with other predicate types, non-DSSE payloads (simple signing), and malformed base64 inner payloads.

  • [pattern-violation] internal/evaluation_target/application_snapshot_image/application_snapshot_image.goisImageSignatureAttestation reads the signature via sig.Payload() while extractSignaturesFromBundle reads via sig.Uncompressed(). The existing parseAttestationsFromBundles also uses sig.Payload(). Using different accessors for the same data is inconsistent and could diverge with a different oci.Signature implementation. Consider using Payload() consistently.

  • [edge-case] internal/evaluation_target/application_snapshot_image/application_snapshot_image.go — In isImageSignatureAttestation, when base64 decoding or inner JSON parsing fails on a valid DSSE envelope, the function returns false. This creates asymmetric behavior: in ValidateImageSignature the entry is skipped (not treated as an image signature), while in parseAttestationsFromBundles it passes the filter and is processed as an attestation. In practice, downstream parsing would also fail on such malformed payloads, but the asymmetry is worth noting.

  • [naming-convention] internal/evaluation_target/application_snapshot_image/application_snapshot_image.go — Typo in TODO comment: "thi shere" should be "this here".

Previous run (3)

Review

Findings

Medium

  • [missing-test] internal/evaluation_target/application_snapshot_image/application_snapshot_image.go — No unit tests were added for the new isImageSignatureAttestation and extractSignaturesFromBundle functions. These contain non-trivial logic: base64 decoding, JSON parsing of nested DSSE envelopes, predicate type matching, and distinct error-path return values. The snapshot test covers one happy path but does not exercise edge cases (non-DSSE payloads, base64 decode failures, bundles with multiple signatures, fallback from empty oci.Signature fields to cosign.Signature values).

  • [complexity-ratio] internal/evaluation_target/application_snapshot_image/application_snapshot_image.go — Multiple TODO and uncertainty comments throughout the new code indicate this is exploratory/incremental work: "I'm not 100% sure we're doing the right thing here", "the above comment might be stale and/or wrong", missing certificate extraction acknowledged but not tracked with a follow-up issue. Consider creating a tracked issue for the known gaps (certificate extraction for v2 parity, syntax validation in the bundle path) so they don't get lost.

Low

  • [edge-case] internal/evaluation_target/application_snapshot_image/application_snapshot_image.goisImageSignatureAttestation uses sig.Payload() while extractSignaturesFromBundle uses sig.Uncompressed(). These return the same data for current types but have different contracts. Using a consistent access method (or parsing once and passing through) would be more robust. See also: [edge-case] error handling finding at this location.

  • [edge-case] internal/evaluation_target/application_snapshot_image/application_snapshot_image.go — In isImageSignatureAttestation, the return value on error is inconsistent: JSON unmarshal failure returns true (treat as image signature), but base64 decode failure returns false (treat as attestation). In practice this is mitigated because parseAttestationsFromBundles already skips entries with payload errors and all entries have passed cosign verification upstream, but the asymmetry could cause confusion if the upstream behavior changes.

  • [pattern-inconsistency] internal/evaluation_target/application_snapshot_image/application_snapshot_image.go — Comment typos: "This will appears" → "This will appear", "This does seems" → "This does seem", "thi shere" → "this here".

  • [pattern-inconsistency] internal/evaluation_target/application_snapshot_image/application_snapshot_image.go — The 06440o644 change is correct modern Go style but is an isolated fix; the codebase uses 0644 in ~40+ other locations. Consider a separate cleanup PR for consistency.

Info

  • [naming-convention] internal/evaluation_target/application_snapshot_image/application_snapshot_image.go — The constant cosignSignPredicateType is unexported and in a different package from the existing Predicate* constants in internal/attestation/, so the different naming pattern is acceptable, though predicateCosignSign would be marginally more consistent.

  • [edge-case] internal/evaluation_target/application_snapshot_image/application_snapshot_image.goextractSignaturesFromBundle returns nil, nil when attestationPayload.Signatures is empty. This is consistent with createEntitySignatures in attestation.go but silently produces no signatures. The caller appends the result, so no crash, but a bundle with zero signatures would pass without notice.

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jun 3, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 4, 2026

Codecov Report

❌ Patch coverage is 79.36508% with 13 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ation_snapshot_image/application_snapshot_image.go 79.36% 13 Missing ⚠️
Flag Coverage Δ
acceptance 53.46% <57.14%> (-0.02%) ⬇️
generative 16.87% <0.00%> (-0.14%) ⬇️
integration 27.80% <0.00%> (-0.21%) ⬇️
unit 69.05% <74.60%> (-0.12%) ⬇️

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

Files with missing lines Coverage Δ
...ation_snapshot_image/application_snapshot_image.go 84.25% <79.36%> (-1.57%) ⬇️

... and 5 files with indirect coverage changes

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

@fullsend-ai-review fullsend-ai-review Bot removed the requires-manual-review Review requires human judgment label Jun 4, 2026
@fullsend-ai-review fullsend-ai-review Bot added the ready-for-merge All reviewers approved — ready to merge label Jun 4, 2026
simonbaird and others added 3 commits June 4, 2026 12:48
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>
(Suggested by @fullsend-ai-review.)

Ref: https://issues.redhat.com/browse/EC-1690
Co-authored-by: Claude Code <noreply@anthropic.com>
(Suggested by @fullsend-ai-review.)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions Bot added size: XL and removed size: L labels Jun 4, 2026
@simonbaird simonbaird marked this pull request as ready for review June 4, 2026 16:50
@simonbaird simonbaird changed the title Improve signature and attestation detail output for sigstore bundles Improve signature and attestation detail output for v3 style sigstore bundles Jun 4, 2026
@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed ready-for-merge All reviewers approved — ready to merge labels Jun 4, 2026
Co-authored-by: Claude Code <noreply@anthropic.com>
@simonbaird
Copy link
Copy Markdown
Member Author

I think it's probably good enough to merge now.

@fullsend-ai-review fullsend-ai-review Bot added ready-for-merge All reviewers approved — ready to merge and removed requires-manual-review Review requires human judgment labels Jun 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-merge All reviewers approved — ready to merge size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants