fad-checker 2.2.2 → 2.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,42 +5,47 @@
5
5
  [![license](https://img.shields.io/npm/l/fad-checker.svg)](https://github.com/n8tz/fad-checker/blob/main/package.json)
6
6
  [![node](https://img.shields.io/node/v/fad-checker.svg)](https://nodejs.org)
7
7
 
8
- > **F**ucking **A**utonomous **D**ependency **C**hecker
9
-
10
- `fad-checker` scans **Maven**, **npm**, **Yarn**, **Composer (PHP)**, **PyPI (Python)**, **NuGet (C#/.NET)**, **Go**, **Ruby**, **vendored JavaScript** and **committed native binaries** in any source tree — multi-module, monorepo, polyglot, whatever you've got — and produces a single self-contained HTML report with CVE (prioritised by **EPSS + CISA KEV**), EOL, obsolete, outdated and **license** findings, plus per-ecosystem fix recipes. It also exports a **CycloneDX 1.6 SBOM** and a **CSAF 2.0 VEX**.
8
+ > **F**abulous **A**utonomous **D**ependency **C**hecker<br>
9
+ > AKA **F**uckin' **A**utonomous **D**ependency **C**hecker<br>
11
10
 
11
+ `fad-checker` audits **Maven · npm · Yarn · pnpm · Composer · PyPI · NuGet · Go · Ruby**, vendored JavaScript and committed native binaries in any source tree — multi-module, monorepo, polyglot — and produces a self-contained **HTML + Word report** (CVE prioritised by EPSS + CISA KEV, EOL, obsolete, outdated, licenses) plus **CycloneDX SBOM / CSAF VEX / SARIF / JSON** exports. **No build tools, no Docker, no network needed** — it reads lockfiles and manifests straight off disk.
12
12
 
13
13
  🌐 **[Project site & docs →](https://n8tz.github.io/fad-checker/)**
14
14
 
15
- <p align="center"><img src="docs/assets/cli.png" alt="fad-checker terminal output — a [n/N] checklist warming each vulnerability database, then CVE findings coloured by severity" height="760"></p>
15
+ <p align="center"><img src="docs/assets/demo.gif" alt="fad-checker animated terminal demo — a [n/N] checklist warming each vulnerability database, then CVE findings coloured by severity with KEV badges" height="600"></p>
16
16
 
17
- It runs against the source files alone. **No `mvn`, no `npm install`, no `composer install`, no `pip`, no `dotnet restore`, no Docker.** It reads `pom.xml`, `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`, `composer.lock`, `poetry.lock`/`Pipfile.lock`/`uv.lock`/`pdm.lock`/`pyproject.toml`/`requirements.txt`, and `packages.lock.json`/`*.csproj`/`*.fsproj`/`*.vbproj`/`packages.config` directly.
17
+ ## Features
18
18
 
19
- > **Supported ecosystems: Maven, npm, Yarn (v1 + Berry/v2+), pnpm, Composer, PyPI, NuGet, Go, Ruby + committed native binaries.** Each is a self-contained **codec** (`lib/codecs/`) — adding another is adding a codec, no orchestrator surgery. Vendored JS (jQuery, Bootstrap, PDF.js, etc.) is also scanned via retire.js. **Embedded JARs** committed into the tree — vendored libs, Spring-Boot fat-jars, shaded uber-jars inside `.jar`/`.war`/`.ear` — are unzipped in-memory and their Maven coordinates scanned too (disable with `--no-jars`). **Committed native binaries** (`.dll`/`.exe`/`.so`/`.dylib`) are detected (magic-byte confirmed, so images/assets are never picked up) and **identified by checksum** via deps.dev + CIRCL — to flag tampered/unknown files and libraries that ought to be declared dependencies (disable with `--no-binaries`).
19
+ - **9 ecosystems in one pass** Maven, npm/Yarn/pnpm, Composer (PHP), PyPI, NuGet, Go, Ruby; plus **vendored JS** (retire.js), committed **native binaries** (`.dll`/`.exe`/`.so`/`.dylib`, identified by checksum via deps.dev + CIRCL) and **embedded JARs** (fat-jars/war/ear, unzipped in-memory).
20
+ - **No build tools** — reads `pom.xml`, `package-lock`/`yarn.lock`/`pnpm-lock`, `composer.lock`, `poetry`/`Pipfile`/`uv`/`pdm` locks, `packages.lock.json`/`*.csproj`, `go.mod`, `Gemfile.lock` directly. No `mvn`/`npm install`/`pip`/`dotnet restore`/`go build`/`bundle`, no `node_modules/`. → [how it stays build-free](docs/COMPARISON.md#how-its-autonomous-no-build-tools)
21
+ - **CVE, merged & prioritised** — CVEProject + OSV.dev + NVD, CPE/version cross-checked to cut false positives, ranked **CISA KEV → EPSS → CVSS**.
22
+ - **Per-module Maven version mediation** — recovers vulnerable transitive versions that a global `<dependencyManagement>` pin hides in another module (lifted Snyk-corroborated coverage **156 → 181** on a real 25-module reactor, finding CVEs a single Snyk scan missed).
23
+ - **Air-gapped / PASSI** — **zero network under `--offline`** (regression-tested), offline Maven transitive resolution, and `--osv-db` for cache-independent offline OSV recall. → [Air-gapped](#air-gapped--passi-audits)
24
+ - **Supply-chain risk** — known-**malicious** advisories (`MAL-`, always block the CI gate) + suspected **typosquats** (`--typosquat`).
25
+ - **Lifecycle** — EOL (endoflife.date), obsolete/deprecated, outdated — across every ecosystem.
26
+ - **Licenses** *(opt-in `--licenses`)* — SPDX-normalised, copyleft/proprietary flagged.
27
+ - **Outputs & CI** — HTML + Word `.doc`, CycloneDX 1.6 SBOM, CSAF 2.0 VEX, SARIF 2.1.0, JSON; gate with `--fail-on`, triage with `--ignore`/`--vex`.
20
28
 
21
- ---
29
+ 📖 **[Usage & all flags](docs/USAGE.md)** · **[Architecture](docs/ARCHITECTURE.md)** · **[Comparison vs other tools](docs/COMPARISON.md)** · **[Data sources](docs/DATA-SOURCES.md)**
22
30
 
23
- ## Why "Autonomous"?
31
+ ## Quick start
24
32
 
25
- Because it doesn't need anything you don't already have on disk:
33
+ ```bash
34
+ npm install -g fad-checker
35
+ fad-checker -s ./my-project # → ./fad-checker-report/cve-report.html
36
+ ```
26
37
 
27
- | You don't need | Why |
28
- | --- | --- |
29
- | Maven installed | `pom.xml` files are parsed directly with xml2js. Properties, profiles and local BOMs are resolved in-process. Transitive deps fetched from Maven Central if `--transitive` (cached forever). |
30
- | `mvn dependency:tree` | Same as above. We walk the tree ourselves. |
31
- | `npm install` / a `node_modules/` | `package-lock.json` (v1/v2/v3), `yarn.lock` (v1 + Berry/v2+) and `pnpm-lock.yaml` (v5/v6/v9) are parsed as text/JSON/YAML. Versions come from the lockfile — no installation. |
32
- | `yarn install` / `pnpm install` | Same. We read `yarn.lock` (v1 + Berry) and `pnpm-lock.yaml` directly. |
33
- | `composer install` | `composer.lock` is parsed directly (concrete versions + transitive). `composer.json` alone → best-effort on pinned versions + warning. |
34
- | `pip` / `poetry` / a venv | `poetry.lock`, `Pipfile.lock`, `uv.lock`, `pdm.lock` are parsed for concrete versions; `pyproject.toml` (PEP 621 + poetry) and `requirements.txt` (following `-r`/`-c` includes) are best-effort on exact pins. Names normalised per PEP 503. |
35
- | `dotnet restore` | `packages.lock.json` is parsed; otherwise `*.csproj`/`*.fsproj`/`*.vbproj` (+ `Directory.Packages.props` Central Package Management) and legacy `packages.config`, best-effort on pinned versions. |
36
- | `go build` / a Go toolchain | `go.mod` is parsed (the full pruned graph on Go ≥1.17, `// indirect` → transitive); `go.sum` is the fallback. No module download. |
37
- | `bundle install` | `Gemfile.lock` is parsed for the resolved gem set. No Ruby, no bundler. |
38
- | `snyk` binary | Built-in CVE matching via CVEProject + OSV + NVD (merged), prioritised with EPSS + CISA KEV (see below). Snyk is *optional* (`--snyk`). |
39
- | A network connection | First run downloads CVE / OSV / EOL data; subsequent runs use cached copies (`--offline` to force). |
38
+ A free [NVD API key](https://nvd.nist.gov/developers/request-an-api-key) (instant) gives 10× faster enrichment: `fad-checker --set-nvd-key YOUR_KEY`. A few common runs — full list via `fad-checker --help` or [docs/USAGE.md](docs/USAGE.md):
40
39
 
41
- Exactly **one** runtime dependencies must be on PATH (or installed automatically through npm): Node ≥ 20. Everything else is bundled or fetched lazily.
40
+ ```bash
41
+ fad-checker -s ./proj -e "^com\.acme\." # exclude private libs (coord regex)
42
+ fad-checker -s ./proj -t ../clean -e "^com\.acme\." --snyk # cleaned POM tree + merge Snyk
43
+ fad-checker -s ./proj --offline # fully offline (zero network)
44
+ fad-checker -s ./proj --osv-db --typosquat # offline-complete OSV + typosquat
45
+ fad-checker -s ./proj --licenses --fail-on high # license chapter + CI gate
46
+ ```
42
47
 
43
- ---
48
+ A single self-contained binary (no Node), from-source install and shell completion are in → [docs/USAGE.md](docs/USAGE.md).
44
49
 
45
50
  ## What it finds
46
51
 
@@ -53,6 +58,7 @@ Exactly **one** runtime dependencies must be on PATH (or installed automatically
53
58
  | **1D. Unmanaged / vendored JavaScript** | [retire.js](https://retirejs.github.io/) (`--verbose`) | **Inventory of every standalone JS lib** committed into the tree (jQuery, Bootstrap, PDF.js, …) that no package manager governs — vulnerable *or not*. A cyber-hygiene constat: unknown provenance/integrity/patch story. `--no-vendored-js-inventory` to skip |
54
59
  | **2. Vendored JS (vulnerable)** | [retire.js](https://retirejs.github.io/) | The subset of the above with known CVEs/advisories — old jQuery/Bootstrap/Angular/PDF.js copies with no lockfile |
55
60
  | **3. CVE in dev deps** | same | Same as chapter 1, but for `test`/`provided` (Maven) and `dev`/`optional`/`peer` (npm) |
61
+ | **Supply-chain risk** | OSV `MAL-…` + name heuristic | **Known-malicious** packages (always block the CI gate, any `--fail-on` level) and **suspected typosquats** (`--typosquat`: an npm/PyPI name one edit from a popular package — `lodahs`↔`lodash`) |
56
62
  | **4. EOL frameworks** | endoflife.date | Spring Boot 2.5, Hibernate 4.x, EOL JDKs, AngularJS, Laravel/Symfony, Django, .NET, etc. |
57
63
  | **5. Obsolete libraries** | curated list (Maven) + registry maintainer flags | log4j 1.x, jackson-mapper-asl, joda-time, …; npm `deprecated`, Composer `abandoned`, PyPI `yanked`/inactive, NuGet `deprecation` |
58
64
  | **6. Outdated libraries** | Maven Central + npm / Packagist / PyPI / NuGet registries | Available newer versions, with release dates |
@@ -61,171 +67,18 @@ Exactly **one** runtime dependencies must be on PATH (or installed automatically
61
67
 
62
68
  The HTML report opens in any browser, contains every detail (CVSS vectors, references, full descriptions, CPE configurations, via-paths for transitives) and ships a Word-compatible `.doc` twin. Every match carries a **composite priority** (KEV-exploited > EPSS likelihood > CVSS severity), and the run can additionally emit a **CycloneDX 1.6 SBOM** (`--report-sbom`, vulnerabilities inline) and a **CSAF 2.0 VEX** (`--report-csaf`) for downstream tooling.
63
69
 
64
- ---
65
-
66
- ## Quick start
67
-
68
- ```bash
69
- npm install -g fad-checker
70
- fad-checker -s ./my-project
71
- ```
72
-
73
- That's it. The report lands in `./fad-checker-report/cve-report.html`.
74
-
75
- Want a 10× faster NVD enrichment? [Get a free NVD API key](https://nvd.nist.gov/developers/request-an-api-key) (instant), then:
76
-
77
- ```bash
78
- fad-checker --set-nvd-key YOUR_KEY
79
- ```
80
-
81
- ---
82
-
83
- ## Common runs
84
-
85
- ```bash
86
- # Read-only full scan (default: all sources on)
87
- fad-checker -s ./proj
88
-
89
- # Exclude private/internal libs by groupId/name regex
90
- fad-checker -s ./proj -e "^(com\.acme|org\.private)\."
91
-
92
- # Ignore whole sub-paths during the walk (gitignore-style glob, relative to -s)
93
- fad-checker -s ./proj --exclude-path "packages/legacy/**" --exclude-path "**/fixtures/**"
94
- fad-checker -s ./proj --no-default-excludes # walk everything (incl. node_modules/.git)
95
-
96
- # Also write cleaned POMs (private deps stripped, ready for Snyk)
97
- fad-checker -s ./proj -t ../proj-clean -e "^com\.acme\."
98
-
99
- # Then run Snyk on the cleaned tree and merge findings
100
- fad-checker -s ./proj -t ../proj-clean -e "^com\.acme\." --snyk
101
-
102
- # Faster: skip Maven Central / no transitive walk
103
- fad-checker -s ./proj --no-all-libs --no-transitive
104
-
105
- # Fully offline (uses cached data only)
106
- fad-checker -s ./proj --offline
107
-
108
- # License + copyleft-policy chapter (off by default)
109
- fad-checker -s ./proj --licenses
110
-
111
- # Pick ecosystems — --ecosystem is a list: auto (default) | all | comma list
112
- fad-checker -s ./proj --ecosystem maven # Maven only
113
- fad-checker -s ./proj --ecosystem maven,npm,pypi # several
114
- fad-checker -s ./proj --no-nuget --no-composer # or opt out per codec
115
- fad-checker -s ./proj --no-binaries # skip the native-binary scan
116
-
117
- # Private registry + reusable defaults (see "Configuration file & environment")
118
- fad-checker --add-repo npm verdaccio https://npm.acme.com/ --token "$NPM_TOKEN"
119
- fad-checker --config ./ci/fad-env.json # all options from a JSON file
120
- fad-checker --source ./proj # --source / --src are aliases
121
- ```
122
-
123
- Run `fad-checker --help` for the full flag list.
124
-
125
- ---
126
-
127
- ## What a report looks like
128
-
129
70
  <p align="center"><img src="docs/assets/report.png" alt="fad-checker HTML report — executive summary with severity tiles and a detailed CVE table with CWE, descriptions and fix versions" width="900"></p>
130
71
 
131
- The console prints a summary; the full detail lives in the self-contained HTML/`.doc`:
132
-
133
- ```
134
- ╭───────────────────────────────────────────────────╮
135
- │ fad-checker v2.2.1 · Autonomous Dependency Checker │
136
- ╰───────────────────────────────────────────────────╯
137
- Executive Summary [CRITICAL] — 1708 dependencies scanned
138
- • 81 CVE in production deps (critical=5, high=53, medium=12, low=11)
139
- • 32 CVE in dev/test deps · 17 vulnerable vendored JS (retire.js)
140
- • 2 EOL frameworks · 13 obsolete/deprecated · 172 outdated
141
- • 4 scan-completeness alerts — see chapter 0
142
- ```
143
-
144
- The HTML report organises this into chapters (0 Warnings, 1 CVE prod, 1B embedded JARs,
145
- 1C native binaries, 1D vendored-JS inventory, 2 vendored-JS vulns, 3 dev CVE, 4 EOL,
146
- 5 obsolete, 6 outdated, 7 Licenses *(opt-in)*, 8 Fix Recommendations), each grouped by
147
- ecosystem and manifest. Every CVE row carries: severity badge · CVE/GHSA id · coord &
148
- version · declaring manifest · source(s) · fix-version · priority (KEV/EPSS) — click for
149
- the full panel (CVSS vectors, references, transitive paths, CPE configs).
150
-
151
- ---
152
-
153
- ## Install
154
-
155
- ### As a global CLI
156
-
157
- ```bash
158
- npm install -g fad-checker
159
- ```
160
-
161
- ### From source
162
-
163
- ```bash
164
- git clone <repo-url> fad-checker
165
- cd fad-checker
166
- npm install
167
- node fad-checker.js --help
168
- ```
169
-
170
- ### Single-binary build (no Node required)
171
-
172
- ```bash
173
- npm install # one-time, brings in bun
174
- npm run build # → dist/fad-checker-linux + dist/fad-checker.exe
175
- ```
176
-
177
- ### Shell completion
178
-
179
- ```bash
180
- fad-checker --completion bash > /etc/bash_completion.d/fad-checker
181
- # or for zsh:
182
- fad-checker --completion zsh > ~/.zsh/completions/_fad-checker
183
- ```
184
-
185
- ---
186
-
187
- ## How it scans without any build tool
188
-
189
- **TL;DR** — `fad-checker` never runs your build. For each ecosystem it reads the **lockfile** (or, failing that, the manifest's pinned versions) straight off disk to learn the exact dependency versions, then matches those coordinates against vulnerability/EOL/registry data over the network (cached, `--offline`-able). No `mvn`, `npm install`, `pip`, `dotnet restore`, `go build` or `bundle` — and no `node_modules/`.
190
-
191
- | Ecosystem | Read directly | Transitive versions come from |
192
- | --- | --- | --- |
193
- | Maven | `pom.xml` (+ parents, BOMs, profiles) | child POMs fetched from Maven Central (cached) |
194
- | npm / Yarn / pnpm | `package-lock.json` · `yarn.lock` (v1+Berry) · `pnpm-lock.yaml` | the lockfile itself |
195
- | Composer | `composer.lock` (else `composer.json`) | the lockfile |
196
- | PyPI | `poetry.lock` · `Pipfile.lock` · `uv.lock` · `pdm.lock` (else `pyproject.toml`/`requirements.txt`) | the lockfile |
197
- | NuGet | `packages.lock.json` (else `*.csproj`/`packages.config`) | the lockfile |
198
- | Go | `go.mod` (`// indirect` → transitive; `go.sum` fallback) | the module graph in `go.mod` |
199
- | Ruby | `Gemfile.lock` (`specs:`) | the lockfile |
200
- | Vendored JS / binaries | the committed `.js` / `.jar` / `.so` files themselves | n/a (read in place) |
201
-
202
- Highlights of the matching layer: **three CVE sources merged** (CVEProject + OSV.dev + NVD), **CPE/version cross-check** to drop false positives, **EPSS + CISA KEV** prioritisation, lockfile-first with a **best-effort pinned-version fallback** when no lockfile, in-memory **embedded-JAR** unzip (no disk, no zip-slip), and **checksum identity** for native binaries. Outputs: HTML + `.doc` by default, plus opt-in `--report-sbom`/`-csaf`/`-json`/`-sarif`; CI gating via `--fail-on` with `--ignore`/`--vex` triage.
203
-
204
- > Full per-ecosystem and per-stage detail lives in **[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)** and **[`docs/USAGE.md`](docs/USAGE.md)**.
205
-
206
- ---
207
-
208
- ## Caching
209
-
210
- All cached data lives in `~/.fad-checker/` — every source is hit at most once per its
211
- TTL (12 h–7 d, transitive POMs forever as they're immutable). Full per-cache TTL table
212
- in [`docs/USAGE.md`](docs/USAGE.md). Export the lot to share between machines:
72
+ ## Air-gapped / PASSI audits
213
73
 
214
- ```bash
215
- fad-checker --export-cache fad-cache.tar.gz
216
- # on the other box:
217
- fad-checker --import-cache fad-cache.tar.gz
218
- ```
219
-
220
- `--include-config` ships the NVD API key too (off by default).
221
-
222
- > The cache export bundles **everything** under `~/.fad-checker/` (except `config.json`),
223
- > including the retire.js findings **and** the warmed retire.js signature DB — so a
224
- > machine that imports it can scan vendored JavaScript fully offline.
225
-
226
- ---
227
-
228
- ## Air-gapped / PASSI audits: anonymized dependency descriptor
74
+ > **Zero-data-sent guarantee.** Under `--offline`, fad-checker makes **no network calls
75
+ > whatsoever** — it reads only the warmed `~/.fad-checker/` caches and never transmits a
76
+ > dependency, path or finding off the machine. It is regression-tested
77
+ > (`test/offline-guarantee.test.js`, a tripwire fetcher that throws if touched) and
78
+ > auditor-reproducible: `unshare -rn node fad-checker.js -s ./proj --offline …` runs it in a
79
+ > namespace with **no network interface** and yields byte-identical findings. Unlike the
80
+ > mainstream OSS scanners, fad also resolves the **Maven transitive graph** offline so on
81
+ > an air-gapped multi-module project it finds the transitive CVEs they can't.
229
82
 
230
83
  When the audited system is **offline / confidential** (typical of a PASSI engagement) it
231
84
  can't reach OSV / NVD / Maven Central / npm. Split the work across machines while keeping
@@ -264,134 +117,16 @@ What the descriptor (`fad-deps/1`) contains vs. drops:
264
117
 
265
118
  The online phase report is itself path-free; vendored-JavaScript (retire.js) findings are
266
119
  produced **offline in phase 3**, since retire needs the actual `.js` files — its signature
267
- DB is warmed online (phase 2) and carried by `--export-cache`.
268
-
269
- ---
270
-
271
- ## Custom repositories & registries
272
-
273
- Register private **Nexus / Artifactory / Verdaccio / devpi / GOPROXY** registries (for **`maven`, `npm`, `pypi`, `ruby`, `go`**) so transitive resolution, outdated/deprecation and license lookups reach them:
274
-
275
- ```bash
276
- fad-checker --add-repo maven nexus https://nexus.acme.com/repository/maven-public/ --auth alice:s3cr3t
277
- fad-checker --add-repo npm verdaccio https://npm.acme.com/ --token "$NPM_TOKEN"
278
- fad-checker -s ./proj --repo npm=https://npm.acme.com/ # one-off, repeatable
279
- ```
280
-
281
- Tried in declared order, public registry last; auth via `--auth user:pass` / `--token TOK` / inline URL; responses cached per coordinate. NuGet/Composer private feeds aren't supported yet. **Full details, the PyPI/Ruby JSON-API caveat and `--list-repos`/`--remove-repo` → [`docs/USAGE.md`](docs/USAGE.md).**
282
-
283
- ---
284
-
285
- ## Configuration file & environment
286
-
287
- Don't retype flags every run. Precedence: **CLI flag > `--config <file.json>` / `./.fad-env.json` (JSON, keys = camelCase options) > `FAD_CHECKER_ENV` (a string of CLI flags) > `~/.fad-checker/config.json` > defaults.** A file/env value only fills an option you didn't pass; **registries are merged across every layer**.
288
-
289
- ```bash
290
- fad-checker --config ./ci/fad-env.json
291
- FAD_CHECKER_ENV='--fail-on high --no-nuget' fad-checker -s ./proj
292
- ```
293
-
294
- `-s` / `--src` / `--source` are aliases. **`-e/--exclude`** is a regex on the **coordinate** (drops deps); **`--exclude-path`** is a gitignore-style glob on the **path** (prunes the walk). Full `.fad-env.json` schema → [`docs/USAGE.md`](docs/USAGE.md).
295
-
296
- ---
297
-
298
- ## Data sources & acknowledgments
299
-
300
- `fad-checker` is glue around several outstanding public datasets. Each is used per its license terms.
301
-
302
- | Source | What we use | License | API / endpoint |
303
- | --- | --- | --- | --- |
304
- | [CVEProject `cvelistV5`](https://github.com/CVEProject/cvelistV5) | Daily bulk CVE bundle, filtered to Maven-relevant entries | CC0-1.0 | GitHub release asset (zip) |
305
- | [OSV.dev](https://osv.dev/) (Google + GitHub Security Lab) | Per-dep vulnerability lookup (Maven, npm, Packagist, PyPI, NuGet, …) | CC-BY 4.0 | `POST api.osv.dev/v1/querybatch`, `GET api.osv.dev/v1/vulns/{id}` |
306
- | [NIST NVD](https://nvd.nist.gov/) | Canonical CVE description + CVSS vectors + CPE configurations + CWE | US-gov public domain | `GET services.nvd.nist.gov/rest/json/cves/2.0?cveId=…` — free [API key](https://nvd.nist.gov/developers/request-an-api-key) bumps the rate limit 10× |
307
- | [FIRST.org EPSS](https://www.first.org/epss/) | Exploit-prediction score + percentile per CVE | CC-BY 4.0 | `GET api.first.org/data/v1/epss?cve=…` (batched) |
308
- | [CISA KEV](https://www.cisa.gov/known-exploited-vulnerabilities-catalog) | Known-exploited-vulnerability catalogue membership | US-gov public domain | `GET cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json` |
309
- | [endoflife.date](https://endoflife.date/) | Framework / runtime EOL cycle data | MIT | `GET endoflife.date/api/{product}.json` |
310
- | [Maven Central](https://search.maven.org/) | Latest-version lookups + transitive POM fetches | Free public service | Solr `search.maven.org/solrsearch/select?q=…` + `repo1.maven.org/maven2/<coord>` |
311
- | [npm registry](https://registry.npmjs.org/) | Per-version `deprecated` + `dist-tags.latest` | Free public service | `GET registry.npmjs.org/<pkg>` |
312
- | [Packagist](https://packagist.org/) | Latest stable + `abandoned` flag | Free public service | `GET packagist.org/packages/<vendor>/<pkg>.json` |
313
- | [PyPI](https://pypi.org/) | Latest + `yanked` + "Inactive" classifier | Free public service | `GET pypi.org/pypi/<pkg>/json` |
314
- | [NuGet](https://www.nuget.org/) | Latest stable + per-version `deprecation` | Free public service | `GET api.nuget.org/v3/registration5-gz-semver2/<id>/index.json` |
315
- | [Go module proxy](https://proxy.golang.org/) | Latest module version (outdated) | Free public service | `GET proxy.golang.org/<module>/@latest` |
316
- | [RubyGems](https://rubygems.org/) | Latest stable + licenses | Free public service | `GET rubygems.org/api/v1/gems/<gem>.json` |
317
- | [deps.dev](https://deps.dev/) | Native-binary identity by checksum (→ package coordinate) | Free public API (CC-BY) | `GET api.deps.dev/v3/query?hash.type=SHA1&hash.value=<base64>` |
318
- | [CIRCL hashlookup](https://hashlookup.circl.lu/) | Known-good file identity (NSRL/distro/CDN) + KnownMalicious | Free public service | `GET hashlookup.circl.lu/lookup/sha256/<hash>` |
319
- | [retire.js](https://retirejs.github.io/retire.js/) | Vendored-JS signature DB + scanner | Apache-2.0 | npm package `retire`, executed locally |
320
- | [Snyk](https://snyk.io/) (optional) | Additional CVE source via `snyk test --all-projects --json` | Per Snyk EULA; needs a Snyk account | Local CLI `snyk` |
321
- | [MITRE CWE](https://cwe.mitre.org/) | Weakness category links in the report | Free public reference | Linked by URL only, no API call |
322
-
323
- Persistent caches mean each source is hit at most once per its TTL (see [Caching](#caching) table). No telemetry, no third-party analytics — every request listed above is made directly to the named endpoint with a `User-Agent: fad-checker-*` header.
324
-
325
- ---
326
-
327
- ## Safety rails
328
-
329
- Built-in guardrails that fire **before** any disk write:
330
-
331
- - `--target` is required unless you're running read-only (no `-t`).
332
- - `--target` may not equal or be a subdirectory of `--src`.
333
- - `--target` is `rimraf`'d before being rewritten — never point it at anything precious.
334
-
335
- ---
336
-
337
- ## Comparison
338
-
339
- `fad-checker` is **not** a Trivy/Grype competitor — those are container-and-SBOM supply-chain
340
- scanners. It targets a narrower job: a **zero-setup, multi-ecosystem audit of a source
341
- checkout, with an audit-ready report and a confidential / air-gapped workflow** — the kind
342
- of thing a security consultant or an ANSSI-PASSI engagement needs.
343
-
344
- | | **fad-checker** | OSV-Scanner | Trivy | Grype + Syft | OWASP DC | Snyk OSS |
345
- | --- | --- | --- | --- | --- | --- | --- |
346
- | Ecosystems it targets¹ | Maven, npm, Yarn, **pnpm**, Composer, PyPI, NuGet, Go, Ruby + vendored JS + **native binaries** | 11+ langs / 19+ lockfiles | 20+ | 20+ | Java/.NET (others exp.) | many |
347
- | Reads lockfiles without `install`/build | ✅ | ✅ | ✅ | ✅ | ⚠️ Java needs Maven Central/build | ❌ build required |
348
- | Best-effort when **no lockfile** (pinned versions) | ✅ | ❌ | ❌ | ❌ | ⚠️ | ⚠️ |
349
- | Vulnerability sources | CVEProject + OSV + NVD + EPSS + KEV + retire.js (+ Snyk), merged | OSV.dev | Aqua DB | Anchore DB | NVD / CPE | Snyk DB |
350
- | False-positive control | CPE/version cross-check | ecosystem-aware | ecosystem-aware | ecosystem-aware | ⚠️ CPE → noisy | ecosystem-aware |
351
- | **EOL** (end-of-life) detection | ✅ endoflife.date | ❌ | ❌ | ❌ | ❌ | ~ |
352
- | **Outdated / deprecated** | ✅ registries + curated | ❌ | ❌ | ❌ | ❌ | ~ |
353
- | Containers / OS packages | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ |
354
- | SBOM (CycloneDX/SPDX) | ✅ CycloneDX 1.6 (+ CSAF 2.0 VEX) | ✅ | ✅ | ✅ (Syft) | ~ | ✅ |
355
- | License compliance | ✅ SPDX + copyleft policy | ~ | ✅ | ~ | ❌ | ✅ |
356
- | EPSS / KEV prioritization | ✅ FIRST.org EPSS + CISA KEV | ~ | ✅ | ✅ | ❌ | ✅ |
357
- | CI gating (`--fail-on`) + triage | ✅ severity/KEV + ignore/VEX | ✅ | ✅ | ✅ | ⚠️ | ✅ |
358
- | Auto-remediation / PRs | ❌ (fix recipes only) | ✅ `fix` | ❌ | ❌ | ❌ | ✅ |
359
- | Offline | ✅ cache | ✅ local DB | ✅ | ✅ | ✅ feed | ❌ mostly online |
360
- | **Scan without exposing the codebase**² | ✅ anonymized descriptor | ❌ | ❌ | ❌ | ❌ | ❌ |
361
- | **Maven private-dep cleanup** (→ Snyk) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
362
- | Output | **HTML + Word `.doc`** + JSON / SARIF / CycloneDX / CSAF | table/JSON/SARIF | table/JSON/SARIF | table/JSON/SARIF | HTML/XML/JSON | JSON / cloud UI |
363
-
364
- ¹ Narrower language coverage — no Rust/Dart/Swift (Go and Ruby are now covered).
365
-
366
- ² Phase 1 exports only public coordinates; the online scan never sees your source tree —
367
- see [Air-gapped / PASSI](#air-gapped--passi-audits-anonymized-dependency-descriptor). OSV-Scanner
368
- has an offline mode, but it still needs the **source on the scanning machine**.
369
-
370
- **Where it fits:** a one-shot audit of a polyglot checkout you may not be able to build, a
371
- presentable HTML/Word deliverable, and confidential / air-gapped engagements.
372
-
373
- **Where it doesn't:** container/OS scanning,
374
- reachability analysis, auto-fix PRs — reach for **Trivy** or **Grype + Syft**.
375
-
376
- You don't have to choose — `fad-checker` takes Snyk's results as input (`--snyk`) and merges them.
377
-
378
- > Sources: [OSV-Scanner lockfiles](https://google.github.io/osv-scanner/supported-languages-and-lockfiles/) ·
379
- > [Trivy Java/`pom.xml` (Maven Central, `--offline-scan`)](https://trivy.dev/docs/latest/coverage/language/java/) ·
380
- > [Syft `java-pom-cataloger` (source dirs)](https://github.com/anchore/syft/issues/676) ·
381
- > [OWASP DC needs internet/build for Java](https://jeremylong.github.io/DependencyCheck/data/index.html) ·
382
- > [Snyk requires building the project](https://docs.snyk.io/supported-languages/technical-specifications-and-guidance) ·
383
- > [EOL/outdated "most tools skip" (Aikido)](https://www.aikido.dev/code/outdated-eol-software)
384
-
385
- ---
120
+ DB is warmed online (phase 2) and carried by `--export-cache`. Full offline/cache control →
121
+ [`docs/USAGE.md`](docs/USAGE.md).
386
122
 
387
123
  ## Docs
388
124
 
389
- - [`docs/USAGE.md`](docs/USAGE.md) — every flag, every workflow, examples.
125
+ - [`docs/USAGE.md`](docs/USAGE.md) — every flag and workflow: offline/cache control, private registries, config files, recipes, safety rails.
390
126
  - [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) — internals: codecs, collection, matching, report pipeline.
391
- - [`CHANGELOG.md`](CHANGELOG.md) — release history.
392
- - [`CLAUDE.md`](CLAUDE.md) — code-level orientation for contributors.
393
-
394
- ---
127
+ - [`docs/COMPARISON.md`](docs/COMPARISON.md) — vs OSV-Scanner / Trivy / Grype / OWASP DC / Snyk, and how it stays build-free.
128
+ - [`docs/DATA-SOURCES.md`](docs/DATA-SOURCES.md) — the public datasets fad-checker uses + their licenses.
129
+ - [`CHANGELOG.md`](CHANGELOG.md) · [`CLAUDE.md`](CLAUDE.md) — release history · code-level orientation for contributors.
395
130
 
396
131
  ## License
397
132
 
@@ -0,0 +1,39 @@
1
+ {
2
+ "_comment": "Curated list of widely-used npm + PyPI packages used as TYPOSQUAT TARGETS by lib/malware.js#detectTyposquats. A resolved dep whose name is edit-distance 1 from one of these (but isn't it) is flagged as a suspected typosquat/slopsquat. Not exhaustive — focused on the most-impersonated packages. Add names here to widen proactive coverage.",
3
+ "npm": [
4
+ "react", "react-dom", "lodash", "express", "axios", "chalk", "commander", "request",
5
+ "debug", "async", "moment", "underscore", "jquery", "webpack", "babel-core", "vue",
6
+ "angular", "typescript", "eslint", "prettier", "jest", "mocha", "chai", "bluebird",
7
+ "yargs", "glob", "minimist", "colors", "dotenv", "body-parser", "cors", "mongoose",
8
+ "redux", "next", "gatsby", "rxjs", "classnames", "prop-types", "uuid", "bcrypt",
9
+ "jsonwebtoken", "passport", "nodemon", "node-fetch", "cross-env", "rimraf", "fs-extra",
10
+ "semver", "validator", "winston", "morgan", "helmet", "sequelize", "knex", "mysql",
11
+ "redis", "ioredis", "dayjs", "date-fns", "ramda", "immutable", "styled-components",
12
+ "tailwindcss", "postcss", "autoprefixer", "browserify", "rollup", "parcel", "esbuild",
13
+ "husky", "lint-staged", "concurrently", "nanoid", "form-data", "tslib", "core-js",
14
+ "socket.io", "nodemailer", "puppeteer", "playwright", "cheerio", "inquirer", "ora",
15
+ "execa", "chokidar", "ws", "got", "undici", "zod", "yup", "formik", "react-router",
16
+ "react-router-dom", "react-redux", "vuex", "pinia", "svelte", "preact", "ant-design",
17
+ "bootstrap", "jquery-ui", "three", "d3", "chart.js", "leaflet", "highlight.js",
18
+ "marked", "handlebars", "ejs", "pug", "nunjucks", "graphql", "apollo-server",
19
+ "mongodb", "pg", "typeorm", "prisma", "jsdom", "supertest", "sinon", "nyc", "ava",
20
+ "babel-loader", "ts-loader", "css-loader", "style-loader", "html-webpack-plugin",
21
+ "colord"
22
+ ],
23
+ "pypi": [
24
+ "requests", "numpy", "pandas", "flask", "django", "scipy", "matplotlib", "pillow",
25
+ "setuptools", "wheel", "urllib3", "certifi", "pytest", "six", "click", "jinja2",
26
+ "werkzeug", "sqlalchemy", "boto3", "botocore", "pyyaml", "cryptography",
27
+ "beautifulsoup4", "lxml", "scikit-learn", "tensorflow", "keras", "scrapy",
28
+ "celery", "psycopg2", "pymongo", "fastapi", "uvicorn", "pydantic", "aiohttp",
29
+ "httpx", "tqdm", "colorama", "rich", "typer", "python-dateutil", "pytz", "attrs",
30
+ "packaging", "idna", "charset-normalizer", "markupsafe", "openpyxl", "tornado",
31
+ "gunicorn", "redis", "selenium", "paramiko", "fabric", "pyjwt", "passlib", "bcrypt",
32
+ "marshmallow", "flask-sqlalchemy", "flask-cors", "flask-restful", "django-rest-framework",
33
+ "djangorestframework", "gevent", "greenlet", "asgiref", "starlette", "anyio",
34
+ "httpcore", "sniffio", "wrapt", "decorator", "more-itertools", "toml", "tomli",
35
+ "pyparsing", "jsonschema", "protobuf", "grpcio", "boto", "awscli", "google-api-python-client",
36
+ "opencv-python", "imageio", "networkx", "sympy", "statsmodels", "seaborn", "plotly",
37
+ "dash", "streamlit", "jupyter", "ipython", "notebook", "nbconvert", "cython"
38
+ ]
39
+ }
package/fad-checker.js CHANGED
@@ -236,7 +236,10 @@ program
236
236
  .option("--ignore-test", "skip test-scoped dependencies in report")
237
237
  .option("--cve-refresh", "force re-download of CVE database")
238
238
  .option("--cve-offline", "use cached CVE index only (no download)")
239
+ .option("--osv-db", "import + match the full local OSV database (Maven) — offline-complete recall, independent of the per-dep OSV cache")
240
+ .option("--osv-db-refresh", "force re-download of the local OSV database")
239
241
  .option("--snyk", "run snyk on cleaned POMs and merge into report (requires --target)")
242
+ .option("--typosquat", "flag npm/PyPI deps whose name is one edit from a popular package (heuristic typosquat/slopsquat detection)")
240
243
  .option("--no-retire", "skip retire.js vendored-JS scan")
241
244
  .option("--no-vendored-js-inventory", "don't list ALL identified vendored JS libs (chapter 1D) — keep only the vulnerable ones (chapter 2)")
242
245
  .option("--retire-refresh", "ignore retire cache and re-scan")
@@ -750,6 +753,9 @@ async function runReportFlow(resolved, ecoFlags = {}) {
750
753
  ? require("./lib/maven-bom").collectImportBoms(mavenPropsByPom) : [];
751
754
  const willBom = importBoms.length > 0;
752
755
  const willOsv = !!options.osv;
756
+ // Local OSV DB import (Maven): offline-complete OSV recall, independent of the per-dep
757
+ // OSV.dev cache. Opt-in (downloads ~9 MB once); then matches online or offline.
758
+ const willOsvDb = !!options.osvDb && runMaven;
753
759
  const willOutdated = !!options.allLibs;
754
760
  const willNvd = !!options.nvd;
755
761
  const willEpss = !!options.epss;
@@ -760,7 +766,7 @@ async function runReportFlow(resolved, ecoFlags = {}) {
760
766
  const willBinaryId = [...resolved.values()].some(d => d.provenance === "binary");
761
767
  // License detection piggybacks on the registry passes (same fetched metadata),
762
768
  // so it adds no progress step of its own.
763
- const totalSteps = [willBom, willTransitive, willOverlay, willCve, /*EOL*/ true, willOutdated, /*npm reg*/ true, ...otherRegistryIds.map(() => true), willOsv, willNvd, willEpss, willKev, willRetire, willBinaryId].filter(Boolean).length;
769
+ const totalSteps = [willBom, willTransitive, willOverlay, willCve, /*EOL*/ true, willOutdated, /*npm reg*/ true, ...otherRegistryIds.map(() => true), willOsv, willOsvDb, willNvd, willEpss, willKev, willRetire, willBinaryId].filter(Boolean).length;
764
770
  const progress = new ui.Progress(totalSteps);
765
771
 
766
772
  if (willBom) {
@@ -912,6 +918,25 @@ async function runReportFlow(resolved, ecoFlags = {}) {
912
918
  }
913
919
  }
914
920
 
921
+ // 4b'. Local OSV database (Maven) — offline-COMPLETE recall. The per-dep OSV.dev
922
+ // queries above only cover deps cached online; the imported full OSV DB matches every
923
+ // dep offline, deterministically, regardless of cache warmth (the OSV-Scanner model).
924
+ if (willOsvDb) {
925
+ const st = progress.start("OSV database (local, Maven)");
926
+ try {
927
+ const { ensureOsvDb, matchOsvDbDeps } = require("./lib/osv-db");
928
+ const index = await ensureOsvDb({ offline, refresh: !!options.osvDbRefresh, verbose, ecosystem: "maven" });
929
+ if (!index) {
930
+ st.done(offline ? "no local OSV DB (run once online with --osv-db)" : "unavailable");
931
+ } else {
932
+ const dbMatches = matchOsvDbDeps(resolved, index);
933
+ const before = cveMatches.length;
934
+ cveMatches = mergeBySource(cveMatches, dbMatches);
935
+ st.done(`${index.count} advisories · ${dbMatches.length} matched · +${cveMatches.length - before} new`);
936
+ }
937
+ } catch (err) { st.fail(err.message); }
938
+ }
939
+
915
940
  // 4c. NVD enrichment — canonical description + full CVSS for matched CVEs.
916
941
  if (willNvd) {
917
942
  const st = progress.start("NVD enrichment");
@@ -1019,6 +1044,22 @@ async function runReportFlow(resolved, ecoFlags = {}) {
1019
1044
  }
1020
1045
  }
1021
1046
 
1047
+ // 6b. Supply-chain risk lane (pure + offline): flag KNOWN-MALICIOUS advisories
1048
+ // (OSV MAL-… already in the match set) always, and detect suspected TYPOSQUATS
1049
+ // (opt-in --typosquat, heuristic — names one edit from a popular package).
1050
+ const { flagMalicious, detectTyposquats } = require("./lib/malware");
1051
+ const maliciousCount = flagMalicious(cveMatches);
1052
+ const typosquats = options.typosquat ? detectTyposquats(resolved) : [];
1053
+ if (maliciousCount || typosquats.length) {
1054
+ ui.section("Supply-chain risk");
1055
+ if (maliciousCount) ui.warn(chalk.red.bold(`${maliciousCount} KNOWN-MALICIOUS package advisory(ies) (MAL-…) — treat the build as compromised`));
1056
+ if (typosquats.length) {
1057
+ ui.warn(`${typosquats.length} suspected typosquat(s) — heuristic, verify each is intentional:`);
1058
+ for (const t of typosquats.slice(0, 15)) console.log(" " + chalk.yellow(t.name) + chalk.dim(` (${t.ecosystem}) ≈ ${t.resembles}`));
1059
+ if (typosquats.length > 15) console.log(chalk.dim(` …and ${typosquats.length - 15} more (see JSON export)`));
1060
+ }
1061
+ }
1062
+
1022
1063
  // Split prod vs dev based on the dep's isDev flag (set at collection time
1023
1064
  // from Maven scope=test/provided and npm dev/devOptional/optional). Keep the
1024
1065
  // full per-bucket list (including cpeFiltered) so the HTML report can render
@@ -1253,7 +1294,7 @@ async function runReportFlow(resolved, ecoFlags = {}) {
1253
1294
  try {
1254
1295
  const { writeFindings } = require("./lib/json-export");
1255
1296
  await ensureDir(out.json);
1256
- writeFindings({ cveMatches, retireMatches, vendoredJsInventory, eolResults, obsoleteResults, outdatedResults, licenseResults, resolvedDeps: resolved, projectInfo, toolVersion: pkg.version }, out.json);
1297
+ writeFindings({ cveMatches, retireMatches, vendoredJsInventory, eolResults, obsoleteResults, outdatedResults, licenseResults, resolvedDeps: resolved, projectInfo, toolVersion: pkg.version, typosquats }, out.json);
1257
1298
  wrote.push(["Findings JSON", out.json]);
1258
1299
  } catch (err) { ui.warn(`JSON export failed: ${err.message}`); }
1259
1300
  }
@@ -1280,9 +1321,12 @@ async function runReportFlow(resolved, ecoFlags = {}) {
1280
1321
  const { evaluateGate } = require("./lib/gate");
1281
1322
  // Embedded-binary findings are real production risk → gate on them too.
1282
1323
  const gate = evaluateGate([...prodActive, ...embeddedActive], options.failOn);
1283
- if (gate.failed) {
1324
+ // A known-malicious package always blocks, regardless of the --fail-on level.
1325
+ const maliciousActive = [...prodActive, ...embeddedActive].filter(m => m.malicious);
1326
+ if (gate.failed || maliciousActive.length) {
1284
1327
  ui.section("Gate");
1285
- console.log(chalk.red(`✗ --fail-on ${options.failOn}: ${gate.reason}`));
1328
+ if (maliciousActive.length) console.log(chalk.red.bold(`✗ ${maliciousActive.length} known-malicious package(s) detected — always blocks`));
1329
+ if (gate.failed) console.log(chalk.red(`✗ --fail-on ${options.failOn}: ${gate.reason}`));
1286
1330
  process.exitCode = 1;
1287
1331
  } else if (verbose) {
1288
1332
  ui.info(chalk.dim(`--fail-on ${options.failOn}: no blocking finding`));