Verify a release

Use packslip verify to check downloaded release files before unpacking or running them. You need the signed bundle, the files you want to check, and a signer policy you trust. Install packslip first if the command is not available.

Choose the policy from the publisher’s documentation or an earlier release you already trust:

Publisher suppliesUse
A GitHub or GitLab repositoryAn identity prefix and issuer
A ps1_… signer fingerprint--pin, which identifies the repository across renames
An Ed25519 public key--pubkey

Pass every downloaded file with --artifact. If you have not chosen a file yet, verify the bundle alone, inspect it with packslip show, then verify the selected download under the same policy. For a command that does discovery, verification, and installation together, see Install a tool. To verify keyless releases in a project that cannot install packslip, commit a verifier into it.

Verify against the expected repository

For a release signed in GitHub Actions, pin the repository you meant to download:

packslip verify packslip.sigstore.json \
  --identity-prefix https://github.com/owner/repo/ \
  --issuer https://token.actions.githubusercontent.com \
  --artifact mytool-1.2.3-linux-x64.tar.gz

Keep the trailing slash: without it, the prefix https://github.com/owner/repo also matches workflows of https://github.com/owner/repo-fork.

Without identity flags, verify derives the policy from the project the bundle claims: for github.com/owner/repo, a workflow under https://github.com/owner/repo/ through GitHub’s issuer, and the equivalent for a gitlab.com project. That shows only that the signer belongs to the project the document names. It does not show that this is the project or version you intended to install, so compare both with your request. For a project on any other host there is nothing to derive, and verify stops with no identity to verify against; pass --pubkey or identity flags.

The identity flags replace that derived policy, and each one checks only what it names. Pass --issuer together with --identity-prefix or --identity: --issuer alone accepts a workflow of any repository on that forge.

For a release signed through a reusable workflow of another repository, the signing identity names that workflow, not the project’s repository. Pass --source-repository https://github.com/owner/repo with --issuer instead of an identity flag: the certificate must record that repository as its source, whichever workflow signed.

To pin one workflow file, extend the prefix through its @: --identity-prefix https://github.com/owner/repo/.github/workflows/release.yml@. --identity takes one exact certificate identity, which includes the workflow’s ref, such as https://github.com/owner/repo/.github/workflows/release.yml@refs/tags/v1.2.3, so it accepts only releases signed from that tag. Use it to check a single release, not as a standing pin.

For a GitLab project, the prefix ends in //, which separates the project path from the pipeline’s configuration file, and the issuer is https://gitlab.com:

packslip verify packslip.sigstore.json \
  --identity-prefix https://gitlab.com/group/tool// \
  --issuer https://gitlab.com \
  --artifact mytool-1.2.3-linux-x64.tar.gz

The packslip verify and packslip pin references list every flag, including --trusted-root for a sigstore deployment other than the public one.

Pin a signer with its fingerprint

An identity prefix names a repository by its current name. Once a repository is renamed or deleted, someone else can create a new one under the old name, and its workflows match the same prefix. packslip verify remembers nothing between runs, so it accepts the newcomer. An installer that remembers the repository ID of the first release it accepts refuses the newcomer later, but it still has to trust that first release. A signer fingerprint closes the gap. It is ps1_ followed by 26 characters derived from the forge’s issuer and the repository’s numeric ID, so it names one repository whatever that repository is called.

A vendor publishes its fingerprint on its website or in its README. Record it in your Dockerfile, CI workflow, or lockfile. Obtain it before the repository name changes hands: a fingerprint copied from the new repository, or calculated from its release, identifies the new owner’s repository.

A vendor gets its fingerprint by running packslip pin on a release its own workflow just published, as Publish with GitHub Actions shows. A consumer can do the same with a release it already trusts:

packslip pin packslip.sigstore.json

For the github.com/jdx/hk 2.3.0 release, it prints:

ps1_snirenkjwr7m5ozgcufameodnm

packslip pin verifies the bundle as packslip verify does, under the policy the project name implies or the identity flags you pass, then prints the fingerprint of the repository that signed it. It cannot tell the genuine repository from one that took its name, so run it only on a release you already trust.

A consumer then passes the fingerprint it recorded to verify:

packslip verify packslip.sigstore.json \
  --pin ps1_snirenkjwr7m5ozgcufameodnm \
  --artifact hk-x86_64-unknown-linux-gnu.tar.gz

