Operator CI runbook

Everything a platform engineer needs to run SBOMFlow in continuous integration — end to end, offline by default, and driven entirely by exit codes so a pipeline can act on the result without parsing output. GitHub Actions is shown first (with a native composite action and PR annotations); GitLab and Jenkins are covered too, and any CI that can run a Python CLI can run SBOMFlow.

Everything here uses shipped, offline behaviour. A default run makes zero network requests; enabling a real vulnerability source is an explicit opt-in (see Offline vs network in CI).


The 60-second version

Add one job that installs SBOMFlow, runs an offline audit with the gate you want to enforce, annotates the PR, and uploads the evidence bundle:

bash
python -m pip install .
sbomflow audit . --output evidence --zip --fail-on-vulnerabilities --severity-threshold high
sbomflow annotate evidence --format github   # or: --format gitlab
  • The audit step exits non-zero (1) if the gate you chose blocks — that is what fails the build. With no --fail-on-* flag the gate is informational and the step exits 0.
  • annotate is output-only and offline — it turns release-gate.json + issues.json into native CI annotations and should run even when the audit step failed, so reviewers see why on the PR.

Ready-to-copy examples live in the repository under examples/ci/: github-actions-sbomflow-audit.yml, gitlab-ci-sbomflow-audit.yml, and Jenkinsfile-sbomflow-audit.


GitHub Actions, step by step

yaml
name: SBOMFlow Offline Release Gate
on:
  workflow_dispatch:
  pull_request:
  push:
    branches: [main]
permissions:
  contents: read
jobs:
  sbomflow-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-python@v6
        with:
          python-version: "3.12"
      - name: Install SBOMFlow
        run: python -m pip install .
      - name: Run offline release gate
        run: sbomflow audit . --output evidence --zip --fail-on-gaps --fail-on-vulnerabilities --severity-threshold high
      - name: Annotate the pull request with the gate verdict
        if: always()
        run: sbomflow annotate evidence --format github
      - name: Upload reviewer evidence bundle
        uses: actions/upload-artifact@v7
        if: always()
        with:
          name: sbomflow-evidence-bundle
          path: evidence/evidence-bundle.*

What each step does:

  • checkout / setup-python — standard. SBOMFlow supports CPython 3.11–3.14 and needs no third-party runtime dependencies.
  • installpip install . from the repo, or install the published wheel.
  • audit — the deterministic pipeline. --zip also writes evidence/evidence-bundle.zip; --fail-on-* selects the enforced gate; --severity-threshold sets the level at/above which findings count.
  • annotate (if: always()) — runs even after a blocked gate so the reason appears inline on the PR. Offline and output-only.
  • upload-artifact (if: always()) — keeps the reviewer bundle regardless of the gate result; you never lose the evidence.

Supply-chain hygiene: the example pins actions to major version tags for readability. For production, pin third-party actions to a full commit SHA.

Native composite action

The repository also ships a composite action at .github/actions/sbomflow/action.yml with typed inputs (target, output, use-osv, fail-on-gaps, fail-on-vulnerabilities, fail-on-unreviewed, severity-threshold, …). Use it instead of hand-writing steps when you want a smaller, declarative job.


GitLab CI

Use examples/ci/gitlab-ci-sbomflow-audit.yml. The audit step is identical; for merge-request feedback, emit a Code Quality report:

bash
sbomflow audit . --output evidence --zip --fail-on-vulnerabilities --severity-threshold high
sbomflow annotate evidence --format gitlab > gl-code-quality.json

Then publish gl-code-quality.json as a codequality report artifact — GitLab renders the findings directly on the merge request.

Jenkins

Use examples/ci/Jenkinsfile-sbomflow-audit. Run the same audit command in a stage; a non-zero exit fails the stage (that is the gate). Archive evidence/evidence-bundle.* as build artifacts. Jenkins has no native SBOMFlow annotation format — rely on the exit code and the archived bundle.


Choosing a gate policy

The gate is informational until you enable a policy, and every enforced block records exactly which finding/gap IDs are responsible. Pick the --fail-on-* flags that match your risk bar:

FlagBlocks the build when…
--fail-on-vulnerabilitiesany advisory finding at/above the threshold
--fail-on-keva finding is on the CISA Known Exploited Vulnerabilities list
--fail-on-gapsa required evidence item is missing at/above the threshold
--fail-on-unreviewedobserved evidence has no human review yet
--fail-on-new-vulnerabilities / --fail-on-new-kev / --fail-on-new-critical-or-higha new issue appeared vs the previous release
--fail-on-evidence-regressiona previously-resolved finding reappeared
--fail-on-reachable-vulnerabilities / --fail-on-new-reachable-vulnerabilitiesa finding has reachability context
--fail-on-denied-licensea component matches a denied-license policy
--fail-on-support-period-missingthe declared support period is absent
--fail-on-missing-approvalsrequired sign-off has not been recorded

--severity-threshold {low,medium,high,critical} sets the level for the severity-based flags. Reviewed VEX (fixed / valid not_affected) and unexpired waivers suppress a block — machine observations alone never do.

Rehearse before enforcing. Add --gate-dry-run to see exactly what would block: the gate is evaluated and reported in full, release-gate.json is marked dry_run: true (its exit_code shows the would-be result), and the process always exits 0. Wire it into CI first, watch a few runs, then drop --gate-dry-run to let it fail the build.


Exit codes in CI

Act on the number, not the log text:

CodeMeaningTypical CI response
0success, or an informational / dry-run gatepass
1an enforced gate blocked — a policy decision, not a crashfail the build; read release-gate.json
2usage / input / IO / config error — see the [Ennn] codefix the invocation; check the error reference
3init refused / bundle verification failedinvestigate before proceeding
4structural validation failure (sbomflow validate)treat as an integrity incident
5strict warnings-as-errors matched (--strict)resolve the warnings or drop --strict

Full detail: exit codes. A blocked gate (1) and a crash (2+) are deliberately different codes so a pipeline can distinguish "policy said no" from "the tool could not run."


Artifact retention & release-to-release drift

Always upload the evidence bundle (evidence/evidence-bundle.*) — it is the portable reviewer/auditor deliverable and your record of what shipped. To compare against the previous release, restore its evidence/ directory from your release storage and pass it back in:

bash
sbomflow audit . --output evidence --zip \
  --previous-output previous-evidence \
  --fail-on-evidence-regression --fail-on-new-kev

This blocks only on new problems or regressions since the last release — ideal once a baseline exists.


Reproducibility in CI

Pass a fixed --as-of timestamp for byte-reproducible artifacts across re-runs (operational timing is kept in a separate section so it never affects the reproducible output). Re-running the same commit + inputs + --as-of yields identical evidence, which you can confirm with sbomflow validate or sbomflow compare-releases.


Offline vs network in CI

  • Default: fully offline. No network egress is required; air-gapped runners work as-is.
  • Real advisories need an explicit opt-in: --use-osv (queries the OSV API) or a local snapshot file (--nvd-file, --kev-file, --epss-file, or a pinned vulndb snapshot). Prefer snapshot files on restricted runners so the build stays deterministic and offline.
  • Behind a proxy / TLS gateway, enabled lookups honour standard HTTPS_PROXY / SSL_CERT_FILE environment variables; a failure is surfaced as a warning and never corrupts already-written evidence. See Security & privacy for the full per-command network table.

Failure modes in CI — what each means and what to do

Symptom in the pipelineCauseAction
audit step exits 1an enforced gate blockedexpected when a policy matched; read release-gate.json / run sbomflow explain evidence --gate; suppress legitimately via reviewed VEX or a waiver
audit step exits 2 with [Ennn]usage / input / config errorfix per the error reference (E001 bad path, E003 bad --output, E004 disk/permission, E010 bad config)
step exits 4validate found a hash/chain/count mismatchintegrity incident — regenerate from source, do not edit artifacts
step exits 5--strict turned a warning into a failureinspect scan-warnings.json; resolve or drop --strict
annotate shows nothingno findings, or wrong output dirconfirm the audit wrote release-gate.json/issues.json to the path you pass to annotate
results differ between runsno pinned --as-ofpass a fixed --as-of

More symptoms and fixes: Troubleshooting.


See also