npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sbom-diff-sentinel

v1.1.0

Published

SBOM diff gate: compares CycloneDX 1.4-1.6 / SPDX 2.3 SBOMs or git refs, reports added, removed, upgraded and downgraded components plus licence, supplier and hash changes, checks OSV.dev (cached, offline-capable) for new vs fixed vulns, honours VEX, and

Downloads

116

Readme

sbom-diff-sentinel

npm version CI Node License: MIT

See exactly what a pull request changes in your software bill of materials, and block the risky changes.

sbom-diff-sentinel compares two SBOMs, or your project at a git ref against the working tree. It reports components that were added, removed, upgraded or downgraded, plus license, supplier and hash changes. It checks the added and changed components against OSV.dev and splits the results into new and fixed vulnerabilities, each with a severity. Then it applies a policy: fail on new vulnerabilities at or above a severity, downgrades, tampered hashes or oversized changes. Licence changes are reported (with license-sentinel categories) but not judged: put your licence policy in license-sentinel.

  • Reads CycloneDX 1.4–1.6 JSON and SPDX 2.3 JSON
  • Generates SBOMs from npm, pnpm and yarn lockfiles with license-sentinel
  • Text, JSON, SARIF and Markdown output (the Markdown suits PR comments and job summaries)
  • CLI, library and GitHub Action. Node.js 18+, one zero-dependency runtime dependency

Quick start

npx sbom-diff-sentinel --base origin/main             # project at origin/main vs working tree
npx sbom-diff-sentinel base.cdx.json head.spdx.json   # two SBOM files (formats can be mixed)
npx sbom-diff-sentinel base.cdx.json                  # SBOM file vs the current project
npx sbom-diff-sentinel --generate bom.cdx.json        # write a CycloneDX 1.5 SBOM for the project
SBOM diff: base.cdx.json (8) → head.spdx.json (10)

Added (3)
  + [email protected] MIT
  + [email protected] AGPL-3.0-only
  + [email protected] MIT OR GPL-3.0-only

Removed (1)
  - [email protected]

Upgraded (1)
  ↑ lodash 4.17.20 → 4.17.21

Downgraded (1)
  ↓ minimist 1.2.8 → 1.2.5

License changes (1)
  chalk: MIT → GPL-3.0-only

Supplier changes (1)
  debug: TJ Holowaychuk → Evil Corp

Hash changes (1)
  ! [email protected]

Fixed vulnerabilities (1)
  ✓ GHSA-29mw-wpgm-hmr9 [email protected] (medium)

Policy findings (32)
  critical [email protected]: GHSA-xvch-5gv4-984h (CVE-2021-44906) Prototype Pollution in minimist [new-vulnerability]
           ↳ See https://osv.dev/vulnerability/GHSA-xvch-5gv4-984h for fixed versions
  high     [email protected]: GHSA-cph5-m8f7-6c5x (CVE-2021-3749) axios Inefficient Regular Expression Complexity vulnerability [new-vulnerability]
  …
  high     [email protected]: hash changed for the same version [hash-changed]
  medium   debug: supplier "TJ Holowaychuk" → "Evil Corp" [supplier-changed]
  medium   minimist downgraded 1.2.8 → 1.2.5 [downgrade]
  low      chalk: license MIT → GPL-3.0-only [license-changed]

6 change(s): +3 -1 ↑1 ↓1; vulns: 26 new, 1 fixed (checked); 1 critical, 15 high, 14 medium, 2 low

VEX, OSV cache and PR comments

sbom-diff-sentinel --base origin/main --vex security/openvex.json --pr-comment pr.md
sbom-diff-sentinel base.cdx.json head.cdx.json --offline        # cached OSV results only
permissions: { contents: read, pull-requests: write }
steps:
  - uses: actions/checkout@v4
    with: { fetch-depth: 0 }
  - uses: MrLouisWright/sbom-diff-sentinel@v1
    with: { pr-comment: 'true', vex: 'security/openvex.json' }

Licence policy (copyleft and so on) belongs in license-sentinel; sbom-diff-sentinel only reports licence changes.

Module formats and types

Ships ESM and CommonJS (import and require both work, via exports), with TypeScript declarations generated from the source at build time. Tests run on Node 18/20/22 in GitHub Actions (test/), including require/import checks against the packed tarball; releases are published with npm provenance.

Rules

| Rule | Default severity | What it catches | |---|---|---| | new-vulnerability | the advisory's severity | An added or changed component has an OSV advisory that the base didn't have (matched by ID and aliases) | | hash-changed | high | Same name and version, different hash: a republished or tampered package, or a different mirror | | too-many-changes | high | More component changes than --max-changes | | osv-error | high | The OSV lookup failed, so the check can't silently pass. Use --offline to skip it on purpose | | downgrade | medium (--deny-downgrades raises it to high) | A component moved to a lower version | | supplier-changed | medium | The supplier or publisher changed | | license-changed | low | Any license change | | allowlist-expired | low | An allowlist entry is past its expires date | | unscanned-component | info | No purl or version, so it couldn't be checked on OSV | | sbom-warning | info | Spec version outside CycloneDX 1.4–1.6 or SPDX 2.3 (parsed anyway) |

