fad-checker 2.4.3 → 2.4.5

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/AI_POLICY.md ADDED
@@ -0,0 +1,74 @@
1
+ # AI policy
2
+
3
+ ## How this project is built
4
+
5
+ `fad-checker` is based on a personal project then enhanced with heavy use of Claude Code. `CLAUDE.md` in the repo root is exactly
6
+ what it looks like: the orientation map the assistant works from.
7
+
8
+ I would rather state that plainly than have someone discover it and wonder what else was not
9
+ said. For a security tool the provenance of the code is a fair question, so here is what the
10
+ work is actually held to.
11
+
12
+ ## What the code is held to
13
+
14
+ None of this is a claim about how the code was written. All of it is checkable by you:
15
+
16
+ - **606 tests**, run with `npm test`. No test touches the network.
17
+ - **`test/offline-guarantee.test.js`** is a tripwire: it installs a fetcher that throws if
18
+ anything reaches for the network under `--offline`, on a cold cache. The zero-network
19
+ guarantee is a test, not a promise. It is also reproducible outside the suite:
20
+ `unshare -rn node fad-checker.js -s ./proj --offline` runs in a namespace with no network
21
+ interface at all.
22
+ - **Incremental history.** The work is spread across the commit log, not dropped in one
23
+ initial commit.
24
+ - **Measured, not asserted.** Coverage numbers in the docs come from comparison against a
25
+ Snyk baseline on a real multi-module project, with the methodology stated where they appear.
26
+ - **Fixtures come from real-world shapes.** Parsing behaviour is locked by a fixture in
27
+ `test/fixtures/`, derived from a manifest layout that actually occurs, not from an
28
+ invented one.
29
+
30
+ The honest counterweight: this is a young single-maintainer project and it has false positives
31
+ and false negatives. The [README says so](README.md), and the
32
+ [accuracy issue template](.github/ISSUE_TEMPLATE/false_positive.yml) exists because those
33
+ reports are the most valuable thing the project can receive.
34
+
35
+ ## Where review actually mattered
36
+
37
+ One example, because it is the kind of failure that matters for a scanner.
38
+
39
+ The first implementation of per-module Maven version mediation
40
+ (`lib/version-overlay.js`) re-resolved each module using only that module's own
41
+ `<dependencies>`. That is wrong: Maven inherits `<dependencies>` from the parent chain into
42
+ every child, where they stay direct and beat any transitive of the same coordinate. The
43
+ consequence was that a parent-declared, reactor-wide remediation was invisible, so the overlay
44
+ "recovered" the very version that remediation overrides, and reported CVEs against a version
45
+ no classpath holds.
46
+
47
+ Fabricating findings is worse for a security tool than missing them. The fix walks the local
48
+ parent chain, and `test/version-overlay-inherited-direct.test.js` locks the behaviour in both
49
+ directions: the masked version must surface, and the overridden one must not.
50
+
51
+ The general rule this project follows: a plausible-looking finding is not a finding. Unresolved
52
+ versions are excluded from matching rather than assumed vulnerable, CPE configurations are
53
+ cross-checked against the actual version, and there is no `!version means affected` shortcut
54
+ anywhere in the matcher.
55
+
56
+ ## What is expected of contributions
57
+
58
+ The same bar, regardless of how the code was produced:
59
+
60
+ - A test that fails before the change and passes after it.
61
+ - For parsing changes, a fixture derived from a real-world shape.
62
+ - No claim in the docs that is not backed by something a reader can run.
63
+ - You understand the change well enough to explain it in review without assistance.
64
+
65
+ Use whatever tools you like to get there. What is not welcome is a change whose author cannot
66
+ answer questions about it, because reviewing that costs more than writing it did. See
67
+ [`CONTRIBUTING.md`](CONTRIBUTING.md).
68
+
69
+ ## Reports generated by this tool
70
+
71
+ `fad-checker` uses **no LLM at runtime**. Findings come from public vulnerability databases
72
+ (CVEProject, OSV.dev, NVD), registry metadata and deterministic parsers. Severity, priority and
73
+ version matching are computed, not generated. Nothing in a report is written by a language
74
+ model, and the tool makes no network call to any AI service, ever.
package/CHANGELOG.md CHANGED
@@ -5,7 +5,178 @@ This project adheres to [Semantic Versioning](https://semver.org/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ### Added
9
+ - **`--nvd-cpe-match` (opt-in, off by default): match dependencies against NVD's CPE version
10
+ ranges.** OSV/GHSA declare affected ranges per release *branch*; NVD declares them for every
11
+ affected branch. For `CVE-2020-9546`, OSV covers 2.9.0–2.9.10.4 while NVD also covers
12
+ 2.0.0–2.7.9.7 — so `jackson-databind:2.5.2` is affected and never got a fix. fad already had
13
+ that data in its NVD cache and only ever used it **subtractively**, to filter false positives.
14
+ This uses it additively, restricted to coordinates with an **unambiguous 1:1** entry in
15
+ `data/cpe-coord-map.json` (no name heuristics), and only for CVEs already enriched, so it adds
16
+ no network path.
17
+
18
+ **It is off by default because its measured precision is poor, and the reason is structural.**
19
+ On Apache Dubbo 2.7.8 it adds 76 findings of which **9 (12%) are corroborated by Snyk**. CPE
20
+ products are *framework*-level (`spring_framework`, `netty`, `log4j`) while Maven coordinates
21
+ are *artifact*-level, so a framework CVE lands on every artifact of that framework —
22
+ `CVE-2016-1000027` is a spring-web flaw and CPE puts it on spring-core, `CVE-2019-20444` is
23
+ netty-codec-http and CPE puts it on netty-common. Curation cannot fix a granularity mismatch;
24
+ allowing the map's deliberate 1:N entries made it worse still (262 findings, 8% corroborated).
25
+ Shipped as a triage aid ("what might I be missing?"), never as a default. Locked by
26
+ `test/nvd-cpe-match.test.js`, including a test asserting that a coordinate with no curated
27
+ entry is not matched even when the name heuristic would have accepted it.
28
+
29
+ The investigation behind it also settled what fad's 118 benchmark misses actually are, and
30
+ the answer is not flattering: **they are real misses, not the other scanner's noise.** All 87
31
+ public-CVE ones were checked against NVD — 7 confirmed, 13 where NVD's own range disagrees,
32
+ 67 where NVD names no CPE for the artifact. Both minorities were traced. The "NVD is silent"
33
+ bulk are public-database coverage gaps: `CVE-2023-6481` on `logback-classic@1.2.2` exists in
34
+ OSV with **no Maven package binding at all** (only a GIT range), while its own fixed-version
35
+ list `1.2.12, 1.3.13, 1.4.13` shows the 1.2.x branch was affected and fixed at 1.2.12 — so
36
+ 1.2.2 is vulnerable and no public-source scanner can see it. The "NVD contradicts" cases are
37
+ NVD contradicting itself: sibling jackson-databind gadget CVEs published weeks apart declare
38
+ `2.0.0–2.7.9.7 / 2.8.0–2.8.11.6 / 2.9.0–2.9.10.4` (CVE-2020-9546) versus `2.9.0–2.9.10.4`
39
+ alone (CVE-2020-10672). Public advisory data declares ranges per release *branch* and old
40
+ unpatched branches are routinely absent; a hand-curated commercial database fills that in and
41
+ aggregating public sources does not reproduce it. Documented in `docs/BENCHMARK.md` rather
42
+ than left as "not yet diagnosed".
43
+
8
44
  ### Fixed
45
+ - **An imported BOM's `<properties>` leaked into the importing project — and won.**
46
+ `<scope>import</scope>` imports a BOM's `<dependencyManagement>` and **nothing else**: the
47
+ BOM resolves its managed versions in its own property context, and its `<properties>` never
48
+ become the importer's. `core.js` merged them, and merged them so the BOM won
49
+ (`{...merged.properties, ...imported.properties}`), so a BOM silently redefined the
50
+ importing project's own property values. On Apache Dubbo 2.7.8 the reactor root sets
51
+ `<hibernate_validator_version>5.2.4.Final</hibernate_validator_version>` and
52
+ `dubbo-dependencies-bom` redefines it to `5.4.1.Final`, so `dubbo-filter-validation`'s
53
+ `<version>${hibernate_validator_version}</version>` resolved to the wrong version — and a
54
+ different version is a different CVE set. (`mvn dependency:tree` reports
55
+ `hibernate-validator:jar:5.2.4.Final:test` for that module.) The BOM's managed entries are
56
+ now interpolated against the BOM's own properties at the import boundary and the properties
57
+ are dropped, mirroring what `transitive.js#effectivePom` already did for EXTERNAL import
58
+ BOMs. Locked by `test/bom-property-leak.test.js`, which guards both directions: the leak
59
+ must stop **and** importing a BOM must still supply managed versions.
60
+ - **A version declared only at test scope is now reported as dev.** `isDev` lives on the
61
+ coord-wide record, so a version declared solely at `<scope>test</scope>` inherited the
62
+ coordinate's production flag whenever the same coordinate was production at some other
63
+ version — counting toward the production total and the `--fail-on` gate. Per-version scopes
64
+ are now recorded next to per-version paths (`versionScopes`, mirror of `versionPaths`), and
65
+ attribution applies the same widest-wins rule already used for overlay-recovered versions.
66
+ On Dubbo, `hibernate-validator:5.2.4.Final` moves to the dev chapter, attributed to
67
+ `dubbo-filter-validation` — exactly what Maven reports.
68
+
69
+ Air-gapped recall on the public benchmark reaches **657/657 (100%)** of OSV-Scanner's own
70
+ online finding set, up from 653.
71
+
72
+ ### Changed
73
+ - **The benchmark now measures every scanner at full capability, not just air-gapped.**
74
+ `docs/BENCHMARK.md` carries two tables, because they answer different questions. **Full
75
+ capability** (all five online, best configuration, populated `~/.m2`, union 908 pairs):
76
+ fad-checker 790 (87.0%), OSV-Scanner 657, Snyk 603, Trivy 546, Grype+Syft 45. **No tool
77
+ finds everything, fad included** — its 118 misses all come from Snyk, 30 of them under a
78
+ proprietary `SNYK-*` id no public database carries, and **88 genuine public-CVE misses that
79
+ remain undiagnosed**. **No network** (`unshare -rn`, against OSV-Scanner's online output):
80
+ fad 657/657, Grype+Syft 45, Trivy 40, OSV-Scanner 37.
81
+ Also documented: Trivy's result is identical online and with a fully populated `~/.m2` (the
82
+ local repository substitutes for the network entirely), Grype+Syft does not move at all
83
+ between default and fully-enabled configuration, and the `settings.xml` mirror trick for
84
+ reproducing the run when Maven Central rate-limits the IP.
85
+ - **The per-module overlay could not recover a version held only on a TEST classpath.**
86
+ The overlay exists because the global transitive pass dedupes by `g:a` across the whole
87
+ reactor and keeps one version per coordinate — but it hardcoded
88
+ `includedScopes: ["compile","runtime","provided"]`, so a version reachable only through a
89
+ test-scoped dependency was structurally unreachable. Measured on Apache Dubbo 2.7.8, this
90
+ one omission accounted for **every one** of the 78 findings OSV-Scanner reported that fad
91
+ missed (`jackson-databind:2.8.4:test` in dubbo-registry-sofa,
92
+ `hibernate-validator:5.2.4.Final:test` in dubbo-filter-validation, `okhttp:3.11.0` /
93
+ `okio:1.14.0` in dubbo-configcenter-apollo, `commons-compress:1.18:test` in
94
+ dubbo-remoting-etcd3 — each verified against `mvn dependency:tree`).
95
+ Air-gapped recall on that project: **579 → 653 of 657 (88.1% → 99.4%)**, production
96
+ findings unchanged at 651, dev findings 11 → 147, **zero production finding lost**.
97
+ - **A version is now dev only when EVERY module resolving it does so at test scope.**
98
+ On Dubbo, `jackson-databind:2.10.4` is test-scoped in `dubbo-config-spring` but
99
+ **compile**-scoped in `dubbo-configcenter-nacos`. Reading the first recorded provenance
100
+ called the version dev and dropped a genuine production finding out of the count and out
101
+ of the `--fail-on` gate.
102
+ - **The overlay records provenance per module even when the version is already known.**
103
+ It used to `continue` on the first module to contribute a version, so a second module
104
+ resolving the same version at a different scope left no trace at all — which is exactly
105
+ what made the previous item invisible.
106
+ - **A DECLARED version now wins over any transitive provenance for the same version.**
107
+ `xstream:1.4.10` is declared outright in `dubbo-registry-eureka` *and* reached as a
108
+ test-scoped transitive of `dubbo-config-api`; letting the transitive provenance win
109
+ demoted 35 findings, one of them KEV, into the dev chapter. A manifest that writes
110
+ `<version>` for a coordinate is the authority on that version.
111
+
112
+ ### Previously fixed
113
+ - **The transitive closure of a test-scoped dependency was never scanned.** Maven's scope
114
+ matrix says `test → compile = test`: the compile dependencies of a test-scoped dependency
115
+ are on the test classpath, and so are theirs, recursively (only `test → test` is omitted).
116
+ `expandWithTransitives` passed test-scoped roots into resolution (`includeTestDeps`, on
117
+ unless `--ignore-test`) but `resolveTransitiveDeps` then filtered accepted propagated
118
+ scopes to `compile/runtime/provided`, so **every child of a test root was discarded at the
119
+ first hop**. Net effect: the dev chapter only ever listed *directly declared* test
120
+ dependencies, never their transitives. On Apache Dubbo 2.7.8 this hid
121
+ `spring-boot:1.5.17.RELEASE` and `spring-boot-autoconfigure:1.5.17.RELEASE`, four hops down
122
+ `registry-test → registry-server-integration → spring-boot-starter`, all of which
123
+ `mvn dependency:tree` reports at scope=test.
124
+ - **Scope is now widened, never narrowed, when a coord is reached by several paths.** This is
125
+ the half of the fix above that matters most. The traversal dedupes by `g:a` and keeps the
126
+ first chain walked, so marking everything under a test root as dev let **BFS order decide
127
+ the scope**: a coordinate reachable from *both* a test root and a compile root got stamped
128
+ test if the test path happened to be walked first. Measured: 6 production findings
129
+ (`spring-core:4.3.16.RELEASE`, `commons-lang:2.6`, …) silently moved into the dev chapter,
130
+ dropping out of the production count **and out of the `--fail-on` gate**. A false negative
131
+ on the production classpath is worse than the gap being fixed. A revisit now upgrades
132
+ `test` to the wider propagated scope and never the reverse. Locked by
133
+ `test/transitive-test-scope.test.js` (6 tests, including the both-paths case and the
134
+ `test → test` omission).
135
+ Net on Dubbo: **575 → 579** recovered pairs, production findings **unchanged at 650**, dev
136
+ findings 7 → 11.
137
+ - **Maven hard-pin versions (`[1.2.3]`) are now normalised to the bare version.**
138
+ Maven's `[x]` syntax means *exactly* x — a concrete version wearing range brackets —
139
+ and real upstream POMs use it (`io.grpc:grpc-netty:1.22.1` declares
140
+ `<version>[4.1.35.Final]</version>`). fad kept the brackets verbatim, so the coordinate
141
+ was wrong **everywhere downstream**: the report, the purl, and every SBOM/CSAF/SARIF/JSON
142
+ export carried `netty-codec-http2@[4.1.35.Final]`, which cannot be joined with any other
143
+ tool's output for the same dependency. Fixed by `lib/maven-version.js#normalizeHardPin`,
144
+ applied on both paths that produce a version — `cve-match.js#resolveDepVersion` (declared
145
+ deps, after `${…}` interpolation, so `[${netty.version}]` works) and `lib/transitive.js`
146
+ (deps read out of upstream POMs, which is where this actually came from). A **genuine
147
+ range keeps its brackets**: choosing a version out of `[1.0,2.0)` is resolution, not
148
+ normalisation, and it must keep surfacing as unresolved rather than silently becoming
149
+ concrete. Found by the new public benchmark, where it cost **9 recovered findings**
150
+ (566 → 575 of OSV-Scanner's reference set on Apache Dubbo 2.7.8). Note that the OSV cache
151
+ is keyed by coordinate **and** version, so this fix invalidates entries warmed under the
152
+ old string — an offline re-run needs a cache re-warm to see the corrected coordinate.
153
+ - **Docs referenced a `--transitive` flag that does not exist.** Transitive resolution is
154
+ **on by default**; `--no-transitive` disables it. Corrected in `ARCHITECTURE.md` and
155
+ `COMPARISON.md`.
156
+
157
+ ### Added
158
+ - **`docs/BENCHMARK.md` — a reproducible air-gapped recall benchmark.** Replaces the
159
+ unverifiable private-project figure that headlined the README. Measured on **Apache Dubbo
160
+ 2.7.8** (105-module reactor, pinned commit), with both scanners run under `unshare -rn` in
161
+ a namespace with **no network interface**, and graded against **OSV-Scanner's own online
162
+ output** as the reference set (657 distinct `package@version | vulnerability` pairs) rather
163
+ than against fad's own notion of a finding: **fad-checker recovers 575 (87.5%)**,
164
+ **OSV-Scanner recovers 37 (5.6%)**. Documents the exact commands, the tool versions, the
165
+ 82 pairs fad misses **and why** (version-mediation divergence, plus two genuinely
166
+ unresolved `spring-boot` coordinates), and the caveats — a warmed cache is required, and
167
+ one project is one shape.
168
+
169
+ ### Changed
170
+ - `docs/COMPARISON.md`: four competitor cells corrected after re-verification against
171
+ upstream docs and source. Syft **does** have Maven transitive resolution
172
+ (`java.resolve-transitive-dependencies`, opt-in, off by default); Trivy consults a local
173
+ `~/.m2` before the network; Trivy **does** report end-of-service-life, but only for OS
174
+ distributions (the EOL row is now scoped to *application* frameworks); and the
175
+ "scan without exposing the codebase" row now concedes the SBOM-then-scan-online route,
176
+ keeping only the two differences that are sourceable. Adds a version stamp for every tool
177
+ compared and an explicit note that no ⚠️/❌ cell means "unmaintained".
178
+
179
+ ### Previously fixed
9
180
  - **External `<parent>` POMs (spring-boot-starter-parent) now backfill their managed
10
181
  versions.** A versionless dep whose version is inherited from an external `<parent>`
11
182
  (e.g. `spring-boot-starter-actuator` under `spring-boot-starter-parent`, whose own
@@ -0,0 +1,61 @@
1
+ # Contributing
2
+
3
+ Thanks for looking. This is a young single-maintainer project, so the most valuable
4
+ contribution isn't necessarily code — it's **telling me where the output is wrong**.
5
+
6
+ ## The most useful thing you can do
7
+
8
+ Run it on a real project and report what it got wrong:
9
+
10
+ ```bash
11
+ npx fad-checker -s ./your-project
12
+ ```
13
+
14
+ A [false positive / false negative report](https://github.com/9pings/fad-checker/issues/new?template=false_positive.yml)
15
+ with the coordinate and the manifest snippet that produced it is worth more than a feature
16
+ request. Dependency resolution has a long tail of real-world shapes — inherited BOMs, profile
17
+ matrices, `replace` directives, vendored jars — and the only way to cover them is to be shown
18
+ one that breaks.
19
+
20
+ ## Development setup
21
+
22
+ Node ≥ 20, no other tooling required.
23
+
24
+ ```bash
25
+ npm install
26
+ npm test # 606 tests via node --test
27
+ node --test test/core.test.js # a single file
28
+ node fad-checker.js -s test/fixtures/monorepo-mixed --offline --no-report
29
+ ```
30
+
31
+ Building the single-file binaries needs [bun](https://bun.sh): `npm run build`.
32
+
33
+ ## Ground rules for changes
34
+
35
+ - **Tests must not touch the network.** Every fixture-driven test drives an in-memory
36
+ registry/fetcher. `test/offline-guarantee.test.js` is a tripwire that fails if any code path
37
+ reaches for the network under `--offline` — don't work around it.
38
+ - **Add a fixture, not just an assertion.** New parsing behaviour belongs in
39
+ `test/fixtures/`, next to the shapes that already live there.
40
+ - **A parse failure must never kill the run.** One malformed manifest logs a warning and the
41
+ scan continues; there is no `process.exit(1)` mid-pipeline.
42
+ - **New ecosystem = new codec.** Implement the interface in
43
+ `lib/codecs/codec.interface.js`, register it, and the orchestrator needs no edits. See
44
+ [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).
45
+ - Commit messages follow Conventional Commits (`fix(maven): …`, `feat(gradle): …`).
46
+ - [`CLAUDE.md`](CLAUDE.md) is the code-level orientation map — conventions, invariants and the
47
+ reasoning behind the non-obvious ones. Read it before a non-trivial change; it will save you
48
+ from "simplifying" something that's load-bearing.
49
+
50
+ ## On AI-assisted development
51
+
52
+ This codebase is written with heavy use of Claude Code, and I'm not going to pretend
53
+ otherwise — `CLAUDE.md` in the repo root is exactly what it looks like. What I ask of my own
54
+ changes and of contributions is the same either way: a test that fails before and passes
55
+ after, a fixture derived from a real-world shape, and no claim in the docs that isn't backed
56
+ by something you can run. If you find a place where the code doesn't meet that bar, that's a
57
+ bug report I want.
58
+
59
+ ## Reporting a vulnerability in fad-checker itself
60
+
61
+ See [`SECURITY.md`](SECURITY.md) — please report privately, not as a public issue.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pp9Ping
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -2,34 +2,35 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/fad-checker.svg)](https://www.npmjs.com/package/fad-checker)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/fad-checker.svg)](https://www.npmjs.com/package/fad-checker)
5
- [![license](https://img.shields.io/npm/l/fad-checker.svg)](https://github.com/9pings/fad-checker/blob/main/package.json)
5
+ [![license](https://img.shields.io/npm/l/fad-checker.svg)](LICENSE)
6
6
  [![node](https://img.shields.io/node/v/fad-checker.svg)](https://nodejs.org)
7
+ [![CI](https://github.com/9pings/fad-checker/actions/workflows/ci.yml/badge.svg)](https://github.com/9pings/fad-checker/actions/workflows/ci.yml)
7
8
 
8
9
  > **F**abulous **A**utonomous **D**ependency **C**hecker<br>
9
10
  > AKA **F**uckin' **A**utonomous **D**ependency **C**hecker<br>
10
11
 
11
- `fad-checker` audits **Maven · Gradle · npm · Yarn · pnpm · Composer · PyPI · NuGet · Go · Ruby**, vendored JavaScript, committed native binaries and cryptographic material (certificates & private/public keys) in any source tree multi-module, monorepo, polyglot and produces a self-contained **HTML + Word report** (CVE prioritised by EPSS + CISA KEV, EOL, obsolete, outdated, licenses) plus **CycloneDX SBOM / CSAF VEX / SARIF / JSON** exports. **No build tools, no Docker, no network needed** it reads lockfiles and manifests straight off disk.
12
+ `fad-checker` audits **Maven · Gradle · npm · Yarn · pnpm · Composer · PyPI · NuGet · Go · Ruby**, vendored JavaScript, committed native binaries and cryptographic material (certificates & private/public keys) in any source tree; multi-module, monorepo, polyglot; and produces a self-contained **HTML + Word report** (CVE prioritised by EPSS + CISA KEV, EOL, obsolete, outdated, licenses) plus **CycloneDX SBOM / CSAF VEX / SARIF / JSON** exports. **No build tools, no Docker, no network needed**; it reads lockfiles and manifests straight off disk.
12
13
 
13
14
  🌐 **[Project site & docs →](https://9pings.github.io/fad-checker/)**
14
15
 
15
16
  > [!WARNING]
16
- > **Young project expect rough edges.** fad-checker is new and under active development, so it may still contain bugs (including false positives and false negatives). Treat its output as a strong first pass, **double-check anything critical**, and please [report issues](https://github.com/9pings/fad-checker/issues) they get fixed fast.
17
+ > **Young project; expect rough edges.** fad-checker is new and under active development, so it may still contain bugs (including false positives and false negatives). Treat its output as a strong first pass, **double-check anything critical**, and please [report issues](https://github.com/9pings/fad-checker/issues); they get fixed fast.
17
18
 
18
- <p align="center"><img src="docs/assets/demo.gif" alt="fad-checker animated terminal demo a [n/N] checklist warming each vulnerability database, then CVE findings coloured by severity with KEV badges" height="600"></p>
19
+ <p align="center"><img src="docs/assets/demo.gif" alt="fad-checker animated terminal demo; a [n/N] checklist warming each vulnerability database, then CVE findings coloured by severity with KEV badges" height="600"></p>
19
20
 
20
21
  ## Features
21
22
 
22
- - **10 ecosystems in one pass** Maven, **Gradle**, npm/Yarn/pnpm, Composer (PHP), PyPI, NuGet, Go, Ruby; plus **vendored JS** (retire.js), committed **native binaries** (`.dll`/`.exe`/`.so`/`.dylib`, identified by checksum via deps.dev + CIRCL) and **embedded JARs** (fat-jars/war/ear, unzipped in-memory).
23
- - **Crypto material** committed **certificates** (X.509, PEM/DER), **private &amp; public keys** (PEM / OpenSSH every algorithm / PuTTY / PGP / one-line SSH) and **keystores** (JKS/JCEKS/PKCS#12). Each key is labelled **private** (a committed secret → critical) or **public**; certs are checked for **expiry, weak key (RSA<2048), weak signature (MD5/SHA1) and self-signed** all parsed offline with the built-in X.509 parser, no network. `--no-certs` to disable.
24
- - **No build tools** reads `pom.xml`, `build.gradle(.kts)`/`gradle.lockfile`/`libs.versions.toml`, `package-lock`/`yarn.lock`/`pnpm-lock`, `composer.lock`, `poetry`/`Pipfile`/`uv`/`pdm` locks, `packages.lock.json`/`*.csproj`, `go.mod`, `Gemfile.lock` directly. No `mvn`/`gradle`/`npm install`/`pip`/`dotnet restore`/`go build`/`bundle`, no `node_modules/`. → [how it stays build-free](docs/COMPARISON.md#how-its-autonomous-no-build-tools)
25
- - **CVE, merged & prioritised** CVEProject + OSV.dev + NVD, CPE/version cross-checked to cut false positives, ranked **CISA KEV → EPSS → CVSS**.
26
- - **Per-module Maven version mediation** recovers vulnerable transitive versions that a global `<dependencyManagement>` pin hides in another module (lifted Snyk-corroborated coverage **156 181** on a real 25-module reactor, finding CVEs a single Snyk scan missed).
27
- - **Air-gapped** **zero network under `--offline`** (regression-tested), offline Maven transitive resolution, and `--osv-db` for cache-independent offline OSV recall. → [Air-gapped](#air-gapped-audits)
28
- - **Supply-chain risk** known-**malicious** advisories (`MAL-`, always block the CI gate) + suspected **typosquats** (`--typosquat`).
29
- - **Lifecycle** EOL (endoflife.date), obsolete/deprecated, outdated across every ecosystem.
30
- - **Licenses** *(opt-in `--licenses`)* SPDX-normalised, copyleft/proprietary flagged.
31
- - **Audit-grade & reproducible** every report carries a **provenance manifest** (data-source freshness + run config) and a **Methodology, data sources & limitations** chapter; artifacts ship a **`SHA256SUMS`** integrity manifest (`sha256sum -c`); **differential audits** diff against a prior run (`--baseline`, or `fad diff a.json b.json`) and CI can gate on *new* findings (`--fail-on-new`).
32
- - **Outputs & CI** HTML + Word `.doc`, CycloneDX 1.6 SBOM, CSAF 2.0 VEX, SARIF 2.1.0, JSON; gate with `--fail-on` / `--fail-on-new`, triage with `--ignore`/`--vex`. Private registries for Maven, npm, PyPI, Ruby, Go, **NuGet** and **Composer**.
23
+ - **10 ecosystems in one pass**; Maven, **Gradle**, npm/Yarn/pnpm, Composer (PHP), PyPI, NuGet, Go, Ruby; plus **vendored JS** (retire.js), committed **native binaries** (`.dll`/`.exe`/`.so`/`.dylib`, identified by checksum via deps.dev + CIRCL) and **embedded JARs** (fat-jars/war/ear, unzipped in-memory).
24
+ - **Crypto material**; committed **certificates** (X.509, PEM/DER), **private &amp; public keys** (PEM / OpenSSH every algorithm / PuTTY / PGP / one-line SSH) and **keystores** (JKS/JCEKS/PKCS#12). Each key is labelled **private** (a committed secret → critical) or **public**; certs are checked for **expiry, weak key (RSA<2048), weak signature (MD5/SHA1) and self-signed**; all parsed offline with the built-in X.509 parser, no network. `--no-certs` to disable.
25
+ - **No build tools**; reads `pom.xml`, `build.gradle(.kts)`/`gradle.lockfile`/`libs.versions.toml`, `package-lock`/`yarn.lock`/`pnpm-lock`, `composer.lock`, `poetry`/`Pipfile`/`uv`/`pdm` locks, `packages.lock.json`/`*.csproj`, `go.mod`, `Gemfile.lock` directly. No `mvn`/`gradle`/`npm install`/`pip`/`dotnet restore`/`go build`/`bundle`, no `node_modules/`. → [how it stays build-free](docs/COMPARISON.md#how-its-autonomous-no-build-tools)
26
+ - **CVE, merged & prioritised**; CVEProject + OSV.dev + NVD, CPE/version cross-checked to cut false positives, ranked **CISA KEV → EPSS → CVSS**.
27
+ - **Per-module Maven version mediation**; recovers vulnerable transitive versions that a global `<dependencyManagement>` pin hides in another module, applying Maven's own nearest-wins semantics per module rather than resolving the whole reactor as one tree.
28
+ - **Air-gapped**; **zero network under `--offline`** (regression-tested), offline Maven transitive resolution, and `--osv-db` for cache-independent offline OSV recall. Benchmarked on **Apache Dubbo 2.7.8** (105 modules) against OSV-Scanner, Snyk, Trivy and Grype+Syft: at **full capability** fad finds 790 of a 908-pair union (87%, the highest of the five, and it still misses 118 that Snyk finds); **under `unshare -rn` with no network interface** it recovers **657/657** of OSV-Scanner's *online* result, versus 45 / 40 / 37 for the others. → [Benchmark](docs/BENCHMARK.md) · [Air-gapped](#air-gapped-audits)
29
+ - **Supply-chain risk**; known-**malicious** advisories (`MAL-`, always block the CI gate) + suspected **typosquats** (`--typosquat`).
30
+ - **Lifecycle**; EOL (endoflife.date), obsolete/deprecated, outdated; across every ecosystem.
31
+ - **Licenses** *(opt-in `--licenses`)*; SPDX-normalised, copyleft/proprietary flagged.
32
+ - **Audit-grade & reproducible**; every report carries a **provenance manifest** (data-source freshness + run config) and a **Methodology, data sources & limitations** chapter; artifacts ship a **`SHA256SUMS`** integrity manifest (`sha256sum -c`); **differential audits** diff against a prior run (`--baseline`, or `fad diff a.json b.json`) and CI can gate on *new* findings (`--fail-on-new`).
33
+ - **Outputs & CI**; HTML + Word `.doc`, CycloneDX 1.6 SBOM, CSAF 2.0 VEX, SARIF 2.1.0, JSON; gate with `--fail-on` / `--fail-on-new`, triage with `--ignore`/`--vex`. Private registries for Maven, npm, PyPI, Ruby, Go, **NuGet** and **Composer**.
33
34
 
34
35
  📖 **[Usage & all flags](docs/USAGE.md)** · **[Architecture](docs/ARCHITECTURE.md)** · **[Comparison vs other tools](docs/COMPARISON.md)** · **[Data sources](docs/DATA-SOURCES.md)**
35
36
 
@@ -40,18 +41,25 @@ npm install -g fad-checker
40
41
  fad-checker -s ./my-project # → ./fad-checker-report/cve-report.html
41
42
  ```
42
43
 
43
- A free [NVD API key](https://nvd.nist.gov/developers/request-an-api-key) (instant) gives 10× faster enrichment: `fad-checker --set-nvd-key YOUR_KEY`. A few common runs full list via `fad-checker --help` or [docs/USAGE.md](docs/USAGE.md):
44
+ A free [NVD API key](https://nvd.nist.gov/developers/request-an-api-key) (instant) gives 10× faster enrichment: `fad-checker --set-nvd-key YOUR_KEY`. A few common runs; full list via `fad-checker --help` or [docs/USAGE.md](docs/USAGE.md):
44
45
 
45
46
  ```bash
46
47
  fad-checker -s ./proj -e "^com\.acme\." # exclude private libs (coord regex)
47
48
  fad-checker -s ./proj -t ../clean -e "^com\.acme\." --snyk # cleaned POM tree + merge Snyk
48
- fad-checker -s ./proj --offline # fully offline (zero network)
49
+ fad-checker -s ./proj --offline # fully offline (zero network, needs a warmed cache)
49
50
  fad-checker -s ./proj --osv-db --typosquat # offline-complete OSV + typosquat
50
51
  fad-checker -s ./proj --licenses --fail-on high # license chapter + CI gate
51
52
  fad-checker -s ./proj --report-json --baseline last.json --fail-on-new # differential audit: fail CI on NEW findings
52
53
  fad-checker diff last.json this.json # standalone diff of two findings JSONs
53
54
  ```
54
55
 
56
+ > [!IMPORTANT]
57
+ > **`--offline` reads the cache, it doesn't replace it.** On a *cold* cache there is nothing to
58
+ > match against, so an offline first run legitimately reports **0 CVE / 0 EOL / 0 outdated**;
59
+ > that's an empty cache, not a clean project. Warm it once (a normal online run on any project,
60
+ > or `--import-cache`), then `--offline` returns the full result set with zero network calls.
61
+ > Air-gapped machines get their cache via [`--export-cache` / `--import-cache`](#air-gapped-audits).
62
+
55
63
  A single self-contained binary (no Node), from-source install and shell completion are in → [docs/USAGE.md](docs/USAGE.md).
56
64
 
57
65
  ## What it finds
@@ -61,51 +69,51 @@ The report is organised into **root chapters** (each grouping related sub-chapte
61
69
  | Chapter | Source | What it catches |
62
70
  | --- | --- | --- |
63
71
  | **0. Warnings** *(top)* | local heuristics | Missing lockfiles, unresolved Maven versions (BOM-managed), private libs not on Maven Central |
64
- | **Δ. Changes since baseline** *(top, with `--baseline`)* | diff vs prior JSON | New / fixed / unchanged findings per category + the list of **new production CVEs** for repeat audits and `--fail-on-new` CI gating |
65
- | **1. CVE** *(X direct, Y indirect, Z dev)* | CVEProject + OSV.dev + NVD + CPE | **1.1 Production** public CVE / GHSA in prod deps, per ecosystem, per manifest, **prioritised** by CISA KEV + EPSS + CVSS · **1.2 Vendored JS vulns** ([retire.js](https://retirejs.github.io/)) · **1.3 Dev** (`test`/`provided`, `dev`/`optional`/`peer`) · **1.4 Likely false positives** (CPE-filtered) |
66
- | **2. Unmanaged / unversioned components** | deps.dev + CIRCL (by checksum), retire.js, built-in X.509 | **2.1 Embedded binaries** CVEs in libs shipped inside committed `.jar`/`.war`/`.ear` (fat-jars, shaded uber-jars) · **2.2 Native binaries** (`.dll`/`.exe`/`.so`/`.dylib`) identified by hash, flagged should-be-managed / name≠checksum / unknown / malicious · **2.3 Vendored JavaScript** inventory (jQuery, Bootstrap, …) vulnerable *or not* · **2.4 Certificates &amp; key material** committed certs (expiry / weak key / weak signature / self-signed), **private vs public keys** (PEM/OpenSSH/PuTTY/PGP/SSH) and keystores, all parsed offline |
72
+ | **Δ. Changes since baseline** *(top, with `--baseline`)* | diff vs prior JSON | New / fixed / unchanged findings per category + the list of **new production CVEs**; for repeat audits and `--fail-on-new` CI gating |
73
+ | **1. CVE** *(X direct, Y indirect, Z dev)* | CVEProject + OSV.dev + NVD + CPE | **1.1 Production**; public CVE / GHSA in prod deps, per ecosystem, per manifest, **prioritised** by CISA KEV + EPSS + CVSS · **1.2 Vendored JS vulns** ([retire.js](https://retirejs.github.io/)) · **1.3 Dev** (`test`/`provided`, `dev`/`optional`/`peer`) · **1.4 Likely false positives** (CPE-filtered) |
74
+ | **2. Unmanaged / unversioned components** | deps.dev + CIRCL (by checksum), retire.js, built-in X.509 | **2.1 Embedded binaries**; CVEs in libs shipped inside committed `.jar`/`.war`/`.ear` (fat-jars, shaded uber-jars) · **2.2 Native binaries** (`.dll`/`.exe`/`.so`/`.dylib`) identified by hash, flagged should-be-managed / name≠checksum / unknown / malicious · **2.3 Vendored JavaScript** inventory (jQuery, Bootstrap, …) vulnerable *or not* · **2.4 Certificates &amp; key material**; committed certs (expiry / weak key / weak signature / self-signed), **private vs public keys** (PEM/OpenSSH/PuTTY/PGP/SSH) and keystores, all parsed offline |
67
75
  | **3. Maintenance / lifecycle** *(X EOL, Y obsolete, Z outdated)* | endoflife.date · curated + registry flags · Maven Central / npm / Packagist / PyPI / NuGet | **3.1 End-of-Life** frameworks · **3.2 Obsolete / deprecated / abandoned / yanked** · **3.3 Outdated** (newer version available, with release dates) |
68
76
  | **4. Licenses** *(opt-in: `--licenses`)* | registry metadata + Maven POMs → SPDX policy | Each dep's license normalised to SPDX and classified; copyleft (GPL/AGPL/LGPL/MPL), proprietary and unknown flagged for review |
69
77
  | **5. Fix Recommendations** | computed | Per-ecosystem pin recipes: Maven `<dependencyManagement>`, Gradle `constraints { }`, npm `overrides`, yarn `resolutions`, `composer require`, `pip install`, `dotnet add package` |
70
78
  | **6. Scan context & limitations** | provenance manifest + walk | **6.1 Scanned descriptors** (every manifest parsed) · **6.2 Ignored directories** (pruned paths + rule) · **6.3 Methodology, data sources & limitations** (data-source freshness, run config, explicit statement of **what fad-checker does *not* assess**) |
71
- | **Supply-chain risk** *(cross-cutting)* | OSV `MAL-…` + name heuristic | **Known-malicious** packages (always block the CI gate, any `--fail-on` level) and **suspected typosquats** (`--typosquat`: an npm/PyPI name one edit from a popular package `lodahs`↔`lodash`) |
79
+ | **Supply-chain risk** *(cross-cutting)* | OSV `MAL-…` + name heuristic | **Known-malicious** packages (always block the CI gate, any `--fail-on` level) and **suspected typosquats** (`--typosquat`: an npm/PyPI name one edit from a popular package; `lodahs`↔`lodash`) |
72
80
 
73
81
  The HTML report opens in any browser, contains every detail (CVSS vectors, references, full descriptions, CPE configurations, via-paths for transitives) and ships a Word-compatible `.doc` twin. Every match carries a **composite priority** (KEV-exploited > EPSS likelihood > CVSS severity), and the run can additionally emit a **CycloneDX 1.6 SBOM** (`--report-sbom`, vulnerabilities inline) and a **CSAF 2.0 VEX** (`--report-csaf`) for downstream tooling.
74
82
 
75
- <p align="center"><img src="docs/assets/report.png" alt="fad-checker HTML report executive summary with severity tiles and a detailed CVE table with CWE, descriptions and fix versions" width="900"></p>
83
+ <p align="center"><img src="docs/assets/report.png" alt="fad-checker HTML report; executive summary with severity tiles and a detailed CVE table with CWE, descriptions and fix versions" width="900"></p>
76
84
 
77
85
  ## Air-gapped audits
78
86
 
79
87
  > **Zero-data-sent guarantee.** Under `--offline`, fad-checker makes **no network calls
80
- > whatsoever** it reads only the warmed `~/.fad-checker/` caches and never transmits a
88
+ > whatsoever**; it reads only the warmed `~/.fad-checker/` caches and never transmits a
81
89
  > dependency, path or finding off the machine. It is regression-tested
82
90
  > (`test/offline-guarantee.test.js`, a tripwire fetcher that throws if touched) and
83
91
  > auditor-reproducible: `unshare -rn node fad-checker.js -s ./proj --offline …` runs it in a
84
92
  > namespace with **no network interface** and yields byte-identical findings. Unlike the
85
- > mainstream OSS scanners, fad also resolves the **Maven transitive graph** offline so on
93
+ > mainstream OSS scanners, fad also resolves the **Maven transitive graph** offline; so on
86
94
  > an air-gapped multi-module project it finds the transitive CVEs they can't.
87
95
 
88
96
  When the audited system is **offline / confidential** (typical of a regulated or air-gapped audit) it
89
97
  can't reach OSV / NVD / Maven Central / npm. Split the work across machines while keeping
90
98
  **zero environment information** off the secure enclave: an anonymized descriptor carries
91
- only **public package coordinates** no filesystem paths, no registry URLs, no
92
- hostnames/usernames and the **detailed report is produced back on the offline machine**.
99
+ only **public package coordinates**; no filesystem paths, no registry URLs, no
100
+ hostnames/usernames; and the **detailed report is produced back on the offline machine**.
93
101
 
94
102
  The transfer relies on a property of fad-checker's caches: they are keyed by *coordinate*
95
103
  or *vuln id*, never by path, so they are **machine-independent**. The online step just
96
104
  **warms the caches**; the offline step replays the scan and gets cache hits.
97
105
 
98
106
  ```bash
99
- # ── Phase 1 OFFLINE (audited machine): export the anonymized descriptor ──
107
+ # ── Phase 1; OFFLINE (audited machine): export the anonymized descriptor ──
100
108
  # Exclude private/internal packages with -e (offline we can't tell private from public).
101
109
  fad-checker -s ./proj -e "^(client|internal)\." --export-anonymized deps.json
102
110
  # → deps.json: public coordinates only. Review it before it leaves the enclave.
103
111
 
104
- # ── Phase 2 ONLINE (any machine, no source needed): warm the caches ──
112
+ # ── Phase 2; ONLINE (any machine, no source needed): warm the caches ──
105
113
  fad-checker --import-anonymized deps.json # scans coordinates → OSV/NVD/CVE/registry/EOL + retire signatures
106
114
  fad-checker --export-cache fad-cache.tar.gz # bundle the warmed ~/.fad-checker/
107
115
 
108
- # ── Phase 3 OFFLINE (audited machine): full report, all local context ──
116
+ # ── Phase 3; OFFLINE (audited machine): full report, all local context ──
109
117
  fad-checker --import-cache fad-cache.tar.gz
110
118
  fad-checker -s ./proj --offline # re-collect locally (real paths) + cache hits
111
119
  # → full HTML/.doc report with manifests & structure, generated inside the enclave.
@@ -121,18 +129,36 @@ What the descriptor (`fad-deps/1`) contains vs. drops:
121
129
  | scope, isDev | parent chains, lockfile type |
122
130
 
123
131
  The online phase report is itself path-free; vendored-JavaScript (retire.js) findings are
124
- produced **offline in phase 3**, since retire needs the actual `.js` files its signature
132
+ produced **offline in phase 3**, since retire needs the actual `.js` files; its signature
125
133
  DB is warmed online (phase 2) and carried by `--export-cache`. Full offline/cache control →
126
134
  [`docs/USAGE.md`](docs/USAGE.md).
127
135
 
128
136
  ## Docs
129
137
 
130
- - [`docs/USAGE.md`](docs/USAGE.md) every flag and workflow: offline/cache control, private registries, config files, recipes, safety rails.
131
- - [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) internals: codecs, collection, matching, report pipeline.
132
- - [`docs/COMPARISON.md`](docs/COMPARISON.md) vs OSV-Scanner / Trivy / Grype / OWASP DC / Snyk, and how it stays build-free.
133
- - [`docs/DATA-SOURCES.md`](docs/DATA-SOURCES.md) — the public datasets fad-checker uses + their licenses.
134
- - [`CHANGELOG.md`](CHANGELOG.md) · [`CLAUDE.md`](CLAUDE.md) release history · code-level orientation for contributors.
138
+ - [`docs/USAGE.md`](docs/USAGE.md); every flag and workflow: offline/cache control, private registries, config files, recipes, safety rails.
139
+ - [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md); internals: codecs, collection, matching, report pipeline.
140
+ - [`docs/COMPARISON.md`](docs/COMPARISON.md); vs OSV-Scanner / Trivy / Grype / OWASP DC / Snyk, and how it stays build-free.
141
+ - [`docs/BENCHMARK.md`](docs/BENCHMARK.md) — reproducible air-gapped recall benchmark vs OSV-Scanner on a public 105-module project.
142
+ - [`docs/DATA-SOURCES.md`](docs/DATA-SOURCES.md); the public datasets fad-checker uses + their licenses.
143
+ - [`CHANGELOG.md`](CHANGELOG.md) · [`CLAUDE.md`](CLAUDE.md); release history · code-level orientation for contributors.
144
+
145
+ ## Contributing
146
+
147
+ The most useful contribution to a young scanner is **telling it where it's wrong**: run it on a
148
+ real project and file a [false positive / false negative report](https://github.com/9pings/fad-checker/issues/new?template=false_positive.yml)
149
+ with the coordinate and the manifest snippet that produced it. Dev setup, ground rules and the
150
+ codec extension point → [`CONTRIBUTING.md`](CONTRIBUTING.md). Vulnerabilities in fad-checker
151
+ itself → [`SECURITY.md`](SECURITY.md) (please report privately).
152
+
153
+ **On AI assistance:** this codebase is written with heavy use of Claude Code; [`CLAUDE.md`](CLAUDE.md)
154
+ in the repo root is exactly what it looks like. The bar it's held to is the one you can check
155
+ yourself: **606 tests** (`npm test`), the zero-network guarantee enforced by a tripwire test and
156
+ reproducible under `unshare -rn`, and coverage numbers measured against a Snyk baseline rather
157
+ than asserted. `fad-checker` itself uses **no LLM at runtime**; findings come from public
158
+ vulnerability databases and deterministic parsers, and no report text is generated. Full
159
+ statement, including where review actually caught a bad finding →
160
+ [`AI_POLICY.md`](AI_POLICY.md). Where the code doesn't meet that bar, that's a bug report I want.
135
161
 
136
162
  ## License
137
163
 
138
- MIT see [`LICENSE`](LICENSE).
164
+ MIT; see [`LICENSE`](LICENSE).
package/SECURITY.md ADDED
@@ -0,0 +1,60 @@
1
+ # Security policy
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Report vulnerabilities in `fad-checker` itself **privately**, via
6
+ [GitHub private security advisories](https://github.com/9pings/fad-checker/security/advisories/new).
7
+ If that isn't available to you, email `pp9Ping@gmail.com` with `fad-checker security` in the
8
+ subject.
9
+
10
+ Please don't open a public issue for a vulnerability. This is a single-maintainer project —
11
+ expect a first response within **7 days**, and a fix or a plan within **30 days** for anything
12
+ confirmed. Credit is given in the release notes unless you'd rather stay anonymous.
13
+
14
+ Findings *about your own dependencies* that fad-checker reported are not vulnerabilities in
15
+ this project — those belong in a normal issue (the
16
+ [false positive / false negative](https://github.com/9pings/fad-checker/issues/new?template=false_positive.yml)
17
+ template).
18
+
19
+ ## Supported versions
20
+
21
+ Only the latest released version on npm is supported. There are no backport branches.
22
+
23
+ ## Threat model
24
+
25
+ fad-checker parses **untrusted input**: manifests, lockfiles, archives and certificates from a
26
+ source tree you may not control. The parsing surface is where security bugs are expected, and
27
+ reports there are especially welcome. In particular:
28
+
29
+ - **Archive extraction** — `.jar`/`.war`/`.ear` are unzipped **in memory** (`fflate`), never to
30
+ disk, and nested archives are recursed to a bounded depth. Path traversal (zip-slip) has no
31
+ filesystem to reach, but resource exhaustion (zip bombs) is a valid report.
32
+ - **Certificate and key parsing** — X.509 goes through Node's built-in `crypto.X509Certificate`.
33
+ Key material is **classified and hashed, never decrypted**, and its contents are never written
34
+ to the report.
35
+ - **XML / YAML / TOML parsing** — `pom.xml` (xml2js), lockfiles (js-yaml, smol-toml). XXE and
36
+ entity-expansion reports are in scope.
37
+ - **Command execution** — the only subprocesses are `snyk` (opt-in `--snyk`), `retire`, and
38
+ `curl`/`unzip` for the CVE bundle download.
39
+
40
+ ## What leaves your machine
41
+
42
+ Under `--offline`, **nothing**: no network call is made at all, only the warmed
43
+ `~/.fad-checker/` caches are read. This is enforced by a regression test
44
+ (`test/offline-guarantee.test.js`, a tripwire fetcher that throws if touched) and is
45
+ reproducible in a network-free namespace:
46
+
47
+ ```bash
48
+ unshare -rn node fad-checker.js -s ./proj --offline
49
+ ```
50
+
51
+ Online, fad-checker sends **package coordinates and versions** to the data sources listed in
52
+ [`docs/DATA-SOURCES.md`](docs/DATA-SOURCES.md) (OSV.dev, NVD, Maven Central, npm, Packagist,
53
+ PyPI, NuGet, RubyGems, proxy.golang.org, endoflife.date, FIRST.org EPSS, CISA KEV, deps.dev,
54
+ CIRCL hashlookup) — plus **file checksums** for committed native binaries. It never uploads
55
+ source code, file paths or manifest contents. If you consider your dependency list itself
56
+ sensitive, use the anonymized air-gapped workflow described in the README.
57
+
58
+ Configuration, including your NVD API key, lives in `~/.fad-checker/config.json` with mode
59
+ `0600`. Registry credentials given via `--add-repo --auth`/`--token` are stored there too —
60
+ they are sent only to the registry they belong to, and never appear in a report or export.