Installation & access

SBOMFlow is a standard-library Python CLI with zero required runtime dependencies. It requires Python 3.11 or newer and runs fully offline on a laptop or in locked-down CI.

Important

SBOMFlow is in an early, invite-only phase while we work directly with a
small number of design partners, and the source is not publicly distributed
yet. v0.5.5 is available to approved private testers. To evaluate it on
your own build, request access below — we'll set up a short call and get you a
copy to test. Public pipx, PyPI, and Homebrew channels will follow a public
release.

Warning

There is no public package index entry for v0.5.5. Any instruction of the
shape pip install sbomflow or pip install 'sbomflow[extra]' resolves the
name sbomflow against an index that does not carry this release, and it will
fail. Install from the local wheel or the local source tree you were given,
exactly as described below.

Request access to evaluate#

Want to run SBOMFlow against a real build of your own? Email hello@sbomflow.com with a line about your product and build system (Yocto, Zephyr, Buildroot, or other). We'll arrange a short call to understand your release workflow and set you up with an evaluation copy.

The engine runs fully offline on your own machines, so nothing about your source, firmware, or findings ever leaves your environment — evaluating it does not send us your code.

Before you ask, or before you install: Evaluation terms & access says where the licence notice for a copy is, what an entitlement is not, and how to request the terms for an evaluation, a pilot or a design-partner arrangement.

Install the v0.5.5 tester build#

Approved testers receive one wheel through the controlled access channel — never from a package index, and never from a public download link. The journey is four steps, and the fourth one is the part most install guides leave out.

1 — Receive the wheel. The file you are sent is named:

sbomflow-0.5.5-py3-none-any.whl

Alongside it you receive its SHA-256 digest and the written list of restrictions that apply to this build.

2 — Verify the digest before you install anything. The v0.5.5 wheel's SHA-256 is:

6744b6ab1c52d27a31b3fbbca606176a1b0b012726760bfaead4ab3651a6ae1e
bash
sha256sum sbomflow-0.5.5-py3-none-any.whl      # Linux
shasum -a 256 sbomflow-0.5.5-py3-none-any.whl  # macOS

Compare the output character for character with the digest above. If it differs, stop and ask for a fresh copy — do not install it.

On Windows neither command exists. PowerShell has a built-in one, and certutil is the equivalent from cmd.exe:

powershell
Get-FileHash .\sbomflow-0.5.5-py3-none-any.whl -Algorithm SHA256
certutil -hashfile sbomflow-0.5.5-py3-none-any.whl SHA256

Both Windows commands report the digest in upper case where sha256sum reports it in lower case, so compare the two case-insensitively — the characters still have to match one for one. Inside WSL you are on Linux: use sha256sum against the file as seen from the WSL filesystem.

Note

Those two Windows lines are unverified by this project. They are the
platform's own documented digest tools, not something SBOMFlow ships or
executes, and no SBOMFlow lane runs them: the native-Windows CI lane runs the
test suite rather than an install, and it has never completed green. They are
given because a Windows or WSL evaluator otherwise has no way to check the
wheel before installing it. The supported Windows path is
WSL.

3 — Install from the local wheel. Use a clean virtual environment, and forbid the index explicitly:

bash
python3 -m venv .venv
.venv/bin/python -m pip install --no-index --no-deps ./sbomflow-0.5.5-py3-none-any.whl
.venv/bin/sbomflow --version
.venv/bin/sbomflow doctor .

--no-index forbids any index lookup, so the install cannot silently fall back to fetching something else under the same name. --no-deps is safe because the core has zero required runtime dependencies. This is the exact form the release process installs on a clean machine, so it is the one tested path.

If pip refuses with externally-managed-environment

Debian, Ubuntu, Fedora and Homebrew ship an interpreter marked externally managed (PEP 668). Installing into it — including with --user — is refused before anything is downloaded or written:

error: externally-managed-environment

× This environment is externally managed

That refusal is the packaging system protecting itself, not a problem with the wheel, and --break-system-packages is the wrong answer for an evaluation copy. There are two correct remedies, and the venv above is already one of them:

  • A virtual environment. python3 -m venv .venv creates an interpreter that carries no such marker, which is why step 3 installs into .venv rather than into the system interpreter. Nothing further is needed.
  • pipx, if you would rather have sbomflow on your PATH than a path into .venv. pipx makes and manages the virtual environment for you, and it accepts a local wheel — point it at the file, never at the name:

``bash pipx install ./sbomflow-0.5.5-py3-none-any.whl sbomflow --version ``

pipx install sbomflow (the bare name) resolves against a package index that does not carry this release and fails; see the warning at the top of this page. Uninstall with pipx uninstall sbomflow.

Note

The pipx form is unverified by this project. The release process installs
the wheel into a plain venv with --no-index --no-deps, and that is the path
every clean-machine check exercises; no lane installs through pipx. It is
documented because a PEP 668 host is the common case on a non-admin Linux
workstation, and because the wheel it is handed is the same file either way.

4 — Understand what the digest does and does not prove. The checksums prove the bytes you received match the bytes the build produced. They are not a signature and do not establish who built them.

Important

This build is unsigned and carries no CI attestation. The wheel is not
signed, and no hosted continuous-integration service produced a build
attestation for it. Matching the digest tells you the file was not altered
between the build and your machine; it tells you nothing about who produced
it. Treat the access channel itself as the thing that establishes provenance,
and verify the digest through it rather than from a copy that travelled with
the file.

What was verified about this build#

FactValue
Release tagv0.5.5
Release commitbc402ff3886351f90c8e82912300ddfc54302df0
Release tree3f1fefac51b633445568c3a82543fe4fed13cfd8
Release wheelsbomflow-0.5.5-py3-none-any.whl
Wheel SHA-2566744b6ab1c52d27a31b3fbbca606176a1b0b012726760bfaead4ab3651a6ae1e
Byte-identical builds of that wheel3
Wheel entries inspected501 of 501
Network markers in the install verification0

Every mandatory release check ran and passed before the build was handed to anyone; none was skipped, and a skipped check would never have counted as a pass. Three independent builds of the same tree produced a byte-identical wheel, and the clean-machine install ran with the index and dependency resolution both switched off, which is why the network-marker count is a measurement rather than an assumption. Every one of the wheel's 501 entries was inspected, so the examined count and the total are the same number.

Warning

**The reproducibility scope is one machine, one interpreter, one operating
system.** All three builds ran on the same machine, the same CPython build and
the same OS. This is same-environment reproducibility. It is not a
cross-platform or toolchain-independent reproducibility claim, and it must not
be read as one. Separately, "no known release-blocking defects" is not
"error-free": it means nothing release-blocking was known at the time the
build was certified.

The honest one-line summary of this build is: a certified private v0.5.5 tester build with no known release-blocking defects, subject to the documented restrictions.

Install from a source tree instead#

If your access came as a source tree rather than a wheel, install that tree directly. The name sbomflow is never resolved against an index here — the . is the local directory:

bash
cd sbomflow            # the source tree provided with your access
python -m pip install .
sbomflow --version

This installs the sbomflow entry point. Verify it resolves:

bash
sbomflow doctor .

Optional extras#

Some optional capabilities use the cryptography package. Build-provenance verification uses the attest extra and bundle signing uses the sign extra; the baseline engine never requires either.

From a source tree, name the extra against the local path:

bash
python -m pip install ".[attest,sign]"

From a wheel install there is no local path to attach an extra to, and sbomflow[attest,sign] would resolve against an index that has no v0.5.5. So install the extra's own third-party package by name instead — that is exactly what the extra pulls in:

ExtraWhat to installWhat it unlocks
yamlpyyamlfull YAML config parsing (a bundled fallback loader handles simple sbomflow.yaml files without it)
validatejsonschemaofficial JSON-Schema validation of emitted SBOMs and of sbomflow.yaml
attestcryptographybuild-provenance verification
signcryptographybundle signing and signature verification
reachabilitytree-sitter, tree-sitter-c, tree-sitter-cppsource-reference observation for reachability context (never a VEX status or a gate decision)
bash
.venv/bin/python -m pip install cryptography     # the attest / sign extra

sbomflow doctor . lists which extras are present, and every command reports an absent extra rather than failing the whole run.

Offline

Installing the core touches nothing over the network — it has zero
dependencies; only the optional extras above fetch third-party packages.
Running SBOMFlow afterwards is offline by default.

Air-gapped install (no package index)#

For machines with no PyPI egress, build a self-contained bundle on a networked machine and carry it across:

bash
make offline-installer          # from the source tree -> dist/offline/ and a reproducible dist/sbomflow-offline-<version>.zip

The bundle holds the wheel, the sdist, a SHA256SUMS file, and an INSTALL.txt with the exact digests and verify-then-install steps. On the air-gapped machine:

bash
sha256sum -c SHA256SUMS
python3 -m venv /opt/sbomflow-venv
/opt/sbomflow-venv/bin/python -m pip install --no-index --no-deps ./sbomflow-<version>-py3-none-any.whl
/opt/sbomflow-venv/bin/sbomflow --version

