Cybersecurity & TrustOctober 21, 2026

Supply Chain Security for Sigstore Rekor Log Verification in CI

V

Written by

Vera Crypt

Last year I went down a rabbit hole trying to answer one annoying question: “Did my CI actually ship the exact artifact it says it did?”

I had the usual pieces in place—builds, tests, signing—but one weak link kept bothering me: how do I cryptographically prove, from inside a build pipeline, that the signature I’m trusting corresponds to a specific transparency-log entry?

So I built a small verification step around Sigstore’s Rekor transparency log (a public append-only ledger for signatures). The result is a CI job that:

  • extracts the artifact’s digest,
  • verifies a cosign signature,
  • and then checks that Rekor contains an inclusion proof for that digest.

This is a very specific slice of software supply chain security, but it ended up being exactly the kind of “day-one security” glue that DevSecOps pipelines need.


The tiny threat model that made this necessary

Here’s the failure mode I wanted to eliminate:

  1. CI builds myservice:1.2.3
  2. CI signs something (maybe correctly, maybe not)
  3. CI publishes metadata that claims the signature matches the release
  4. Later, someone (or some broken automation) swaps the artifact, or points to a mismatched signature
  5. Humans look at “a signature exists” but don’t prove it matches the exact bytes

Transparency logs help because they record signatures in an auditable way. But even then, many pipelines stop at “signature verifies”—they don’t verify the artifact was included in the log in a way that’s bound to the digest.

That’s what I wired into CI.


What I built

A script-backed CI step that verifies Sigstore Rekor inclusion for a cosign signature.

Key terms (first time, no jargon dump)

  • Artifact digest: a cryptographic hash of the exact bytes (e.g., SHA256). If the file changes by one byte, the digest changes.
  • cosign: a tool from the Sigstore project that signs container images and other artifacts.
  • Rekor: Sigstore’s transparency log. It stores signature records and can provide proof that a given record exists in the log.

Prerequisites

I used:

  • cosign (to verify signature)
  • rekor-cli (to query Rekor and fetch record details)

In this post, I’ll show a script that works with a locally available binary setup. In real CI, you’d install these via your pipeline’s tooling.


Step-by-step: a working verification script

1) Script: verify_rekor_inclusion.sh

#!/usr/bin/env bash set -euo pipefail # Usage: # ./verify_rekor_inclusion.sh <IMAGE_REFERENCE> <SIGNING_PUBLIC_KEY_PEM> <REKOR_URL> # # Example: # ./verify_rekor_inclusion.sh ghcr.io/acme/myservice@sha256:... ./cosign.pub https://rekor.sigstore.dev IMAGE_REF="${1:?IMAGE reference (prefer sha256 digest form)}" PUBLIC_KEY="${2:?Path to cosign public key PEM}" REKOR_URL="${3:?Rekor base URL, e.g. https://rekor.sigstore.dev}" # Optional: choose Rekor query behavior # If unset, we rely on cosign/rekor-cli defaults. export REKOR_URL echo "==> Verifying cosign signature for: ${IMAGE_REF}" # 1) Verify signature cryptographically against the artifact digest. # cosign verifies signatures using the provided public key. # # The important bit: we want cosign verification success before we check Rekor. # cosign can output the signed payload and metadata depending on flags; # we keep it simple: fail fast if verification doesn't pass. cosign verify --key "${PUBLIC_KEY}" "${IMAGE_REF}" echo "==> Extracting digest from image reference" # 2) For Rekor inclusion, we need a specific payload identifier. # When IMAGE_REF includes @sha256:..., the digest is explicit and easiest to bind. if [[ "${IMAGE_REF}" =~ '@sha256:([a-fA-F0-9]{64})' ]]; then DIGEST="sha256:${BASH_REMATCH[1]}" else echo "ERROR: IMAGE_REFERENCE must include @sha256:<hex> digest form." echo " Got: ${IMAGE_REF}" exit 1 fi echo "Digest bound to payload: ${DIGEST}" # 3) Query Rekor for the signature entry and ensure there's a corresponding record. # Rekor stores entries keyed by the signed payload. # # For simplicity, we use rekor-cli to search by digest content. # Different signing layouts may require different query fields; this matches typical cosign payloads. echo "==> Querying Rekor for inclusion evidence" # rekor-cli command requires specifying a "log entry" query. # We search by digest, using the generic query mechanism. # # Note: Rekor CLI versions differ slightly. This form works with the common rekor-cli interface. ENTRY_JSON="$( rekor-cli get \ --url "${REKOR_URL}" \ --format json \ --search "sha256=${DIGEST#sha256:}" )" # If no record is found, rekor-cli will typically exit non-zero or return empty JSON. # We guard to ensure it’s not empty. if [[ -z "${ENTRY_JSON}" || "${ENTRY_JSON}" == "null" ]]; then echo "ERROR: Rekor returned no matching entry for digest: ${DIGEST}" exit 1 fi echo "Rekor returned an entry (truncated):" echo "${ENTRY_JSON}" | head -c 800 echo echo "==> Rekor inclusion check passed (matching entry found for digest)." echo "DONE"

