# cosign Cheat Sheet

> **Tool:** cosign (Sigstore)
> **Category:** Security & Compliance
> **Verified against:** cosign v3.1.3, flags verified via `cosign --help` / `cosign sign --help` /
> `cosign verify --help` run locally, 2026-08-29
> **Official docs:** https://docs.sigstore.dev/cosign/system_config/installation/

## What it is and where it fits 🎯

cosign signs and verifies container images, blobs, and attestations (SBOMs, provenance) so a downstream
consumer — an admission controller, a verification pipeline, a curious engineer — can prove an artifact came
from where it claims to and hasn't been tampered with since. It's the last link in the supply-chain trust
chain this series builds up: Syft says what's in an image, Grype/Trivy say whether that's vulnerable, and
cosign says the whole thing is provably authentic. Supports both **keyless** (OIDC/Fulcio-based, no key
management) and traditional **key-pair** signing.

## Keyless signing — how the trust actually gets established

```mermaid
sequenceDiagram
    participant Dev as CI Pipeline
    participant Fulcio as Fulcio (Sigstore CA)
    participant OIDC as OIDC Provider (e.g. GitHub Actions)
    participant Rekor as Rekor (transparency log)
    participant Reg as Registry

    Dev->>OIDC: Request identity token (ambient, automatic in CI)
    OIDC-->>Dev: Signed OIDC token proving "this is workflow X in repo Y"
    Dev->>Fulcio: Request short-lived signing cert, presenting the OIDC token
    Fulcio-->>Dev: Certificate binding the CI identity to a signing key (valid ~10 min)
    Dev->>Dev: Sign the image digest with the short-lived key
    Dev->>Rekor: Publish signature + cert to the public transparency log
    Dev->>Reg: Push signature alongside the image

    Note over Rekor: Anyone can later audit "was this really signed by this CI identity, at this time"
```

No long-lived private key ever exists to leak — the signing key is generated fresh, used once, and the
identity binding (a real CI pipeline's OIDC identity, not an anonymous keypair) is what a verifier actually
checks against.

## Installation

```bash
curl -sL -o cosign https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
chmod +x cosign && sudo mv cosign /usr/local/bin/
go install github.com/sigstore/cosign/v3/cmd/cosign@latest    # if you have a Go toolchain (note the v3 module path)
brew install cosign

cosign version
```

## Keyless signing (recommended — no key management)

```bash
cosign sign myregistry.io/myapp:latest                 # opens a browser for the Sigstore OIDC flow, signs by digest
cosign sign --yes myregistry.io/myapp@sha256:abc123...   # skip the confirmation prompt (for CI)
```

> [!IMPORTANT]
> **Always sign by digest (`@sha256:...`), not by tag.** A tag can be repointed after signing, so signing a
> tag doesn't guarantee what gets verified later is actually what you signed — `cosign sign` itself warns
> about exactly this. In CI (GitHub Actions, GitLab CI), the ambient OIDC token from the platform is picked
> up automatically — no interactive browser flow needed, which is what makes keyless signing practical in an
> unattended pipeline at all.

## Key-pair signing

```bash
cosign generate-key-pair                                  # writes cosign.key (private) + cosign.pub (public)
cosign sign --key cosign.key myregistry.io/myapp@sha256:abc123...
cosign sign --key env://COSIGN_PRIVATE_KEY myregistry.io/myapp@sha256:abc123...   # key from an env var, not a file
cosign sign --key awskms://alias/my-signing-key myregistry.io/myapp@sha256:...     # key held in AWS/GCP/Azure KMS or Vault
```

## Verifying

```bash
cosign verify myregistry.io/myapp:latest \
  --certificate-identity=you@example.com \
  --certificate-oidc-issuer=https://accounts.google.com     # keyless verification requires pinning expected identity + issuer

cosign verify --key cosign.pub myregistry.io/myapp:latest    # key-pair verification

cosign verify myregistry.io/myapp:latest \
  --certificate-identity-regexp='.*@example\.com$' \
  --certificate-oidc-issuer-regexp='https://token\.actions\.githubusercontent\.com'   # e.g. "signed by any GitHub Actions workflow in our org"
```

> [!WARNING]
> **Keyless verification without `--certificate-identity`/`--certificate-oidc-issuer` (or their regexp
> variants) is rejected outright.** You must state who/what you expect to have signed the artifact — otherwise
> "verified" only proves *someone* went through the Sigstore flow, not that it was specifically your pipeline.
> Skipping this check is the single most damaging mistake possible with keyless signing: it turns a strong
> identity-bound guarantee into a meaningless "a signature exists" check.

## Attestations (SBOM, provenance, scan results)

```bash
cosign attest --predicate sbom.cdx.json --type cyclonedx --key cosign.key myapp@sha256:abc123...
cosign verify-attestation --type cyclonedx --key cosign.pub myapp:latest
cosign attest-blob --predicate provenance.json --key cosign.key artifact.tar.gz
```

> [!TIP]
> **One verification pipeline (`cosign verify-attestation`) checks any attestation type** — SBOM, build
> provenance, scan results — without needing format-specific parsing logic per attestation kind. Standardizing
> on in-toto-style attestations for everything you want to attach to an artifact (rather than inventing a
> bespoke format per data type) is what makes this generality actually pay off operationally.

## Blob (non-container-artifact) signing

```bash
cosign sign-blob --key cosign.key myfile.tar.gz --output-signature myfile.sig
cosign verify-blob --key cosign.pub --signature myfile.sig myfile.tar.gz
```

Useful for signing something that isn't a container image at all — a Terraform module tarball, a release
binary, a Helm chart package.

## Inspecting what's attached to an image

```bash
cosign tree myregistry.io/myapp:latest      # show signatures, SBOMs, and attestations attached to an image
```

## Real-world scenario: enforcing "only signed images from our CI" at admission time

A cluster wants to reject any image that wasn't signed by the org's actual GitHub Actions pipeline — combining
cosign's verification with a Kubernetes admission controller (Kyverno or Gatekeeper/OPA, from earlier in this
series):

