From 12c97f42aaa667e4234d7076cf5fdb264cee277a Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Sat, 29 Nov 2025 21:56:21 +0300 Subject: [PATCH 1/4] feat(hip): add HIP for OCI Artifact Selection and Referrers Support This HIP proposes enabling Helm to: 1. Select chart manifests from OCI Image Index by artifactType 2. Set artifactType field when pushing charts 3. Support Referrers API via --subject flag Addresses helm/helm#31582 Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- hips/hip-9999.md | 270 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 hips/hip-9999.md diff --git a/hips/hip-9999.md b/hips/hip-9999.md new file mode 100644 index 00000000..6b881d23 --- /dev/null +++ b/hips/hip-9999.md @@ -0,0 +1,270 @@ +--- +hip: "9999" +title: "OCI Artifact Selection and Referrers Support" +authors: ["Aleksei Sviridkin "] +created: "2025-11-29" +type: "feature" +status: "draft" +--- + +## Abstract + +This proposal enables Helm to select chart manifests from OCI Image Index manifests and adds support for the OCI Referrers API. Currently, `helm pull` fails when encountering an OCI Image Index containing multiple artifacts. This HIP introduces artifact selection logic based on the `artifactType` field (with fallback to `config.mediaType`), sets `artifactType` during `helm push`, and optionally allows charts to be associated with container images via the `subject` field and Referrers API. + +## Motivation + +The OCI Image and Distribution specifications v1.1 (released February 2024) introduced native support for artifacts and the Referrers API. These features enable bundling multiple artifacts under a single OCI reference and establishing relationships between them. + +Currently, users who want to publish both a Helm chart and a container image for the same application version must use workarounds: + +- Tag suffixes (e.g., `:v1.0.0` for image, `:v1.0.0-helm` for chart) +- Separate repository paths (e.g., `registry/app` for image, `registry/app-chart` for chart) +- Completely separate registries + +These workarounds introduce unnecessary complexity in CI/CD pipelines, break atomic versioning guarantees, and require additional tooling to keep artifacts synchronized. Tools like ArtifactHub, ArgoCD, and GitOps workflows would benefit from a single source of truth for versioned artifacts. + +The OCI 1.1 specifications provide a standard solution: an Image Index can contain multiple manifests with different `artifactType` values, and the Referrers API allows querying artifacts associated with a specific image. Helm should leverage these capabilities. + +## Rationale + +### Why `artifactType` first, then `config.mediaType` fallback? + +The OCI Image Specification explicitly states that "artifacts have historically been created without an `artifactType` field, and tooling to work with artifacts should fallback to the `config.mediaType` value." Following this guidance ensures compatibility with: + +1. Charts pushed by current Helm versions (which don't set `artifactType`) +2. Charts pushed by third-party tools that may not set `artifactType` +3. Registries that strip or don't preserve `artifactType` + +### Why skip descriptors with `platform` field? + +Descriptors with a `platform` field are container images targeted at specific architectures. A Helm chart is not platform-specific and should never have a `platform` field. Skipping these descriptors avoids unnecessary manifest fetches. + +### Why use first match? + +The OCI Image Index specification states: "If multiple manifests match a client or runtime's requirements, the first matching entry SHOULD be used." This behavior is consistent with multi-architecture image selection. + +### Why add Referrers API support? + +The Referrers API enables discovering all artifacts (charts, SBOMs, signatures) associated with a specific container image. For Helm, this allows: + +- Finding the chart that deploys a specific image version +- Ensuring chart and image are always used together +- Enabling security scanning workflows that link vulnerabilities to deployment configurations + +## Specification + +This HIP introduces three related changes: + +### 1. Artifact Selection from Image Index + +When `helm pull`, `helm install`, or `helm dependency update` encounters an OCI reference that resolves to an Image Index (`application/vnd.oci.image.index.v1+json`), Helm MUST select a chart manifest using the following algorithm: + +1. **First pass**: Iterate through descriptors in `manifests[]` and check for `artifactType: application/vnd.cncf.helm.config.v1+json` + - If exactly one descriptor matches, select it + - If multiple descriptors match, select the first one (per OCI spec) + +2. **Second pass** (fallback): If no match in first pass, iterate through descriptors WITHOUT a `platform` field: + - Fetch the referenced manifest + - Check if `config.mediaType` equals `application/vnd.cncf.helm.config.v1+json` + - If exactly one manifest matches, select it + - If multiple manifests match, select the first one + +3. **Skip**: Descriptors with a `platform` field SHOULD be skipped as they represent container images + +4. **Error**: If no matching chart manifest is found, return an error indicating no Helm chart was found in the Image Index + +Example Image Index with multiple artifacts: + +```json +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", + "manifests": [ + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:abc123...", + "size": 1234, + "platform": { + "architecture": "amd64", + "os": "linux" + } + }, + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:def456...", + "size": 567, + "artifactType": "application/vnd.cncf.helm.config.v1+json" + } + ] +} +``` + +In this example, Helm would select the second descriptor (digest `sha256:def456...`) because it has the matching `artifactType`. + +### 2. Set `artifactType` on Push + +When `helm push` creates a manifest, it MUST set the `artifactType` field to `application/vnd.cncf.helm.config.v1+json`. + +Current manifest structure: + +```json +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.cncf.helm.config.v1+json", + "digest": "sha256:...", + "size": 137 + }, + "layers": [...] +} +``` + +Proposed manifest structure: + +```json +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "artifactType": "application/vnd.cncf.helm.config.v1+json", + "config": { + "mediaType": "application/vnd.cncf.helm.config.v1+json", + "digest": "sha256:...", + "size": 137 + }, + "layers": [...] +} +``` + +This enables efficient selection from Image Index without fetching manifest content. + +### 3. Referrers API Support + +A new optional flag `--subject` is added to `helm push`: + +```bash +helm push mychart-1.0.0.tgz oci://registry.example.com/myapp --subject sha256:abc123... +``` + +When `--subject` is specified, Helm MUST: + +1. Set the `subject` field in the chart manifest to reference the specified digest: + +```json +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "artifactType": "application/vnd.cncf.helm.config.v1+json", + "subject": { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:abc123...", + "size": 1234 + }, + "config": {...}, + "layers": [...] +} +``` + +2. Handle the registry response according to OCI Distribution Spec 1.1: + - If registry returns `OCI-Subject` header, the referrer is tracked automatically + - If registry does not return `OCI-Subject` header, fall back to Referrers Tag Schema + +The `--subject` flag accepts: +- A digest (e.g., `sha256:abc123...`) +- An OCI reference that will be resolved to a digest (e.g., `oci://registry.example.com/myapp:v1.0.0`) + +## Backwards Compatibility + +This proposal is fully backwards compatible: + +1. **Single manifests**: Charts stored as single manifests (not Image Index) continue to work unchanged +2. **Existing charts**: Charts without `artifactType` are still selectable via `config.mediaType` fallback +3. **Registry compatibility**: `artifactType` is an optional field per OCI spec; registries MUST NOT error on unknown fields +4. **Optional Referrers**: The `--subject` flag is optional; existing `helm push` workflows are unchanged + +## Security Implications + +1. **No new attack vectors**: Artifact selection uses the same validation logic applied after manifest fetch +2. **Referrers association is weak**: The `subject` field creates an association, not a cryptographic binding. Chart integrity still depends on digest verification +3. **Registry trust model unchanged**: Users must still trust the registry to serve correct manifests + +## How to Teach This + +### Documentation Updates + +1. Update the OCI registry documentation on helm.sh to explain Image Index support +2. Add examples showing how to create multi-artifact Image Index using tools like `crane` or `oras` +3. Document the `--subject` flag and Referrers API integration + +### Example: Creating Multi-Artifact Image Index + +```bash +# Push container image +docker push registry.example.com/myapp:v1.0.0 + +# Push Helm chart +helm push myapp-1.0.0.tgz oci://registry.example.com/myapp + +# Create Image Index combining both +crane index append \ + --manifest registry.example.com/myapp:v1.0.0 \ + --manifest registry.example.com/myapp:v1.0.0-helm \ + --tag registry.example.com/myapp:v1.0.0 + +# Now helm pull works on the combined reference +helm pull oci://registry.example.com/myapp --version 1.0.0 +``` + +### Example: Associating Chart with Image + +```bash +# Get image digest +IMAGE_DIGEST=$(crane digest registry.example.com/myapp:v1.0.0) + +# Push chart with subject reference +helm push myapp-1.0.0.tgz oci://registry.example.com/myapp --subject $IMAGE_DIGEST + +# Query referrers (using oras) +oras discover registry.example.com/myapp@$IMAGE_DIGEST +``` + +## Reference Implementation + +A reference implementation is available at [helm/helm#31583](https://github.com/helm/helm/pull/31583). + +## Rejected Ideas + +### Requiring explicit `--artifact-type` flag + +Rejected because it adds unnecessary verbosity to common operations. The artifact type is always `application/vnd.cncf.helm.config.v1+json` for Helm charts. + +### Only supporting `artifactType` without `config.mediaType` fallback + +Rejected because it would break compatibility with existing charts and third-party tooling that doesn't set `artifactType`. + +### Making `--subject` mandatory + +Rejected because most users don't need Referrers API integration. The feature should be opt-in. + +### Automatic subject detection + +Considered automatically detecting the "main" image in an Image Index and setting it as subject. Rejected because: +- Ambiguous when multiple images exist +- May not match user intent +- Explicit is better than implicit + +## Open Issues + +1. **Fetching chart via Referrers API**: Should `helm pull` support fetching a chart by image digest using the Referrers API? For example: `helm pull --referrer-of oci://registry.example.com/myapp@sha256:abc123...` + +2. **Multiple charts per image**: What should happen when multiple charts reference the same image via Referrers API? Current proposal: return all, let user select. + +## References + +- [OCI Image Specification v1.1](https://github.com/opencontainers/image-spec/releases/tag/v1.1.0) +- [OCI Distribution Specification v1.1](https://github.com/opencontainers/distribution-spec/releases/tag/v1.1.0) +- [OCI Image Index Specification](https://github.com/opencontainers/image-spec/blob/main/image-index.md) +- [OCI Descriptor Specification](https://github.com/opencontainers/image-spec/blob/main/descriptor.md) +- [HIP-0006: OCI Support](https://github.com/helm/community/blob/main/hips/hip-0006.md) +- [helm/helm#31582: Support for OCI Image Index](https://github.com/helm/helm/issues/31582) +- [helm/helm#31583: Reference Implementation](https://github.com/helm/helm/pull/31583) From 97c33acd6ad05208856856197c38c3f7921be6ec Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Tue, 2 Dec 2025 03:46:41 +0300 Subject: [PATCH 2/4] docs(hip): clarify artifactType and layer mediaType rationale Address review feedback from TerryHowe: - Explain why artifactType equals config.mediaType (per OCI spec) - Explain why layer mediaType cannot be used for selection Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- hips/hip-9999.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hips/hip-9999.md b/hips/hip-9999.md index 6b881d23..1854d026 100644 --- a/hips/hip-9999.md +++ b/hips/hip-9999.md @@ -35,6 +35,14 @@ The OCI Image Specification explicitly states that "artifacts have historically 2. Charts pushed by third-party tools that may not set `artifactType` 3. Registries that strip or don't preserve `artifactType` +### Why `artifactType` equals `config.mediaType` + +The OCI Image Spec (descriptor.md) explicitly defines `artifactType` in Index descriptors as "the value of the config descriptor `mediaType` when the descriptor references an image manifest." This equality is intentional — it exposes the artifact identifier at the Index level for efficient selection without fetching manifests. Using `application/vnd.cncf.helm.config.v1+json` for both fields is correct per OCI spec, not a naming collision. + +### Why not use layer mediaType for selection + +Layer mediaTypes (e.g., `application/vnd.cncf.helm.chart.content.v1.tar+gzip`) are not exposed in Image Index descriptors — only `mediaType`, `digest`, `size`, `platform`, `artifactType`, and `annotations` are available. To check layers, Helm would need to fetch every manifest in the Index first, defeating the purpose of efficient artifact selection. The `artifactType`/`config.mediaType` approach provides artifact identification at the Index level without additional round-trips. + ### Why skip descriptors with `platform` field? Descriptors with a `platform` field are container images targeted at specific architectures. A Helm chart is not platform-specific and should never have a `platform` field. Skipping these descriptors avoids unnecessary manifest fetches. From fad9fa5cfc6fec576cad2e9fc2e220d05de00523 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Sun, 28 Jun 2026 01:13:04 +0300 Subject: [PATCH 3/4] docs(hip): reframe motivation and add deterministic chart selection Rework the proposal around the real gap. Since the index-pull regression was fixed (helm/helm#31776) Helm traverses an index but selects the chart by last-match-wins over hardcoded media types, which is order-dependent and names the output from the reference rather than the selected chart, so it can return a mislabeled chart. Specify selection by descriptor artifactType with name/version disambiguation via org.opencontainers.image.title/version, erroring on genuine ambiguity instead of guessing. Keep the Referrers/subject half optional and separable from selection, clarify push-into-existing-tag behavior, and add a real-world motivation and an offline reproduction. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- hips/hip-9999.md | 283 +++++++++++++++++++++++------------------------ 1 file changed, 141 insertions(+), 142 deletions(-) diff --git a/hips/hip-9999.md b/hips/hip-9999.md index 1854d026..05c9ba7b 100644 --- a/hips/hip-9999.md +++ b/hips/hip-9999.md @@ -9,79 +9,92 @@ status: "draft" ## Abstract -This proposal enables Helm to select chart manifests from OCI Image Index manifests and adds support for the OCI Referrers API. Currently, `helm pull` fails when encountering an OCI Image Index containing multiple artifacts. This HIP introduces artifact selection logic based on the `artifactType` field (with fallback to `config.mediaType`), sets `artifactType` during `helm push`, and optionally allows charts to be associated with container images via the `subject` field and Referrers API. +This proposal gives Helm a deterministic way to select a chart from an OCI Image Index that holds more than one manifest, and adds optional support for the OCI Referrers API. Since v3.20.2 / v3.21 / v4.2 Helm no longer errors on an Image Index (regression fix [helm/helm#31776](https://github.com/helm/helm/pull/31776)), but it still picks the chart by scanning for hardcoded Helm media types and keeping the last match — with no awareness of `artifactType`, chart name, or version. For an index that carries a chart alongside its container image, or more than one chart, that is order-dependent and can silently return (and mislabel) the wrong chart. This HIP defines selection by the descriptor `artifactType` (with a `config.mediaType` fallback), disambiguation by chart name and version via descriptor annotations, sets `artifactType` on `helm push`, and optionally associates a chart with an image through the `subject` field and Referrers API. ## Motivation -The OCI Image and Distribution specifications v1.1 (released February 2024) introduced native support for artifacts and the Referrers API. These features enable bundling multiple artifacts under a single OCI reference and establishing relationships between them. +The OCI Image and Distribution specifications v1.1 (February 2024) introduced first-class artifacts, per-descriptor `artifactType`, and the Referrers API — primitives that let a single OCI reference hold multiple related manifests and express relationships between them. These primitives did not exist when Helm's OCI support was designed, so Helm still treats one OCI reference as exactly one chart. -Currently, users who want to publish both a Helm chart and a container image for the same application version must use workarounds: +Today a publisher who wants both a chart and its container image under one version must fall back to workarounds: -- Tag suffixes (e.g., `:v1.0.0` for image, `:v1.0.0-helm` for chart) -- Separate repository paths (e.g., `registry/app` for image, `registry/app-chart` for chart) -- Completely separate registries +- Tag suffixes (`:v1.0.0` for the image, `:v1.0.0-helm` for the chart). +- Separate repository paths (`registry/app` vs `registry/app-chart`). +- Entirely separate registries. -These workarounds introduce unnecessary complexity in CI/CD pipelines, break atomic versioning guarantees, and require additional tooling to keep artifacts synchronized. Tools like ArtifactHub, ArgoCD, and GitOps workflows would benefit from a single source of truth for versioned artifacts. +These break atomic versioning, complicate CI/CD, and need extra tooling to keep the two artifacts in lockstep. The idea of using an Image Index to carry these together is not new — it was raised as far back as [helm/helm#8332](https://github.com/helm/helm/issues/8332) (2020) — but the OCI 1.1 primitives needed to do it cleanly only landed in 2024. -The OCI 1.1 specifications provide a standard solution: an Image Index can contain multiple manifests with different `artifactType` values, and the Referrers API allows querying artifacts associated with a specific image. Helm should leverage these capabilities. +The relevant gap is no longer that Helm crashes on an index. That regression was fixed in [helm/helm#31776](https://github.com/helm/helm/pull/31776). The gap is that Helm's selection logic is not artifact-aware. In `pkg/registry/client.go` the pull collects every descriptor whose media type is in a fixed allow-list (across the whole index graph) and then assigns the chart by a `switch` over media types that keeps the last match: -## Rationale +```go +// pkg/registry/client.go (v4.2.2), lines 462-475 +for _, descriptor := range genericResult.Descriptors { + d := descriptor + switch d.MediaType { + case ConfigMediaType: + configDescriptor = &d // overwritten on each match -> last wins + case ChartLayerMediaType: + chartDescriptor = &d // last wins + case LegacyChartLayerMediaType: + chartDescriptor = &d + } +} +``` -### Why `artifactType` first, then `config.mediaType` fallback? +There is no use of `artifactType`, no `platform` filtering, and no matching against the requested chart name or version. Two consequences follow for any index with more than one chart-shaped manifest: -The OCI Image Specification explicitly states that "artifacts have historically been created without an `artifactType` field, and tooling to work with artifacts should fallback to the `config.mediaType` value." Following this guidance ensures compatibility with: +- **Order-dependent selection.** Reordering the manifests in the index changes which chart you get. +- **Silent mislabeling.** Helm names the downloaded chart from the last path segment of the reference, not from the selected chart's metadata, so a pull of `oci://registry/app` can write `app-1.0.0.tgz` that actually contains a different chart. -1. Charts pushed by current Helm versions (which don't set `artifactType`) -2. Charts pushed by third-party tools that may not set `artifactType` -3. Registries that strip or don't preserve `artifactType` +Selecting on the descriptor `artifactType` reads what is already in the index response, so it fetches fewer manifests than the current path, not more; single-manifest charts (the vast majority) are unaffected. Pulling and installing stay pure Helm — only assembling a multi-artifact index needs external tooling (`oras`, `crane`), which is an opt-in publisher choice this HIP does not add to Helm. -### Why `artifactType` equals `config.mediaType` +Tools such as ArtifactHub, Argo CD, and GitOps pipelines would benefit from a single, digest-pinned source of truth per version. The OCI 1.1 specifications already provide the mechanism (`artifactType` on index descriptors, annotations for naming, the Referrers API for relationships); Helm should use them. -The OCI Image Spec (descriptor.md) explicitly defines `artifactType` in Index descriptors as "the value of the config descriptor `mediaType` when the descriptor references an image manifest." This equality is intentional — it exposes the artifact identifier at the Index level for efficient selection without fetching manifests. Using `application/vnd.cncf.helm.config.v1+json` for both fields is correct per OCI spec, not a naming collision. +## Real-world motivation -### Why not use layer mediaType for selection +This is not hypothetical. As a maintainer of [Cozystack](https://github.com/cozystack/cozystack) (a CNCF Sandbox Kubernetes platform), I hit the chart/image split directly in air-gapped delivery. -Layer mediaTypes (e.g., `application/vnd.cncf.helm.chart.content.v1.tar+gzip`) are not exposed in Image Index descriptors — only `mediaType`, `digest`, `size`, `platform`, `artifactType`, and `annotations` are available. To check layers, Helm would need to fetch every manifest in the Index first, defeating the purpose of efficient artifact selection. The `artifactType`/`config.mediaType` approach provides artifact identification at the Index level without additional round-trips. +Cozystack publishes its ≈158 first-party charts (223 including vendored sub-charts) as a single OCI artifact (`cozystack-packages`) and its 30+ first-party container images as separate `ghcr.io/cozystack/cozystack/*` references. The charts are fetched by Flux's source-controller over its own OCI/HTTP client; the images are pulled by containerd. There is no per-component unit that co-versions a chart with the image it deploys, so an operator mirroring the platform into an air-gapped registry runs two independent flows with two redirection mechanisms, and nothing ties a chart digest to its image digest. -### Why skip descriptors with `platform` field? +Being able to ship a chart and its image as one digest-pinned OCI reference — and to pull the chart out of a multi-artifact bundle deterministically by name — would remove a whole class of "which chart goes with which image, and did both get mirrored" bookkeeping. The same need shows up for anyone assembling an offline bundle of many charts with external tooling (`oras`, `crane`): the bundle is assemblable today, but Helm cannot select a specific chart from it by name. -Descriptors with a `platform` field are container images targeted at specific architectures. A Helm chart is not platform-specific and should never have a `platform` field. Skipping these descriptors avoids unnecessary manifest fetches. +## Rationale -### Why use first match? +### Why `artifactType` first, then `config.mediaType` fallback -The OCI Image Index specification states: "If multiple manifests match a client or runtime's requirements, the first matching entry SHOULD be used." This behavior is consistent with multi-architecture image selection. +The OCI Image Specification states that "artifacts have historically been created without an `artifactType` field, and tooling to work with artifacts should fallback to the `config.mediaType` value." Following that guidance keeps compatibility with charts pushed by current Helm (which do not set `artifactType`), by third-party tools, and by registries that do not preserve it. -### Why add Referrers API support? +### Selection by `artifactType` is a Helm policy over a spec-provided field -The Referrers API enables discovering all artifacts (charts, SBOMs, signatures) associated with a specific container image. For Helm, this allows: +The OCI Image Index spec defines `artifactType`, `platform`, and `annotations` as per-descriptor fields and says "if multiple manifests match a client or runtime's requirements, the first matching entry SHOULD be used," but it only spells out `platform` matching in detail. Selecting an entry by `artifactType` is therefore a Helm-defined policy layered on a spec-provided field, not an OCI mandate. It is sound because `artifactType` equals the config `mediaType` for an image manifest, so it identifies a Helm chart at the index level without fetching every manifest. -- Finding the chart that deploys a specific image version -- Ensuring chart and image are always used together -- Enabling security scanning workflows that link vulnerabilities to deployment configurations +### Why disambiguate by chart name and version -## Specification +`artifactType` alone identifies "this is a Helm chart," not "this is the chart you asked for." An index may carry several charts. To select deterministically, Helm matches the requested chart name against the `org.opencontainers.image.title` annotation and the requested version against `org.opencontainers.image.version` (falling back to the chart's `Chart.yaml` after fetch when annotations are absent). `org.opencontainers.image.ref.name` is deliberately not used: the spec scopes it to the OCI image-layout (`index.json` on disk), not to registry indexes. -This HIP introduces three related changes: +### Why skip descriptors with a `platform` field -### 1. Artifact Selection from Image Index +Descriptors with a `platform` field are architecture-specific container images. A Helm chart is not platform-specific, so skipping platform descriptors avoids needless manifest fetches and never confuses an image for a chart. -When `helm pull`, `helm install`, or `helm dependency update` encounters an OCI reference that resolves to an Image Index (`application/vnd.oci.image.index.v1+json`), Helm MUST select a chart manifest using the following algorithm: +### Why add Referrers API support -1. **First pass**: Iterate through descriptors in `manifests[]` and check for `artifactType: application/vnd.cncf.helm.config.v1+json` - - If exactly one descriptor matches, select it - - If multiple descriptors match, select the first one (per OCI spec) +The Referrers API lets a client discover all artifacts (charts, SBOMs, signatures) associated with a specific image via the `subject` field. For Helm this enables finding the chart that deploys a given image, keeping chart and image together, and security workflows that link a deployment to its image. -2. **Second pass** (fallback): If no match in first pass, iterate through descriptors WITHOUT a `platform` field: - - Fetch the referenced manifest - - Check if `config.mediaType` equals `application/vnd.cncf.helm.config.v1+json` - - If exactly one manifest matches, select it - - If multiple manifests match, select the first one +This association is optional and explicit: `--subject` takes a user-supplied digest, Helm does not infer which images a chart templates (a chart often references several, and the attached image need not be one of them), and the selection logic in §1 works with no `subject` at all. The two halves of this HIP are independent — selection is the load-bearing change and can be evaluated on its own. -3. **Skip**: Descriptors with a `platform` field SHOULD be skipped as they represent container images +## Specification -4. **Error**: If no matching chart manifest is found, return an error indicating no Helm chart was found in the Image Index +This HIP introduces three related changes. -Example Image Index with multiple artifacts: +### 1. Artifact selection from an Image Index + +When `helm pull`, `helm install`, or `helm dependency update` resolves an OCI reference to an Image Index (`application/vnd.oci.image.index.v1+json`), Helm MUST select a chart manifest as follows: + +1. **Candidate set.** Consider only descriptors WITHOUT a `platform` field. A descriptor is a chart candidate if its `artifactType` equals `application/vnd.cncf.helm.config.v1+json`. If no descriptor carries `artifactType` (legacy indexes), fall back to fetching the non-platform manifests and treating those whose `config.mediaType` equals the Helm config media type as candidates. +2. **Disambiguation.** If more than one candidate remains, match the requested chart name against the descriptor's `org.opencontainers.image.title` annotation, and the requested version against `org.opencontainers.image.version` (or the chart version after fetch). The requested chart name is the last path segment of the reference; the version is the resolved `--version`. +3. **Resolution.** Select the unique candidate that matches. If exactly one candidate exists overall, select it. If multiple candidates remain after disambiguation (ambiguous), return an error listing the candidates rather than guessing — this replaces today's silent last-match-wins behavior. +4. **Not found.** If no candidate matches, return an error indicating no matching Helm chart was found in the index. + +Example index carrying a container image and a chart: ```json { @@ -91,44 +104,28 @@ Example Image Index with multiple artifacts: { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:abc123...", - "size": 1234, - "platform": { - "architecture": "amd64", - "os": "linux" - } + "size": 401, + "platform": { "architecture": "amd64", "os": "linux" } }, { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:def456...", - "size": 567, - "artifactType": "application/vnd.cncf.helm.config.v1+json" + "size": 587, + "artifactType": "application/vnd.cncf.helm.config.v1+json", + "annotations": { + "org.opencontainers.image.title": "demoapp", + "org.opencontainers.image.version": "0.1.0" + } } ] } ``` -In this example, Helm would select the second descriptor (digest `sha256:def456...`) because it has the matching `artifactType`. - -### 2. Set `artifactType` on Push +Helm skips the first descriptor (it has a `platform`) and selects the second by `artifactType`; the `title`/`version` annotations let it pick `demoapp` specifically when the index holds more than one chart. -When `helm push` creates a manifest, it MUST set the `artifactType` field to `application/vnd.cncf.helm.config.v1+json`. - -Current manifest structure: - -```json -{ - "schemaVersion": 2, - "mediaType": "application/vnd.oci.image.manifest.v1+json", - "config": { - "mediaType": "application/vnd.cncf.helm.config.v1+json", - "digest": "sha256:...", - "size": 137 - }, - "layers": [...] -} -``` +### 2. Set `artifactType` on push -Proposed manifest structure: +`helm push` MUST set the manifest `artifactType` to `application/vnd.cncf.helm.config.v1+json` (equal to the config `mediaType`) and SHOULD set the `org.opencontainers.image.title` and `org.opencontainers.image.version` annotations to the chart name and version. This exposes the chart's identity at the index level so selection needs no manifest fetch. ```json { @@ -140,132 +137,131 @@ Proposed manifest structure: "digest": "sha256:...", "size": 137 }, - "layers": [...] + "layers": [ "..." ] } ``` -This enables efficient selection from Image Index without fetching manifest content. - -### 3. Referrers API Support +### 3. Referrers API support -A new optional flag `--subject` is added to `helm push`: +A new optional `--subject` flag on `helm push` associates a chart with another artifact (typically the container image it deploys): ```bash helm push mychart-1.0.0.tgz oci://registry.example.com/myapp --subject sha256:abc123... ``` -When `--subject` is specified, Helm MUST: +When `--subject` is set, Helm MUST set the manifest `subject` field to the referenced descriptor and handle the registry response per OCI Distribution Spec 1.1: if the registry returns the `OCI-Subject` header the referrer is tracked automatically; otherwise Helm MUST fall back to the Referrers Tag Schema. The `--subject` flag accepts a digest or an OCI reference resolved to a digest. -1. Set the `subject` field in the chart manifest to reference the specified digest: +### 4. Push into an existing tag -```json -{ - "schemaVersion": 2, - "mediaType": "application/vnd.oci.image.manifest.v1+json", - "artifactType": "application/vnd.cncf.helm.config.v1+json", - "subject": { - "mediaType": "application/vnd.oci.image.manifest.v1+json", - "digest": "sha256:abc123...", - "size": 1234 - }, - "config": {...}, - "layers": [...] -} -``` - -2. Handle the registry response according to OCI Distribution Spec 1.1: - - If registry returns `OCI-Subject` header, the referrer is tracked automatically - - If registry does not return `OCI-Subject` header, fall back to Referrers Tag Schema - -The `--subject` flag accepts: -- A digest (e.g., `sha256:abc123...`) -- An OCI reference that will be resolved to a digest (e.g., `oci://registry.example.com/myapp:v1.0.0`) +`helm push` keeps its current behavior of overwriting the tag, and this HIP does NOT add index-merge or auto-index-creation logic to Helm. Assembling a multi-artifact Image Index (a chart with its image, or several charts) is done with external tooling (`oras`, `crane`) and pushed as a unit; Helm only needs to *select* from such an index on pull. Letting `helm push` publish a manifest by digest / without moving a tag — so a chart can be folded into an externally-assembled index without clobbering an existing tag — is a reasonable enhancement, tracked in Open Issues rather than specified here, to avoid baking implicit index logic into `helm push`. ## Backwards Compatibility -This proposal is fully backwards compatible: +This proposal is backwards compatible: -1. **Single manifests**: Charts stored as single manifests (not Image Index) continue to work unchanged -2. **Existing charts**: Charts without `artifactType` are still selectable via `config.mediaType` fallback -3. **Registry compatibility**: `artifactType` is an optional field per OCI spec; registries MUST NOT error on unknown fields -4. **Optional Referrers**: The `--subject` flag is optional; existing `helm push` workflows are unchanged +1. **Single manifests** (the overwhelming majority of charts today) are unaffected — there is no index to select from. +2. **Existing charts without `artifactType`** remain selectable through the `config.mediaType` fallback. +3. **`artifactType` is optional per OCI spec**; registries MUST NOT error on it. +4. **`--subject` is opt-in**; existing `helm push` workflows are unchanged. +5. **Selection becomes safer, not different, for the common case.** A reference that resolves to a single chart behaves exactly as before; only ambiguous multi-candidate indexes change — from silent last-match-wins to deterministic selection or an explicit error. ## Security Implications -1. **No new attack vectors**: Artifact selection uses the same validation logic applied after manifest fetch -2. **Referrers association is weak**: The `subject` field creates an association, not a cryptographic binding. Chart integrity still depends on digest verification -3. **Registry trust model unchanged**: Users must still trust the registry to serve correct manifests +1. **No new attack vectors.** Selection uses the same validation applied after a manifest fetch; the chart bytes are still digest-verified. +2. **Referrers association is weak by design.** `subject` expresses an association, not a cryptographic binding; integrity still rests on digests and any signatures attached as referrers. +3. **Registry trust model unchanged.** Users still trust the registry to serve correct manifests. +4. **Removes a silent-mislabeling foot-gun.** Erroring on ambiguity instead of returning the last match prevents a chart being delivered under another chart's name. ## How to Teach This -### Documentation Updates +### Documentation updates -1. Update the OCI registry documentation on helm.sh to explain Image Index support -2. Add examples showing how to create multi-artifact Image Index using tools like `crane` or `oras` -3. Document the `--subject` flag and Referrers API integration +1. Document Image Index support and selection rules in the helm.sh OCI guide. +2. Show how to assemble a multi-artifact Image Index with `oras` or `crane`. +3. Document the `--subject` flag and Referrers integration. -### Example: Creating Multi-Artifact Image Index +### Example: assemble a chart + image index ```bash -# Push container image -docker push registry.example.com/myapp:v1.0.0 - -# Push Helm chart +# Push the chart (helm sets artifactType + title/version annotations under this HIP) helm push myapp-1.0.0.tgz oci://registry.example.com/myapp -# Create Image Index combining both -crane index append \ - --manifest registry.example.com/myapp:v1.0.0 \ - --manifest registry.example.com/myapp:v1.0.0-helm \ - --tag registry.example.com/myapp:v1.0.0 +# Build a scratch image (or use a real one) in the same repository +crane append --oci-empty-base --new_layer layer.tar --new_tag registry.example.com/myapp:appimg -# Now helm pull works on the combined reference +# Assemble an index referencing both manifests, then push it to the chart's tag +oras manifest push registry.example.com/myapp:1.0.0 \ + --media-type application/vnd.oci.image.index.v1+json index.json + +# Helm selects the chart from the combined reference helm pull oci://registry.example.com/myapp --version 1.0.0 ``` -### Example: Associating Chart with Image +### Example: associate a chart with its image + +```bash +IMAGE_DIGEST=$(crane digest registry.example.com/myapp:appimg) +helm push myapp-1.0.0.tgz oci://registry.example.com/myapp --subject "$IMAGE_DIGEST" +oras discover registry.example.com/myapp@"$IMAGE_DIGEST" +``` + +## Reproduction + +The current behavior and the gap this HIP closes are reproducible offline against the reference OCI registry (`registry:2`) with `helm` ≥ v3.20.2, `oras`, and `crane`. ```bash -# Get image digest -IMAGE_DIGEST=$(crane digest registry.example.com/myapp:v1.0.0) +docker run -d -p 5001:5000 registry:2 +helm create demoapp && (cd demoapp && sed -i 's/^version:.*/version: 0.1.0/' Chart.yaml) +helm package demoapp +helm push demoapp-0.1.0.tgz oci://localhost:5001/poc --plain-http # -> poc/demoapp:0.1.0 + +# Assemble a chart+image index at poc/demoapp:0.1.0 (image manifest + chart manifest +# with artifactType); see index.json in this PoC. Then: +helm pull oci://localhost:5001/poc/demoapp --version 0.1.0 --plain-http +# Pulled: the chart is extracted from the mixed index (works on >= v3.20.2 / v4.2; +# fails on v3.18.0-v3.20.1 / v4.0-v4.1 with "image.index...: not found"). +``` -# Push chart with subject reference -helm push myapp-1.0.0.tgz oci://registry.example.com/myapp --subject $IMAGE_DIGEST +The gap — order-dependent, name-blind selection — reproduces with two charts in one index: -# Query referrers (using oras) -oras discover registry.example.com/myapp@$IMAGE_DIGEST +```bash +# Index order [demoapp, otherapp], pull .../poc/demoapp -> file demoapp-*.tgz, content demoapp +# Index order [otherapp, demoapp], pull .../poc/demoapp -> file demoapp-*.tgz, content OTHERAPP ``` +Reversing the manifest order flips the chart returned, while the output file keeps the requested name — demonstrating both the non-determinism and the mislabeling that selection by `artifactType` + `title`/`version` removes. + ## Reference Implementation A reference implementation is available at [helm/helm#31583](https://github.com/helm/helm/pull/31583). ## Rejected Ideas -### Requiring explicit `--artifact-type` flag +### Requiring an explicit `--artifact-type` flag -Rejected because it adds unnecessary verbosity to common operations. The artifact type is always `application/vnd.cncf.helm.config.v1+json` for Helm charts. +Rejected as needless verbosity; the artifact type is always `application/vnd.cncf.helm.config.v1+json` for Helm charts. -### Only supporting `artifactType` without `config.mediaType` fallback +### `artifactType` without the `config.mediaType` fallback -Rejected because it would break compatibility with existing charts and third-party tooling that doesn't set `artifactType`. +Rejected; it would break existing charts and third-party tooling that do not set `artifactType`. -### Making `--subject` mandatory +### Using `org.opencontainers.image.ref.name` for name selection -Rejected because most users don't need Referrers API integration. The feature should be opt-in. +Rejected; the OCI spec scopes `ref.name` to the image-layout (`index.json` on disk), not registry indexes. `org.opencontainers.image.title` is the registry-valid carrier for an artifact name. -### Automatic subject detection +### Keeping last-match-wins for ambiguous indexes -Considered automatically detecting the "main" image in an Image Index and setting it as subject. Rejected because: -- Ambiguous when multiple images exist -- May not match user intent -- Explicit is better than implicit +Rejected; returning the last media-type match silently is exactly the mislabeling foot-gun this HIP removes. Ambiguity that cannot be resolved by name/version MUST be an error. -## Open Issues +### Making `--subject` mandatory or auto-detecting the subject + +Rejected; most users do not need Referrers integration, and auto-detecting the "main" image in an index is ambiguous and may not match intent. Explicit is better than implicit. -1. **Fetching chart via Referrers API**: Should `helm pull` support fetching a chart by image digest using the Referrers API? For example: `helm pull --referrer-of oci://registry.example.com/myapp@sha256:abc123...` +## Open Issues -2. **Multiple charts per image**: What should happen when multiple charts reference the same image via Referrers API? Current proposal: return all, let user select. +1. **Fetch a chart via the Referrers API.** Should `helm pull` support fetching the chart that refers to an image, e.g. `helm pull --referrer-of oci://registry.example.com/myapp@sha256:abc123...`? +2. **Multiple charts referring to one image.** When several charts reference the same image via the Referrers API, return all and let the user select. +3. **Push without a tag / by digest.** Allow `helm push` to publish a manifest without moving a tag (pull already works by digest), so a chart can be folded into an externally-assembled index without overwriting an existing tag. ## References @@ -273,6 +269,9 @@ Considered automatically detecting the "main" image in an Image Index and settin - [OCI Distribution Specification v1.1](https://github.com/opencontainers/distribution-spec/releases/tag/v1.1.0) - [OCI Image Index Specification](https://github.com/opencontainers/image-spec/blob/main/image-index.md) - [OCI Descriptor Specification](https://github.com/opencontainers/image-spec/blob/main/descriptor.md) +- [OCI Annotations](https://github.com/opencontainers/image-spec/blob/main/annotations.md) - [HIP-0006: OCI Support](https://github.com/helm/community/blob/main/hips/hip-0006.md) +- [helm/helm#8332: SemVer and signed releases with OCI registries (2020, the original ask)](https://github.com/helm/helm/issues/8332) +- [helm/helm#31776: regression fix — pull from OCI indices](https://github.com/helm/helm/pull/31776) - [helm/helm#31582: Support for OCI Image Index](https://github.com/helm/helm/issues/31582) - [helm/helm#31583: Reference Implementation](https://github.com/helm/helm/pull/31583) From 2fd308317bbb6ed332cbbd98e60dd64bc873dd3b Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Sun, 28 Jun 2026 02:14:01 +0300 Subject: [PATCH 4/4] docs(hip): scope --subject to a same-repository digest Align the Referrers section with the reference implementation: --subject takes a digest that must already exist in the chart's target repository (the Referrers API is per-repository), and Helm resolves it there to a complete descriptor so the manifest carries a spec-valid subject rather than a digest-only stub. Cross-repository subjects move to Open Issues. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- hips/hip-9999.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hips/hip-9999.md b/hips/hip-9999.md index 05c9ba7b..fe06c83a 100644 --- a/hips/hip-9999.md +++ b/hips/hip-9999.md @@ -143,13 +143,13 @@ Helm skips the first descriptor (it has a `platform`) and selects the second by ### 3. Referrers API support -A new optional `--subject` flag on `helm push` associates a chart with another artifact (typically the container image it deploys): +A new optional `--subject` flag on `helm push` associates a chart with another artifact (typically the container image it deploys) that already lives in the same repository: ```bash helm push mychart-1.0.0.tgz oci://registry.example.com/myapp --subject sha256:abc123... ``` -When `--subject` is set, Helm MUST set the manifest `subject` field to the referenced descriptor and handle the registry response per OCI Distribution Spec 1.1: if the registry returns the `OCI-Subject` header the referrer is tracked automatically; otherwise Helm MUST fall back to the Referrers Tag Schema. The `--subject` flag accepts a digest or an OCI reference resolved to a digest. +`--subject` takes a digest. Because the Referrers API is per-repository, the subject must already exist in the chart's target repository; Helm resolves the digest against that repository to a complete descriptor (`mediaType` + `size`) so the manifest carries a spec-valid `subject` — a digest-only descriptor is not valid and a Referrers-aware registry may reject it. Helm then sets the manifest `subject` field and handles the registry response per OCI Distribution Spec 1.1: if the registry returns the `OCI-Subject` header the referrer is tracked automatically; otherwise Helm MUST fall back to the Referrers Tag Schema. A cross-repository subject reference is out of scope here (see Open Issues). ### 4. Push into an existing tag @@ -262,6 +262,7 @@ Rejected; most users do not need Referrers integration, and auto-detecting the " 1. **Fetch a chart via the Referrers API.** Should `helm pull` support fetching the chart that refers to an image, e.g. `helm pull --referrer-of oci://registry.example.com/myapp@sha256:abc123...`? 2. **Multiple charts referring to one image.** When several charts reference the same image via the Referrers API, return all and let the user select. 3. **Push without a tag / by digest.** Allow `helm push` to publish a manifest without moving a tag (pull already works by digest), so a chart can be folded into an externally-assembled index without overwriting an existing tag. +4. **Cross-repository subject.** Accept an `--subject` that points at a different repository (resolving the descriptor there), rather than requiring the subject to live in the chart's own repository. ## References