Your first offline audit
This walks through producing an evidence bundle from a product directory, fully offline.
Fastest path: quickstart#
quickstart auto-detects your project, scaffolds a minimal sbomflow.yaml with placeholders, runs an offline audit, and prints next steps:
sbomflow quickstart .Before it writes anything, quickstart prints what it is about to do: the resolved scan root, where the evidence pack goes, which build inputs were detected, a bounded count of the in-scope files, the offline network posture, and whether anything already sits at the output path. Nothing is created until that disclosure has been printed and the run is allowed to proceed.
A path under your home directory is printed as ~/my-product, not the account-qualified absolute form — this block routinely reaches a screenshot, a shared terminal or a CI log, and it does not need to carry your account name to do its job. It is still the directory you can recognise and confirm, and you can expand the ~ back to the exact path. A path outside your home directory is printed verbatim, deliberately: "we are about to write somewhere you did not expect" is the one thing this block must never hide.
The same disclosure, machine-readable#
sbomflow quickstart ./my-product --json--json prints the disclosure as one versioned JSON document on stdout and exits without writing, auditing or asking anything — it is the pre-write surface, so a script can read the scope and decide before a run happens. Pin schema_version: it is sbomflow-quickstart-preflight-v1 here and sbomflow-quickstart-ci-preflight-v1 under --ci, which is a different document with a destination and no output directory. It is the same document the terminal form renders, not a summary assembled next to it, so the two can never disagree. In particular the JSON keeps the distinctions the text keeps: count_state says whether the file count is exact, a labelled estimate_bounded lower bound (the walk stopped at its entry budget), or unavailable; and output_state is one of absent, empty, populated, unreadable or not_a_directory — an output directory that could not be listed reports as unreadable and never as empty. Every state also travels with the sentence explaining what it means, which is the same sentence printed in the terminal.
Naming a directory, and what happens if you do not#
Naming a directory is the shape scripts and CI should use, and it is never questioned:
sbomflow quickstart ./my-productA run with no directory would use whichever directory you happen to be in. That is the ambiguous case, so:
- at a real terminal,
quickstartasks you to confirm the scope it just printed; - with output redirected, in CI, or anywhere without a terminal, it refuses rather than blocking on an answer that can never arrive. Name the directory, or accept the printed scope in advance:
sbomflow quickstart --yes--yes skips the question, never the disclosure — the scope is still printed in full, so a CI log records what was scanned. Declining the confirmation writes nothing and is not an error: the command exits 0 with nothing created.
Or use the guided, interactive setup (a real terminal only — see Interactive setup):
sbomflow setupRun a full audit#
sbomflow audit ./my-product \
--output ./evidence \
--product-name "My Gateway" \
--product-version "1.0.0" \
--product-class defaultImportant
--product-class records the manufacturer's declaration. SBOMFlow does not
classify your product; it records the class you declare (default,important-class-i, important-class-ii, or critical).
This writes a directory of provenance-bearing artifacts, including evidence-pack.json, cyclonedx-sbom.json, spdx-sbom.json, cra-coverage.json, issues.json, release-gate.json, and a portable evidence-bundle.*. Add --as-of <ISO-8601> when you need byte-reproducible release artifacts from pinned inputs and run context. See Read an evidence bundle.
cra-coverage.json is written because this run uses the default requirement regime, cra, and --product-class is a CRA declaration. Pass --requirement-regime none instead and the run assesses no requirement and writes no CRA files; everything else above is the same. See Use SBOMFlow without CRA assessment.
If the product already ships with an SBOM#
An existing CycloneDX or SPDX document in the tree is recognised but not inventoried. SBOMFlow never imports a document it merely walked past, because a document found on disk is a different claim from one you supplied — so a directory whose only input is rootfs.cdx.json audits to 0 components and records a sbom_document_not_imported note naming the file and the flag. Pass it explicitly to inventory it:
sbomflow audit ./my-product --import-sbom ./my-product/rootfs.cdx.json --output ./evidenceImported components are always labelled as a supplier claim, never as SBOMFlow's own observed evidence.
Re-running into the same output directory#
Every evidence directory carries an exact .sbomflow-output marker. When that directory is inside the product tree, a later audit records one named excluded_sbomflow_output disposition and does not re-ingest its own reports as product evidence. A custody set (sbomflow custody init) inside the product tree carries its own .sbomflow-custody-set marker and is excluded under the same disposition. Each excluded directory is named by a scan_directory_excluded_by_marker warning, so a re-run into a nested output directory reports that warning. Re-running audit on an unchanged project with the same --as-of and inputs is therefore safe and byte-reproducible. A quickstart re-run is equally safe and leaves the evidence pack and bundle byte-identical, but it records timing metrics, so metrics.json and run-completion.json differ between runs.
The exclusion is deliberately narrow. A lookalike marker is not trusted, and generic build/ or dist/ directories are not hidden wholesale: recognized lockfiles and embedded build evidence inside them are still scanned.
Offline vs. opt-in enrichment#
Offline
By default the audit uses a clearly-labelled, non-real sample vulnerability
feed (identifiers begin with CVE-SAMPLE-). No data leaves your machine.
Network — opt-in
To match real advisories, enable a named lookup explicitly, e.g. --use-osv
(OSV), --use-nvd, --use-kev, --use-epss. These send component identifiers
(package URLs) to the named service — never your source or firmware. Real
firmware/OS-layer CVEs require a distro-tagged SBOM (pkg:deb / pkg:apk).
Exit codes#
The audit uses stable exit codes. A 0 means the run completed or a gate was informational; a 1 means an enforced release gate blocked the run. Common input mistakes return 2 with a stable error code and a fix.
If your first run fails#
The two most common first-run mistakes explain themselves: pointing at a path that does not exist (E001) or at a single file instead of the product directory (E002). Every failure prints its code, a one-line fix, a docs link and the command to run next — usually sbomflow help <command> for the command that refused — and the same explanation is available offline:
sbomflow help error E001
sbomflow doctor .doctor reports your environment and config facts (Python version, optional extras, discovered config and its problems) and is the first thing to run when a failure does not explain itself. The troubleshooting guide maps every symptom to a fix.