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,148 @@
1
+ const { test } = require("node:test");
2
+ const assert = require("node:assert/strict");
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const {
6
+ parseCpe23,
7
+ matchVersionRange,
8
+ cpeMatchesDep,
9
+ evaluateCveForDep,
10
+ refineMatchesWithCpe,
11
+ } = require("../lib/cpe");
12
+
13
+ const FIX = path.join(__dirname, "fixtures", "cve-samples");
14
+
15
+ test("parseCpe23 splits all 13 fields and handles escaping", () => {
16
+ const c = parseCpe23("cpe:2.3:a:apache:log4j:2.14.0:*:*:*:*:*:*:*");
17
+ assert.equal(c.part, "a");
18
+ assert.equal(c.vendor, "apache");
19
+ assert.equal(c.product, "log4j");
20
+ assert.equal(c.version, "2.14.0");
21
+ });
22
+
23
+ test("parseCpe23 returns null for malformed URI", () => {
24
+ assert.equal(parseCpe23("not a cpe"), null);
25
+ assert.equal(parseCpe23("cpe:2.3:a:vendor"), null);
26
+ assert.equal(parseCpe23(42), null);
27
+ });
28
+
29
+ test("parseCpe23 handles backslash-escaped colons (e.g. eclipse:vert\\.x)", () => {
30
+ const c = parseCpe23("cpe:2.3:a:vendor:weird\\:name:1.0:*:*:*:*:*:*:*");
31
+ assert.equal(c.product, "weird:name");
32
+ });
33
+
34
+ test("matchVersionRange honours versionStartIncluding + versionEndExcluding", () => {
35
+ const m = {
36
+ criteria: "cpe:2.3:a:apache:log4j:*:*:*:*:*:*:*:*",
37
+ vulnerable: true,
38
+ versionStartIncluding: "2.0.0",
39
+ versionEndExcluding: "2.15.0",
40
+ };
41
+ assert.equal(matchVersionRange("2.14.1", m), true);
42
+ assert.equal(matchVersionRange("2.0.0", m), true); // lower-inclusive
43
+ assert.equal(matchVersionRange("2.15.0", m), false); // upper-exclusive
44
+ assert.equal(matchVersionRange("1.9.0", m), false);
45
+ assert.equal(matchVersionRange("2.17.0", m), false);
46
+ });
47
+
48
+ test("matchVersionRange honours hard-pinned criteria version", () => {
49
+ const m = { criteria: "cpe:2.3:a:apache:log4j:2.14.0:*:*:*:*:*:*:*", vulnerable: true };
50
+ assert.equal(matchVersionRange("2.14.0", m), true);
51
+ assert.equal(matchVersionRange("2.14.1", m), false);
52
+ });
53
+
54
+ test("matchVersionRange returns true for unknown dep version (conservative)", () => {
55
+ const m = { criteria: "cpe:2.3:a:apache:log4j:*:*:*:*:*:*:*:*", vulnerable: true, versionEndExcluding: "2.15.0" };
56
+ assert.equal(matchVersionRange(null, m), true);
57
+ });
58
+
59
+ test("cpeMatchesDep — curated map exact maven coord", () => {
60
+ const cpe = parseCpe23("cpe:2.3:a:apache:log4j:*:*:*:*:*:*:*:*");
61
+ const dep = { groupId: "org.apache.logging.log4j", artifactId: "log4j-core", ecosystem: "maven" };
62
+ assert.equal(cpeMatchesDep(cpe, dep), true);
63
+ });
64
+
65
+ test("cpeMatchesDep — heuristic vendor token in groupId", () => {
66
+ const cpe = parseCpe23("cpe:2.3:a:apache:commons-totally-novel:*:*:*:*:*:*:*:*");
67
+ const dep = { groupId: "org.apache.commons", artifactId: "commons-totally-novel", ecosystem: "maven" };
68
+ assert.equal(cpeMatchesDep(cpe, dep), true);
69
+ });
70
+
71
+ test("cpeMatchesDep — npm bare name", () => {
72
+ const cpe = parseCpe23("cpe:2.3:a:lodash:lodash:*:*:*:*:*:node.js:*:*");
73
+ const dep = { groupId: "", artifactId: "lodash", ecosystem: "npm" };
74
+ assert.equal(cpeMatchesDep(cpe, dep), true);
75
+ });
76
+
77
+ test("cpeMatchesDep — npm scoped package via vendor split", () => {
78
+ const cpe = parseCpe23("cpe:2.3:a:scope:pkg:*:*:*:*:*:*:*:*");
79
+ const dep = { groupId: "", artifactId: "@scope/pkg", ecosystem: "npm" };
80
+ assert.equal(cpeMatchesDep(cpe, dep), true);
81
+ });
82
+
83
+ test("cpeMatchesDep — wrong artifact does not match", () => {
84
+ const cpe = parseCpe23("cpe:2.3:a:apache:log4j:*:*:*:*:*:*:*:*");
85
+ const dep = { groupId: "org.apache.commons", artifactId: "commons-io", ecosystem: "maven" };
86
+ assert.equal(cpeMatchesDep(cpe, dep), false);
87
+ });
88
+
89
+ test("evaluateCveForDep — Log4Shell NVD record matches vulnerable log4j-core", () => {
90
+ const cve = JSON.parse(fs.readFileSync(path.join(FIX, "nvd-log4shell.json"), "utf8"));
91
+ const dep = { groupId: "org.apache.logging.log4j", artifactId: "log4j-core", version: "2.14.0", ecosystem: "maven" };
92
+ const { affected, confidence } = evaluateCveForDep(cve, dep);
93
+ assert.equal(affected, true);
94
+ assert.equal(confidence, "exact");
95
+ });
96
+
97
+ test("evaluateCveForDep — patched log4j is not affected (version out of range)", () => {
98
+ const cve = JSON.parse(fs.readFileSync(path.join(FIX, "nvd-log4shell.json"), "utf8"));
99
+ const dep = { groupId: "org.apache.logging.log4j", artifactId: "log4j-core", version: "2.17.1", ecosystem: "maven" };
100
+ const { affected } = evaluateCveForDep(cve, dep);
101
+ assert.equal(affected, false);
102
+ });
103
+
104
+ test("evaluateCveForDep — unrelated maven dep is not affected", () => {
105
+ const cve = JSON.parse(fs.readFileSync(path.join(FIX, "nvd-log4shell.json"), "utf8"));
106
+ const dep = { groupId: "com.google.guava", artifactId: "guava", version: "31.0", ecosystem: "maven" };
107
+ const { affected } = evaluateCveForDep(cve, dep);
108
+ assert.equal(affected, false);
109
+ });
110
+
111
+ test("evaluateCveForDep — npm lodash matches the lodash CVE", () => {
112
+ const cve = JSON.parse(fs.readFileSync(path.join(FIX, "nvd-npm-lodash.json"), "utf8"));
113
+ const dep = { groupId: "", artifactId: "lodash", version: "4.17.10", ecosystem: "npm" };
114
+ const { affected, confidence } = evaluateCveForDep(cve, dep);
115
+ assert.equal(affected, true);
116
+ assert.equal(confidence, "exact");
117
+ });
118
+
119
+ test("evaluateCveForDep — patched npm lodash is not affected", () => {
120
+ const cve = JSON.parse(fs.readFileSync(path.join(FIX, "nvd-npm-lodash.json"), "utf8"));
121
+ const dep = { groupId: "", artifactId: "lodash", version: "4.17.20", ecosystem: "npm" };
122
+ const { affected } = evaluateCveForDep(cve, dep);
123
+ assert.equal(affected, false);
124
+ });
125
+
126
+ test("refineMatchesWithCpe upgrades possible→exact when curated map confirms", () => {
127
+ const cve = JSON.parse(fs.readFileSync(path.join(FIX, "nvd-log4shell.json"), "utf8"));
128
+ const matches = [{
129
+ dep: { groupId: "org.apache.logging.log4j", artifactId: "log4j-core", version: "2.14.0", ecosystem: "maven" },
130
+ cve: { id: cve.id, configurations: cve.configurations, severity: "HIGH" },
131
+ confidence: "possible",
132
+ }];
133
+ refineMatchesWithCpe(matches);
134
+ assert.equal(matches[0].confidence, "exact");
135
+ assert.equal(matches[0].cpeConfidence, "exact");
136
+ assert.equal(matches[0].cpeFiltered, undefined);
137
+ });
138
+
139
+ test("refineMatchesWithCpe flags out-of-range version as likely FP", () => {
140
+ const cve = JSON.parse(fs.readFileSync(path.join(FIX, "nvd-log4shell.json"), "utf8"));
141
+ const matches = [{
142
+ dep: { groupId: "org.apache.logging.log4j", artifactId: "log4j-core", version: "2.17.1", ecosystem: "maven" },
143
+ cve: { id: cve.id, configurations: cve.configurations, severity: "HIGH" },
144
+ confidence: "probable",
145
+ }];
146
+ refineMatchesWithCpe(matches);
147
+ assert.equal(matches[0].cpeFiltered, true);
148
+ });
@@ -0,0 +1,39 @@
1
+ const { test } = require("node:test");
2
+ const assert = require("node:assert/strict");
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { extractMavenRelevantCve, isMavenRelevant } = require("../lib/cve-download");
6
+
7
+ const SAMPLES = path.join(__dirname, "fixtures", "cve-samples");
8
+
9
+ test("extractMavenRelevantCve returns null for non-Java CVEs", () => {
10
+ const json = JSON.parse(fs.readFileSync(path.join(SAMPLES, "cve-non-java.json"), "utf8"));
11
+ assert.equal(extractMavenRelevantCve(json), null);
12
+ });
13
+
14
+ test("extractMavenRelevantCve parses log4j CVE with packageName", () => {
15
+ const json = JSON.parse(fs.readFileSync(path.join(SAMPLES, "cve-with-packagename.json"), "utf8"));
16
+ const cve = extractMavenRelevantCve(json);
17
+ assert.equal(cve.id, "CVE-2021-44228");
18
+ assert.equal(cve.severity, "CRITICAL");
19
+ assert.equal(cve.score, 10);
20
+ assert.equal(cve.fixVersion, "2.15.0");
21
+ assert.equal(cve.affected[0].packageName, "org.apache.logging.log4j:log4j-core");
22
+ });
23
+
24
+ test("extractMavenRelevantCve handles product-only CVEs (no packageName)", () => {
25
+ const json = JSON.parse(fs.readFileSync(path.join(SAMPLES, "cve-product-only.json"), "utf8"));
26
+ const cve = extractMavenRelevantCve(json);
27
+ assert.equal(cve.id, "CVE-2017-5638");
28
+ assert.equal(cve.affected[0].product, "struts2-core");
29
+ assert.equal(cve.affected[0].packageName, null);
30
+ });
31
+
32
+ test("isMavenRelevant true on known java vendors", () => {
33
+ assert.equal(isMavenRelevant([{ vendor: "apache", product: "log4j-core" }]), true);
34
+ assert.equal(isMavenRelevant([{ vendor: "fasterxml", product: "jackson" }]), true);
35
+ });
36
+
37
+ test("isMavenRelevant false on unrelated ecosystems", () => {
38
+ assert.equal(isMavenRelevant([{ vendor: "node-vendor", product: "left-pad", collectionURL: "https://registry.npmjs.org" }]), false);
39
+ });
@@ -0,0 +1,108 @@
1
+ const { test } = require("node:test");
2
+ const assert = require("node:assert/strict");
3
+ const path = require("path");
4
+ const fs = require("fs");
5
+ const core = require("../lib/core");
6
+ const { collectResolvedDeps, matchDepsAgainstCves, vendorMatchesGroup } = require("../lib/cve-match");
7
+ const { extractMavenRelevantCve, isMavenRelevant } = require("../lib/cve-download");
8
+
9
+ const FIXTURES = path.join(__dirname, "fixtures");
10
+ const COMPLEX = path.join(FIXTURES, "complex-enterprise");
11
+ const CVE_SAMPLES = path.join(FIXTURES, "cve-samples");
12
+
13
+ async function pipeline(src) {
14
+ const store = core.newMetadataStore();
15
+ const props = {};
16
+ const pomFiles = core.findPomFiles(src);
17
+ for (const f of pomFiles) await core.parsePom(f, store);
18
+ for (const f of pomFiles) await core.getAllInheritedProps(f, store, props);
19
+ return { store, props };
20
+ }
21
+
22
+ function buildIndexFromSamples() {
23
+ const index = { meta: {}, byPackageName: {}, byProduct: {} };
24
+ for (const f of fs.readdirSync(CVE_SAMPLES)) {
25
+ const json = JSON.parse(fs.readFileSync(path.join(CVE_SAMPLES, f), "utf8"));
26
+ const cve = extractMavenRelevantCve(json);
27
+ if (!cve) continue;
28
+ for (const a of cve.affected) {
29
+ const e = { id: cve.id, severity: cve.severity, score: cve.score, description: cve.description, ranges: a.ranges, vendor: a.vendor, product: a.product, fixVersion: cve.fixVersion };
30
+ if (a.packageName) (index.byPackageName[a.packageName.toLowerCase()] ||= []).push(e);
31
+ if (a.product) (index.byProduct[a.product] ||= []).push(e);
32
+ }
33
+ }
34
+ return index;
35
+ }
36
+
37
+ test("isMavenRelevant filters out non-Java CVEs", () => {
38
+ const npmCve = JSON.parse(fs.readFileSync(path.join(CVE_SAMPLES, "cve-non-java.json"), "utf8"));
39
+ assert.equal(isMavenRelevant(npmCve.containers.cna.affected), false);
40
+
41
+ const log4jCve = JSON.parse(fs.readFileSync(path.join(CVE_SAMPLES, "cve-with-packagename.json"), "utf8"));
42
+ assert.equal(isMavenRelevant(log4jCve.containers.cna.affected), true);
43
+ });
44
+
45
+ test("vendorMatchesGroup heuristic", () => {
46
+ assert.equal(vendorMatchesGroup("apache", "org.apache.commons"), true);
47
+ assert.equal(vendorMatchesGroup("apache", "org.eclipse.jetty"), false);
48
+ assert.equal(vendorMatchesGroup("springframework", "org.springframework"), true);
49
+ });
50
+
51
+ test("collectResolvedDeps dedupes by g:a and includes external parent POMs", async () => {
52
+ const { store, props } = await pipeline(COMPLEX);
53
+ const deps = collectResolvedDeps(store, props, {});
54
+ // External Spring Boot parent should be present as scope='parent'
55
+ const sbParent = deps.get("org.springframework.boot:spring-boot-starter-parent");
56
+ assert.ok(sbParent, "external parent should be collected");
57
+ assert.equal(sbParent.scope, "parent");
58
+ });
59
+
60
+ test("collectResolvedDeps keeps highest version on conflict", async () => {
61
+ const { store, props } = await pipeline(COMPLEX);
62
+ const deps = collectResolvedDeps(store, props, {});
63
+ const lang = deps.get("org.apache.commons:commons-lang3");
64
+ if (lang) assert.equal(lang.version, "3.12.0");
65
+ });
66
+
67
+ test("collectResolvedDeps --ignore-test drops test-scope deps", async () => {
68
+ const { store, props } = await pipeline(COMPLEX);
69
+ const withTest = collectResolvedDeps(store, props, {});
70
+ const withoutTest = collectResolvedDeps(store, props, { ignoreTest: true });
71
+ assert.ok(withTest.has("junit:junit"));
72
+ assert.ok(!withoutTest.has("junit:junit"));
73
+ });
74
+
75
+ test("matchDepsAgainstCves: log4j 2.14 is matched as CRITICAL via packageName", async () => {
76
+ const { store, props } = await pipeline(COMPLEX);
77
+ const deps = collectResolvedDeps(store, props, {});
78
+ const idx = buildIndexFromSamples();
79
+ const matches = matchDepsAgainstCves(deps, idx);
80
+ const log4j = matches.find(m => m.cve.id === "CVE-2021-44228");
81
+ assert.ok(log4j, "log4j CVE not matched");
82
+ assert.equal(log4j.dep.artifactId, "log4j-core");
83
+ assert.equal(log4j.cve.severity, "CRITICAL");
84
+ assert.equal(log4j.confidence, "exact");
85
+ });
86
+
87
+ test("matchDepsAgainstCves dedupes by (dep, cveId)", () => {
88
+ // Synthesize an index where the same dep+cveId appears twice
89
+ const idx = {
90
+ byPackageName: {
91
+ "org.apache.logging.log4j:log4j-core": [
92
+ { id: "CVE-2021-44228", severity: "CRITICAL", ranges: [{ version: "2.0", lessThan: "2.15.0" }], vendor: "apache", product: "log4j-core" },
93
+ ],
94
+ },
95
+ byProduct: {
96
+ "log4j-core": [
97
+ { id: "CVE-2021-44228", severity: "CRITICAL", ranges: [{ version: "2.0", lessThan: "2.15.0" }], vendor: "apache", product: "log4j-core" },
98
+ ],
99
+ },
100
+ };
101
+ const deps = new Map([
102
+ ["org.apache.logging.log4j:log4j-core", {
103
+ groupId: "org.apache.logging.log4j", artifactId: "log4j-core", version: "2.14.0", scope: "compile", pomPaths: [],
104
+ }],
105
+ ]);
106
+ const matches = matchDepsAgainstCves(deps, idx);
107
+ assert.equal(matches.length, 1, "duplicate (dep, cveId) match should be dedup'd");
108
+ });
@@ -0,0 +1,79 @@
1
+ const { test } = require("node:test");
2
+ const assert = require("node:assert/strict");
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const os = require("os");
6
+ const { generateHtmlReport, generateWordReport, computeStats, writeReports, esc } = require("../lib/cve-report");
7
+
8
+ const sampleMatches = [
9
+ {
10
+ dep: { groupId: "org.apache.logging.log4j", artifactId: "log4j-core", version: "2.14.0", scope: "compile" },
11
+ cve: { id: "CVE-2021-44228", severity: "CRITICAL", score: 10, description: "Log4Shell", fixVersion: "2.15.0" },
12
+ confidence: "exact",
13
+ },
14
+ {
15
+ dep: { groupId: "com.fasterxml.jackson.core", artifactId: "jackson-databind", version: "2.13.0", scope: "compile" },
16
+ cve: { id: "CVE-2022-42003", severity: "HIGH", score: 7.5, description: "DoS via deep nesting", fixVersion: "2.13.5" },
17
+ confidence: "exact",
18
+ },
19
+ ];
20
+
21
+ const projectInfo = { name: "demo", src: "/tmp/demo", generatedAt: "2026-05-21", cveDataDate: null };
22
+
23
+ test("computeStats counts by severity", () => {
24
+ const s = computeStats(sampleMatches);
25
+ assert.equal(s.total, 2);
26
+ assert.equal(s.critical, 1);
27
+ assert.equal(s.high, 1);
28
+ assert.equal(s.medium, 0);
29
+ });
30
+
31
+ test("computeStats handles undefined/null severity", () => {
32
+ const s = computeStats([{ cve: { severity: null } }, { cve: {} }]);
33
+ assert.equal(s.unknown, 2);
34
+ });
35
+
36
+ test("esc escapes HTML entities", () => {
37
+ assert.equal(esc(`<script>alert("x")</script>&`), "&lt;script&gt;alert(&quot;x&quot;)&lt;/script&gt;&amp;");
38
+ });
39
+
40
+ test("generateHtmlReport produces well-formed self-contained HTML", () => {
41
+ const html = generateHtmlReport({
42
+ cveMatches: sampleMatches,
43
+ eolResults: [], obsoleteResults: [], outdatedResults: [],
44
+ projectInfo,
45
+ });
46
+ assert.ok(html.startsWith("<!doctype html>"));
47
+ assert.ok(html.includes("CVE-2021-44228"));
48
+ assert.ok(html.includes("Log4Shell"));
49
+ assert.ok(html.includes("<style>"), "should have inline CSS");
50
+ assert.ok(!html.includes("</body>\n<body>"), "no double body");
51
+ });
52
+
53
+ test("generateWordReport contains Word XML namespaces", () => {
54
+ const doc = generateWordReport({
55
+ cveMatches: sampleMatches,
56
+ eolResults: [], obsoleteResults: [], outdatedResults: [],
57
+ projectInfo,
58
+ });
59
+ assert.ok(doc.includes("xmlns:o=\"urn:schemas-microsoft-com:office:office\""));
60
+ assert.ok(doc.includes("Word.Document"));
61
+ });
62
+
63
+ test("writeReports writes both files to disk", async () => {
64
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "fad-check-rep-"));
65
+ const r = await writeReports({
66
+ cveMatches: sampleMatches,
67
+ eolResults: [{ dep: { groupId: "org.hibernate", artifactId: "hibernate-core", version: "5.6" }, product: "Hibernate ORM", eol: "2025-08-08" }],
68
+ obsoleteResults: [{ dep: { groupId: "log4j", artifactId: "log4j", version: "1.2" }, severity: "CRITICAL", replacement: "log4j-core 2.x" }],
69
+ outdatedResults: [],
70
+ projectInfo,
71
+ outputDir: tmp,
72
+ });
73
+ assert.ok(fs.existsSync(r.htmlPath));
74
+ assert.ok(fs.existsSync(r.docPath));
75
+ const html = fs.readFileSync(r.htmlPath, "utf8");
76
+ assert.ok(html.includes("Hibernate ORM"));
77
+ assert.ok(html.includes("log4j:log4j"));
78
+ fs.rmSync(tmp, { recursive: true, force: true });
79
+ });
@@ -0,0 +1,32 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project xmlns="http://maven.apache.org/POM/4.0.0">
3
+ <modelVersion>4.0.0</modelVersion>
4
+ <parent>
5
+ <groupId>com.acme.enterprise</groupId>
6
+ <artifactId>enterprise-root</artifactId>
7
+ <version>4.2.1</version>
8
+ <relativePath>..</relativePath>
9
+ </parent>
10
+ <artifactId>api</artifactId>
11
+ <dependencyManagement>
12
+ <dependencies>
13
+ <dependency>
14
+ <groupId>com.acme.enterprise</groupId>
15
+ <artifactId>build-bom</artifactId>
16
+ <version>4.2.1</version>
17
+ <type>pom</type>
18
+ <scope>import</scope>
19
+ </dependency>
20
+ </dependencies>
21
+ </dependencyManagement>
22
+ <dependencies>
23
+ <dependency>
24
+ <groupId>com.fasterxml.jackson.core</groupId>
25
+ <artifactId>jackson-databind</artifactId>
26
+ </dependency>
27
+ <dependency>
28
+ <groupId>com.acme.private</groupId>
29
+ <artifactId>acme-commons</artifactId>
30
+ </dependency>
31
+ </dependencies>
32
+ </project>
@@ -0,0 +1,46 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project xmlns="http://maven.apache.org/POM/4.0.0">
3
+ <modelVersion>4.0.0</modelVersion>
4
+ <parent>
5
+ <groupId>com.acme.enterprise</groupId>
6
+ <artifactId>enterprise-root</artifactId>
7
+ <version>4.2.1</version>
8
+ <relativePath>..</relativePath>
9
+ </parent>
10
+ <artifactId>build-bom</artifactId>
11
+ <packaging>pom</packaging>
12
+ <dependencyManagement>
13
+ <dependencies>
14
+ <dependency>
15
+ <groupId>org.hibernate</groupId>
16
+ <artifactId>hibernate-core</artifactId>
17
+ <version>${hibernate.version}</version>
18
+ </dependency>
19
+ <dependency>
20
+ <groupId>com.fasterxml.jackson.core</groupId>
21
+ <artifactId>jackson-databind</artifactId>
22
+ <version>${jackson.version}</version>
23
+ </dependency>
24
+ <dependency>
25
+ <groupId>org.apache.logging.log4j</groupId>
26
+ <artifactId>log4j-core</artifactId>
27
+ <version>2.14.0</version>
28
+ </dependency>
29
+ <dependency>
30
+ <groupId>com.acme.private</groupId>
31
+ <artifactId>acme-commons</artifactId>
32
+ <version>${acme-commons.version}</version>
33
+ </dependency>
34
+ <dependency>
35
+ <groupId>commons-io</groupId>
36
+ <artifactId>commons-io</artifactId>
37
+ <version>2.7</version>
38
+ </dependency>
39
+ <dependency>
40
+ <groupId>org.codehaus.jackson</groupId>
41
+ <artifactId>jackson-mapper-asl</artifactId>
42
+ <version>1.9.13</version>
43
+ </dependency>
44
+ </dependencies>
45
+ </dependencyManagement>
46
+ </project>
@@ -0,0 +1,37 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project xmlns="http://maven.apache.org/POM/4.0.0">
3
+ <modelVersion>4.0.0</modelVersion>
4
+ <parent>
5
+ <groupId>com.acme.enterprise</groupId>
6
+ <artifactId>enterprise-root</artifactId>
7
+ <version>4.2.1</version>
8
+ <relativePath>..</relativePath>
9
+ </parent>
10
+ <artifactId>dao</artifactId>
11
+ <dependencyManagement>
12
+ <dependencies>
13
+ <dependency>
14
+ <groupId>com.acme.enterprise</groupId>
15
+ <artifactId>build-bom</artifactId>
16
+ <version>4.2.1</version>
17
+ <type>pom</type>
18
+ <scope>import</scope>
19
+ </dependency>
20
+ </dependencies>
21
+ </dependencyManagement>
22
+ <dependencies>
23
+ <dependency>
24
+ <groupId>org.hibernate</groupId>
25
+ <artifactId>hibernate-core</artifactId>
26
+ </dependency>
27
+ <dependency>
28
+ <groupId>org.apache.commons</groupId>
29
+ <artifactId>commons-lang3</artifactId>
30
+ <version>3.12.0</version>
31
+ </dependency>
32
+ <dependency>
33
+ <groupId>commons-io</groupId>
34
+ <artifactId>commons-io</artifactId>
35
+ </dependency>
36
+ </dependencies>
37
+ </project>
@@ -0,0 +1,66 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project xmlns="http://maven.apache.org/POM/4.0.0">
3
+ <modelVersion>4.0.0</modelVersion>
4
+ <parent>
5
+ <groupId>org.springframework.boot</groupId>
6
+ <artifactId>spring-boot-starter-parent</artifactId>
7
+ <version>2.7.18</version>
8
+ </parent>
9
+ <groupId>com.acme.enterprise</groupId>
10
+ <artifactId>enterprise-root</artifactId>
11
+ <version>4.2.1</version>
12
+ <packaging>pom</packaging>
13
+ <modules>
14
+ <module>build</module>
15
+ <module>api</module>
16
+ <module>dao</module>
17
+ <module>web</module>
18
+ </modules>
19
+ <properties>
20
+ <java.version>17</java.version>
21
+ <hibernate.version>5.6.15.Final</hibernate.version>
22
+ <jackson.version>2.13.5</jackson.version>
23
+ <acme-commons.version>3.0.0</acme-commons.version>
24
+ </properties>
25
+ <profiles>
26
+ <profile>
27
+ <id>dev</id>
28
+ <activation>
29
+ <activeByDefault>true</activeByDefault>
30
+ </activation>
31
+ <properties>
32
+ <env.profile>dev</env.profile>
33
+ </properties>
34
+ <dependencies>
35
+ <dependency>
36
+ <groupId>com.h2database</groupId>
37
+ <artifactId>h2</artifactId>
38
+ <version>2.2.224</version>
39
+ </dependency>
40
+ </dependencies>
41
+ </profile>
42
+ <profile>
43
+ <id>prod</id>
44
+ <properties>
45
+ <env.profile>prod</env.profile>
46
+ </properties>
47
+ <dependencies>
48
+ <dependency>
49
+ <groupId>org.postgresql</groupId>
50
+ <artifactId>postgresql</artifactId>
51
+ <version>42.6.0</version>
52
+ </dependency>
53
+ </dependencies>
54
+ </profile>
55
+ <profile>
56
+ <id>oracle</id>
57
+ <dependencies>
58
+ <dependency>
59
+ <groupId>com.acme.private</groupId>
60
+ <artifactId>acme-oracle-driver</artifactId>
61
+ <version>1.2.3</version>
62
+ </dependency>
63
+ </dependencies>
64
+ </profile>
65
+ </profiles>
66
+ </project>
@@ -0,0 +1,77 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project xmlns="http://maven.apache.org/POM/4.0.0">
3
+ <modelVersion>4.0.0</modelVersion>
4
+ <parent>
5
+ <groupId>com.acme.enterprise</groupId>
6
+ <artifactId>enterprise-root</artifactId>
7
+ <version>4.2.1</version>
8
+ <relativePath>..</relativePath>
9
+ </parent>
10
+ <artifactId>web</artifactId>
11
+ <packaging>war</packaging>
12
+ <profiles>
13
+ <profile>
14
+ <id>tomcat</id>
15
+ <activation>
16
+ <activeByDefault>true</activeByDefault>
17
+ </activation>
18
+ <properties>
19
+ <servlet.container>tomcat</servlet.container>
20
+ </properties>
21
+ <dependencies>
22
+ <dependency>
23
+ <groupId>org.apache.tomcat.embed</groupId>
24
+ <artifactId>tomcat-embed-core</artifactId>
25
+ <version>9.0.83</version>
26
+ </dependency>
27
+ </dependencies>
28
+ </profile>
29
+ <profile>
30
+ <id>jetty</id>
31
+ <dependencies>
32
+ <dependency>
33
+ <groupId>org.eclipse.jetty</groupId>
34
+ <artifactId>jetty-server</artifactId>
35
+ <version>9.4.51.v20230217</version>
36
+ </dependency>
37
+ </dependencies>
38
+ </profile>
39
+ </profiles>
40
+ <dependencyManagement>
41
+ <dependencies>
42
+ <dependency>
43
+ <groupId>com.acme.enterprise</groupId>
44
+ <artifactId>build-bom</artifactId>
45
+ <version>4.2.1</version>
46
+ <type>pom</type>
47
+ <scope>import</scope>
48
+ </dependency>
49
+ </dependencies>
50
+ </dependencyManagement>
51
+ <dependencies>
52
+ <dependency>
53
+ <groupId>com.acme.enterprise</groupId>
54
+ <artifactId>api</artifactId>
55
+ <version>4.2.1</version>
56
+ </dependency>
57
+ <dependency>
58
+ <groupId>com.acme.enterprise</groupId>
59
+ <artifactId>dao</artifactId>
60
+ <version>4.2.1</version>
61
+ </dependency>
62
+ <dependency>
63
+ <groupId>org.apache.logging.log4j</groupId>
64
+ <artifactId>log4j-core</artifactId>
65
+ </dependency>
66
+ <dependency>
67
+ <groupId>org.codehaus.jackson</groupId>
68
+ <artifactId>jackson-mapper-asl</artifactId>
69
+ </dependency>
70
+ <dependency>
71
+ <groupId>junit</groupId>
72
+ <artifactId>junit</artifactId>
73
+ <version>4.13.2</version>
74
+ <scope>test</scope>
75
+ </dependency>
76
+ </dependencies>
77
+ </project>
@@ -0,0 +1,19 @@
1
+ {
2
+ "cveMetadata": {
3
+ "cveId": "CVE-2024-00001"
4
+ },
5
+ "containers": {
6
+ "cna": {
7
+ "descriptions": [{ "lang": "en", "value": "Some npm package vulnerability — should be excluded from Maven index." }],
8
+ "metrics": [{ "cvssV3_1": { "baseScore": 5.0, "baseSeverity": "MEDIUM" } }],
9
+ "affected": [
10
+ {
11
+ "vendor": "npm-vendor",
12
+ "product": "left-pad",
13
+ "collectionURL": "https://registry.npmjs.org",
14
+ "versions": [{ "version": "1.0.0", "status": "affected", "lessThan": "1.3.0" }]
15
+ }
16
+ ]
17
+ }
18
+ }
19
+ }