TutorialsSBOM as evidence

Tutorial: SBOM as evidence

Customers and regulators increasingly ask for a Software Bill of Materials as part of attestation. Pencheff produces SPDX 2.3 and CycloneDX 1.5 from any repository using the same manifest parsers that drive SCA — so the SBOM and the findings list are consistent by construction.

Scenario

  • Customer ask. Acme Corp’s vendor security questionnaire requires an SBOM “in CycloneDX 1.5 format” for every product release.
  • Constraint. The SBOM has to match the production build, not a developer’s laptop.
  • Goal. Auto-attached, immutable, machine-readable SBOMs on every release tag.

1. Wire SBOM generation into the release tag

Generation runs from a release-tag GitHub workflow so it’s tied to the build that actually ships:

# .github/workflows/release.yml
name: release
 
on:
  push:
    tags: [ "v*.*.*" ]
 
jobs:
  sbom:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
 
      - name: Re-generate SBOM and upload as release artifact
        env:
          PENCHEFF_API_KEY: ${{ secrets.PENCHEFF_API_KEY }}
          PENCHEFF_API_BASE: ${{ secrets.PENCHEFF_API_BASE }}
          PENCHEFF_REPO_ID: ${{ secrets.PENCHEFF_REPO_ID }}
        run: |
          curl -X POST -H "Authorization: Bearer $PENCHEFF_API_KEY" \
            "$PENCHEFF_API_BASE/repos/$PENCHEFF_REPO_ID/sbom"
          curl -H "Authorization: Bearer $PENCHEFF_API_KEY" \
            "$PENCHEFF_API_BASE/repos/$PENCHEFF_REPO_ID/sbom" \
            -o sbom-cyclonedx.json
 
      - uses: actions/upload-artifact@v4
        with:
          name: sbom
          path: sbom-cyclonedx.json

POST /repos/{id}/sbom regenerates the SBOM from the latest commit on the default branch and replaces the previous one; GET pulls it back as JSON. The SBOM also re-renders on the repo’s page in Table + JSON views.

2. Pull the SBOM after a release

curl -H "Authorization: Bearer $PENCHEFF_API_KEY" \
  "$PENCHEFF_API_BASE/repos/$REPO_ID/sbom" \
  -o acme-api-sbom-cyclonedx.json

The latest SBOM is durable on the repo row — subsequent generations replace it. For per-release immutability, attach the artefact you uploaded in step 1 to the GitHub Release directly.

3. Use it as audit evidence

  • NIST 800-53 SR-3 — “Supply Chain Controls and Processes.” The SBOM is the source-of-record for components and suppliers.
  • NIST 800-53 SC-12 — “Cryptographic Key Establishment.” Crypto libs in the SBOM are the inputs the auditor checks against your approved list.
  • SOC 2 CC8.1 — “Change Management.” The SBOM diff between two release tags is your change ledger.
  • ISO 27001:2022 A.5.21 — “Managing Information Security in the ICT Supply Chain.” Same SBOM, framed as a supplier-management artefact.

The compliance rollup at /repos/scans/{id}/compliance cites these controls automatically when the SCA pass produces findings.

4. Diff two SBOMs for change-management evidence

The simplest path is to keep the SBOM from each release tag as a GitHub Release asset and diff them with jq:

# Pull both SBOMs from your release tags into a single shell.
gh release download v1.41.0 -p sbom-cyclonedx.json -O sbom-old.json
gh release download v1.42.0 -p sbom-cyclonedx.json -O sbom-new.json
 
# Diff component-version pairs — the result is the change ledger.
jq -r '.components[] | "\(.purl)"' sbom-old.json | sort > old.txt
jq -r '.components[] | "\(.purl)"' sbom-new.json | sort > new.txt
diff old.txt new.txt > sbom-diff.txt

The diff highlights added components, removed components, and version bumps; drop the output straight into the release PR description for the SOC 2 CC8.1 ledger.

Each SCA finding card links to the SBOM component row that owns it. An auditor reading a CVE finding can pivot to the exact PURL + license + supplier without leaving the report.

Deliverable

  • CycloneDX + SPDX SBOMs, immutably attached to every release tag.
  • A diff artefact for SOC 2 CC8.1 change-management evidence.
  • A compliance rollup that cites the SBOM for SR-3 / SC-12 / A.5.21.

Next