@noregressions/sbom-plus-cli
v0.1.0
Published
Annotates a CycloneDX SBOM with EOL data (via HeroDevs' hd CLI) and CVE findings (Snyk, OSV-Scanner, Grype), rendering a Markdown report and optionally gating CI on EOL/CVSS thresholds. Companion to the SBOM+ Maven plugin (dev.noregressions:sbom-plus-mave
Readme
@noregressions/sbom-plus-cli (node-cli)
Node/TypeScript port of everything downstream of SBOM generation in this
project: annotate a CycloneDX SBOM with EOL data (via HeroDevs' hd CLI)
and CVE findings (Snyk, OSV-Scanner, Grype), render a Markdown report, and
optionally gate CI on EOL/CVSS thresholds.
Why this exists
The Java Maven plugin in ../maven-plugin does one thing only: walk a
Maven build's dependency/plugin/BOM graphs and emit a comprehensive
CycloneDX SBOM. That's the one piece that genuinely has to live inside a
build tool's own object model (the plugin classloader realm isn't visible
from outside Maven). Everything after that - shelling out to the four CVE
providers, joining their reports back onto the SBOM's components, rendering
a report, gating a build - is pure JSON-in/JSON-out data processing with no
Maven dependency at all, so it lives here instead, as a standalone tool
that works against any CycloneDX 1.4/1.5/1.6 SBOM, not just ones this
repo's Maven plugin produced. That means it should work as-is against an
SBOM from cyclonedx-npm, cyclonedx-python, syft, or any other
generator, for any language ecosystem - not just Maven/Java projects.
Nothing in maven-plugin/core (Java) has been removed yet - this is an
additive port, not a replacement, until it's been proven out.
Usage
Once published, install globally and run as sbom-plus (the package name
is @noregressions/sbom-plus-cli - noregressions mirrors the Maven
plugin's groupId, dev.noregressions, since neither half of this tool is
HeroDevs-branded; the CLI binary itself is just sbom-plus, matching the
Maven plugin's sbom-plus:scan/sbom-plus:scan-aggregate goal prefix):
npm install -g @noregressions/sbom-plus-cli
sbom-plus --sbom path/to/bom.jsonOr from a local checkout, without installing/publishing:
npm install
npm run build
node dist/cli.js --sbom path/to/bom.jsonOr during development, without a build step:
npx tsx src/cli.ts --sbom path/to/bom.jsonMaven projects: generate the SBOM too, in one step
For a Maven project you don't need to pre-generate the SBOM at all - pass
--project instead of --sbom and the CLI invokes the published SBOM+
Maven plugin (dev.noregressions:sbom-plus-maven-plugin, on Maven
Central) directly, then runs the normal pipeline on the result:
node dist/cli.js --project path/to/maven-projectThis runs mvn -B -f <dir>/pom.xml
dev.noregressions:sbom-plus-maven-plugin:0.0.2:scan-aggregate
-DsbomPlus.sbomOutputFile=<temp-file> (the reactor-wide goal, so it
covers single- and multi-module projects alike) and reads the SBOM back
from a throwaway temp file it controls directly, via the
-DsbomPlus.sbomOutputFile property the plugin exposes as of 0.0.2.
This means it doesn't need to guess where project.build.directory
resolves to - a pom that relocates its build directory away from
target/ works fine. The generated SBOM is also copied to
<out-dir>/herodevs-sbom-aggregate.json so it isn't left behind in a temp
directory the OS may clean up later.
Reports default to being written into the project root; override with
--out-dir. Requires mvn on PATH (Maven 3.9.9+); override with
--mvn-command. Override the plugin version with --plugin-version
(pinned to 0.0.2 by default so runs are reproducible) - note that the
-DsbomPlus.sbomOutputFile override requires plugin 0.0.2 or newer;
pointing --plugin-version at 0.0.1 will fail with a clear error
explaining why.
--sbom and --project are mutually exclusive; --sbom remains the
ecosystem-agnostic path for SBOMs from any other generator.
hd is required (fails fast with install instructions if missing, same as
the Java side). Snyk/OSV-Scanner/Grype are each tri-state via
--snyk/--osv/--grype <auto|true|false> - auto (the default)
auto-detects the CLI on PATH, true requires it, false disables it even
if installed.
Known gap vs. the Java renderer
The Markdown report is missing two columns the Java EolReportRenderer
has: Classpath category and Brought In Via. Both come from Maven-specific
enumeration data that isn't in a generic CycloneDX SBOM today. The plan is
for the Java SBOM writer to embed that as herodevs:* component
properties so this tool can pick it back up - not built yet.
OSV findings are grouped, not per-raw-advisory
providers/osv.ts builds one finding per groups[] entry (osv-scanner's
own de-duplication of aliased advisories into one real issue), not one per
raw vulnerabilities[] entry. This matters in practice: confirmed against
a real run against ../sample-python-project, PyPI packages routinely
have twice as many vulnerabilities[] entries as groups[] (e.g. urllib3:
24 vulnerabilities, 12 groups) because PyPI advisories are frequently
dual-published to both a PyPI-specific id (PYSEC-...) and a GitHub
Security Advisory (GHSA-...) for the same real vulnerability. Iterating
vulnerabilities[] directly double-counts every such pair as two findings
with identical cveId/severity/score. This is a latent bug in the Java
OsvAnnotator too - it was never caught there because the Maven fixtures
tested against happened to have a 1:1 vulnerabilities:groups ratio in
every case.
Matching key coverage
annotate/match.ts's fallbackKeyFor (used for Snyk/OSV, since neither
echoes a purl in its own output) is confirmed against real Snyk/OSV output
for three ecosystems, run against ../sample-node-project and
../sample-python-project: Maven (groupId:artifactId:version), npm
unscoped (name:version), npm scoped (@scope/name:version - note the
slash, not a colon, since that's how Snyk/OSV themselves report a scoped
package's name) and PyPI (name:version, no group concept at all).
Testing
npm testReuses the same real, verified JSON fixtures as the Java core module's
tests (hd, Snyk, OSV, Grype) rather than re-deriving them - see each
provider's test file for the source of its fixture.