A release signed from any other repository, including one that took over the old name, is refused, and the message names both fingerprints. Checking the same hk release against another repository’s fingerprint prints:

verification failed: the release is signed by ps1_snirenkjwr7m5ozgcufameodnm, but the pin is ps1_kwhjac5qpc45qetfh6ppwisi6a

--pin adds a repository check to the identity policy. The example uses the policy derived from the signed project’s GitHub name. The keyless signature must pass that policy, its certificate must contain a repository ID, and that ID must produce the fingerprint you pinned. packslip also checks that the signing repository is the one the statement names. Older certificates without repository IDs cannot pass --pin; use an identity prefix and issuer for those releases.

The fingerprint stays the same when the repository is renamed, moves to another owner, or changes its release workflow. Every tool in a monorepo shares it, so still check that the verified project is the tool you asked for. A key-signed project has no fingerprint; pin its key with --pubkey. See Signer fingerprint for how it is derived.

Verify against a public key

For a key-signed release, pass the vendor’s public key: the .pub file its packslip keygen wrote. Get it independently of the release, such as from the vendor’s website, or reuse a key you pinned earlier:

packslip verify packslip.sigstore.json \
  --pubkey release.pub \
  --artifact mytool-1.2.3-linux-x64.tar.gz

--pubkey also accepts the public key’s base64 line. The bundle’s key hint is an unverified label for the signing key: packslip never trusts it, and your own tooling should not either. Use --allow-unlogged only when you have chosen to accept unlogged signatures for this vendor.

Check every file you use

Repeat --artifact to check multiple files, including separate resource assets:

packslip verify packslip.sigstore.json \
  --pubkey release.pub \
  --artifact mytool-1.2.3-linux-x64.tar.gz \
  --artifact mytool.cdx.json \
  --json

The command checks the signature, the certificate chain of a keyless bundle, the transparency-log entry (a bundle without one fails unless you pass --allow-unlogged), and the statement’s structure. It then looks up each supplied file in the statement by file name and compares its SHA-256 with the signed digest, and for an artifact, also its size. Keep the original file names: a renamed file fails with not listed in the document.

The success line counts artifacts and resource assets separately, so you can see whether every file you meant to check was supplied:

ok: example.com/mytool 1.2.3 published 2026-10-02T19:32:44Z signed by 5CA6E9FAFB1F4098 (sigstore-key) unlogged (1 of 1 artifact(s) and 1 of 1 asset(s) checked, 1 resource(s))

A release that lists no assets omits the asset count: (2 of 3 artifact(s) checked). With --json, checked_artifacts and artifact_count cover artifacts, and checked_assets and asset_count cover assets. An asset you supply is reported only in checked_assets.

Without --artifact, success verifies the bundle alone. No remote artifact is downloaded, hashed, or installed. --json prints a report to standard output for scripts; on failure that stream is empty and the reason goes to standard error. Exit status is 0 for success, 1 for failed verification or unusable input, and 2 for a usage error.

Read the verified artifacts from a script

Build and deployment tooling often needs the verified metadata, not an install: which file to fetch for a platform, its digest, and the commands it provides. --json includes every artifact the signed statement lists, whether or not you passed --artifact, so one command verifies the bundle and returns what to stage:

packslip verify packslip.sigstore.json --pin ps1_snirenkjwr7m5ozgcufameodnm --json \
  | jq -r '.artifacts[] | select(.os == "linux" and .arch == "x86_64") | "\(.name) \(.sha256)"'

For the hk 2.3.0 release, this prints each matching artifact’s name and signed SHA-256. Each entry in artifacts has the statement’s fields for that artifact (name, size, url, format, os, arch, libc, variant, bin, requires, provenance, extensions; absent ones are omitted) and the signed sha256. Download the file yourself, from the signed url or a mirror that keeps the file name, then confirm it by running verify again with --artifact, or compare its SHA-256 with the signed digest. Where the release lists several builds for a platform, the artifact selection rules decide which one a consumer takes; the Rust library’s packslip::select_artifact implements them.

The report follows verify-report-v1.json, which packslip schema --report prints. Within version 1, packslip adds report fields but never removes, renames, or changes the meaning of one, so ignore fields you do not know.

Verify a release list

The same command accepts a signed release list:

packslip verify packslip.json --pubkey release.pub