Vulnerability severity comes from the CVSS v3 base score (computed from the vector), then the advisory's rating (GHSA MODERATE maps to medium). Advisories without either are rated medium and marked as estimated.

How components are matched

Components are matched by package URL without the version (pkg:npm/lodash, pkg:maven/org.example/core), falling back to the name. Qualifiers such as ?type=jar are ignored. When a package appears in several versions, versions on both sides are unchanged, the rest are paired highest-to-highest as upgrades or downgrades, and any leftovers count as added or removed. Hashes are compared only when both sides share an algorithm.

Options

--base <file|ref>      Base SBOM file, or a git ref to generate it from (e.g. origin/main, HEAD~1, v1.2.0)
--head <file|dir>      Head SBOM file or project directory (default: --project)
-C, --project <dir>    Project directory (default: .); may be a subdirectory of the repo
--include-dev          Include devDependencies when generating
--offline              Skip the OSV.dev lookup
--osv-url <url>        OSV API base URL (default: https://api.osv.dev)
--timeout <ms>         OSV request timeout (default: 15000)
--fail-on <sev>        critical | high | medium | low | info | none (default: high)
--vex <file>           CycloneDX VEX / OpenVEX (repeatable)
--cache-dir <dir>      OSV cache directory; --cache-ttl <s>, --no-cache
--pr-comment <file>    Short PR comment Markdown
--max-changes <n>      Fail when more than n components change
--deny-downgrades      Treat downgrades as high severity
--config <file>        Config file (default: .sbom-diff-sentinel.json or package.json "sbom-diff-sentinel")
--format <fmt>         text | json | sarif | markdown (default: text)
--json/--sarif/--markdown <file>   Also write that report to a file
--generate [file]      Write a CycloneDX 1.5 SBOM for the project (stdout if no file)
-q, --quiet            Only print the summary line

Exit codes: 0 pass, 1 findings at or above --fail-on, 2 usage or runtime error (bad SBOM, unknown git ref and so on).

With --base <ref>, the tool reads package.json and the lockfile at that ref with git show and builds both sides from lockfiles, so they're generated the same way. Your working tree is never checked out or changed.

Config

.sbom-diff-sentinel.json (or a "sbom-diff-sentinel" key in package.json):

{
  "failOn": "high",
  "vex": ["security/vex.json"],
  "maxChanges": 40,
  "rules": { "downgrade": "high", "license-changed": "off" },
  "allowlist": [
    { "id": "CVE-2021-3749", "reason": "server-side only, not reachable", "expires": "2027-01-31" },
    { "package": "@internal/*", "rule": "supplier-changed", "reason": "registry migration" },
    { "package": "chalk", "rule": "license-changed", "reason": "legal approved LEGAL-123" }
  ]
}

An allowlist entry matches a vulnerability ID or alias (id), a package name glob (package) and/or a rule. Matching findings are suppressed and reported as allowlisted. After expires, the finding comes back with a note, and unused expired entries are flagged.

GitHub Action

on: pull_request
permissions:
  contents: read
jobs:
  sbom-diff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0, persist-credentials: false }
      - uses: MrLouisWright/sbom-diff-sentinel@v1
        id: sbom
        with:
          fail-on: high          # base defaults to origin/<PR base branch>
          max-changes: '50'

The Markdown report is written to the job summary. Its path is in steps.sbom.outputs.markdown-file, so you can post it as a sticky PR comment (it starts with <!-- sbom-diff-sentinel -->). Inputs: base, head, project, fail-on, vex, pr-comment, github-token, max-changes, include-dev, offline, config, sarif, json, job-summary and args.

Library

import { readSbom, generateSbomAtRef, generateSbom, analyze, formatMarkdown, exitCode } from 'sbom-diff-sentinel';

const report = await analyze(generateSbomAtRef('.', 'origin/main'), generateSbom('.'), { maxChanges: 50 });
console.log(report.diff.upgraded, report.vulnerabilities.new);
process.exitCode = exitCode(report);

TypeScript types are included.

Support

If this project is useful to you, you can support its development with a donation: Buy me a coffee. It helps keep these tools maintained, documented and free for everyone.

Credit (optional)

Free to use under MIT, no attribution required. If sbom-diff-sentinel helps you, a link back is appreciated:

<a href="https://louiswright.co.uk">Built with sbom-diff-sentinel by Louis Wright</a>
[![Built with sbom-diff-sentinel by Louis Wright](https://img.shields.io/badge/built%20with-sbom--diff--sentinel-blue)](https://louiswright.co.uk)

License

MIT © Louis Wright (Louis Wright)