Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pkg/oci/remote/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,9 @@ func WriteReferrer(d name.Digest, artifactType string, layers []v1.Layer, annota
MediaType: mediaType,
Digest: layerDigest,
Size: layerSize,
Annotations: map[string]string{
"org.opencontainers.image.title": fmt.Sprintf("%s-%s.sigstore.json", layerDigest.Algorithm, layerDigest.Hex),
},
}
}

Expand Down
53 changes: 52 additions & 1 deletion pkg/oci/remote/write_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,24 @@ func TestWriteAttestationNewBundleFormat(t *testing.T) {
if refManifest.Annotations["dev.sigstore.bundle.predicateType"] != predicateType {
t.Errorf("Expected predicateType annotation to be %s, got %s", predicateType, refManifest.Annotations["dev.sigstore.bundle.predicateType"])
}

// Verify the layer has the org.opencontainers.image.title annotation
if len(refManifest.Layers) == 0 {
t.Fatal("Expected at least one layer in manifest")
}
layer := refManifest.Layers[0]
if layer.Annotations == nil {
t.Fatal("Expected layer to have annotations, but Annotations is nil")
}
title, ok := layer.Annotations["org.opencontainers.image.title"]
if !ok {
t.Error("Expected layer to have 'org.opencontainers.image.title' annotation, but it was not found")
}
// Verify the title format matches {algorithm}-{hex}.sigstore.json
expectedTitle := fmt.Sprintf("%s-%s.sigstore.json", layer.Digest.Algorithm, layer.Digest.Hex)
if title != expectedTitle {
t.Errorf("Expected layer title to be %s, got %s", expectedTitle, title)
}
}

func TestWriteAttestationsReferrer(t *testing.T) {
Expand Down Expand Up @@ -333,7 +351,22 @@ func TestWriteAttestationsReferrer(t *testing.T) {

// Verify we have at least one layer
if len(refManifest.Layers) == 0 {
t.Error("Expected at least one layer in manifest")
t.Fatal("Expected at least one layer in manifest")
}
// Verify each layer has the org.opencontainers.image.title annotation
for i, layer := range refManifest.Layers {
if layer.Annotations == nil {
t.Fatalf("Expected layer %d to have annotations, but Annotations is nil", i)
}
title, ok := layer.Annotations["org.opencontainers.image.title"]
if !ok {
t.Errorf("Expected layer %d to have 'org.opencontainers.image.title' annotation, but it was not found", i)
}
// Verify the title format matches {algorithm}-{hex}.sigstore.json
expectedTitle := fmt.Sprintf("%s-%s.sigstore.json", layer.Digest.Algorithm, layer.Digest.Hex)
if title != expectedTitle {
t.Errorf("Expected layer %d title to be %s, got %s", i, expectedTitle, title)
}
}
}

Expand Down Expand Up @@ -411,6 +444,24 @@ func TestWriteReferrer(t *testing.T) {
t.Errorf("Expected 1 layer, got %d", len(refManifest.Layers))
}

// Verify the layer has the org.opencontainers.image.title annotation
if len(refManifest.Layers) == 0 {
t.Fatal("Expected at least one layer in manifest")
}
layer := refManifest.Layers[0]
if layer.Annotations == nil {
t.Fatal("Expected layer to have annotations, but Annotations is nil")
}
title, ok := layer.Annotations["org.opencontainers.image.title"]
if !ok {
t.Error("Expected layer to have 'org.opencontainers.image.title' annotation, but it was not found")
}
// Verify the title format matches {algorithm}-{hex}.sigstore.json
expectedTitle := fmt.Sprintf("%s-%s.sigstore.json", layer.Digest.Algorithm, layer.Digest.Hex)
if title != expectedTitle {
t.Errorf("Expected layer title to be %s, got %s", expectedTitle, title)
}

// Verify the subject is set
if refManifest.Subject == nil {
t.Error("Expected Subject to be set")
Expand Down
27 changes: 24 additions & 3 deletions specs/BUNDLE_SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ Content-Type: application/vnd.oci.image.manifest.v1+json
{
"digest": "sha256:cafed00d...",
"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json",
"size": 4971
"size": 4971,
"annotations": {
"org.opencontainers.image.title": "sha256-cafed00d.sigstore.json"
}
}
],
"subject": {
Expand Down Expand Up @@ -184,7 +187,10 @@ GET /v2/foo/manifests/sha256:badf00d..
{
"digest": "sha256:cafed00d...",
"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json",
"size": 4971
"size": 4971,
"annotations": {
"org.opencontainers.image.title": "sha256-cafed00d.sigstore.json"
}
}
],
"subject": {
Expand Down Expand Up @@ -249,6 +255,18 @@ when it was created:
the pre-defined annotation keys identified in the
[OCI spec](https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys)).

### Layer Annotations

In addition to manifest-level annotations, individual layer descriptors within
the bundle manifest may optionally include the `org.opencontainers.image.title`
annotation to provide a meaningful filename for the attestation bundle:

- `org.opencontainers.image.title` (optional) - A suggested filename for the
layer content, formatted as `{digest-algorithm}-{digest-hex}.sigstore.json`.
The hyphen separator (rather than colon) ensures the filename is valid across
all platforms, including Windows. This enables tools like `oras pull` to save
attestation bundles with collision-free, human-readable filenames.

These annotations should be included as part of the bundle manifest:

```json
Expand All @@ -270,7 +288,10 @@ These annotations should be included as part of the bundle manifest:
{
"digest": "sha256:cafed00d...",
"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json",
"size": 4971
"size": 4971,
"annotations": {
"org.opencontainers.image.title": "sha256-cafed00d.sigstore.json"
}
}
],
"subject": {
Expand Down
Loading