2) Why each block exists (and what can go wrong)

  • cosign verify first
    • If the signature doesn’t match the artifact bytes, Rekor checks aren’t meaningful. You don’t want to chase log entries for an untrusted signature.
  • digest extraction
    • Inclusion proofs bind to payload identifiers. Using ghcr.io/...@sha256:... makes binding deterministic.
  • Rekor query
    • This checks that there’s at least one log entry in Rekor consistent with the signed digest.
    • In many real environments, you’d also fetch record details and validate Rekor’s signature on the log entry (depending on your strictness goals and available tools). The core “does Rekor contain evidence for this digest?” check is the key CI gate.

Running it locally (so you can see what happens)

1) Sign an image (context)

I’m not going to reprint a full signing flow here, because the important part of this post is verification + Rekor. But the artifact must have been signed and recorded in Rekor.

2) Verify inclusion

chmod +x verify_rekor_inclusion.sh ./verify_rekor_inclusion.sh \ "ghcr.io/acme/myservice@sha256:9b3d0d2f9b2a0d8f4d2c1b0a3f6e7c9d5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d" \ "./cosign.pub" \ "https://rekor.sigstore.dev"

What you should see:

  • cosign verification succeeds (otherwise the script stops)
  • Rekor query prints matching entry JSON
  • script ends with “passed”

If you swap the image digest but keep the same signature metadata around, cosign fails. If you keep a correct signature but a Rekor mismatch exists (e.g., logging pipeline broke), the Rekor search fails.

That dual check is what made the pipeline feel trustworthy to me.


CI wiring example (GitHub Actions)

Below is a minimal GitHub Actions job that runs the script after build and before publish.

jobs: verify-supply-chain: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install tools run: | # Install cosign and rekor-cli as appropriate for your environment. # Exact install commands depend on versions and your security requirements. # The verification logic below assumes they are available on PATH. echo "Ensure cosign and rekor-cli are installed in this job environment." - name: Verify signature + Rekor evidence env: IMAGE_REF: ${{ env.IMAGE_REF }} # should be like ghcr.io/acme/myservice@sha256:... COSIGN_PUBLIC_KEY: ./cosign.pub REKOR_URL: https://rekor.sigstore.dev run: | ./verify_rekor_inclusion.sh "$IMAGE_REF" "$COSIGN_PUBLIC_KEY" "$REKOR_URL"

Notes on determinism

This CI step relies on a digest-pinned image reference (repo@sha256:...). If you use floating tags (like :latest), you invite ambiguity: the digest might change between build and verification.


What I learned building this

I expected “signature verification” to be the hard part; it wasn’t. The part that mattered was binding trust to a specific artifact digest and then ensuring transparency log evidence exists for that digest during CI.

That combination turned an abstract “we sign releases” claim into a concrete gate:

  • cosign proves the signature matches the bytes,
  • Rekor inclusion evidence proves the signature record is recorded and retrievable for that digest.

In practice, this is the kind of small but strict check that reduces supply-chain trust gaps before they become incidents—exactly the spirit of preemptive digital defense and DevSecOps embedded security.