Skip to content

fix: Classify BuildKit attestations as accessories#85

Open
bupd wants to merge 6 commits intomainfrom
fix-buildkitd
Open

fix: Classify BuildKit attestations as accessories#85
bupd wants to merge 6 commits intomainfrom
fix-buildkitd

Conversation

@bupd
Copy link
Member

@bupd bupd commented Mar 18, 2026

Summary

  • refactor artifact manifest abstraction to use a registry-based handler model while keeping existing manifest support intact
  • classify BuildKit attestation manifests as attestation.buildkit accessories instead of unknown/unknown index children
  • expose the new accessory type in the portal so attestations render in the existing accessory tree

Related Issues

Type of Change

  • Bug fix (fix:)
  • New feature (feat:)
  • Breaking change (feat!: / fix!:)
  • Documentation (docs:)
  • Refactoring (refactor:)
  • CI/CD or build changes (ci: / build:)
  • Dependencies update (chore:)
  • Tests (test:)

Release Notes

  • Harbor now classifies BuildKit attestation manifests under multi-arch indexes as accessories, so they no longer appear as unknown/unknown child rows.

Testing

  • Unit tests added/updated
  • Manual testing performed

Checklist

  • PR title follows Conventional Commits format
  • Commits are signed off (git commit -s)
  • No new warnings introduced

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for BuildKit attestations as artifact accessories, enabling tracking of provenance and SBOM data
    • Implemented intelligent accessory inheritance for Cosign signatures from parent artifacts
    • Refactored metadata extraction to support multiple artifact manifest formats with improved flexibility
  • Tests

    • Added comprehensive test coverage for BuildKit attestation handling, legacy reference filtering, and accessory population scenarios

Aloui-Ikram and others added 5 commits March 17, 2026 23:17
Signed-off-by: Aloui-Ikram <ikramaloui145@example.com>
Signed-off-by: bupd <bupdprasanth@gmail.com>
- Added `InheritedAccessories` to the Artifact model to prevent mixing inherited parent signatures with direct artifact accessories.
- Updated `populateAccessories` to store parent signatures in the new field to maintain data integrity.
- Fixed `ListAccessories` handler to properly parse multi-type UI queries using `strings.Contains` and serve inherited signatures when requested.

Signed-off-by: Aloui-Ikram <ikramaloui145@example.com>
Signed-off-by: bupd <bupdprasanth@gmail.com>
The new populateAccessories logic calls artMgr.ListReferences whenever
an artifact has no direct cosign signature. Several existing tests
(TestList, TestListWithLatest, TestDeleteDeeply, TestWalk) mock
accMgr.List to return empty accessories but did not set up a
ListReferences expectation, causing the mock to panic.

Add the missing mock expectations returning an empty reference slice
so the new code path exits cleanly.

Signed-off-by: bupd <bupdprasanth@gmail.com>
Signed-off-by: bupd <bupdprasanth@gmail.com>
Signed-off-by: bupd <bupdprasanth@gmail.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

This change introduces a pluggable manifest abstraction registry system to extract metadata from different OCI manifest types (v1, v2, index). It adds a new BuildKit attestation accessory type detected from OCI index manifests. Legacy BuildKit attestation references are filtered during artifact assembly and synthesized as accessories during population. The artifact model gains fields for accessory candidates and inherited accessories.

Changes

