Compare releases and keep history
Compare releases, keep an index, render dashboards. This page documents every command in the history group, in the order sbomflow help history lists them. Each command's section is generated from the command itself — its summary, description and options from the parser, and its reads, writes, exit codes, examples and see-also from the command catalog — so it says exactly what sbomflow <command> --help says. Every example below is executed offline by the test suite, and every non-zero exit code is either produced by an executed probe or carries a recorded reason why no offline probe can produce it.
See the CLI overview for how the thirteen groups fit together and the command reference for every command's options in one place.
Overview#
sbomflow compare-releases·sbomflow index-releases·sbomflow dashboard·sbomflow portfolio.sbomflow compare-releases --require-both-releases— fail when the comparison did not happen, instead of letting an uncompared run read as "no drift". Every surface that shows a drift count now states whether a comparison occurred at all, and a comparison with a missing side no longer reports the absent release as change.
With a family.json beside the portfolio config, portfolio also reports the latest release per channel|variant across a declared family's member products — the question a multi-variant manufacturer asks. Two members can legitimately claim the same channel|variant: the product name is part of a product's identity, so two products differing only by name are distinct products that share one key. When that happens no winner is picked, because releases of different products are not comparable. The key is left out of the merged map, every contender is listed under latest_by_channel_variant_collisions, and a warning names them.
A release that declares release.applies_to_variants in sbomflow.yaml is listed under each declared variant in releases_by_declared_variant, per product in index-releases and merged per family in portfolio, with applicability: declared, and under no variant it did not declare. A family in family.json may list its variants; the family view then lists each of them, with no release where none declares it, and validate-config, analyze and audit refuse a declared id the family does not list (E010).
sbomflow store add <output-dir>— index a release output. A pack that contradicts its own seal is refused. A directory whoserun-completion.jsonno longer agrees with the bytes beside it, or one of whose artifacts is present but will not open, is rejected with the reason named. The unreadable case is refused in its own words: the repair is access, not re-running the analysis, and the two are never merged into one message.
A pack that is merely smaller than the full artifact set is admitted — the library writer and a backfilled declared record both legitimately produce one — but the verdict now travels with it. Every entry records completeness_state (the classifier's own word) and validation (passes, or rejected_by_validate), so a store listing can no longer present an interrupted run exactly as it presents a whole one, which is what it did when nothing was recorded at all.
Re-adding the same pack stays a no-op. Re-adding a different pack that happens to share an identity is refused: content_hash is the digest of evidence-pack.json alone, so a pack that has grown or lost other members matched an indexed entry and was reported Already present. A second, pack-wide digest now decides that question and names the members that differ. An entry indexed before that digest existed carries none, and the result says the comparison did not happen rather than guessing either way.
sbomflow store export --output <dir>— copy the whole estate out offline, plus acompleteness-manifest.jsonthat reconciles the export against the store's own index: every indexed release appears, exported or not, with a reason when not, alongside the whole-store seal and an explicit list of what the export does not cover. The copy mirrors the store layout, so it re-opens with the ordinarystorecommands — no import step, no SBOMFlow-only container. Exits non-zero if the estate is not fully accounted for.--dry-runreconciles and reports without writing copies, and reaches the same completeness verdict and exit code as a real export — so you can check whether an estate would travel intact before committing the disk.
A store this run could not enumerate is refused before anything is written. An export replaces whatever is at its destination, and it is assembled from the store index alone — release directories on disk are never consulted. So an index that is absent, will not parse, or records zero releases stops the export rather than producing an empty copy that declares itself complete: the export you already had is worth more than a fresh one that vouches for nothing. The manifest records store_index_state so a receiver can check that precondition rather than infer it, and complete can only be true when that state is read. Repair the index with sbomflow store reindex (see PB-12), confirm with sbomflow store verify, then export again.
Replacing an export never leaves you with neither version. The new copy is built and re-verified in a .<name>.staging directory beside the destination; only then is the previous export moved aside to .<name>.previous, the new one put in its place, and the moved-aside copy discarded. Nothing is deleted until the replacement is in place, so a publish that fails is a refusal (E051) with the previous export exactly where it was, never a destination holding nothing. Each of those moves is a single atomic rename, so the destination is always one whole export and never a mixture of two — but the pair is not atomic, so a machine that dies mid-swap can leave the destination briefly absent with the previous export sitting at .<name>.previous. Both dot-named directories are SBOMFlow's own working copies: rename .<name>.previous back to the destination to recover the export you had, or simply run the export again, which restores it first and then replaces it. Nothing here calls fsync, so what survives a power cut remains the filesystem's business; what is guaranteed is that no export is destroyed before its replacement is in place.
An export is a share boundary, so it runs a preflight first and REFUSES rather than publishing something sensitive. The preflight reads the store's own members before anything is written, so a refusal exports nothing at all and any previous export at that destination is left exactly as it was. It reports the member path and the violation class — never the value. Inspected classes: secret-shaped filenames, private-key material, credential/token material, absolute machine paths, and the same sharing contract the reviewer bundle uses, applied to JSON and JSONL members.
"Absolute machine path" means exactly that. A repository-relative path that merely passes through a directory called home/ or Users/ — web/home/page.tsx — is ordinary evidence and does not refuse the export. Likewise the sharing contract's hostname class is a machine name, not any word that resembles one: a component called node-red or a file called host_config.json travels. A JSON Pointer SBOMFlow itself writes — the /vulnerabilities in the CSAF sentence of an audit with nothing to state, or the /schemaVersion a strict reader names — is not a machine path either, so such a release exports; a host path beside it is still refused. See the redaction contract for the exact shapes.
SBOMFlow will not filter or redact the offending member for you: dropping a record would break the completeness guarantee the export exists to provide, and rewriting stored bytes would invalidate their recorded hashes. Resolve it at the source (remove the file from the release output and re-run store add), then export again.
What a clean preflight does and does not mean. It means those classes were not observed in the members that could be inspected. Members that could not be inspected — binary or non-UTF-8 content, or a structured member that did not parse — are listed and counted separately in the manifest, because "not inspected" is not "clean". It is not proof that an export is free of every possible secret, and it is never a statement about legal safety.
Note that analyzing with an absolute path records that path in the pack as provenance, and the preflight refuses to publish it at the share boundary. Run analyze from inside your workspace with a relative target if the estate is intended to travel.
sbomflow store reissue-plan— whenstore exportrefuses, diagnose why and plan a safe reissue. Read-only: it writesportable-reissue-plan.jsonand a rendering, never an export, and never modifies the store. It reads the export's OWN preflight result rather than a second detector, so a plan and an export cannot disagree. Each release is reportedportable,requires_reanalysis_inputorblocked; each finding names the member relative to its release and the violation class — never the matched value, path, credential or key material, because a diagnosis must not republish what the refusal exists to contain. Next actions are emitted as copy-pasteable commands only where every value is known and safe to print; otherwise they carry<PLACEHOLDERS>plus the exact inputs you must supply, because SBOMFlow does not retain your source checkout and will not invent one. Exits 0 even when work is required — a diagnosis is not a failure.
If the original source or config is gone, a release whose generated evidence carries machine identity cannot be made shareable by rewriting it: the audit log is hash-chained and every stored digest is asserted by the completeness manifest. That evidence stays fully usable locally.
A store this run could not enumerate is refused, not diagnosed. This is the command you reach for when store export already refused, so it runs precisely when the store is in trouble — including when the index is the thing that is missing. Every count and verdict in the plan is folded over the index entries, so an index nobody could read produced zero releases inspected and reissue_needed: false — "all of them can travel, nothing to do", for a store it never opened. It exits non-zero and names the state instead. See PB-12.
store verify,store retain,store empty-trashandstore reissue-planrefuse an index they could not enumerate. Each folds its verdict over the store index, and an empty fold reads exactly like a healthy one — so a store that had lost itsindex.jsonverified clean, retained nothing, reported no release on hold, and needed no reissue. The read is a precondition now, and the refusal names which state it found:absent,unreadable(bytes denied or an I/O error) ormalformed(bytes read, not a usable index). An index that was read and honestly records zero releases is the fourth state,empty, and every one of these commands behaves over it exactly as it always did. Repair withsbomflow store reindex— see PB-12.
store empty-trash --apply is the only path in the store that permanently deletes, and whether a staged release is under a preservation hold is recorded only in the index. It therefore cannot tell "no release is held" from "no hold could be read", and treats the second as a refusal: an unread input never widens what an irreversible step may delete. store retain --apply likewise writes nothing at all when the index it would rewrite could not be read — it used to create one asserting the store held zero releases.
store queryandstore timelinerefuse the same three states. They only report, so nothing is deleted or overwritten — but "no release matches your filters", "no stored release contains a matching finding" and "not observed in any stored release" are exactly what an operator sees when the index is gone and every pack is still on disk. That last one is decision memory answering the audit-day question, so the reassuring answer is the costly one. They exit non-zero and name the state instead. An index that was read and honestly records zero releases still answers normally.
store list is the exception, and only for absent. It shares its reader with the cross-release context analyze builds (aging, regression, carry-forward), which runs against a store directory you may have created and never added to — a CI job that pre-creates its evidence store has exactly that shape on its first run, and refusing there would fail an analysis with nothing wrong with it. So store list still prints an empty list for a store whose index.json is missing. Use store verify, which refuses, to tell an empty store from a store that lost its index. An index that is present and unusable (unreadable or malformed) is refused by store list.
sbomflow migrate-in <dir>— import another tool's estate (Trivy / Grype / SARIF exports) as origin-labelled claims, composing the importers that already ship. Every row carriesclaim_origin; nothing becomes a reviewer decision, VEX status, approval or gate input, because an incumbent tool's status is a claim about what that tool said, not authority over your product. Files it cannot read or recognise are listed with the reason rather than skipped, so a migration never looks more complete than it was. Every entry the walk reaches resolves to exactly one outcome —imported,unrecognised(its name is not one this command opens, or it was read and its structure matched no shipped importer),unreadable(it was tried and could not be read or listed) orrefused(a symlink to a directory, or one whose target leaves the source directory — nothing behind it was examined) — and the counts sum toentries_reached, so an entry cannot be present without being counted. Anotes.txtbeside the exports is therefore named in the summary as unrecognised rather than passing unmentioned, and a subdirectory the process cannot list becomes anunreadablerow instead of an absence. When more entries were discovered than the walk may examine,discovery_boundedsays so and the manifest describes a prefix of the directory. That holds at row level too: a file can be imported and incomplete, so rows an importer could not read are counted per file, totalled asunreadable_rows_total, and the summary names those files under Imported INCOMPLETELY. A row carrying no vulnerability id, package name or purl is reported as unreadable rather than imported as a finding that names nothing — most often that means another tool's export was detected as a format it is not.
Who this is for: release managers, operators, portfolio owners, programme managers, migrating teams, auditor liaisons and data analysts.
sbomflow compare-releases#
Compare two SBOMFlow output directories and write release-drift.json.
Compare two existing runs of the same product and report what changed:
components, findings, KEV/EPSS, reachability, gaps, reviews to recheck,
support period, artifact hashes, and gate policy.
Preserved artifacts (traces, dumps, test results declared under
preserved_artifacts:) are compared by digest and written to
preserved-artifact-changes.json, beside release-drift.json, never in it.
Drift is comparison context only - it never replaces human review or
closes tracker issues.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | two analyze or audit output directories of the same product. |
| Writes | release-drift.json into --output; with --html also release-drift.html; preserved-artifact-changes.json into --output, beside release-drift.json; the release notes with --release-notes |
--output names | a directory |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok, including a comparison where one side was unavailable · 2 usage or input error, or with --require-both-releases a missing side |
| Machine output | artifact-suffices — writes release-drift.json |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
--report-theme | {light,dark} | light | light, dark | no | Palette for generated HTML surfaces (default: light). Print output is always light. |
--previous | PREVIOUS | none | any | yes | Previous output directory. |
--current | CURRENT | none | any | yes | Current output directory. |
--output | OUTPUT | none | any | yes | Where release-drift.json is written (directory). |
--require-both-releases | (flag) | false | — | no | Automation contract: exit 2 when either release directory is unavailable, instead of writing a drift in which nothing was compared. release-drift.json is still written first, so the reason survives the refusal. Without this flag exit codes are unchanged. |
--html | (flag) | false | — | no | Also write release-drift.html, a human-readable release-diff report. |
--release-notes | FILE | none | any | no | Also write template-locked engineering security release notes (markdown): counts plus the list of fixed advisory ids. No free prose, no severities, and no still-open advisory ids. |
--release-notes-format | FORMAT | markdown | markdown, cyclonedx, both | no | Which rendering(s) of the release note to write. 'markdown' (the default) is --release-notes FILE alone. 'cyclonedx' writes only engineering-release-notes.cdx.json - a standalone CycloneDX 1.7 document - into --output. 'both' writes both, and then --release-notes names the markdown destination. Both renderings project one normalized view, so they carry the same facts; the CycloneDX document adds no claim of its own. |
--as-of | AS_OF | none | any | no | Pinned comparison timestamp. |
Examples
Report what changed between two releases, with shareable notes.
sbomflow compare-releases --previous out-prev --current out --output drift --release-notes drift/RELEASE-NOTES.mdAlso write the human-readable drift report as HTML.
sbomflow compare-releases --previous out-prev --current out --output drift --htmlRefuse, after writing the drift, when a release directory is missing. Exits 2.
sbomflow compare-releases --previous missing-dir --current out --output drift --require-both-releasesSee also: sbomflow analyze, sbomflow store timeline, sbomflow release-chain verify, sbomflow help goal history.
sbomflow store#
Local, file-based evidence store: accumulate releases and query across them.
A local, file-based evidence store: accumulate finished release outputs
and query, verify, export and retain them across products and releases.
Deterministic and offline, with no database. --store is set here, before
the verb. Name a verb, or the group prints its usage and exits 2.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | nothing itself; its verbs read and write one local evidence store. |
| Writes | nothing itself; each verb's help names what it writes |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 a verb ran and returned 0 · 2 no verb given, or the verb refused |
| Machine output | human-only-by-design — group dispatcher: prints usage/guidance only; the machine surfaces are its nested verbs |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
--store | STORE | .sbomflow-store | any | no | Store directory (default: .sbomflow-store). |
Examples
Record a finished release in the local evidence store.
sbomflow store --store .sbomflow-store add outTell the cross-release story of one advisory.
sbomflow store --store .sbomflow-store timeline --finding CVE-SAMPLE-OPENSSL-001List the stored releases.
sbomflow store --store .sbomflow-store listSee also: sbomflow store add, sbomflow store list, sbomflow store verify, sbomflow help goal history.
sbomflow store add#
Add a release output directory to the store.
Add one finished analyze or audit output directory to the store: its release
record is indexed in index.json, and the pack is copied in unless --no-copy
indexes it by reference. The source directory is never modified.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | one analyze or audit output directory. |
| Writes | index.json (the store index, with a copy of the release unless --no-copy) |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok · 2 missing directory or refused store |
| Machine output | artifact-suffices — records the release into the store (JSON records + index); store list / store show with --json are the read side |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
EVIDENCE_DIR | EVIDENCE_DIR | none | any | yes | Directory produced by 'analyze'/'audit'. |
--no-copy | (flag) | false | — | no | Index the release by reference instead of copying it into the store. |
--as-of | AS_OF | none | any | no | Pinned timestamp for the store index. |
--break-stale-lock | (flag) | false | — | no | Remove a provably-stale .sbomflow.lock (crashed run) from the store root before mutating. Never automatic; confirm no other run is active first. |
Examples
Record a finished release in the local evidence store.
sbomflow store --store .sbomflow-store add outStart a new store with the earlier release.
sbomflow store --store fresh-store add out-prevIndex a release by reference instead of copying it in.
sbomflow store --store fresh-store add out --no-copySee also: sbomflow store list, sbomflow store verify, sbomflow store timeline.
sbomflow store empty-trash#
Report (default) or permanently delete the retention .trash/ staging area.
Report what the retention staging area .trash/ holds, which `store retain
--apply` fills, or with --apply delete it permanently. The report is the
default and writes nothing; the index records of trashed releases are kept
either way.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | the retention staging area, .trash/, of one store. |
| Writes | with --apply deletes .trash/ permanently; nothing otherwise; stdout (what is staged; --json prints JSON) |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok · 2 an unreadable store index |
| Machine output | has-json — read/status surface; one versioned JSON document on stdout |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
--apply | (flag) | false | — | no | Permanently delete .trash/ (default reports only). |
--json | (flag) | false | — | no | Emit JSON. |
--break-stale-lock | (flag) | false | — | no | Remove a provably-stale .sbomflow.lock (crashed run) from the store root before mutating. Never automatic; confirm no other run is active first. |
Examples
Report what the retention staging area holds.
sbomflow store --store .sbomflow-store empty-trashThe same report as JSON.
sbomflow store --store .sbomflow-store empty-trash --jsonPermanently delete the staging area.
sbomflow store --store .sbomflow-store empty-trash --applySee also: sbomflow store retain, sbomflow store verify.
sbomflow store exploitation-delta#
List newly KEV-listed identifiers and the stored releases whose frozen evidence records them.
Compare two KEV snapshots the local vulndb store named by --vulndb-dir holds
(no store is guessed from the working directory) and join every
identifier the --to catalogue adds to every stored release whose frozen
evidence records it as a finding id, an alias or a CPE candidate. Each row
carries the release's support state at the --to catalogue's dateReleased;
past-support releases are listed, never filtered. A release that could not
be read is named with its reason, never counted as zero exposure.
Offline and read-only apart from --output. Not a reporting determination:
it records no awareness time and opens no case.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | experimental |
| Regime | any |
| Reads | two KEV snapshots in the local vulndb store --vulndb-dir names (none is guessed) and every stored release's frozen evidence pack and recorded CPE candidates. |
| Writes | exploitation-delta.json (the file --output names, never inside the store); stdout (the snapshot pair, added and removed counts, examined and not-examined releases and exposures by support state) |
--output names | one file |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 the delta was written · 2 no --vulndb-dir (no store is guessed), a missing or unreadable vulndb store or store index, a snapshot id the vulndb store does not hold or that is not a KEV snapshot, bytes that no longer hash to the recorded digest, --from newer than --to or an order no catalogue states, the same id twice, or an --output inside the store; nothing is written |
| Machine output | artifact-suffices — writes the delta document to --output; that file is the machine surface |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
--vulndb-dir | DIR | none | any | no | Local vulndb store holding the two KEV snapshots (the directory vulndb update --dir wrote). Offline: its index and the two archives named by id are read, their bytes re-hashed against the recorded digests, and nothing is fetched. Required in effect: no default is assumed, because a store guessed from the working directory would make the delta depend on where the command was run; without it the command refuses. |
--from | KEV_SNAPSHOT_ID | none | any | yes | The older KEV snapshot id (as vulndb inspect lists it). |
--to | KEV_SNAPSHOT_ID | none | any | yes | The newer KEV snapshot id; its dateReleased must not precede --from's. |
--output | FILE | none | any | yes | Where the delta document is written (file). Never inside the store. |
Examples
Join the identifiers a newer KEV snapshot adds to every stored release, with each release's support state.
sbomflow store --store .sbomflow-store exploitation-delta --vulndb-dir kev-vulndb --from d9f358890df059b2 --to d2db4c619089a909 --output kev-delta.jsonRefuse two snapshots named the wrong way round; the order comes from each catalogue's dateReleased, and nothing is written. Exits 2.
sbomflow store --store .sbomflow-store exploitation-delta --vulndb-dir kev-vulndb --from d2db4c619089a909 --to d9f358890df059b2 --output kev-delta.jsonSee also: sbomflow case correlate, sbomflow recheck, sbomflow store query, sbomflow vulndb inspect.
sbomflow store export#
Export the whole estate + a completeness manifest (offline, portable).
Export every stored release as a portable directory with a completeness
manifest that reconciles against the store index. --dry-run reports what would
travel and writes no copies. A release that cannot travel is listed with the
reason; `store reissue-plan` diagnoses it.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | one store and every release its index records. |
| Writes | the portable export and its completeness manifest, into --output; stdout (the manifest; --json prints JSON) |
--output names | a directory |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok · 1 an incomplete estate: an indexed release has no stored copy to export (each one is named in the manifest) · 2 a missing store or a refused export · 4 a copied release does not verify, exactly as store verify would report it (each failing member is named) |
| Machine output | has-json — one versioned completeness manifest, also written to the export directory; reconciles against the store index. store_index_state records how much of that index was read, and complete can only be true when it is read |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
--output | OUTPUT | none | any | yes | Where the portable export is written (directory). |
--dry-run | (flag) | false | — | no | Reconcile and report what would travel without writing copies. |
--json | (flag) | false | — | no | Emit the manifest as JSON. |
--as-of | AS_OF | none | any | no | Pinned timestamp recorded in the manifest. |
Examples
Export the whole estate with a completeness manifest.
sbomflow store --store .sbomflow-store export --output exportReport what would travel, as JSON, without writing copies.
sbomflow store --store .sbomflow-store export --output export --dry-run --jsonPin the manifest's timestamp.
sbomflow store --store .sbomflow-store export --output export --as-of 2026-07-01T00:00:00ZSee also: sbomflow store reissue-plan, sbomflow store verify, sbomflow passport issue.
sbomflow store hold#
Place a preservation hold: retention and empty-trash skip the release.
Place a preservation hold on one stored release. While it is held,
`store retain` keeps the release whatever its policy says, and
`store empty-trash --apply` refuses while the release's copy is staged
in .trash/. The hold records who placed it, when, and the reason exactly
as stated, in index.json and in the append-only store-hold-log.jsonl.
It has no expiry: it stays until `store release-hold` lifts it. It does
not change the release, its content hash or the store seal, and
SBOMFlow does not assess the stated reason.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | one store's index and the named release. |
| Writes | index.json (the hold on the release's index record, and one line in store-hold-log.jsonl); stdout (what was placed; --json prints the release row) |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok · 2 no release by that id, a release already held, or a missing actor or reason |
| Machine output | has-json — read/status surface; one versioned JSON document on stdout |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
RELEASE | RELEASE | none | any | yes | Release id or content-hash prefix naming one stored release (as store show takes). |
--actor | ACTOR | none | any | yes | Named person placing the hold; recorded with it. |
--reason | REASON | none | any | yes | Why the hold is placed, recorded exactly as stated. |
--as-of | AS_OF | none | any | no | Pinned timestamp recorded as when the hold was placed (default: now, UTC). |
--json | (flag) | false | — | no | Emit the release row, with its hold, as JSON. |
Examples
Place a preservation hold on one stored release, naming who placed it and why.
sbomflow store --store .sbomflow-store hold example-gateway-2.4.1 --actor records-team --reason 'Kept for the 2.4.1 field-return review.'The same, at a pinned time, printing the release row with its hold as JSON.
sbomflow store --store .sbomflow-store hold example-gateway-2.4.1 --actor records-team --reason 'Kept for the 2.4.1 field-return review.' --as-of 2026-07-01T00:00:00Z --jsonRefuse a second hold on a release that is already held, naming the first. Exits 2.
sbomflow store --store .sbomflow-store hold example-gateway-2.4.1 --actor records-team --reason 'A second hold.'See also: sbomflow store release-hold, sbomflow store retain, sbomflow store empty-trash.
sbomflow store list#
List stored releases.
List the releases the store's index records, one row each: product, release,
content hash and gate outcome. An empty or absent store lists nothing. Read-
only.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | one store's index. |
| Writes | stdout (one row per stored release; --json prints JSON) |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok, including an empty or absent store · 2 an unreadable store index |
| Machine output | has-json — read/status surface; one versioned JSON document on stdout |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
--product | PRODUCT | none | any | no | Filter by product id or name substring. |
--json | (flag) | false | — | no | Emit JSON. |
Examples
List the stored releases.
sbomflow store --store .sbomflow-store listThe same list as JSON.
sbomflow store --store .sbomflow-store list --jsonOnly one product's releases.
sbomflow store --store .sbomflow-store list --product GatewaySee also: sbomflow store show, sbomflow store query, sbomflow store add.
sbomflow store query#
Query stored releases by attributes.
Query stored releases by attributes (product, channel, gate outcome, KEV
findings, gap count), or switch to cross-release finding rows with --finding-
key, --component or --cve: every release where it appeared, with the observed
version, the decision and the gate outcome. Read-only.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | one store's index and stored packs. |
| Writes | stdout (the matching releases or rows; --json prints JSON) |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok, including no match · 2 usage error |
| Machine output | has-json — read/status surface; one versioned JSON document on stdout |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
--product | PRODUCT | none | any | no | Filter by product id or name substring. |
--channel | CHANNEL | none | any | no | Filter by release channel. |
--gate | {pass,fail,enforced,informational} | none | pass, fail, enforced, informational | no | Filter by gate outcome. |
--has-kev | (flag) | false | — | no | Only releases with KEV findings. |
--min-gaps | MIN_GAPS | none | any | no | Only releases with >= N evidence gaps. |
--finding-key | FINDING_KEY | none | any | no | Cross-release: every release where this finding key appeared. |
--component | NAME[@VERSION] | none | any | no | Cross-release: every release where this component's findings appeared. |
--cve | CVE | none | any | no | Cross-release: every release where this CVE (by id or alias) appeared. |
--limit | LIMIT | none | any | no | Cross-release: cap the number of rows returned. |
--offset | OFFSET | 0 | any | no | Cross-release: skip the first N rows (deterministic pagination). |
--json | (flag) | false | — | no | Emit JSON. |
Examples
Every release in which an advisory appeared.
sbomflow store --store .sbomflow-store query --cve CVE-SAMPLE-OPENSSL-001Releases whose gate was informational, as JSON.
sbomflow store --store .sbomflow-store query --gate informational --jsonOnly releases with known-exploited findings.
sbomflow store --store .sbomflow-store query --has-kevSee also: sbomflow store timeline, sbomflow store list, sbomflow index query.
sbomflow store register-rename#
Record an operator-declared product rename so its release history stays connected.
Declare that a product was renamed, so its releases before and after the
rename resolve to one identity (drift/history/queries span the rename).
Nothing is ever merged automatically: renames are operator-declared,
never fuzzy-matched, and each is an audited event in registry.json.
Both names must already have releases in the store; their real stored
identities are connected (never recomputed).| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | one store's index; both names must already have releases in it. |
| Writes | registry.json in the store root, with an audited rename event; stdout (the rename record; --json prints JSON) |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok · 2 a conflicting registration or a name with no releases |
| Machine output | has-json — read/status surface; one versioned JSON document on stdout |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
--from | FROM_NAME | none | any | yes | Previous product name. |
--to | TO_NAME | none | any | yes | Current (canonical) product name. |
--note | NOTE | none | any | no | Optional note recorded with the rename. |
--as-of | AS_OF | none | any | no | Pinned timestamp for the rename event. |
--json | (flag) | false | — | no | Emit the rename record as JSON. |
Examples
Declare that a product was renamed, so its history stays connected.
sbomflow store --store .sbomflow-store register-rename --from 'Example Embedded Linux Gateway' --to 'Example Gateway' --note 'Renamed at 3.0.0.'The same rename, as JSON, at a pinned time.
sbomflow store --store .sbomflow-store register-rename --from 'Example Embedded Linux Gateway' --to 'Example Gateway' --json --as-of 2026-07-01T00:00:00ZRefuse a name that has no release in the store. Exits 2.
sbomflow store --store .sbomflow-store register-rename --from 'No Such Product' --to 'Example Gateway'See also: sbomflow store list, sbomflow store query, sbomflow store timeline.
sbomflow store reindex#
Rebuild index.json from the canonical release packs the store owns.
Rebuild the store's index.json from the canonical release packs it
owns. Readable retention tombstones are preserved, every unreadable pack
is reported, and a prior index that will not parse is refused rather
than rebuilt over.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | the canonical release packs a store owns, and its current index. |
| Writes | index.json (rebuilt, keeping readable retention tombstones); stdout (what was rebuilt and skipped; --json prints JSON) |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok · 2 an unreadable prior index |
| Machine output | has-json — rebuilds the deterministic index from canonical store-owned packs and reports every skipped input; prior_index_state says how much of the replaced index was read, and a prior index that will not parse is refused rather than rebuilt over |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
--as-of | AS_OF | none | any | no | Pinned timestamp for deterministic output. |
--json | (flag) | false | — | no | Emit JSON. |
--break-stale-lock | (flag) | false | — | no | Remove a provably-stale .sbomflow.lock from the store root before mutating. Never automatic. |
Examples
Rebuild the index from the packs the store owns.
sbomflow store --store .sbomflow-store reindexThe same rebuild as JSON, at a pinned time.
sbomflow store --store .sbomflow-store reindex --json --as-of 2026-07-01T00:00:00ZRemove a provably stale lock left by a crashed run first.
sbomflow store --store .sbomflow-store reindex --break-stale-lockSee also: sbomflow store verify, sbomflow store add.
sbomflow store reissue#
Create a NEW portable release from a fresh analysis (append-only).
Create a NEW portable release from a fresh analysis of the original
source, following a still-current reissue plan. Append-only: the
original is never edited, deleted or superseded, and no decision is
carried across, because approvals, waivers and VEX belong to the
evidence they were made against.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | a current reissue plan, and the original source checkout for a fresh analysis. |
| Writes | evidence-pack.json (a NEW release pack, into an empty --output); portable-reissue-receipt.json beside it; the original is unchanged |
--output names | a directory |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok · 2 a stale or missing plan, or an unusable source or output |
| Machine output | artifact-suffices — writes a new release plus portable-reissue-receipt.json; human summary on stdout |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
release_id | release_id | none | any | yes | The release that could not be exported. |
--plan | PLAN | none | any | yes | A still-current portable-reissue-plan.json. A stale plan is refused. |
--source | SOURCE | none | any | yes | The original source checkout. SBOMFlow does not retain one. |
--target | TARGET | . | any | no | Target RELATIVE to --source (default: '.'). An absolute target is refused. |
--output | OUTPUT | none | any | yes | Where the new evidence pack is written (directory). Must be empty and outside --source. |
--config | CONFIG | none | any | no | Config to use for the fresh analysis. |
--as-of | AS_OF | none | any | no | Pinned timestamp recorded in the receipt. |
Examples
Reissue a release as a new portable pack from a fresh analysis.
sbomflow store --store .sbomflow-store reissue example-gateway-2.4.1 --plan plan.json --source . --output reissuedPin the receipt's timestamp.
sbomflow store --store .sbomflow-store reissue example-gateway-2.4.1 --plan plan.json --source . --output reissued --as-of 2026-07-01T00:00:00ZRefuse a plan that is not there. Exits 2.
sbomflow store --store .sbomflow-store reissue example-gateway-2.4.1 --plan missing-plan.json --source . --output reissuedSee also: sbomflow store reissue-plan, sbomflow store export.
sbomflow store reissue-plan#
Diagnose why an export was refused and plan a safe reissue (read-only).
Diagnose why a stored release could not be exported and write a
portable-reissue-plan.json saying how a fresh analysis could reissue it.
Read-only: it writes no export and never modifies the store.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | one store's index and stored packs. |
| Writes | portable-reissue-plan.json (at --output, or beside the store); stdout (the diagnosis; --json prints JSON) |
--output names | one file |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok · 2 an unknown release or a missing store |
| Machine output | has-json — read-only diagnosis of a refused export; one versioned JSON document on stdout |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
--output | OUTPUT | none | any | no | Write portable-reissue-plan.json here (file). Default: alongside the store. |
--release | RELEASE | none | any | no | Limit the diagnosis to this release id (repeatable). |
--json | (flag) | false | — | no | Emit the plan as JSON; an --output named beside it is written too, with the same document. |
--as-of | AS_OF | none | any | no | Pinned timestamp recorded in the plan. |
Examples
Diagnose every stored release and plan any reissue.
sbomflow store --store .sbomflow-store reissue-planWrite the plan to a named file, as JSON.
sbomflow store --store .sbomflow-store reissue-plan --output plan.json --jsonOnly one release.
sbomflow store --store .sbomflow-store reissue-plan --release example-gateway-2.4.1See also: sbomflow store reissue, sbomflow store export.
sbomflow store release-hold#
Lift a preservation hold; retention applies to the release again.
Lift the preservation hold on one stored release. The hold is not
deleted: its record stays in index.json flagged as lifted, with who
lifted it, when and the reason exactly as stated, and the event is
appended to store-hold-log.jsonl. From then on `store retain` and
`store empty-trash` treat the release like any other. A release that
is not held is refused.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | one store's index and the named release. |
| Writes | index.json (the lift on the release's index record (the hold is kept, flagged lifted), and one line in store-hold-log.jsonl); stdout (what was lifted; --json prints the release row) |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok · 2 no release by that id, a release not held, or a missing actor or reason |
| Machine output | has-json — read/status surface; one versioned JSON document on stdout |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
RELEASE | RELEASE | none | any | yes | Release id or content-hash prefix naming one stored release (as store show takes). |
--actor | ACTOR | none | any | yes | Named person lifting the hold; recorded with it. |
--reason | REASON | none | any | yes | Why the hold is lifted, recorded exactly as stated. |
--as-of | AS_OF | none | any | no | Pinned timestamp recorded as when the hold was lifted (default: now, UTC). |
--json | (flag) | false | — | no | Emit the release row, with its hold, as JSON. |
Examples
Lift the hold, naming who lifted it and why; the hold's record is kept.
sbomflow store --store .sbomflow-store release-hold example-gateway-2.4.1 --actor records-team --reason 'Field-return review closed.'The same, at a pinned time, printing the release row with its lifted hold as JSON.
sbomflow store --store .sbomflow-store release-hold example-gateway-2.4.1 --actor records-team --reason 'Field-return review closed.' --as-of 2026-07-01T00:00:00Z --jsonRefuse to lift a hold on a release that is not held. Exits 2.
sbomflow store --store .sbomflow-store release-hold example-gateway-2.4.1 --actor records-team --reason 'Field-return review closed.'See also: sbomflow store hold, sbomflow store retain, sbomflow store empty-trash.
sbomflow store retain#
Plan (default) or apply a retention policy that trashes old stored copies.
Apply a retention policy to the local store with a two-step safety pattern.
Default writes a deterministic store-retention-plan.json (kept vs.
trash, with reasons) and changes NOTHING.
--apply moves each trash victim's stored copy to .trash/<stamp>/ (never
a hard delete), keeps its index record, then re-verifies
integrity and rolls back on any failure.
Policy keys (retain.yaml or .json): keep_last (N newest per product),
keep_channels (list), min_age_days (protect newer releases). Every rule
only protects; only keep_last makes a release a trash candidate. An
UNRECOGNISED key is refused, never ignored: since every other rule only
protects, a misspelled one would drop the protection while keep_last
still trashed. Permanently delete the staging area with
`store empty-trash --apply`.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | a retention policy file and one store's index. |
| Writes | store-retention-plan.json; with --apply trash moves into .trash/; stdout (the plan; --json prints JSON) |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok · 2 a missing or unrecognised policy |
| Machine output | has-json — read/status surface; one versioned JSON document on stdout |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
--policy | POLICY | none | any | yes | Retention policy file (YAML/JSON). |
--apply | (flag) | false | — | no | Act on the plan (default is a dry-run plan only). |
--as-of | AS_OF | none | any | no | Pinned timestamp for the plan / trash stamp. |
--json | (flag) | false | — | no | Emit the plan as JSON. |
--break-stale-lock | (flag) | false | — | no | Remove a provably-stale .sbomflow.lock (crashed run) from the store root before mutating. Never automatic; confirm no other run is active first. |
Examples
Plan which stored copies the policy would trash, changing nothing.
sbomflow store --store .sbomflow-store retain --policy retain.yamlThe plan as JSON, at a pinned time.
sbomflow store --store .sbomflow-store retain --policy retain.yaml --json --as-of 2026-07-01T00:00:00ZApply it: older copies move to .trash/ and their index records stay.
sbomflow store --store .sbomflow-store retain --policy retain.yaml --applySee also: sbomflow store empty-trash, sbomflow store verify, sbomflow vulndb retention.
sbomflow store show#
Show one stored release by id or content hash.
Show one stored release's record by release id or content-hash prefix: product
identity, gate outcome, counts and where the pack is kept. Read-only.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | one store's index and the named release. |
| Writes | stdout (the release's stored record; --json prints JSON) |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok · 2 no release by that id or hash |
| Machine output | has-json — read/status surface; one versioned JSON document on stdout |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
key | key | none | any | yes | Release id or content-hash prefix. |
--json | (flag) | false | — | no | Emit JSON. |
Examples
Show one stored release by id.
sbomflow store --store .sbomflow-store show example-gateway-2.4.1The same record as JSON.
sbomflow store --store .sbomflow-store show example-gateway-2.4.1 --jsonRefuse an id the store does not hold. Exits 2.
sbomflow store --store .sbomflow-store show no-such-releaseSee also: sbomflow store list, sbomflow store verify, sbomflow store timeline.
sbomflow store timeline#
Tell the cross-release story of one finding (read-only, offline).
Compose the ordered story of a single finding across every stored release:
when it was first observed, how its severity/version/decision changed, which
releases it blocked, whether a waiver covered it, and when it stopped being
observed.
Absence is NOT a fix: a finding that stops appearing may have had its
component removed, renamed, or simply not scanned. Releases stored by
reference hold no canonical pack and are reported as explicit gaps, never
interpolated.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | every stored release's pack. |
| Writes | stdout (the finding's story, release by release; --json prints JSON) |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok, including a finding never observed · 2 a missing store |
| Machine output | has-json — read/status surface; one versioned JSON document on stdout |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
--finding | CVE|FINDING-KEY | none | any | yes | Advisory id (CVE-…, GHSA-…, OSV-…) or an opaque finding key. |
--product | PRODUCT | none | any | no | Scope to one product id or name substring. |
--as-of | AS_OF | none | any | no | Pin the report timestamp (ISO-8601 UTC) for reproducible output. |
--json | (flag) | false | — | no | Emit JSON. |
Examples
Tell the cross-release story of one advisory.
sbomflow store --store .sbomflow-store timeline --finding CVE-SAMPLE-OPENSSL-001The same story as JSON, at a pinned time.
sbomflow store --store .sbomflow-store timeline --finding CVE-SAMPLE-OPENSSL-001 --json --as-of 2026-07-01T00:00:00ZScope it to one product.
sbomflow store --store .sbomflow-store timeline --finding CVE-SAMPLE-BUSYBOX-001 --product GatewaySee also: sbomflow store query, sbomflow compare-releases, sbomflow recheck.
sbomflow store verify#
Verify store integrity: recompute every stored release's content hash.
Recompute each stored release's content hash, and list orphans and
missing releases, with a whole-store seal to compare after a transfer.
A tracked release that is missing or tampered exits 4. Read-only.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | one store's index and every stored release. |
| Writes | stdout (one line per release and the store seal; --json prints JSON) |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok · 2 a missing store or a malformed --expected-seal · 4 a tracked release is missing or tampered |
| Machine output | has-json — read/status surface; one versioned JSON document on stdout |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
--json | (flag) | false | — | no | Emit JSON. |
--strict | (flag) | false | — | no | Machine-transfer/DR check: a by-reference release whose external directory cannot be read here FAILS instead of reporting 'by_reference' (exit 4). Use after moving or restoring a store. |
--reference-root | REFERENCE_ROOT | none | any | no | Where by-reference release directories live on THIS machine, for a store that moved. Changes only where --strict looks; no recorded path is rewritten. |
--expected-seal | EXPECTED_SEAL | none | any | no | A whole-store seal recorded earlier (the 'seal' block of a previous --json run). A release lost in transfer fails here even though every surviving release still hashes correctly. |
Examples
Recompute every stored release's content hash.
sbomflow store --store .sbomflow-store verifyThe same check as JSON, with the whole-store seal.
sbomflow store --store .sbomflow-store verify --jsonFail a by-reference release this machine cannot read. Exits 4.
sbomflow store --store by-reference verify --strict --reference-root nowhereSee also: sbomflow store verify-gates, sbomflow store verify-replay, sbomflow store reindex.
sbomflow store verify-gates#
Replay each stored release's gate and check its recorded outcome reproduces.
Answer the auditor's question: did release N's recorded gate outcome come
from the policy we think it did, over the pack we have on file?
For each release this checks two things:
provenance the policy's hash matches the recorded one; and
integrity re-running that policy over the stored pack reproduces the
recorded outcome.
--policy is optional: a release records the full policy body it enforced, so
it replays from its own record. Supply --policy for a release written before
that body was recorded, or to check a release against a policy by hand.
Read-only. Exits 4 if any release with a matching policy fails to
reproduce its recorded outcome (a tamper/inconsistency signal).| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | every stored release's pack and the policy each recorded. |
| Writes | stdout (one provenance and integrity verdict per release; --json prints JSON) |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok · 2 an unknown policy or unreadable input · 4 a release's recorded gate outcome did not reproduce |
| Machine output | has-json — read/status surface; one versioned JSON document on stdout |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
--policy | POLICY | none | any | no | The gate policy whose recorded outcomes you are verifying: a shipped preset name or a policy file path (.json/.yaml), resolved exactly as sbomflow gate simulate and sbomflow policy show resolve it. Optional: when omitted, each release resolves its own policy from what it recorded — a packaged --policy-profile binding, else the full policy body it stored. A release that recorded neither, or whose stored body does not reproduce its recorded hash, is reported unverifiable, never guessed. |
--vulndb-dir | DIR | none | any | no | Local vulndb store to resolve each release's recorded source set against, for the replayability column only. Reads that store's index; no network on any path. Without it, a release that recorded a set reports unknown — source_store_not_examined, which states what this run looked at rather than anything about the release: replayable and the byte-level findings behind it (source_bytes_unavailable, source_bytes_unverifiable_by_content, source_store_unreadable) are only ever produced when this is supplied. No default is assumed — a store guessed from the working directory would make the column depend on where the command was run. Changes no gate verdict, no summary counter and no exit code. |
--product | PRODUCT | none | any | no | Scope to one product id or name substring. |
--as-of | AS_OF | none | any | no | Pin the report timestamp (ISO-8601 UTC) for reproducible output. |
--json | (flag) | false | — | no | Emit JSON. |
Examples
Replay every stored gate from the policy each release recorded.
sbomflow store --store .sbomflow-store verify-gatesCheck them against a named policy instead.
sbomflow store --store .sbomflow-store verify-gates --policy cra-defaultThe verdicts as JSON.
sbomflow store --store .sbomflow-store verify-gates --jsonSee also: sbomflow store verify, sbomflow gate simulate, sbomflow policy show.
sbomflow store verify-replay#
Re-derive a stored release's vulnerability result from the exact bytes it recorded.
Answer a different question from 'recheck-estate': not 'what would we
find today', but 'does release N's recorded result still follow from the
inputs release N actually read'.
Every input is resolved by the digest the RELEASE recorded. Nothing is
substituted from what is active now, nothing is fetched, and an input
that cannot be found is a terminal result of its own — never a
reproduction. Read-only: the stored release is left byte-identical and
the report is written to an isolated output directory.
A clean reproduction proves only that the deterministic computation can
be repeated from those bytes. It does not prove the source was complete,
the product is safe now, the release was approved, or any legal
conclusion.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | every stored release's recorded inputs and, with --vulndb-dir, the retained advisory snapshots. |
| Writes | vulnerability-replay-verification.json (with --output, into an isolated directory); stdout (one reproduction verdict per release; --json prints JSON) |
--output names | a directory |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok · 2 an unknown release or unusable input · 4 a stored release's result did not reproduce from its recorded bytes |
| Machine output | has-json — replays a stored release's recorded vulnerability inputs and reports whether each resolved to its exact recorded bytes; a recorded input it cannot find is reported as unavailable, never reproduced from whatever is current |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
[release] | [release] | none | any | no | One release id or content hash. Omitted, every release in the store (optionally narrowed by --product) is replayed. |
--vulndb-dir | DIR | none | any | no | Local vulndb store holding the retained snapshots. Offline: the index and archives are read, nothing is fetched. Without it every release reports source_bytes_unavailable — which is the honest answer, since no store was examined; a store guessed from the working directory would make the verdict depend on where the command was run. |
--product | PRODUCT | none | any | no | Scope to one product id or name substring. |
--output | DIR | none | any | no | Where vulnerability-replay-verification.json is written (directory); it must be ISOLATED. Never inside the store: a report deposited in the stored release would change the bytes a later replay hashes. |
--as-of | AS_OF | none | any | no | Pin the report timestamp (ISO-8601 UTC) for reproducible output. |
--json | (flag) | false | — | no | Emit JSON. |
Examples
Replay every stored release from the exact bytes it recorded.
sbomflow store --store .sbomflow-store verify-replayWrite the verification report to an isolated directory.
sbomflow store --store .sbomflow-store verify-replay --output replayOne release, as JSON.
sbomflow store --store .sbomflow-store verify-replay example-gateway-2.4.1 --jsonSee also: sbomflow store verify-gates, sbomflow vulndb verify, sbomflow recheck.
sbomflow index#
Build/validate/query the derived SQLite evidence index (JSON stays canonical).
A derived SQLite index over one output directory or a whole evidence store,
for fast read-only queries. The JSON artifacts stay canonical: the index is
rebuilt from them and never written back. Name a verb, or the group prints its
usage and exits 2.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | nothing itself; its verbs build and read a SQLite index. |
| Writes | nothing itself; each verb's help names what it writes |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 a verb ran and returned 0 · 2 no verb given, or the verb refused |
| Machine output | human-only-by-design — group dispatcher: prints usage/guidance only; the machine surfaces are its nested verbs |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
Examples
Build a SQLite index from a finished run.
sbomflow index rebuild out --db index.sqliteList the releases the index holds.
sbomflow index query releases --db index.sqliteName no verb: print the group's usage and exit 2. Exits 2.
sbomflow indexSee also: sbomflow index rebuild, sbomflow index query, sbomflow store.
sbomflow index query#
Run a read-only named query over the index.
Run one read-only named query over the index: releases, components, findings,
unreviewed evidence, the release that recorded a source hash, or a free-text
search. Nothing is written.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | one SQLite index database. |
| Writes | stdout (the named query's rows; --json prints JSON) |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok · 2 a missing database or a usage error |
| Machine output | has-json — read/status surface; one versioned JSON document on stdout |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
name | name | none | releases, components, findings, unreviewed-evidence, source-hash, search | yes | The named query to run. One of: releases, components, findings, unreviewed-evidence, source-hash, search. |
--db | DB | none | any | yes | Index database path. |
--release | RELEASE | none | any | no | Filter to a release id. |
--path | SOURCE_PATH | none | any | no | Source path (for source-hash). |
--term | TERM | none | any | no | Search term (for search). |
--kev | (flag) | false | — | no | Findings: only known-exploited (KEV). |
--json | (flag) | false | — | no | Emit deterministic JSON. |
Examples
List the releases the index holds.
sbomflow index query releases --db index.sqliteEvery finding, as JSON.
sbomflow index query findings --db index.sqlite --jsonSearch the index for a term.
sbomflow index query search --term openssl --db index.sqliteSee also: sbomflow index rebuild, sbomflow index validate, sbomflow store query.
sbomflow index rebuild#
(Re)build the SQLite index from an output dir or an evidence store.
(Re)build the SQLite index from an analysis output directory or an evidence
store. The database is derived from the JSON artifacts, which stay canonical;
rebuilding it changes no evidence.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | one output directory or one evidence store. |
| Writes | sbomflow-index.sqlite, a SQLite database at --db |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok · 2 missing or unusable input |
| Machine output | artifact-suffices — writes the derived SQLite index — itself a machine-queryable artifact (index query speaks --json) |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
path | path | none | any | yes | An analysis output dir or a store dir. |
--db | DB | none | any | no | Index database path (default: <path>/sbomflow-index.sqlite). |
--as-of | AS_OF | none | any | no | Pinned index build timestamp. |
Examples
Build the index beside a finished run.
sbomflow index rebuild outBuild one from a whole store, at a named path.
sbomflow index rebuild .sbomflow-store --db index.sqlitePin the build timestamp.
sbomflow index rebuild out --db index.sqlite --as-of 2026-07-01T00:00:00ZSee also: sbomflow index query, sbomflow index validate, sbomflow index-releases.
sbomflow index validate#
Validate the SQLite index (schema/integrity/FK).
Check a SQLite index's schema version, integrity and foreign keys. A missing
database is refused (exit 2) and one that fails a check exits 4, so a pipeline
can rebuild before it queries.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | one SQLite index database. |
| Writes | stdout (the errors and notes; --json prints JSON) |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 the index is valid · 2 the index database is not there (E015, a missing input) · 4 the index failed a schema, integrity or foreign-key check (a structural validation failure) |
| Machine output | has-json — structured errors/notes on stdout; the exit code is unchanged, so switching on --json cannot change the verdict |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
--json | (flag) | false | — | no | Emit the errors/notes as one JSON document. |
--db | DB | none | any | yes | Index database path. |
Examples
Check the index's schema, integrity and foreign keys.
sbomflow index validate --db index.sqliteThe same check as JSON.
sbomflow index validate --db index.sqlite --jsonA database that is not there is refused as a missing input and exits 2. Exits 2.
sbomflow index validate --db missing.sqliteSee also: sbomflow index rebuild, sbomflow index query.
sbomflow index-releases#
Build release-index.json, the file dashboard reads, from SBOMFlow output directories.
Read one or more output directories and write one cross-release release-
index.json, the machine-readable input the dashboard and portfolio views
render. A directory that cannot be read is listed with a warning rather than
failing the run.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | one or more output directories; an unusable one is listed with a warning. |
| Writes | release-index.json at --output |
--output names | one file |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok, with a warning for each unusable directory · 2 usage error |
| Machine output | artifact-suffices — writes release-index.json, the machine-readable cross-release index. A product entry also carries product_line when its releases declared one distinct from the product name; absent means no line was declared, never that grouping failed |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
output_dirs [output_dirs ...] | output_dirs [output_dirs ...] | none | any | yes | Output directories to index. |
--output | OUTPUT | none | any | yes | Where release-index.json is written (file). |
--as-of | AS_OF | none | any | no | Pinned index timestamp. |
Examples
Index two releases into one cross-release file.
sbomflow index-releases out-prev out --output release-index.jsonPin the index timestamp.
sbomflow index-releases out-prev out --output release-index.json --as-of 2026-07-01T00:00:00ZA missing directory is listed with a warning, not fatal.
sbomflow index-releases missing-dir out --output release-index.jsonSee also: sbomflow dashboard, sbomflow portfolio, sbomflow store.
sbomflow dashboard#
Render a static release dashboard from release-index.json or output directories.
Render one static HTML dashboard of releases: from a release-index.json, or
from output directories indexed on the fly. A directory that cannot be read is
left out with a warning. No server and no network.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | a release-index.json, or one or more output directories. |
| Writes | dashboard.html at --output |
--output names | one file |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok · 2 usage error |
| Machine output | artifact-suffices — renders a static human dashboard from release-index.json, which is already the machine surface |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
--report-theme | {light,dark} | light | light, dark | no | Palette for generated HTML surfaces (default: light). Print output is always light. |
inputs [inputs ...] | inputs [inputs ...] | none | any | yes | A release-index.json file, or one or more SBOMFlow output directories. |
--output | OUTPUT | none | any | yes | Where dashboard.html is written (file). |
--as-of | AS_OF | none | any | no | Pinned dashboard timestamp when indexing dirs. |
Examples
Render a static dashboard for two releases.
sbomflow dashboard out-prev out --output dashboard.htmlRender it from a release index.
sbomflow dashboard release-index.json --output dashboard.htmlIn the dark palette.
sbomflow dashboard out --output dashboard.html --report-theme darkSee also: sbomflow index-releases, sbomflow portfolio, sbomflow review-view.
sbomflow portfolio#
Aggregate the releases a portfolio config lists into one index and dashboard.
Read a portfolio config (JSON or YAML) listing output directories, flat or
grouped by product, and aggregate them into one cross-product dashboard and,
with --index, a portfolio-index.json. Relative paths resolve against the
config file's own directory. Grouping is presentation only; it never changes a
product's identity.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | a portfolio config and the output directories it lists. |
| Writes | the portfolio dashboard HTML at --output; portfolio-index.json with --index |
--output names | one file |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok · 2 a missing or malformed config |
| Machine output | artifact-suffices — writes the cross-product portfolio index plus its dashboard. When the config declares products:[], the index also carries config_grouping: the operator's own group names joined to the product_ids their directories resolved to. Presentation only — it never changes identity, and a group whose outputs span two product identities is reported as spanning two rather than merged |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
config | config | none | any | yes | Portfolio config file (.json/.yaml). |
--output | OUTPUT | none | any | yes | Where the portfolio dashboard HTML is written (file). |
--index | INDEX | none | any | no | Also write the aggregated portfolio-index.json here. |
--as-of | AS_OF | none | any | no | Pinned aggregation timestamp. |
Examples
Aggregate the releases a portfolio config lists into one dashboard.
sbomflow portfolio portfolio.yaml --output portfolio.htmlAlso write the aggregated index, at a pinned time.
sbomflow portfolio portfolio.yaml --output portfolio.html --index portfolio-index.json --as-of 2026-07-01T00:00:00ZRefuse a config that is not there. Exits 2.
sbomflow portfolio missing-portfolio.yaml --output portfolio.htmlSee also: sbomflow dashboard, sbomflow index-releases, sbomflow batch-run.
sbomflow release-chain#
Verify a hash-linked chain of release records across output directories.
Tamper-evident release history: each release record written with
`analyze --previous-output` embeds the hash of the record before it, and
the verb here walks that chain. Name the verb, or the group prints its
usage and exits 2.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | nothing itself; its verb reads release records. |
| Writes | nothing; its verb writes nothing either |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 the verb ran and returned 0 · 2 no verb given, or the verb refused |
| Machine output | human-only-by-design — group dispatcher: prints usage/guidance only; the machine surfaces are its nested verbs |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
Examples
Verify that a release links to the one before it.
sbomflow release-chain verify out-prev out-nextA single record is its own genesis.
sbomflow release-chain verify outName no verb: print the group's usage and exit 2. Exits 2.
sbomflow release-chainSee also: sbomflow release-chain verify, sbomflow compare-releases.
sbomflow release-chain verify#
Walk release records in order and verify each links to the previous by hash.
Verify tamper-evident release history. Each record after the first must
embed the SHA-256 of the record before it; the first is the genesis. A
wrong or edited link is tampered and an unreadable record is a break
(exit 4); a record that carries no link at all is detached, which is a
gap rather than corruption (exit 8).
Links are written by `analyze --previous-output`; this only reads them.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | release-record.json from each directory, in the order given. |
| Writes | stdout (the chain report; --json prints JSON) |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 the chain verified, or a single genesis record · 2 usage error · 4 a record is unreadable, or a link is broken or tampered · 8 a record carries no link to its predecessor |
| Machine output | has-json — read/status surface; one versioned JSON document on stdout |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
record_dirs [record_dirs ...] | record_dirs [record_dirs ...] | none | any | yes | Output directories in chronological order. |
--json | (flag) | false | — | no | Emit the chain report as JSON. |
Examples
Verify that a release links to the one before it.
sbomflow release-chain verify out-prev out-nextThe chain report as JSON.
sbomflow release-chain verify out-prev out-next --jsonTwo runs analyzed without --previous-output are detached and exit 8. Exits 8.
sbomflow release-chain verify out-prev outSee also: sbomflow analyze, sbomflow compare-releases, sbomflow store verify.
sbomflow backfill#
Register past releases in the store as DECLARED records, never as observed runs.
Backfill historical releases, shipped before SBOMFlow, from an
operator-authored manifest (product, and per release: version, date,
channel, optional sbom file, support period, note). Each row becomes one
DECLARED release record (record_provenance=declared), chained by hash.
Offline, local files only, no source scanning.
Declared records give the timeline continuity history needs WITHOUT
fabricating a run: composition comes only from a supplied SBOM (imported,
referenced + hashed) else 'unknown', and declared records are excluded from
observed-only computations (e.g. the evidence-regression drift gate).| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | an operator-authored manifest of past releases, and any SBOM file it names. |
| Writes | index.json (one DECLARED release record per row, chained by hash); store-backfill-log.jsonl; stdout (the summary; --json prints JSON) |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok · 2 a missing or malformed manifest, a duplicate version or a bad date |
| Machine output | has-json — read/status surface; one versioned JSON document on stdout |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
manifest | manifest | none | any | yes | Backfill manifest (YAML/JSON). |
--store | STORE | .sbomflow-store | any | no | Store directory to backfill into (default: .sbomflow-store). |
--as-of | AS_OF | none | any | no | Pinned timestamp for the store index / backfill log. |
--json | (flag) | false | — | no | Emit the backfill summary as JSON. |
Examples
Register two past releases as declared records in a new store.
sbomflow backfill backfill.yaml --store fresh-storeBackfill into a store that already holds an observed release, at a pinned time.
sbomflow backfill backfill.yaml --store .sbomflow-store --as-of 2026-07-01T00:00:00ZPrint the summary as JSON.
sbomflow backfill backfill.yaml --store fresh-store --jsonSee also: sbomflow store list, sbomflow store timeline, sbomflow migrate-in.
sbomflow migrate-in#
Import another tool's estate as origin-labelled CLAIMS (offline; never decisions).
Point this at a directory of another tool's exports. Every row is
imported as a CLAIM carrying its origin — never a reviewer decision,
VEX status, approval or gate input. An incumbent tool's status is a
claim about what that tool said, not authority over your product.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | stable |
| Regime | any |
| Reads | a directory of another tool's exports (Trivy, Grype, SARIF JSON). |
| Writes | the migration manifest, into --output; stdout (the manifest summary; --json prints JSON) |
--output names | a directory |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 ok · 2 an unreadable source directory |
| Machine output | has-json — one versioned migration manifest; every row carries claim_origin and unimported files are listed with reasons |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
source | source | none | any | yes | Directory of competitor exports. |
--output | OUTPUT | none | any | no | Where the migration manifest is written (directory). |
--json | (flag) | false | — | no | Emit the manifest as JSON. |
--as-of | AS_OF | none | any | no | Pinned timestamp recorded in the manifest. |
Examples
List what another tool's exports would bring in, as origin-labelled claims.
sbomflow migrate-in exportsWrite the migration manifest, at a pinned time.
sbomflow migrate-in exports --output migration --as-of 2026-07-01T00:00:00ZPrint the manifest as JSON.
sbomflow migrate-in exports --jsonSee also: sbomflow ingest-trivy, sbomflow ingest-grype, sbomflow ingest-sarif, sbomflow backfill.
sbomflow sbom-set#
Work with SBOM documents you were handed (offline).
Work with SBOM documents supplied to you, without changing them. Offline;
nothing is fetched or sent.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | experimental |
| Regime | any |
| Reads | nothing itself; its verb reads the SBOM files it is given. |
| Writes | nothing itself; its verb's help names what it writes |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 a verb ran and returned 0 · 2 no verb given, or the verb refused |
| Machine output | human-only-by-design — group dispatcher: prints usage/guidance only; the machine surfaces are its nested verbs |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
Examples
Compare a supplier's CycloneDX SBOM with an integrator's SPDX SBOM of the same build.
sbomflow sbom-set compare sboms/gateway-2.4.1.supplier.cdx.json sboms/gateway-2.4.1.integrator.spdx.jsonSee also: sbomflow sbom-set compare, sbomflow help history.
sbomflow sbom-set compare#
Explain where two or more SBOMs for the same build agree and differ.
Compare two or more CycloneDX or SPDX 2.x SBOMs that should describe the same
build, and say where they agree and where they differ: components, versions,
identifiers, digests, licences, dependency edges, scope, generator and field
coverage, N sources wide, each fact with the document coordinate it came from.
Comparability comes first. The documents must be shown to describe the same
bytes: each states the same release id and subject digest, or the operator
declares they belong together (recorded as declared, never verified). Otherwise
the result is incomparable_subject and no figure is computed at all. A shared
name or version string is not a binding, and different digests are never
'low agreement'.
Nothing is merged, scored or ranked: a fact in two sources is corroborated
presence, not truth, and the originals are never modified. A document this
build cannot read is counted as unsupported, never as an empty SBOM.| Fact | Value |
|---|---|
| Group | history — Compare releases & keep history |
| Stability | experimental |
| Regime | any |
| Reads | two or more CycloneDX JSON or SPDX 2.x (JSON or tag-value) SBOM files, and optionally the released bytes (--subject). |
| Writes | stdout (the summary, or the JSON document with --json); sbom-concordance.json (the same JSON document, only with --output) |
--output names | one file |
| Network | offline — This command has no network-capable path. |
| Exit codes | 0 a comparison was produced, whatever it found (comparable or incomparable_subject) · 2 a refusal: fewer than two SBOMs, a missing or unreadable SBOM or --subject file, or an --output that names an input; nothing is written |
| Machine output | has-json — one sbomflow-sbom-concordance-v1 document with --json (stdout) or --output (file): the subject binding, every input's custody record and, when the binding holds, the nine dimensions |
Options
| Option | Value | Default | Allowed | Required | Help |
|---|---|---|---|---|---|
-h, --help | (flag) | none | — | no | show this help message and exit |
SBOM [SBOM ...] | SBOM [SBOM ...] | none | any | yes | Two or more CycloneDX JSON or SPDX 2.x (JSON or tag-value) files. |
--subject | FILE | none | any | no | The released bytes. SBOMFlow hashes them, and a document stating another subject digest is incomparable. It never makes a binding stronger. |
--declared-same-subject | (flag) | false | — | no | Record the operator's statement that these documents describe the same subject. Recorded as declared, never verified; a digest mismatch still refuses. |
--output | FILE | none | any | no | Also write the sbom-concordance JSON document here, replacing it (file). |
--json | (flag) | false | — | no | Print the JSON document on stdout instead of the summary. |
Examples
Compare two SBOMs of one build in two formats, checking each stated subject digest against the released bytes.
sbomflow sbom-set compare sboms/gateway-2.4.1.supplier.cdx.json sboms/gateway-2.4.1.integrator.spdx.json --subject sboms/released-image.binTwo SBOMs with the same names and version but different subject digests are incomparable, and no figure is computed.
sbomflow sbom-set compare sboms/gateway-2.4.1.supplier.cdx.json sboms/other-build.cdx.jsonThree sources, N-way, as the JSON document on stdout and in a file.
sbomflow sbom-set compare sboms/gateway-2.4.1.supplier.cdx.json sboms/gateway-2.4.1.pipeline.cdx.json sboms/gateway-2.4.1.integrator.spdx.json --json --output sbom-concordance.jsonRefused (exit 2): a comparison needs at least two SBOMs; nothing is written. Exits 2.
sbomflow sbom-set compare sboms/gateway-2.4.1.supplier.cdx.jsonSee also: sbomflow compare-releases, sbomflow reproducible-compare, sbomflow help history.