Back to updates
New releaseJul 24, 2026

fleet-cve-scanner v1.1.0

An open-source, single-script CVE scanner for RMM-managed fleets. Pure PowerShell 7 — joins your RMM software inventory against NVD, CISA KEV, EPSS and SSVC to answer: is this version vulnerable, and how urgent is it?

Share

fleet-cve-scanner

An open-source, single-script CVE scanner for RMM-managed fleets. Pure PowerShell 7, no agents, no appliances, no license fees.

It answers one question for every piece of software on every endpoint you manage: is the installed version known to be vulnerable, and how urgent is it? — by joining your RMM's software inventory against free, authoritative security feeds:

  • NIST NVD — known CVEs + affected version ranges
  • CISA KEV — actively exploited in the wild (bypasses the score threshold)
  • EPSS — probability of exploitation in the next 30 days
  • CISA SSVC — act / attend / track prioritization decisions
  • MSRC — proof a Microsoft patch already covers the finding
  • endoflife.date — software that will never be patched again

Outputs: a per-device CSV report, a SQLite history with SLA clocks and week-over-week change events, a self-contained HTML dashboard, and an optional markdown exposure report.

Status

v1.0.0. The engine was ported clean-room from a scanner that ran weekly in production against a Windows fleet (see docs/HISTORY.md). Before this release the two were run against the same live fleet and compared row-for-row: across tens of thousands of findings where both saw an identical (device, software, version), every computed field — status, CVSS, severity, KEV flag, SSVC decision, fix version — matched exactly.

That comparison covers the verdict logic. It did not exercise the history/SLA layer, and it is not a substitute for validating the tool in your own environment. Read docs/known-limitations.md before trusting it — it is written to be honest rather than flattering.

Quick start

You need PowerShell 7 (pwsh). Two ways to run:

A. Live NinjaOne scan

cp config.example.json config.json
# edit config.json: fill ninjaone.client_id / client_secret / base_url,
# set output.report_dir, and (recommended) nvd.api_key
pwsh -File fleet-cve-scan.ps1

B. Offline — any RMM, SCCM, or Intune (no API access)

Export your inventory to a CSV with columns hostname, software, version (optional device_id, os), then:

cp config.example.json config.json   # only output.report_dir is needed here
pwsh -File fleet-cve-scan.ps1 -InputCsv inventory.csv

-InputCsv makes zero NinjaOne calls — no OAuth, no API. It still queries NVD and the other public feeds, so you still want an nvd.api_key in config.json and a valid output.report_dir. See docs/rmm-adapters.md for the input contract and per-RMM export recipes.

Installing prerequisites

  • PowerShell 7 — macOS: brew install powershell; Windows: winget install Microsoft.PowerShell; Debian/Ubuntu: install from the Microsoft package repo (packages.microsoft.com).
  • sqlite3 (optional, powers history/trending) — macOS: preinstalled or brew install sqlite; Debian/Ubuntu: apt-get install sqlite3; Windows: download the SQLite "tools" bundle from sqlite.org and put sqlite3.exe on PATH or beside the script. Without it the scan still runs; first_seen falls back to the run date.

Requirements

  • PowerShell 7 (7.4+ recommended). The parallel scan requires 7.0+.
  • sqlite3 — optional; enables the history DB, trending, and SLA first_seen tracking. Absent = those features skip, scan still completes.
  • Network egress to NVD, CISA (KEV), epss.empiricalsecurity.com (the EPSS bulk-score host — FIRST moved EPSS data hosting there), MSRC, endoflife.date, and GitHub raw (CVE Program cvelistV5) — plus the NinjaOne API in live mode.
  • Credentials — live mode needs NinjaOne OAuth2 client id/secret; offline mode needs only an inventory CSV. An NVD API key is technically optional but effectively required at fleet scale: NVD allows 50 requests/30s with a key and only 5/30s without one. The scanner detects which you have and throttles to match, so a keyless run is correct but roughly 10x slower — fine for a handful of products, impractical for a real fleet. Keys are free.

Configuration reference

Config is a JSON file (default config.json beside the script; override with -ConfigPath). Start from config.example.json. Every key the scanner reads, its default when omitted, and what it does:

ninjaone (live mode)

KeyDefaultPurpose
ninjaone.client_id""NinjaOne API OAuth2 client id. Required for live scans; leave blank for -InputCsv.
ninjaone.client_secret""NinjaOne API OAuth2 client secret.
ninjaone.base_url— (example ships https://app.ninjarmm.com)NinjaOne API base URL; required for live scans, no built-in fallback. Use your region's host (e.g. eu.ninjarmm.com, oc.ninjarmm.com).

nvd

KeyDefaultPurpose
nvd.api_key""NVD API key, sent as the apiKey header. Blank runs unauthenticated, which drops the defaults for nvd_rate_limit and nvd_min_spacing_ms to NVD's anonymous ceiling (4/30s, 6500ms) instead of the authenticated one (48/30s, 700ms). Free, and ~10x faster.

Scan & scoring

KeyDefaultPurpose
cvss_threshold7.0Minimum CVSS base score for a scored CVE to be reported VULNERABLE. KEV-listed CVEs bypass this floor.
ssvc_mission_prevalencehighSSVC Mission Prevalence stakeholder input (low / medium / high).
ssvc_public_wellbeinghighSSVC Public Well-being stakeholder input (low / medium / high).

Caching & rate limiting

KeyDefaultPurpose
nvd_cache_ttl_days7Days an NVD result (per software name) stays cached before re-query.
nvd_cache_flush_every500Checkpoint the NVD cache to disk every N completed items, so a killed scan keeps its fetches. 0 disables (end-of-scan save still runs).
nvd_min_spacing_ms700 with an API key, 6500 withoutMinimum milliseconds between NVD calls. The binding rate constraint — tune this first.
nvd_rate_limit48 with an API key, 4 withoutSliding-window cap on NVD calls per 30s (a backstop). Floor of 1.
parallel_throttle20ForEach-Object -Parallel runspace count. NVD calls are serialized regardless; this only bounds cache-hit concurrency. Floor of 1.
kev_cache_ttl_hours24TTL for the cached CISA KEV catalog.
epss_cache_ttl_hours24TTL for the cached FIRST.org EPSS score file.
eol_cache_ttl_days7TTL for cached endoflife.date lookups.
msrc_cache_ttl_days30TTL for cached MSRC CVRF data.
cvelist_cache_ttl_days7TTL for cached CVE Program (cvelistV5) / CISA-ADP SSVC data.
cvelist_fetch_budget300Max CVE-record fetches per run for SSVC/vulnrichment enrichment; the rest defer to the next run.

nvd_min_spacing_ms, nvd_rate_limit, and parallel_throttle are read by the scanner but not present in config.example.json — add them only if you need to tune. See docs/rate-limiting.md.

Enrichment & SLA

Categories