Cohort / File(s) Summary
Manifest Abstraction Registry
src/controller/artifact/manifest_abstractor.go
Introduces thread-safe registry system with factory pattern for media-type-specific manifest abstractors; enables pluggable delegation of metadata extraction.
Manifest Abstractor Implementations
src/controller/artifact/manifest_abstractor_v1.go, src/controller/artifact/manifest_abstractor_v2.go, src/controller/artifact/manifest_abstractor_index.go
Adds three concrete abstractor implementations for v1, v2, and index manifests; index abstractor includes BuildKit attestation detection via descriptor annotations and manifest inspection.
Refactored Core Abstraction
src/controller/artifact/abstractor.go
Replaces explicit per-manifest handling with delegated calls to registered manifest abstractors; removes inlined v1/v2/index branching logic (140 lines reduced).
BuildKit Reference and Accessory Handling
src/controller/artifact/controller.go
Adds filtering of legacy BuildKit attestation references during assembly; introduces legacy BuildKit attestation synthesis in populateAccessories; includes inherited Cosign signature handling for OCI index children.
Artifact Model Extensions
src/pkg/artifact/model.go
Adds AccessoryCandidate type with fields for artifact relationships and sub-artifact metadata; adds AccessoryCandidates field to Artifact.
Accessory Model & Type
src/pkg/accessory/model/accessory.go, src/pkg/accessory/model/attestation/attestation.go
Defines TypeBuildKitAttestation constant; introduces BuildKitAttestation struct with Kind and IsHard methods; registers type in init for system recognition.
Artifact Controller Updates
src/controller/artifact/model.go
Adds InheritedAccessories field (display-only) to Artifact struct for inherited accessory data.
API Handler
src/server/v2.0/handler/artifact.go
Updates ListAccessories to fetch inherited accessories when no direct accessories exist and Cosign signature is requested; merges inherited and direct accessories.
Test Coverage
src/controller/artifact/abstractor_test.go, src/controller/artifact/controller_test.go, src/pkg/accessory/model/attestation/attestation_test.go
Adds tests for OCI index with BuildKit attestation metadata extraction; validates legacy reference filtering and accessory synthesis; verifies BuildKitAttestation type behavior.
Package Initialization
src/core/main.go, src/jobservice/main.go
Adds side-effect imports for attestation model to trigger init-time registration.
Frontend Support
src/portal/src/app/base/project/repository/artifact/artifact.ts
Adds ATTESTATION member to AccessoryType enum.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Handler as ListAccessories Handler
    participant Controller as Artifact Controller
    participant Registry as Manifest Abstractor<br/>Registry
    participant Abstractor as Index Manifest<br/>Abstractor
    participant Accessory as Accessory Model

    Client->>Handler: List Accessories
    Handler->>Controller: GetByDigest with Accessory Details
    
    Controller->>Controller: AbstractMetadata
    Controller->>Registry: getManifestAbstractor(mediaType)
    Registry-->>Controller: indexManifestAbstractor
    
    Controller->>Abstractor: AbstractMetadata(context, artifact, content)
    Abstractor->>Abstractor: Parse OCI Index Manifest
    
    loop For Each Manifest in Index
        Abstractor->>Abstractor: Check if BuildKit Attestation<br/>(via descriptor annotations)
        alt Is BuildKit Attestation
            Abstractor->>Accessory: Create AccessoryCandidate<br/>(type: attestation.buildkit)
            Accessory-->>Abstractor: AccessoryCandidate
            Abstractor->>Abstractor: Aggregate Size & Add to Candidates
        else Regular Child
            Abstractor->>Abstractor: Fetch Child Artifact<br/>Create Reference
            Abstractor->>Abstractor: Add Reference
        end
    end
    
    Abstractor-->>Controller: Artifact with Candidates & References
    Controller->>Controller: Ensure Accessory Candidates<br/>in Transaction
    Controller-->>Handler: Artifact with Accessories
    Handler-->>Client: Accessory List
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: classifying BuildKit attestations as accessories, which is the primary objective of this PR.
Description check ✅ Passed The description includes all required template sections: Summary, Related Issues, Type of Change (with multiple boxes checked), Release Notes, Testing checklist, and final checklist items. All sections are filled appropriately.

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

Important

Merge conflicts detected (Beta)

  • Resolve merge conflict in branch fix-buildkitd
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-buildkitd
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions
Copy link

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@github-actions
Copy link

Preview images for this PR are available in registry.goharbor.io/harbor-next with tag pr-85.

  • registry.goharbor.io/harbor-next/harbor-core:pr-85
  • registry.goharbor.io/harbor-next/harbor-jobservice:pr-85
  • registry.goharbor.io/harbor-next/harbor-registryctl:pr-85
  • registry.goharbor.io/harbor-next/harbor-exporter:pr-85
  • registry.goharbor.io/harbor-next/harbor-portal:pr-85
  • registry.goharbor.io/harbor-next/harbor-registry:pr-85
  • registry.goharbor.io/harbor-next/harbor-trivy-adapter:pr-85

Verify a preview image:

cosign verify \
  --certificate-identity-regexp="https://github.com/container-registry/harbor-next/.github/workflows/pr-ci.yml@.*" \
  --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
  registry.goharbor.io/harbor-next/harbor-core:pr-85

Verify SBOM attestation:

cosign verify-attestation \
  --certificate-identity-regexp="https://github.com/container-registry/harbor-next/.github/workflows/pr-ci.yml@.*" \
  --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
  --type spdxjson \
  registry.goharbor.io/harbor-next/harbor-core:pr-85

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