--no-index forbids any index lookup, and --no-deps is safe because the core has zero required runtime dependencies. The release process performs exactly this install on a clean machine, so a core that had grown a dependency would fail it. This release carries no build-service attestation: the check was run and recorded locally. See the release record.

The bundle builder never reaches the network. Optional extras have third-party dependencies, so they are included only when you supply your own wheelhouse — and when you do not, INSTALL.txt says so plainly instead of shipping a core-only bundle that looks complete:

Populate that wheelhouse by naming the extras' own third-party packages. Do not write pip download 'sbomflow[…]': that resolves the name sbomflow against an index which does not carry this release, so it fails before it downloads anything.

bash
pip download pyyaml jsonschema cryptography -d ./wheelhouse   # networked machine
make offline-installer EXTRAS_DIR=./wheelhouse

Those three packages are the whole of the yaml, validate, attest and sign extras. Add tree-sitter tree-sitter-c tree-sitter-cpp if you also want the reachability extra.

Installing the extras from that wheelhouse#

The --no-index --no-deps ./<file>.whl form above installs one named file and resolves nothing. An extra's package does have dependencies of its own, so point pip at the directory instead of at a file and let it resolve inside that directory:

bash
/opt/sbomflow-venv/bin/python -m pip install \
  --no-index --find-links=./wheelhouse cryptography

--find-links=DIR gives pip a local place to resolve from; --no-index stops it consulting an index while it does so. Keep both on the command: they are the two halves of one instruction, and each was measured on its own. With --no-index and no --find-links, pip has nowhere to look and says so — No matching distribution found — rather than quietly reaching for the network. With both, the install completes from the directory even when the configured index URL cannot resolve at all, which is what makes it an air-gapped form rather than one that merely happens to hit a cache.

Any extra's dependencies must already be in the directory: pip download on the networked machine puts them there, because it downloads the full dependency tree by default.

The checksums prove the bytes you received match the bytes the build produced. They are not a signature and do not establish who built them.

Deployment contexts#

ContextInstallation and durable state
Non-admin workstationUse a virtual environment, or pipx if you want the command on PATH. Do not reach for python -m pip install --user .: a Debian, Ubuntu, Fedora or Homebrew interpreter is externally managed (PEP 668) and refuses that form outright — see the PEP 668 remedy above. Keep the evidence store and vulnerability snapshots in operator-owned directories with access limited to the reviewers who need them.
CI containerMount persistent volumes for the evidence store and vulnerability database; do not leave either only in the container filesystem. Pin the evaluation-copy revision or wheel digest, run offline by default, and make network enrichment an explicit job choice.
Ephemeral CI runnerRestore the evidence store and .sbomflow-vulndb from an access-controlled cache or artifact before the run, then persist the updated state after a successful run. If neither is restored, the job starts without release history or cached snapshot state; it must not describe that absence as a clean result.
Locked-down enterpriseUse the air-gapped bundle or an internally mirrored, digest-verified wheel. Run under a dedicated unprivileged account, keep source and evidence on approved local storage, permit no egress by default, and inject credentials only for an explicitly enabled integration.

A minimal container job should bind durable host or managed-runner storage rather than the image layer. Adapt the paths to your runner and access policy:

bash
docker run --rm --network none \
  --mount type=bind,src="$PWD/product",dst=/work/product,readonly \
  --mount type=volume,src=sbomflow-store,dst=/var/lib/sbomflow/store \
  --mount type=volume,src=sbomflow-vulndb,dst=/var/lib/sbomflow/vulndb \
  sbomflow-evaluation:local \
  sbomflow analyze /work/product --output /var/lib/sbomflow/store/current

The store and vulnerability database can contain release metadata and security findings. Back them up, restrict their readers, and apply the same retention and incident-response controls as other product-security evidence.

Measured capacity reference#

The current synthetic 50,000-component reference pack measured approximately 187 MB of output and 0.85 GB peak resident memory. This is a CI alarm reference, not a supported ceiling, performance guarantee, SLA, or prediction for a particular firmware estate. Input shapes, dependency density, findings, filesystem behavior, and optional artifacts can materially change both figures. Validate the complete workflow with your own representative build artifacts and leave headroom for the Python process, temporary files, and retained releases.

Compatibility matrix#