```bash
cosign verify myregistry.io/myapp:latest \
  --certificate-identity-regexp='https://github.com/my-org/.*' \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com
```

An admission policy wraps this exact check as a Kubernetes-native gate — any pod spec referencing an
unsigned, or wrongly-signed, image is rejected before it ever schedules, closing the loop between "we sign in
CI" and "we actually enforce that signature means something in production."

## Real-world scenario: GitHub Actions signing recipe

```yaml
# .github/workflows/sign.yml
name: Sign and push
on:
  push:
    tags: ['v*']
permissions:
  id-token: write     # required for keyless signing's ambient OIDC token
  contents: read
  packages: write
jobs:
  build-sign:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: sigstore/cosign-installer@v3
      - name: Build and push
        run: |
          docker build -t ghcr.io/my-org/myapp:${{ github.ref_name }} .
          docker push ghcr.io/my-org/myapp:${{ github.ref_name }}
      - name: Sign
        run: |
          DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' ghcr.io/my-org/myapp:${{ github.ref_name }})
          cosign sign --yes "$DIGEST"
```

> [!IMPORTANT]
> `permissions: id-token: write` is not optional — without it, GitHub Actions never issues the OIDC token
> keyless signing depends on, and the sign step fails outright rather than silently falling back to something
> less secure.

## Common pitfalls

- **Signing by tag instead of digest** — see the IMPORTANT callout above.
- **Verifying without pinning identity/issuer** — see the WARNING above; this is the mistake that makes
  keyless verification meaningless.
- **Forgetting `id-token: write` in GitHub Actions** — the most common reason keyless signing "doesn't work"
  in a fresh CI setup.
- **Assuming a signature alone proves the image is safe** — cosign proves *authenticity/provenance*, not
  *absence of vulnerabilities*; it's complementary to Trivy/Grype, not a substitute for scanning.

## Exit codes

`0` success · non-zero on a signing/verification failure — check stderr for the specific reason
(certificate mismatch, transparency-log lookup failure, missing key, etc.).

## When to reach for something else

cosign is the de facto standard for OCI-artifact signing in the Sigstore ecosystem; there isn't a direct
substitute covered elsewhere in this series. It pairs with, rather than replaces, Syft (SBOM generation) and
Trivy/Grype (vulnerability scanning) — signing an unscanned, vulnerable image just proves you know exactly
which vulnerable image you shipped.
