Summary
Bring nodewright's container evidence to parity with the layout AICR settled on in NVIDIA/aicr#2426: a CycloneDX SBOM and an OpenVEX document attested to each platform manifest digest, with SLSA provenance on the index.
Nodewright is most of the way there already. The gaps are the attestation subject, a missing VEX document, and two cosign flags.
Current state
.github/actions/cosign-sign-sbom already does the right things in the right order:
cosign sign on the image
cosign attest --type cyclonedx with a Syft-generated CycloneDX SBOM
actions/attest-build-provenance for SLSA
Images are built multi-arch (PLATFORMS: linux/amd64,linux/arm64), and operator-ci.yaml resolves the multi-platform manifest digest and passes that as subject-digest.
Gaps
1. Evidence is attached to the index, not the platform manifests
This is the substantive one, and it is the same defect AICR fixed in #2426.
crane digest <tag> returns the index. An SBOM describes exactly one root filesystem, so attaching a single SBOM to a multi-platform index means it cannot honestly describe either child. A consumer who resolves linux/amd64 and enumerates referrers there finds nothing.
Fix: resolve each platform manifest with crane digest --platform linux/<arch> and attest that platform's SBOM to its own digest. AICR's .github/actions/attest-image-from-tag does this and fails closed if a platform is missing or resolves to the index digest, which catches a release that silently lost an architecture.
2. No VEX document
Nodewright publishes no OpenVEX (.openvex.json does not exist, zero references in the repo). Without one there is no way to say "this CVE does not apply to us and here is why", so every finding in a scan is either a real problem or unexplained noise.
AICR's .openvex.json plus the aicr-managing-openvex skill is the working model, including the constraint learned the hard way in NVIDIA/aicr#2706: the document is signed and published, so it carries triage statements only. Working notes belong in a separate state file with a lifecycle, or they grow without bound.
Use OpenVEX, not CycloneDX, for the VEX. cosign attest --type cyclonedx sets predicate type https://cyclonedx.org/bom regardless of whether the payload is an SBOM or a VEX, and a referrer descriptor has no name field. Two CycloneDX documents on one manifest are therefore indistinguishable in a referrers listing, and telling them apart means pulling and decoding every payload. OpenVEX gets https://openvex.dev/ns, so the listing stays self-describing.
3. --new-bundle-format=true is not set
Zero occurrences in the action. This controls whether the attestation publishes through the OCI referrers API or as a legacy .att tag, and it has moved between cosign releases. It should be an explicit decision rather than an inherited default. AICR sets it on every attest call and has a release-policy test pinning that.
4. No timeout on cosign calls
AICR bounds every cosign attest with timeout --foreground 120s, so a hung registry call fails the release instead of hanging the job.
Worth copying, and worth not copying
Copy: the per-platform subject resolution with fail-closed checks; the OpenVEX guard that validates the document against the v0.2.0 contract before anything is signed, in both source and projection form; the digest-binding step that rewrites VEX product identifiers to pkg:oci/<image>@sha256:<platform-digest>; and the release-policy tests that pin attestation subjects and predicate types so a refactor cannot silently regress them.
Do not copy: AICR's tooling field history. See #2706 — an 8KB changelog accumulated in a field meant for a tool identifier, because the maintenance workflow required producing revision evidence and named nowhere to put it. If nodewright adopts a VEX document, give it a state file with a deletion rule from day one.
Note: nodewright is ahead on one thing
Nodewright runs cosign sign on the image; AICR does not, and publishes attestations only. That gap is currently causing real friction — Kyverno's ImageValidatingPolicy has a known issue specific to signature-less, attestation-only images. Nodewright should keep doing this, and AICR should probably follow.
Success criteria
Related
Summary
Bring nodewright's container evidence to parity with the layout AICR settled on in NVIDIA/aicr#2426: a CycloneDX SBOM and an OpenVEX document attested to each platform manifest digest, with SLSA provenance on the index.
Nodewright is most of the way there already. The gaps are the attestation subject, a missing VEX document, and two cosign flags.
Current state
.github/actions/cosign-sign-sbomalready does the right things in the right order:cosign signon the imagecosign attest --type cyclonedxwith a Syft-generated CycloneDX SBOMactions/attest-build-provenancefor SLSAImages are built multi-arch (
PLATFORMS: linux/amd64,linux/arm64), andoperator-ci.yamlresolves the multi-platform manifest digest and passes that assubject-digest.Gaps
1. Evidence is attached to the index, not the platform manifests
This is the substantive one, and it is the same defect AICR fixed in #2426.
crane digest <tag>returns the index. An SBOM describes exactly one root filesystem, so attaching a single SBOM to a multi-platform index means it cannot honestly describe either child. A consumer who resolveslinux/amd64and enumerates referrers there finds nothing.Fix: resolve each platform manifest with
crane digest --platform linux/<arch>and attest that platform's SBOM to its own digest. AICR's.github/actions/attest-image-from-tagdoes this and fails closed if a platform is missing or resolves to the index digest, which catches a release that silently lost an architecture.2. No VEX document
Nodewright publishes no OpenVEX (
.openvex.jsondoes not exist, zero references in the repo). Without one there is no way to say "this CVE does not apply to us and here is why", so every finding in a scan is either a real problem or unexplained noise.AICR's
.openvex.jsonplus theaicr-managing-openvexskill is the working model, including the constraint learned the hard way in NVIDIA/aicr#2706: the document is signed and published, so it carries triage statements only. Working notes belong in a separate state file with a lifecycle, or they grow without bound.Use OpenVEX, not CycloneDX, for the VEX.
cosign attest --type cyclonedxsets predicate typehttps://cyclonedx.org/bomregardless of whether the payload is an SBOM or a VEX, and a referrer descriptor has no name field. Two CycloneDX documents on one manifest are therefore indistinguishable in a referrers listing, and telling them apart means pulling and decoding every payload. OpenVEX getshttps://openvex.dev/ns, so the listing stays self-describing.3.
--new-bundle-format=trueis not setZero occurrences in the action. This controls whether the attestation publishes through the OCI referrers API or as a legacy
.atttag, and it has moved between cosign releases. It should be an explicit decision rather than an inherited default. AICR sets it on every attest call and has a release-policy test pinning that.4. No timeout on cosign calls
AICR bounds every
cosign attestwithtimeout --foreground 120s, so a hung registry call fails the release instead of hanging the job.Worth copying, and worth not copying
Copy: the per-platform subject resolution with fail-closed checks; the OpenVEX guard that validates the document against the v0.2.0 contract before anything is signed, in both source and projection form; the digest-binding step that rewrites VEX product identifiers to
pkg:oci/<image>@sha256:<platform-digest>; and the release-policy tests that pin attestation subjects and predicate types so a refactor cannot silently regress them.Do not copy: AICR's
toolingfield history. See #2706 — an 8KB changelog accumulated in a field meant for a tool identifier, because the maintenance workflow required producing revision evidence and named nowhere to put it. If nodewright adopts a VEX document, give it a state file with a deletion rule from day one.Note: nodewright is ahead on one thing
Nodewright runs
cosign signon the image; AICR does not, and publishes attestations only. That gap is currently causing real friction — Kyverno'sImageValidatingPolicyhas a known issue specific to signature-less, attestation-only images. Nodewright should keep doing this, and AICR should probably follow.Success criteria
--new-bundle-format=trueset explicitly on every attest call.Related