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
21 changes: 16 additions & 5 deletions .agents/skills/sbom/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,28 @@ The OpenShell SBOM tooling produces source-tree CycloneDX JSON SBOMs using Syft,

SBOMs are **release artifacts only** -- they are generated on demand and not committed to the repository. Output lands in `deploy/sbom/output/` (gitignored).

Release Dev and Release Tag image builds separately embed cargo-auditable
metadata in the staged gateway and supervisor binaries. This metadata describes
the binary's Rust dependency graph and lets Syft discover Cargo packages from
the binary itself. It is not a complete image SBOM and is not an OCI SBOM
attestation; publishing such an attestation remains separate work.
Pushed gateway and supervisor images carry an SPDX SBOM and minimal SLSA provenance as OCI attestations. Release Dev and Release Tag binaries also embed cargo-auditable metadata, so their image SBOMs include linked Rust crates.

## Prerequisites

- `mise install` has been run (installs Syft and other tools)
- The repository is checked out at the root

## Inspecting an Image SBOM

BuildKit uses its default Syft scanner and attaches one SPDX document per platform. Read one without pulling the image:

```bash
docker buildx imagetools inspect ghcr.io/nvidia/openshell/gateway:latest \
--format '{{ json (index .SBOM "linux/amd64").SPDX }}'
```

Validate the final attestation, requiring a Cargo package for an auditable image:

```bash
tasks/scripts/verify-image-sbom.sh ghcr.io/nvidia/openshell/gateway:latest --require-cargo
```

## Inspecting an Auditable Image Binary

Opt into auditable metadata when staging a local image binary:
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,10 @@ jobs:
volumes:
- /var/run/docker.sock:/var/run/docker.sock
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs['checkout-ref'] || github.sha }}

- name: Log in to GHCR
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin

Expand All @@ -309,3 +313,6 @@ jobs:
--prefer-index=false \
-t "${image}:${{ needs.resolve.outputs.image_tag_base }}" \
"${refs[@]}"

- name: Verify merged manifest SBOM attestation
run: tasks/scripts/verify-image-sbom.sh "ghcr.io/nvidia/openshell/${{ inputs.component }}:${{ needs.resolve.outputs.image_tag_base }}" ${{ inputs.auditable && '--require-cargo' || '' }}
14 changes: 12 additions & 2 deletions architecture/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ binary. That section holds data rather than symbols, so it survives the
workspace's `strip = true` release profile, and Syft can catalog the crates
present in image binaries instead of inferring them from the source tree. This
is a different artifact from the source SBOM produced by `syft dir:.` in
`tasks/sbom.toml`, which describes the checkout, and from an OCI SBOM
attestation, which remains out of scope.
`tasks/sbom.toml`, which describes the checkout, and from the image SBOM
attestation below, which describes a published image.

Only release image builds are auditable. `docker-build.yml` and
`rust-native-build.yml` take an `auditable` input that defaults to false, so PR
Expand All @@ -151,6 +151,16 @@ be misidentified as `rustc`. Auditable builds are verified by scanning the built
binary with Syft and requiring at least one decoded Cargo package; the check runs
only for those builds.

Pushed Docker images carry minimal SLSA provenance and a per-platform SPDX SBOM
generated by BuildKit's default Syft scanner. The registry exporter uses OCI
media types and `oci-artifact=true`, so each attestation identifies its subject.
GHCR exposes these through the image index because it has no referrers API.

Attestations require a registry-backed image index. Local builds therefore keep
`--provenance=false`, and Podman builds carry neither attestation.
`tasks/scripts/verify-image-sbom.sh` verifies the merged multi-arch tag and also
requires Cargo packages for auditable Release Dev and Release Tag images.

Runtime layout:

- **Gateway**: `gcr.io/distroless/cc-debian13:nonroot` base, GNU-linked binary at
Expand Down
36 changes: 36 additions & 0 deletions docs/security/verifying-images.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
title: "Verify the Contents of Published OpenShell Images"
sidebar-title: "Verify Image Contents"
slug: "security/verify-image-contents"
description: "Read the SBOM attestation attached to published gateway and supervisor images to audit what each image contains."
keywords: "Generative AI, Cybersecurity, Supply Chain, SBOM, Container Images"
position: 2
---

Published gateway and supervisor images carry one SPDX SBOM per platform as OCI attestations.

## Inspect an Image

Read a platform's document without pulling the image:

```shell
docker buildx imagetools inspect ghcr.io/nvidia/openshell/gateway:latest --format '{{ json (index .SBOM "linux/amd64").SPDX }}'
```

