# Verify a downloaded release before you install it

Source: https://www.testifysec.com/docs/cilock/use-cases/verify-a-download/

A Claude Code session downloads cosign v3.1.3 from GitHub and checks its Sigstore bundle with cilock verify-bundle, taking the expected signer from the project's docs, then shows a changed byte and a wrong signer are both refused.

More projects now publish a Sigstore bundle (a `.sigstore.json` file) next to each release binary. The bundle carries the signature, the short-lived signing certificate and the transparency-log proof. It does not tell you whether the signer is the one you expected. That part is yours: a valid signature from the wrong identity is still a valid signature.

 

`cilock verify-bundle` checks a bundle under the Sigstore client verification procedure and refuses to run without an expected identity and issuer. In this session the prompt asks Claude to check a cosign release before installing it, and to take the expected signer from Sigstore's documentation rather than from the bundle.

 

How this was recorded

TestifySec wrote both prompts and sent them into the session. Claude Code and `cilock` ran unedited, and the recording was not changed afterwards.

 

A Claude Code session downloading cosign v3.1.3, verifying its Sigstore bundle with cilock verify-bundle, then showing a one-bit change and a wrong signer identity are both rejected

[Download terminal recording (.cast)](https://www.testifysec.com/media/docs/recordings/verify-download.cast)

 

## What happens, step by step

 

1. **Find the expected signer somewhere other than the bundle.** Claude sends a subagent to read Sigstore's installation docs ("Verifying Cosign Releases", for cosign 3.x), which name the signer as `keyless@projectsigstore.iam.gserviceaccount.com` with the issuer `https://accounts.google.com`. Reading the identity out of the bundle and then checking the bundle against it would prove nothing.
 2. **Download the release and its bundle.** `cosign-darwin-arm64` and `cosign-darwin-arm64.sigstore.json` from the v3.1.3 GitHub release, with `curl`.
 3. **Verify.**
   
    
   
   ```bash
cilock verify-bundle --bundle cosign-darwin-arm64.sigstore.json \
  --certificate-identity keyless@projectsigstore.iam.gserviceaccount.com \
  --certificate-oidc-issuer https://accounts.google.com \
  cosign-darwin-arm64
```
   
    
   
   cilock prints `Verified OK` and exits `0`. It fetched the public-good Sigstore trusted root over TUF, because no `--trusted-root` was given.
 4. **Stop before installing.** Claude reports the SHA-256 and the install command and waits. A second prompt asks for proof the check can fail first.
 5. **Change one bit.** A copy with one bit flipped at offset 1,000,000 is refused: `invalid signature when validating ASN.1 encoded signature`.
 6. **Expect the wrong signer.** The real binary checked against `attacker@example.com` is refused: `expected SAN value "attacker@example.com", got "keyless@projectsigstore.iam.gserviceaccount.com"`. Claude adds a test of its own: the right identity with the GitHub Actions issuer is refused too, because the identity and the issuer must both match.

 

## What the verification requires

 

`cilock verify-bundle` requires every check the trusted root can back:

 

| Check | What it rules out |
| --- | --- |
| Signature over the file's digest | a changed or swapped binary |
| Exact certificate SAN (`--certificate-identity`) | a valid signature from someone else |
| Exact OIDC issuer (`--certificate-oidc-issuer`) | the same identity string minted by a different provider |
| SCT, when the root distributes CT logs | a certificate the CA never logged |
| Transparency-log entry, when the root distributes Rekor logs | a signature that was never publicly recorded |
| Signing time from a TSA timestamp or a log inclusion promise | a signature made outside the certificate's lifetime |

 

Neither `--certificate-identity` nor `--certificate-oidc-issuer` can be empty, and both are exact matches. For a bundle signed with a managed key, pass `--key` with the PEM public key instead. The artifact can be a file path or a digest such as `sha256:<hex>`.

 

Where to get the identity

For a release built in GitHub Actions the identity is usually the workflow path at a tag, for example `https://github.com/acme/app/.github/workflows/release.yml@refs/tags/v1.2.0`, with the issuer `https://token.actions.githubusercontent.com`. Take it from the project's documentation or security policy, not from the bundle you are checking.

 

## Run it yourself

 

The recording used `cilock` built from `main` at commit `5b614726`; the `verify-bundle` command ships in CI/lock 4.6.0 and later. Verification needs network access to fetch the Sigstore trusted root unless you pass `--trusted-root` with a local `trusted_root.json`.

 

To record your own evidence about a build rather than check someone else's signature, see [Release gates](https://www.testifysec.com/docs/cilock/use-cases/release-gates) and [Signing and identity](https://www.testifysec.com/docs/cilock/concepts/signing-and-identity).

Reference generated from the product documentation. Match commands and support details to your installed release.
