Skip to main content

Verify the distribution

Verify Stacklok-built container images through the Replicated proxy with your license and the open source Cosign tool. Stacklok signs each image through GitHub OIDC and records the signature in Sigstore's Rekor transparency log. Each image also includes an SPDX software bill of materials (SBOM), SLSA build provenance, and an OpenVEX vulnerability assessment.

Replace these placeholders throughout:

  • <LICENSE_ID> - your Stacklok-issued license ID.
  • <YOUR_EMAIL> - the email on your customer record.
  • <IMAGE> - an image name from the table in Which images are signed (for example, operator).
  • <VERSION> - the platform version (the chart appVersion) you installed.
  • <NAMESPACE> and <SELECTOR> - the namespace and label selector for a running workload, when you read a digest from the cluster.

Prerequisites

  • cosign v2.x or v3.x.
  • An OCI client to authenticate and resolve image digests: docker, podman, oras, or crane. Cosign uses the client's credential store.
  • jq and base64 to inspect decoded attestation payloads.
  • Outbound HTTPS to image-proxy.stacklok.com, plus fulcio.sigstore.dev, rekor.sigstore.dev, and tuf-repo-cdn.sigstore.dev for Sigstore's roots.
  • A valid, non-expired license. The proxy authenticates every request against it. Your license is available from the Stacklok install portal at install.stacklok.com.

Step 1: Authenticate to the proxy

Log in with your customer email as the username and your license ID as the password. The client writes the credentials to ~/.docker/config.json or $REGISTRY_AUTH_FILE, where Cosign can use them.

# Docker
docker login image-proxy.stacklok.com -u "<YOUR_EMAIL>"

# Podman
podman login image-proxy.stacklok.com -u "<YOUR_EMAIL>"

# oras
oras login image-proxy.stacklok.com -u "<YOUR_EMAIL>" --password-stdin <<<"<LICENSE_ID>"

# crane
crane auth login image-proxy.stacklok.com -u "<YOUR_EMAIL>" --password-stdin <<<"<LICENSE_ID>"
Keep the license ID out of your shell history

Read the license ID into a temporary variable, then pipe it to docker login:

read -rsp 'License ID: ' LICENSE_ID
printf '\n'
printf '%s' "$LICENSE_ID" | docker login image-proxy.stacklok.com \
-u "<YOUR_EMAIL>" --password-stdin
unset LICENSE_ID

Step 2: Resolve the image digest

Resolve the digest of the image you deployed. The install portal provides the registry path for your release.

BASE=image-proxy.stacklok.com/proxy/stacklok-enterprise/ghcr.io/stacklok/stacklok-enterprise

# Read the digest from the running workload.
kubectl -n <NAMESPACE> get pod -l <SELECTOR> \
-o jsonpath='{.items[0].status.containerStatuses[0].imageID}'

# Resolve with Docker without pulling the image.
docker buildx imagetools inspect "$BASE/<IMAGE>:<VERSION>" \
--format '{{json .Manifest.Digest}}' | tr -d '"'

# Pull and inspect with Docker.
docker pull -q "$BASE/<IMAGE>:<VERSION>" >/dev/null \
&& docker inspect --format '{{index .RepoDigests 0}}' "$BASE/<IMAGE>:<VERSION>"

# oras
oras resolve "$BASE/<IMAGE>:<VERSION>"

# crane
crane digest "$BASE/<IMAGE>:<VERSION>"

Set DIGEST=sha256:... from whichever command you used.

note

Cosign can resolve and verify a tag directly. Verifying the deployed digest ensures that you check the exact image running in your cluster.

Step 3: Verify the image

Verify that GitHub's OIDC issuer signed the image through one of Stacklok's release workflows:

IDENTITY='^https://github\.com/stacklok/stacklok-enterprise-platform/\.github/workflows/_release-(image|toolhive-cloud-ui|upstream-repackaged)\.yml@.*$'
ISSUER='https://token.actions.githubusercontent.com'
IMAGE="$BASE/<IMAGE>@${DIGEST}"

Verify the signature

cosign verify \
--certificate-identity-regexp="$IDENTITY" \
--certificate-oidc-issuer="$ISSUER" \
"$IMAGE"

A successful run prints the validated claims and signed payloads.

Verify the SBOM attestation

Stacklok ships an SPDX SBOM as a signed in-toto attestation on every image:

cosign verify-attestation \
--type spdxjson \
--certificate-identity-regexp="$IDENTITY" \
--certificate-oidc-issuer="$ISSUER" \
"$IMAGE" \
| jq -r '.payload' | base64 -d \
| jq '{name: .predicate.name, spdxVersion: .predicate.spdxVersion, packages: (.predicate.packages | length)}'

The pipeline decodes the SPDX document and prints its name, SPDX version, and package count. Remove the final jq filter to inspect the full SBOM.

