A software bill of materials (SBOM) is now table-stakes for supply-chain security, but bolting CycloneDX onto an already-slow CI/CD is a sure way to spark dev revolt. This post shows how we generate, diff, sign, and publish SBOMs on every pull-request in < 90 seconds—and prove it with real latency numbers from a Micro-GCC squad maintaining an 8-service Node + Go platform. Copy-paste GitHub Actions included.
Most teams produce a single SBOM artifact the night before release. That works—until:
Our rule: SBOMs should appear as fast as SAST warnings. That means every PR, every image tag, every release.
Anatomy of a 90-Second SBOM Job (320 w)
yaml
CopyEdit
jobs:
sbom:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
– uses: actions/checkout@v4
– name: Set up Node
uses: actions/setup-node@v4
with: node-version: ’20’
– name: Install deps (cached)
uses: bahmutov/npm-install@v1
– name: Generate CycloneDX JSON
run: npx @cyclonedx/bom -o sbom.json
– name: Diff against main
id: diff
run: |
echo “::set-output name=changed::$(git diff origin/main sbom.json | wc -l)”
– name: Upload to S3
if: steps.diff.outputs.changed != ‘0’
uses: jakejarvis/s3-sync-action@v0.5.1
with:
args: –acl private –follow-symlinks –exact-timestamps
– name: Sign SBOM
run: cosign attach sbom \
–sbom sbom.json \
ghcr.io/your/repo:${{ github.sha }}
Latency breakdown (median):
| Step | Time |
| npm-install (cache hit) | 25 s |
| Generate JSON | 12 s |
| Diff | 1 s |
| S3 upload + Cosign sign | 45 s |
| Total | 83 s |
Tip ➊ Cache dependencies by checksum, not lockfile date; 95 % of PRs reuse cache.
Tip ➋ Skip S3 upload when diff == 0 to save 40+ seconds on doc-only commits.
| Model | Pros | Cons | When to Use |
| Container Attach (cosign) | Co-located with image; verified by cosign verify | Only OCI images, not zip/tar artifacts | Microservices on K8s |
| Artifact Repo (S3 / MinIO) | Works for any file; cheap | Requires URL mapping to version | Polyglot monorepos |
| Git Tag | Easy diffing | Bloats repo; binary in Git | Small libs or infra templates |
Signature standard: we use Sigstore/cosign—developers need zero key management; GitHub OIDC tokens issue short-lived certs.
Result: devs see SBOM as their tool, not security theater.
Poly-Repo: push each SBOM to an S3 bucket with path /service/<repo>/<sha>/sbom.json. A Glue crawler builds an Athena table for org-wide queries:
sql
CopyEdit
SELECT repo, COUNT(*) AS crits
FROM sbom_view
WHERE severity = ‘CRITICAL’
GROUP BY repo
ORDER BY crits DESC;
SAP ABAP: SAP CP orchestrates ABAP Git exports → Node job runs cyclonedx-bom on *.abapgit.xml. Attach SBOM as a transport artifact; ATC gate fails if missing.Edge case – binary blobs: use CycloneDX component-hash to reference fixed firmware. Store blob SBOMs in S3; link hash in SBOM externalReferences.
Before SBOM pipeline: release retro spent 4 hours on dependency review; Black-Friday freeze 3 days.
After:
PM’s comment: “We found vulnerabilities while they were still headlines, not headlines about us.”
| Pitfall | Fix |
| “CI time blew up” | Cache deps; skip upload on no-diff; parallelise sign/upload. |
| “SBOM invalid JSON” | Use CycloneDX CLI ≥ v3.7; run cyclonedx validate. |
| “Too many CVE false positives” | Switch to OWASP dependency-track w/ policy suppression YAML. |
| “Private NPM packages missing” | Add GitHub PAT with module read scope; CycloneDX CLI resolves them. |