5 issues found across 17 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/controller/artifact/manifest_abstractor_v1.go">

<violation number="1" location="src/controller/artifact/manifest_abstractor_v1.go:62">
P1: Register `schema1.MediaTypeManifest` here too; otherwise unsigned Docker v1 manifests remain unsupported after the refactor.</violation>
</file>

<file name="src/controller/artifact/controller.go">

<violation number="1" location="src/controller/artifact/controller.go:810">
P2: This inherited-signature lookup adds extra per-artifact queries to the default artifact GET/LIST path, but `InheritedAccessories` are not returned there. It creates an avoidable N+1 regression for artifact listing/detail responses.</violation>
</file>

<file name="src/controller/artifact/manifest_abstractor.go">

<violation number="1" location="src/controller/artifact/manifest_abstractor.go:41">
P2: Make alias registration atomic; a duplicate later in `mediaTypes` currently leaves earlier aliases registered and can cause some manifest media types to fail with `unsupported manifest media type`.</violation>
</file>

<file name="src/server/v2.0/handler/artifact.go">

<violation number="1" location="src/server/v2.0/handler/artifact.go:392">
P2: Checking inheritance only when `len(accs) == 0` misses inherited cosign signatures whenever the same query also returns a direct SBOM accessory.</violation>
</file>

<file name="src/controller/artifact/manifest_abstractor_index.go">

<violation number="1" location="src/controller/artifact/manifest_abstractor_index.go:134">
P2: Propagate attestation-inspection errors instead of silently downgrading the manifest to a normal child reference.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

}

