Verifying a published mcptest release
Every tagged release of mcptest ships seven things, every one of them keyed to a verifiable identity: the binary, a CycloneDX SBOM, an SPDX SBOM, a SHA256SUMS file, a per-file cosign signature, a per-file certificate, and a SLSA build-provenance attestation. None of them depend on you trusting Soap Bucket. Every signature is anchored to the GitHub Actions OIDC issuer and the workflow that produced it.
This page walks through verifying a release end to end. The commands work on Linux, macOS, and Windows (WSL).
What ships
For mcptest version <version> the release page at https://github.com/soapbucket/mcptest/releases/tag/v<version> carries:
| File | Purpose |
|---|---|
mcptest-<version>-<target>.tar.gz (or .zip) | The binary plus LICENSE and NOTICE. One per target triple. |
<archive>.sha256 | Per-archive sha sidecar from the build job. |
mcptest-<version>.cdx.json | Canonical CycloneDX 1.5 SBOM. Same content as mcptest sbom from any platform's binary. |
mcptest-<version>.spdx.json | SPDX 2.3 SBOM generated by syft. |
SHA256SUMS | Aggregate sha for every artifact above, signed alongside. |
<file>.sig, <file>.pem | Cosign keyless signature and certificate, one pair per artifact. |
attestation.intoto.jsonl | SLSA L3 build provenance, signed by the GitHub OIDC issuer. |
Step 1: verify the cosign signature on the binary
VERSION=<version>
TARGET=x86_64-unknown-linux-gnu
ARCHIVE="mcptest-${VERSION}-${TARGET}.tar.gz"
cosign verify-blob \
--certificate-identity-regexp '^https://github.com/soapbucket/mcptest/' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
--signature "${ARCHIVE}.sig" \
--certificate "${ARCHIVE}.pem" \
"${ARCHIVE}"
A successful verification prints Verified OK. A failure means either the archive has been tampered with, the signature is for a different file, or someone signed it from a workflow that is not the mcptest release workflow. Treat any of those as "do not run this binary".
The --certificate-identity-regexp is what pins the signature to our workflow. A signature from any other identity (a different repo, a different workflow, a different branch) fails this check by design.
Step 2: verify the SBOMs
The CycloneDX and SPDX SBOMs are signed alongside the binary:
for f in \
"mcptest-${VERSION}.cdx.json" \
"mcptest-${VERSION}.spdx.json" \
SHA256SUMS; do
cosign verify-blob \
--certificate-identity-regexp '^https://github.com/soapbucket/mcptest/' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
--signature "${f}.sig" \
--certificate "${f}.pem" \
"$f"
done
Once the SBOM signatures verify, feed the CycloneDX file into any scanner you like:
trivy sbom "mcptest-${VERSION}.cdx.json"
grype sbom:./mcptest-${VERSION}.cdx.json
Step 3: confirm the embedded SBOM matches the published SBOM
Unpack the binary, then run its own sbom subcommand and compare:
tar -xzf "${ARCHIVE}"
cd "mcptest-${VERSION}-${TARGET}"
./mcptest sbom --verify
./mcptest sbom > runtime.cdx.json
diff -q runtime.cdx.json "../mcptest-${VERSION}.cdx.json"
mcptest sbom --verify re-hashes the embedded BOM bytes at runtime against the SHA the build stamped in. A clean binary returns mcptest sbom: embedded BOM verified. diff produces no output when the embedded BOM byte-for-byte matches the published CycloneDX file.
Step 4: verify the SLSA build provenance
gh attestation verify "${ARCHIVE}" \
--repo soapbucket/mcptest
This confirms the archive was produced by the mcptest release workflow on a managed GitHub Actions runner, from the exact commit and tag the attestation records. The trust path goes:
- the binary,
- which
gh attestationresolves to an in-toto statement, - which was signed by the GitHub Actions OIDC issuer,
- which only mints credentials for our workflow ref.
A modified binary fails this step because its sha no longer matches the attestation. A rebuild from a different commit fails because the attestation subject would change.
Step 5: verify the SHA256SUMS covers what you actually downloaded
sha256sum --check SHA256SUMS
This is a belt-and-braces check on top of the per-file cosign signatures. If you verified the cosign signature on SHA256SUMS in step 2, every sha in this file is also signed.
What this proves, summarized
| Step | Catches |
|---|---|
| 1. cosign verify-blob on the archive | Archive tampered after publish; wrong identity signed it. |
| 2. cosign verify-blob on the SBOMs | SBOM swapped; SHA256SUMS swapped. |
3. mcptest sbom --verify + diff | Embedded BOM bytes do not match the published one. |
| 4. SLSA provenance | Binary not built by the mcptest release workflow. |
| 5. SHA256SUMS | Local download corruption. |
If all five pass, the binary you have was built by the workflow you expect, contains the SBOM you can see, and is byte-identical to what the release publishes. That is the strongest claim a release pipeline can make without you also reproducing the build yourself.
Reproducible build, for the truly paranoid
git clone https://github.com/soapbucket/mcptest
cd mcptest
git checkout v${VERSION}
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct HEAD) \
cargo build --release -p mcptest
sha256sum target/release/mcptest
The SOURCE_DATE_EPOCH step is what the release workflow uses, so the SBOM timestamp matches. The Rust toolchain pin in rust-toolchain.toml fixes the compiler version. With those two inputs identical and Cargo.lock committed, the locally built binary should hash to the same sha as the published one.
See also
- Software Bill of Materials (mcptest sbom). What
mcptest sbomand--verifyactually do. - Release process. What the release workflow does on a tag push.
NOTICE. The human-readable attribution counterpart to the CycloneDX BOM.