Build1 publisher2 min readPublished
Signed payload's subject binds soit's image to a digest, not directly to its v1.0.0 tag
Reading soit-ai/soit v1.0.0 out of ghcr.io by hand shows how far a container signature reaches. It covers one digest, and the release tag survives as a string in the build predicate that nothing compares for you.
The Engineer · Build desk

What happened
- The v1.0.0 tag of ghcr.io/soit-ai/soit/server resolves to an 856-byte OCI image index, not to the image manifest, according to a hand walk of the registry published on dev.to.
- That index holds a 2,589-byte linux/amd64 manifest and a 565-byte unknown/unknown entry, which the post identifies as BuildKit's own attestation manifest for the amd64 digest.
- The registry's referrers API returns MANIFEST_UNKNOWN for both the index and the platform digest, and the bundles surface only through the spec's colon-to-dash tag fallback.
- Behind that fallback tag sit two Sigstore bundles, a SLSA provenance attestation and an SPDX SBOM, created within the same minute on 5 August 2026.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint A client that pulled the linux/amd64 manifest by digest has nothing in the signed payload to match, because the only subject written down is the index digest; it has to walk back up to the index before any attestation applies to what it is running.
- decision Anyone writing an admission policy for these images has to decide whether to assert externalParameters.workflow.ref, because signature validity and subject match can both pass without any check ever reading the release tag.
- exposure Any tool that treats the referrers error as an answer will report a properly signed image as unsigned, and the fix is a second lookup against the dash-form tag.
The amd64 manifest one level below the index names an 8,769-byte config blob and 12 layers, 684,216,450 bytes compressed [6]. That is 652.5 MiB [19]. The post counts three hashes with three meanings at that point, says the signature covers the first, and notes a fourth hash arriving later that is none of the three [7].
What the signature covers is the base64 in-toto payload inside the DSSE envelope, and nothing else in the bundle [14]. The payload is an in-toto Statement v1 whose subject names ghcr.io/soit-ai/soit/server at digest 96b80ae1...5929 [15]. The post describes that subject as the one place where the tie between this signature and that image is actually written down [12].
The string v1.0.0 lives a level deeper, in the predicate: externalParameters.workflow.ref is refs/tags/v1.0.0, with repository https://github.com/soit-ai/soit and path .github/workflows/release.yml [16]. Beside it sit event_name push, repository_id 910429753, and runner_environment github-hosted [17]. A valid signature proves the payload was not edited, and a matching subject proves which digest the payload describes. Whether refs/tags/v1.0.0 matches the tag you asked for is a third question, answered by reading the predicate [14][16].
Downloading the bundle blob and hashing it returns eae5d902...dd86, the digest that was requested; in a content-addressed registry that step verifies itself, and the post notes no trust is required yet [13]. The two bundles come to 1,626 bytes between them [21]. Provenance and SBOM were created 9.058 seconds apart, at 16:14:12.946Z and 16:14:22.004Z on 5 August 2026 [20][11].
For this walk to describe someone else's image, a few things have to hold. The image has to be a multi-platform index built by BuildKit; the 565-byte unknown/unknown entry with its attestation-manifest annotation comes from that build path [5]. A single-platform image has no index, so the tag digest and the manifest digest are one question instead of two. The registry has to be one that answers referrers with MANIFEST_UNKNOWN [8], because a registry implementing referrers natively returns the bundles without the tag fallback. And the provenance has to come from a GitHub-hosted runner and a workflow file, which is what puts a ref field in the predicate at all [16][17].
The post's headline says gh attestation verify "said yes to an image we never released" [22]. The available text breaks off inside the predicate's resolvedDependencies, at git+https://github.com/soit-ai/soit [18], so the demonstration behind that headline and the post's own enumeration of the four questions are named but not shown [1].
What to watch
- The post's later section on the two other attestation tags, sha256-3a5b3b1a... and sha256-236201a5..., and which digests they attest.
- Whether ghcr.io begins answering the OCI referrers API directly, which would take the colon-to-dash tag fallback out of every verification path.
- Whether GitHub's CLI exposes a check on externalParameters.workflow.ref so a policy can assert the release tag it expects.