func init() {
mediaTypes := []string{"", "application/json", schema1.MediaTypeSignedManifest}
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 18, 2026

Choose a reason for hiding this comment

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

P1: Register schema1.MediaTypeManifest here too; otherwise unsigned Docker v1 manifests remain unsupported after the refactor.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/controller/artifact/manifest_abstractor_v1.go, line 62:

<comment>Register `schema1.MediaTypeManifest` here too; otherwise unsigned Docker v1 manifests remain unsupported after the refactor.</comment>

<file context>
@@ -0,0 +1,68 @@
+}
+
+func init() {
+	mediaTypes := []string{"", "application/json", schema1.MediaTypeSignedManifest}
+	if err := registerManifestAbstractor(func(a *abstractor) manifestAbstractor {
+		return &manifestV1Abstractor{blobMgr: a.blobMgr}
</file context>
Fix with Cubic

manifestAbstractorLock.Lock()
defer manifestAbstractorLock.Unlock()

for _, mediaType := range mediaTypes {
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 18, 2026

Choose a reason for hiding this comment

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

P2: Make alias registration atomic; a duplicate later in mediaTypes currently leaves earlier aliases registered and can cause some manifest media types to fail with unsupported manifest media type.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/controller/artifact/manifest_abstractor.go, line 41:

<comment>Make alias registration atomic; a duplicate later in `mediaTypes` currently leaves earlier aliases registered and can cause some manifest media types to fail with `unsupported manifest media type`.</comment>

<file context>
@@ -0,0 +1,61 @@
+	manifestAbstractorLock.Lock()
+	defer manifestAbstractorLock.Unlock()
+
+	for _, mediaType := range mediaTypes {
+		if _, ok := manifestAbstractorFactories[mediaType]; ok {
+			return fmt.Errorf("manifest abstractor for media type %s already exists", mediaType)
</file context>
Fix with Cubic

}

// If no direct signature found, check for Cosign signatures inherited from a parent OCI index.
if len(accs) == 0 {
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 18, 2026

Choose a reason for hiding this comment

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

P2: Checking inheritance only when len(accs) == 0 misses inherited cosign signatures whenever the same query also returns a direct SBOM accessory.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/server/v2.0/handler/artifact.go, line 392:

<comment>Checking inheritance only when `len(accs) == 0` misses inherited cosign signatures whenever the same query also returns a direct SBOM accessory.</comment>

<file context>
@@ -386,6 +388,27 @@ func (a *artifactAPI) ListAccessories(ctx context.Context, params operation.List
 	}
 
+	// If no direct signature found, check for Cosign signatures inherited from a parent OCI index.
+	if len(accs) == 0 {
+		// Use strings.Contains because the UI sends a list of types (e.g. type={signature.cosign ...})
+		if strings.Contains(lib.StringValue(params.Q), "signature.cosign") {
</file context>
Fix with Cubic

}

func (m *indexManifestAbstractor) isBuildKitAttestationManifest(repository, digest string) bool {
manifest, _, err := m.regCli.PullManifest(repository, digest)
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 18, 2026

Choose a reason for hiding this comment

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

P2: Propagate attestation-inspection errors instead of silently downgrading the manifest to a normal child reference.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/controller/artifact/manifest_abstractor_index.go, line 134:

<comment>Propagate attestation-inspection errors instead of silently downgrading the manifest to a normal child reference.</comment>

<file context>
@@ -0,0 +1,168 @@
+}
+
+func (m *indexManifestAbstractor) isBuildKitAttestationManifest(repository, digest string) bool {
+	manifest, _, err := m.regCli.PullManifest(repository, digest)
+	if err != nil {
+		return false
</file context>
Fix with Cubic

Signed-off-by: bupd <bupdprasanth@gmail.com>
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: 2

🧹 Nitpick comments (2)
src/pkg/accessory/model/attestation/attestation_test.go (1)

22-27: Fail fast on accessory construction errors in SetupSuite.

Discarding the model.New error here turns a registration/constructor regression into a nil-interface panic across the suite, which makes the failure much harder to diagnose.

💡 Proposed change
 func (suite *AttestationTestSuite) SetupSuite() {
 	suite.digest = suite.DigestString()
 	suite.subDigest = suite.DigestString()
-	suite.accessory, _ = model.New(model.TypeBuildKitAttestation, model.AccessoryData{
+	var err error
+	suite.accessory, err = model.New(model.TypeBuildKitAttestation, model.AccessoryData{
 		ArtifactID:        1,
 		SubArtifactDigest: suite.subDigest,
 		Size:              1234,
 		Digest:            suite.digest,
 	})
+	suite.Require().NoError(err)
+	suite.Require().NotNil(suite.accessory)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pkg/accessory/model/attestation/attestation_test.go` around lines 22 -
27, In SetupSuite, don't ignore the error returned by model.New; capture the
returned error from model.New(model.TypeBuildKitAttestation, ...) and fail fast
when it is non-nil (e.g., using suite.Require().NoError(err) or
suite.T().Fatalf) before assigning suite.accessory, so a
constructor/registration regression surfaces immediately instead of causing
nil-interface panics later; update the SetupSuite code around the model.New call
to check err and handle it accordingly.
src/controller/artifact/controller.go (1)

817-841: Consider handling multiple parent Cosign signatures.

The current implementation returns immediately after finding the first Cosign signature from any parent (Line 837). If a child artifact is referenced by multiple parent indexes, each potentially having different Cosign signatures, only the first one found is added to InheritedAccessories.

This may be intentional to avoid UI clutter, but if completeness is desired, consider collecting all parent Cosign signatures:

♻️ Optional: Collect all inherited Cosign signatures
 	if !hasCosign {
 		parents, err := c.artMgr.ListReferences(ctx, &q.Query{
 			Keywords: map[string]any{"ChildID": art.ID},
 		})
 		if err != nil {
 			log.Errorf("failed to list parent references of artifact %d: %v", art.ID, err)
 			return
 		}
 		if len(parents) == 0 {
 			return
 		}
+		seen := map[int64]struct{}{}
 		for _, p := range parents {
 			parentAccs, err := c.accessoryMgr.List(ctx, q.New(q.KeyWords{"SubjectArtifactID": p.ParentID}))
 			if err != nil {
 				continue
 			}
 			for _, pAcc := range parentAccs {
 				if pAcc.GetData().Type == accessorymodel.TypeCosignSignature {
+					if _, ok := seen[pAcc.GetData().ArtifactID]; ok {
+						continue
+					}
+					seen[pAcc.GetData().ArtifactID] = struct{}{}
 					art.InheritedAccessories = append(art.InheritedAccessories, pAcc)
-					return
 				}
 			}
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/controller/artifact/controller.go` around lines 817 - 841, The code
returns after adding the first parent cosign signature, so other parent
signatures may be skipped; modify the block where hasCosign is false (using
c.artMgr.ListReferences and c.accessoryMgr.List) to continue scanning all
parents instead of returning after the first match: for each parent (from
ListReferences) iterate parentAccs and for every accessory whose GetData().Type
== accessorymodel.TypeCosignSignature append it to art.InheritedAccessories, and
only return (or exit) after all parents have been processed; ensure you remove
the early return inside the inner loop so multiple cosign signatures from
different parents are collected.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/controller/artifact/manifest_abstractor_index.go`:
- Around line 96-110: The code silently treats transient probe/pull/JSON errors
from isBuildKitAttestationManifest as "false" and falls back to normal
child-reference handling; change isBuildKitAttestationManifest to surface errors
(e.g., return (bool, error) or add a variant that returns an error) and update
callers (including toBuildKitAttestationCandidate and the other call site around
lines 133-159) to check the error and return it (instead of returning nil,nil
and downgrading the descriptor) so probe failures are propagated rather than
reclassifying attestations.

In `@src/server/v2.0/handler/artifact.go`:
- Around line 391-410: The current logic gates inherited Cosign lookup on
len(accs) == 0 which incorrectly prevents adding an inherited Cosign when other
direct accessories exist; change the condition to check whether a direct Cosign
is already present (e.g., scan accs for any acc.GetData().Type ==
accessorymodel.TypeCosignSignature) and only skip inheritance if a direct Cosign
exists; if no direct Cosign is found, call a.artCtl.Get(ctx, art.ID,
&artifact.Option{WithAccessory: true}) to retrieve artWithAccs, scan
artWithAccs.Accessories and artWithAccs.InheritedAccessories for
TypeCosignSignature, append any inherited Cosign to accs, and update total
appropriately (and ensure this merge happens before final pagination/counting so
the inherited item is included in the paged response).

---

Nitpick comments:
In `@src/controller/artifact/controller.go`:
- Around line 817-841: The code returns after adding the first parent cosign
signature, so other parent signatures may be skipped; modify the block where
hasCosign is false (using c.artMgr.ListReferences and c.accessoryMgr.List) to
continue scanning all parents instead of returning after the first match: for
each parent (from ListReferences) iterate parentAccs and for every accessory
whose GetData().Type == accessorymodel.TypeCosignSignature append it to
art.InheritedAccessories, and only return (or exit) after all parents have been
processed; ensure you remove the early return inside the inner loop so multiple
cosign signatures from different parents are collected.

In `@src/pkg/accessory/model/attestation/attestation_test.go`:
- Around line 22-27: In SetupSuite, don't ignore the error returned by
model.New; capture the returned error from
model.New(model.TypeBuildKitAttestation, ...) and fail fast when it is non-nil
(e.g., using suite.Require().NoError(err) or suite.T().Fatalf) before assigning
suite.accessory, so a constructor/registration regression surfaces immediately
instead of causing nil-interface panics later; update the SetupSuite code around
the model.New call to check err and handle it accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: de7c1f62-173c-49d7-90bf-c51d1a450e6d

📥 Commits

Reviewing files that changed from the base of the PR and between c6f9e37 and b13e43f.

📒 Files selected for processing (17)
  • src/controller/artifact/abstractor.go
  • src/controller/artifact/abstractor_test.go
  • src/controller/artifact/controller.go
  • src/controller/artifact/controller_test.go
  • src/controller/artifact/manifest_abstractor.go
  • src/controller/artifact/manifest_abstractor_index.go
  • src/controller/artifact/manifest_abstractor_v1.go
  • src/controller/artifact/manifest_abstractor_v2.go
  • src/controller/artifact/model.go
  • src/core/main.go
  • src/jobservice/main.go
  • src/pkg/accessory/model/accessory.go
  • src/pkg/accessory/model/attestation/attestation.go
  • src/pkg/accessory/model/attestation/attestation_test.go
  • src/pkg/artifact/model.go
  • src/portal/src/app/base/project/repository/artifact/artifact.ts
  • src/server/v2.0/handler/artifact.go

Comment on lines +96 to +110
func (m *indexManifestAbstractor) toBuildKitAttestationCandidate(ctx context.Context, repository string, descriptor v1.Descriptor) (*pkgartifact.AccessoryCandidate, error) {
if descriptor.MediaType != v1.MediaTypeImageManifest && descriptor.MediaType != schema2.MediaTypeManifest {
return nil, nil
}
if descriptor.Annotations[buildKitReferenceTypeAnnotation] != buildKitAttestationManifestType {
return nil, nil
}

targetDigest := descriptor.Annotations[buildKitReferenceDigestAnnotation]
if targetDigest == "" {
return nil, nil
}

if !m.isBuildKitAttestationManifest(repository, descriptor.Digest.String()) {
return nil, nil
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't silently downgrade attestation descriptors on probe failures.

isBuildKitAttestationManifest currently returns false for pull/payload/JSON errors, and the caller then falls back to the normal child-reference path. A transient registry failure will therefore reclassify the attestation as a regular child instead of surfacing the failure, which makes this fix nondeterministic.

💡 Suggested change
-	if !m.isBuildKitAttestationManifest(repository, descriptor.Digest.String()) {
-		return nil, nil
-	}
+	isAttestation, err := m.isBuildKitAttestationManifest(repository, descriptor.Digest.String())
+	if err != nil {
+		return nil, err
+	}
+	if !isAttestation {
+		return nil, nil
+	}
@@
-func (m *indexManifestAbstractor) isBuildKitAttestationManifest(repository, digest string) bool {
+func (m *indexManifestAbstractor) isBuildKitAttestationManifest(repository, digest string) (bool, error) {
 	manifest, _, err := m.regCli.PullManifest(repository, digest)
 	if err != nil {
-		return false
+		return false, err
 	}
 
 	mediaType, content, err := manifest.Payload()
 	if err != nil {
-		return false
+		return false, err
 	}
 	if mediaType != v1.MediaTypeImageManifest && mediaType != schema2.MediaTypeManifest {
-		return false
+		return false, nil
 	}
 
 	imageManifest := &v1.Manifest{}
 	if err := json.Unmarshal(content, imageManifest); err != nil {
-		return false
+		return false, err
 	}
 
 	for _, layer := range imageManifest.Layers {
 		if layer.MediaType == inTotoLayerMediaType {
-			return true
+			return true, nil
 		}
 	}
 
-	return false
+	return false, nil
 }

Also applies to: 133-159

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

In `@src/controller/artifact/manifest_abstractor_index.go` around lines 96 - 110,
The code silently treats transient probe/pull/JSON errors from
isBuildKitAttestationManifest as "false" and falls back to normal
child-reference handling; change isBuildKitAttestationManifest to surface errors
(e.g., return (bool, error) or add a variant that returns an error) and update
callers (including toBuildKitAttestationCandidate and the other call site around
lines 133-159) to check the error and return it (instead of returning nil,nil
and downgrading the descriptor) so probe failures are propagated rather than
reclassifying attestations.

Comment on lines +391 to +410
// If no direct signature found, check for Cosign signatures inherited from a parent OCI index.
if len(accs) == 0 {
// Use strings.Contains because the UI sends a list of types (e.g. type={signature.cosign ...})
if strings.Contains(lib.StringValue(params.Q), "signature.cosign") {
artWithAccs, err := a.artCtl.Get(ctx, art.ID, &artifact.Option{WithAccessory: true})
if err != nil {
log.Warningf("failed to get artifact %d with accessories for inheritance check: %v", art.ID, err)
} else if artWithAccs != nil {
// Combine direct and inherited accessories
allAccs := append(artWithAccs.Accessories, artWithAccs.InheritedAccessories...)
for _, acc := range allAccs {
if acc.GetData().Type == accessorymodel.TypeCosignSignature {
accs = append(accs, acc)
total = 1
break
}
}
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't gate inherited Cosign lookup on len(accs) == 0.

The controller only suppresses inherited Cosign when a direct Cosign exists, not when any direct accessory exists. With a multi-type query like type={attestation.buildkit,signature.cosign}, direct BuildKit accessories make accs non-empty, so the inherited Cosign never gets returned. Because the inherited entry is appended after Count/List, this path also bypasses the final result's count/paging semantics. Please key this off “has direct Cosign already” and merge the inherited item before finalizing the paged response.

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

In `@src/server/v2.0/handler/artifact.go` around lines 391 - 410, The current
logic gates inherited Cosign lookup on len(accs) == 0 which incorrectly prevents
adding an inherited Cosign when other direct accessories exist; change the
condition to check whether a direct Cosign is already present (e.g., scan accs
for any acc.GetData().Type == accessorymodel.TypeCosignSignature) and only skip
inheritance if a direct Cosign exists; if no direct Cosign is found, call
a.artCtl.Get(ctx, art.ID, &artifact.Option{WithAccessory: true}) to retrieve
artWithAccs, scan artWithAccs.Accessories and artWithAccs.InheritedAccessories
for TypeCosignSignature, append any inherited Cosign to accs, and update total
appropriately (and ensure this merge happens before final pagination/counting so
the inherited item is included in the paged response).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant