supply chain security

Verifying Build Provenance with SLSA and Sigstore

Verify signed artifacts and SLSA build provenance with Sigstore cosign and slsa-verifier, enforce keyless OIDC identity, and apply SLSA Build levels to harden the software supply chain.

attestationcode-signingcosignkeyless-signingprovenancesigstoreslsasupply-chain
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Build-provenance verification answers a question that defeats many supply-chain attacks: was this artifact actually built from the source I think it was, by the builder I trust, without tampering? Attackers who compromise a build system, swap a compiled release, or inject a malicious step (as in the SolarWinds and 3CX incidents) produce artifacts that look legitimate but lack verifiable provenance. SLSA (Supply-chain Levels for Software Artifacts, https://slsa.dev) defines Build levels (L1–L3) describing increasing provenance integrity, and Sigstore (https://www.sigstore.dev) provides the signing and transparency infrastructure: cosign for signing/verifying artifacts and attestations, Fulcio for short-lived keyless certificates bound to an OIDC identity, and Rekor as a tamper-evident transparency log.

This skill covers verifying signatures and SLSA provenance with cosign (cosign verify, cosign verify-attestation, cosign verify-blob-attestation) and slsa-verifier (slsa-verifier verify-artifact), enforcing the builder identity (the GitHub Actions workflow that produced the artifact) and the expected source repository. Keyless verification ties trust to an OIDC issuer (e.g., https://token.actions.githubusercontent.com) and a certificate identity rather than a long-lived private key.

This maps to MITRE ATT&CK T1195 — Supply Chain Compromise (provenance verification detects/blocks tampered artifacts) and NIST CSF PR.DS-06 (integrity-checking mechanisms are used to verify software, firmware, and information integrity).

When to Use

  • In CI/CD before deploying or promoting any container image or release binary.
  • When consuming third-party artifacts (base images, Go/npm releases) that publish attestations.
  • When establishing a SLSA Build L3 producer pipeline and enforcing it at the consumer side.
  • During incident response to confirm whether a deployed artifact's provenance is intact.
  • In admission control (e.g., Kubernetes via policy-controller / Kyverno) to admit only verified images.

Prerequisites

  • cosign (Sigstore CLI):
    go install github.com/sigstore/cosign/v2/cmd/cosign@latest
    # or download a release binary from https://github.com/sigstore/cosign/releases
  • slsa-verifier:
    go install github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier@latest
    # or:
    curl -sSL https://github.com/slsa-framework/slsa-verifier/releases/latest/download/slsa-verifier-linux-amd64 \
      -o /usr/local/bin/slsa-verifier && chmod +x /usr/local/bin/slsa-verifier
  • Network access to Rekor (https://rekor.sigstore.dev) and Fulcio for transparency-log verification.
  • The artifact plus its provenance/attestation bundle (.sigstore, .intoto.jsonl, or attached OCI attestation).

Objectives

  • Verify a keyless cosign signature on a container image, pinning OIDC issuer and certificate identity.
  • Verify a SLSA provenance attestation on an image with cosign verify-attestation --type slsaprovenance.
  • Verify a release binary's provenance with slsa-verifier verify-artifact, pinning source repo and tag.
  • Verify GitHub artifact attestations / blob bundles with cosign verify-blob-attestation.
  • Gate CI and admission control on successful verification; understand SLSA Build L1–L3.

MITRE ATT&CK Mapping

ID Tactic Technique Name Relevance
T1195 Initial Access Supply Chain Compromise Verifying provenance and signatures detects artifacts that were tampered with or substituted in the build/distribution chain, preventing supply-chain compromise from reaching deployment.

Workflow

Step 1: Verify a keyless cosign signature on an image

Pin both the OIDC issuer and the certificate identity (the exact workflow that signed). A bare cosign verify without identity pinning is meaningless — anyone can sign.

cosign verify \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  --certificate-identity-regexp "^https://github.com/myorg/myrepo/.github/workflows/.*@refs/tags/v.*" \
  ghcr.io/myorg/myrepo:v1.2.3

A non-zero exit or empty result means verification failed — do not deploy.

Step 2: Verify the SLSA provenance attestation on the image

The signature proves who signed; the provenance attestation proves how it was built. Verify the in-toto SLSA predicate type.

cosign verify-attestation \
  --type slsaprovenance \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  --certificate-identity "https://github.com/myorg/myrepo/.github/workflows/build-sign.yml@refs/heads/main" \
  ghcr.io/myorg/myrepo:v1.2.3

Supported predicate types include slsaprovenance, slsaprovenance02, and slsaprovenance1.

Step 3: Inspect the provenance predicate

Decode the verified attestation to confirm the source repo, commit, and builder match expectations.

cosign verify-attestation --type slsaprovenance \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  --certificate-identity-regexp '.*' \
  ghcr.io/myorg/myrepo:v1.2.3 \
  | jq -r '.payload' | base64 -d | jq '.predicate.buildDefinition.externalParameters, .predicate.runDetails.builder.id'

Step 4: Verify a release binary with slsa-verifier

For downloadable binaries (e.g., produced by slsa-github-generator), pin the source URI and the tag. slsa-verifier checks the cryptographic signature on the provenance and that the expected builder produced it.

slsa-verifier verify-artifact slsa-test-linux-amd64 \
  --provenance-path slsa-test-linux-amd64.intoto.jsonl \
  --source-uri github.com/myorg/myrepo \
  --source-tag v1.2.3
 
# Optionally pin the builder identity (SLSA L3)
slsa-verifier verify-artifact ./mybin \
  --provenance-path ./mybin.intoto.jsonl \
  --source-uri github.com/myorg/myrepo \
  --builder-id https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@refs/tags/v2.0.0

Step 5: Verify GitHub artifact attestations / blob bundles

For artifacts signed via actions/attest-build-provenance, the bundle uses the new Sigstore bundle format.

cosign verify-blob-attestation \
  --bundle ./myartifact.sigstore.json \
  --new-bundle-format \
  --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
  --certificate-identity-regexp="^https://github.com/myorg/myrepo/" \
  ./myartifact
 
# Equivalent native GitHub CLI verification
gh attestation verify ./myartifact --repo myorg/myrepo

Step 6: Enforce verification as a gate

Wrap verification so the pipeline fails closed on any error.

#!/usr/bin/env bash
set -euo pipefail
IMG="ghcr.io/myorg/myrepo:v1.2.3"
cosign verify \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  --certificate-identity-regexp "^https://github.com/myorg/myrepo/" "$IMG" >/dev/null
cosign verify-attestation --type slsaprovenance \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  --certificate-identity-regexp "^https://github.com/myorg/myrepo/" "$IMG" >/dev/null
echo "[+] $IMG verified: signature + SLSA provenance OK"

Step 7: Map findings to SLSA Build levels

Document the level each consumed artifact achieves:

  • Build L1 — provenance exists (the build process generates it), but it may be unsigned/forgeable.
  • Build L2 — provenance is signed by a hosted build service.
  • Build L3 — provenance is non-forgeable: generated on an isolated, hardened builder where secrets are unavailable to user-defined steps (e.g., slsa-github-generator reusable workflows). Require L3 for high-trust artifacts.

Tools and Resources

Tool / Resource Purpose Link
cosign Sign/verify artifacts and attestations (keyless) https://github.com/sigstore/cosign
slsa-verifier Verify SLSA provenance from compliant builders https://github.com/slsa-framework/slsa-verifier
slsa-github-generator Produce SLSA L3 provenance in GitHub Actions https://github.com/slsa-framework/slsa-github-generator
actions/attest-build-provenance GitHub-native provenance attestation https://github.com/actions/attest-build-provenance
SLSA specification Build levels and provenance schema https://slsa.dev/spec/v1.0/
Sigstore docs Fulcio, Rekor, cosign verification https://docs.sigstore.dev/cosign/verifying/verify/

Verification Identity Reference

Field Where it comes from Why it matters
--certificate-oidc-issuer The OIDC issuer (e.g., GitHub Actions) Restricts who could have requested the signing cert
--certificate-identity[-regexp] The exact/patterned workflow identity (SAN) Restricts which workflow signed; prevents impersonation
--source-uri (slsa-verifier) Expected source repo Confirms the artifact came from your repo
--source-tag / --source-versioned-tag Expected git tag Prevents rollback/substitution
--builder-id Trusted builder workflow ref Enforces SLSA L3 non-forgeable builder

Validation Criteria

  • cosign and slsa-verifier installed and report versions
  • Image signature verified with pinned OIDC issuer AND certificate identity
  • SLSA provenance attestation verified (--type slsaprovenance)
  • Provenance predicate inspected; source repo/commit/builder match
  • Release binary verified with slsa-verifier (source-uri + tag pinned)
  • GitHub blob/bundle attestation verified
  • Verification wired as a fail-closed CI/admission gate
  • Each consumed artifact assigned a SLSA Build level
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 2

api-reference.md2.4 KB

cosign & slsa-verifier CLI Reference

Sources:

cosign — verification commands

Command Purpose
cosign verify <image> Verify image signature(s)
cosign verify-attestation <image> Verify in-toto attestation attached to image
cosign verify-blob <file> Verify a detached signature on a blob
cosign verify-blob-attestation <file> Verify an attestation bundle for a blob
cosign download attestation <image> Pull attestations for offline inspection
cosign tree <image> Show signatures/attestations attached to an image

cosign — key verification flags

Flag Meaning
--certificate-oidc-issuer <url> Required keyless: OIDC issuer that minted the cert
--certificate-identity <san> Exact certificate identity (workflow SAN)
--certificate-identity-regexp <re> Regex form of identity
--type <type> Predicate type: slsaprovenance, slsaprovenance02, slsaprovenance1, spdx, cyclonedx, vuln, custom
--bundle <file> Sigstore bundle for blob attestation
--new-bundle-format Use the new Sigstore bundle format
--key <path> Verify with a fixed public key (non-keyless)
--rekor-url <url> Transparency log (default https://rekor.sigstore.dev)

GitHub OIDC issuer (constant)

https://token.actions.githubusercontent.com

slsa-verifier

Command Purpose
slsa-verifier verify-artifact <artifact> Verify provenance for a binary/artifact
slsa-verifier verify-image <image> Verify provenance for a container image
slsa-verifier verify-npm-package <tarball> Verify npm package provenance

slsa-verifier flags

Flag Meaning
--provenance-path <file> Path to provenance (.intoto.jsonl / .sigstore)
--source-uri <repo> Expected source repository (GitHub URIs)
--source-tag <tag> Expected git tag
--source-versioned-tag <tag> Semver-aware tag match
--builder-id <ref> Pin the trusted builder workflow (SLSA L3)
--print-provenance Print the verified provenance to stdout

GitHub CLI native verification

Command Purpose
gh attestation verify <artifact> --repo <org>/<repo> Verify GitHub-generated build provenance
standards.md1.4 KB

Standards Mapping — Verifying Build Provenance with SLSA and Sigstore

MITRE ATT&CK

ID Technique Name Rationale
T1195 Supply Chain Compromise Verifying signatures and SLSA provenance detects artifacts tampered with or substituted anywhere in the build and distribution chain, blocking supply-chain compromise before deployment. Ref: https://attack.mitre.org/techniques/T1195/

NIST Cybersecurity Framework 2.0

ID Subcategory Rationale
PR.DS-06 Integrity-checking mechanisms are used to verify software, firmware, and information integrity cosign signature verification and SLSA provenance verification are integrity-checking mechanisms that cryptographically confirm an artifact was built from the expected source by the expected builder and was not modified.

SLSA Build Levels

Level Guarantee
Build L1 Provenance exists (may be forgeable)
Build L2 Provenance signed by a hosted build service
Build L3 Non-forgeable provenance from a hardened, isolated builder

Ref: https://slsa.dev/spec/v1.0/levels

Supporting References

Scripts 1

agent.py4.7 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
provenance-verify — verify Sigstore signatures and SLSA provenance, fail closed.

Wraps the real cosign and slsa-verifier CLIs:
  - cosign:        https://github.com/sigstore/cosign
  - slsa-verifier: https://github.com/slsa-framework/slsa-verifier

Install the binaries first:
    go install github.com/sigstore/cosign/v2/cmd/cosign@latest
    go install github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier@latest

Examples:
    python agent.py image \
        --image ghcr.io/myorg/myrepo:v1.2.3 \
        --oidc-issuer https://token.actions.githubusercontent.com \
        --identity-regexp '^https://github.com/myorg/myrepo/'

    python agent.py artifact \
        --artifact ./mybin \
        --provenance ./mybin.intoto.jsonl \
        --source-uri github.com/myorg/myrepo \
        --source-tag v1.2.3
"""
import argparse
import base64
import json
import shutil
import subprocess
import sys

GITHUB_OIDC = "https://token.actions.githubusercontent.com"


def _require(tool: str) -> None:
    if shutil.which(tool) is None:
        sys.exit(f"error: '{tool}' not found on PATH. See the skill prerequisites.")


def _run(argv: list, capture: bool = False) -> subprocess.CompletedProcess:
    print(f"[*] {' '.join(argv)}", file=sys.stderr)
    return subprocess.run(argv, capture_output=capture, text=True)


def verify_image(args) -> int:
    _require("cosign")
    issuer = args.oidc_issuer
    # 1) signature
    sig = ["cosign", "verify",
           "--certificate-oidc-issuer", issuer]
    if args.identity:
        sig += ["--certificate-identity", args.identity]
    else:
        sig += ["--certificate-identity-regexp", args.identity_regexp]
    sig.append(args.image)
    if _run(sig).returncode != 0:
        print("[!] signature verification FAILED", file=sys.stderr)
        return 1
    print("[+] signature OK", file=sys.stderr)

    # 2) SLSA provenance attestation
    att = ["cosign", "verify-attestation", "--type", args.predicate_type,
           "--certificate-oidc-issuer", issuer]
    if args.identity:
        att += ["--certificate-identity", args.identity]
    else:
        att += ["--certificate-identity-regexp", args.identity_regexp]
    att.append(args.image)
    proc = _run(att, capture=True)
    if proc.returncode != 0:
        print("[!] provenance verification FAILED", file=sys.stderr)
        print(proc.stderr, file=sys.stderr)
        return 1
    print("[+] SLSA provenance OK", file=sys.stderr)

    # 3) decode and surface key provenance fields
    try:
        payload = json.loads(proc.stdout.splitlines()[0])["payload"]
        decoded = json.loads(base64.b64decode(payload))
        pred = decoded.get("predicate", {})
        builder = (pred.get("runDetails", {}).get("builder", {}).get("id")
                   or pred.get("builder", {}).get("id"))
        print(json.dumps({"image": args.image, "verified": True,
                          "builder_id": builder}, indent=2))
    except Exception:
        print(json.dumps({"image": args.image, "verified": True}, indent=2))
    return 0


def verify_artifact(args) -> int:
    _require("slsa-verifier")
    argv = ["slsa-verifier", "verify-artifact", args.artifact,
            "--provenance-path", args.provenance,
            "--source-uri", args.source_uri]
    if args.source_tag:
        argv += ["--source-tag", args.source_tag]
    if args.builder_id:
        argv += ["--builder-id", args.builder_id]
    rc = _run(argv).returncode
    if rc != 0:
        print("[!] artifact provenance verification FAILED", file=sys.stderr)
        return 1
    print(json.dumps({"artifact": args.artifact, "verified": True,
                      "source_uri": args.source_uri,
                      "source_tag": args.source_tag}, indent=2))
    return 0


def main() -> int:
    p = argparse.ArgumentParser(description="Verify Sigstore signatures and SLSA provenance.")
    sub = p.add_subparsers(dest="cmd", required=True)

    im = sub.add_parser("image", help="verify container image signature + provenance")
    im.add_argument("--image", required=True)
    im.add_argument("--oidc-issuer", default=GITHUB_OIDC)
    g = im.add_mutually_exclusive_group(required=True)
    g.add_argument("--identity")
    g.add_argument("--identity-regexp")
    im.add_argument("--predicate-type", default="slsaprovenance")
    im.set_defaults(func=verify_image)

    ar = sub.add_parser("artifact", help="verify binary/artifact provenance via slsa-verifier")
    ar.add_argument("--artifact", required=True)
    ar.add_argument("--provenance", required=True)
    ar.add_argument("--source-uri", required=True)
    ar.add_argument("--source-tag")
    ar.add_argument("--builder-id")
    ar.set_defaults(func=verify_artifact)

    args = p.parse_args()
    return args.func(args)


if __name__ == "__main__":
    sys.exit(main())
Keep exploring