Skip to content

feat(security): bind generated recipe to signed catalog (recipe-artifact integrity) #1217

Description

@lockwobr

Summary

There is no integrity binding between a generated recipe artifact and the signed catalog it was supposedly derived from. A recipe altered between aicr recipe -o recipe.yaml and aicr bundle -r recipe.yaml --attest is bundled and attested as-is. The bundle attestation records only the criteria label and the checksums.txt digest, never the recipe content or a re-derivation against the catalog, so a tampered recipe produces a structurally valid attestation over a tampered bundle.

This is the recipe-artifact peer of the recipe-data provenance shipped in #1155 (signed catalog/registry). #1155 protects the inputs to recipe generation; this issue covers the gap that the generated recipe is trusted verbatim by the bundler with no link back to that signed catalog.

Surfaced while reviewing #1216. Filed standalone and attached to #1149 per the epic's "new gaps surfaced while implementing should be filed as standalone issues" note.

Why this matters (security-meaningful edit vectors)

The recipe is the build contract for the bundle, and several ComponentRef fields (pkg/recipe/metadata.go) flow verbatim into deployer output with no re-validation against the registry:

  • Source (chart repo / OCI reference) — becomes the repository the cluster pulls from at deploy time: Repository: ref.Source (pkg/bundler/deployer/helmfile/helmfile.go:346), HelmRepository.URL: ref.Source (pkg/bundler/deployer/flux/sources.go:64-68). Repointing gpu-operator's Source to oci://attacker.io/charts makes the generated ArgoCD/Flux/Helmfile artifact pull the operator chart from an attacker.
  • Overrides (map[string]any, arbitrary Helm values, highest merge precedence)image.repository/image.tag, securityContext.privileged: true, hostPath/hostPID/hostNetwork, serviceAccount/RBAC toggles, bounded only by the chart schema (wide for these charts).
  • Version / Tag — downgrade a chart/image to a known-CVE version or pin a malicious tag.
  • PreManifestFiles / ManifestFiles — raw manifests embedded and applied at sync-wave N-1 (before the chart); with --data the caller controls the data dir, enabling injection of a privileged DaemonSet or ClusterRoleBinding that lands first. (Path traversal out of the data root is already blocked.)
  • Namespace / Path — retarget into a namespace with relaxed PodSecurity, or (Kustomize) a different kustomization path.

What constrains this today (and why it is not sufficient)

  • The only recipe-vs-policy check is WithAllowLists, which validates criteria (service/accelerator/intent/os/platform), not Source/Version/Overrides (pkg/bundler/bundler.go:93-95).
  • The bundle attestation signs checksums.txt, which covers the emitted (tampered) values, but only under the editor's signing identity. Identity-pinned aicr verify rejects a non-NVIDIA/aicr signer, and --data caps trust at attested rather than verified.

So an external attacker cannot launder a tampered recipe into a "verified" NVIDIA bundle. The residual gap: nothing recomputes the recipe from (criteria + signed catalog) and compares, so trust rests entirely on the signer identity, not on recipe correctness. A tamper inside an otherwise-trusted pipeline (between recipe and bundle) still verifies clean, and the recipe content is not even captured as a digest in the attestation.

Proposed options (not mutually exclusive)

  1. Record a recipe-content digest as a resolvedDependency in the bundle attestation, so a verifier can detect divergence. Cheapest; detection only.
  2. Re-derive at bundle time — have the bundler regenerate the recipe from criteria against the verified catalog and reject -r inputs that do not match. Strongest; this is exactly what feat: recipe-data provenance (signed catalog/registry) #1155 (signed catalog) makes possible (signed catalog + deterministic generation = verifier can recompute the expected recipe).
  3. Sign the recipe artifact with a recipe attest / recipe verify pair mirroring feat: recipe-data provenance (signed catalog/registry) #1155.

Acceptance criteria (draft)

  • A bundle carries a verifiable link between its emitted content and the recipe/catalog it claims to derive from.
  • A recipe altered after generation is detectable at aicr verify time (or refused at bundle time), independent of the signer identity.
  • Lands with passing make test / make lint and docs updated.

Out of scope

  • Changes to the in-toto predicate/statement shape beyond adding a recipe dependency/subject.
  • New attestation types beyond what options 1–3 require.

Related: #1149 (epic), #1155 (signed catalog), #1216 (PR that surfaced this).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions