fad-checker 1.0.0 → 1.0.2

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 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-check` — **Fucking Autonomous Dependency Checker**. Node.js CLI (`fad-check`, or short alias `fad`) that:
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-check.js -s ./proj # read-only, full report
31
- node fad-check.js -s ./proj -t ../pom-clean -e "^client\\." # write cleaned tree
32
- node fad-check.js -s ./proj -t ../pom-clean -e "^client\\." --snyk # also drive snyk
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-check-linux
42
- npm run build:win # → dist/fad-check.exe
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-check.js Thin CLI: commander parsing, orchestration only.
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-check couldn't fully resolve.
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-check/.
71
- lib/config.js Persistent user config in ~/.fad-check/config.json (mode 0600).
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-check.bash, fad-check.zsh
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-check/`.
108
- - **Persistent config**: `~/.fad-check/config.json` (mode 0600). Set NVD key via `fad-check --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).
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-check/cve-data/maven-cve-index.json` | 24 h |
120
- | OSV per-dep stub list | `~/.fad-check/osv-cache/<eco>__<g>__<a>__<v>.json` | 12 h |
121
- | OSV vuln details | `~/.fad-check/osv-cache/vuln_<id>.json` | 12 h |
122
- | NVD CVE record | `~/.fad-check/nvd-cache/<cveId>.json` | 7 d |
123
- | endoflife.date cycles | `~/.fad-check/eol-cache.json` | 7 d |
124
- | Maven Central latest | `~/.fad-check/version-cache.json` | 24 h |
125
- | Transitive POM | `~/.fad-check/poms-cache/<g>__<a>__<v>.pom` | ∞ (immutable on Maven Central) |
126
- | retire.js findings | `~/.fad-check/retire-cache/<md5(src)>.json` | 24 h |
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 |
@@ -0,0 +1,343 @@
1
+ # fad-checker — Analyse critique complète
2
+
3
+ **Date :** 2026-05-22
4
+ **Périmètre :** intégralité du code (`fad-checker.js` + `lib/**/*.js` + `data/*.json` + `test/`)
5
+ **Méthode :** 4 audits parallèles à effort xhigh (reuse / quality / efficiency / **faux positifs**)
6
+ **Verdict global :** architecture saine, mais **4 chemins fail-open en cascade dans le matcher CVE** produisent un taux de faux positifs estimé entre **20 % et 60 %** selon le niveau de confiance retenu. À traiter en priorité avant toute exploitation en production.
7
+
8
+ ---
9
+
10
+ ## TL;DR — Risque de faux positifs
11
+
12
+ | Tier de confiance | Taux de FP estimé sur ~500 deps | Recommandation |
13
+ |---|---|---|
14
+ | `exact` uniquement | 5–10 % | Utilisable (triage léger) |
15
+ | `exact` + `probable` | 20–30 % | Triage manuel obligatoire |
16
+ | Tous tiers (`exact` + `probable` + `possible`) | **40–60 %** | Inexploitable sans tri humain |
17
+
18
+ Quatre fail-open en cascade :
19
+
20
+ 1. **H1** — `isVersionAffected` retourne `true` quand le CVE n'a aucune borne de version (`lib/maven-version.js:101-124`).
21
+ 2. **H2** — `vendorMatchesGroup` fait du *substring matching* (`g.includes(v) || v.includes(g)`) — `vendor="open"` matche `org.opensaml.*` (`lib/cve-match.js:164-173`).
22
+ 3. **H3** — Tier-3 `possible` émis pour toute collision d'`artifactId`, même quand le vendor est totalement étranger (`lib/cve-match.js:191-198`).
23
+ 4. **H4** — Le filtre CPE (« primary FP filter ») se contente de **marquer** `cpeFiltered=true`, il ne supprime jamais (`lib/cpe.js:269-272`).
24
+
25
+ Ces quatre interagissent de manière pathologique : H3 laisse passer une masse de matches `possible`, H2 en remonte beaucoup en `probable`, H1 valide toutes les versions, et H4 ne filtre plus rien en aval.
26
+
27
+ ---
28
+
29
+ ## A. Faux positifs (critère #1 demandé)
30
+
31
+ ### HIGH
32
+
33
+ #### H1 — `isVersionAffected` fail-open quand pas de bornes
34
+ **Fichier :** `lib/maven-version.js:101-124`
35
+
36
+ ```js
37
+ if (spec.version && spec.version !== "0" && spec.version !== "*") { /* lower */ }
38
+ if (spec.lessThan) { ... }
39
+ if (spec.lessThanOrEqual) { ... }
40
+ if (!spec.lessThan && !spec.lessThanOrEqual && spec.version && …) { /* exact */ }
41
+ return true; // ← fall-through "affected"
42
+ ```
43
+
44
+ Empiriquement : `isVersionAffected("2.14", { status: "affected" })` → `true`. CVEProject contient régulièrement des entrées `versions:[{status:"affected"}]` sans bornes (stub mal formé). Combiné à H2, **toute dep qui hit le `byProduct` tier-2 avec un range sparse est flaggée sur toute version**.
45
+
46
+ **FP concret :** CVE listant `affected: [{ product: "log4j", versions: [{ status: "affected" }] }]` → flag `log4j-core 2.99.0` (patché) car pas de borne haute, pas d'exact-match, fall-through → `true`.
47
+
48
+ **Fix :**
49
+ ```js
50
+ if (!spec.version && !spec.lessThan && !spec.lessThanOrEqual) return false;
51
+ ```
52
+
53
+ ---
54
+
55
+ #### H2 — `vendorMatchesGroup` substring-match → cross-vendor leak
56
+ **Fichier :** `lib/cve-match.js:164-173`
57
+
58
+ ```js
59
+ if (g.includes(v) || v.includes(g)) return true;
60
+ ```
61
+
62
+ `vendorMatchesGroup("spring", "foospringbar")` → `true`. NVD/CVEProject contiennent régulièrement des vendors `"a"`, `"oss"`, `"com"`, `"java"`, `"go"`, `"web"`, `"open"`, `"ibm"`, `"net"` — tous présents en sous-chaîne dans des groupIds réels.
63
+
64
+ **FP concrets :**
65
+ - CVE `vendor="open"` matche `org.opensaml:opensaml-core` (`"opensaml".includes("open")`).
66
+ - CVE `vendor="ibm"` matche `com.ibmcloudant:cloudant-client`.
67
+ - CVE `vendor="spring"` matche `org.springframework.*` ET `com.foospringbar.*`.
68
+
69
+ **Fix :** restreindre à (a) `g === v`, (b) `g.split(".").includes(v)`, (c) `v.includes(g)` seulement si `g.length >= 4`. Drop des branches substring non bornées.
70
+
71
+ ---
72
+
73
+ #### H3 — Tier-3 `possible` émis avec vendor totalement étranger
74
+ **Fichier :** `lib/cve-match.js:191-198`
75
+
76
+ ```js
77
+ const productMatches = byProduct[dep.artifactId.toLowerCase()] || [];
78
+ for (const e of productMatches) {
79
+ if (vendorMatchesGroup(e.vendor, dep.groupId)) {
80
+ all.push(...matchOne(dep, [e], "probable"));
81
+ } else {
82
+ all.push(...matchOne(dep, [e], "possible")); // ← émis quand même
83
+ }
84
+ }
85
+ ```
86
+
87
+ Toute dep dont l'`artifactId` collide avec un produit non relié produit un match `possible`. Collisions fréquentes : `core`, `common`, `api`, `client`, `utils`, `parser`, `web`. Une CVE avec `product="core"` flagge **toutes** les deps `*-core`.
88
+
89
+ **Fix :** drop le tier `possible` ou ne l'afficher que si la refinement CPE (`refineMatchesWithCpe`) l'a confirmé.
90
+
91
+ ---
92
+
93
+ #### H4 — CPE refinement flagge sans supprimer
94
+ **Fichier :** `lib/cpe.js:269-272`
95
+
96
+ ```js
97
+ if (!affected && rec.configurations.length) {
98
+ m.cpeFiltered = true; // ← marqué, jamais supprimé
99
+ }
100
+ ```
101
+
102
+ `cpeFiltered` est un soft tag. Si `lib/cve-report.js` ne strippe pas `cpeFiltered === true` (à vérifier dans le rendu), **les matches prouvés faux positifs restent dans la punch list**.
103
+
104
+ De plus, le skip `if (!rec.configurations.length && !rec.cpes.length) continue;` (`cpe.js:266`) est fail-open : une CVE sans configurations NVD (CVE récente ou réservée) est laissée au tier que le matcher lui a donné.
105
+
106
+ **Fix :** (a) filtrer par défaut `cpeFiltered=true` avec flag `--show-filtered`, ou (b) section séparée « likely false positive » dans le report.
107
+
108
+ ---
109
+
110
+ #### H5 — Le qualificateur CPE `update` (RC, alpha) est ignoré
111
+ **Fichier :** `lib/cpe.js:81-100` (`matchVersionRange`)
112
+
113
+ Le champ index 6 du CPE 2.3 (`update`) est parsé mais jamais comparé. NVD liste fréquemment les pré-releases via le champ update (`cpe:2.3:a:vendor:product:1.0.0:rc1:*…`). Le hard-pin compare littéralement la `version` ignorant `update` :
114
+
115
+ ```js
116
+ if (parsed.version && parsed.version !== "*" && parsed.version !== "-") {
117
+ try { return compareMavenVersions(depVersion, parsed.version) === 0; }
118
+ ```
119
+
120
+ → `criteria: cpe:2.3:a:apache:foo:1.0.0:beta1:*…` matche `dep=1.0.0` (release). FP.
121
+
122
+ **Fix :** si `update` n'est ni `*` ni `-`, concaténer (`${version}-${update}`) avant `compareMavenVersions` (qui gère déjà `1.0.0-beta1`).
123
+
124
+ ### MEDIUM
125
+
126
+ #### M1 — `compareMavenVersions` accepte du garbage sans broncher
127
+ **Fichier :** `lib/maven-version.js:25-94`
128
+
129
+ `compareMavenVersions("${foo}", "1.0")` → `-1`. Une variable Maven non résolue est comparée comme une vraie version. La pipeline filtre `/\$\{/.test(dep.version)` dans `expandWithTransitives`, `queryOsvForDeps`, `checkOutdatedDeps` — **mais pas dans `matchDepsAgainstCves`** (`cve-match.js:151-162`). Donc dep avec version `${foo}` → `isVersionAffected("${foo}", range)` → fall-through `true` (H1).
130
+
131
+ **Fix :** skipper les deps avec `${…}` non résolu dans `matchDepsAgainstCves`.
132
+
133
+ ---
134
+
135
+ #### M2 — OSV : stubs cachés ré-émis sur la mauvaise version
136
+ **Fichier :** `lib/osv.js:242-265` (`runMatches`)
137
+
138
+ `vulnToMatch` ne refait pas de check range local. Le cache key `(g, a, v)` est versionné, donc pour la même dep+version c'est sûr. Mais le branch « stub only » émet `severity: "UNKNOWN"` sans description — vrai FP si la dep a été upgradée entre la build du cache et le scan.
139
+
140
+ `dep.version` avec `1.0.0.Final` ou `1.0.0.RELEASE` est envoyée verbatim à OSV qui ne normalise pas → recall patchy (FN, pas FP).
141
+
142
+ **Fix :** toujours évaluer `vuln.affected[].ranges` localement avant d'émettre. Drop des matches stub-only.
143
+
144
+ ---
145
+
146
+ #### M3 — `parseRange` exporté mais jamais appelé
147
+ **Fichier :** `lib/maven-version.js:131-146`
148
+
149
+ `parseRange` traite les ranges Maven (`[1.0,2.0)`) mais aucun caller. Une `<version>[1.0,2.0)</version>` est passée verbatim à `compareMavenVersions("[1.0,2.0)", spec.version)`. Le tokenizer split sur `[.\-]`, `[1` devient un segment string → ranking sub-release → typiquement FN, mais peut devenir FP via H1.
150
+
151
+ **Fix :** câbler `parseRange` dans la pipeline ; sinon reporter ces deps en `unresolved-versions`.
152
+
153
+ ---
154
+
155
+ #### M4 — `cpeMatchesDep` ré-introduit le substring leak de H2
156
+ **Fichier :** `lib/cpe.js:170-173`
157
+
158
+ Le filtre CPE supposé *narrow* contient lui-même la même heuristique substring que H2. Après que H2 fait passer un match, ce leak l'empêche de le re-filtrer.
159
+
160
+ ---
161
+
162
+ #### M5 — retire.js passé sans validation locale
163
+ **Fichier :** `lib/retire.js:148-193`
164
+
165
+ Le wrapper a accès à `below`/`atOrAbove` mais ne les compare jamais. Les régressions historiques des signatures retire (matchs sur des bundles non reliés) passent. Pas d'équivalent à `--includeOsvData` / `--ignorefile`.
166
+
167
+ retire émet aussi un CVE id synthétique `RETIRE-<component>-<version>` qui ne dédup jamais contre fad/osv/nvd → **même vuln rapportée 2 fois**.
168
+
169
+ ---
170
+
171
+ #### M6 — Cohérence des clés `byProduct`
172
+ **Fichier :** `lib/cve-download.js:163-164` + `lib/cve-match.js:191`
173
+
174
+ Index keyé sur `a.product` brut, lookup sur `.toLowerCase()`. Actuellement safe car `extractAffectedRanges` lowercase upstream — mais fragile. À documenter ou normaliser explicitement.
175
+
176
+ ### LOW
177
+
178
+ - **L1** — `data/known-obsolete.json:92-96` : `struts2-core` flaggé HIGH sans gate de version (Struts 2.5.30 patché remonte « obsolete »).
179
+ - **L2** — `findCycleForVersion` (`lib/outdated.js:79-86`) prefix-match OK, pas de FP trouvé.
180
+ - **L3** — Tests : aucun ne couvre les edge cases ci-dessus (no-bounds, substring vendor leak, possible tier, update CPE, garbage `${...}`, `parseRange`).
181
+
182
+ ### Verdict faux positifs
183
+
184
+ **Architecture saine, implémentation fail-open.** Les 4 cascades (H1+H2+H3+H4) doivent être patchées avant prod.
185
+
186
+ Patches prioritaires (ordre) :
187
+ 1. H1 → one-liner, plus gros gain
188
+ 2. H2 → tightening de `vendorMatchesGroup`
189
+ 3. H3 → cacher le tier `possible` par défaut
190
+ 4. H4 → strip `cpeFiltered:true` du report par défaut
191
+ 5. M1 → skip `${...}` dans `matchDepsAgainstCves`
192
+ 6. M2 → re-eval local des OSV ranges
193
+
194
+ ---
195
+
196
+ ## B. Code reuse
197
+
198
+ ### HIGH
199
+
200
+ - **Helper de cache disque triplicé** : `lib/osv.js:25-44`, `lib/nvd.js:28-45`, `lib/retire.js:27-45`. Pattern identique (`_fetchedAt` + TTL + JSON). `lib/outdated.js:22-37` même chose avec shape différent.
201
+ → Extraire `lib/cache-disk.js` exportant `makeCache(dir, ttlMs)`.
202
+
203
+ - **`severityFromScore` + `SEVERITY_RANK` dupliqués 7+ fois** : `lib/nvd.js:64-71`, `lib/cve-download.js:82-89`, `lib/osv.js:60-66`, `lib/cve-match.js:9`, `lib/snyk.js:112`, `lib/cve-report.js:{634, 761, 1085, 1280}`, `fad-checker.js:657`.
204
+ → `lib/severity.js` + `sortMatchesBySeverity()` helper.
205
+
206
+ - **Merge-by-source logic dupliquée** : `fad-checker.js:628-665` (`mergeBySource`) vs `lib/snyk.js:97-120` (`mergeWithFadResults`). Le `"both"` de Snyk désaccorde avec le `"fad+osv"` ailleurs.
207
+ → Promouvoir `mergeBySource` dans `lib/cve-match.js`.
208
+
209
+ ### MEDIUM
210
+
211
+ - **`depLabel` / `depToKey` réinventés 6 fois** : `fad-checker.js:551`, `lib/cpe.js:177-184`, `lib/osv.js:100-102`, `lib/cve-report.js:{648, 669, 773, 1296}`.
212
+ - **Walker de répertoires + `SKIP_DIRS` dupliqués 4 fois** : `lib/core.js:9-37`, `lib/npm/parse.js:253-284`, `lib/npm/collect.js:202-222`, `lib/retire.js:77-81`.
213
+ - **Deux parsers POM divergents** : `lib/core.js:49-117` vs `lib/transitive.js:111-158`. Légitime (besoins différents) mais extraction parent/coord en commun possible.
214
+ - **CLI hand-roll argv parsing** : `fad-checker.js:25-150` walk de `process.argv` pour `--completion`, `--set-nvd-key`, etc. — devrait être des subcommands `commander`.
215
+
216
+ ### LOW
217
+
218
+ - 3 comparateurs de versions divergents : `compareMavenVersions`, `compareVersionsLoose` (`cve-report.js:705-715`), `semverCompare` (`npm/collect.js:25-37`).
219
+ - `parseMavenMetadataLatest` (`outdated.js:212-224`) en regex au lieu d'xml2js (OK car XML trivial).
220
+
221
+ ### Modules clean
222
+ `maven-version.js`, `maven-repo.js`, `config.js`, `scan-completeness.js`, `cache-archive.js`.
223
+
224
+ ---
225
+
226
+ ## C. Code quality
227
+
228
+ ### HIGH
229
+
230
+ - **`fad-checker.js` (665 lignes) = god script** : pré-parse argv mélangé à l'orchestration, business logic dans la CLI.
231
+ - **`lib/cve-report.js` (1455 lignes) = mega-module** : CSS inline (~200 lignes), JS inline (`TOGGLE_SCRIPT`), business logic (`buildFixRecommendations`, `versionJump`, `pickTopCriticalMatches`) et rendu HTML dans le même fichier. `RENDER_CTX` module-global (lignes 247-249) reset à chaque `buildBody` — comment auto-avoue le workaround.
232
+ → Splitter en `lib/report/{css,html,recommendations,word}.js`. Passer `srcRoot` en paramètre.
233
+ - **Stringly-typed partout** : `"fad" | "osv" | "nvd" | "snyk" | "retire"` en chaînes nues + class CSS `.source.snyk` qui couple le HTML à ces littéraux. Renommer une source casse silencieusement le CSS.
234
+ → `lib/keys.js` + `SOURCES = Object.freeze({...})`.
235
+
236
+ ### MEDIUM
237
+
238
+ - **Param sprawl sur `writeReports` / `buildBody` / `renderExecutiveSummary`** — 10 champs disjoints recomposés à chaque appel.
239
+ - **CVE matcher : duplication structurelle tier-2/tier-3** (`cve-match.js:175-219`) — flatten en single loop avec ternaire.
240
+ - **Lockfile parsers v1 vs v2/v3 collés en un seul `parsePackageLock`** (`npm/parse.js:76-176`) — splitter.
241
+ - **~30 `catch {}` silencieux** dans tout le code. Beaucoup légitimes, certains masquent des bugs réels (`osv.js:233`, `outdated.js:187,201`, `fad-checker.js:259`).
242
+ - **`RENDER_CTX` global** → thread `srcRoot` via les signatures (3 niveaux max).
243
+ - **Skip-dir lists copy-collées 4x** avec divergence `build/` (Maven keep, JS skip) — documenter une fois.
244
+ - **`lib/core.js:230-235`** repeat parent-resolution dans `rewritePoms` — consolider.
245
+
246
+ ### LOW
247
+
248
+ - Cache helpers ré-implémentés (cf. reuse).
249
+ - Commentaires narratifs sans valeur (`cve-report.js:404-406`, `cve-report.js:519-521`, `transitive.js:204-206`, `fad-checker.js:202`).
250
+ - Pollution `byId` potentielle via `excludedById`/`missingById` (`core.js:240-285`) — appliquer le guard du CLAUDE.md.
251
+ - `lib/osv.js:188` arithmétique d'index fragile (`(batchIdx - 1) * BATCH_SIZE + j`) — utiliser `i + j`.
252
+ - Verbosité booléenne threadée 4+ niveaux → `lib/log.js`.
253
+
254
+ ---
255
+
256
+ ## D. Efficiency
257
+
258
+ ### HIGH
259
+
260
+ - **CVE index loadé eagerly** (`lib/cve-download.js:299-301`) : `JSON.parse(readFileSync)` synchrone même pour un projet de 3 deps. Charger async + lazy par bucket.
261
+ - **`cpe.js` re-parse les CPE et walk 2 fois** (`cpe.js:194-245`) : `parseCpe23` appelé répétitivement, walk pour confidence après le walk d'évaluation. 2M+ string-splits sur un projet 800 deps × 50 matches.
262
+ → Cache `m._parsed ||= parseCpe23(m.criteria)` + retour du match satisfaisant.
263
+ - **NVD enrichment 100 % serial** (`nvd.js:186-203`) : `sleep(600|6000)` entre chaque appel. 200 CVEs uniques = 2 min avec clé, **20 min sans**. NIST policy = 50/30s window → permet parallélisme.
264
+ - **TOCTOU `existsSync` + `readFile`** dans `transitive.js:52`, `nvd.js:34`, `osv.js:33`, `retire.js:32-37`, `outdated.js:22-25`. Doublé syscalls.
265
+ - **`outdated.checkOutdatedDeps` cache reset global** (`outdated.js:230`) : `cache.entries = {}` si meta TTL périmé → 600 deps refetch même si entrées fraîches.
266
+ → TTL par-entrée.
267
+
268
+ ### MEDIUM
269
+
270
+ - `findEolProduct` re-sort la prefix-list dans la boucle (`outdated.js:46-52`) — sort once at module load.
271
+ - `outByKey` Map rebuild 6x dans `cve-report.js` (`:633-679`, `:760-795`, `:993-1003`) — builder once dans `buildBody`.
272
+ - CVE bulk JSON `readFileSync` per file (`cve-download.js:274-287`) — borné par cache 24h, MEDIUM.
273
+ - `cve-report.js` : `renderDetailPanel` (`:417-493`) + `groupExternalRefs` (`:340`) re-alloués par row → 45k allocations sur un report 5k rows. Pré-classifier à l'enrichment NVD.
274
+ - **POMs parsés 2x sur le chemin `rewritePoms`** (`core.js:50` puis re-read à `:219`). `structuredClone` ou skip re-parse en read-only.
275
+ - **`transitive.js:314` : `queue.shift()` O(n)** sur 4000+ transitives → 16M memmoves. Switch en cursor.
276
+ - **Maven Central : pas de batch endpoint** (`outdated.js:176`) — solrsearch accepte OR boolean. 600 deps → 1 requête au lieu de 600.
277
+ - **OSV detail fetch serial** dans `queryBatch` (`osv.js:218-236`) — `p-limit(10)` similaire à fad-checker.js.
278
+
279
+ ### LOW
280
+
281
+ - `getAllInheritedProps` rebuild via spread — fine en dessous de 10k POMs.
282
+ - `xml2js` 3-5x plus lent que `fast-xml-parser` — hors scope.
283
+ - Eager `require("chalk")` etc. — invisible.
284
+
285
+ ### Note positive
286
+ Le matcher CVE (`matchDepsAgainstCves`) est **Map-indexé correctement** (`byPackageName` / `byProduct` pré-bucketés) — pas de O(n²) sur le hot path.
287
+
288
+ ---
289
+
290
+ ## E. Recommandations priorisées
291
+
292
+ | # | Action | Severity | Effort | Impact |
293
+ |---|---|---|---|---|
294
+ | 1 | Patch `isVersionAffected` fail-closed (H1) | HIGH FP | 1 ligne | Énorme |
295
+ | 2 | Tighten `vendorMatchesGroup` substring (H2) | HIGH FP | 10 lignes | Énorme |
296
+ | 3 | Drop ou hide tier `possible` (H3) | HIGH FP | 5 lignes | Énorme |
297
+ | 4 | Strip `cpeFiltered:true` du report (H4) | HIGH FP | 3 lignes | Gros |
298
+ | 5 | Handle CPE `update` qualifier (H5) | HIGH FP | 15 lignes | Moyen |
299
+ | 6 | Skip `${...}` dans matcher (M1) | MED FP | 3 lignes | Moyen |
300
+ | 7 | OSV ranges re-eval local (M2) | MED FP | 30 lignes | Moyen |
301
+ | 8 | Extract `lib/severity.js` + `SEVERITY_RANK` | HIGH reuse | 1h | Maintenance |
302
+ | 9 | Extract `lib/cache-disk.js` | HIGH reuse | 1h | Maintenance |
303
+ | 10 | NVD enrichment parallèle (token-bucket) | HIGH perf | 30min | 10x speedup |
304
+ | 11 | Batch Maven Central Solr (#13 efficiency) | HIGH perf | 1h | 100x speedup |
305
+ | 12 | `parseCpe23` memoization (CPE perf) | HIGH perf | 10min | 2-3x speedup |
306
+ | 13 | Splitter `cve-report.js` en sous-modules | HIGH quality | 2h | Maintenance |
307
+ | 14 | Tests des edge cases FP (`update`, no-bounds, substring) | LOW | 2h | Filet de sécurité |
308
+
309
+ **Quick wins (< 1 jour cumulé)** : #1, #2, #3, #4, #6, #12 → diviserait le taux de FP par ~3-5 et accélérerait le rendu de ~2-3×.
310
+
311
+ ---
312
+
313
+ ## F. Verdict final
314
+
315
+ - **Architecture :** **bonne**. Séparation Maven/npm, 3-tier matching, post-CPE refinement, multi-source dedup, caches TTL — tout est en place.
316
+ - **Implémentation matcher :** **fail-open systémique**. Les 4 cascades H1→H4 transforment un outil correctement architecturé en générateur de faux positifs.
317
+ - **Performance :** correcte en CPU (Map-indexed), **mauvaise en I/O** (NVD serial, Solr unitaire, double-parse POM).
318
+ - **Qualité :** 2 mega-modules (`cve-report.js` 1455 LoC, `fad-checker.js` 665 LoC) à splitter, ~30 `catch {}` à auditer, 7+ duplications de `SEVERITY_RANK`.
319
+ - **Tests :** 96 tests existants mais happy-path-heavy ; aucune couverture des edge cases FP listés ci-dessus.
320
+
321
+ **Recommandation pratique :** sans les patches H1-H4, le rapport doit être traité comme une **liste de départ pour triage manuel**, pas comme un inventaire de vulnérabilités exploitable. Le tier `exact` reste fiable. Tout le reste demande des yeux humains sur la description CVE avant action.
322
+
323
+ ---
324
+
325
+ ## Annexe — Références fichier:ligne
326
+
327
+ **Faux positifs :**
328
+ - `lib/maven-version.js:25-94, 101-124, 131-146`
329
+ - `lib/cve-match.js:9, 99-109, 151-162, 164-173, 175-219, 191-198`
330
+ - `lib/cpe.js:25, 36-68, 81-100, 161-184, 194-245, 266, 269-272`
331
+ - `lib/osv.js:25-44, 60-66, 100-102, 188, 218-236, 242-265, 273`
332
+ - `lib/nvd.js:28-45, 64-71, 145, 186-203`
333
+ - `lib/retire.js:24-45, 77-81, 148-193`
334
+ - `lib/outdated.js:22-37, 46-52, 79-86, 168-187, 212-254`
335
+ - `lib/cve-download.js:82-89, 163-164, 274-301`
336
+ - `lib/transitive.js:51-101, 111-158, 204-235, 273-320`
337
+ - `lib/core.js:9-37, 49-149, 219-285`
338
+ - `lib/npm/{parse.js:76-291, collect.js:25-224}`
339
+ - `lib/cve-report.js:48-247, 340, 404-493, 519-521, 559, 633-1455`
340
+ - `fad-checker.js:25-150, 202, 259, 551, 628-665`
341
+ - `data/{cpe-coord-map.json, known-obsolete.json:92-96}`
342
+
343
+ **Tests manquants :** `test/{cpe,cve-match,maven-version}.test.js` — ajouter edge cases `update`, no-bounds, substring vendor leak, garbage version.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # fad-check
1
+ # fad-checker
2
2
 
3
3
  > **F**ucking **A**utonomous **D**ependency **C**hecker
4
4
 
5
- `fad-check` 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.
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-check
51
- fad-check -s ./my-project
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-check-report/cve-report.html`.
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-check --set-nvd-key YOUR_KEY
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-check -s ./proj
68
+ fad-checker -s ./proj
69
69
 
70
70
  # Exclude private/internal libs by groupId regex
71
- fad-check -s ./proj -e "^(com\.acme|org\.private)\."
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-check -s ./proj -t ../proj-clean -e "^com\.acme\."
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-check -s ./proj -t ../proj-clean -e "^com\.acme\." --snyk
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-check -s ./proj --no-all-libs --no-transitive
80
+ fad-checker -s ./proj --no-all-libs --no-transitive
81
81
 
82
82
  # Fully offline (uses cached data only)
83
- fad-check -s ./proj --offline
83
+ fad-checker -s ./proj --offline
84
84
 
85
85
  # Only one ecosystem
86
- fad-check -s ./proj --ecosystem maven # or npm | both | auto (default)
86
+ fad-checker -s ./proj --ecosystem maven # or npm | both | auto (default)
87
87
  ```
88
88
 
89
- Run `fad-check --help` for the full flag list.
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-check
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-check
141
- cd fad-check
140
+ git clone <repo-url> fad-checker
141
+ cd fad-checker
142
142
  npm install
143
- node fad-check.js --help
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-check-linux + dist/fad-check.exe
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-check --completion bash > /etc/bash_completion.d/fad-check
156
+ fad-checker --completion bash > /etc/bash_completion.d/fad-checker
157
157
  # or for zsh:
158
- fad-check --completion zsh > ~/.zsh/completions/_fad-check
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-check` against a *checkout* with no build environment.
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-check/`:
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-check --export-cache fad-cache.tar.gz
197
+ fad-checker --export-cache fad-cache.tar.gz
198
198
  # on the other box:
199
- fad-check --import-cache fad-cache.tar.gz
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-check` 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.
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-check/config.json)
212
- fad-check --add-repo nexus https://nexus.acme.com/repository/maven-public/
213
- fad-check --add-repo nexus-priv https://nexus.acme.com/repository/maven-private/ --auth alice:s3cr3t
214
- fad-check --list-repos
215
- fad-check --remove-repo nexus-priv
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-check -s ./proj --repo https://nexus.acme.com/repository/maven-public/
218
+ fad-checker -s ./proj --repo https://nexus.acme.com/repository/maven-public/
219
219
  # Inline auth in the URL also works:
220
- fad-check -s ./proj --repo https://alice:s3cr3t@nexus.acme.com/repository/maven-public/
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-check` is glue around several outstanding public datasets. Each is used per its license terms.
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-check-*` header.
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-check` adds |
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-check` will use any of them as input (`--snyk`) and merge results.
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-check (Fucking Autonomous Dependency Checker)
3
- # Source this file or copy it to /etc/bash_completion.d/fad-check
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-check
23
+ complete -F _fad_check_complete fad-checker
@@ -1,5 +1,5 @@
1
- #compdef fad-check
2
- # Zsh completion for fad-check (Fucking Autonomous Dependency Checker)
1
+ #compdef fad-checker
2
+ # Zsh completion for fad-checker (Fucking Autonomous Dependency Checker)
3
3
  _fad_check() {
4
4
  local -a opts
5
5
  opts=(