This checks the list’s signature and structure; --pin and the identity flags apply as they do to a release. The CLI does not compare the expiry with the current time or the sequence with lists you accepted before. The ok: line and --json report the list’s expires_at and sequence, and a consumer must enforce both, as consumer rule 5 says. The CLI also does not fetch or verify the release bundles the list references, and it does not accept --artifact for a release list.

Understand the result

This example verifies the github.com/jdx/hk 2.3.0 bundle without --artifact:

packslip verify packslip.sigstore.json

The command prints a summary line, the signing repository, and one requires line for each artifact that declares requirements. The output below is trimmed to one of its seven requires lines:

ok: github.com/jdx/hk 2.3.0 published 2026-09-26T19:59:21.295096144Z signed by https://github.com/jdx/hk/.github/workflows/release.yml@refs/tags/v2.3.0 (sigstore-oidc) logged 2026-09-26T19:59:21Z (0 of 7 artifact(s) and 0 of 5 asset(s) checked, provenance linked, 7 resource(s))
  repository https://github.com/jdx/hk (id 922514152), owner https://github.com/jdx (id 216188)
  requires hk-x86_64-pc-windows-msvc.zip: libs combase.dll vcruntime140.dll
A successful verification establishes…It does not establish…
The statement was signed by an identity or key allowed by the policy.The signer or its build environment was uncompromised.
The signer belongs to the project the statement names, or matches your flags.The project and version are the ones you asked for.
Supplied files match the signed digests.The software is safe or free of vulnerabilities.
A logged signature has verified transparency-log evidence.This is the newest release or the vendor’s recommended version.
The statement contains provenance links, if reported.The linked provenance has been fetched or verified.
Each artifact’s declared host requirements are reported.This host meets them.

For the full account, see What a verified packslip proves.

packslip show BUNDLE prints the statement without verifying it. Use it for inspection, not as evidence of authenticity.

Troubleshoot a failure

SymptomWhat to check
expected an identity starting with or identity mismatch: expectedThe release was signed outside the repository or identity you pinned. --identity is exact, including the tag ref, so a standing pin needs --identity-prefix. Do not loosen the pin just to make the command pass. If the repository was renamed or moved, see the next row.
The repository was renamed or moved to another ownerWith --pin, or a pin the library stored, nothing changes: the repository ID is the same, so releases from before and after the move verify, and the ok: and repository lines show the name each release was signed under. An --identity-prefix pin names the old location, so releases signed after the move fail with expected an identity starting with. Before you update the prefix, confirm the move: check that packslip pin prints the same fingerprint for a new release as for one you accepted before the move, or ask the project.
issuer mismatch: expected--issuer names a different OIDC issuer from the one that signed the release. Pass the issuer of the CI system that ran the release: https://token.actions.githubusercontent.com for GitHub Actions, https://gitlab.com for GitLab CI.
the release is signed by ps1_…, but the pin is ps1_…A different repository signed the release, often one that took a renamed or deleted repository’s name. Find where the project went, and do not replace your pin with the fingerprint the message prints.
records no repository IDThe certificate predates Fulcio’s repository extensions or comes from another CI system. Pin the release with --identity-prefix and --issuer instead of --pin.
no identity to verify againstThe project is not named on GitHub or GitLab, so verify has no policy to derive. Pass --pubkey for a key-signed release, or identity flags for a keyless one.
an identity was pinned; pin the key insteadThe release is key-signed. Pass the vendor’s public key with --pubkey.
a key was pinned; pin an identity insteadThe release is keyless. Pin its identity or its signer fingerprint instead of a key.
no transparency log entryConfirm with the vendor that it omitted logging on purpose before you pass --allow-unlogged.
not listed in the documentRestore the file’s original name.
sha256 is or size is, followed by document saysConfirm the original file name and release version, then download it again from the vendor’s URL.
An installer reports an expired, rolled-back, or missing signed listObtain a current list and retain existing trust state while investigating. verify checks the list’s signature and structure; the installer enforces freshness and remembered sequence.

Build an installer or mirror

packslip verify checks one document at a time and keeps no state between runs. If you are implementing an installer, add discovery, artifact selection, and remembered trust; see Build an installer or mirror and the consumer rules.

Take the crate as a library

The packslip crate’s verifier, selection rules, and feature flags are covered in Use the Rust library.