Verify the SLSA build provenance

Every image carries SLSA build L3 provenance as a signed attestation (predicate type https://slsa.dev/provenance/v1):

cosign verify-attestation \
--type slsaprovenance1 \
--certificate-identity-regexp="$IDENTITY" \
--certificate-oidc-issuer="$ISSUER" \
"$IMAGE" \
| jq -r '.payload' | base64 -d \
| jq '.predicate.runDetails.builder.id, .predicate.buildDefinition.buildType'

--type slsaprovenance1 selects the SLSA provenance v1.0 schema. Confirm that runDetails.builder.id identifies a trusted stacklok-enterprise-platform/.github/workflows/... reusable workflow.

Verify the OpenVEX attestation

Every image carries Stacklok's OpenVEX document as a signed attestation (predicate type https://openvex.dev/ns). It records the assessed status of known CVEs (for example, not_affected with a justification) so your scanner can suppress false positives:

cosign verify-attestation \
--type openvex \
--certificate-identity-regexp="$IDENTITY" \
--certificate-oidc-issuer="$ISSUER" \
"$IMAGE" \
| jq -r '.payload' | base64 -d \
| jq -r '.predicate.statements[] | "[\(.status)] \(.vulnerability.name) \(.justification // .impact_statement // "")"'

The jq pipeline lists each CVE and its assessment. To use the document with an OpenVEX-compatible scanner, write the decoded predicate to a file:

cosign verify-attestation \
--type openvex \
--certificate-identity-regexp="$IDENTITY" \
--certificate-oidc-issuer="$ISSUER" \
"$IMAGE" \
| jq -r '.payload' | base64 -d | jq '.predicate' > vex.json

Inspect what is attached (optional)

If you have oras, it lists every artifact attached to the image (signature, SBOM, attestations) in one call:

oras discover "$IMAGE"

Which images are signed

Image (<IMAGE>)Signing workflow
thv, operator, proxyrunner, vmcp, registry-api, toolhive-enterprise_release-image.yml
ai-gateway-operator, ai-gateway-main-processor, ai-gateway-api-key-service_release-image.yml
cloud-ui_release-toolhive-cloud-ui.yml
ai-gateway-controller, ai-gateway-extproc, envoy-gateway, envoy-ratelimit, presidio-analyzer_release-upstream-repackaged.yml
Stacklok License ManagerUpstream signature

Stacklok repackages and signs Envoy Gateway, Envoy Ratelimit, and Presidio. The Stacklok License Manager retains its upstream image and signature. It appears as stacklok-license-manager-* in kubectl get pods and is pulled by digest from proxy.replicated.com/library/replicated-sdk-image.

Verify across all images

To check every Stacklok-signed image for a release in one pass:

BASE=image-proxy.stacklok.com/proxy/stacklok-enterprise/ghcr.io/stacklok/stacklok-enterprise
IDENTITY='^https://github\.com/stacklok/stacklok-enterprise-platform/\.github/workflows/_release-(image|toolhive-cloud-ui|upstream-repackaged)\.yml@.*$'
ISSUER='https://token.actions.githubusercontent.com'
VERSION='<VERSION>'

for IMAGE in thv operator proxyrunner vmcp registry-api toolhive-enterprise cloud-ui \
ai-gateway-operator ai-gateway-main-processor ai-gateway-api-key-service \
ai-gateway-controller ai-gateway-extproc \
envoy-gateway envoy-ratelimit presidio-analyzer; do
# Digest lookup via docker buildx (swap for `crane digest` or `oras resolve`).
DIGEST=$(docker buildx imagetools inspect "$BASE/$IMAGE:$VERSION" \
--format '{{json .Manifest.Digest}}' 2>/dev/null | tr -d '"')
[ -n "$DIGEST" ] || { echo "SKIP $IMAGE (no $VERSION)"; continue; }
if cosign verify --certificate-identity-regexp="$IDENTITY" \
--certificate-oidc-issuer="$ISSUER" "$BASE/$IMAGE@$DIGEST" >/dev/null 2>&1; then
echo "OK $IMAGE@$DIGEST"
else
echo "FAIL $IMAGE@$DIGEST"
fi
done

Next steps

Troubleshooting

no signatures found or 500 on a /referrers/ request

You are likely on an older proxy deployment. The proxy must return 404 on the OCI Referrers endpoint so Cosign falls back to the tag schema. Contact Stacklok.

401 Unauthorized on login or pull

Use your customer email as the username and <LICENSE_ID> as the password. Confirm that your license is valid and assigned to the channel serving the version.

certificate identity ... does not match

Trust only a github.com/stacklok/stacklok-enterprise-platform/.github/workflows/... identity signed by https://token.actions.githubusercontent.com. Contact Stacklok for any other identity or issuer.

no matching attestation for SLSA provenance

Request the provenance for your release from Stacklok.