feat(e9): publish signed hub OCI image - #170
Conversation
GSTACK-Checkpoint: 2026-07-14/e9-hub-oci-publication#1 Signed-off-by: Gnani Rahul <gnani.nutakki@gmail.com>
📝 WalkthroughWalkthroughThe release workflow now publishes and signs an immutable multi-platform hub image, attaches provenance and SBOM attestations, verifies release archives locally, and uploads image evidence. Documentation and policy tests enforce digest-only consumption and fail-closed release behavior. ChangesHub OCI publication
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant ReleaseTag
participant ReleaseWorkflow
participant GHCR
participant Cosign
participant GitHubAttestations
ReleaseTag->>ReleaseWorkflow: Start signed release
ReleaseWorkflow->>GHCR: Push amd64 and arm64 hub image
ReleaseWorkflow->>Cosign: Sign and verify manifest digest
ReleaseWorkflow->>GitHubAttestations: Attach provenance and SPDX SBOM
ReleaseWorkflow->>ReleaseTag: Upload digest and verification artifacts
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
tests/scripts/release_hub_image_policy_test.sh (1)
12-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate helper:
assert_text_containsis identical toassert_contains.Both functions have the exact same body. Consolidate into a single helper to avoid confusion for future maintainers.
♻️ Proposed consolidation
-assert_text_contains() { - local text="$1" - local needle="$2" - local description="$3" - - if [[ "$text" != *"$needle"* ]]; then - printf '[release-hub-image] FAIL: %s\n' "$description" >&2 - exit 1 - fi - printf '[release-hub-image] PASS: %s\n' "$description" -}Then replace all
assert_text_containscall sites withassert_contains.Also applies to: 51-61
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/scripts/release_hub_image_policy_test.sh` around lines 12 - 22, Remove the duplicate assert_text_contains helper and retain assert_contains as the single containment assertion helper. Replace every assert_text_contains call site with assert_contains, preserving the existing arguments and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/release.yml:
- Around line 174-181: Update the “Generate hub image SPDX SBOM” workflow step
to resolve and scan the hub image separately for linux/amd64 and linux/arm64
rather than scanning the multi-architecture index via HUB_DIGEST. Produce
distinct platform-specific SPDX files and ensure the corresponding
attestation/publishing steps reference each per-platform SBOM, following the
existing per-arch release pattern.
In `@docs/RELEASE.md`:
- Around line 34-72: Update the “Verify a hub OCI image” workflow to download
the release’s sith_${version}_hub.image.sigstore.json bundle alongside the
digest file, then run cosign verify-blob against the digest file using that
bundle and the exact tag workflow identity before assigning or consuming $image.
Preserve the existing digest-format validation and subsequent image signature,
provenance, and SBOM checks.
In `@hack/verify-release-hub-image.sh`:
- Around line 16-17: Split the readonly declarations for REPOSITORY_ROOT and
DIST_DIRECTORY from their command substitutions: assign each resolved directory
path first, then apply readonly in separate statements. Preserve the existing
path calculations while ensuring a failing cd, especially for the "$2"
distribution directory, propagates under set -e instead of being masked.
---
Nitpick comments:
In `@tests/scripts/release_hub_image_policy_test.sh`:
- Around line 12-22: Remove the duplicate assert_text_contains helper and retain
assert_contains as the single containment assertion helper. Replace every
assert_text_contains call site with assert_contains, preserving the existing
arguments and behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 964bc8ff-e2ca-4b89-aefb-aa6ab14df715
📒 Files selected for processing (8)
.github/workflows/release.ymlMakefileREADME.mddocs/RELEASE.mdhack/verify-release-hub-image.shsessions/2026-07-14-e9-hub-oci-publication.mdtests/scripts/release_hub_image_policy_test.shtools/internal/releasepack/policy_test.go
| - name: Generate hub image SPDX SBOM | ||
| env: | ||
| HUB_DIGEST: ${{ steps.hub_image.outputs.digest }} | ||
| run: | | ||
| set -euo pipefail | ||
| test -n "$HUB_DIGEST" | ||
| syft "${HUB_IMAGE}@${HUB_DIGEST}" \ | ||
| --output "spdx-json=dist/sith_${VERSION}_hub.spdx.json" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the relevant workflow section with line numbers.
sed -n '150,240p' .github/workflows/release.yml
echo
echo "---- syft usage in repo ----"
rg -n "syft .*--platform|syft " .github/workflows . || trueRepository: ArdurAI/sith
Length of output: 4225
🌐 Web query:
Syft default behavior when analyzing a multi-arch image index with no --platform flag documented default linux/amd64
💡 Result:
When analyzing a multi-architecture container image index without an explicitly provided --platform flag, Syft defaults to analyzing the linux/amd64 manifest [1]. While Syft provides a --platform flag to allow users to specify a target architecture (e.g., linux/arm64) [2][3], in the absence of this configuration, the tool automatically targets the linux/amd64 architecture for unspecific image references [1]. Historically, this behavior has been a point of discussion in the community regarding how Syft handles multi-platform images and OCI layouts [4][5][6], but the documented default remains linux/amd64 for multi-arch image indexes [1].
Citations:
- 1: https://oss.anchore.com/docs/guides/sbom/scan-targets/
- 2: https://oss.anchore.com/docs/reference/syft/cli/
- 3: Add platform selection anchore/syft#866
- 4: Support SBOM creation for container image indexes anchore/syft#1683
- 5: Support for
application/vnd.oci.image.index.v1+jsonmanifests in root OCI layout anchore/syft#1545 - 6: Enhance specifying the mandatory "platform" arg for multi arch images anchore/sbom-action#419
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Gather syft platform/docs references from public docs if available via common README snippets in repo references.
rg -n "platform" .github/workflows/release.yml README.md docs . 2>/dev/null | head -n 200 || trueRepository: ArdurAI/sith
Length of output: 38044
🌐 Web query:
Syft analyze multi-platform image index without --platform which manifest is selected default linux/amd64 or current platform
💡 Result:
When analyzing a multi-platform container image index without specifying the --platform flag, Syft does not strictly default to linux/amd64. Instead, Syft uses the architecture of the host machine (the environment where the tool is being executed) as the default platform [1][2]. While some older documentation or specific contexts may have referenced linux/amd64, updates to Syft and its underlying library, stereoscope, have aligned the default behavior to match the host architecture [1][2]. If your host machine is running on an arm64 architecture, Syft will attempt to pull the linux/arm64 manifest by default [1]. Key points regarding this behavior: - Host-based selection: Syft detects the host platform and uses that for manifest selection unless explicitly overridden by the --platform flag [1][2]. - Explicit override: To ensure a specific platform is analyzed, you must use the --platform flag (e.g., --platform linux/amd64), which forces Syft to target that specific manifest regardless of the host environment [3][4]. - Limitations: Syft currently analyzes only the single manifest resolved by the platform selection (either the default host-based one or the user-specified one); it does not automatically aggregate dependencies from all manifests within a multi-platform index into a single SBOM [5][6].
Citations:
- 1: syft produces different output when run with sudo anchore/syft#1391
- 2: Use build architecture as default platform anchore/syft#1394
- 3: https://oss.anchore.com/docs/guides/sbom/scan-targets/
- 4: Add platform selection anchore/syft#866
- 5: Support SBOM creation for container image indexes anchore/syft#1683
- 6: generate attestations for muli-arch signed SBOMs anchore/syft#3562
🌐 Web query:
anchore syft platform flag image index default platform documentation
💡 Result:
When scanning container images with Anchore Syft, the --platform flag is used to specify the operating system and architecture of the image to be analyzed [1][2]. Default Behavior: When scanning multi-platform images or image references that do not specify a platform, Syft defaults to targeting the linux/amd64 manifest [1]. Using the --platform Flag: The --platform flag allows you to override this default and target a specific architecture or OS/architecture combination [1][3]. The flag accepts values in formats similar to those used by Docker, such as: - Architecture only: --platform arm64 - OS/Architecture combination: --platform linux/arm64 - Architecture variants: --platform linux/arm/v8 Configuration: You can also set the platform via the configuration file or an environment variable (SYFT_PLATFORM) [2][4]. Important Limitations: Syft currently analyzes a single specific platform's manifest at a time when scanning an image index (or multi-arch manifest list) [5][6]. It is unable to automatically combine multiple architectures into a single unified SBOM in one command [5]. If you need an SBOM that covers all architectures in a multi-platform image, you must perform separate scans for each desired platform and manage the resulting SBOMs accordingly [7].
Citations:
- 1: https://oss.anchore.com/docs/guides/sbom/scan-targets/
- 2: https://oss.anchore.com/docs/reference/syft/configuration/
- 3: Add platform selection anchore/syft#866
- 4: https://github.com/anchore/syft/wiki/Configuration
- 5: Support SBOM creation for container image indexes anchore/syft#1683
- 6: Support for
application/vnd.oci.image.index.v1+jsonmanifests in root OCI layout anchore/syft#1545 - 7: https://github.com/AgentSecOps/SecOpsAgentKit/blob/main/skills/secsdlc/sbom-syft/SKILL.md
Generate separate SBOMs for each hub platform
syft "${HUB_IMAGE}@${HUB_DIGEST}" resolves and scans only one platform from the multi-arch hub index, but the attestation is published against the index digest itself. That makes the SBOM appear to cover both linux/amd64 and linux/arm64 when it only describes a single manifest. Generate and attest one SBOM per platform instead, matching the existing per-arch release pattern.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/release.yml around lines 174 - 181, Update the “Generate
hub image SPDX SBOM” workflow step to resolve and scan the hub image separately
for linux/amd64 and linux/arm64 rather than scanning the multi-architecture
index via HUB_DIGEST. Produce distinct platform-specific SPDX files and ensure
the corresponding attestation/publishing steps reference each per-platform SBOM,
following the existing per-arch release pattern.
| ## Verify a hub OCI image | ||
|
|
||
| Hub images are published only by a signed Sith release tag and must be consumed by immutable | ||
| manifest digest. A release includes a signed `sith_<version>_hub.image` file whose only line is the | ||
| digest address; do not substitute the convenient version tag or add `latest` to a Helm value. | ||
|
|
||
| ```bash | ||
| tag=vX.Y.Z # use a tag released after hub-image publication is enabled | ||
| version=${tag#v} | ||
| gh release download "$tag" --repo ArdurAI/sith \ | ||
| --pattern "sith_${version}_hub.image" \ | ||
| --dir "sith-$version" | ||
| image=$(cat "sith-$version/sith_${version}_hub.image") | ||
| case "$image" in ghcr.io/ardurai/sith-hub@sha256:*) ;; *) exit 1 ;; esac | ||
| ``` | ||
|
|
||
| Verify the keyless image signature against the exact tag workflow identity, then verify GitHub | ||
| provenance and the SPDX SBOM attestation. These commands require registry access; the later air-gap | ||
| workflow consumes mirrored, pre-verified material rather than weakening this verification boundary. | ||
|
|
||
| ```bash | ||
| identity="https://github.com/ArdurAI/sith/.github/workflows/release.yml@refs/tags/${tag}" | ||
| cosign verify \ | ||
| --certificate-identity "$identity" \ | ||
| --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \ | ||
| "$image" | ||
| gh attestation verify "oci://$image" \ | ||
| --repo ArdurAI/sith \ | ||
| --signer-workflow ArdurAI/sith/.github/workflows/release.yml | ||
| gh attestation verify "oci://$image" \ | ||
| --repo ArdurAI/sith \ | ||
| --signer-workflow ArdurAI/sith/.github/workflows/release.yml \ | ||
| --predicate-type https://spdx.dev/Document/v2.3 | ||
| ``` | ||
|
|
||
| The existing Helm chart remains fail-closed: it accepts this digest and names of pre-materialized | ||
| runtime and migration Secrets only. It neither creates secret data nor supplies a KMS provider, | ||
| database, ingress, or mutable image reference. | ||
|
|
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
Verify the signed digest evidence before consuming it.
The workflow emits sith_<version>_hub.image.sigstore.json, but this guide downloads only sith_<version>_hub.image and never runs cosign verify-blob on it. Download the bundle and verify it against the exact release-tag workflow identity before passing $image to Helm; otherwise the documented “signed digest evidence” check is skipped.
Suggested verification
gh release download "$tag" --repo ArdurAI/sith \
--pattern "sith_${version}_hub.image" \
+ --pattern "sith_${version}_hub.image.sigstore.json" \
--dir "sith-$version"
image=$(cat "sith-$version/sith_${version}_hub.image")
+cosign verify-blob \
+ --bundle "sith-$version/sith_${version}_hub.image.sigstore.json" \
+ --certificate-identity "https://github.com/ArdurAI/sith/.github/workflows/release.yml@refs/tags/${tag}" \
+ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
+ "sith-$version/sith_${version}_hub.image"
case "$image" in ghcr.io/ardurai/sith-hub@sha256:*) ;; *) exit 1 ;; esac📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ## Verify a hub OCI image | |
| Hub images are published only by a signed Sith release tag and must be consumed by immutable | |
| manifest digest. A release includes a signed `sith_<version>_hub.image` file whose only line is the | |
| digest address; do not substitute the convenient version tag or add `latest` to a Helm value. | |
| ```bash | |
| tag=vX.Y.Z # use a tag released after hub-image publication is enabled | |
| version=${tag#v} | |
| gh release download "$tag" --repo ArdurAI/sith \ | |
| --pattern "sith_${version}_hub.image" \ | |
| --dir "sith-$version" | |
| image=$(cat "sith-$version/sith_${version}_hub.image") | |
| case "$image" in ghcr.io/ardurai/sith-hub@sha256:*) ;; *) exit 1 ;; esac | |
| ``` | |
| Verify the keyless image signature against the exact tag workflow identity, then verify GitHub | |
| provenance and the SPDX SBOM attestation. These commands require registry access; the later air-gap | |
| workflow consumes mirrored, pre-verified material rather than weakening this verification boundary. | |
| ```bash | |
| identity="https://github.com/ArdurAI/sith/.github/workflows/release.yml@refs/tags/${tag}" | |
| cosign verify \ | |
| --certificate-identity "$identity" \ | |
| --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \ | |
| "$image" | |
| gh attestation verify "oci://$image" \ | |
| --repo ArdurAI/sith \ | |
| --signer-workflow ArdurAI/sith/.github/workflows/release.yml | |
| gh attestation verify "oci://$image" \ | |
| --repo ArdurAI/sith \ | |
| --signer-workflow ArdurAI/sith/.github/workflows/release.yml \ | |
| --predicate-type https://spdx.dev/Document/v2.3 | |
| ``` | |
| The existing Helm chart remains fail-closed: it accepts this digest and names of pre-materialized | |
| runtime and migration Secrets only. It neither creates secret data nor supplies a KMS provider, | |
| database, ingress, or mutable image reference. | |
| ## Verify a hub OCI image | |
| Hub images are published only by a signed Sith release tag and must be consumed by immutable | |
| manifest digest. A release includes a signed `sith_<version>_hub.image` file whose only line is the | |
| digest address; do not substitute the convenient version tag or add `latest` to a Helm value. | |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/RELEASE.md` around lines 34 - 72, Update the “Verify a hub OCI image”
workflow to download the release’s sith_${version}_hub.image.sigstore.json
bundle alongside the digest file, then run cosign verify-blob against the digest
file using that bundle and the exact tag workflow identity before assigning or
consuming $image. Preserve the existing digest-format validation and subsequent
image signature, provenance, and SBOM checks.
| readonly REPOSITORY_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" | ||
| readonly DIST_DIRECTORY="$(cd "$2" && pwd -P)" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Split readonly declaration from assignment (SC2155).
Under set -e, a failing cd "$2" here would be masked since the exit status of the compound readonly VAR=$(...) statement is that of readonly, not the subshell — leaving DIST_DIRECTORY empty instead of failing immediately with a clear diagnostic.
🔧 Suggested fix
-readonly REPOSITORY_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
-readonly DIST_DIRECTORY="$(cd "$2" && pwd -P)"
+REPOSITORY_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
+readonly REPOSITORY_ROOT
+DIST_DIRECTORY="$(cd "$2" && pwd -P)"
+readonly DIST_DIRECTORY📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| readonly REPOSITORY_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" | |
| readonly DIST_DIRECTORY="$(cd "$2" && pwd -P)" | |
| REPOSITORY_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" | |
| readonly REPOSITORY_ROOT | |
| DIST_DIRECTORY="$(cd "$2" && pwd -P)" | |
| readonly DIST_DIRECTORY |
🧰 Tools
🪛 Shellcheck (0.11.0)
[warning] 16-16: Declare and assign separately to avoid masking return values.
(SC2155)
[warning] 17-17: Declare and assign separately to avoid masking return values.
(SC2155)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@hack/verify-release-hub-image.sh` around lines 16 - 17, Split the readonly
declarations for REPOSITORY_ROOT and DIST_DIRECTORY from their command
substitutions: assign each resolved directory path first, then apply readonly in
separate statements. Preserve the existing path calculations while ensuring a
failing cd, especially for the "$2" distribution directory, propagates under set
-e instead of being masked.
Source: Linters/SAST tools
Summary
Validation
make cimake release-checkmake e2e-ocimake e2e-kind KIND=/Volumes/EXTENDED/MacData/tools/bin/kindmake e2e-isolationReview
Closes #169
Summary by CodeRabbit
New Features
Documentation
Bug Fixes