fad-checker 1.0.0

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.
Files changed (67) hide show
  1. package/CLAUDE.md +126 -0
  2. package/README.md +279 -0
  3. package/completions/fad-check.bash +23 -0
  4. package/completions/fad-check.zsh +27 -0
  5. package/data/cpe-coord-map.json +112 -0
  6. package/data/eol-mapping.json +34 -0
  7. package/data/known-obsolete.json +142 -0
  8. package/data/known-public-namespaces.json +79 -0
  9. package/docs/ARCHITECTURE.md +152 -0
  10. package/docs/USAGE.md +179 -0
  11. package/fad-check.js +665 -0
  12. package/lib/cache-archive.js +107 -0
  13. package/lib/config.js +87 -0
  14. package/lib/core.js +317 -0
  15. package/lib/cpe.js +287 -0
  16. package/lib/cve-download.js +369 -0
  17. package/lib/cve-match.js +228 -0
  18. package/lib/cve-report.js +1455 -0
  19. package/lib/maven-repo.js +134 -0
  20. package/lib/maven-version.js +153 -0
  21. package/lib/npm/collect.js +224 -0
  22. package/lib/npm/parse.js +291 -0
  23. package/lib/nvd.js +239 -0
  24. package/lib/osv.js +298 -0
  25. package/lib/outdated.js +265 -0
  26. package/lib/retire.js +211 -0
  27. package/lib/scan-completeness.js +67 -0
  28. package/lib/snyk.js +127 -0
  29. package/lib/transitive.js +410 -0
  30. package/package.json +35 -0
  31. package/test/core.test.js +153 -0
  32. package/test/cpe.test.js +148 -0
  33. package/test/cve-download.test.js +39 -0
  34. package/test/cve-match.test.js +108 -0
  35. package/test/cve-report.test.js +79 -0
  36. package/test/fixtures/complex-enterprise/api/pom.xml +32 -0
  37. package/test/fixtures/complex-enterprise/build/pom.xml +46 -0
  38. package/test/fixtures/complex-enterprise/dao/pom.xml +37 -0
  39. package/test/fixtures/complex-enterprise/pom.xml +66 -0
  40. package/test/fixtures/complex-enterprise/web/pom.xml +77 -0
  41. package/test/fixtures/cve-samples/cve-non-java.json +19 -0
  42. package/test/fixtures/cve-samples/cve-product-only.json +31 -0
  43. package/test/fixtures/cve-samples/cve-with-packagename.json +37 -0
  44. package/test/fixtures/cve-samples/nvd-log4shell.json +40 -0
  45. package/test/fixtures/cve-samples/nvd-npm-lodash.json +22 -0
  46. package/test/fixtures/monorepo-mixed/libs/common-bom/pom.xml +26 -0
  47. package/test/fixtures/monorepo-mixed/packages/cli/package.json +14 -0
  48. package/test/fixtures/monorepo-mixed/packages/cli/yarn.lock +41 -0
  49. package/test/fixtures/monorepo-mixed/packages/no-lock/package.json +9 -0
  50. package/test/fixtures/monorepo-mixed/packages/web-app/package-lock.json +71 -0
  51. package/test/fixtures/monorepo-mixed/packages/web-app/package.json +17 -0
  52. package/test/fixtures/monorepo-mixed/pom.xml +29 -0
  53. package/test/fixtures/monorepo-mixed/services/api/pom.xml +27 -0
  54. package/test/fixtures/monorepo-mixed/services/worker/pom.xml +28 -0
  55. package/test/fixtures/private-lib-detection/core/pom.xml +26 -0
  56. package/test/fixtures/private-lib-detection/plugin/pom.xml +23 -0
  57. package/test/fixtures/private-lib-detection/pom.xml +35 -0
  58. package/test/fixtures/simple/app/pom.xml +28 -0
  59. package/test/fixtures/simple/lib/pom.xml +18 -0
  60. package/test/fixtures/simple/pom.xml +24 -0
  61. package/test/maven-repo.test.js +111 -0
  62. package/test/maven-version.test.js +48 -0
  63. package/test/monorepo.test.js +132 -0
  64. package/test/npm.test.js +146 -0
  65. package/test/outdated.test.js +56 -0
  66. package/test/snyk.test.js +64 -0
  67. package/test/transitive.test.js +305 -0
