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 +74 -0
- package/CHANGELOG.md +171 -0
- package/CONTRIBUTING.md +61 -0
- package/LICENSE +21 -0
- package/README.md +62 -36
- package/SECURITY.md +60 -0
- package/fad-checker.js +38 -1
- package/lib/attribution.js +116 -0
- package/lib/core.js +58 -2
- package/lib/cpe.js +101 -9
- package/lib/cve-match.js +43 -2
- package/lib/dep-record.js +9 -0
- package/lib/maven-version.js +21 -0
- package/lib/retire.js +19 -4
- package/lib/transitive.js +22 -2
- package/lib/version-overlay.js +67 -22
- package/package.json +1 -1
package/lib/version-overlay.js
CHANGED
|
@@ -121,27 +121,48 @@ async function buildModuleManagement(pomPath, store, propsByPom, opts = {}) {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
/**
|
|
124
|
-
* The direct dependencies
|
|
125
|
-
*
|
|
126
|
-
*
|
|
124
|
+
* The direct dependencies of ONE module, with concrete versions resolved (own
|
|
125
|
+
* ${properties} → module's managed map → the global resolved version). These seed
|
|
126
|
+
* the per-module transitive walk.
|
|
127
|
+
*
|
|
128
|
+
* Includes the <dependencies> INHERITED from the local parent chain. Maven inherits
|
|
129
|
+
* them into the child, where they stay DIRECT (depth-0) deps and therefore beat any
|
|
130
|
+
* transitive of the same coord — but core.js#getAllInheritedProps deliberately does
|
|
131
|
+
* NOT merge parent <dependencies> into propsByPom[child] (collectResolvedDeps merges
|
|
132
|
+
* them globally by g:a instead). Reading only the module's own <dependencies> would
|
|
133
|
+
* hide a parent-declared remediation (e.g. commons-compress:1.27.1 overriding the
|
|
134
|
+
* vulnerable 1.24.0 that minio pulls in) and let the overlay "recover" the very
|
|
135
|
+
* version that declaration overrides — fabricating CVEs against a version no
|
|
136
|
+
* classpath holds.
|
|
137
|
+
*
|
|
138
|
+
* Chain is child-first, so the closest declaration of a g:a wins, like Maven. `props`
|
|
139
|
+
* is the child's merged map (parent props as base, child overrides on top), which is
|
|
140
|
+
* the right context for a parent-declared dep too.
|
|
127
141
|
*/
|
|
128
|
-
function buildModuleDirects(pomPath, propsByPom, moduleMgmt, resolvedDeps, opts = {}) {
|
|
142
|
+
function buildModuleDirects(pomPath, store, propsByPom, moduleMgmt, resolvedDeps, opts = {}) {
|
|
129
143
|
const entry = propsByPom[pomPath];
|
|
130
144
|
if (!entry) return [];
|
|
131
145
|
const props = entry.properties || {};
|
|
132
146
|
const out = [];
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
147
|
+
const seen = new Set();
|
|
148
|
+
const chain = store ? localChain(pomPath, store).chain : [pomPath];
|
|
149
|
+
for (const pom of chain) {
|
|
150
|
+
for (const node of (propsByPom[pom] || {}).dependencies || []) {
|
|
151
|
+
const d = flattenSafe(node);
|
|
152
|
+
if (!d || !d.groupId || !d.artifactId || d.optional || d.isImport) continue;
|
|
153
|
+
if (d.scope === "test" && !opts.includeTestDeps) continue;
|
|
154
|
+
if (d.scope === "system" || d.scope === "import") continue;
|
|
155
|
+
// Interpolate ${…} in the coordinate (e.g. ${project.groupId}) like the version.
|
|
156
|
+
const gid = resolveDepVersion(d.groupId, props);
|
|
157
|
+
const aid = resolveDepVersion(d.artifactId, props);
|
|
158
|
+
const key = `${gid}:${aid}`;
|
|
159
|
+
if (seen.has(key)) continue;
|
|
160
|
+
let v = resolveDepVersion(d.rawVersion, props);
|
|
161
|
+
if (!isConcrete(v)) v = moduleMgmt.get(key) || resolvedDeps.get(key)?.version || null;
|
|
162
|
+
if (!isConcrete(v)) continue;
|
|
163
|
+
seen.add(key);
|
|
164
|
+
out.push({ groupId: gid, artifactId: aid, version: String(v), scope: d.scope, exclusions: d.exclusions });
|
|
165
|
+
}
|
|
145
166
|
}
|
|
146
167
|
return out;
|
|
147
168
|
}
|
|
@@ -163,7 +184,7 @@ async function expandPerModuleOverlay(resolvedDeps, store, propsByPom, opts = {}
|
|
|
163
184
|
|
|
164
185
|
for (const pomPath of Object.keys(propsByPom)) {
|
|
165
186
|
const moduleMgmt = await buildModuleManagement(pomPath, store, propsByPom, tOpts);
|
|
166
|
-
const directs = buildModuleDirects(pomPath, propsByPom, moduleMgmt, resolvedDeps, tOpts);
|
|
187
|
+
const directs = buildModuleDirects(pomPath, store, propsByPom, moduleMgmt, resolvedDeps, tOpts);
|
|
167
188
|
if (!directs.length) continue;
|
|
168
189
|
modules++;
|
|
169
190
|
|
|
@@ -173,7 +194,17 @@ async function expandPerModuleOverlay(resolvedDeps, store, propsByPom, opts = {}
|
|
|
173
194
|
...tOpts,
|
|
174
195
|
rootDepMgmt: moduleMgmt,
|
|
175
196
|
maxDepth: opts.maxDepth || 6,
|
|
176
|
-
|
|
197
|
+
// Maven: `test → compile = test`. A version reachable only through a
|
|
198
|
+
// test-scoped dep is on that module's TEST classpath, and the global pass
|
|
199
|
+
// masks it exactly like any other per-module version. Recovering it is the
|
|
200
|
+
// whole point of this overlay, so "test" has to be an accepted propagated
|
|
201
|
+
// scope whenever test deps are in scope at all. Measured on Apache Dubbo
|
|
202
|
+
// 2.7.8: this single omission accounted for ALL 78 findings OSV-Scanner
|
|
203
|
+
// reported that fad missed (jackson-databind:2.8.4:test in registry-sofa,
|
|
204
|
+
// hibernate-validator:5.2.4.Final:test in filter-validation, …).
|
|
205
|
+
includedScopes: opts.includeTestDeps
|
|
206
|
+
? ["compile", "runtime", "provided", "test"]
|
|
207
|
+
: ["compile", "runtime", "provided"],
|
|
177
208
|
});
|
|
178
209
|
} catch { continue; }
|
|
179
210
|
|
|
@@ -184,11 +215,25 @@ async function expandPerModuleOverlay(resolvedDeps, store, propsByPom, opts = {}
|
|
|
184
215
|
if (!existing) continue; // additive to coords already scanned
|
|
185
216
|
if (existing.provenance === "embedded" || existing.provenance === "binary") continue;
|
|
186
217
|
if (!Array.isArray(existing.versions)) existing.versions = isConcrete(existing.version) ? [existing.version] : [];
|
|
187
|
-
|
|
188
|
-
|
|
218
|
+
// Record provenance even when the version is ALREADY in the scan set. Several
|
|
219
|
+
// modules routinely resolve the same version, and they can disagree on scope —
|
|
220
|
+
// on Dubbo, jackson-databind:2.10.4 arrives test-scoped in dubbo-config-spring
|
|
221
|
+
// and COMPILE-scoped in dubbo-configcenter-nacos. Bailing out on the first
|
|
222
|
+
// module to reach a version left only that module's entry, so
|
|
223
|
+
// lib/attribution.js saw a single test-scoped provenance and demoted a genuine
|
|
224
|
+
// production finding to dev. Only the `versions[]` push is conditional.
|
|
225
|
+
const alreadyScanned = existing.versions.includes(String(v));
|
|
226
|
+
if (!alreadyScanned) existing.versions.push(String(v));
|
|
189
227
|
existing.maskedVersions = existing.maskedVersions || [];
|
|
190
|
-
existing.maskedVersions.
|
|
191
|
-
|
|
228
|
+
if (existing.maskedVersions.some(x => String(x.version) === String(v) && x.module === pomPath)) continue;
|
|
229
|
+
// `scope` travels with the recovered version: the record is coord-wide, so this
|
|
230
|
+
// is the only thing that tells lib/attribution.js whether THIS version sits on a
|
|
231
|
+
// test classpath. Without it a test-only version inherits the coord's production
|
|
232
|
+
// flag and inflates the production count.
|
|
233
|
+
existing.maskedVersions.push({ version: String(v), via: t.via, viaPaths: t.viaPaths, module: pomPath, depth: t.depth, scope: t.scope });
|
|
234
|
+
// `recovered` reports versions ADDED to the scan set, so a provenance-only entry
|
|
235
|
+
// for a version another module already contributed must not inflate the count.
|
|
236
|
+
if (!alreadyScanned) recovered.push({ coord: key, version: String(v), module: pomPath, had: existing.version });
|
|
192
237
|
}
|
|
193
238
|
}
|
|
194
239
|
return { appended: recovered.length, modules, recovered };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fad-checker",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.5",
|
|
4
4
|
"description": "Scan ALL Maven, Gradle, npm, Yarn, Composer, Python, C#/.NET, Go & Ruby dependencies — plus embedded JARs (fat-jars/war/ear) — in a source tree ONE SHOT without mvn/python/etc — CVE (EPSS/KEV-prioritised), EOL, obsolete, outdated & licenses, with SBOM/CSAF/SARIF/JSON exports, CI gating and fix recos",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sca",
|