mcptest docs GitHub

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:

FilePurpose
mcptest-<version>-<target>.tar.gz (or .zip)The binary plus LICENSE and NOTICE. One per target triple.
<archive>.sha256Per-archive sha sidecar from the build job.
mcptest-<version>.cdx.jsonCanonical CycloneDX 1.5 SBOM. Same content as mcptest sbom from any platform's binary.
mcptest-<version>.spdx.jsonSPDX 2.3 SBOM generated by syft.
SHA256SUMSAggregate sha for every artifact above, signed alongside.
<file>.sig, <file>.pemCosign keyless signature and certificate, one pair per artifact.
attestation.intoto.jsonlSLSA 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:

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

StepCatches
1. cosign verify-blob on the archiveArchive tampered after publish; wrong identity signed it.
2. cosign verify-blob on the SBOMsSBOM swapped; SHA256SUMS swapped.
3. mcptest sbom --verify + diffEmbedded BOM bytes do not match the published one.
4. SLSA provenanceBinary not built by the mcptest release workflow.
5. SHA256SUMSLocal 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