@@ -0,0 +1,142 @@
1
+ {
2
+ "log4j:log4j": {
3
+ "severity": "CRITICAL",
4
+ "replacement": "org.apache.logging.log4j:log4j-core (2.x)",
5
+ "reason": "Log4j 1.x reached EOL 2015. Multiple unpatched CVEs."
6
+ },
7
+ "commons-logging:commons-logging": {
8
+ "severity": "MEDIUM",
9
+ "replacement": "org.slf4j:slf4j-api + jcl-over-slf4j",
10
+ "reason": "Apache Commons Logging is largely superseded by SLF4J. Classloader leak issues."
11
+ },
12
+ "commons-io:commons-io": {
13
+ "severity": "MEDIUM",
14
+ "replacement": "org.apache.commons:commons-io (2.x+)",
15
+ "reason": "The pre-2.x coordinate is deprecated; use the org.apache.commons groupId."
16
+ },
17
+ "javax.servlet:servlet-api": {
18
+ "severity": "MEDIUM",
19
+ "replacement": "jakarta.servlet:jakarta.servlet-api",
20
+ "reason": "javax.* namespace moved to jakarta.* in Jakarta EE 9+."
21
+ },
22
+ "javax.persistence:persistence-api": {
23
+ "severity": "MEDIUM",
24
+ "replacement": "jakarta.persistence:jakarta.persistence-api",
25
+ "reason": "javax.* → jakarta.* migration."
26
+ },
27
+ "javax.xml.bind:jaxb-api": {
28
+ "severity": "MEDIUM",
29
+ "replacement": "jakarta.xml.bind:jakarta.xml.bind-api",
30
+ "reason": "Removed from the JDK in Java 11; use Jakarta XML Binding."
31
+ },
32
+ "org.codehaus.jackson:jackson-core-asl": {
33
+ "severity": "HIGH",
34
+ "replacement": "com.fasterxml.jackson.core:jackson-core",
35
+ "reason": "Jackson 1.x is EOL since 2014. Has known unpatched CVEs."
36
+ },
37
+ "org.codehaus.jackson:jackson-mapper-asl": {
38
+ "severity": "HIGH",
39
+ "replacement": "com.fasterxml.jackson.core:jackson-databind",
40
+ "reason": "Jackson 1.x is EOL. Multiple deserialization CVEs."
41
+ },
42
+ "xerces:xerces": {
43
+ "severity": "MEDIUM",
44
+ "replacement": "xerces:xercesImpl",
45
+ "reason": "Old Xerces coordinate; use xercesImpl."
46
+ },
47
+ "xml-apis:xml-apis": {
48
+ "severity": "LOW",
49
+ "replacement": "(part of JDK since Java 5)",
50
+ "reason": "XML APIs are bundled with the JDK."
51
+ },
52
+ "net.sf.json-lib:json-lib": {
53
+ "severity": "MEDIUM",
54
+ "replacement": "com.fasterxml.jackson.core:jackson-databind",
55
+ "reason": "json-lib hasn't been updated since 2010."
56
+ },
57
+ "org.json:json": {
58
+ "severity": "LOW",
59
+ "replacement": "com.fasterxml.jackson.core:jackson-databind",
60
+ "reason": "Performance issues, restrictive license. Prefer Jackson."
61
+ },
62
+ "com.googlecode.json-simple:json-simple": {
63
+ "severity": "MEDIUM",
64
+ "replacement": "com.fasterxml.jackson.core:jackson-databind",
65
+ "reason": "Unmaintained since 2012."
66
+ },
67
+ "joda-time:joda-time": {
68
+ "severity": "LOW",
69
+ "replacement": "java.time (JDK 8+)",
70
+ "reason": "Joda-Time author recommends java.time on JDK 8+."
71
+ },
72
+ "javax.mail:mail": {
73
+ "severity": "MEDIUM",
74
+ "replacement": "com.sun.mail:jakarta.mail or jakarta.mail:jakarta.mail-api",
75
+ "reason": "javax.mail is the old coordinate; moved to Jakarta Mail."
76
+ },
77
+ "javax.activation:activation": {
78
+ "severity": "MEDIUM",
79
+ "replacement": "jakarta.activation:jakarta.activation-api",
80
+ "reason": "javax.* → jakarta.* migration; also removed from JDK in 11."
81
+ },
82
+ "javax.annotation:javax.annotation-api": {
83
+ "severity": "MEDIUM",
84
+ "replacement": "jakarta.annotation:jakarta.annotation-api",
85
+ "reason": "javax.* → jakarta.* migration."
86
+ },
87
+ "struts:struts": {
88
+ "severity": "CRITICAL",
89
+ "replacement": "(framework rewrite recommended; Struts 1.x is EOL with critical CVEs)",
90
+ "reason": "Apache Struts 1.x reached EOL April 2013."
91
+ },
92
+ "org.apache.struts:struts2-core": {
93
+ "severity": "HIGH",
94
+ "replacement": "(audit version: many critical RCEs in Struts 2.x)",
95
+ "reason": "Many versions of Struts 2 have critical RCE CVEs (CVE-2017-5638 etc). Verify version is patched."
96
+ },
97
+ "axis:axis": {
98
+ "severity": "MEDIUM",
99
+ "replacement": "org.apache.cxf:cxf-rt-frontend-jaxws",
100
+ "reason": "Apache Axis 1.x is EOL since 2006. Apache CXF is the modern alternative."
101
+ },
102
+ "net.sf.ehcache:ehcache": {
103
+ "severity": "LOW",
104
+ "replacement": "org.ehcache:ehcache (3.x)",
105
+ "reason": "Ehcache 2.x is in maintenance only; use Ehcache 3.x."
106
+ },
107
+ "c3p0:c3p0": {
108
+ "severity": "LOW",
109
+ "replacement": "com.zaxxer:HikariCP",
110
+ "reason": "c3p0 maintained but HikariCP is the recommended modern connection pool."
111
+ },
112
+ "commons-httpclient:commons-httpclient": {
113
+ "severity": "MEDIUM",
114
+ "replacement": "org.apache.httpcomponents.client5:httpclient5",
115
+ "reason": "Apache Commons HttpClient 3.x is EOL since 2011."
116
+ },
117
+ "org.apache.httpcomponents:httpclient": {
118
+ "severity": "MEDIUM",
119
+ "replacement": "org.apache.httpcomponents.client5:httpclient5",
120
+ "reason": "HttpClient 4.x in maintenance; HttpClient 5.x is the active line."
121
+ },
122
+ "junit:junit": {
123
+ "severity": "LOW",
124
+ "replacement": "org.junit.jupiter:junit-jupiter (JUnit 5)",
125
+ "reason": "JUnit 4 is in maintenance; JUnit 5 is the modern line."
126
+ },
127
+ "org.hamcrest:hamcrest-all": {
128
+ "severity": "LOW",
129
+ "replacement": "org.hamcrest:hamcrest",
130
+ "reason": "hamcrest-all is deprecated; use the single 'hamcrest' artifact."
131
+ },
132
+ "org.springframework:spring-asm": {
133
+ "severity": "LOW",
134
+ "replacement": "(merged into spring-core)",
135
+ "reason": "Removed in Spring 3.2+."
136
+ },
137
+ "asm:asm": {
138
+ "severity": "LOW",
139
+ "replacement": "org.ow2.asm:asm",
140
+ "reason": "Old asm coordinate; use org.ow2.asm."
141
+ }
142
+ }
@@ -0,0 +1,79 @@
1
+ {
2
+ "namespaces": [
3
+ "org.apache",
4
+ "org.springframework",
5
+ "org.springframework.boot",
6
+ "org.springframework.security",
7
+ "org.springframework.cloud",
8
+ "org.springframework.data",
9
+ "com.fasterxml",
10
+ "com.fasterxml.jackson.core",
11
+ "com.fasterxml.jackson.dataformat",
12
+ "com.fasterxml.jackson.module",
13
+ "com.google.guava",
14
+ "com.google.code.gson",
15
+ "com.google.protobuf",
16
+ "io.netty",
17
+ "io.projectreactor",
18
+ "org.hibernate",
19
+ "org.hibernate.orm",
20
+ "org.hibernate.validator",
21
+ "org.jboss",
22
+ "org.jboss.logging",
23
+ "org.wildfly",
24
+ "org.eclipse",
25
+ "org.eclipse.jetty",
26
+ "org.eclipse.persistence",
27
+ "org.slf4j",
28
+ "ch.qos.logback",
29
+ "org.apache.logging.log4j",
30
+ "org.apache.commons",
31
+ "org.apache.httpcomponents",
32
+ "org.apache.httpcomponents.client5",
33
+ "org.apache.maven",
34
+ "org.apache.tomcat",
35
+ "org.apache.tomcat.embed",
36
+ "org.apache.cxf",
37
+ "org.apache.struts",
38
+ "org.apache.poi",
39
+ "org.apache.kafka",
40
+ "org.apache.activemq",
41
+ "org.apache.camel",
42
+ "jakarta",
43
+ "jakarta.servlet",
44
+ "jakarta.persistence",
45
+ "jakarta.xml.bind",
46
+ "jakarta.annotation",
47
+ "jakarta.activation",
48
+ "jakarta.mail",
49
+ "junit",
50
+ "org.junit",
51
+ "org.junit.jupiter",
52
+ "org.junit.platform",
53
+ "org.testng",
54
+ "org.mockito",
55
+ "org.hamcrest",
56
+ "org.assertj",
57
+ "org.ow2.asm",
58
+ "io.swagger",
59
+ "io.swagger.core.v3",
60
+ "io.micrometer",
61
+ "io.opentelemetry",
62
+ "io.opentracing",
63
+ "io.grpc",
64
+ "com.squareup",
65
+ "com.squareup.okhttp3",
66
+ "com.squareup.retrofit2",
67
+ "com.zaxxer",
68
+ "redis.clients",
69
+ "org.postgresql",
70
+ "mysql",
71
+ "com.mysql",
72
+ "com.oracle.database.jdbc",
73
+ "com.h2database",
74
+ "org.hsqldb",
75
+ "org.mariadb.jdbc",
76
+ "org.mongodb",
77
+ "com.datastax.oss"
78
+ ]
79
+ }
@@ -0,0 +1,152 @@
1
+ # Architecture
2
+
3
+ This is the deep-dive for anyone modifying `fad-check`'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
+
5
+ ## Module map
6
+
7
+ ```
8
+ fad-check.js Thin CLI: commander parsing, orchestration only.
9
+ lib/core.js POM parsing, parent resolution, all-profile merge, rewrite.
10
+ lib/maven-version.js Maven version parsing + range comparison (no external deps).
11
+ lib/cve-download.js Bulk download of CVEProject/cvelistV5 + Maven-relevant index build.
12
+ lib/cve-match.js Resolved-dep collection + 3-tier CVE matching with dedup.
13
+ lib/cve-report.js Self-contained HTML and Word-compatible (.doc) report rendering.
14
+ lib/cpe.js CPE 2.3 parsing + NVD configurations evaluator (post-match refinement).
15
+ lib/outdated.js EOL (endoflife.date), obsolete (curated), outdated (Maven Central).
16
+ lib/transitive.js Maven Central POM walker (transitive resolution).
17
+ lib/osv.js OSV.dev batched query + per-vuln detail fetch.
18
+ lib/nvd.js NIST NVD enrichment (CVSS, references, CPE configurations).
19
+ lib/snyk.js `snyk test --all-projects --json` runner + merge.
20
+ lib/retire.js retire.js (vendored-JS scanner) wrapper + cache + normaliser.
21
+ lib/scan-completeness.js Warnings for deps we couldn't fully resolve.
22
+ lib/npm/parse.js package.json, package-lock.json (v1/2/3), yarn.lock v1 parsers.
23
+ lib/npm/collect.js Merge across JS manifests → unified resolvedDeps Map.
24
+ lib/cache-archive.js tar.gz / zip export & import of ~/.fad-check/.
25
+ lib/config.js Persistent user config in ~/.fad-check/config.json (mode 0600).
26
+ data/ Curated JSON: known-obsolete, eol-mapping, cpe-coord-map, known-public-namespaces.
27
+ completions/ fad-check.bash, fad-check.zsh
28
+ test/ node:test suite + fixtures (simple, complex-enterprise, monorepo-mixed, …).
29
+ ```
30
+
31
+ ## The resolved-deps Map
32
+
33
+ The whole pipeline hinges on a single `Map<string, depRecord>` keyed by:
34
+ - `groupId:artifactId` for Maven entries
35
+ - `npm:<name>` for npm/yarn entries
36
+
37
+ Each `depRecord` carries:
38
+
39
+ ```js
40
+ {
41
+ groupId, artifactId, version,
42
+ scope, // "compile" | "test" | "import" | "transitive" | "parent" | "prod" | "dev" | "peer" | "optional"
43
+ isDev, // Maven test/provided OR npm dev/devOptional/optional
44
+ ecosystem, // "maven" | "npm"
45
+ ecosystemType, // "maven" | "npm" | "yarn" | "retire"
46
+ pomPaths, // absolute paths to manifests declaring this dep
47
+ manifestPaths, // same as pomPaths but used by the npm collector
48
+ // Transitive-only:
49
+ via, viaPaths, depth,
50
+ // npm-only:
51
+ lockType, resolved, integrity,
52
+ }
53
+ ```
54
+
55
+ The Maven keyspace and npm keyspace never collide — `:lodash` (Maven groupId-less) becomes `npm:lodash` so the same Map can hold both ecosystems without overwrites.
56
+
57
+ ## Cleanup pipeline (`lib/core.js`)
58
+
59
+ 1. `findPomFiles(src)` — recursive walk, skips known output dirs (`target/`, `node_modules/`, `.git/`, `.idea/`, `dist/`, `build-output/`, `out/`, `.next/`, `.nuxt/`, `coverage/`, `.gradle/`, `.mvn/`, `.vscode/`, `bower_components/`, `jspm_packages/`). Note: `build/` is **not** skipped on the Maven side because some multi-module projects use it for a BOM module.
60
+ 2. `parsePom()` — xml2js to JSON. Extracts groupId/artifactId/version, `<parent>`, properties, and indexes every profile (recording which one is `activeByDefault`). Templates with literal `\${…}` are skipped.
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
+ 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
+
64
+ ## Report pipeline (driven by `fad-check.js` when `--report` is set)
65
+
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
+ 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-check/cve-data/maven-cve-index.json`. Fresh for 24h. `--cve-refresh` forces rebuild, `--cve-offline` uses cache only.
69
+ 4. **CVE matching** — `matchDepsAgainstCves()` runs three tiers:
70
+ - `exact`: `byPackageName["g:a"]` hit
71
+ - `probable`: `byProduct[artifactId]` + vendor matches groupId (`apache` ↔ `org.apache.*`)
72
+ - `possible`: product-only match
73
+ Dedupes by `(dep, cve.id)` and sorts by severity. npm deps are skipped here — they're scanned by OSV instead.
74
+ 5. **OSV** (default on) — `queryOsvForDeps()` POSTs batched queries to `api.osv.dev/v1/querybatch` (Maven ecosystem for Maven deps, npm ecosystem for npm deps). Per-dep stub list cached 12h; per-vuln details cached 12h.
75
+ 6. **NVD enrichment** (default on) — for every CVE id matched, fetch the full NVD record (description, CVSS vectors, references categorised by tag, CPE configurations). Rate-limited per NIST policy (5/30s unauthenticated, 50/30s with `NVD_API_KEY`).
76
+ 7. **CPE refinement** — `refineMatchesWithCpe()` walks NVD's `configurations[].nodes[]` against each matched dep:
77
+ - Confirms the dep version actually falls in the vulnerable range (else `cpeFiltered: true` — likely false positive).
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-check match shape, with the vendored file path attached so the report can show where the offending `.js` lives. Cache: `~/.fad-check/retire-cache/<md5(src)>.json`, 24h TTL.
80
+ 9. **EOL / Obsolete / Outdated** — `lib/outdated.js`:
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
+ - **Obsolete**: lookup in `data/known-obsolete.json` (curated: log4j 1.x, jackson-mapper-asl, joda-time, commons-httpclient 3.x, …).
83
+ - **Outdated**: Maven Central Solr query for the latest version. Cache 24h. Concurrency 8.
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-check-report/`.
86
+
87
+ ## Report structure
88
+
89
+ ```
90
+ <Executive Summary> ← global criticality + key bullet counts
91
+ <Summary cards> ← critical / high / medium / low / EOL / obsolete / outdated
92
+ <Toolbar> ← expand-all / collapse-all / expand CVE details
93
+
94
+ 0. Warnings & scan-completeness ← chapter 0 if any warnings
95
+ 1. CVE Vulnerabilities — production (N)
96
+ 1.a Maven (n)
97
+ 1.a.0 All (n) ← combined direct + transitive
98
+ By pom.xml (k files) ← wrapper always present
99
+ <relative-path-1> (m) ← direct deps in this pom only
100
+ <relative-path-2> (m)
101
+
102
+ 1.b npm (package-lock) (n)
103
+ 1.b.0 All (n)
104
+ By package-lock.json (k files)
105
+ web/package-lock.json (m)
106
+ 2. CVE in dev dependencies (M) ← same structure as 1
107
+ 3. Vendored JS scan — retire.js (R)
108
+ 4. End-of-Life Frameworks
109
+ 5. Obsolete / Deprecated Libraries
110
+ 6. Outdated Libraries
111
+ 7. Fix Recommendations ← per-ecosystem snippets
112
+ 7.a Maven ← dependencyManagement XML
113
+ 7.b npm ← package.json overrides
114
+ 7.c yarn ← package.json resolutions
115
+ ```
116
+
117
+ ## Important conventions
118
+
119
+ - **`coord()` always trims**: real-world POMs occasionally contain whitespace around `<artifactId>` (seen in the wild). Every coord-derived lookup goes through `coord()` in `lib/core.js`.
120
+ - **`byId` keys are never polluted with `undefined`**: we only index a POM by id when both `groupId` and `artifactId` are present. Test `byId does not get polluted with undefined keys` enforces this.
121
+ - **All profiles are merged, never prompted for**: previous versions prompted the user when a POM had multiple profiles. We now union every profile's deps so Snyk sees every dep any profile could pull in. `activeByDefault` wins only for property value conflicts.
122
+ - **No `process.exit(1)` mid-pipeline**: a parse/rewrite failure for one POM logs and continues so the summary still prints.
123
+ - **HTML report is self-contained**: inline CSS, no external assets. The `.doc` variant is the same HTML with Office XML namespace meta tags — Word opens it natively.
124
+ - **Map keys are ecosystem-namespaced**: Maven uses `g:a`, npm uses `npm:name`. They never collide so they can share one resolved-deps Map.
125
+ - **Lockfile-only npm**: `package.json` without sibling `package-lock.json`/`yarn.lock` is intentionally skipped (its ranges aren't queryable) and reported in chapter 0. Avoids false negatives on deps that haven't been installed yet.
126
+ - **Source identifiers**: every match carries `source: "fad" | "osv" | "nvd" | "snyk" | "retire"` (or a `+`-joined combination like `"fad+osv+nvd"`). The legacy "mbdc" identifier was renamed to "fad" in 3.0.
127
+
128
+ ## Gotchas / edge cases worth knowing
129
+
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
+ - The bundle ships as `cves.zip.zip` (a zip whose sole content is another zip). `extractZip()` recurses up to 3 levels.
132
+ - `endoflife.date` API responses are cached locally for 7 days; Maven Central version lookups for 24 hours.
133
+ - **Persistent config**: `~/.fad-check/config.json` (mode 0600) stores per-user state, currently the NVD API key. Set via `fad-check --set-nvd-key <KEY>`.
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
+ - `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
+ - 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.
137
+ - Unresolved `${…}` Maven variables are kept verbatim in the rewritten POM. `lib/cve-match.js` resolves them lazily via `resolveDepVersion()` when collecting deps for the scan. Deps that *still* can't be resolved (external BOM) are surfaced in chapter 0 as `unresolved-versions` warnings.
138
+ - **Per-cache TTLs** are documented in the README's "Caching" table.
139
+
140
+ ## Testing
141
+
142
+ ```bash
143
+ npm test # full suite (96 tests)
144
+ node --test test/core.test.js # one file
145
+ ```
146
+
147
+ Test fixtures live in `test/fixtures/`:
148
+ - `simple/` — 3 POMs with parent inheritance + property substitution
149
+ - `complex-enterprise/` — Spring Boot parent (external), local BOM via `scope=import`, three profiles (two of which inject env-specific JDBC drivers), test-scoped JUnit, jackson-databind via BOM-managed version
150
+ - `private-lib-detection/` — mixed public/private groupIds and an externally-hosted private parent — verifies missing-parent tracking
151
+ - `monorepo-mixed/` — combined Maven (4 POMs: parent + BOM + 2 modules) + JS (npm package-lock v3 + yarn.lock v1 + a no-lockfile package.json to test the warning)
152
+ - `cve-samples/` — small CVE / NVD JSON files to exercise the matchers without the 500 MB real bundle
package/docs/USAGE.md ADDED
@@ -0,0 +1,179 @@
1
+ # USAGE
2
+
3
+ Every flag, every common workflow, with copy-pasteable commands.
4
+
5
+ ## Synopsis
6
+
7
+ ```text
8
+ fad-check -s <src> [-t <target>] [-e <regex>] [other options]
9
+ ```
10
+
11
+ - `-s, --src <src>` — **required**. Root of the source tree to scan. Contains `pom.xml` and/or `package(-lock).json` / `yarn.lock`.
12
+ - `-t, --target <dir>` — optional. If given, write a parallel directory of "cleaned" POMs (private/excluded deps stripped) to `<dir>` — useful as Snyk input. Without `-t`, the run is read-only.
13
+
14
+ ## Output
15
+
16
+ By default the HTML + Word reports land in `./fad-check-report/`. Override with `--report-output <dir>`.
17
+
18
+ ## Ecosystem selection
19
+
20
+ ```bash
21
+ # Auto-detect (default): scan whatever pom.xml / package(-lock).json / yarn.lock exists
22
+ fad-check -s .
23
+
24
+ # Force one ecosystem
25
+ fad-check -s . --ecosystem maven
26
+ fad-check -s . --ecosystem npm
27
+ fad-check -s . --ecosystem both # scan both even if only one is auto-detected
28
+
29
+ # Disable JS scanning entirely (Maven-only)
30
+ fad-check -s . --no-js
31
+ ```
32
+
33
+ ## Filtering deps
34
+
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
+
37
+ ```bash
38
+ fad-check -s . -e "^(com\.acme|org\.private)\."
39
+ fad-check -s . -e "^@acme/"
40
+ ```
41
+
42
+ The excluded coords are listed at the end of the run so you can audit the regex.
43
+
44
+ ## Per-source toggles
45
+
46
+ Each data source can be disabled independently:
47
+
48
+ | Flag | Effect |
49
+ | --- | --- |
50
+ | `--no-report` | Skip the report flow entirely (just cleanup) |
51
+ | `--no-transitive` | Don't fetch transitive Maven deps from Maven Central |
52
+ | `--no-all-libs` | Don't query Maven Central for latest versions (skips chapter 6 Outdated and the "missing on Central" check) |
53
+ | `--no-osv` | Skip OSV.dev (Google + GitHub aggregated feed) |
54
+ | `--no-nvd` | Skip NVD enrichment (no full CVSS, no CPE refinement) |
55
+ | `--no-retire` | Skip retire.js vendored-JS scan |
56
+ | `--ignore-test` | Drop test-scoped Maven deps and dev npm deps from the scan entirely (chapter 2 will be empty) |
57
+
58
+ ## Offline / cache control
59
+
60
+ ```bash
61
+ # Use cached data only, no network (works for everything)
62
+ fad-check -s . --offline
63
+
64
+ # Per-source offline
65
+ fad-check -s . --cve-offline # use cached CVE index only
66
+ fad-check -s . --cve-refresh # force re-download of CVE bundle
67
+ fad-check -s . --retire-refresh # force re-scan with retire.js (ignore cache)
68
+
69
+ # Cache export / import (useful for air-gapped boxes)
70
+ fad-check --export-cache fad-cache.tar.gz
71
+ fad-check --export-cache fad-cache.tar.gz --include-config # bundle NVD key too
72
+ fad-check --import-cache fad-cache.tar.gz
73
+ fad-check --import-cache fad-cache.tar.gz --force # replace existing without backup
74
+ ```
75
+
76
+ ## NVD API key
77
+
78
+ NVD's public rate limit is 5 requests / 30s without a key. The free key bumps it to 50 / 30s — **10× faster** for the enrichment step.
79
+
80
+ ```bash
81
+ # Get a key in 30 seconds: https://nvd.nist.gov/developers/request-an-api-key
82
+ fad-check --set-nvd-key YOUR_KEY # stored in ~/.fad-check/config.json (mode 0600)
83
+ fad-check --show-config # confirm it's persisted (key masked)
84
+ ```
85
+
86
+ Or pass it ad-hoc via the `NVD_API_KEY` env var.
87
+
88
+ ## Snyk integration
89
+
90
+ If you have `snyk` installed and authenticated, `fad-check` can drive it:
91
+
92
+ ```bash
93
+ fad-check -s ./proj -t ../proj-clean -e "^com\.acme\." --snyk
94
+ ```
95
+
96
+ This:
97
+ 1. Generates the cleaned POM tree at `../proj-clean/`.
98
+ 2. Runs `snyk test --all-projects --json` against it.
99
+ 3. Merges Snyk's findings into the report — entries present in both `fad-check` and Snyk are tagged `source: "both"`.
100
+
101
+ `--snyk` requires `-t` (Snyk needs a real POM tree to scan).
102
+
103
+ ## Read-only vs write mode
104
+
105
+ | Mode | Trigger | Disk writes |
106
+ | --- | --- | --- |
107
+ | Read-only | `-t` omitted (default) | Only `~/.fad-check/` caches and the report dir |
108
+ | Write | `-t <dir>` provided | Above + the cleaned POM tree at `<dir>` (and `<dir>` is `rimraf`'d first!) |
109
+
110
+ The `--target` guardrails refuse:
111
+ - empty `--src`
112
+ - `--target` equal to or a subdirectory of `--src`
113
+
114
+ ## Verbosity
115
+
116
+ ```bash
117
+ fad-check -s . -v # progress per source (OSV batches, NVD pages, retire scan, …)
118
+ ```
119
+
120
+ ## Shell completion
121
+
122
+ ```bash
123
+ fad-check --completion bash > /etc/bash_completion.d/fad-check
124
+ fad-check --completion zsh > ~/.zsh/completions/_fad-check
125
+ ```
126
+
127
+ ## All flags at a glance
128
+
129
+ ```bash
130
+ fad-check --help
131
+ ```
132
+
133
+ ## Recipes
134
+
135
+ ### CI gate: fail the build on any CRITICAL prod CVE
136
+
137
+ `fad-check` exits 0 even when CVEs are found (it's a reporter, not a gate). Wire your own:
138
+
139
+ ```bash
140
+ fad-check -s . --no-nvd > /dev/null
141
+ # Then grep the report or parse the structured output (planned).
142
+ ```
143
+
144
+ (A `--fail-on critical` flag is a planned addition — track it in issues.)
145
+
146
+ ### Diff two runs
147
+
148
+ Keep dated copies of the report:
149
+
150
+ ```bash
151
+ fad-check -s . --report-output reports/$(date +%F)
152
+ diff reports/2026-04-01/cve-report.html reports/2026-05-01/cve-report.html
153
+ ```
154
+
155
+ ### Air-gapped scan
156
+
157
+ On a connected machine:
158
+
159
+ ```bash
160
+ fad-check -s ./dummy-empty-dir # populates ~/.fad-check/ caches
161
+ fad-check --export-cache fad-cache.tar.gz --include-config
162
+ ```
163
+
164
+ Move `fad-cache.tar.gz` to the air-gapped box, then:
165
+
166
+ ```bash
167
+ fad-check --import-cache fad-cache.tar.gz
168
+ fad-check -s ./real-project --offline
169
+ ```
170
+
171
+ ### Monorepo with Maven + JS + vendored JS
172
+
173
+ The melino-style project (Java backend, React frontend in `web/`, vendored jQuery/PDF.js under `web/src/main/webapp/`):
174
+
175
+ ```bash
176
+ fad-check -s . -e "^(com\.captcha|org\.voxaly|com\.voxaly)\."
177
+ # → finds CVE in Maven deps, in web/package-lock.json deps,
178
+ # AND in the vendored .js files under webapp/.
179
+ ```