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 +21 -21
- package/CRITICAL-REVIEW.md +343 -0
- package/README.md +37 -37
- package/completions/{fad-check.bash → fad-checker.bash} +3 -3
- package/completions/{fad-check.zsh → fad-checker.zsh} +2 -2
- package/docs/ARCHITECTURE.md +10 -10
- package/docs/USAGE.md +35 -35
- package/{fad-check.js → fad-checker.js} +25 -21
- package/lib/cache-archive.js +9 -9
- package/lib/config.js +3 -3
- package/lib/cpe.js +23 -5
- package/lib/cve-download.js +3 -3
- package/lib/cve-match.js +13 -7
- package/lib/cve-report.js +4 -4
- package/lib/maven-repo.js +3 -3
- package/lib/maven-version.js +6 -0
- package/lib/nvd.js +62 -17
- package/lib/osv.js +53 -28
- package/lib/outdated.js +25 -5
- package/lib/retire.js +5 -5
- package/lib/scan-completeness.js +2 -2
- package/lib/snyk.js +4 -4
- package/lib/transitive.js +15 -7
- package/package.json +6 -6
- package/test/core.test.js +4 -4
- package/test/cpe.test.js +18 -0
- package/test/cve-match.test.js +56 -0
- package/test/cve-report.test.js +1 -1
- package/test/maven-version.test.js +9 -0
- package/test/monorepo.test.js +1 -1
- package/test/snyk.test.js +1 -1
- package/test/transitive.test.js +2 -2
package/test/cpe.test.js
CHANGED
|
@@ -51,6 +51,24 @@ test("matchVersionRange honours hard-pinned criteria version", () => {
|
|
|
51
51
|
assert.equal(matchVersionRange("2.14.1", m), false);
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
+
test("matchVersionRange folds CPE update qualifier into the version pin (H5)", () => {
|
|
55
|
+
// CPE 2.3 ":1.0.0:rc1:" describes the 1.0.0-rc1 pre-release. A release dep
|
|
56
|
+
// at 1.0.0 must NOT match — that was the H5 cascade.
|
|
57
|
+
const beta = { criteria: "cpe:2.3:a:apache:foo:1.0.0:beta1:*:*:*:*:*:*", vulnerable: true };
|
|
58
|
+
assert.equal(matchVersionRange("1.0.0", beta), false);
|
|
59
|
+
assert.equal(matchVersionRange("1.0.0-beta1", beta), true);
|
|
60
|
+
|
|
61
|
+
const rc = { criteria: "cpe:2.3:a:apache:foo:1.0.0:rc1:*:*:*:*:*:*", vulnerable: true };
|
|
62
|
+
assert.equal(matchVersionRange("1.0.0", rc), false);
|
|
63
|
+
assert.equal(matchVersionRange("1.0.0-rc1", rc), true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("cpeMatchesDep rejects short-token vendor leak (M4 mirror of H2)", () => {
|
|
67
|
+
const cpe = parseCpe23("cpe:2.3:a:a:log4j-core:*:*:*:*:*:*:*:*");
|
|
68
|
+
const dep = { groupId: "com.unrelated.log4j-core", artifactId: "log4j-core", ecosystem: "maven" };
|
|
69
|
+
assert.equal(cpeMatchesDep(cpe, dep), false);
|
|
70
|
+
});
|
|
71
|
+
|
|
54
72
|
test("matchVersionRange returns true for unknown dep version (conservative)", () => {
|
|
55
73
|
const m = { criteria: "cpe:2.3:a:apache:log4j:*:*:*:*:*:*:*:*", vulnerable: true, versionEndExcluding: "2.15.0" };
|
|
56
74
|
assert.equal(matchVersionRange(null, m), true);
|
package/test/cve-match.test.js
CHANGED
|
@@ -48,6 +48,62 @@ test("vendorMatchesGroup heuristic", () => {
|
|
|
48
48
|
assert.equal(vendorMatchesGroup("springframework", "org.springframework"), true);
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
+
test("vendorMatchesGroup rejects substring leaks (H2)", () => {
|
|
52
|
+
// All these cases used to leak through unbounded `g.includes(v)` / `v.includes(g)`
|
|
53
|
+
// branches. The fix keeps only equality + dot-segment membership.
|
|
54
|
+
assert.equal(vendorMatchesGroup("a", "com.acme.client"), false);
|
|
55
|
+
assert.equal(vendorMatchesGroup("ibm", "com.ibmcloudant.driver"), false);
|
|
56
|
+
assert.equal(vendorMatchesGroup("go", "org.golang.x"), false);
|
|
57
|
+
// "open" used to wrongly match every org.opensaml.* groupId via substring
|
|
58
|
+
assert.equal(vendorMatchesGroup("open", "org.opensaml.core"), false);
|
|
59
|
+
// Dot-segment match still works for legitimate cases
|
|
60
|
+
assert.equal(vendorMatchesGroup("apache", "org.apache.commons"), true);
|
|
61
|
+
assert.equal(vendorMatchesGroup("springframework", "org.springframework.boot"), true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("matchDepsAgainstCves hides 'possible' tier by default (H3)", () => {
|
|
65
|
+
// product matches `log4j-core` but vendor `acme` doesn't match groupId.
|
|
66
|
+
// Without opts.includePossibleTier we expect zero matches.
|
|
67
|
+
const idx = {
|
|
68
|
+
byPackageName: {},
|
|
69
|
+
byProduct: {
|
|
70
|
+
"log4j-core": [
|
|
71
|
+
{ id: "CVE-FAKE-0001", severity: "HIGH", vendor: "acme", product: "log4j-core",
|
|
72
|
+
ranges: [{ version: "2.0", lessThan: "2.20.0", status: "affected" }] },
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
const deps = new Map([
|
|
77
|
+
["org.apache.logging.log4j:log4j-core", {
|
|
78
|
+
groupId: "org.apache.logging.log4j", artifactId: "log4j-core", version: "2.14.0", scope: "compile", pomPaths: [],
|
|
79
|
+
}],
|
|
80
|
+
]);
|
|
81
|
+
assert.equal(matchDepsAgainstCves(deps, idx).length, 0);
|
|
82
|
+
assert.equal(matchDepsAgainstCves(deps, idx, { includePossibleTier: true }).length, 1);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("matchDepsAgainstCves does not flag deps when range has no bounds (H1+H3)", () => {
|
|
86
|
+
// Sparse range entry (`{status:"affected"}` with no bounds) used to flag
|
|
87
|
+
// every version that hit the product bucket. Combined with H3 hiding the
|
|
88
|
+
// possible tier, this should now produce zero matches even when the
|
|
89
|
+
// vendor matches.
|
|
90
|
+
const idx = {
|
|
91
|
+
byPackageName: {},
|
|
92
|
+
byProduct: {
|
|
93
|
+
"log4j-core": [
|
|
94
|
+
{ id: "CVE-FAKE-0002", severity: "HIGH", vendor: "apache", product: "log4j-core",
|
|
95
|
+
ranges: [{ status: "affected" }] },
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
const deps = new Map([
|
|
100
|
+
["org.apache.logging.log4j:log4j-core", {
|
|
101
|
+
groupId: "org.apache.logging.log4j", artifactId: "log4j-core", version: "2.99.0", scope: "compile", pomPaths: [],
|
|
102
|
+
}],
|
|
103
|
+
]);
|
|
104
|
+
assert.equal(matchDepsAgainstCves(deps, idx).length, 0);
|
|
105
|
+
});
|
|
106
|
+
|
|
51
107
|
test("collectResolvedDeps dedupes by g:a and includes external parent POMs", async () => {
|
|
52
108
|
const { store, props } = await pipeline(COMPLEX);
|
|
53
109
|
const deps = collectResolvedDeps(store, props, {});
|
package/test/cve-report.test.js
CHANGED
|
@@ -61,7 +61,7 @@ test("generateWordReport contains Word XML namespaces", () => {
|
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
test("writeReports writes both files to disk", async () => {
|
|
64
|
-
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "fad-
|
|
64
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "fad-checker-rep-"));
|
|
65
65
|
const r = await writeReports({
|
|
66
66
|
cveMatches: sampleMatches,
|
|
67
67
|
eolResults: [{ dep: { groupId: "org.hibernate", artifactId: "hibernate-core", version: "5.6" }, product: "Hibernate ORM", eol: "2025-08-08" }],
|
|
@@ -41,6 +41,15 @@ test("isVersionAffected returns false when status != affected", () => {
|
|
|
41
41
|
assert.equal(isVersionAffected("1.5", spec), false);
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
+
test("isVersionAffected fail-closed when spec has no version bounds (H1)", () => {
|
|
45
|
+
// CVEProject sometimes emits {status:"affected"} stubs with no version
|
|
46
|
+
// fields. The matcher must NOT fall through to `return true` — that was
|
|
47
|
+
// the H1 cascade.
|
|
48
|
+
assert.equal(isVersionAffected("2.14.0", { status: "affected" }), false);
|
|
49
|
+
assert.equal(isVersionAffected("2.14.0", {}), false);
|
|
50
|
+
assert.equal(isVersionAffected("0.0.1", { status: "affected" }), false);
|
|
51
|
+
});
|
|
52
|
+
|
|
44
53
|
test("parseRange handles Maven range syntax", () => {
|
|
45
54
|
assert.deepEqual(parseRange("1.2.3"), { exact: "1.2.3" });
|
|
46
55
|
assert.deepEqual(parseRange("[1.0,2.0)"), { lower: "1.0", lowerInclusive: true, upper: "2.0", upperInclusive: false });
|
package/test/monorepo.test.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* - 1 npm package (package-lock v3) with prod/dev/peer deps
|
|
5
5
|
* - 1 yarn-v1 package with prod/dev deps + a private @acme/* dep
|
|
6
6
|
*
|
|
7
|
-
* Drives the same code paths as `fad-
|
|
7
|
+
* Drives the same code paths as `fad-checker --report` minus the network calls.
|
|
8
8
|
*/
|
|
9
9
|
const { test } = require("node:test");
|
|
10
10
|
const assert = require("node:assert/strict");
|
package/test/snyk.test.js
CHANGED
|
@@ -35,7 +35,7 @@ test("parseSnykStdout accepts a JSON object or array", () => {
|
|
|
35
35
|
assert.equal(obj.length, 1);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
test("parseSnykResults normalises to fad-
|
|
38
|
+
test("parseSnykResults normalises to fad-checker match shape", () => {
|
|
39
39
|
const out = parseSnykResults(snykSample);
|
|
40
40
|
assert.equal(out.length, 2);
|
|
41
41
|
assert.equal(out[0].dep.groupId, "org.apache.logging.log4j");
|
package/test/transitive.test.js
CHANGED
|
@@ -5,9 +5,9 @@ const os = require("os");
|
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const { parsePomXml, resolveProps, buildMgmt, SCOPE_MATRIX, resolveTransitiveDeps } = require("../lib/transitive");
|
|
7
7
|
|
|
8
|
-
// Per-test cache dir so we don't poison ~/.fad-
|
|
8
|
+
// Per-test cache dir so we don't poison ~/.fad-checker/poms-cache during tests
|
|
9
9
|
function freshCache() {
|
|
10
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "fad-
|
|
10
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "fad-checker-pom-cache-"));
|
|
11
11
|
return dir;
|
|
12
12
|
}
|
|
13
13
|
|