"Python 3.11 or newer" is a floor, not a promise. The honest answer to "is my Python and OS supported?" has three parts: which CI lane covers the combination, which events actually trigger that lane, and whether the lane has ever completed green. A lane that exists but is skipped on your change proves nothing, and a lane that runs and fails is not the same as one that was never tried. The table below is kept in lockstep with the CI workflow by two drift tests — one pins the combinations, one pins the trigger of each row — so neither can quietly fall out of date. Verification states below were read from the complete CI history.

PythonOperating systemStatus
3.11Ubuntu (ubuntu-latest)Runs make test + make demo on every push; has completed green
3.12Ubuntu (ubuntu-latest)Runs make test + make demo on pull requests, the nightly schedule and manual dispatch — not on pushes to main; has completed green
3.13Ubuntu (ubuntu-latest)Runs make test + make demo on pull requests, the nightly schedule and manual dispatch — not on pushes to main; has completed green
3.14Ubuntu (ubuntu-latest)Runs make test on every push (optional-extras lane), plus make test + make demo on pull requests and nightly; has completed green
3.14macOS on Apple Silicon (macos-latest)Runs make test + make demo on pull requests, the nightly schedule and manual dispatch — not on pushes to main; has completed green
3.14Windows (windows-latest)Runs make test (invoked directly — Windows has no make) on pull requests, the nightly schedule and manual dispatch — not on pushes to main. Has never completed green. Native Windows is not a supported platform — see below

Read the trigger column carefully before drawing conclusions from a green build badge on main. A push to main runs the Ubuntu 3.11 lane, the 3.14 optional-extras lane, the linting/typing lane and the memory-budget lane, and skips the 3.12/3.13/3.14 interpreter fan-out, macOS, Windows, the reproducible-build check and the air-gapped installer check. A green push run is therefore evidence about Ubuntu on two interpreters, and about nothing else.

macOS and Windows each run one interpreter lane deliberately: a second and third operating system catch OS differences (paths, file locking, archive behaviour) — Windows most of all, since it is the one platform whose file-locking primitive differs — while the four-minor interpreter spread is covered by the Ubuntu rows above.

Two combinations carry no verification, for two different reasons. They are named rather than left to inference, and the two reasons are not merged into one word:

  • macOS on Intel — Unverified. No CI lane exists and no run has been performed, so nothing is known either way.
  • Native Windows — tested and red, which is a different and stronger statement than unverified. A lane exists, runs on every pull request and every nightly, and has never passed. The next section says exactly what it reports.

Everything else is verified by running the full offline suite locally on macOS on Apple Silicon before a change lands. That local run covers no other operating system and no packaging step — wheel and sdist reproducibility and the air-gapped install are proven only by the CI lanes above, which do not run on pushes to main.

It reaches a second interpreter in exactly one place, and the scope is worth stating precisely rather than rounding up. The timestamp-parsing tests re-run their corpus under every other CPython installed on the machine, because datetime.fromisoformat accepts different spellings on different minors and a suite that measures one interpreter cannot see that split at all. Those tests find interpreters by searching known install locations and confirming each one by executing it. When the machine genuinely has only one interpreter they skip, and say which of those two facts applies; a search that finds nothing at all fails instead of skipping.

This is agreement about timestamp parsing, on whatever interpreters happen to be installed. It is not a cross-interpreter determinism check, it does not prove byte-identical evidence across versions, and it claims nothing about a minor that is not installed locally. The published support matrix is still carried by the CI lanes above.

Windows: use WSL#

Run SBOMFlow on Windows through WSL. Native Windows is not a supported platform today, and this section says plainly why rather than leaving you to discover it.

The native-Windows CI lane runs and has never passed: the whole offline suite executes there and completes with failures, consistently across runs. That is a bounded, real defect surface — not a lane that never got started, and not a platform that quietly works. Until those failures are fixed and the lane runs green, any claim that native Windows works would be a guess, and this page will state the result when there is one.

The reviewer decision-write lock is OS-backed on Windows — an exclusive byte-range lock, not a no-op that would let two concurrent sbomflow review processes clobber a decision. That is a real piece of Windows support, and it is still not sufficient on its own to call the platform verified.

