Security & privacy

SBOMFlow is designed so that sensitive build information stays with you.

Your machine — everything runs here

source & build treeSBOMFlow engineoutput directory (plain files)

133 of 143 commands in the registry are fully offline — no gate exists that could take them online.

Named public sources — only behind an exact gate

  • sbomflow analyze--use-osv --use-nvd --use-kev --use-epss --resolve-west-imports config: osv/nvd enabled with scan.offline=false config: exploitation.use_kev/use_epss=true
  • sbomflow audit--use-osv --use-nvd --use-kev --use-epss --resolve-west-imports config: osv/nvd enabled with scan.offline=false config: exploitation.use_kev/use_epss=true
  • sbomflow batch-runeach job's own config: osv/nvd enabled with scan.offline=false each job's own config: exploitation.use_kev/use_epss=true
  • sbomflow notify--apply
  • sbomflow sync-dependencytrack--apply
  • sbomflow sync-issues--apply
  • sbomflow sync-jira--apply
  • sbomflow sync-servicenow--apply
  • sbomflow vulndbsubcommand: update
  • sbomflow vulndb updateexplicit `vulndb update` invocation
Generated from the same network-policy registry as the table below; the counts and gates cannot drift from it.

Offline by default#

Offline

The baseline engine runs entirely offline. A default audit makes zero
network requests. This is verified by tests in the project.

By default, none of the following leaves your environment: source code, firmware, SBOMs, file or directory paths, vulnerability details, reviewer notes, or build artifacts.

Terminal and text-output safety#

Attacker-controlled names, paths, and warning text cross one display-safe boundary before human console, Markdown, or plain-text rendering. C0 controls (except tab), C1 controls, ANSI/OSC introducers, carriage returns, and Unicode bidirectional formatting controls are rendered as visible escape sequences, so they cannot clear a terminal, forge a line, set a title, create a live terminal hyperlink, or visually reverse a filename. Ordinary text remains byte-identical. Structured JSON retains the original value and its normal JSON escaping so machine-readable evidence is not rewritten by the display layer.

That the boundary is reached at all is enforced mechanically, not by review: a repository-wide test walks every shipped module and fails if any of them can write to the console without crossing it — by calling the builtin writer, by naming it through builtins, or by handing it around as a value. The one module that legitimately emits an escape of its own is the interactive sbomflow setup onboarding, whose bold headings keep their style codes while the text inside them — a detected product name, a path you typed, an identifier read out of an evidence pack — still crosses the boundary.