List the packages instead of the full document:

```shell
docker buildx imagetools inspect ghcr.io/nvidia/openshell/gateway:latest --format '{{ range (index .SBOM "linux/amd64").SPDX.packages }}{{ .name }}@{{ .versionInfo }}{{ println }}{{ end }}'
```

The same commands work for `ghcr.io/nvidia/openshell/supervisor`.

## Coverage

Every SBOM lists the base-image packages. Release Dev and Release Tag images also list the Rust crates compiled into their OpenShell binary.

<Note>
OpenShell also publishes minimal SLSA provenance. It records how BuildKit produced the image, including its source revision, build platform, and base-image materials, without the extra build parameters included by full provenance.
</Note>
15 changes: 10 additions & 5 deletions tasks/scripts/docker-build-image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -168,14 +168,19 @@ if [[ "${IS_FINAL_IMAGE}" == "1" ]]; then
TAG_ARGS=(-t "${IMAGE_NAME}:${IMAGE_TAG}")
fi

ATTESTATION_ARGS=(--provenance=false)
OUTPUT_ARGS=()
if [[ -n "${DOCKER_OUTPUT:-}" ]]; then
OUTPUT_ARGS=(--output "${DOCKER_OUTPUT}")
elif [[ "${IS_FINAL_IMAGE}" == "1" ]]; then
if [[ "${DOCKER_PUSH:-}" == "1" ]]; then
OUTPUT_ARGS=(--push)
elif [[ "${DOCKER_PLATFORM:-}" == *","* ]]; then
OUTPUT_ARGS=(--push)
if [[ "${DOCKER_PUSH:-}" == "1" || "${DOCKER_PLATFORM:-}" == *","* ]]; then
if ce_is_docker; then
# Attestations require a registry-backed image index.
ATTESTATION_ARGS=(--provenance=mode=min --attest type=sbom)
OUTPUT_ARGS=(--output "type=image,push=true,oci-mediatypes=true,oci-artifact=true")
else
OUTPUT_ARGS=(--push)
fi
else
OUTPUT_ARGS=(--load)
fi
Expand All @@ -191,7 +196,7 @@ ce_build \
-f "${DOCKERFILE}" \
--target "${DOCKER_TARGET}" \
${TAG_ARGS[@]+"${TAG_ARGS[@]}"} \
--provenance=false \
${ATTESTATION_ARGS[@]+"${ATTESTATION_ARGS[@]}"} \
"$@" \
${OUTPUT_ARGS[@]+"${OUTPUT_ARGS[@]}"} \
.
53 changes: 53 additions & 0 deletions tasks/scripts/verify-image-sbom.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env bash

# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

set -euo pipefail

# Validate the final image's SBOM attestation and, for auditable builds, require
# at least one Cargo package discovered from the binary metadata.

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/container-engine.sh"

usage() {
echo "Usage: verify-image-sbom.sh <image-ref> [--require-cargo]" >&2
}

IMAGE=${1:-}
REQUIRE_CARGO=${2:-}
if [[ -z "${IMAGE}" || $# -gt 2 || ( -n "${REQUIRE_CARGO}" && "${REQUIRE_CARGO}" != "--require-cargo" ) ]]; then
usage
exit 2
fi

if ! ce_is_docker; then
echo "Error: SBOM attestations are produced on the Docker/buildx path; ${CONTAINER_ENGINE} has no imagetools equivalent" >&2
exit 2
fi

echo "==> Inspecting SBOM attestation of ${IMAGE}"
SBOM_JSON="$(ce buildx imagetools inspect "${IMAGE}" --format '{{ json .SBOM }}')"
COUNTS="$(
jq -r '
[.. | objects | .SPDX? | select(type == "object")] as $documents
| [
($documents | length),
([$documents[] | .. | strings | select(startswith("pkg:cargo/"))] | length)
]
| @tsv
' <<<"${SBOM_JSON}"
)"
read -r SPDX_COUNT CARGO_COUNT <<<"${COUNTS}"

if [[ "${SPDX_COUNT}" -eq 0 ]]; then
echo "Error: ${IMAGE} carries no SPDX SBOM" >&2
exit 1
fi
if [[ "${REQUIRE_CARGO}" == "--require-cargo" && "${CARGO_COUNT}" -eq 0 ]]; then
echo "Error: ${IMAGE} SBOM contains no Cargo packages" >&2
exit 1
fi

echo "SBOM attestation verified: SPDX=${SPDX_COUNT}, Cargo=${CARGO_COUNT}"
Loading