Under WSL you are on a Linux environment, which matches the Ubuntu rows above — those rows have completed green, so WSL rests on verified ground rather than on hope. Three further things about Windows-shaped input are worth stating:

  • Path-traversal safety is hardened for Windows-shaped input. SBOMFlow only ever writes POSIX member names, and a dedicated robustness suite proves the single shared safety gate refuses the full Windows attack surface — backslash separators (..\..\etc), UNC and \\?\ device paths, drive-absolute and drive-relative specs (C:\…, C:file), and NTFS alternate data streams (file:$DATA) — while ordinary relative members are still accepted.
  • Reserved device names are a known native-Windows edge. A member literally named CON, NUL, or COM1 is a device on native Windows; SBOMFlow treats it as an ordinary relative name (refusing every such name would reject legitimate files like aux.json elsewhere). This is one concrete reason WSL is the recommended Windows path.
  • A console that cannot encode the summary no longer fails the run. The human summaries contain —, ·, … and →, which a Windows console in its OEM code page (cp437/cp850) — and any host started under the C/POSIX locale — cannot represent. Those glyphs now print as visible \uXXXX escapes instead of raising, so the command keeps its own exit code rather than reporting a finished run as [E019] or [E099]. --json output is unaffected: it is ensure_ascii JSON, which every one of those code pages represents exactly, and only the stream's error handler is changed, never its encoding.

Minimum Python & support window#

SBOMFlow requires Python 3.11 or newer. requires-python guards pip install, but a direct source or PYTHONPATH run on an older interpreter is also handled gracefully: SBOMFlow checks the interpreter before loading the CLI and, on anything below 3.11, prints one clear line and exits with code 2 —

SBOMFlow requires Python 3.11 or newer (found 3.10.x).

— instead of a cryptic SyntaxError from deep in the import chain. The supported window tracks the compatibility matrix above: the floor rises only when the oldest listed version does, and never silently.

Support policy. Every CPython minor version from 3.11 through the newest stable release has a CI lane, and every one of them has completed green. Only the floor (3.11) runs on every push; the full interpreter fan-out runs on pull requests, the nightly schedule and manual dispatch, as the table above records per row. A new stable minor joins the matrix within one SBOMFlow release of its GA. Pre-release interpreters are never listed. The requires-python floor, the Programming Language :: Python :: classifiers, the CI matrix, and the table above are held in agreement by a drift test, so this is a checked fact rather than a promise.

Man page and shell completions#

A troff man page and static bash/zsh completions ship in the repository, generated from the real CLI parser (so they never drift from the commands --help prints) and drift-guarded by tests. Install them optionally:

bash
# man page
sudo install -m 0644 docs/sbomflow.1 /usr/local/share/man/man1/sbomflow.1
man sbomflow

# bash completion (adjust the path for your distro)
sudo install -m 0644 completions/sbomflow.bash /etc/bash_completion.d/sbomflow

# zsh completion (place on your $fpath, then rebuild the completion cache)
sudo install -m 0644 completions/_sbomflow /usr/local/share/zsh/site-functions/_sbomflow

The shipped man page and completions are generated directly from the CLI parser, so they always match the current commands. SBOMFlow never auto-installs into your shell.

Uninstall, cleanup, and offboarding#

SBOMFlow is easy to remove completely, which matters for a controlled pilot: a partner should be able to prove nothing lingers on their machines afterwards.

What SBOMFlow writes. The engine writes only to the output directory you name (--output), plus a cache directory only if you explicitly pass one (--scan-cache-dir / --osv-cache-dir). sbomflow init writes a single sbomflow.yaml into the project directory you point it at. Nothing is written to your home directory: the optional entitlement file (~/.config/sbomflow/entitlement.json) is read if present and never created by the tool. There is no hidden state, no daemon, no telemetry, and no default network call.

To remove the tool:

bash
pip uninstall sbomflow            # or: pipx uninstall sbomflow, if you used pipx

If you installed into a dedicated virtual environment (recommended), simply delete the environment directory.

To remove all generated evidence, delete the output directories you created:

bash
rm -rf ./sbomflow-output ./evidence   # whatever paths you passed to --output

Because outputs are self-contained and deterministic, deleting those directories removes every SBOM, report, bundle, audit log, and review/approval file SBOMFlow produced. Keep a copy first if you need the evidence for your own records — the evidence bundle is a portable, hash-verifiable archive designed exactly for that.

Planned install channels (after a public release)#

The following are not yet available and will be documented when a public release ships:

  • pipx install sbomflow — the name, resolved against an index. The local-file form, pipx install ./sbomflow-<version>-py3-none-any.whl, needs no index and is documented above.
  • python -m pip install sbomflow (from PyPI)
  • a Homebrew formula (a Python CLI ships as a formula, not a cask)

When a Homebrew tap is published, follow the tap's own trust guidance. Prefer installing the specific formula over trusting an entire third-party tap; do not disable Homebrew's trust checks globally.

Next#

Continue to your first offline audit.