
Offline single-binary web app that ingests CycloneDX, SPDX and syft SBOMs, runs an ensemble of CVE scanners, enriches findings with EPSS, CISA-KEV and nuclei, and exports SARIF, CSAF and PDF.
A single-binary, offline web application to investigate SBOM (Software Bill of Materials) files. Upload an SBOM or import a folder of them, analyse each with an ensemble of independent tools, review the normalised findings in one local UI, and export the result in eight formats. No installation, no network access, no telemetry.
NOASSERTION from a
field that is simply absent.The home page (/) lists every stored SBOM and stays usable as the corpus
grows:
log4j or
openssl finds every SBOM that ships that component. The query is
length-capped and parsed leniently./components answers the inverse question — which of my SBOMs declare this
component — across the whole corpus at once, by component name or exact
package URL. It reads a dedicated component index built at ingest, separate
from the dashboard's full-text index.
Because a stale index would make a genuinely affected SBOM read as "not affected", the page states how many SBOMs are not yet indexed and offers Rebuild index, which re-parses the retained bytes for exactly those. A search is only complete once that warning is gone.
"Indexed" means the index was built, which is not the same as "produced
rows": a metadata-only SBOM declares no components, so it contributes none
and is still fully indexed. The store records the fact of indexing per SBOM
(schema v8, components_indexed_at) rather than inferring it from row
existence — inferring it meant a component-free SBOM could never leave the
"not yet indexed" set, so the warning survived every rebuild and the button
reported the same SBOMs as freshly indexed forever. Upgrading migrates
automatically and backfills anything that already had rows; one rebuild
settles the remainder.
Each tool is optional at runtime and detected on PATH (override the binary
with SBA_TOOL_<NAME>_BIN). Findings never depend on any single tool. Before
each scan the tool is started once with its version argument: no binary means
skip (not found on PATH), while a binary that cannot be started, or that
gives no answer within 15 seconds, means fail with that reason.
| Tool | Role | Verified against |
|---|---|---|
| syft | SBOM normaliser / CycloneDX 1.6 converter | 1.52.0 |
| grype | CVE matcher | 0.119.0 |
| trivy | Vulnerability scanner | 0.74.0 |
| osv-scanner | OSV database lookups | 2.4.0, 2.6.0 |
| bomber | Independent ensemble matcher | 0.5.1 |
| bomdrift | Supply-chain drift / typosquat (SARIF) | 0.9.9 |
| inspektr | SBOM vulnerability scanner (JSON) | 0.1.9 |
| shieldbom | Offline embedded/IoT CVE scanner (SARIF) | 0.3.0 |
| provenant | Licence / copyright / provenance (ScanCode) | 1.0.10 |
"Verified against" is the version each invocation was actually exercised with,
not a floor and not a pin: tools are resolved on PATH at runtime and any
compatible version works. osv-scanner lists two because 2.6.0 was
re-verified after the 2.x line changed its logging — the documented
scan --lockfile <input> --format json form is unchanged and still exits
non-zero on findings, so no code change was needed.
The first eight tools contribute CVE-shaped vulnerability findings;
provenant is different — it reports licence, copyright, holder and author
provenance, which the auditor stores and displays as its own finding shape (a
"Licence & provenance" section per run, included in the Markdown export) rather
than discarding it.
The scanners cannot read every format directly: bomber consumes only
CycloneDX ≤ 1.6, and none of the scanners read SPDX 3.0 JSON-LD. In both cases
the auditor normalises the input to CycloneDX via syft convert first, and
skips with a documented reason when syft is unavailable rather than let a tool
report zero packages. grype's vulnerability database is refreshed once per run
(out of band) so a routine staleness is a fresh scan, not a failed one.
After analysis, each CVE-shaped finding is enriched with three real signals pulled from a vulnlookup service (default https://vulnlookup.ndaal.eu):