fad-checker 1.0.0 → 1.0.1
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/CLAUDE.md +21 -21
- package/README.md +37 -37
- package/completions/{fad-check.bash → fad-checker.bash} +3 -3
- package/completions/{fad-check.zsh → fad-checker.zsh} +2 -2
- package/docs/ARCHITECTURE.md +10 -10
- package/docs/USAGE.md +35 -35
- package/{fad-check.js → fad-checker.js} +19 -19
- package/lib/cache-archive.js +9 -9
- package/lib/config.js +3 -3
- package/lib/cve-download.js +3 -3
- package/lib/cve-report.js +4 -4
- package/lib/maven-repo.js +3 -3
- package/lib/nvd.js +5 -5
- package/lib/osv.js +6 -6
- package/lib/outdated.js +5 -5
- package/lib/retire.js +5 -5
- package/lib/scan-completeness.js +2 -2
- package/lib/snyk.js +4 -4
- package/lib/transitive.js +3 -3
- package/package.json +6 -6
- package/test/core.test.js +4 -4
- package/test/cve-report.test.js +1 -1
- package/test/monorepo.test.js +1 -1
- package/test/snyk.test.js +1 -1
- package/test/transitive.test.js +2 -2
package/CLAUDE.md
CHANGED
|
@@ -4,7 +4,7 @@ Code-level orientation for contributors and Claude Code sessions on this repo.
|
|
|
4
4
|
|
|
5
5
|
## What this is
|
|
6
6
|
|
|
7
|
-
`fad-
|
|
7
|
+
`fad-checker` — **Fucking Autonomous Dependency Checker**. Node.js CLI (`fad-checker`, or short alias `fad`) that:
|
|
8
8
|
|
|
9
9
|
1. Walks a multi-module Maven tree, removes private/excluded dependencies (regex on groupId), writes a parallel directory of "cleaned" POMs that can be fed to Snyk.
|
|
10
10
|
2. Walks every JS package (`package.json` + `package-lock.json` v1/v2/v3 or `yarn.lock` v1) in the same source tree.
|
|
@@ -27,9 +27,9 @@ npm install
|
|
|
27
27
|
npm test # 96 unit tests via node --test
|
|
28
28
|
|
|
29
29
|
# basic cleanup workflow
|
|
30
|
-
node fad-
|
|
31
|
-
node fad-
|
|
32
|
-
node fad-
|
|
30
|
+
node fad-checker.js -s ./proj # read-only, full report
|
|
31
|
+
node fad-checker.js -s ./proj -t ../pom-clean -e "^client\\." # write cleaned tree
|
|
32
|
+
node fad-checker.js -s ./proj -t ../pom-clean -e "^client\\." --snyk # also drive snyk
|
|
33
33
|
|
|
34
34
|
# read the full usage doc
|
|
35
35
|
cat docs/USAGE.md
|
|
@@ -38,8 +38,8 @@ cat docs/USAGE.md
|
|
|
38
38
|
Binary builds (requires `bun`):
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
npm run build:linux # → dist/fad-
|
|
42
|
-
npm run build:win # → dist/fad-
|
|
41
|
+
npm run build:linux # → dist/fad-checker-linux
|
|
42
|
+
npm run build:win # → dist/fad-checker.exe
|
|
43
43
|
npm run build # both
|
|
44
44
|
```
|
|
45
45
|
|
|
@@ -51,7 +51,7 @@ Guardrails enforced at startup:
|
|
|
51
51
|
## Architecture (one-liner per file)
|
|
52
52
|
|
|
53
53
|
```
|
|
54
|
-
fad-
|
|
54
|
+
fad-checker.js Thin CLI: commander parsing, orchestration only.
|
|
55
55
|
lib/core.js POM parsing, parent resolution, all-profile merge, rewrite.
|
|
56
56
|
lib/maven-version.js Maven version parsing + range comparison (no external deps).
|
|
57
57
|
lib/cve-download.js Bulk download of CVEProject/cvelistV5 + Maven-relevant index build.
|
|
@@ -64,13 +64,13 @@ lib/osv.js OSV.dev batched query + per-vuln detail fetch.
|
|
|
64
64
|
lib/nvd.js NIST NVD enrichment (CVSS, references, CPE configurations).
|
|
65
65
|
lib/snyk.js `snyk test --all-projects --json` runner + merge.
|
|
66
66
|
lib/retire.js retire.js (vendored-JS scanner) wrapper + cache + normaliser.
|
|
67
|
-
lib/scan-completeness.js Warnings for deps fad-
|
|
67
|
+
lib/scan-completeness.js Warnings for deps fad-checker couldn't fully resolve.
|
|
68
68
|
lib/npm/parse.js package.json, package-lock.json (v1/2/3), yarn.lock v1 parsers.
|
|
69
69
|
lib/npm/collect.js Merge across JS manifests → unified resolvedDeps Map.
|
|
70
|
-
lib/cache-archive.js tar.gz / zip export & import of ~/.fad-
|
|
71
|
-
lib/config.js Persistent user config in ~/.fad-
|
|
70
|
+
lib/cache-archive.js tar.gz / zip export & import of ~/.fad-checker/.
|
|
71
|
+
lib/config.js Persistent user config in ~/.fad-checker/config.json (mode 0600).
|
|
72
72
|
data/ known-obsolete.json, eol-mapping.json, cpe-coord-map.json, known-public-namespaces.json
|
|
73
|
-
completions/ fad-
|
|
73
|
+
completions/ fad-checker.bash, fad-checker.zsh
|
|
74
74
|
test/ node:test suite + fixtures (simple, complex-enterprise, monorepo-mixed, …).
|
|
75
75
|
```
|
|
76
76
|
|
|
@@ -104,8 +104,8 @@ Test fixtures live in `test/fixtures/`:
|
|
|
104
104
|
## Gotchas / edge cases worth knowing
|
|
105
105
|
|
|
106
106
|
- CVE bundle from CVEProject is ~500 MB unpacked. Shells out to `curl + unzip` (fallback to `fetch()` + `unzip` / `Expand-Archive`). Extracted JSON deleted after index build. Ships as `cves.zip.zip` (nested zip) — `extractZip()` recurses up to 3 levels.
|
|
107
|
-
- `endoflife.date` API responses cached 7 days; Maven Central version lookups cached 24 hours. Cache lives in `~/.fad-
|
|
108
|
-
- **Persistent config**: `~/.fad-
|
|
107
|
+
- `endoflife.date` API responses cached 7 days; Maven Central version lookups cached 24 hours. Cache lives in `~/.fad-checker/`.
|
|
108
|
+
- **Persistent config**: `~/.fad-checker/config.json` (mode 0600). Set NVD key via `fad-checker --set-nvd-key <KEY>` (free, instant from <https://nvd.nist.gov/developers/request-an-api-key> — bumps rate limit from 5/30s to 50/30s).
|
|
109
109
|
- **`--offline` umbrella flag**: skips every network call (CVE/OSV/NVD/Maven Central/endoflife/retire). Falls back to whatever is already cached. Per-source variants (`--cve-offline`, `--no-osv`, `--no-nvd`, `--no-retire`, `--no-transitive`) still work independently.
|
|
110
110
|
- `snyk` is not a hard dep — shells out via `execFile`. `snyk` exits 1 on findings; the JSON is still on stdout.
|
|
111
111
|
- The cleaned POM is the union of every profile's deps. Counts will be larger than the source POM. Intentional — don't "fix" that.
|
|
@@ -116,11 +116,11 @@ Test fixtures live in `test/fixtures/`:
|
|
|
116
116
|
|
|
117
117
|
| Cache | Location | TTL |
|
|
118
118
|
|---|---|---|
|
|
119
|
-
| CVEProject bulk index | `~/.fad-
|
|
120
|
-
| OSV per-dep stub list | `~/.fad-
|
|
121
|
-
| OSV vuln details | `~/.fad-
|
|
122
|
-
| NVD CVE record | `~/.fad-
|
|
123
|
-
| endoflife.date cycles | `~/.fad-
|
|
124
|
-
| Maven Central latest | `~/.fad-
|
|
125
|
-
| Transitive POM | `~/.fad-
|
|
126
|
-
| retire.js findings | `~/.fad-
|
|
119
|
+
| CVEProject bulk index | `~/.fad-checker/cve-data/maven-cve-index.json` | 24 h |
|
|
120
|
+
| OSV per-dep stub list | `~/.fad-checker/osv-cache/<eco>__<g>__<a>__<v>.json` | 12 h |
|
|
121
|
+
| OSV vuln details | `~/.fad-checker/osv-cache/vuln_<id>.json` | 12 h |
|
|
122
|
+
| NVD CVE record | `~/.fad-checker/nvd-cache/<cveId>.json` | 7 d |
|
|
123
|
+
| endoflife.date cycles | `~/.fad-checker/eol-cache.json` | 7 d |
|
|
124
|
+
| Maven Central latest | `~/.fad-checker/version-cache.json` | 24 h |
|
|
125
|
+
| Transitive POM | `~/.fad-checker/poms-cache/<g>__<a>__<v>.pom` | ∞ (immutable on Maven Central) |
|
|
126
|
+
| retire.js findings | `~/.fad-checker/retire-cache/<md5(src)>.json` | 24 h |
|
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# fad-
|
|
1
|
+
# fad-checker
|
|
2
2
|
|
|
3
3
|
> **F**ucking **A**utonomous **D**ependency **C**hecker
|
|
4
4
|
|
|
5
|
-
`fad-
|
|
5
|
+
`fad-checker` scans **Maven**, **npm**, **Yarn** and **vendored JavaScript** in any source tree — multi-module, monorepo, polyglot, whatever you've got — and produces a single self-contained HTML report with CVE, EOL, obsolete and outdated findings, plus per-ecosystem fix recipes.
|
|
6
6
|
|
|
7
7
|
It runs against the source files alone. **No `mvn`, no `npm install`, no `yarn`, no Docker.** It reads `pom.xml`, `package-lock.json` and `yarn.lock` directly.
|
|
8
8
|
|
|
@@ -47,16 +47,16 @@ The HTML report opens in any browser, contains every detail (CVSS vectors, refer
|
|
|
47
47
|
## Quick start
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
npm install -g fad-
|
|
51
|
-
fad-
|
|
50
|
+
npm install -g fad-checker
|
|
51
|
+
fad-checker -s ./my-project
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
That's it. The report lands in `./fad-
|
|
54
|
+
That's it. The report lands in `./fad-checker-report/cve-report.html`.
|
|
55
55
|
|
|
56
56
|
Want a 10× faster NVD enrichment? [Get a free NVD API key](https://nvd.nist.gov/developers/request-an-api-key) (instant), then:
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
fad-
|
|
59
|
+
fad-checker --set-nvd-key YOUR_KEY
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
---
|
|
@@ -65,28 +65,28 @@ fad-check --set-nvd-key YOUR_KEY
|
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
67
|
# Read-only full scan (default: all sources on)
|
|
68
|
-
fad-
|
|
68
|
+
fad-checker -s ./proj
|
|
69
69
|
|
|
70
70
|
# Exclude private/internal libs by groupId regex
|
|
71
|
-
fad-
|
|
71
|
+
fad-checker -s ./proj -e "^(com\.acme|org\.private)\."
|
|
72
72
|
|
|
73
73
|
# Also write cleaned POMs (private deps stripped, ready for Snyk)
|
|
74
|
-
fad-
|
|
74
|
+
fad-checker -s ./proj -t ../proj-clean -e "^com\.acme\."
|
|
75
75
|
|
|
76
76
|
# Then run Snyk on the cleaned tree and merge findings
|
|
77
|
-
fad-
|
|
77
|
+
fad-checker -s ./proj -t ../proj-clean -e "^com\.acme\." --snyk
|
|
78
78
|
|
|
79
79
|
# Faster: skip Maven Central / no transitive walk
|
|
80
|
-
fad-
|
|
80
|
+
fad-checker -s ./proj --no-all-libs --no-transitive
|
|
81
81
|
|
|
82
82
|
# Fully offline (uses cached data only)
|
|
83
|
-
fad-
|
|
83
|
+
fad-checker -s ./proj --offline
|
|
84
84
|
|
|
85
85
|
# Only one ecosystem
|
|
86
|
-
fad-
|
|
86
|
+
fad-checker -s ./proj --ecosystem maven # or npm | both | auto (default)
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
Run `fad-
|
|
89
|
+
Run `fad-checker --help` for the full flag list.
|
|
90
90
|
|
|
91
91
|
---
|
|
92
92
|
|
|
@@ -131,38 +131,38 @@ Each CVE row shows: severity badge · CVE / GHSA id · dep coord & version · wh
|
|
|
131
131
|
### As a global CLI
|
|
132
132
|
|
|
133
133
|
```bash
|
|
134
|
-
npm install -g fad-
|
|
134
|
+
npm install -g fad-checker
|
|
135
135
|
```
|
|
136
136
|
|
|
137
137
|
### From source
|
|
138
138
|
|
|
139
139
|
```bash
|
|
140
|
-
git clone <repo-url> fad-
|
|
141
|
-
cd fad-
|
|
140
|
+
git clone <repo-url> fad-checker
|
|
141
|
+
cd fad-checker
|
|
142
142
|
npm install
|
|
143
|
-
node fad-
|
|
143
|
+
node fad-checker.js --help
|
|
144
144
|
```
|
|
145
145
|
|
|
146
146
|
### Single-binary build (no Node required)
|
|
147
147
|
|
|
148
148
|
```bash
|
|
149
149
|
npm install # one-time, brings in bun
|
|
150
|
-
npm run build # → dist/fad-
|
|
150
|
+
npm run build # → dist/fad-checker-linux + dist/fad-checker.exe
|
|
151
151
|
```
|
|
152
152
|
|
|
153
153
|
### Shell completion
|
|
154
154
|
|
|
155
155
|
```bash
|
|
156
|
-
fad-
|
|
156
|
+
fad-checker --completion bash > /etc/bash_completion.d/fad-checker
|
|
157
157
|
# or for zsh:
|
|
158
|
-
fad-
|
|
158
|
+
fad-checker --completion zsh > ~/.zsh/completions/_fad-checker
|
|
159
159
|
```
|
|
160
160
|
|
|
161
161
|
---
|
|
162
162
|
|
|
163
163
|
## How it scans without any build tool
|
|
164
164
|
|
|
165
|
-
This is the surprising bit. The whole point is that you can run `fad-
|
|
165
|
+
This is the surprising bit. The whole point is that you can run `fad-checker` against a *checkout* with no build environment.
|
|
166
166
|
|
|
167
167
|
- **Maven** — `pom.xml` files are parsed with xml2js. Property substitution (`${jackson.version}`), parent inheritance, local BOM imports (`<scope>import</scope>`) and every profile are resolved in-process. Transitive deps are walked by fetching child POMs from Maven Central (cached forever — POMs are immutable). When the project uses an **external BOM** (`spring-boot-dependencies` etc.), the deps whose version comes from that BOM can't be resolved without `mvn` itself — those are surfaced in chapter 0 as "unresolved-versions" so you know what's missing.
|
|
168
168
|
- **npm / Yarn** — `package-lock.json` (v1, v2, v3) and `yarn.lock` v1 are parsed directly. Lockfiles already contain every transitive version. No `node_modules/` traversal, no `npm install`. A package.json *without* a sibling lockfile is intentionally skipped (its ranges aren't queryable) and reported in chapter 0.
|
|
@@ -177,7 +177,7 @@ This is the surprising bit. The whole point is that you can run `fad-check` agai
|
|
|
177
177
|
|
|
178
178
|
## Caching
|
|
179
179
|
|
|
180
|
-
All cached data lives in `~/.fad-
|
|
180
|
+
All cached data lives in `~/.fad-checker/`:
|
|
181
181
|
|
|
182
182
|
| Cache | Path | TTL |
|
|
183
183
|
| --- | --- | --- |
|
|
@@ -194,9 +194,9 @@ All cached data lives in `~/.fad-check/`:
|
|
|
194
194
|
Export the lot to share between machines:
|
|
195
195
|
|
|
196
196
|
```bash
|
|
197
|
-
fad-
|
|
197
|
+
fad-checker --export-cache fad-cache.tar.gz
|
|
198
198
|
# on the other box:
|
|
199
|
-
fad-
|
|
199
|
+
fad-checker --import-cache fad-cache.tar.gz
|
|
200
200
|
```
|
|
201
201
|
|
|
202
202
|
`--include-config` ships the NVD API key too (off by default).
|
|
@@ -205,19 +205,19 @@ fad-check --import-cache fad-cache.tar.gz
|
|
|
205
205
|
|
|
206
206
|
## Custom Maven repositories
|
|
207
207
|
|
|
208
|
-
Out of the box `fad-
|
|
208
|
+
Out of the box `fad-checker` queries Maven Central for transitive POMs and latest versions. If your project depends on artifacts that live on a private Nexus / Artifactory / JBoss repo, add them so transitive resolution and outdated checks work end-to-end.
|
|
209
209
|
|
|
210
210
|
```bash
|
|
211
|
-
# Persist a repo (lives in ~/.fad-
|
|
212
|
-
fad-
|
|
213
|
-
fad-
|
|
214
|
-
fad-
|
|
215
|
-
fad-
|
|
211
|
+
# Persist a repo (lives in ~/.fad-checker/config.json)
|
|
212
|
+
fad-checker --add-repo nexus https://nexus.acme.com/repository/maven-public/
|
|
213
|
+
fad-checker --add-repo nexus-priv https://nexus.acme.com/repository/maven-private/ --auth alice:s3cr3t
|
|
214
|
+
fad-checker --list-repos
|
|
215
|
+
fad-checker --remove-repo nexus-priv
|
|
216
216
|
|
|
217
217
|
# One-off (not persisted) — repeatable
|
|
218
|
-
fad-
|
|
218
|
+
fad-checker -s ./proj --repo https://nexus.acme.com/repository/maven-public/
|
|
219
219
|
# Inline auth in the URL also works:
|
|
220
|
-
fad-
|
|
220
|
+
fad-checker -s ./proj --repo https://alice:s3cr3t@nexus.acme.com/repository/maven-public/
|
|
221
221
|
```
|
|
222
222
|
|
|
223
223
|
Repos are tried **in declared order, Maven Central last**. Auth is sent as a `Basic <base64>` header. POMs and `maven-metadata.xml` are cached per coord, so subsequent runs are free even against a private repo.
|
|
@@ -226,7 +226,7 @@ Repos are tried **in declared order, Maven Central last**. Auth is sent as a `Ba
|
|
|
226
226
|
|
|
227
227
|
## Data sources & acknowledgments
|
|
228
228
|
|
|
229
|
-
`fad-
|
|
229
|
+
`fad-checker` is glue around several outstanding public datasets. Each is used per its license terms.
|
|
230
230
|
|
|
231
231
|
| Source | What we use | License | API / endpoint |
|
|
232
232
|
| --- | --- | --- | --- |
|
|
@@ -239,7 +239,7 @@ Repos are tried **in declared order, Maven Central last**. Auth is sent as a `Ba
|
|
|
239
239
|
| [Snyk](https://snyk.io/) (optional) | Additional CVE source via `snyk test --all-projects --json` | Per Snyk EULA; needs a Snyk account | Local CLI `snyk` |
|
|
240
240
|
| [MITRE CWE](https://cwe.mitre.org/) | Weakness category links in the report | Free public reference | Linked by URL only, no API call |
|
|
241
241
|
|
|
242
|
-
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-
|
|
242
|
+
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.
|
|
243
243
|
|
|
244
244
|
---
|
|
245
245
|
|
|
@@ -255,14 +255,14 @@ Built-in guardrails that fire **before** any disk write:
|
|
|
255
255
|
|
|
256
256
|
## Compared to…
|
|
257
257
|
|
|
258
|
-
| Tool | What `fad-
|
|
258
|
+
| Tool | What `fad-checker` adds |
|
|
259
259
|
| --- | --- |
|
|
260
260
|
| `mvn dependency:tree` | No Maven needed; multi-source CVE scan; HTML report |
|
|
261
261
|
| `npm audit` | Polyglot (Maven + npm + vendored JS in one report); EOL & obsolete checks; works without `npm install` |
|
|
262
262
|
| Snyk CLI | Free; offline-capable; integrates Snyk's results if you have it |
|
|
263
263
|
| OWASP DC | Faster (cached); cleaner UI; multi-source dedup |
|
|
264
264
|
|
|
265
|
-
You don't have to choose — `fad-
|
|
265
|
+
You don't have to choose — `fad-checker` will use any of them as input (`--snyk`) and merge results.
|
|
266
266
|
|
|
267
267
|
---
|
|
268
268
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Bash completion for fad-
|
|
3
|
-
# Source this file or copy it to /etc/bash_completion.d/fad-
|
|
2
|
+
# Bash completion for fad-checker (Fucking Autonomous Dependency Checker)
|
|
3
|
+
# Source this file or copy it to /etc/bash_completion.d/fad-checker
|
|
4
4
|
_fad_check_complete() {
|
|
5
5
|
local cur prev opts
|
|
6
6
|
COMPREPLY=()
|
|
@@ -20,4 +20,4 @@ _fad_check_complete() {
|
|
|
20
20
|
return 0
|
|
21
21
|
fi
|
|
22
22
|
}
|
|
23
|
-
complete -F _fad_check_complete fad-
|
|
23
|
+
complete -F _fad_check_complete fad-checker
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Architecture
|
|
2
2
|
|
|
3
|
-
This is the deep-dive for anyone modifying `fad-
|
|
3
|
+
This is the deep-dive for anyone modifying `fad-checker`'s internals or wondering why a particular finding shows up the way it does. For day-to-day usage, see [`USAGE.md`](USAGE.md).
|
|
4
4
|
|
|
5
5
|
## Module map
|
|
6
6
|
|
|
7
7
|
```
|
|
8
|
-
fad-
|
|
8
|
+
fad-checker.js Thin CLI: commander parsing, orchestration only.
|
|
9
9
|
lib/core.js POM parsing, parent resolution, all-profile merge, rewrite.
|
|
10
10
|
lib/maven-version.js Maven version parsing + range comparison (no external deps).
|
|
11
11
|
lib/cve-download.js Bulk download of CVEProject/cvelistV5 + Maven-relevant index build.
|
|
@@ -21,10 +21,10 @@ lib/retire.js retire.js (vendored-JS scanner) wrapper + cache + n
|
|
|
21
21
|
lib/scan-completeness.js Warnings for deps we couldn't fully resolve.
|
|
22
22
|
lib/npm/parse.js package.json, package-lock.json (v1/2/3), yarn.lock v1 parsers.
|
|
23
23
|
lib/npm/collect.js Merge across JS manifests → unified resolvedDeps Map.
|
|
24
|
-
lib/cache-archive.js tar.gz / zip export & import of ~/.fad-
|
|
25
|
-
lib/config.js Persistent user config in ~/.fad-
|
|
24
|
+
lib/cache-archive.js tar.gz / zip export & import of ~/.fad-checker/.
|
|
25
|
+
lib/config.js Persistent user config in ~/.fad-checker/config.json (mode 0600).
|
|
26
26
|
data/ Curated JSON: known-obsolete, eol-mapping, cpe-coord-map, known-public-namespaces.
|
|
27
|
-
completions/ fad-
|
|
27
|
+
completions/ fad-checker.bash, fad-checker.zsh
|
|
28
28
|
test/ node:test suite + fixtures (simple, complex-enterprise, monorepo-mixed, …).
|
|
29
29
|
```
|
|
30
30
|
|
|
@@ -61,11 +61,11 @@ The Maven keyspace and npm keyspace never collide — `:lodash` (Maven groupId-l
|
|
|
61
61
|
3. `getAllInheritedProps()` — merges `<dependencies>`, `<dependencyManagement>`, `<properties>` from **every** `<profile>` (with `activeByDefault` properties winning for value conflicts), follows `<scope>import</scope>` BOMs to other local POMs, and recurses into resolved parents.
|
|
62
62
|
4. `rewritePoms()` — strips everything outside `nodeToKeep`, runs `cleanDeps()` to apply the `-e` regex, rewrites the `<parent>.relativePath` and `version` to the parent's value (not the child's). Skips disk writes when `readOnly`.
|
|
63
63
|
|
|
64
|
-
## Report pipeline (driven by `fad-
|
|
64
|
+
## Report pipeline (driven by `fad-checker.js` when `--report` is set)
|
|
65
65
|
|
|
66
66
|
1. **Collect** — `collectResolvedDeps()` dedupes by `groupId:artifactId`, keeps the highest version on conflict, includes external parent POMs as `scope='parent'`. `--ignore-test` honored. For npm, `collectNpmDeps()` walks JS manifests (lockfile-only — `package.json` without sibling lockfile is skipped + warned).
|
|
67
67
|
2. **Transitive expansion** (optional, `--transitive`) — `expandWithTransitives()` walks the Maven Central POM graph honouring exclusions, root depMgmt overrides, nearest-wins on version conflict, `--transitive-depth` cap. Skips test + optional scopes by default.
|
|
68
|
-
3. **CVE index** — `ensureCveIndex()` downloads the daily CVEProject zip (via `curl + unzip`, or falls back to `fetch()` + `unzip` / PowerShell `Expand-Archive`), filters to Maven-relevant entries, caches the compact index to `~/.fad-
|
|
68
|
+
3. **CVE index** — `ensureCveIndex()` downloads the daily CVEProject zip (via `curl + unzip`, or falls back to `fetch()` + `unzip` / PowerShell `Expand-Archive`), filters to Maven-relevant entries, caches the compact index to `~/.fad-checker/cve-data/maven-cve-index.json`. Fresh for 24h. `--cve-refresh` forces rebuild, `--cve-offline` uses cache only.
|
|
69
69
|
4. **CVE matching** — `matchDepsAgainstCves()` runs three tiers:
|
|
70
70
|
- `exact`: `byPackageName["g:a"]` hit
|
|
71
71
|
- `probable`: `byProduct[artifactId]` + vendor matches groupId (`apache` ↔ `org.apache.*`)
|
|
@@ -76,13 +76,13 @@ The Maven keyspace and npm keyspace never collide — `:lodash` (Maven groupId-l
|
|
|
76
76
|
7. **CPE refinement** — `refineMatchesWithCpe()` walks NVD's `configurations[].nodes[]` against each matched dep:
|
|
77
77
|
- Confirms the dep version actually falls in the vulnerable range (else `cpeFiltered: true` — likely false positive).
|
|
78
78
|
- Upgrades match `confidence` from `possible` → `probable` → `exact` when a curated `cpe-coord-map.json` entry confirms vendor:product → dep coord.
|
|
79
|
-
8. **retire.js** (default on) — shells out to `retire --outputformat json --jspath <src>`. Output normalised to fad-
|
|
79
|
+
8. **retire.js** (default on) — shells out to `retire --outputformat json --jspath <src>`. Output normalised to fad-checker match shape, with the vendored file path attached so the report can show where the offending `.js` lives. Cache: `~/.fad-checker/retire-cache/<md5(src)>.json`, 24h TTL.
|
|
80
80
|
9. **EOL / Obsolete / Outdated** — `lib/outdated.js`:
|
|
81
81
|
- **EOL**: matches dep coord against `data/eol-mapping.json`, fetches the cycle list from endoflife.date (cached 7d), flags cycles past their EOL date.
|
|
82
82
|
- **Obsolete**: lookup in `data/known-obsolete.json` (curated: log4j 1.x, jackson-mapper-asl, joda-time, commons-httpclient 3.x, …).
|
|
83
83
|
- **Outdated**: Maven Central Solr query for the latest version. Cache 24h. Concurrency 8.
|
|
84
84
|
10. **Snyk** (optional, `--snyk`) — runs `snyk test --all-projects --json` against the cleaned target dir. Normalised + merged. Findings in both sources tagged `source: "both"`.
|
|
85
|
-
11. **Render** — `writeReports()` produces `cve-report.html` (self-contained, inline CSS, no external assets) and `cve-report.doc` (same HTML with Office XML namespace meta tags so Word opens it natively). Default output dir: `./fad-
|
|
85
|
+
11. **Render** — `writeReports()` produces `cve-report.html` (self-contained, inline CSS, no external assets) and `cve-report.doc` (same HTML with Office XML namespace meta tags so Word opens it natively). Default output dir: `./fad-checker-report/`.
|
|
86
86
|
|
|
87
87
|
## Report structure
|
|
88
88
|
|
|
@@ -130,7 +130,7 @@ The Maven keyspace and npm keyspace never collide — `:lodash` (Maven groupId-l
|
|
|
130
130
|
- The CVE bundle from CVEProject is ~500 MB unpacked. We shell out to `curl + unzip` (Node built-in fallback to `fetch()` + system `unzip` / PowerShell `Expand-Archive`). The extracted JSON is deleted after the index is built.
|
|
131
131
|
- The bundle ships as `cves.zip.zip` (a zip whose sole content is another zip). `extractZip()` recurses up to 3 levels.
|
|
132
132
|
- `endoflife.date` API responses are cached locally for 7 days; Maven Central version lookups for 24 hours.
|
|
133
|
-
- **Persistent config**: `~/.fad-
|
|
133
|
+
- **Persistent config**: `~/.fad-checker/config.json` (mode 0600) stores per-user state, currently the NVD API key. Set via `fad-checker --set-nvd-key <KEY>`.
|
|
134
134
|
- **`--offline` umbrella flag**: skips every network call (CVE index download, OSV queries, NVD enrichment, endoflife.date lookups, Maven Central version queries, transitive POM fetches, retire.js scans). Falls back to whatever is already cached. Per-source variants (`--cve-offline`, `--no-osv`, `--no-nvd`, `--no-retire`) still work independently.
|
|
135
135
|
- `snyk` is not a dependency — we shell out via `execFile`. `snyk` exits 1 when it finds vulnerabilities, which is expected (the JSON is still on stdout).
|
|
136
136
|
- The cleaned POM is the union of every profile's deps. Counts will therefore be larger than the source POM. This is intentional — verify your reasoning before "reducing" them.
|
package/docs/USAGE.md
CHANGED
|
@@ -5,7 +5,7 @@ Every flag, every common workflow, with copy-pasteable commands.
|
|
|
5
5
|
## Synopsis
|
|
6
6
|
|
|
7
7
|
```text
|
|
8
|
-
fad-
|
|
8
|
+
fad-checker -s <src> [-t <target>] [-e <regex>] [other options]
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
- `-s, --src <src>` — **required**. Root of the source tree to scan. Contains `pom.xml` and/or `package(-lock).json` / `yarn.lock`.
|
|
@@ -13,21 +13,21 @@ fad-check -s <src> [-t <target>] [-e <regex>] [other options]
|
|
|
13
13
|
|
|
14
14
|
## Output
|
|
15
15
|
|
|
16
|
-
By default the HTML + Word reports land in `./fad-
|
|
16
|
+
By default the HTML + Word reports land in `./fad-checker-report/`. Override with `--report-output <dir>`.
|
|
17
17
|
|
|
18
18
|
## Ecosystem selection
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
21
|
# Auto-detect (default): scan whatever pom.xml / package(-lock).json / yarn.lock exists
|
|
22
|
-
fad-
|
|
22
|
+
fad-checker -s .
|
|
23
23
|
|
|
24
24
|
# Force one ecosystem
|
|
25
|
-
fad-
|
|
26
|
-
fad-
|
|
27
|
-
fad-
|
|
25
|
+
fad-checker -s . --ecosystem maven
|
|
26
|
+
fad-checker -s . --ecosystem npm
|
|
27
|
+
fad-checker -s . --ecosystem both # scan both even if only one is auto-detected
|
|
28
28
|
|
|
29
29
|
# Disable JS scanning entirely (Maven-only)
|
|
30
|
-
fad-
|
|
30
|
+
fad-checker -s . --no-js
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
## Filtering deps
|
|
@@ -35,8 +35,8 @@ fad-check -s . --no-js
|
|
|
35
35
|
`-e <regex>` filters out coords whose **groupId** (Maven) or **name** (npm) matches the regex. Useful for private/internal libs that you know aren't on a public registry.
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
fad-
|
|
39
|
-
fad-
|
|
38
|
+
fad-checker -s . -e "^(com\.acme|org\.private)\."
|
|
39
|
+
fad-checker -s . -e "^@acme/"
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
The excluded coords are listed at the end of the run so you can audit the regex.
|
|
@@ -59,18 +59,18 @@ Each data source can be disabled independently:
|
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
61
|
# Use cached data only, no network (works for everything)
|
|
62
|
-
fad-
|
|
62
|
+
fad-checker -s . --offline
|
|
63
63
|
|
|
64
64
|
# Per-source offline
|
|
65
|
-
fad-
|
|
66
|
-
fad-
|
|
67
|
-
fad-
|
|
65
|
+
fad-checker -s . --cve-offline # use cached CVE index only
|
|
66
|
+
fad-checker -s . --cve-refresh # force re-download of CVE bundle
|
|
67
|
+
fad-checker -s . --retire-refresh # force re-scan with retire.js (ignore cache)
|
|
68
68
|
|
|
69
69
|
# Cache export / import (useful for air-gapped boxes)
|
|
70
|
-
fad-
|
|
71
|
-
fad-
|
|
72
|
-
fad-
|
|
73
|
-
fad-
|
|
70
|
+
fad-checker --export-cache fad-cache.tar.gz
|
|
71
|
+
fad-checker --export-cache fad-cache.tar.gz --include-config # bundle NVD key too
|
|
72
|
+
fad-checker --import-cache fad-cache.tar.gz
|
|
73
|
+
fad-checker --import-cache fad-cache.tar.gz --force # replace existing without backup
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
## NVD API key
|
|
@@ -79,24 +79,24 @@ NVD's public rate limit is 5 requests / 30s without a key. The free key bumps it
|
|
|
79
79
|
|
|
80
80
|
```bash
|
|
81
81
|
# Get a key in 30 seconds: https://nvd.nist.gov/developers/request-an-api-key
|
|
82
|
-
fad-
|
|
83
|
-
fad-
|
|
82
|
+
fad-checker --set-nvd-key YOUR_KEY # stored in ~/.fad-checker/config.json (mode 0600)
|
|
83
|
+
fad-checker --show-config # confirm it's persisted (key masked)
|
|
84
84
|
```
|
|
85
85
|
|
|
86
86
|
Or pass it ad-hoc via the `NVD_API_KEY` env var.
|
|
87
87
|
|
|
88
88
|
## Snyk integration
|
|
89
89
|
|
|
90
|
-
If you have `snyk` installed and authenticated, `fad-
|
|
90
|
+
If you have `snyk` installed and authenticated, `fad-checker` can drive it:
|
|
91
91
|
|
|
92
92
|
```bash
|
|
93
|
-
fad-
|
|
93
|
+
fad-checker -s ./proj -t ../proj-clean -e "^com\.acme\." --snyk
|
|
94
94
|
```
|
|
95
95
|
|
|
96
96
|
This:
|
|
97
97
|
1. Generates the cleaned POM tree at `../proj-clean/`.
|
|
98
98
|
2. Runs `snyk test --all-projects --json` against it.
|
|
99
|
-
3. Merges Snyk's findings into the report — entries present in both `fad-
|
|
99
|
+
3. Merges Snyk's findings into the report — entries present in both `fad-checker` and Snyk are tagged `source: "both"`.
|
|
100
100
|
|
|
101
101
|
`--snyk` requires `-t` (Snyk needs a real POM tree to scan).
|
|
102
102
|
|
|
@@ -104,7 +104,7 @@ This:
|
|
|
104
104
|
|
|
105
105
|
| Mode | Trigger | Disk writes |
|
|
106
106
|
| --- | --- | --- |
|
|
107
|
-
| Read-only | `-t` omitted (default) | Only `~/.fad-
|
|
107
|
+
| Read-only | `-t` omitted (default) | Only `~/.fad-checker/` caches and the report dir |
|
|
108
108
|
| Write | `-t <dir>` provided | Above + the cleaned POM tree at `<dir>` (and `<dir>` is `rimraf`'d first!) |
|
|
109
109
|
|
|
110
110
|
The `--target` guardrails refuse:
|
|
@@ -114,30 +114,30 @@ The `--target` guardrails refuse:
|
|
|
114
114
|
## Verbosity
|
|
115
115
|
|
|
116
116
|
```bash
|
|
117
|
-
fad-
|
|
117
|
+
fad-checker -s . -v # progress per source (OSV batches, NVD pages, retire scan, …)
|
|
118
118
|
```
|
|
119
119
|
|
|
120
120
|
## Shell completion
|
|
121
121
|
|
|
122
122
|
```bash
|
|
123
|
-
fad-
|
|
124
|
-
fad-
|
|
123
|
+
fad-checker --completion bash > /etc/bash_completion.d/fad-checker
|
|
124
|
+
fad-checker --completion zsh > ~/.zsh/completions/_fad-checker
|
|
125
125
|
```
|
|
126
126
|
|
|
127
127
|
## All flags at a glance
|
|
128
128
|
|
|
129
129
|
```bash
|
|
130
|
-
fad-
|
|
130
|
+
fad-checker --help
|
|
131
131
|
```
|
|
132
132
|
|
|
133
133
|
## Recipes
|
|
134
134
|
|
|
135
135
|
### CI gate: fail the build on any CRITICAL prod CVE
|
|
136
136
|
|
|
137
|
-
`fad-
|
|
137
|
+
`fad-checker` exits 0 even when CVEs are found (it's a reporter, not a gate). Wire your own:
|
|
138
138
|
|
|
139
139
|
```bash
|
|
140
|
-
fad-
|
|
140
|
+
fad-checker -s . --no-nvd > /dev/null
|
|
141
141
|
# Then grep the report or parse the structured output (planned).
|
|
142
142
|
```
|
|
143
143
|
|
|
@@ -148,7 +148,7 @@ fad-check -s . --no-nvd > /dev/null
|
|
|
148
148
|
Keep dated copies of the report:
|
|
149
149
|
|
|
150
150
|
```bash
|
|
151
|
-
fad-
|
|
151
|
+
fad-checker -s . --report-output reports/$(date +%F)
|
|
152
152
|
diff reports/2026-04-01/cve-report.html reports/2026-05-01/cve-report.html
|
|
153
153
|
```
|
|
154
154
|
|
|
@@ -157,15 +157,15 @@ diff reports/2026-04-01/cve-report.html reports/2026-05-01/cve-report.html
|
|
|
157
157
|
On a connected machine:
|
|
158
158
|
|
|
159
159
|
```bash
|
|
160
|
-
fad-
|
|
161
|
-
fad-
|
|
160
|
+
fad-checker -s ./dummy-empty-dir # populates ~/.fad-checker/ caches
|
|
161
|
+
fad-checker --export-cache fad-cache.tar.gz --include-config
|
|
162
162
|
```
|
|
163
163
|
|
|
164
164
|
Move `fad-cache.tar.gz` to the air-gapped box, then:
|
|
165
165
|
|
|
166
166
|
```bash
|
|
167
|
-
fad-
|
|
168
|
-
fad-
|
|
167
|
+
fad-checker --import-cache fad-cache.tar.gz
|
|
168
|
+
fad-checker -s ./real-project --offline
|
|
169
169
|
```
|
|
170
170
|
|
|
171
171
|
### Monorepo with Maven + JS + vendored JS
|
|
@@ -173,7 +173,7 @@ fad-check -s ./real-project --offline
|
|
|
173
173
|
The melino-style project (Java backend, React frontend in `web/`, vendored jQuery/PDF.js under `web/src/main/webapp/`):
|
|
174
174
|
|
|
175
175
|
```bash
|
|
176
|
-
fad-
|
|
176
|
+
fad-checker -s . -e "^(com\.captcha|org\.voxaly|com\.voxaly)\."
|
|
177
177
|
# → finds CVE in Maven deps, in web/package-lock.json deps,
|
|
178
178
|
# AND in the vendored .js files under webapp/.
|
|
179
179
|
```
|
|
@@ -27,7 +27,7 @@ if (process.argv.includes("--completion")) {
|
|
|
27
27
|
const shell = process.argv[shellIdx] && !process.argv[shellIdx].startsWith("-")
|
|
28
28
|
? process.argv[shellIdx]
|
|
29
29
|
: "bash";
|
|
30
|
-
const completionPath = path.join(__dirname, "completions", `fad-
|
|
30
|
+
const completionPath = path.join(__dirname, "completions", `fad-checker.${shell}`);
|
|
31
31
|
try {
|
|
32
32
|
process.stdout.write(fs.readFileSync(completionPath, "utf8"));
|
|
33
33
|
process.exit(0);
|
|
@@ -87,7 +87,7 @@ if (process.argv.includes("--add-repo") || process.argv.includes("--remove-repo"
|
|
|
87
87
|
const url = process.argv[idx + 2];
|
|
88
88
|
if (!name || name.startsWith("-") || !url || url.startsWith("-")) {
|
|
89
89
|
console.error(chalk.red("❌ --add-repo requires <name> <url>"));
|
|
90
|
-
console.error(" Example: fad-
|
|
90
|
+
console.error(" Example: fad-checker --add-repo nexus https://nexus.acme.com/repository/maven-public/");
|
|
91
91
|
console.error(" Optional auth: --add-repo nexus https://nexus.acme.com/repository/maven-public/ --auth user:pass");
|
|
92
92
|
process.exit(1);
|
|
93
93
|
}
|
|
@@ -121,7 +121,7 @@ if (process.argv.includes("--export-cache") || process.argv.includes("--import-c
|
|
|
121
121
|
if (exportIdx !== -1) {
|
|
122
122
|
const dest = process.argv[exportIdx + 1];
|
|
123
123
|
if (!dest || dest.startsWith("-")) {
|
|
124
|
-
console.error(chalk.red("❌ --export-cache requires a destination path (e.g. fad-
|
|
124
|
+
console.error(chalk.red("❌ --export-cache requires a destination path (e.g. fad-checker-cache.tar.gz)"));
|
|
125
125
|
process.exit(1);
|
|
126
126
|
}
|
|
127
127
|
const includeConfig = process.argv.includes("--include-config");
|
|
@@ -150,11 +150,11 @@ if (process.argv.includes("--export-cache") || process.argv.includes("--import-c
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
const USAGE = `
|
|
153
|
-
(1) fad-
|
|
154
|
-
(2) fad-
|
|
155
|
-
(3) fad-
|
|
156
|
-
(4) fad-
|
|
157
|
-
(5) fad-
|
|
153
|
+
(1) fad-checker -s ./proj # read-only: full report (CVE + EOL + obsolete + outdated + transitive)
|
|
154
|
+
(2) fad-checker -s ./proj -e "^(org.private|client)" # same, with regex exclusion of private deps
|
|
155
|
+
(3) fad-checker -s ./proj -t ../pom-clean -e "^(org.private|client)" # write cleaned POMs + full report
|
|
156
|
+
(4) fad-checker -s ./proj --no-transitive --no-all-libs # faster, only direct deps, no Maven Central queries
|
|
157
|
+
(5) fad-checker -s ./proj -t ../pom-clean -e "^..." --snyk # also run snyk and merge findings
|
|
158
158
|
`;
|
|
159
159
|
|
|
160
160
|
program
|
|
@@ -173,13 +173,13 @@ program
|
|
|
173
173
|
.option("--no-osv", "skip OSV.dev (Google/GitHub aggregated Maven CVE feed)")
|
|
174
174
|
.option("--no-nvd", "skip NIST NVD enrichment of matched CVEs")
|
|
175
175
|
.option("--offline", "no network: use cached CVE/OSV/NVD/POM data only")
|
|
176
|
-
.option("--set-nvd-key <key>", "save NVD API key to ~/.fad-
|
|
177
|
-
.option("--show-config", "print the persisted ~/.fad-
|
|
178
|
-
.option("--export-cache <file>", "tar.gz/zip the ~/.fad-
|
|
179
|
-
.option("--import-cache <file>", "restore ~/.fad-
|
|
176
|
+
.option("--set-nvd-key <key>", "save NVD API key to ~/.fad-checker/config.json (10× faster NVD enrichment)")
|
|
177
|
+
.option("--show-config", "print the persisted ~/.fad-checker/config.json")
|
|
178
|
+
.option("--export-cache <file>", "tar.gz/zip the ~/.fad-checker/ caches to <file> (excludes config.json by default)")
|
|
179
|
+
.option("--import-cache <file>", "restore ~/.fad-checker/ from a previously exported archive (existing dir is moved to .bak unless --force)")
|
|
180
180
|
.option("--include-config", "with --export-cache: also bundle config.json (contains the NVD API key)")
|
|
181
|
-
.option("--force", "with --import-cache: replace ~/.fad-
|
|
182
|
-
.option("--report-output <dir>", "report output directory", "./fad-
|
|
181
|
+
.option("--force", "with --import-cache: replace ~/.fad-checker/ without backup")
|
|
182
|
+
.option("--report-output <dir>", "report output directory", "./fad-checker-report")
|
|
183
183
|
.option("--ignore-test", "skip test-scoped dependencies in report")
|
|
184
184
|
.option("--cve-refresh", "force re-download of CVE database")
|
|
185
185
|
.option("--cve-offline", "use cached CVE index only (no download)")
|
|
@@ -218,7 +218,7 @@ async function checkMavenLibExist(groupId, artifactId, repos) {
|
|
|
218
218
|
const p = `${g.replace(/\./g, "/")}/${a}/maven-metadata.xml`;
|
|
219
219
|
const { existsInAny } = require("./lib/maven-repo");
|
|
220
220
|
try {
|
|
221
|
-
const hit = await existsInAny(repos, p, { userAgent: "fad-
|
|
221
|
+
const hit = await existsInAny(repos, p, { userAgent: "fad-checker-existence" });
|
|
222
222
|
if (hit) return true;
|
|
223
223
|
console.log(`❌ NOT found on any repo: ${g}:${a}`);
|
|
224
224
|
return false;
|
|
@@ -231,7 +231,7 @@ async function checkMavenLibExist(groupId, artifactId, repos) {
|
|
|
231
231
|
(async function main() {
|
|
232
232
|
console.log(chalk.bold.cyan("\n🚀 Fucking Autonomous Dependency Checker\n") + chalk.gray("─────────────────────────────"));
|
|
233
233
|
|
|
234
|
-
// Build the Maven repo list once: persisted repos (from ~/.fad-
|
|
234
|
+
// Build the Maven repo list once: persisted repos (from ~/.fad-checker/config.json)
|
|
235
235
|
// + ad-hoc --repo URLs + Maven Central as final fallback. Used by transitive
|
|
236
236
|
// resolution, outdated-version check, and existence check.
|
|
237
237
|
const { getMavenRepos } = require("./lib/config");
|
|
@@ -401,7 +401,7 @@ async function runReportFlow(allPomMetadata, allPropsByPom, ecoFlags = {}) {
|
|
|
401
401
|
}
|
|
402
402
|
const directCount = resolved.size;
|
|
403
403
|
|
|
404
|
-
// Scan-completeness signals: BOMs and unresolved-version deps mean fad-
|
|
404
|
+
// Scan-completeness signals: BOMs and unresolved-version deps mean fad-checker
|
|
405
405
|
// has gone as far as it can without running Maven/Snyk itself.
|
|
406
406
|
if (runMaven) {
|
|
407
407
|
const { detectScanCompletenessWarnings } = require("./lib/scan-completeness");
|
|
@@ -581,7 +581,7 @@ async function runReportFlow(allPomMetadata, allPropsByPom, ecoFlags = {}) {
|
|
|
581
581
|
if (!outdatedResults.length && options.allLibs) console.log(chalk.gray(" (none)"));
|
|
582
582
|
if (!options.allLibs) console.log(chalk.gray(" (re-run with -a/--allLibs to query Maven Central)"));
|
|
583
583
|
|
|
584
|
-
const reportDir = options.reportOutput || "./fad-
|
|
584
|
+
const reportDir = options.reportOutput || "./fad-checker-report";
|
|
585
585
|
await fs.promises.mkdir(reportDir, { recursive: true });
|
|
586
586
|
const projectInfo = {
|
|
587
587
|
name: path.basename(path.resolve(options.src)),
|
|
@@ -613,7 +613,7 @@ async function runReportFlow(allPomMetadata, allPropsByPom, ecoFlags = {}) {
|
|
|
613
613
|
const paths = (dep?.pomPaths || []).map(p => path.relative(options.src, p));
|
|
614
614
|
return { id, manifestPaths: paths };
|
|
615
615
|
}),
|
|
616
|
-
message: `${privateLibIds.length} Maven coord(s) not found on Maven Central — they are private/internal libraries. Their CVEs (if any) cannot be detected by fad-
|
|
616
|
+
message: `${privateLibIds.length} Maven coord(s) not found on Maven Central — they are private/internal libraries. Their CVEs (if any) cannot be detected by fad-checker; if you have an internal CVE feed, audit them separately.`,
|
|
617
617
|
}] : []),
|
|
618
618
|
],
|
|
619
619
|
});
|
package/lib/cache-archive.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/cache-archive.js — export / import the entire ~/.fad-
|
|
2
|
+
* lib/cache-archive.js — export / import the entire ~/.fad-checker/ directory.
|
|
3
3
|
*
|
|
4
4
|
* Use case:
|
|
5
5
|
* - Move the warmed-up CVE/OSV/NVD/POM caches between machines
|
|
@@ -20,17 +20,17 @@ const { execFile } = require("child_process");
|
|
|
20
20
|
const { promisify } = require("util");
|
|
21
21
|
const execFileP = promisify(execFile);
|
|
22
22
|
|
|
23
|
-
const FAD_CACHE_DIR = path.join(os.homedir(), ".fad-
|
|
23
|
+
const FAD_CACHE_DIR = path.join(os.homedir(), ".fad-checker");
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Files inside ~/.fad-
|
|
26
|
+
* Files inside ~/.fad-checker/ that hold secrets and should NOT be shipped by default.
|
|
27
27
|
* Override by passing `includeConfig: true`.
|
|
28
28
|
*/
|
|
29
29
|
const SENSITIVE_FILES = ["config.json"];
|
|
30
30
|
|
|
31
31
|
async function exportCache(destFile, opts = {}) {
|
|
32
32
|
const { verbose, includeConfig } = opts;
|
|
33
|
-
if (!fs.existsSync(FAD_CACHE_DIR)) throw new Error(`no ~/.fad-
|
|
33
|
+
if (!fs.existsSync(FAD_CACHE_DIR)) throw new Error(`no ~/.fad-checker/ directory to export`);
|
|
34
34
|
const abs = path.resolve(destFile);
|
|
35
35
|
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
36
36
|
|
|
@@ -49,7 +49,7 @@ async function exportCache(destFile, opts = {}) {
|
|
|
49
49
|
if (verbose) console.log(`📦 Compress-Archive -Path ${FAD_CACHE_DIR}\\* -DestinationPath ${abs}`);
|
|
50
50
|
// Powershell Compress-Archive doesn't have a clean exclude flag — manual copy
|
|
51
51
|
await execFileP("powershell", ["-NoProfile", "-Command",
|
|
52
|
-
`$src='${FAD_CACHE_DIR}'; $dst='${abs}'; ${includeConfig ? `Compress-Archive -Path "$src\\*" -DestinationPath $dst -Force` : `$tmp=Join-Path $env:TEMP "fad-
|
|
52
|
+
`$src='${FAD_CACHE_DIR}'; $dst='${abs}'; ${includeConfig ? `Compress-Archive -Path "$src\\*" -DestinationPath $dst -Force` : `$tmp=Join-Path $env:TEMP "fad-checker-stage-$(Get-Random)"; Copy-Item $src $tmp -Recurse; ${SENSITIVE_FILES.map(f => `Remove-Item -Force -ErrorAction SilentlyContinue (Join-Path $tmp '${f}')`).join("; ")}; Compress-Archive -Path "$tmp\\*" -DestinationPath $dst -Force; Remove-Item $tmp -Recurse -Force`}`]);
|
|
53
53
|
} else {
|
|
54
54
|
const args = ["-r", "-q", abs, path.basename(FAD_CACHE_DIR)];
|
|
55
55
|
for (const e of excludes) { args.push("-x"); args.push(e); }
|
|
@@ -70,13 +70,13 @@ async function importCache(srcFile, opts = {}) {
|
|
|
70
70
|
if (!fs.existsSync(abs)) throw new Error(`archive not found: ${abs}`);
|
|
71
71
|
|
|
72
72
|
if (fs.existsSync(FAD_CACHE_DIR) && !force) {
|
|
73
|
-
// Move existing aside as .fad-
|
|
73
|
+
// Move existing aside as .fad-checker.bak-<timestamp>
|
|
74
74
|
const backup = `${FAD_CACHE_DIR}.bak-${Date.now()}`;
|
|
75
75
|
fs.renameSync(FAD_CACHE_DIR, backup);
|
|
76
|
-
if (verbose) console.log(`💾 existing ~/.fad-
|
|
76
|
+
if (verbose) console.log(`💾 existing ~/.fad-checker/ moved to ${backup}`);
|
|
77
77
|
} else if (force && fs.existsSync(FAD_CACHE_DIR)) {
|
|
78
78
|
fs.rmSync(FAD_CACHE_DIR, { recursive: true, force: true });
|
|
79
|
-
if (verbose) console.log(`🗑 --force: existing ~/.fad-
|
|
79
|
+
if (verbose) console.log(`🗑 --force: existing ~/.fad-checker/ removed`);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
const parent = path.dirname(FAD_CACHE_DIR);
|
|
@@ -99,7 +99,7 @@ async function importCache(srcFile, opts = {}) {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
if (!fs.existsSync(FAD_CACHE_DIR)) {
|
|
102
|
-
throw new Error(`import completed but ~/.fad-
|
|
102
|
+
throw new Error(`import completed but ~/.fad-checker/ was not created — was the archive built with fad-checker --export-cache?`);
|
|
103
103
|
}
|
|
104
104
|
return { dir: FAD_CACHE_DIR };
|
|
105
105
|
}
|
package/lib/config.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/config.js — persistent user config in ~/.fad-
|
|
2
|
+
* lib/config.js — persistent user config in ~/.fad-checker/config.json
|
|
3
3
|
*
|
|
4
4
|
* Stores credentials and per-user preferences that should survive across runs.
|
|
5
5
|
* Currently: NVD API key (so users don't have to re-export the env var).
|
|
@@ -8,7 +8,7 @@ const fs = require("fs");
|
|
|
8
8
|
const path = require("path");
|
|
9
9
|
const os = require("os");
|
|
10
10
|
|
|
11
|
-
const CONFIG_DIR = path.join(os.homedir(), ".fad-
|
|
11
|
+
const CONFIG_DIR = path.join(os.homedir(), ".fad-checker");
|
|
12
12
|
const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
13
13
|
|
|
14
14
|
function load() {
|
|
@@ -35,7 +35,7 @@ function get(key) {
|
|
|
35
35
|
return load()[key];
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
/** NVD API key resolution: env var first, then ~/.fad-
|
|
38
|
+
/** NVD API key resolution: env var first, then ~/.fad-checker/config.json. */
|
|
39
39
|
function getNvdApiKey() {
|
|
40
40
|
return process.env.NVD_API_KEY || get("nvd_api_key") || null;
|
|
41
41
|
}
|
package/lib/cve-download.js
CHANGED
|
@@ -13,7 +13,7 @@ const { execFile } = require("child_process");
|
|
|
13
13
|
const { promisify } = require("util");
|
|
14
14
|
const execFileP = promisify(execFile);
|
|
15
15
|
|
|
16
|
-
const CVE_DATA_DIR = path.join(os.homedir(), ".fad-
|
|
16
|
+
const CVE_DATA_DIR = path.join(os.homedir(), ".fad-checker", "cve-data");
|
|
17
17
|
const CVE_INDEX_PATH = path.join(CVE_DATA_DIR, "maven-cve-index.json");
|
|
18
18
|
const CVE_META_PATH = path.join(CVE_DATA_DIR, "meta.json");
|
|
19
19
|
const CVE_EXTRACT_DIR = path.join(CVE_DATA_DIR, "extracted");
|
|
@@ -174,7 +174,7 @@ async function commandExists(name) {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
async function fetchLatestRelease() {
|
|
177
|
-
const headers = { "User-Agent": "fad-
|
|
177
|
+
const headers = { "User-Agent": "fad-checker-cve-downloader" };
|
|
178
178
|
if (process.env.GITHUB_TOKEN) headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
|
179
179
|
const res = await fetch("https://api.github.com/repos/CVEProject/cvelistV5/releases/latest", { headers });
|
|
180
180
|
if (!res.ok) throw new Error(`GitHub API HTTP ${res.status}`);
|
|
@@ -341,7 +341,7 @@ async function downloadCveData(opts = {}) {
|
|
|
341
341
|
}
|
|
342
342
|
|
|
343
343
|
/**
|
|
344
|
-
* Single entry point used by fad-
|
|
344
|
+
* Single entry point used by fad-checker.js: returns a usable CVE index,
|
|
345
345
|
* downloading/refreshing as needed.
|
|
346
346
|
*/
|
|
347
347
|
async function ensureCveIndex(opts = {}) {
|
package/lib/cve-report.js
CHANGED
|
@@ -1246,11 +1246,11 @@ function buildBody({ cveMatches, devCveMatches, retireMatches, eolResults, obsol
|
|
|
1246
1246
|
|
|
1247
1247
|
return `
|
|
1248
1248
|
<header class="report-header">
|
|
1249
|
-
<h1>FAD-
|
|
1249
|
+
<h1>FAD-Checker Report</h1>
|
|
1250
1250
|
<div class="report-subtitle">Multi-ecosystem dependency security audit</div>
|
|
1251
1251
|
<div class="meta">
|
|
1252
1252
|
Project: <strong>${esc(projectInfo.name)}</strong> · <code class="path">${esc(projectInfo.src)}</code><br>
|
|
1253
|
-
Generated: ${esc(projectInfo.generatedAt)}${projectInfo.toolVersion ? ` · fad-
|
|
1253
|
+
Generated: ${esc(projectInfo.generatedAt)}${projectInfo.toolVersion ? ` · fad-checker ${esc(projectInfo.toolVersion)}` : ""}${projectInfo.cveDataDate ? ` · CVE data: ${esc(projectInfo.cveDataDate)}` : ""}
|
|
1254
1254
|
</div>
|
|
1255
1255
|
</header>
|
|
1256
1256
|
${exec}
|
|
@@ -1401,7 +1401,7 @@ const TOGGLE_SCRIPT = `
|
|
|
1401
1401
|
|
|
1402
1402
|
function generateHtmlReport(payload) {
|
|
1403
1403
|
return `<!doctype html>
|
|
1404
|
-
<html><head><meta charset="utf-8"><title>FAD-
|
|
1404
|
+
<html><head><meta charset="utf-8"><title>FAD-Checker Report</title><style>${CSS}</style></head>
|
|
1405
1405
|
<body>${buildBody(payload)}${TOGGLE_SCRIPT}</body></html>`;
|
|
1406
1406
|
}
|
|
1407
1407
|
|
|
@@ -1426,7 +1426,7 @@ function generateWordReport(payload) {
|
|
|
1426
1426
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
1427
1427
|
<meta name="ProgId" content="Word.Document">
|
|
1428
1428
|
<meta name="Generator" content="Microsoft Word 15">
|
|
1429
|
-
<title>FAD-
|
|
1429
|
+
<title>FAD-Checker Report</title>
|
|
1430
1430
|
<style>${CSS}
|
|
1431
1431
|
/* In Word: keep every detail row expanded since scripts are disabled */
|
|
1432
1432
|
tr.detail-row { display: table-row !important; }
|
package/lib/maven-repo.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Reading <maven-metadata.xml> for latest-version discovery
|
|
9
9
|
* (lib/outdated.js)
|
|
10
10
|
*
|
|
11
|
-
* Repository entry shape (from ~/.fad-
|
|
11
|
+
* Repository entry shape (from ~/.fad-checker/config.json or CLI):
|
|
12
12
|
* { name?, url, auth? } auth = "user:pass" (we wrap as Basic <base64>)
|
|
13
13
|
*
|
|
14
14
|
* URL convention: each repo URL must end at the directory under which Maven
|
|
@@ -76,11 +76,11 @@ function authHeader(auth) {
|
|
|
76
76
|
* fetcher custom fetch (for tests)
|
|
77
77
|
* readBody read response.text() into body (default false to save mem
|
|
78
78
|
* on HEAD calls; transitive.js sets true for POMs)
|
|
79
|
-
* userAgent default "fad-
|
|
79
|
+
* userAgent default "fad-checker-maven-repo"
|
|
80
80
|
* onMiss callback(repo, status) for telemetry (verbose mode)
|
|
81
81
|
*/
|
|
82
82
|
async function tryRepos(repos, pathSuffix, opts = {}) {
|
|
83
|
-
const { method = "GET", fetcher = globalThis.fetch, readBody = false, userAgent = "fad-
|
|
83
|
+
const { method = "GET", fetcher = globalThis.fetch, readBody = false, userAgent = "fad-checker-maven-repo", onMiss } = opts;
|
|
84
84
|
for (const repo of repos) {
|
|
85
85
|
const url = repo.url + pathSuffix.replace(/^\//, "");
|
|
86
86
|
const headers = { "User-Agent": userAgent };
|
package/lib/nvd.js
CHANGED
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
* API: https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=CVE-YYYY-NNNN
|
|
10
10
|
* Rate limit: 5 req / 30s unauthenticated, 50 req / 30s with NVD_API_KEY env var.
|
|
11
11
|
*
|
|
12
|
-
* Cache: ~/.fad-
|
|
12
|
+
* Cache: ~/.fad-checker/nvd-cache/<cve-id>.json, 7-day TTL.
|
|
13
13
|
*/
|
|
14
14
|
const fs = require("fs");
|
|
15
15
|
const path = require("path");
|
|
16
16
|
const os = require("os");
|
|
17
17
|
const { getNvdApiKey } = require("./config");
|
|
18
18
|
|
|
19
|
-
const NVD_CACHE_DIR = path.join(os.homedir(), ".fad-
|
|
19
|
+
const NVD_CACHE_DIR = path.join(os.homedir(), ".fad-checker", "nvd-cache");
|
|
20
20
|
const NVD_CACHE_TTL_MS = 7 * 24 * 3600 * 1000;
|
|
21
21
|
const NVD_BASE = "https://services.nvd.nist.gov/rest/json/cves/2.0";
|
|
22
22
|
|
|
@@ -146,7 +146,7 @@ async function fetchOne(cveId, opts = {}) {
|
|
|
146
146
|
const cached = readCache(cveId);
|
|
147
147
|
if (cached !== null && cached !== undefined) return cached;
|
|
148
148
|
if (offline) return null;
|
|
149
|
-
const headers = { "User-Agent": "fad-
|
|
149
|
+
const headers = { "User-Agent": "fad-checker-nvd-enrich" };
|
|
150
150
|
const key = getNvdApiKey();
|
|
151
151
|
if (key) headers["apiKey"] = key;
|
|
152
152
|
const url = `${NVD_BASE}?cveId=${encodeURIComponent(cveId)}`;
|
|
@@ -169,7 +169,7 @@ async function fetchOne(cveId, opts = {}) {
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
/**
|
|
172
|
-
* Enrich an array of fad-
|
|
172
|
+
* Enrich an array of fad-checker matches in place by fetching their NVD records.
|
|
173
173
|
* Adds: cve.description (replaced by NVD's), cve.cvssVector, cve.cvssVersion,
|
|
174
174
|
* cve.references, cve.cpes. Severity/score are only overwritten if currently UNKNOWN/null.
|
|
175
175
|
*
|
|
@@ -205,7 +205,7 @@ async function enrichMatches(matches, opts = {}) {
|
|
|
205
205
|
for (const m of matches) {
|
|
206
206
|
const data = byId.get(m.cve?.id);
|
|
207
207
|
if (!data) continue;
|
|
208
|
-
// Merge: prefer NVD's official text + CVSS but keep what fad-
|
|
208
|
+
// Merge: prefer NVD's official text + CVSS but keep what fad-checker/OSV already has
|
|
209
209
|
// NVD's description is the official long form — prefer it when available.
|
|
210
210
|
if (data.description) {
|
|
211
211
|
m.cve.description = data.description.length > 2000
|
package/lib/osv.js
CHANGED
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
* POST /v1/query for a single dep
|
|
12
12
|
* GET /v1/vulns/{id} to fetch full details
|
|
13
13
|
*
|
|
14
|
-
* Cached responses live in ~/.fad-
|
|
14
|
+
* Cached responses live in ~/.fad-checker/osv-cache/ for 12h.
|
|
15
15
|
*/
|
|
16
16
|
const fs = require("fs");
|
|
17
17
|
const path = require("path");
|
|
18
18
|
const os = require("os");
|
|
19
19
|
|
|
20
|
-
const OSV_CACHE_DIR = path.join(os.homedir(), ".fad-
|
|
20
|
+
const OSV_CACHE_DIR = path.join(os.homedir(), ".fad-checker", "osv-cache");
|
|
21
21
|
const OSV_CACHE_TTL_MS = 12 * 3600 * 1000;
|
|
22
22
|
const OSV_BASE = "https://api.osv.dev";
|
|
23
23
|
const BATCH_SIZE = 800; // OSV limit is 1000; stay under for safety
|
|
@@ -101,7 +101,7 @@ function osvPkgName(dep) {
|
|
|
101
101
|
return dep.ecosystem === "npm" ? dep.artifactId : `${dep.groupId}:${dep.artifactId}`;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
/** Convert one OSV vuln to fad-
|
|
104
|
+
/** Convert one OSV vuln to fad-checker match shape. */
|
|
105
105
|
function vulnToMatch(dep, vuln) {
|
|
106
106
|
const id = pickPrimaryId(vuln);
|
|
107
107
|
const { severity, score } = severityFromOsv(vuln);
|
|
@@ -180,7 +180,7 @@ async function queryBatch(deps, opts = {}) {
|
|
|
180
180
|
if (verbose) process.stdout.write(`\r OSV batch ${++batchIdx}/${batches.length} (${batch.length} deps)…`);
|
|
181
181
|
const res = await fetcher(`${OSV_BASE}/v1/querybatch`, {
|
|
182
182
|
method: "POST",
|
|
183
|
-
headers: { "Content-Type": "application/json", "User-Agent": "fad-
|
|
183
|
+
headers: { "Content-Type": "application/json", "User-Agent": "fad-checker-osv" },
|
|
184
184
|
body: JSON.stringify({ queries: batch }),
|
|
185
185
|
});
|
|
186
186
|
if (!res.ok) {
|
|
@@ -223,7 +223,7 @@ async function queryBatch(deps, opts = {}) {
|
|
|
223
223
|
if (offline) continue;
|
|
224
224
|
try {
|
|
225
225
|
const r = await fetcher(`${OSV_BASE}/v1/vulns/${encodeURIComponent(id)}`, {
|
|
226
|
-
headers: { "User-Agent": "fad-
|
|
226
|
+
headers: { "User-Agent": "fad-checker-osv" },
|
|
227
227
|
});
|
|
228
228
|
if (r.ok) {
|
|
229
229
|
const body = await r.json();
|
|
@@ -265,7 +265,7 @@ function runMatches(deps, indexMap, detailById) {
|
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
/**
|
|
268
|
-
* Public: query OSV for every dep in `resolvedDeps` Map, return fad-
|
|
268
|
+
* Public: query OSV for every dep in `resolvedDeps` Map, return fad-checker-shape matches.
|
|
269
269
|
*/
|
|
270
270
|
async function queryOsvForDeps(resolvedDeps, opts = {}) {
|
|
271
271
|
const deps = [];
|
package/lib/outdated.js
CHANGED
|
@@ -13,7 +13,7 @@ const { compareMavenVersions } = require("./maven-version");
|
|
|
13
13
|
const KNOWN_OBSOLETE = require("../data/known-obsolete.json");
|
|
14
14
|
const EOL_MAPPING = require("../data/eol-mapping.json");
|
|
15
15
|
|
|
16
|
-
const CACHE_DIR = path.join(os.homedir(), ".fad-
|
|
16
|
+
const CACHE_DIR = path.join(os.homedir(), ".fad-checker");
|
|
17
17
|
const EOL_CACHE_PATH = path.join(CACHE_DIR, "eol-cache.json");
|
|
18
18
|
const VERSION_CACHE_PATH = path.join(CACHE_DIR, "version-cache.json");
|
|
19
19
|
const EOL_CACHE_MAX_AGE_MS = 7 * 24 * 3600 * 1000; // 7 days
|
|
@@ -58,7 +58,7 @@ async function fetchEndoflife(product, cache, opts = {}) {
|
|
|
58
58
|
if (opts.offline) return null;
|
|
59
59
|
try {
|
|
60
60
|
const res = await fetch(`https://endoflife.date/api/${encodeURIComponent(product)}.json`, {
|
|
61
|
-
headers: { "User-Agent": "fad-
|
|
61
|
+
headers: { "User-Agent": "fad-checker-eol-checker" },
|
|
62
62
|
});
|
|
63
63
|
if (!res.ok) {
|
|
64
64
|
cache.entries[product] = { error: `HTTP ${res.status}` };
|
|
@@ -174,7 +174,7 @@ async function fetchLatestVersion(groupId, artifactId, cache, opts = {}) {
|
|
|
174
174
|
// internal Nexus/Artifactory artifacts that aren't on Central.
|
|
175
175
|
try {
|
|
176
176
|
const url = `https://search.maven.org/solrsearch/select?q=g:%22${encodeURIComponent(groupId)}%22+AND+a:%22${encodeURIComponent(artifactId)}%22&core=gav&rows=1&wt=json`;
|
|
177
|
-
const res = await fetch(url, { headers: { "User-Agent": "fad-
|
|
177
|
+
const res = await fetch(url, { headers: { "User-Agent": "fad-checker-outdated-checker" } });
|
|
178
178
|
if (res.ok) {
|
|
179
179
|
const json = await res.json();
|
|
180
180
|
const doc = json?.response?.docs?.[0];
|
|
@@ -189,7 +189,7 @@ async function fetchLatestVersion(groupId, artifactId, cache, opts = {}) {
|
|
|
189
189
|
if (Array.isArray(opts.repos) && opts.repos.length) {
|
|
190
190
|
try {
|
|
191
191
|
const { fetchMavenMetadata } = require("./maven-repo");
|
|
192
|
-
const hit = await fetchMavenMetadata(opts.repos, groupId, artifactId, { userAgent: "fad-
|
|
192
|
+
const hit = await fetchMavenMetadata(opts.repos, groupId, artifactId, { userAgent: "fad-checker-outdated-checker" });
|
|
193
193
|
if (hit?.body) {
|
|
194
194
|
const latest = parseMavenMetadataLatest(hit.body);
|
|
195
195
|
if (latest) {
|
|
@@ -231,7 +231,7 @@ async function checkOutdatedDeps(resolvedDeps, opts = {}) {
|
|
|
231
231
|
const list = [...resolvedDeps.values()].filter(d => d.version && !/\$\{|SNAPSHOT/i.test(d.version) && d.ecosystem !== "npm");
|
|
232
232
|
const results = [];
|
|
233
233
|
|
|
234
|
-
// Simple p-limit style throttle without requiring p-limit here (already used in fad-
|
|
234
|
+
// Simple p-limit style throttle without requiring p-limit here (already used in fad-checker.js)
|
|
235
235
|
let cursor = 0;
|
|
236
236
|
const workers = Array.from({ length: concurrency }, async () => {
|
|
237
237
|
while (cursor < list.length) {
|
package/lib/retire.js
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* unmanaged .js / .min.js files (no package-lock to back them).
|
|
5
5
|
*
|
|
6
6
|
* retire ships its own signature DB updated weekly; we just shell
|
|
7
|
-
* out to it and normalise the output to fad-
|
|
7
|
+
* out to it and normalise the output to fad-checker match shape so the
|
|
8
8
|
* report can render it like any other CVE source.
|
|
9
9
|
*
|
|
10
|
-
* Cache: ~/.fad-
|
|
10
|
+
* Cache: ~/.fad-checker/retire-cache/<md5(src)>.json, 24 h TTL.
|
|
11
11
|
*
|
|
12
12
|
* The CLI is expected at node_modules/.bin/retire (declared in
|
|
13
13
|
* package.json deps). When bundled with bun, we also try `retire`
|
|
@@ -21,7 +21,7 @@ const { execFile } = require("child_process");
|
|
|
21
21
|
const { promisify } = require("util");
|
|
22
22
|
const execFileP = promisify(execFile);
|
|
23
23
|
|
|
24
|
-
const RETIRE_CACHE_DIR = path.join(os.homedir(), ".fad-
|
|
24
|
+
const RETIRE_CACHE_DIR = path.join(os.homedir(), ".fad-checker", "retire-cache");
|
|
25
25
|
const RETIRE_CACHE_TTL_MS = 24 * 3600 * 1000;
|
|
26
26
|
|
|
27
27
|
function cacheKey(srcDir) {
|
|
@@ -82,7 +82,7 @@ async function runRetire(srcDir, opts = {}) {
|
|
|
82
82
|
|
|
83
83
|
// retire.js refuses to write to /dev/stdout, so we use a real temp file
|
|
84
84
|
// and read it back. Falls back to stdout if --outputpath is rejected.
|
|
85
|
-
const tmpOut = path.join(os.tmpdir(), `fad-
|
|
85
|
+
const tmpOut = path.join(os.tmpdir(), `fad-checker-retire-${process.pid}-${Date.now()}.json`);
|
|
86
86
|
const args = [
|
|
87
87
|
"--outputformat", "json",
|
|
88
88
|
"--outputpath", tmpOut,
|
|
@@ -119,7 +119,7 @@ async function runRetire(srcDir, opts = {}) {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
/**
|
|
122
|
-
* Normalise a retire.js result tree to fad-
|
|
122
|
+
* Normalise a retire.js result tree to fad-checker match shape.
|
|
123
123
|
*
|
|
124
124
|
* retire output (jsonsimple-ish):
|
|
125
125
|
* {
|
package/lib/scan-completeness.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/scan-completeness.js — produce warnings telling the user when fad-
|
|
2
|
+
* lib/scan-completeness.js — produce warnings telling the user when fad-checker
|
|
3
3
|
* has gone as far as it can without running the real build tool.
|
|
4
4
|
*
|
|
5
5
|
* The two big "you need a real scan" cases:
|
|
@@ -57,7 +57,7 @@ function detectScanCompletenessWarnings(resolvedDeps, opts = {}) {
|
|
|
57
57
|
type: "unresolved-versions",
|
|
58
58
|
count: items.length,
|
|
59
59
|
items,
|
|
60
|
-
message: `${items.length} Maven dep(s) without a concrete version — silently skipped from CVE/OSV/outdated scans. Their versions are likely pinned by an external BOM or a property defined outside the source tree. Run "mvn dependency:tree" against the source (or "snyk test --all-projects" against the cleaned POMs${opts.ranSnyk ? " — already done with --snyk" : ", or re-run fad-
|
|
60
|
+
message: `${items.length} Maven dep(s) without a concrete version — silently skipped from CVE/OSV/outdated scans. Their versions are likely pinned by an external BOM or a property defined outside the source tree. Run "mvn dependency:tree" against the source (or "snyk test --all-projects" against the cleaned POMs${opts.ranSnyk ? " — already done with --snyk" : ", or re-run fad-checker with --snyk"}) to resolve them and complete the scan.`,
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
63
|
|
package/lib/snyk.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* lib/snyk.js — optional Snyk integration.
|
|
3
3
|
*
|
|
4
4
|
* Runs `snyk test --all-projects --json` on the cleaned POM directory and
|
|
5
|
-
* normalises the output to fad-
|
|
5
|
+
* normalises the output to fad-checker's CVE match shape so the report can
|
|
6
6
|
* merge findings from both engines.
|
|
7
7
|
*/
|
|
8
8
|
const { execFile } = require("child_process");
|
|
@@ -53,7 +53,7 @@ function parseSnykStdout(stdout) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
* Normalise snyk JSON to fad-
|
|
56
|
+
* Normalise snyk JSON to fad-checker match objects:
|
|
57
57
|
* [{ dep: {groupId, artifactId, version}, cve: {id, severity, score, ...}, source: 'snyk' }]
|
|
58
58
|
*/
|
|
59
59
|
function parseSnykResults(snykProjectsJson) {
|
|
@@ -91,8 +91,8 @@ function parseSnykResults(snykProjectsJson) {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
|
-
* Merge fad-
|
|
95
|
-
* When a finding exists in both, fad-
|
|
94
|
+
* Merge fad-checker and Snyk matches, deduping by (groupId:artifactId, cve.id).
|
|
95
|
+
* When a finding exists in both, fad-checker's row is kept but tagged source='both'.
|
|
96
96
|
*/
|
|
97
97
|
function mergeWithFadResults(fadMatches, snykMatches) {
|
|
98
98
|
const byKey = new Map();
|
package/lib/transitive.js
CHANGED
|
@@ -24,7 +24,7 @@ const path = require("path");
|
|
|
24
24
|
const os = require("os");
|
|
25
25
|
const { parseStringPromise } = require("xml2js");
|
|
26
26
|
|
|
27
|
-
const POM_CACHE_DIR = path.join(os.homedir(), ".fad-
|
|
27
|
+
const POM_CACHE_DIR = path.join(os.homedir(), ".fad-checker", "poms-cache");
|
|
28
28
|
const MAVEN_CENTRAL = "https://repo1.maven.org/maven2";
|
|
29
29
|
|
|
30
30
|
// Maven's scope-propagation matrix (rows: direct dep scope, cols: transitive scope)
|
|
@@ -62,7 +62,7 @@ async function fetchPom(g, a, v, opts = {}) {
|
|
|
62
62
|
if (Array.isArray(repos)) {
|
|
63
63
|
try {
|
|
64
64
|
const { fetchPomFromRepos } = require("./maven-repo");
|
|
65
|
-
const hit = await fetchPomFromRepos(repos, g, a, v, { fetcher, userAgent: "fad-
|
|
65
|
+
const hit = await fetchPomFromRepos(repos, g, a, v, { fetcher, userAgent: "fad-checker-transitive" });
|
|
66
66
|
if (hit?.body) {
|
|
67
67
|
await fs.promises.mkdir(cacheDir, { recursive: true });
|
|
68
68
|
await fs.promises.writeFile(cf, hit.body);
|
|
@@ -79,7 +79,7 @@ async function fetchPom(g, a, v, opts = {}) {
|
|
|
79
79
|
}
|
|
80
80
|
const url = pomPath(g, a, v);
|
|
81
81
|
try {
|
|
82
|
-
const res = await fetcher(url, { headers: { "User-Agent": "fad-
|
|
82
|
+
const res = await fetcher(url, { headers: { "User-Agent": "fad-checker-transitive" } });
|
|
83
83
|
if (res.status === 404) {
|
|
84
84
|
await fs.promises.mkdir(cacheDir, { recursive: true });
|
|
85
85
|
await fs.promises.writeFile(cf, "__NOT_FOUND__");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fad-checker",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Fucking Autonomous Dependency Checker — multi-ecosystem CVE / EOL / outdated / vendored-JS scanner for Maven, npm and Yarn monorepos. Self-contained HTML + Word report.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "N8tz <n8tz.js@gmail.com>",
|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
"N8tz (https://github.com/N8tz)"
|
|
12
12
|
],
|
|
13
13
|
"bin": {
|
|
14
|
-
"fad-checker": "fad-
|
|
15
|
-
"fad": "fad-
|
|
14
|
+
"fad-checker": "fad-checker.js",
|
|
15
|
+
"fad": "fad-checker.js"
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
18
|
"test": "node --test test/*.test.js",
|
|
19
|
-
"build:linux": "bun build fad-
|
|
20
|
-
"build:win": "bun build fad-
|
|
21
|
-
"build:macos": "bun build fad-
|
|
19
|
+
"build:linux": "bun build fad-checker.js --compile --target=bun-linux-x64 --outfile=dist/fad-checker-linux",
|
|
20
|
+
"build:win": "bun build fad-checker.js --compile --target=bun-windows-x64 --outfile=dist/fad-checker.exe",
|
|
21
|
+
"build:macos": "bun build fad-checker.js --compile --target=bun-darwin-x64 --outfile=dist/fad-checker-macos",
|
|
22
22
|
"build": "npm run build:linux && npm run build:win"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
package/test/core.test.js
CHANGED
|
@@ -22,7 +22,7 @@ async function pipeline(src, { deps2Exclude } = {}) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
test("findPomFiles skips target/.git/node_modules", () => {
|
|
25
|
-
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "fad-
|
|
25
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "fad-checker-test-"));
|
|
26
26
|
fs.mkdirSync(path.join(tmp, "target"));
|
|
27
27
|
fs.writeFileSync(path.join(tmp, "target", "pom.xml"), "<project/>");
|
|
28
28
|
fs.writeFileSync(path.join(tmp, "pom.xml"), "<project/>");
|
|
@@ -93,7 +93,7 @@ test("BOM import (scope=import) pulls in managed deps from local BOM", async ()
|
|
|
93
93
|
});
|
|
94
94
|
|
|
95
95
|
test("rewritePoms writes a clean tree in --target mode, target ≠ src", async () => {
|
|
96
|
-
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "fad-
|
|
96
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "fad-checker-target-"));
|
|
97
97
|
const { store, props, pomFiles } = await pipeline(COMPLEX);
|
|
98
98
|
const opts = {
|
|
99
99
|
srcRoot: COMPLEX, targetRoot: tmp,
|
|
@@ -125,7 +125,7 @@ test("rewritePoms in --test (readOnly) mode does not crash with undefined target
|
|
|
125
125
|
|
|
126
126
|
test("missing external parent is flagged in missingById", async () => {
|
|
127
127
|
const { store, props, pomFiles } = await pipeline(PRIVATE_FIX);
|
|
128
|
-
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "fad-
|
|
128
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "fad-checker-priv-"));
|
|
129
129
|
const opts = {
|
|
130
130
|
srcRoot: PRIVATE_FIX, targetRoot: tmp,
|
|
131
131
|
deps2Exclude: /^(com\.client\.private|org\.megacorp)/,
|
|
@@ -143,7 +143,7 @@ test("missing external parent is flagged in missingById", async () => {
|
|
|
143
143
|
|
|
144
144
|
test("parent version in rewritten POM uses parent's version, not child's", async () => {
|
|
145
145
|
// Simple has child app with no own <version>; the rewritten parent ref should be 1.0.0
|
|
146
|
-
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "fad-
|
|
146
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "fad-checker-pv-"));
|
|
147
147
|
const { store, props, pomFiles } = await pipeline(SIMPLE);
|
|
148
148
|
const opts = { srcRoot: SIMPLE, targetRoot: tmp, deps2Exclude: null, verbose: false, readOnly: false };
|
|
149
149
|
for (const f of pomFiles) await core.rewritePoms(f, store, props, opts);
|
package/test/cve-report.test.js
CHANGED
|
@@ -61,7 +61,7 @@ test("generateWordReport contains Word XML namespaces", () => {
|
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
test("writeReports writes both files to disk", async () => {
|
|
64
|
-
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "fad-
|
|
64
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "fad-checker-rep-"));
|
|
65
65
|
const r = await writeReports({
|
|
66
66
|
cveMatches: sampleMatches,
|
|
67
67
|
eolResults: [{ dep: { groupId: "org.hibernate", artifactId: "hibernate-core", version: "5.6" }, product: "Hibernate ORM", eol: "2025-08-08" }],
|
package/test/monorepo.test.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* - 1 npm package (package-lock v3) with prod/dev/peer deps
|
|
5
5
|
* - 1 yarn-v1 package with prod/dev deps + a private @acme/* dep
|
|
6
6
|
*
|
|
7
|
-
* Drives the same code paths as `fad-
|
|
7
|
+
* Drives the same code paths as `fad-checker --report` minus the network calls.
|
|
8
8
|
*/
|
|
9
9
|
const { test } = require("node:test");
|
|
10
10
|
const assert = require("node:assert/strict");
|
package/test/snyk.test.js
CHANGED
|
@@ -35,7 +35,7 @@ test("parseSnykStdout accepts a JSON object or array", () => {
|
|
|
35
35
|
assert.equal(obj.length, 1);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
test("parseSnykResults normalises to fad-
|
|
38
|
+
test("parseSnykResults normalises to fad-checker match shape", () => {
|
|
39
39
|
const out = parseSnykResults(snykSample);
|
|
40
40
|
assert.equal(out.length, 2);
|
|
41
41
|
assert.equal(out[0].dep.groupId, "org.apache.logging.log4j");
|
package/test/transitive.test.js
CHANGED
|
@@ -5,9 +5,9 @@ const os = require("os");
|
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const { parsePomXml, resolveProps, buildMgmt, SCOPE_MATRIX, resolveTransitiveDeps } = require("../lib/transitive");
|
|
7
7
|
|
|
8
|
-
// Per-test cache dir so we don't poison ~/.fad-
|
|
8
|
+
// Per-test cache dir so we don't poison ~/.fad-checker/poms-cache during tests
|
|
9
9
|
function freshCache() {
|
|
10
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "fad-
|
|
10
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "fad-checker-pom-cache-"));
|
|
11
11
|
return dir;
|
|
12
12
|
}
|
|
13
13
|
|