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:
python -m pip install .
sbomflow audit . --output evidence --zip --fail-on-vulnerabilities --severity-threshold high
sbomflow annotate evidence --format github # or: --format gitlab- The
auditstep 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 exits0. annotateis output-only and offline — it turnsrelease-gate.json+issues.jsoninto 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
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.
- install —
pip install .from the repo, or install the published wheel. - audit — the deterministic pipeline.
--zipalso writesevidence/evidence-bundle.zip;--fail-on-*selects the enforced gate;--severity-thresholdsets 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:
sbomflow audit . --output evidence --zip --fail-on-vulnerabilities --severity-threshold high
sbomflow annotate evidence --format gitlab > gl-code-quality.jsonThen 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:
| Flag | Blocks the build when… |
|---|---|
--fail-on-vulnerabilities | any advisory finding at/above the threshold |
--fail-on-kev | a finding is on the CISA Known Exploited Vulnerabilities list |
--fail-on-gaps | a required evidence item is missing at/above the threshold |
--fail-on-unreviewed | observed evidence has no human review yet |
--fail-on-new-vulnerabilities / --fail-on-new-kev / --fail-on-new-critical-or-high | a new issue appeared vs the previous release |
--fail-on-evidence-regression | a previously-resolved finding reappeared |
--fail-on-reachable-vulnerabilities / --fail-on-new-reachable-vulnerabilities | a finding has reachability context |
--fail-on-denied-license | a component matches a denied-license policy |
--fail-on-support-period-missing | the declared support period is absent |
--fail-on-missing-approvals | required 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:
| Code | Meaning | Typical CI response |
|---|---|---|
0 | success, or an informational / dry-run gate | pass |
1 | an enforced gate blocked — a policy decision, not a crash | fail the build; read release-gate.json |
2 | usage / input / IO / config error — see the [Ennn] code | fix the invocation; check the error reference |
3 | init refused / bundle verification failed | investigate before proceeding |
4 | structural validation failure (sbomflow validate) | treat as an integrity incident |
5 | strict 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:
sbomflow audit . --output evidence --zip \
--previous-output previous-evidence \
--fail-on-evidence-regression --fail-on-new-kevThis 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 pinnedvulndbsnapshot). 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_FILEenvironment 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 pipeline | Cause | Action |
|---|---|---|
audit step exits 1 | an enforced gate blocked | expected 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 error | fix per the error reference (E001 bad path, E003 bad --output, E004 disk/permission, E010 bad config) |
step exits 4 | validate found a hash/chain/count mismatch | integrity incident — regenerate from source, do not edit artifacts |
step exits 5 | --strict turned a warning into a failure | inspect scan-warnings.json; resolve or drop --strict |
| annotate shows nothing | no findings, or wrong output dir | confirm the audit wrote release-gate.json/issues.json to the path you pass to annotate |
| results differ between runs | no pinned --as-of | pass a fixed --as-of |
More symptoms and fixes: Troubleshooting.
See also
- CLI reference — every command and flag.
- Exit codes · Error reference · Warning catalog.
- Security & privacy — the exact network behaviour of every command.
- Troubleshooting — symptom → fix for the whole product.