This document explains how to verify a lain release artifact on a
fresh machine, with no prior context. Each step links to the artifact
that proves the previous one. See docs/SUPPLY_CHAIN.md
for why these checks exist.
The updated release workflow publishes the files below for new tags.
Historical releases predating v0.7.4 may lack checksums, SBOMs, or
provenance; these files are not retroactively generated.
| File | What it is |
|---|---|
lain-<ver>-<target>.tar.gz |
The compressed lain binary |
lain-<ver>-<target>.tar.gz.sha256 |
<hash> <filename> for that tarball |
lain-<ver>-<target>.tar.gz.cdx.json |
CycloneDX SBOM of that tarball |
lain-<ver>-<target>.tar.gz.sigstore.json |
Full signed attestation bundle (build provenance) |
lain-<ver>-<target>.tar.gz.intoto.jsonl |
Signed in-toto envelope extracted from that bundle |
lain-<ver>-<target>.tar.gz.cosign.bundle.json |
Direct cosign keyless signature over the tarball's bytes |
SHA256SUMS |
<hash> <filename> for all three tarballs |
server.json |
MCP registry manifest |
Plus, for each tarball, a build-provenance attestation signed using
GitHub Actions' OIDC identity during the build and discoverable via
gh attestation verify. The attestation is generated by
actions/attest-build-provenance,
which produces provenance meeting SLSA v1.0 Build Level 2 —
the claim the README's "Build Provenance" badge links here to substantiate.
Separately, each tarball also carries a direct cosign keyless
signature (.cosign.bundle.json), produced by
sigstore/cosign-installer
cosign sign-blobusing the same GitHub Actions OIDC identity via Fulcio/Rekor. This is independent of the provenance attestation above: it's a direct statement "this exact blob was signed by this identity," verifiable with only thecosignCLI — nogh/ GitHub API call required, useful for verifying outside a GitHub-aware environment.
The supported <target> triples today are:
x86_64-unknown-linux-gnuaarch64-apple-darwinx86_64-pc-windows-msvc
The example below uses Linux x86_64. Set VER to a release that includes
the files listed above; releases before v0.7.4 do not include them (some, lacking these artifacts entirely, have been removed -- see CHANGELOG.md).
VER='REPLACE_WITH_RELEASE_VERSION'
TARGET=x86_64-unknown-linux-gnu
mkdir lain-verify && cd lain-verify
# The tarball itself
gh release download "v${VER}" \
--repo spuentesp/lain \
--pattern "lain-${VER}-${TARGET}.tar.gz"
# Per-binary checksum
gh release download "v${VER}" \
--repo spuentesp/lain \
--pattern "lain-${VER}-${TARGET}.tar.gz.sha256"
# Aggregate checksum
gh release download "v${VER}" \
--repo spuentesp/lain \
--pattern SHA256SUMSEither check the per-binary sidecar:
sha256sum -c "lain-${VER}-${TARGET}.tar.gz.sha256"
# expected output:
# lain-<version>-x86_64-unknown-linux-gnu.tar.gz: OKOr verify the same hash appears in the aggregate file:
grep "lain-${VER}-${TARGET}.tar.gz" SHA256SUMS \
| sha256sum -cgh attestation verify \
"lain-${VER}-${TARGET}.tar.gz" \
--repo spuentesp/lain
# expected output ends with:
# ✓ Verification succeeded!You can also verify using the downloaded bundle:
gh release download "v${VER}" --repo spuentesp/lain \
--pattern "lain-${VER}-${TARGET}.tar.gz.sigstore.json"
gh attestation verify "lain-${VER}-${TARGET}.tar.gz" \
--repo spuentesp/lain \
--bundle "lain-${VER}-${TARGET}.tar.gz.sigstore.json"The .intoto.jsonl file preserves the signed DSSE envelope for tools that
consume in-toto provenance. Use the full .sigstore.json bundle for the
verification command above; it also contains verification material.
The attestation is signed using GitHub Actions' OIDC identity during the build, binding the artifact to the specific commit and workflow run that produced it. If the tarball was modified in transit, or if a fork's release workflow claimed to produce it, this command fails.
gh release download "v${VER}" --repo spuentesp/lain \
--pattern "lain-${VER}-${TARGET}.tar.gz.cosign.bundle.json"
cosign verify-blob \
--bundle "lain-${VER}-${TARGET}.tar.gz.cosign.bundle.json" \
--certificate-identity-regexp "^https://github.com/spuentesp/lain/\.github/workflows/release\.yml@refs/tags/v.*$" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
"lain-${VER}-${TARGET}.tar.gz"
# expected output ends with:
# Verified OKThis checks the same underlying claim as step 3 (the tarball was built
and signed by this repo's release.yml, via GitHub's OIDC identity),
but only needs the cosign CLI — useful in environments without gh
or GitHub API access.
gh release download "v${VER}" \
--repo spuentesp/lain \
--pattern "lain-${VER}-${TARGET}.tar.gz.cdx.json"
# Human-readable component summary
python -c "
import json, sys
sbom = json.load(open('lain-${VER}-${TARGET}.tar.gz.cdx.json'))
for comp in sbom.get('components', []):
print(f\"{comp.get('type', '?'):>10} {comp.get('name', '?')}@{comp.get('version', '?')}\")
" | sortOr upload to a CycloneDX-aware tool (Dependency-Track, Grype, etc.) for deeper analysis.
tar xzf "lain-${VER}-${TARGET}.tar.gz"
./lain --version
# expected output: lain <version>| Failure | Likely cause | Action |
|---|---|---|
sha256sum -c says FAILED |
Downloaded file corrupted, or wrong artifact | Re-download with gh release download |
gh attestation verify fails |
Wrong artifact, or workflow changed | Cross-check the artifact name matches the release tag |
| SBOM parser errors | File truncated | Re-download; report if reproducible |
lain --version mismatch |
Wrong binary for your platform | Re-check the <target> component |
If the SHA256 and provenance checks both pass but lain --version
mismatches the tag, stop and report — that's an artifact-vs-build
disagreement that should not be possible if the workflow ran cleanly.
docs/SUPPLY_CHAIN.md explains why these checks exist and what they
protect against. scripts/check-release-version.py is the in-repo
gate that ensures the metadata side of every release is consistent
across Cargo.toml, Cargo.lock, server.json,
npm-shim/package.json, and Formula/lain.rb. The
scripts/test_sync_metadata.py smoke test (added in PR #2) catches
the description / homepage / keywords drift that the version check
ignores.