---
title: Verifying Published Images
sidebarTitle: Verify images
description: How to verify signatures, provenance, SBOMs, and CVE triage for Sencho Docker images.
---

Every Sencho **release** image is published to two registries with identical content: Docker Hub (`saelix/sencho`) and GitHub Container Registry (`ghcr.io/studio-saelix/sencho`). Release tags are signed and carry verifiable supply-chain artifacts on both registries. The `cosign verify` examples below use the Docker Hub path; substitute `ghcr.io/studio-saelix/sencho:<tag>` to verify the GHCR copy.

<Note>
Pre-merge preview tags (`pr-*`, `preview-*` on Docker Hub) are maintainer-published tester builds. They are **not** cosign-signed and have no SBOM or VEX attestations. Use them only for feature validation before merge; pin production to a release semver tag and verify signatures as below.
</Note>

## Prerequisites

Install the following tools once:

```bash
# cosign (Sigstore)
brew install cosign          # macOS
# or: https://github.com/sigstore/cosign/releases

# syft (optional, for inspecting SBOM content)
brew install syft
# or: https://github.com/anchore/syft/releases

# Trivy (optional, for local vulnerability scan with VEX)
brew install trivy
# or: https://github.com/aquasecurity/trivy/releases
```

## Verify the image signature

Every **release** tag is signed with cosign keyless signing via GitHub Actions OIDC. No private key is stored anywhere; the signing identity is bound to the GitHub Actions workflow that published the image. Preview tags (`pr-*`, `preview-*`) are not signed.

```bash
cosign verify saelix/sencho:<tag> \
  --certificate-identity-regexp "https://github.com/studio-saelix/sencho/.*" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com
```

A successful verification prints the verified payload and exits 0. The Rekor transparency log entry is included in the output; you can also query it directly:

```bash
cosign tree saelix/sencho:<tag>
```

## Verify SLSA provenance

BuildKit produces an SLSA v1 provenance attestation during every multi-arch build. Retrieve and verify it:

```bash
cosign verify-attestation \
  --type slsaprovenance \
  --certificate-identity-regexp "https://github.com/studio-saelix/sencho/.*" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  saelix/sencho:<tag> | jq -r '.payload' | base64 -d | jq
```

## Retrieve the CycloneDX SBOM

```bash
cosign verify-attestation \
  --type cyclonedx \
  --certificate-identity-regexp "https://github.com/studio-saelix/sencho/.*" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  saelix/sencho:<tag> | jq -r '.payload' | base64 -d | jq
```

Alternatively, download `sbom.cdx.json` directly from the GitHub Release assets for the matching version.

## Retrieve the SPDX SBOM

```bash
cosign verify-attestation \
  --type spdxjson \
  --certificate-identity-regexp "https://github.com/studio-saelix/sencho/.*" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  saelix/sencho:<tag> | jq -r '.payload' | base64 -d | jq
```

`sbom.spdx.json` is also available as a GitHub Release asset.

## Retrieve the VEX document

The OpenVEX document explains Sencho's triage decisions for any CVEs that appear in vendored dependencies but are not reachable via Sencho's usage of those dependencies:

```bash
cosign verify-attestation \
  --type openvex \
  --certificate-identity-regexp "https://github.com/studio-saelix/sencho/.*" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  saelix/sencho:<tag> | jq -r '.payload' | base64 -d | jq
```

`sencho.openvex.json` is also available as a GitHub Release asset and at `security/vex/sencho.openvex.json` in the source repository.

## Run a local vulnerability scan with VEX applied

To reproduce the same scan that gates every release:

```bash
# Download the VEX document
curl -Lo sencho.openvex.json \
  https://github.com/studio-saelix/sencho/releases/latest/download/sencho.openvex.json

# Scan with VEX applied (exit 1 if any unresolved HIGH/CRITICAL CVE)
trivy image \
  --vex sencho.openvex.json \
  --severity HIGH,CRITICAL \
  --exit-code 1 \
  saelix/sencho:<tag>
```

A clean result means no unresolved HIGH or CRITICAL CVEs. CVEs listed as `not_affected` in the VEX document are suppressed with their justification visible in the VEX file.

<Note>
Trivy 0.36.0 or later is required for `--vex` flag support.
</Note>

## Available tags

### Release tags

Each release publishes two moving tags plus one immutable tag, so you can choose how aggressively you want to track updates:

| Tag | Example | Updates on |
|---|---|---|
| `latest` | `saelix/sencho:latest` | Every release |
| `X.Y` | `saelix/sencho:0.42` | Every patch release in the `0.42.x` line |
| `X.Y.Z` | `saelix/sencho:0.42.7` | Never (immutable) |

For production, pin to `X.Y.Z` or `X.Y` and verify the signature on every pull. For staging or development, `latest` is fine.

### Integration tag (post-merge, pre-release)

Every push to `main` publishes the current integration build so you can pull and smoke-test the exact code queued for the next release before it is ever tagged. This tag is GHCR-only, published to a separate package from the release image, and carries no signature, SBOM, or VEX attestation.

| Tag | Example | Updates on |
|---|---|---|
| `dev` | `ghcr.io/studio-saelix/sencho-dev:dev` | Every push to `main` |
| `dev-<sha>` | `ghcr.io/studio-saelix/sencho-dev:dev-a1b2c3d` | Never (immutable per build) |

<Note>
The `:dev` image is not for production. It is unsigned, is never published to Docker Hub, and does not carry the SBOM, provenance, or VEX attestations described above.
</Note>

### Preview tags (pre-merge only)

Maintainers publish these from open PRs for external validation. They are unsigned and not for production.

| Tag | Example | Updates on |
|---|---|---|
| `pr-<N>` | `saelix/sencho:pr-1526` | Each re-run of the preview workflow for that PR |
| `preview-<sha>` | `saelix/sencho:preview-abc1234` | Never (immutable per build) |
