BLOG/TEAM CI/CD
THEME
TEAM CI/CD

Sign and SBOM every release

Signing the image and generating an SBOM are the supply-chain steps everyone agrees to and then forgets under deadline. Make them steps in the release workflow, with a verify that won't let an unsigned artifact through.

May 21, 2026 OrchStep Team 6 minROLE: Security EngineerSCALE: Any
RUNNABLE DEMO
Full source for this post: blog/sign-and-sbom
VIEW SOURCE

Two supply-chain steps show up in every security review and disappear in every crunch: sign the artifact, and produce a software bill of materials for it. Nobody disagrees with them. They just live in a runbook nobody opens, so the release that goes out at 6pm on a Friday is the one that's unsigned with no SBOM — exactly the release you'll wish you could attest to three weeks later.

The reliable way to make a step happen is to make it part of the workflow that already runs, with a check at the end that fails if the step didn't. Here's cosign and syft wrapped as ordinary release steps, plus a verify.

The workflow

orchstep.yml
name: supply-chain
# Wrap the supply-chain steps you keep forgetting: generate an SBOM,
# sign the image, then verify the signature before you call it shipped.
defaults:
  image: ghcr.io/acme/api:1.4.0

tasks:
  # `orchstep run attest`
  attest:
    steps:
      - name: sbom
        func: shell
        do: echo "syft {{ vars.image }} -o spdx-json=sbom.json"

      - name: sign
        func: shell
        do: echo "cosign sign --yes {{ vars.image }}"
        retry:
          max_attempts: 3
          interval: "1s"
          backoff_rate: 2.0

      - name: verify
        func: shell
        do: echo "cosign verify {{ vars.image }}"

      - name: gate
        func: assert
        args:
          condition: '{{ ne vars.image "" }}'
          message: "an image reference is required to sign"

Swap the echo for the real tools and nothing else changes: syft writes the bill of materials, cosign sign signs the image, and cosign verify checks the signature you just made — each pointed at the same image reference.

Why each piece is here

SBOM first. The sbom step runs syft against the exact image reference you're about to ship. Generating it as part of the release — not in a quarterly audit — means the bill of materials matches the artifact, because they're produced from the same variable in the same run.

Sign with a retry. Signing talks to a registry and a transparency log, and those flake. The retry: block gives cosign sign three attempts with backoff so a transient network blip doesn't fail an otherwise-good release:

retry:
  max_attempts: 3
  interval: "1s"
  backoff_rate: 2.0

Verify what you signed. The verify step runs cosign verify immediately after signing. This catches the embarrassing case where the sign step "succeeded" but the signature isn't actually retrievable — you find out in the same pipeline, not when a downstream admission controller rejects the image in prod.

Gate on the inputs. The assert step makes the obvious mistake impossible: signing nothing. It refuses to run the task without an image reference, so an empty --var can't produce a "successful" release that signed an empty string.

- name: gate
  func: assert
  args:
    condition: '{{ ne vars.image "" }}'
    message: "an image reference is required to sign"

Point it at any build

The image is a variable, so the same task attests whatever your build produced:

orchstep run attest --var image=ghcr.io/acme/api:2.0.0

And you can preview the exact commands — fully resolved, real tags and all — before they touch a registry:

orchstep run attest --dry-run

What you gained

ConcernRunbook stepOrchStep
Generate an SBOM"remember to run syft"a sbom step every release
Sign the imagemanual cosign signa sign step with retry
Flaky registryre-run by handretry: { max_attempts: 3 }
Confirm the signaturehope it tooka verify step
Sign nothing by mistakepossibleassert on the image ref

This doesn't replace your signing tooling — cosign and syft still do the cryptography. It makes "we sign and SBOM every release" true by construction instead of true when someone remembers.

Where to go next

Install with curl -fsSL https://orchstep.dev/install.sh | sh, then orchstep run attest --dry-run to see the resolved sign-and-verify plan.

#SECURITY#SUPPLY-CHAIN#SBOM#SIGNING
Try it in two minutes — one binary, no signup.
curl -fsSL https://orchstep.dev/install.sh | sh

RELATED — TEAM CI/CD