In the HTML report a URL becomes a live link only when its scheme is allow-listed (http://, https://, mailto:) or it carries no scheme at all; anything else is rendered as text with no anchor. That check trims the same leading and trailing bytes a browser's URL parser trims — every codepoint U+0000U+0020, which is wider than the set Python calls whitespace — so a control byte placed in front of a scheme cannot make the filter and the browser disagree about what the URL says.

Model files are read, never loaded#

Every default run inventories the AI model artifacts in your tree (ai-evidence.json, experimental). That inventory adds no network path: model files are read as bytes and hashed on your machine, and nothing about them is sent to a model registry, hub, or inference service.

  • Nothing is loaded, deserialised, imported, or executed. No model runtime is imported. Identification reads a small fixed prefix and matches documented magic numbers (GGUF, TFLite) or documented structural invariants (SafeTensors; ONNX, which defines no file magic at all). Hashing streams in fixed blocks, so a very large artifact does not become a very large memory cost.
  • Formats whose only faithful reader runs code are recognised and refused. Anything pickle-backed, an executable graph, or an archive that can carry one is recorded and hashed — presence is evidence a reviewer should see — and deliberately never opened.
  • Bytes outrank filenames. A recognised-unsafe signature beats every extension, so a pickle wearing a .onnx name is caught by its bytes and the disagreement is raised rather than swallowed. A file identified only by its name says so, so you can see when a claim rests on a filename.
  • The walk is bounded and does not follow symlinks out of the scan root. If an entry-count or directory-depth budget stops or prunes it, the inventory is reported as PARTIAL and names the budget that fired — a truncated inventory is never presented as a complete one.
  • An empty inventory is stated honestly. It means the scan looked and recognised nothing. It is never a claim that your product contains no AI: unrecognised formats, models fetched at runtime, and models behind an external service are all outside what a filesystem walk can see.

What this does not do: it observes nothing about a running model or agent, assesses no model's behaviour, quality or safety, and reaches no verdict. Recording what an agent is declared to be allowed to do does not constrain that agent and does not mitigate runtime prompt injection. See known limitations.

Named, opt-in network actions#

Network-capable actions are explicit, individually named, and documented. The table below is generated from the same registry that powers command help and sbomflow help --json; adding a command without a declaration fails tests.

<!-- BEGIN GENERATED NETWORK POLICY --> | Command | Network class | Exact gate(s) | |---|---|---| | sbomflow analyze | opt-in-network | --use-osv, --use-nvd, --use-kev, --use-epss, --resolve-west-imports, config: osv/nvd enabled with scan.offline=false, config: exploitation.use_kev/use_epss=true | | sbomflow annotate | offline | None | | sbomflow approve | offline | None | | sbomflow audit | opt-in-network | --use-osv, --use-nvd, --use-kev, --use-epss, --resolve-west-imports, config: osv/nvd enabled with scan.offline=false, config: exploitation.use_kev/use_epss=true | | sbomflow auditor-pack | offline | None | | sbomflow backfill | offline | None | | sbomflow batch-run | opt-in-network | each job's own config: osv/nvd enabled with scan.offline=false, each job's own config: exploitation.use_kev/use_epss=true | | sbomflow bundle | offline | None | | sbomflow case | offline | None | | sbomflow case accept-risk | offline | None | | sbomflow case assign | offline | None | | sbomflow case communicate | offline | None | | sbomflow case correlate | offline | None | | sbomflow case disclose | offline | None | | sbomflow case export-advisory | offline | None | | sbomflow case intake | offline | None | | sbomflow case open | offline | None | | sbomflow case propose-propagation | offline | None | | sbomflow case remediate | offline | None | | sbomflow case reopen | offline | None | | sbomflow case show | offline | None | | sbomflow case transition | offline | None | | sbomflow case triage | offline | None | | sbomflow cmake-evidence | offline | None | | sbomflow compare-releases | offline | None | | sbomflow config | offline | None | | sbomflow config show | offline | None | | sbomflow cosign-verify | offline | None | | sbomflow dashboard | offline | None | | sbomflow decisions | offline | None | | sbomflow decisions recover | offline | None | | sbomflow decisions transactions | offline | None | | sbomflow decisions verify-transactions | offline | None | | sbomflow diffoscope-compare | offline | None | | sbomflow doctor | offline | None | | sbomflow elf-evidence | offline | None | | sbomflow emit-user-info | offline | None | | sbomflow esp-idf-evidence | offline | None | | sbomflow examples | offline | None | | sbomflow explain | offline | None | | sbomflow firmware-extract | offline | None | | sbomflow gate | offline | None | | sbomflow gate simulate | offline | None | | sbomflow help | offline | None | | sbomflow index | offline | None | | sbomflow index query | offline | None | | sbomflow index rebuild | offline | None | | sbomflow index validate | offline | None | | sbomflow index-releases | offline | None | | sbomflow ingest-codex-security | offline | None | | sbomflow ingest-grype | offline | None | | sbomflow ingest-sarif | offline | None | | sbomflow ingest-trivy | offline | None | | sbomflow init | offline | None | | sbomflow license | offline | None | | sbomflow license inspect | offline | None | | sbomflow license install | offline | None | | sbomflow license status | offline | None | | sbomflow license verify | offline | None | | sbomflow linker-map-evidence | offline | None | | sbomflow mcuboot-evidence | offline | None | | sbomflow mcuboot-verify | offline | None | | sbomflow mender-evidence | offline | None | | sbomflow migrate-in | offline | None | | sbomflow next-actions | offline | None | | sbomflow notify | apply-gated | --apply | | sbomflow oci-evidence | offline | None | | sbomflow passport | offline | None | | sbomflow passport issue | offline | None | | sbomflow passport verify | offline | None | | sbomflow policy | offline | None | | sbomflow policy diff | offline | None | | sbomflow policy show | offline | None | | sbomflow portfolio | offline | None | | sbomflow prov-map | offline | None | | sbomflow provenance-check | offline | None | | sbomflow quickstart | offline | None | | sbomflow rauc-evidence | offline | None | | sbomflow recheck | offline | None | | sbomflow regime-profile | offline | None | | sbomflow regime-view | offline | None | | sbomflow release-chain | offline | None | | sbomflow release-chain verify | offline | None | | sbomflow report | offline | None | | sbomflow report freshness | offline | None | | sbomflow reproducible-compare | offline | None | | sbomflow review | offline | None | | sbomflow review-merge | offline | None | | sbomflow review-view | offline | None | | sbomflow schema | offline | None | | sbomflow schema list | offline | None | | sbomflow schema show | offline | None | | sbomflow setup | offline | None | | sbomflow sign-bundle | offline | None | | sbomflow standards-view | offline | None | | sbomflow store | offline | None | | sbomflow store add | offline | None | | sbomflow store empty-trash | offline | None | | sbomflow store export | offline | None | | sbomflow store list | offline | None | | sbomflow store query | offline | None | | sbomflow store register-rename | offline | None | | sbomflow store reindex | offline | None | | sbomflow store reissue | offline | None | | sbomflow store reissue-plan | offline | None | | sbomflow store retain | offline | None | | sbomflow store show | offline | None | | sbomflow store timeline | offline | None | | sbomflow store verify | offline | None | | sbomflow store verify-gates | offline | None | | sbomflow support-bundle | offline | None | | sbomflow swupdate-evidence | offline | None | | sbomflow syft-scan | offline | None | | sbomflow sync-dependencytrack | apply-gated | --apply | | sbomflow sync-issues | apply-gated | --apply | | sbomflow sync-jira | apply-gated | --apply | | sbomflow sync-servicenow | apply-gated | --apply | | sbomflow twister-evidence | offline | None | | sbomflow update-manifest-evidence | offline | None | | sbomflow upgrade | offline | None | | sbomflow upgrade inspect | offline | None | | sbomflow upgrade migrate-copy | offline | None | | sbomflow upgrade plan | offline | None | | sbomflow upgrade validate | offline | None | | sbomflow uptane-evidence | offline | None | | sbomflow validate | offline | None | | sbomflow validate-annexii-model | offline | None | | sbomflow validate-article14-model | offline | None | | sbomflow validate-config | offline | None | | sbomflow validate-graph | offline | None | | sbomflow validate-ssvc-model | offline | None | | sbomflow validate-techdoc-model | offline | None | | sbomflow verify-bundle | offline | None | | sbomflow verify-package-artifacts | offline | None | | sbomflow vulndb | opt-in-network | subcommand: update | | sbomflow vulndb inspect | offline | None | | sbomflow vulndb pin | offline | None | | sbomflow vulndb reindex | offline | None | | sbomflow vulndb retention | offline | None | | sbomflow vulndb sources | offline | None | | sbomflow vulndb update | opt-in-network | explicit vulndb update invocation | | sbomflow vulndb verify | offline | None | | sbomflow zephyr-evidence | offline | None | <!-- END GENERATED NETWORK POLICY -->

Network — opt-in

These send identifiers, never your source or firmware. Offline snapshot
files (--nvd-file, --kev-file, --epss-file) keep even enrichment offline.

Air-gapped use#

SBOMFlow works fully air-gapped: run offline, use snapshot files for enrichment, and never enable a network flag. Nothing is required to phone home.

Network-failure behavior (opt-in paths)#

When you do enable a network path (OSV / NVD / CISA KEV / FIRST EPSS), every failure mode is handled the same way: the failure is recorded as an error and the run continues with the findings left intact — SBOMFlow never produces a half-enriched pack that looks complete, and never raises an uncaught error at the operator. Timeouts, connection errors, HTTP 4xx/5xx (including rate-limit 429s), malformed or truncated JSON, and oversized responses are each caught: enrichment that could not run leaves findings unchanged rather than silently asserting "not exploited", and the error is surfaced in the run's provenance. Each request uses a bounded timeout and a response-size cap, and requests are not retried in a loop (a failure is reported, not hammered). This is proven offline by the network-failure conformance test suite.

The same response-size cap applies to the opt-in sync and notify paths (sync-dependencytrack, sync-issues, sync-jira, sync-servicenow, notify --apply): the reply from your tracker or webhook is read up to a bounded size and refused beyond it rather than truncated, so a runaway or hostile endpoint cannot exhaust memory and a partial reply is never recorded as a complete one. The refusal is reported as an error on the run.

Running behind a corporate proxy#

A pilot's CI environment is often behind an egress proxy with a private CA. The opt-in network paths (OSV / NVD / CISA KEV / FIRST EPSS) share one transport built on the Python standard library, which honors the usual environment variables — no SBOMFlow-specific flags exist or are needed:

  • HTTP/HTTPS proxy: set http_proxy / https_proxy (and no_proxy to exclude specific hosts) before running an opt-in command. urllib picks these up automatically.
  • Private/custom CA: set SSL_CERT_FILE (a single PEM bundle) or SSL_CERT_DIR (a hashed certificate directory) so TLS verification trusts your corporate root — this is standard OpenSSL behavior, not a SBOMFlow override. Without it, a certificate signed by an untrusted private CA fails verification and is recorded as an error, per the network-failure behavior above — it never silently proceeds unverified, and SBOMFlow never offers a "skip verification" flag.

A proxy that is misconfigured or unreachable fails the same way as any other network error above: the failure is recorded, the run continues with findings left intact, and nothing crashes. The offline-by-default path never reads these variables at all — a poisoned or unreachable proxy has zero effect unless you explicitly pass an opt-in network flag. This is proven offline against loopback-only proxy and TLS fixtures — no live network is used to verify it.

Generated HTML reports are self-contained and CSP-locked#

Every HTML surface SBOMFlow generates (the assessment report, reviewer console, release drift report, dashboard, and shared/auditor pages) is a single self-contained file — no scripts, no external stylesheets, fonts, or images, and inline CSS only. Each one embeds a strict Content-Security-Policy meta (default-src 'none'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none') so that, when opened from an internal web server, the browser mechanically enforces that claim: nothing loads from the network and no script can run. unsafe-inline applies to the single inline <style> block only; scripts are never permitted.

Accessible, log-safe terminal output#

SBOMFlow's command-line output is plain text and ANSI-free by default. No colour or escape sequences appear on any non-interactive path, so output stays readable when piped into CI logs, redirected to a file, or read through a screen reader or a refreshable Braille display.

  • NO_COLOR is honoured. Setting the NO_COLOR environment variable to any non-empty value disables the only colour SBOMFlow can emit. TERM=dumb and the sbomflow setup --no-color flag have the same effect.
  • Colour is opt-in and terminal-only. The single place SBOMFlow emits ANSI colour is the optional interactive sbomflow setup onboarding, and only when it is attached to a real terminal. Redirected or piped output is never coloured.
  • Progress is terminal-only and escape-free. The live progress line shown during a long scan appears only on an attached terminal, where it redraws a single line with a carriage return — never a cursor- or colour-control escape sequence. Redirected output is the same plain text, byte for byte. Set SBOMFLOW_NO_PROGRESS to any non-empty value to silence it entirely.

This behaviour is locked by a repository-wide test that runs representative commands under NO_COLOR on a pseudo-terminal and asserts that zero ANSI escape bytes are emitted.

Local metrics stay local#

sbomflow analyze --emit-metrics can write a local metrics.json containing aggregate counts, gate summary, phase durations, tool version, and any local release metadata supplied for that run. It records that it contains no source content, credentials, or personal contact details, but you should still inspect release metadata before sharing it. This artifact is yours — SBOMFlow does not upload it and there is no vendor telemetry.

Secrets#

Credentials for opt-in integrations come from environment variables, never from command-line flags (which would leak into shell history) and never written into evidence artifacts.

No regulatory submission#

SBOMFlow never files, transmits, signs, or submits a regulatory report, and never contacts a CSIRT, ENISA, or a reporting platform. CRA Article 14 outputs are always unsigned drafts for a human to complete.

Organisational continuity and identity-bearing records#

SBOMFlow records evidence and human decisions; it does not decide the legal basis, retention period, or disclosure audience for personal data. Establish those rules with your privacy and legal advisers before operating a shared store. The procedures below preserve the distinction between historical integrity and present-day access:

  • Erasure request versus frozen history: do not edit a frozen release, approval ledger, or hash-chained journal in place. Restrict access while the request is assessed. When removal is required under your organisation's policy, record an authorised superseding action and retain only the minimum separately controlled linkage that policy requires. If the original must be deleted, record the custody/retention action outside the deleted artifact; never rewrite history so its hashes appear unchanged.
  • Ownership transfer: inventory the store, snapshots, keys, approval policies, and open cases; record the former and receiving custodian, scope, time, and transfer verification in the organisation's controlled handover system. Rotate access and integration credentials. A transfer changes custody, not the meaning or author of an earlier observation or sign-off.
  • Consultants and manufacturers: identify the human actor and the capacity in which they acted. A consultant's analysis must not be relabelled as a manufacturer approval, and a free-form author value is attribution—not proof of employment, authority, identity, or legal responsibility.
  • Identity scope: reviewer names, email addresses, notes, and reason text are identity-bearing data. Limit collection to the intended workflow and use a share profile for external exports. A no-profile approvals export emits an IDENTITY-BEARING EXPORT warning because it deliberately retains reviewer identities and authored reason text.

These are operational safeguards, not legal advice. A superseding record does not erase the original, and a hash chain proves only integrity of the records it still covers—not that retention or erasure was legally sufficient.