DevSecOps & Supply Chain Cheatsheet
Operational reference for securing the software supply chain. cosign signing, SBOM generation, SLSA provenance, GitHub Actions security patterns, and the gates that catch supply-chain attacks at CI time before they reach production.
Sigstore / cosign (artifact signing)
5 commandscosign generate-key-pairGenerate a cosign keypair locally. Use only for testing — production should use keyless signing.
Warning: Local keys must be protected like any private key. Lost = re-issue all signatures; leaked = forged signatures.
cosign sign --identity-token=$OIDC_TOKEN <image>@sha256:<digest>Keyless signing via Sigstore Fulcio. The signing identity comes from your OIDC provider (GitHub, Google, etc.).
Production note: Keyless is the production default. The signing certificate is short-lived and tied to a verifiable identity — no keys to leak or rotate.
cosign verify --certificate-identity-regexp=".*@example.com$" --certificate-oidc-issuer=https://accounts.google.com <image>Verify a signature, gating on identity (issuer + subject pattern).
Warning: Without --certificate-identity, ANY Sigstore-signed image passes — defeats the purpose. Always pin the expected signer.
cosign attest --predicate sbom.spdx.json --type spdx <image>Attach an SBOM (or other predicate) as an attestation alongside the image. Stored in the registry.
Production note: Predicate types: spdx, cyclonedx, slsaprovenance, vuln, custom. Use the standardized types when possible.
cosign verify-attestation --type slsaprovenance --certificate-identity-regexp ".*@github.com$" <image>Verify an attestation matches expected predicate type and signer.
Production note: Pair with `cue` or `rego` to validate the predicate content (e.g. provenance level >= L3).
SBOMs (Software Bill of Materials)
5 commandssyft <image> -o spdx-json > sbom.spdx.jsonGenerate an SPDX-format SBOM from an image. SPDX is the ISO standard.
Production note: Generate at build time; attach as attestation. Recreating SBOMs after-the-fact is unreliable.
syft <image> -o cyclonedx-json > sbom.cdx.jsonCycloneDX format. Often preferred by security tooling.
Production note: Both are valid. Pick one for your org and stick with it — multi-format proliferation creates confusion.
grype <image> / grype sbom:sbom.spdx.jsonVulnerability scan against an image or pre-generated SBOM. SBOM-based scans are deterministic.
Production note: Run grype in CI on every build. Gate releases on severity threshold (e.g. fail on Critical, page on High).
trivy image --severity HIGH,CRITICAL <image>Trivy is the alternative to grype. Both are CNCF; pick one.
Production note: Trivy also scans IaC, secrets, misconfigurations — broader scope, different mental model.
oras attach --artifact-type "application/spdx+json" <image> sbom.spdx.jsonAttach SBOM as an OCI referrer to an image. Stays linked through registry copies.
Production note: Modern registries (ghcr, ecr, gcr) support OCI 1.1 referrers — SBOMs travel with the image automatically.
SLSA provenance
5 commandsSLSA Build Level 1Provenance document is generated. Build is documented but not necessarily reproducible.
Production note: Achievable with simple in-pipeline scripts; little assurance value alone.
SLSA Build Level 2Build runs on a hosted service (e.g. GitHub Actions); provenance is signed.
Production note: GitHub Actions + slsa-github-generator is a turnkey path to L2.
SLSA Build Level 3Provenance is non-forgeable: the build platform attests directly, not the build itself.
Production note: L3 is the realistic production target. Requires hardened build runners and isolated provenance generation.
slsa-verifier verify-artifact --provenance-path attestation.intoto.jsonl --source-uri github.com/org/repo --source-tag v1.2.3 <artifact>Verify SLSA provenance against expected source repo and tag.
Production note: Run as an admission gate — only deploy artifacts whose provenance ties to your trusted source repo.
github_action_jobs:
build:
permissions:
id-token: write
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1GitHub Actions reusable workflow that produces SLSA L3 provenance for container images.
Production note: id-token: write is required for OIDC-based keyless signing. Pin the workflow to a specific tag, not @main.
Image admission policy (Kubernetes)
3 commandskyverno verify-images:
imageReferences:
- "registry.example.com/*"
attestors:
- entries:
- keyless:
subject: ".*@example.com$"
issuer: https://accounts.google.comKyverno cluster policy that requires keyless cosign signature on all images from your registry.
Production note: Audit-mode rollout (validationFailureAction: audit) before enforce. Logs unsigned images without blocking; gives you a window to clean up.
cosigned (admission webhook)Sigstore-maintained admission controller that enforces signed images.
Production note: Lighter-weight than Kyverno if signing is the only policy you need. Kyverno wins for richer policy needs.
connaisseur (legacy)Older admission controller for image signing. Still supported.
Warning: Kyverno + cosign or cosigned have eclipsed Connaisseur for new deployments.
GitHub Actions security
6 commandspermissions:
contents: read
id-token: writeWorkflow-level permissions. Default is full GITHUB_TOKEN — explicit minimum is the safe default.
Warning: Without explicit permissions, every workflow has read+write to everything. A compromised dependency exfiltrates secrets immediately.
uses: actions/checkout@v4 # never @main, never @masterPin actions to a specific tag or SHA. Renovate/Dependabot handle updates.
Production note: For public actions, prefer SHA pinning with comments: `uses: actions/checkout@8e5e7e5` # v4.1.7. SHAs are immutable; tags can be moved.
jobs.<job>.environment: productionTie a job to a protected environment with required reviewers. Adds human gate to deploys.
Production note: Protected environments + branch protection rules + required reviewers = ".github/workflows/deploy.yml cannot deploy without two human approvals".
secrets: inherit / env: { GH_TOKEN: ${{ secrets.GH_TOKEN }} }Secret scoping in reusable workflows. inherit shares all; explicit env is least privilege.
Warning: inherit is convenient but leaks every secret to every reusable workflow. Pass only the secrets needed.
pull_request_target vs pull_requestpull_request_target runs in the context of the base branch with secrets — risky for fork PRs. pull_request runs in fork context without secrets.
Warning: pull_request_target with checkout of fork code is the canonical "GitHub Actions RCE via PR" pattern. Don't check out fork code with secrets in scope.
job.<id>.steps[].if: always() && needs.scan.outputs.severity == 'critical'Gate on outputs from previous jobs. Use to block deploys on scan failure.
Production note: Standard CI gate: scan -> deploy, with deploy `needs: [scan]` and a severity check.
Pre-commit / CI gates
4 commandsgitleaks detect --source . --redactFind committed secrets. Run as pre-commit hook AND in CI.
Warning: Pre-commit alone is insufficient — developers can skip hooks. CI gate is the enforcement layer.
trivy config <dir>Scan IaC (Terraform, K8s manifests, Dockerfiles) for misconfigurations.
Production note: Run on every PR that touches infra. Surface findings as PR comments via reviewdog or trivy-action.
kube-linter lint <dir>Lint Kubernetes manifests for common security issues (privileged, no probes, missing labels).
Production note: Pair with kustomize/Helm rendering in CI so you lint the final manifest, not the template.
osv-scanner scan ./Scan dependencies against the OSV database. Faster + more comprehensive than language-specific tools.
Production note: OSV covers npm, pypi, go, maven, etc. in one tool. Simplifies dependency-scan toolchain.
Common misconfigurations
The unsafe pattern, the replacement, and the reason the two are not equivalent in production.
Risky
# Local keypair signing
cosign sign --key cosign.key <image>Hardened
# Keyless via OIDC
cosign sign <image>
# (cosign auto-detects CI OIDC token and uses Fulcio)
# In GitHub Actions:
permissions:
id-token: writeWhy it matters: Local cosign keys must be stored, distributed, and rotated — and "cosign.key" leaks find their way into Git or build logs regularly. Keyless signing uses a short-lived cert tied to a verifiable identity (your OIDC issuer + email). No keys, no rotation, much smaller attack surface.
Risky
# Verify image is signed (any signer)
cosign verify <image>Hardened
# Verify signed BY THE EXPECTED IDENTITY
cosign verify <image> \
--certificate-identity-regexp=".*@example.com$" \
--certificate-oidc-issuer=https://accounts.google.comWhy it matters: `cosign verify` without identity flags only checks "is this signed by anyone via Sigstore?" — which is trivially satisfied by an attacker signing their own image. The identity flags pin the expected signer, so only signatures from your CI / your team pass.
Risky
# GitHub Action with default permissions
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./build.shHardened
jobs:
build:
permissions:
contents: read
id-token: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e5e7e5
# ^ pinned to a specific SHA
- run: ./build.shWhy it matters: Default GITHUB_TOKEN permissions include write to contents, packages, deployments, and more. A compromised dependency in build.sh exfiltrates secrets and rewrites repository history. Explicit minimum permissions + SHA-pinned actions close the most common GitHub Actions supply-chain attacks.
Related learning paths
Cloud Native Security Engineering — Supply Chain module
Module: secure CI/CD, signing, attestation, and the gates that close the supply-chain gap.
ContinueKubernetes Supply Chain Security guide
End-to-end signing and verification for Kubernetes deployments.
ContinueSecure CI/CD Pipelines module
Module: hardened CI runners, secret management, and policy gates.
ContinueSigstore glossary entry
What Sigstore is and how cosign + Fulcio + Rekor compose.
Continue