Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Verifying signed artifacts

Every tagged release of SecureScan publishes signed artifacts:

  • Wheel + sdist — signed with sigstore-python, bundle ships as a GitHub Release asset.
  • Container image — signed by digest with cosign keyless (Sigstore via OIDC).

Both identities are pinned to refs/tags/<tag> — that is why the release workflow is tag-triggered only and does not offer workflow_dispatch (a manual run would publish under a refs/heads/... identity and break these verification commands).

Wheel + sdist (sigstore)

Install sigstore:

pip install sigstore

Download the wheel and its sigstore bundle from the GitHub Release page (both ship as Release assets):

gh release download v0.11.0 \
  --repo Metbcy/securescan \
  --pattern 'securescan-0.11.0-py3-none-any.whl' \
  --pattern 'securescan-0.11.0-py3-none-any.whl.sigstore.json'

Verify:

sigstore verify identity \
  --cert-identity 'https://github.com/Metbcy/securescan/.github/workflows/release.yml@refs/tags/v0.11.0' \
  --cert-oidc-issuer 'https://token.actions.githubusercontent.com' \
  --bundle securescan-0.11.0-py3-none-any.whl.sigstore.json \
  securescan-0.11.0-py3-none-any.whl

You should see:

OK: securescan-0.11.0-py3-none-any.whl

Same shape for the sdist:

sigstore verify identity \
  --cert-identity 'https://github.com/Metbcy/securescan/.github/workflows/release.yml@refs/tags/v0.11.0' \
  --cert-oidc-issuer 'https://token.actions.githubusercontent.com' \
  --bundle securescan-0.11.0.tar.gz.sigstore.json \
  securescan-0.11.0.tar.gz

Container image (cosign keyless)

Install cosign (≥ v2.0).

Verify by tag:

cosign verify ghcr.io/metbcy/securescan:v0.11.0 \
  --certificate-identity 'https://github.com/Metbcy/securescan/.github/workflows/release.yml@refs/tags/v0.11.0' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com'

Verify by digest (the recommended pattern for production — tags can be re-pointed; digests cannot):

DIGEST="$(crane digest ghcr.io/metbcy/securescan:v0.11.0)"
cosign verify "ghcr.io/metbcy/securescan@${DIGEST}" \
  --certificate-identity 'https://github.com/Metbcy/securescan/.github/workflows/release.yml@refs/tags/v0.11.0' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com'

Successful output is JSON:

[
  {
    "critical": {
      "identity": {
        "docker-reference": "ghcr.io/metbcy/securescan"
      },
      "image": {
        "docker-manifest-digest": "sha256:..."
      },
      "type": "cosign container image signature"
    },
    "optional": {
      "Bundle": { "...": "..." },
      "Issuer": "https://token.actions.githubusercontent.com",
      "Subject": "https://github.com/Metbcy/securescan/.github/workflows/release.yml@refs/tags/v0.11.0"
    }
  }
]

What the verification proves

  • The artifact was produced by the SecureScan release workflow at the v0.11.0 tag running on GitHub Actions.
  • The Sigstore transparency log (Rekor) has an immutable record of the signature.
  • The artifact has not been tampered with since signing.

It does not prove:

  • That the v0.11.0 source tree itself is bug-free or malware-free.
  • That the artifact you have was downloaded from the official source (verify the registry / release URL too).

Pinning in production

Pin by digest, not tag

Tags are mutable references. A registry compromise (or a careless re-push) could re-point v0.11.0 to a different image. Pin by digest in production manifests:

​ image: ghcr.io/metbcy/securescan@sha256:abcdef...

The cosign verify ...@sha256:... command above ties the running image to the v0.11.0 release identity, regardless of what a tag now points at.

For Kubernetes, an admission controller like Sigstore Policy Controller or Kyverno's verifyImages can enforce verification at admission time so an unsigned image never starts.

Why these specific identities

The cert identity is the workflow file path at the tagged ref:

https://github.com/Metbcy/securescan/.github/workflows/release.yml@refs/tags/v0.11.0

That URL is self-describing:

SegmentMeaning
Metbcy/securescanThe repository.
.github/workflows/release.ymlThe workflow that signed the artifact.
refs/tags/v0.11.0The git ref the workflow ran against.

The OIDC issuer is GitHub Actions' fixed token issuer:

https://token.actions.githubusercontent.com

Together they prove "this artifact was signed by Metbcy/securescan's release workflow when run against the v0.11.0 tag." Re-running the workflow against a different tag, branch, or repository would produce a different identity that fails the verification.

Troubleshooting

error: tlog entry not found

Sigstore cached transparency entries can lag a few seconds after signing. Retry. If it persists past a minute, the artifact may have been signed against an unsupported transparency log; check the Sigstore status page.

subject mismatch

The --cert-identity does not match the actual signature. Most common causes:

  • Wrong tag in the URL (v0.11.0 vs v0.10.4).
  • Verifying a latest image — latest is built from main and not signed via the tagged-release path.
  • Forking SecureScan and re-running release.yml from your fork. Your fork's signatures will use your org/repo in the identity.

error: bundle does not match

The --bundle file does not correspond to the securescan-*.whl on disk. Make sure you downloaded both from the same GitHub Release.

Source

  • Release workflow: .github/workflows/release.yml
  • The exact verification commands are also appended to each GitHub Release's notes (auto-generated from the release.yml template), with <tag> and <version> substituted in.

Next