Signed releases and verification¶
Editions: OSS, Cloud, Enterprise. Unless stated otherwise, everything on this page ships in OSS.
Version tags published by scripts/release.sh run the GitHub Actions
Release workflow. Since v0.15.0, that workflow
uses GitHub OIDC and Sigstore to sign build provenance for the attached release
assets. No developer signing key or additional repository secret is needed.
The provenance covers CLI binaries, Python packages, the Helm chart, installers,
Compose configuration, SBOMs, and SHA256SUMS. The attached
preloop-v<version>.intoto.jsonl bundle contains the signed statement, its
certificate, and transparency evidence. The workflow verifies every staged
payload and the manifest before creating the GitHub release; a verification
failure stops publication. Checksums cover every payload, excluding the
manifest itself and the bundle generated afterward.
This attestation proves the asset digest and the GitHub workflow identity. It is not a promise that the source or dependencies are free of vulnerabilities. It does not cover GitHub's automatically generated source archives or container images referenced by the Compose file. The Python source distribution attached by the workflow is covered. Windows Authenticode is separate: it is applied only when SignPath is configured. See the code signing policy.
Publishing through release.sh¶
Follow the protected-branch release process. Start from a clean checkout and prepare a release PR:
Review the version/changelog changes, run release validation, and merge the PR.
Check out its exact merge commit and run --finalize --target-commit SHA --push-tag
with the same version. Pushing the v* tag
starts the release workflow, including automatic attestation. The helper
refuses to push unless create-release in this checkout attests
release-assets/*, stages preloop-v<version>.intoto.jsonl, and passes
release notes through a file. That job creates a draft only after the signed
bundle is on disk, then publishes the draft only after GitHub lists
preloop-v<version>.intoto.jsonl among the release assets. A failed upload
stays a draft. After the push the helper watches the tag run. The
main run for the same commit does not count: that run skips Create Release
and still finishes green. The helper exits non-zero if that tag run fails or
the published release is missing the bundle or another required asset.
Running the helper only to prepare files does not publish or sign a release.
Its annotated Git tag is not itself a cryptographic signature; the workflow
signs artifact provenance.
Verify a downloaded asset with the commands below. Keep tag creation restricted
to trusted maintainers and protect changes to .github/workflows/release.yml:
a signing identity can attest malicious artifacts if its build workflow is
compromised.
Verify a downloaded asset¶
Use a current GitHub CLI with gh attestation verify.
Select the release tag and its expected commit from a trusted source. The commit
is also included in the verification example in each new release's notes.
RELEASE_TAG=v0.16.0
RELEASE_COMMIT='<expected-full-commit-sha>'
mkdir -p release-check
cd release-check
gh release download "$RELEASE_TAG" --repo preloop/preloop \
--pattern preloop-linux-amd64 \
--pattern "preloop-${RELEASE_TAG}.intoto.jsonl"
gh attestation verify ./preloop-linux-amd64 \
--bundle "./preloop-${RELEASE_TAG}.intoto.jsonl" \
--repo preloop/preloop \
--signer-workflow preloop/preloop/.github/workflows/release.yml \
--source-ref "refs/tags/${RELEASE_TAG}" \
--source-digest "$RELEASE_COMMIT" \
--deny-self-hosted-runners
Replace the binary filename to verify another attached asset. A successful exit validates the artifact digest, signature, trust chain, and expected identity. Modification of a byte, a bundle from another release, or an unexpected signer causes verification to fail. An expired network request is a failure to verify, not a reason to bypass verification. The bundle is local, but the CLI may still need network access to obtain Sigstore trust roots.
For bulk checksum validation, first verify SHA256SUMS using the same command,
then run sha256sum --check SHA256SUMS (Linux) or
shasum -a 256 --check SHA256SUMS (macOS) after downloading the payloads. Plain
checksums without a trusted signature establish file consistency, not publisher
identity. The installers do not currently enforce these attestations; perform
this verification before executing a downloaded installer or binary.
OpenSSF Scorecard¶
The Signed-Releases check
does not read this workflow file or the GitHub Attestations API. Scorecard
v5.5.0 scores up to the last five GitHub releases that have assets. A release
scores 10 when one asset name ends in .intoto.jsonl, and 8 for
.asc, .minisig, .sig, .sign, or .sigstore. SHA256SUMS and
Windows Authenticode do not count. The aggregate is the floor of the average.
Signing has been configured since v0.15.0, and v0.15.0 attaches
preloop-v0.15.0.intoto.jsonl. The check still scores 2 because four of the
five releases in the window have no such asset: v0.13.0, v0.13.1, and v0.14.0
predate the pipeline, and v0.16.0 was published without the bundle after
Create Release failed before the attest step. The workflow on main writes
release notes to a file before creating the release, which is what avoids that
failure on the next tag. Each new tag still has to finish attest, verify, and
upload preloop-v<version>.intoto.jsonl. Backfilling v0.16.0 is a separate
maintainer action against the bytes already on that release, not something a
source change can mint. Do not treat a workflow file as a signature.
Continue attaching provenance to each real release. Do not manufacture releases just to move the scoring window. Re-signing a historical binary today can prove that today's signer endorsed those bytes, but cannot establish how or where the old binary was built. Do not label that endorsement as original build provenance or silently replace historical assets. Historical provenance should only be published when its actual build evidence is available and verifiable.