Verify a FosterStack Cache release yourself
Check our signature, build provenance, SBOM and vulnerability statements with commands you run yourself. Every command below was run against release 0.2.1, and the output shown is what it printed. No account is needed except where we say so.
What you need
cosign, the gh tool (for the build-provenance steps), and jq. Set the version and your platform once:
$ VER=0.2.1
PLATFORM=linux_arm64 # or linux_amd64, darwin_arm64, darwin_amd64
1. Download the release files
$ gh release download v${VER} --repo fosterstack/cache \
-p checksums.txt -p checksums.txt.bundle \
-p "fscache_${VER}_${PLATFORM}.tar.gz" \
-p "fscache_${VER}_${PLATFORM}.tar.gz.sbom.json" \
-p fosterstack-cache.openvex.json -p release-manifest.json
Downloading from the releases page in a browser works the same.
2. Is the checksums file ours?
This checks the signature on checksums.txt. It needs no GitHub account.
$ cosign verify-blob \
--bundle checksums.txt.bundle \
--certificate-identity-regexp='^https://github.com/fosterstack/cache/' \
--certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
checksums.txt
Verified OK
3. Does your download match it?
$ sha256sum -c <(grep "fscache_${VER}_${PLATFORM}.tar.gz" checksums.txt | grep -v sbom)
fscache_0.2.1_linux_arm64.tar.gz: OK
Steps 2 and 3 together are the important pair. The checksum alone only shows the archive matches a file you downloaded beside it. The signature is what ties that file to us.
4. Is the container image signed by our release workflow?
Pin the workflow and the release tag, not "any workflow in the repository". No account is needed.
$ cosign verify ghcr.io/fosterstack/cache:${VER} \
--certificate-identity-regexp="^https://github.com/fosterstack/cache/.github/workflows/stage-promote.yml@refs/tags/v${VER}$" \
--certificate-oidc-issuer='https://token.actions.githubusercontent.com'
Verification for ghcr.io/fosterstack/cache:0.2.1 --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- Existence of the claims in the transparency log was verified offline
- The code-signing certificate was verified using trusted certificate authority certificates
[{"critical":{"identity":{"docker-reference":"ghcr.io/fosterstack/cache:0.2.1"},"image":{"docker-manifest-digest":"sha256:8df991b5febdf5b4a177325b8af95b6bb6c321059e1715c06cb659c659f5a4f0"},"type":"https://fosterstack.com/attestations/release-manifest/v1"},"optional":{}},{"critical":{"identity":{"docker-reference":"ghcr.io/fosterstack/cache:0.2.1"},"image":{"docker-manifest-digest":"sha256:8df991b5febdf5b4a177325b8af95b6bb6c321059e1715c06cb659c659f5a4f0"},"type":"https://sigstore.dev/cosign/sign/v1"},"optional":{}}]
There is no signing key to steal. The certificate is short-lived and was issued when the workflow ran, and the signature is in a public transparency log. The same command against docker.io/fosterstack/cache:${VER} returns the same digest, sha256:8df991b5febdf5b4a177325b8af95b6bb6c321059e1715c06cb659c659f5a4f0.
5. Where was the image built?
A signature says who signed. Build provenance says what produced the bytes. This step reads GitHub's attestation store, so it needs a token (GH_TOKEN or gh auth login), even though the repository is public.
$ gh attestation verify oci://ghcr.io/fosterstack/cache:${VER} \
--repo fosterstack/cache \
--signer-workflow fosterstack/cache/.github/workflows/stage-image.yml \
--source-ref "refs/tags/v${VER}" \
--format json | jq '.[0].verificationResult | {statement: .statement.predicateType, image: .statement.subject[0].digest, signer: .signature.certificate.subjectAlternativeName, run: .signature.certificate.runInvocationURI}'
{
"statement": "https://slsa.dev/provenance/v1",
"image": {
"sha256": "8df991b5febdf5b4a177325b8af95b6bb6c321059e1715c06cb659c659f5a4f0"
},
"signer": "https://github.com/fosterstack/cache/.github/workflows/stage-image.yml@refs/tags/v0.2.1",
"run": "https://github.com/fosterstack/cache/actions/runs/35554556056/attempts/1"
}
The command exits with status 0 on success. The image digest is the one from step 4, and the run link is the public build that produced it.
6. Where was your download built?
$ gh attestation verify "fscache_${VER}_${PLATFORM}.tar.gz" --repo fosterstack/cache \
--format json | jq -r '.[0].verificationResult.statement.subject[].name, .[0].verificationResult.signature.certificate.subjectAlternativeName'
fscache-fips_0.2.1_linux_amd64.tar.gz
fscache-fips_0.2.1_linux_amd64.tar.gz.sbom.json
fscache-fips_0.2.1_linux_arm64.tar.gz
fscache-fips_0.2.1_linux_arm64.tar.gz.sbom.json
fscache_0.2.1_darwin_amd64.tar.gz
fscache_0.2.1_darwin_amd64.tar.gz.sbom.json
fscache_0.2.1_darwin_arm64.tar.gz
fscache_0.2.1_darwin_arm64.tar.gz.sbom.json
fscache_0.2.1_linux_amd64.tar.gz
fscache_0.2.1_linux_amd64.tar.gz.sbom.json
fscache_0.2.1_linux_arm64.tar.gz
fscache_0.2.1_linux_arm64.tar.gz.sbom.json
checksums.txt
https://github.com/fosterstack/cache/.github/workflows/stage-build.yml@refs/tags/v0.2.1
One statement covers every archive, its SBOM and checksums.txt. Your archive is in the list.
7. Did the whole release chain pass?
Each stage of the release signs its own statement about the exact image. One of them exists only if the final authorization stage checked all the others for this image: the source check, the build, the image assembly, the reproducibility check, each scanner's verdict, and the acceptance tests.
$ gh attestation verify oci://ghcr.io/fosterstack/cache:${VER} \
--repo fosterstack/cache \
--signer-workflow fosterstack/cache/.github/workflows/stage-authorize.yml \
--predicate-type https://fosterstack.com/attestations/release-authorization/v1 \
--format json | jq '.[0].verificationResult | {predicateType: .statement.predicateType, signer: .signature.certificate.subjectAlternativeName}'
{
"predicateType": "https://fosterstack.com/attestations/release-authorization/v1",
"signer": "https://github.com/fosterstack/cache/.github/workflows/stage-authorize.yml@refs/tags/v0.2.1"
}
8. What is inside? The SBOM
Each binary archive has a software bill of materials in SPDX format.
$ jq '{spdxVersion, name, packages: (.packages | length)}' "fscache_${VER}_${PLATFORM}.tar.gz.sbom.json"
{
"spdxVersion": "SPDX-2.3",
"name": "fscache_0.2.1_linux_arm64.tar.gz",
"packages": 13
}
$ jq -r '.packages[0:4][] | "\(.name) \(.versionInfo)"' "fscache_${VER}_${PLATFORM}.tar.gz.sbom.json"
github.com/beorn7/perks v1.0.1
github.com/cespare/xxhash/v2 v2.3.0
github.com/fosterstack/cache v0.2.1
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822
9. Which known vulnerabilities do not apply? The VEX file
The VEX (OpenVEX) file lists vulnerabilities that scanners may report and says why each does not affect this release.
$ jq -r '.statements[] | "\(.vulnerability.name) \(.status) \(.justification)"' fosterstack-cache.openvex.json
CVE-2024-51744 not_affected component_not_present
CVE-2025-60876 not_affected vulnerable_code_not_in_execute_path
CVE-2025-46394 not_affected vulnerable_code_not_in_execute_path
CVE-2024-58251 not_affected vulnerable_code_not_in_execute_path
You do not have to take our word for those. Run your own scanner against the image and give it this file.
10. The release manifest
release-manifest.json lists the evidence for the whole release. It names three image variants (the standard one, debug and fips), each with its digest. The standard one is the digest you verified in steps 4 and 5, at both registries:
$ jq -r '.images[] | "\(.variant) \(.digest)"' release-manifest.json
production sha256:8df991b5febdf5b4a177325b8af95b6bb6c321059e1715c06cb659c659f5a4f0
debug sha256:e2e502f44d8bd9e6967cb160aabeb012e8520ebba0faa88e130143fe534f282e
fips sha256:e125fe3f6ed2c18c478453300ba2b787a42999edf66a39403877efb39865ab1b
$ jq -c '.images[0].refs' release-manifest.json
["ghcr.io/fosterstack/cache@sha256:8df991b5febdf5b4a177325b8af95b6bb6c321059e1715c06cb659c659f5a4f0","docker.io/fosterstack/cache@sha256:8df991b5febdf5b4a177325b8af95b6bb6c321059e1715c06cb659c659f5a4f0"]
$ jq '.verified_statements | length' release-manifest.json
43
That last number is the statements the authorization stage checked, each with its entry in the public transparency log.
What these checks do and do not prove
- A valid signature proves who signed these exact bytes.
- Build provenance proves which repository, workflow and run produced them, which is a public CI run and not a developer machine.
- The SBOM and VEX files are statements we publish. They are as trustworthy as the signature on the release that carries them.
- None of this proves the software has no bugs or that no vulnerability exists. It proves the release is the one we built, in the way we describe.
The workflow names above belong to release 0.2.1. They are version-scoped, so for older releases use the verification guide.
Related: verify a container image signature with cosign and how releases are built and signed.