Menu

Which code checked you: suite_commit and suite_digest

2026-09-24MCP Checkup

A signed result on this site says what the checks observed about a server. It also has to say which checks. Checks change: this month the error-taxonomy check learned to record seven kinds of non-error answer, and the version moved from 0.4.0 to 0.5.0. A result signed last week and a result signed today were produced by different code, and a reader comparing them needs to know that from the result itself, not from our changelog.

So every signed payload since version 0.3 carries two fields.

  • suite_commit is a commit in the public repository, github.com/mcpcheckup/checks.
  • suite_digest is a SHA-512, base64-encoded, of git ls-tree output for the packages/checks directory at that commit.

The evidence page for any signed result since payload version 0.3 prints both, and prints the command that recomputes the second from the first:

git ls-tree -r -z --full-tree <suite_commit> -- packages/checks | openssl dgst -sha512 -binary | openssl base64 -A

Prefix the output with sha512- and compare. The command needs a clone at that commit, git and openssl, and nothing else: no install, no build, no network beyond the clone.

Why a tree listing and not a package

git ls-tree -r prints, for every file under packages/checks, its mode, its type, its blob hash and its path. Hashing that listing identifies the exact bytes of every source file in the check suite, and it does so from committed objects only, so the result cannot depend on the working tree, the operating system, line endings, or which version of Node was installed. Two people at the same commit get the same listing byte for byte. The -z flag matters: it keeps path names as raw bytes rather than quoting them, so a path that is not valid UTF-8 would still be hashed as itself.

Our first attempt at this field did not work that way. Under payload versions 0.1 and 0.2, suite_digest was defined as the integrity hash of a published npm package of the check suite. That package was never published. The value was computed once, on 2026-08-22, pasted into the probe’s configuration file, and left there while the checks changed underneath it more than once. Results signed under those versions show the digest exactly as it was signed, and the evidence page says plainly that it names no tree anyone can fetch and cannot be recomputed. We are not going to re-sign them.

How the probe is stopped from signing a stale value

The lesson from the pasted value was that a hand-maintained identity rots. The current value is never written into a file. It is computed at deploy time from the commit being deployed and passed to the Worker as two variables, by a script that is the only supported way to deploy the probe.

Before it runs anything, that script refuses unless four things hold:

  1. The working tree is clean: no modified tracked file and no untracked file. Ignored files are not checked, but the Worker is built from a git archive export of HEAD, so they never reach it.
  2. HEAD is the freshly fetched tip of the private repository’s main.
  3. The public repository’s main contains a commit whose public packages are tree-equal to HEAD’s. All six public packages, not only the one the digest covers.
  4. The digest of that public commit equals the digest of HEAD.

Then it builds from a git archive export of that commit into a temporary directory, not from the checkout, so that an ignored file imported by a tracked one cannot end up in the bundle while the signed identity names a commit that never contained it. The probe’s own configuration file ships deliberately invalid placeholders for both variables. If anyone deploys around the script, the probe notices the placeholders and refuses to probe at all, rather than signing them into results.

The same script runs in CI on every commit to main and uploads its result as a build artifact named after the commit, with the entry count and byte count of the listing alongside the digest. Before each deploy, the person running it compares the value the script prints with that artifact, and our deploy procedure says not to deploy if they differ. That comparison is a step in our procedure, not something the script enforces.

What is public, and what the public repository is

The private repository holds the site, the probe and the database migrations. Six packages under it are public: the check suite, its fixtures, the canonicalizer, the attestation schema, the verifier script and the SSRF guard. A mirroring script copies exactly those six directories, tree-equal, to the public repository, and a tagged snapshot is pushed only after a review says so. Tags look like snapshot-2026-09-23-a31d17a88c5a. When a snapshot changes the check-suite tree, and so the digest, it also gets a GitHub Release whose notes state the version, what changed, and the old and new digest. The first such Release went out on 2026-09-23 for check suite 0.5.0. Snapshots that only touch the README do not get one.

The public repository also has a SECURITY.md with a private vulnerability reporting channel, and a CONTRIBUTING.md that says what we will and will not take.

What this does not give you

It tells you which check-suite code ran: the packages/checks tree at that commit. The probe that runs it, and the packages installed alongside it, are not part of the digest. It does not, by itself, let you verify the signature. That is a separate check: the verifier script in the public repository checks the signature against the public keys we publish at /.well-known/mcpcheckup-keys.json, and the evidence page prints the two commands for it.

It also does not tell you the code was correct. A digest shows that the check-suite code that ran is the packages/checks tree at that commit. Whether that code drew the right conclusion is a question the code has to answer in the open, which is why it is public.

Where the facts come from

The recompute command was run, on a Linux machine with git 2.34.1 and no access to our deployment, against the commit named on the evidence page linked above, and its output matched the page’s suite_digest byte for byte. The tag names, the Release and its date are on the public repository. The deploy script’s four refusals and its export step are in the private repository’s scripts directory, and each refusal has a test that drives the script against a throwaway repository and asserts that wrangler is never invoked.