Skip to content

BuildKit attestation manifests show as accessories in Harbor #18

@Vad1mo

Description

@Vad1mo

When multi-arch images are built with Docker BuildKit attestations (provenance + SBOM), the OCI Image Index contains additional entries for each platform image with platform: {os: "unknown", architecture: "unknown"}. Harbor displays these as regular artifact rows in the children list, confusing users.

What these entries are

BuildKit (since v0.11 / Docker 24+) embeds attestation manifests (SLSA provenance + SPDX SBOM) as entries within the OCI Image Index. Each real platform image gets a corresponding attestation entry (~2-87KiB) containing in-toto metadata. These are not runnable images — they are supply-chain metadata.

Each attestation is linked to its platform image via annotations:

{
  "annotations": {
    "vnd.docker.reference.digest": "sha256:<platform-image-digest>",
    "vnd.docker.reference.type": "attestation-manifest"
  },
  "platform": { "architecture": "unknown", "os": "unknown" }
}

Root cause

No filtering exists anywhere in the chain:

  • Backend (src/controller/artifact/abstractor.go): abstractIndexMetadata() stores every OCI Index entry as a Reference with the platform verbatim — including unknown/unknown
  • API: returns all references unfiltered
  • Frontend (artifact-list-tab.component.ts): iterates all res.references and renders them

The annotations are already persisted in the artifact_reference.annotations jsonb column but never inspected.

Proposed Solution

Treat attestation manifests as accessories of their parent platform image (similar to cosign signatures), not as index children.

Backend changes

In abstractIndexMetadata (src/controller/artifact/abstractor.go:179), when processing index entries:

  1. Check if annotations["vnd.docker.reference.type"] == "attestation-manifest"
  2. If yes, resolve the subject platform artifact via annotations["vnd.docker.reference.digest"]
  3. Create an artifact_accessory record linking the attestation to the platform image
  4. Exclude the attestation entry from art.References (so it won't appear as a child row)

New accessory type needed:

// src/pkg/accessory/model/accessory.go
TypeBuildKitAttestation = "attestation.buildkit"

With a new model package at src/pkg/accessory/model/attestation/ following the cosign/notation pattern (hard reference, not independently displayable).

Frontend changes

  • Add ATTESTATION = 'attestation.buildkit' to the AccessoryType enum in src/portal/.../artifact.ts
  • The attestation will automatically appear in the existing expandable accessory tree (the <sub-accessories> component) under the platform image — no new columns needed

Result

Before:

Artifacts OS/ARCH
sha256:b1d2caac linux/amd64
sha256:d46c76e5 linux/arm64
sha256:e91e57f4 unknown/unknown
sha256:37c1dde7 unknown/unknown

After:

Artifacts OS/ARCH
sha256:b1d2caac linux/amd64
↳ sha256:e91e57f4 attestation.buildkit
sha256:d46c76e5 linux/arm64
↳ sha256:37c1dde7 attestation.buildkit

Migration consideration

This fix only affects images pushed after the change. For already-pushed images, a display-time fallback could check artifact_reference.annotations for attestation-manifest entries and synthesize virtual accessories, or a one-time migration could create the missing accessory records from existing reference data.

Reproduction

# Build and push multi-arch image with attestations
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --provenance=true --sbom=true \
  -t <registry>/test/image:v1 --push .

Navigate to the image in Harbor UI → expand the manifest index → observe unknown/unknown rows.

API confirms the attestation annotations:

curl -u admin:Harbor12345 \
  <registry>/api/v2.0/projects/test/repositories/image/artifacts/<index-digest> \
  | jq '.references[] | select(.annotations["vnd.docker.reference.type"] == "attestation-manifest")'

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions