@cyclonedx/cdxgen 10.6.1 → 10.7.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 +9 -5
- package/analyzer.js +1 -1
- package/bin/cdxgen.js +14 -2
- package/bin/repl.js +17 -1
- package/data/lic-mapping.json +5 -2
- package/display.js +46 -9
- package/index.js +88 -55
- package/package.json +10 -10
- package/piptree.js +15 -8
- package/server.js +18 -6
- package/types/display.d.ts +3 -2
- package/types/display.d.ts.map +1 -1
- package/types/evinser.d.ts +3 -3
- package/types/index.d.ts +6 -1
- package/types/index.d.ts.map +1 -1
- package/types/piptree.d.ts +1 -1
- package/types/piptree.d.ts.map +1 -1
- package/types/server.d.ts.map +1 -1
- package/types/utils.d.ts +32 -2
- package/types/utils.d.ts.map +1 -1
- package/utils.js +356 -85
- package/utils.test.js +69 -3
package/README.md
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
[![GitHub License][badge-github-license]][github-license]
|
|
6
6
|
[![GitHub Contributors][badge-github-contributors]][github-contributors]
|
|
7
7
|
[![SWH][badge-swh]][swh-cdxgen]
|
|
8
|
+
[![Libraries.io dependency status][badge-libraries]][librariesio]
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
# CycloneDX Generator (cdxgen)
|
|
10
12
|
|
|
@@ -510,14 +512,14 @@ Please check out our [contribute to CycloneDX/cdxgen documentation][github-contr
|
|
|
510
512
|
Before raising a PR, please run the following commands.
|
|
511
513
|
|
|
512
514
|
```bash
|
|
513
|
-
corepack enable
|
|
514
|
-
|
|
515
|
+
corepack enable pnpm
|
|
516
|
+
pnpm install
|
|
515
517
|
# Generate types using jsdoc syntax
|
|
516
|
-
|
|
518
|
+
pnpm run gen-types
|
|
517
519
|
# Run biomejs formatter and linter with auto fix
|
|
518
|
-
|
|
520
|
+
pnpm run lint
|
|
519
521
|
# Run jest tests
|
|
520
|
-
|
|
522
|
+
pnpm test
|
|
521
523
|
```
|
|
522
524
|
|
|
523
525
|
<!-- LINK LABELS -->
|
|
@@ -527,6 +529,7 @@ corepack pnpm test
|
|
|
527
529
|
[badge-github-license]: https://img.shields.io/github/license/cyclonedx/cdxgen
|
|
528
530
|
[badge-github-releases]: https://img.shields.io/github/v/release/cyclonedx/cdxgen
|
|
529
531
|
[badge-jsr]: https://img.shields.io/jsr/v/%40cyclonedx/cdxgen
|
|
532
|
+
[badge-libraries]: https://img.shields.io/librariesio/github/cyclonedx/cdxgen
|
|
530
533
|
[badge-npm]: https://img.shields.io/npm/v/%40cyclonedx%2Fcdxgen
|
|
531
534
|
[badge-npm-downloads]: https://img.shields.io/npm/dy/%40cyclonedx%2Fcdxgen
|
|
532
535
|
[badge-swh]: https://archive.softwareheritage.org/badge/origin/https://github.com/CycloneDX/cdxgen/
|
|
@@ -562,6 +565,7 @@ corepack pnpm test
|
|
|
562
565
|
[jsr-cdxgen]: https://jsr.io/@cyclonedx/cdxgen
|
|
563
566
|
[jwt-homepage]: https://jwt.io
|
|
564
567
|
[jwt-libraries]: https://jwt.io/libraries
|
|
568
|
+
[librariesio]: https://libraries.io/npm/@cyclonedx%2Fcdxgen
|
|
565
569
|
[npmjs-cdxgen]: https://www.npmjs.com/package/@cyclonedx/cdxgen
|
|
566
570
|
[podman-github-rootless]: https://github.com/containers/podman/blob/master/docs/tutorials/rootless_tutorial.md
|
|
567
571
|
[podman-github-remote]: https://github.com/containers/podman/blob/master/docs/tutorials/mac_win_client.md
|
package/analyzer.js
CHANGED
|
@@ -31,7 +31,7 @@ const IGNORE_DIRS = process.env.ASTGEN_IGNORE_DIRS
|
|
|
31
31
|
|
|
32
32
|
const IGNORE_FILE_PATTERN = new RegExp(
|
|
33
33
|
process.env.ASTGEN_IGNORE_FILE_PATTERN ||
|
|
34
|
-
"(conf|config|test|spec|mock|\\.d)\\.(js|ts|tsx)$",
|
|
34
|
+
"(conf|config|test|spec|mock|setup-jest|\\.d)\\.(js|ts|tsx)$",
|
|
35
35
|
"i",
|
|
36
36
|
);
|
|
37
37
|
|
package/bin/cdxgen.js
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
printOccurrences,
|
|
17
17
|
printReachables,
|
|
18
18
|
printServices,
|
|
19
|
+
printSponsorBanner,
|
|
19
20
|
printTable,
|
|
20
21
|
} from "../display.js";
|
|
21
22
|
import { createBom, submitBom } from "../index.js";
|
|
@@ -258,6 +259,12 @@ const args = yargs(hideBin(process.argv))
|
|
|
258
259
|
"ssaf-DRAFT-2023-11",
|
|
259
260
|
],
|
|
260
261
|
})
|
|
262
|
+
.option("no-banner", {
|
|
263
|
+
type: "boolean",
|
|
264
|
+
default: false,
|
|
265
|
+
description:
|
|
266
|
+
"Do not show the donation banner. Set this attribute if you are an active sponsor for OWASP CycloneDX.",
|
|
267
|
+
})
|
|
261
268
|
.completion("completion", "Generate bash/zsh completion")
|
|
262
269
|
.array("filter")
|
|
263
270
|
.array("only")
|
|
@@ -336,6 +343,10 @@ if (process.argv[1].includes("cbom")) {
|
|
|
336
343
|
}
|
|
337
344
|
if (options.standard) {
|
|
338
345
|
options.specVersion = 1.6;
|
|
346
|
+
options.includeFormulation = true;
|
|
347
|
+
}
|
|
348
|
+
if (options.deep && options.specVersion >= 1.5) {
|
|
349
|
+
options.includeFormulation = true;
|
|
339
350
|
}
|
|
340
351
|
/**
|
|
341
352
|
* Method to apply advanced options such as profile and lifecycles
|
|
@@ -446,6 +457,8 @@ const checkPermissions = (filePath) => {
|
|
|
446
457
|
* Method to start the bom creation process
|
|
447
458
|
*/
|
|
448
459
|
(async () => {
|
|
460
|
+
// Display the sponsor banner
|
|
461
|
+
printSponsorBanner(options);
|
|
449
462
|
// Start SBOM server
|
|
450
463
|
if (options.server) {
|
|
451
464
|
const serverModule = await import("../server.js");
|
|
@@ -675,8 +688,7 @@ const checkPermissions = (filePath) => {
|
|
|
675
688
|
// biome-ignore lint/suspicious/noDoubleEquals: yargs passes true for empty values
|
|
676
689
|
if (options.serverUrl && options.serverUrl != true && options.apiKey) {
|
|
677
690
|
try {
|
|
678
|
-
|
|
679
|
-
console.log("Response from server", dbody);
|
|
691
|
+
await submitBom(options, bomNSData.bomJson);
|
|
680
692
|
} catch (err) {
|
|
681
693
|
console.log(err);
|
|
682
694
|
}
|
package/bin/repl.js
CHANGED
|
@@ -154,15 +154,31 @@ cdxgenRepl.defineCommand("search", {
|
|
|
154
154
|
if (sbom) {
|
|
155
155
|
if (searchStr) {
|
|
156
156
|
try {
|
|
157
|
+
const originalSearchString = searchStr;
|
|
158
|
+
let dependenciesSearchStr = searchStr;
|
|
157
159
|
if (!searchStr.includes("~>")) {
|
|
160
|
+
dependenciesSearchStr = `dependencies[ref ~> /${searchStr}/i or dependsOn ~> /${searchStr}/i or provides ~> /${searchStr}/i]`;
|
|
158
161
|
searchStr = `components[group ~> /${searchStr}/i or name ~> /${searchStr}/i or description ~> /${searchStr}/i or publisher ~> /${searchStr}/i or purl ~> /${searchStr}/i]`;
|
|
159
162
|
}
|
|
160
163
|
const expression = jsonata(searchStr);
|
|
161
164
|
const components = await expression.evaluate(sbom);
|
|
165
|
+
const dexpression = jsonata(dependenciesSearchStr);
|
|
166
|
+
const dependencies = await dexpression.evaluate(sbom);
|
|
162
167
|
if (!components) {
|
|
163
168
|
console.log("No results found!");
|
|
164
169
|
} else {
|
|
165
|
-
printTable(
|
|
170
|
+
printTable(
|
|
171
|
+
{ components, dependencies },
|
|
172
|
+
undefined,
|
|
173
|
+
originalSearchString,
|
|
174
|
+
);
|
|
175
|
+
if (dependencies?.length) {
|
|
176
|
+
printDependencyTree(
|
|
177
|
+
{ components, dependencies },
|
|
178
|
+
"dependsOn",
|
|
179
|
+
originalSearchString,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
166
182
|
}
|
|
167
183
|
} catch (e) {
|
|
168
184
|
console.log(e);
|
package/data/lic-mapping.json
CHANGED
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"BSD License",
|
|
39
39
|
"BSD-like",
|
|
40
40
|
"new BSD License",
|
|
41
|
-
"new BSD"
|
|
41
|
+
"new BSD",
|
|
42
|
+
"BSD, Public Domain"
|
|
42
43
|
]
|
|
43
44
|
},
|
|
44
45
|
{
|
|
@@ -197,7 +198,9 @@
|
|
|
197
198
|
"GNU Lesser General Public License (LGPL), version 3",
|
|
198
199
|
"GNU Lesser General Public License (LGPL), version 3.0",
|
|
199
200
|
"GNU Lesser General Public License v3.0",
|
|
200
|
-
"GNU Lesser General Public License (LGPL), Version 3"
|
|
201
|
+
"GNU Lesser General Public License (LGPL), Version 3",
|
|
202
|
+
"GNU Lesser General Public License v3 (LGPLv3)",
|
|
203
|
+
"LGPL v3"
|
|
201
204
|
]
|
|
202
205
|
},
|
|
203
206
|
{
|
package/display.js
CHANGED
|
@@ -11,8 +11,17 @@ const SYMBOLS_ANSI = {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
const MAX_TREE_DEPTH = 6;
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const highlightStr = (s, highlight) => {
|
|
15
|
+
if (highlight && s && s.includes(highlight)) {
|
|
16
|
+
s = s.replaceAll(highlight, `\x1b[1;33m${highlight}\x1b[0m`);
|
|
17
|
+
}
|
|
18
|
+
return s;
|
|
19
|
+
};
|
|
20
|
+
export const printTable = (
|
|
21
|
+
bomJson,
|
|
22
|
+
filterTypes = undefined,
|
|
23
|
+
highlight = undefined,
|
|
24
|
+
) => {
|
|
16
25
|
if (!bomJson || !bomJson.components) {
|
|
17
26
|
return;
|
|
18
27
|
}
|
|
@@ -56,8 +65,8 @@ export const printTable = (bomJson, filterTypes = undefined) => {
|
|
|
56
65
|
]);
|
|
57
66
|
} else {
|
|
58
67
|
stream.write([
|
|
59
|
-
comp.group || "",
|
|
60
|
-
comp.name,
|
|
68
|
+
highlightStr(comp.group || "", highlight),
|
|
69
|
+
highlightStr(comp.name, highlight),
|
|
61
70
|
`\x1b[1;35m${comp.version || ""}\x1b[0m`,
|
|
62
71
|
comp.scope || "",
|
|
63
72
|
]);
|
|
@@ -67,9 +76,9 @@ export const printTable = (bomJson, filterTypes = undefined) => {
|
|
|
67
76
|
if (!filterTypes) {
|
|
68
77
|
console.log(
|
|
69
78
|
"BOM includes",
|
|
70
|
-
bomJson
|
|
79
|
+
bomJson?.components?.length || 0,
|
|
71
80
|
"components and",
|
|
72
|
-
bomJson
|
|
81
|
+
bomJson?.dependencies?.length || 0,
|
|
73
82
|
"dependencies",
|
|
74
83
|
);
|
|
75
84
|
} else {
|
|
@@ -215,7 +224,11 @@ export const printCallStack = (bomJson) => {
|
|
|
215
224
|
console.log(table(data, config));
|
|
216
225
|
}
|
|
217
226
|
};
|
|
218
|
-
export const printDependencyTree = (
|
|
227
|
+
export const printDependencyTree = (
|
|
228
|
+
bomJson,
|
|
229
|
+
mode = "dependsOn",
|
|
230
|
+
highlight = undefined,
|
|
231
|
+
) => {
|
|
219
232
|
const dependencies = bomJson.dependencies || [];
|
|
220
233
|
if (!dependencies.length) {
|
|
221
234
|
return;
|
|
@@ -244,9 +257,11 @@ export const printDependencyTree = (bomJson, mode = "dependsOn") => {
|
|
|
244
257
|
content: `${treeType} Tree\nGenerated with \u2665 by cdxgen`,
|
|
245
258
|
},
|
|
246
259
|
};
|
|
247
|
-
console.log(
|
|
260
|
+
console.log(
|
|
261
|
+
table([[highlightStr(treeGraphics.join("\n"), highlight)]], config),
|
|
262
|
+
);
|
|
248
263
|
} else {
|
|
249
|
-
console.log(treeGraphics.join("\n"));
|
|
264
|
+
console.log(highlightStr(treeGraphics.join("\n"), highlight));
|
|
250
265
|
}
|
|
251
266
|
};
|
|
252
267
|
|
|
@@ -368,3 +383,25 @@ export function printVulnerabilities(vulnerabilities) {
|
|
|
368
383
|
}
|
|
369
384
|
console.log(`${vulnerabilities.length} vulnerabilities found.`);
|
|
370
385
|
}
|
|
386
|
+
|
|
387
|
+
export function printSponsorBanner(options) {
|
|
388
|
+
if (
|
|
389
|
+
process?.env?.CI &&
|
|
390
|
+
!options.noBanner &&
|
|
391
|
+
!process.env?.GITHUB_REPOSITORY?.toLowerCase().startsWith("cyclonedx")
|
|
392
|
+
) {
|
|
393
|
+
const config = {
|
|
394
|
+
header: {
|
|
395
|
+
alignment: "center",
|
|
396
|
+
content: "\u00A4 Donate to the OWASP Foundation",
|
|
397
|
+
},
|
|
398
|
+
};
|
|
399
|
+
let message =
|
|
400
|
+
"OWASP foundation relies on donations to fund our projects.\nDonation link: https://owasp.org/donate/?reponame=www-project-cyclonedx&title=OWASP+CycloneDX";
|
|
401
|
+
if (options.serverUrl && options.apiKey) {
|
|
402
|
+
message = `${message}\nDependency Track: https://owasp.org/donate/?reponame=www-project-dependency-track&title=OWASP+Dependency-Track`;
|
|
403
|
+
}
|
|
404
|
+
const data = [[message]];
|
|
405
|
+
console.log(table(data, config));
|
|
406
|
+
}
|
|
407
|
+
}
|
package/index.js
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
LEIN_CMD,
|
|
37
37
|
MAX_BUFFER,
|
|
38
38
|
PREFER_MAVEN_DEPS_TREE,
|
|
39
|
+
PYTHON_EXCLUDED_COMPONENTS,
|
|
39
40
|
SWIFT_CMD,
|
|
40
41
|
TIMEOUT_MS,
|
|
41
42
|
addEvidenceForDotnet,
|
|
@@ -381,9 +382,10 @@ const addLifecyclesSection = (options) => {
|
|
|
381
382
|
* Method to generate the formulation section based on git metadata
|
|
382
383
|
*
|
|
383
384
|
* @param {Object} options
|
|
385
|
+
* @param {Object} context Context
|
|
384
386
|
* @returns {Array} formulation array
|
|
385
387
|
*/
|
|
386
|
-
const addFormulationSection = (options) => {
|
|
388
|
+
const addFormulationSection = (options, context) => {
|
|
387
389
|
const formulation = [];
|
|
388
390
|
const provides = [];
|
|
389
391
|
const gitBranch = getBranch();
|
|
@@ -393,6 +395,12 @@ const addFormulationSection = (options) => {
|
|
|
393
395
|
let parentOmniborId;
|
|
394
396
|
let treeOmniborId;
|
|
395
397
|
let components = [];
|
|
398
|
+
const aformulation = {};
|
|
399
|
+
// Reuse any existing formulation components
|
|
400
|
+
// See: PR #1172
|
|
401
|
+
if (context?.formulationList?.length) {
|
|
402
|
+
components = components.concat(trimComponents(context.formulationList));
|
|
403
|
+
}
|
|
396
404
|
if (options.specVersion >= 1.6 && Object.keys(treeHashes).length === 2) {
|
|
397
405
|
parentOmniborId = `gitoid:blob:sha1:${treeHashes.parent}`;
|
|
398
406
|
treeOmniborId = `gitoid:blob:sha1:${treeHashes.tree}`;
|
|
@@ -417,8 +425,8 @@ const addFormulationSection = (options) => {
|
|
|
417
425
|
provides: [treeOmniborId],
|
|
418
426
|
});
|
|
419
427
|
}
|
|
428
|
+
// Collect git related components
|
|
420
429
|
if (gitBranch && originUrl && gitFiles) {
|
|
421
|
-
const aformulation = {};
|
|
422
430
|
const gitFileComponents = gitFiles.map((f) =>
|
|
423
431
|
options.specVersion >= 1.6
|
|
424
432
|
? {
|
|
@@ -442,57 +450,65 @@ const addFormulationSection = (options) => {
|
|
|
442
450
|
provides: gitFiles.map((f) => f.ref),
|
|
443
451
|
});
|
|
444
452
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}
|
|
457
|
-
aformulation["bom-ref"] = uuidv4();
|
|
458
|
-
aformulation.components = components;
|
|
459
|
-
let environmentVars = [{ name: "GIT_BRANCH", value: gitBranch }];
|
|
460
|
-
for (const aevar of Object.keys(process.env)) {
|
|
461
|
-
if (
|
|
462
|
-
(aevar.startsWith("GIT") ||
|
|
463
|
-
aevar.startsWith("CI_") ||
|
|
464
|
-
aevar.startsWith("CARGO") ||
|
|
465
|
-
aevar.startsWith("RUST")) &&
|
|
466
|
-
!aevar.toLowerCase().includes("key") &&
|
|
467
|
-
!aevar.toLowerCase().includes("token") &&
|
|
468
|
-
!aevar.toLowerCase().includes("pass") &&
|
|
469
|
-
process.env[aevar] &&
|
|
470
|
-
process.env[aevar].length
|
|
471
|
-
) {
|
|
472
|
-
environmentVars.push({
|
|
473
|
-
name: aevar,
|
|
474
|
-
value: process.env[aevar],
|
|
475
|
-
});
|
|
476
|
-
}
|
|
453
|
+
}
|
|
454
|
+
// Collect build environment details
|
|
455
|
+
const infoComponents = collectEnvInfo(options.path);
|
|
456
|
+
if (infoComponents?.length) {
|
|
457
|
+
components = components.concat(infoComponents);
|
|
458
|
+
}
|
|
459
|
+
// Should we include the OS crypto libraries
|
|
460
|
+
if (options.includeCrypto) {
|
|
461
|
+
const cryptoLibs = collectOSCryptoLibs(options);
|
|
462
|
+
if (cryptoLibs?.length) {
|
|
463
|
+
components = components.concat(cryptoLibs);
|
|
477
464
|
}
|
|
478
|
-
|
|
479
|
-
|
|
465
|
+
}
|
|
466
|
+
aformulation["bom-ref"] = uuidv4();
|
|
467
|
+
aformulation.components = components;
|
|
468
|
+
let environmentVars = gitBranch?.length
|
|
469
|
+
? [{ name: "GIT_BRANCH", value: gitBranch }]
|
|
470
|
+
: [];
|
|
471
|
+
for (const aevar of Object.keys(process.env)) {
|
|
472
|
+
if (
|
|
473
|
+
(aevar.startsWith("GIT") ||
|
|
474
|
+
aevar.startsWith("CI_") ||
|
|
475
|
+
aevar.startsWith("ANDROID") ||
|
|
476
|
+
aevar.startsWith("DENO") ||
|
|
477
|
+
aevar.startsWith("DOTNET") ||
|
|
478
|
+
aevar.startsWith("JAVA_") ||
|
|
479
|
+
aevar.startsWith("SDKMAN") ||
|
|
480
|
+
aevar.startsWith("CARGO") ||
|
|
481
|
+
aevar.startsWith("CONDA") ||
|
|
482
|
+
aevar.startsWith("RUST")) &&
|
|
483
|
+
!aevar.toLowerCase().includes("key") &&
|
|
484
|
+
!aevar.toLowerCase().includes("token") &&
|
|
485
|
+
!aevar.toLowerCase().includes("pass") &&
|
|
486
|
+
!aevar.toLowerCase().includes("secret") &&
|
|
487
|
+
process.env[aevar] &&
|
|
488
|
+
process.env[aevar].length
|
|
489
|
+
) {
|
|
490
|
+
environmentVars.push({
|
|
491
|
+
name: aevar,
|
|
492
|
+
value: process.env[aevar],
|
|
493
|
+
});
|
|
480
494
|
}
|
|
481
|
-
aformulation.workflows = [
|
|
482
|
-
{
|
|
483
|
-
"bom-ref": uuidv4(),
|
|
484
|
-
uid: uuidv4(),
|
|
485
|
-
inputs: [
|
|
486
|
-
{
|
|
487
|
-
source: { ref: originUrl },
|
|
488
|
-
environmentVars,
|
|
489
|
-
},
|
|
490
|
-
],
|
|
491
|
-
taskTypes: ["build", "clone"],
|
|
492
|
-
},
|
|
493
|
-
];
|
|
494
|
-
formulation.push(aformulation);
|
|
495
495
|
}
|
|
496
|
+
if (!environmentVars.length) {
|
|
497
|
+
environmentVars = undefined;
|
|
498
|
+
}
|
|
499
|
+
const sourceInput = environmentVars ? { environmentVars } : {};
|
|
500
|
+
if (originUrl) {
|
|
501
|
+
sourceInput.source = { ref: originUrl };
|
|
502
|
+
}
|
|
503
|
+
aformulation.workflows = [
|
|
504
|
+
{
|
|
505
|
+
"bom-ref": uuidv4(),
|
|
506
|
+
uid: uuidv4(),
|
|
507
|
+
inputs: [sourceInput],
|
|
508
|
+
taskTypes: originUrl ? ["build", "clone"] : ["build"],
|
|
509
|
+
},
|
|
510
|
+
];
|
|
511
|
+
formulation.push(aformulation);
|
|
496
512
|
return { formulation, provides };
|
|
497
513
|
};
|
|
498
514
|
|
|
@@ -782,7 +798,11 @@ function addComponent(
|
|
|
782
798
|
if (!name) {
|
|
783
799
|
return;
|
|
784
800
|
}
|
|
785
|
-
|
|
801
|
+
// Do we need this still?
|
|
802
|
+
if (
|
|
803
|
+
!ptype &&
|
|
804
|
+
["jar", "war", "ear", "pom"].includes(pkg?.qualifiers?.type)
|
|
805
|
+
) {
|
|
786
806
|
ptype = "maven";
|
|
787
807
|
}
|
|
788
808
|
const version = pkg.version || "";
|
|
@@ -814,7 +834,7 @@ function addComponent(
|
|
|
814
834
|
impPkgs.includes(`@${group}`)
|
|
815
835
|
) {
|
|
816
836
|
compScope = "required";
|
|
817
|
-
} else if (impPkgs.length) {
|
|
837
|
+
} else if (impPkgs.length && compScope !== "excluded") {
|
|
818
838
|
compScope = "optional";
|
|
819
839
|
}
|
|
820
840
|
}
|
|
@@ -1057,7 +1077,7 @@ const buildBomNSData = (options, pkgInfo, ptype, context) => {
|
|
|
1057
1077
|
};
|
|
1058
1078
|
const formulationData =
|
|
1059
1079
|
options.includeFormulation && options.specVersion >= 1.5
|
|
1060
|
-
? addFormulationSection(options)
|
|
1080
|
+
? addFormulationSection(options, context)
|
|
1061
1081
|
: undefined;
|
|
1062
1082
|
if (formulationData) {
|
|
1063
1083
|
jsonTpl.formulation = formulationData.formulation;
|
|
@@ -2643,6 +2663,7 @@ export async function createPythonBom(path, options) {
|
|
|
2643
2663
|
let metadataFilename = "";
|
|
2644
2664
|
let dependencies = [];
|
|
2645
2665
|
let pkgList = [];
|
|
2666
|
+
let formulationList = [];
|
|
2646
2667
|
const tempDir = mkdtempSync(join(tmpdir(), "cdxgen-venv-"));
|
|
2647
2668
|
let parentComponent = createDefaultParentComponent(path, "pypi", options);
|
|
2648
2669
|
const pipenvMode = existsSync(join(path, "Pipfile"));
|
|
@@ -2737,6 +2758,9 @@ export async function createPythonBom(path, options) {
|
|
|
2737
2758
|
if (retMap.pkgList?.length) {
|
|
2738
2759
|
pkgList = pkgList.concat(retMap.pkgList);
|
|
2739
2760
|
}
|
|
2761
|
+
if (retMap.formulationList?.length) {
|
|
2762
|
+
formulationList = formulationList.concat(retMap.formulationList);
|
|
2763
|
+
}
|
|
2740
2764
|
if (retMap.dependenciesList) {
|
|
2741
2765
|
dependencies = mergeDependencies(
|
|
2742
2766
|
dependencies,
|
|
@@ -2761,6 +2785,7 @@ export async function createPythonBom(path, options) {
|
|
|
2761
2785
|
filename: poetryFiles.join(", "),
|
|
2762
2786
|
dependencies,
|
|
2763
2787
|
parentComponent,
|
|
2788
|
+
formulationList,
|
|
2764
2789
|
});
|
|
2765
2790
|
}
|
|
2766
2791
|
if (metadataFiles?.length) {
|
|
@@ -2831,6 +2856,9 @@ export async function createPythonBom(path, options) {
|
|
|
2831
2856
|
pkgList = pkgList.concat(pkgMap.pkgList);
|
|
2832
2857
|
frozen = pkgMap.frozen;
|
|
2833
2858
|
}
|
|
2859
|
+
if (pkgMap.formulationList?.length) {
|
|
2860
|
+
formulationList = formulationList.concat(pkgMap.formulationList);
|
|
2861
|
+
}
|
|
2834
2862
|
if (pkgMap.dependenciesList) {
|
|
2835
2863
|
dependencies = mergeDependencies(
|
|
2836
2864
|
dependencies,
|
|
@@ -2939,6 +2967,9 @@ export async function createPythonBom(path, options) {
|
|
|
2939
2967
|
if (pkgMap.pkgList?.length) {
|
|
2940
2968
|
pkgList = pkgList.concat(pkgMap.pkgList);
|
|
2941
2969
|
}
|
|
2970
|
+
if (pkgMap.formulationList?.length) {
|
|
2971
|
+
formulationList = formulationList.concat(pkgMap.formulationList);
|
|
2972
|
+
}
|
|
2942
2973
|
if (pkgMap.dependenciesList) {
|
|
2943
2974
|
dependencies = mergeDependencies(
|
|
2944
2975
|
dependencies,
|
|
@@ -2986,6 +3017,7 @@ export async function createPythonBom(path, options) {
|
|
|
2986
3017
|
filename: metadataFilename,
|
|
2987
3018
|
dependencies,
|
|
2988
3019
|
parentComponent,
|
|
3020
|
+
formulationList,
|
|
2989
3021
|
});
|
|
2990
3022
|
}
|
|
2991
3023
|
|
|
@@ -6305,6 +6337,7 @@ export async function createBom(path, options) {
|
|
|
6305
6337
|
*
|
|
6306
6338
|
* @param {Object} args CLI args
|
|
6307
6339
|
* @param {Object} bomContents BOM Json
|
|
6340
|
+
* @return {Promise<{ token: string } | { errors: string[] } | undefined>} a promise with a token (if request was successful), a body with errors (if request failed) or undefined (in case of invalid arguments)
|
|
6308
6341
|
*/
|
|
6309
6342
|
export async function submitBom(args, bomContents) {
|
|
6310
6343
|
const serverUrl = `${args.serverUrl.replace(/\/$/, "")}/api/v1/bom`;
|
|
@@ -6388,11 +6421,11 @@ export async function submitBom(args, bomContents) {
|
|
|
6388
6421
|
console.log(
|
|
6389
6422
|
"Unable to submit the SBOM to the Dependency-Track server using POST method",
|
|
6390
6423
|
);
|
|
6391
|
-
console.log(error);
|
|
6392
6424
|
}
|
|
6393
6425
|
} else {
|
|
6394
6426
|
console.log("Unable to submit the SBOM to the Dependency-Track server");
|
|
6395
|
-
console.log(error);
|
|
6396
6427
|
}
|
|
6428
|
+
console.log(error.response?.body);
|
|
6429
|
+
return error.response?.body;
|
|
6397
6430
|
}
|
|
6398
6431
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyclonedx/cdxgen",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.7.0",
|
|
4
4
|
"description": "Creates CycloneDX Software Bill of Materials (SBOM) from source or container image",
|
|
5
5
|
"homepage": "http://github.com/cyclonedx/cdxgen",
|
|
6
6
|
"author": "Prabhu Subramanian <prabhu@appthreat.com>",
|
|
@@ -58,17 +58,17 @@
|
|
|
58
58
|
"url": "https://github.com/cyclonedx/cdxgen/issues"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@babel/parser": "^7.24.
|
|
62
|
-
"@babel/traverse": "^7.24.
|
|
63
|
-
"@npmcli/arborist": "7.5.
|
|
64
|
-
"ajv": "^8.
|
|
61
|
+
"@babel/parser": "^7.24.7",
|
|
62
|
+
"@babel/traverse": "^7.24.7",
|
|
63
|
+
"@npmcli/arborist": "7.5.3",
|
|
64
|
+
"ajv": "^8.16.0",
|
|
65
65
|
"ajv-formats": "^3.0.1",
|
|
66
66
|
"cheerio": "^1.0.0-rc.12",
|
|
67
|
-
"edn-data": "1.1.
|
|
67
|
+
"edn-data": "1.1.2",
|
|
68
68
|
"find-up": "7.0.0",
|
|
69
69
|
"glob": "^10.4.1",
|
|
70
70
|
"global-agent": "^3.0.0",
|
|
71
|
-
"got": "14.
|
|
71
|
+
"got": "14.4.1",
|
|
72
72
|
"iconv-lite": "^0.6.3",
|
|
73
73
|
"js-yaml": "^4.1.0",
|
|
74
74
|
"jws": "^4.0.0",
|
|
@@ -80,13 +80,13 @@
|
|
|
80
80
|
"ssri": "^10.0.6",
|
|
81
81
|
"table": "^6.8.2",
|
|
82
82
|
"tar": "^6.2.1",
|
|
83
|
-
"uuid": "^
|
|
83
|
+
"uuid": "^10.0.0",
|
|
84
84
|
"xml-js": "^1.6.11",
|
|
85
85
|
"yargs": "^17.7.2",
|
|
86
86
|
"validate-iri": "^1.0.1"
|
|
87
87
|
},
|
|
88
88
|
"optionalDependencies": {
|
|
89
|
-
"@appthreat/atom": "2.0.
|
|
89
|
+
"@appthreat/atom": "2.0.13",
|
|
90
90
|
"@appthreat/cdx-proto": "1.0.1",
|
|
91
91
|
"@cyclonedx/cdxgen-plugins-bin": "1.6.0",
|
|
92
92
|
"@cyclonedx/cdxgen-plugins-bin-arm64": "1.6.0",
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"types/"
|
|
110
110
|
],
|
|
111
111
|
"devDependencies": {
|
|
112
|
-
"@biomejs/biome": "1.8.
|
|
112
|
+
"@biomejs/biome": "1.8.1",
|
|
113
113
|
"jest": "^29.7.0",
|
|
114
114
|
"typescript": "^5.4.5"
|
|
115
115
|
},
|
package/piptree.js
CHANGED
|
@@ -50,15 +50,16 @@ def get_installed_distributions():
|
|
|
50
50
|
return [d._dist for d in dists]
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
def find_deps(idx,
|
|
53
|
+
def find_deps(idx, path, reqs, traverse_count):
|
|
54
54
|
freqs = []
|
|
55
55
|
for r in reqs:
|
|
56
56
|
d = idx.get(r.key)
|
|
57
57
|
if not d:
|
|
58
58
|
continue
|
|
59
59
|
r.project_name = d.project_name if d is not None else r.project_name
|
|
60
|
-
if
|
|
61
|
-
|
|
60
|
+
if r.key in path:
|
|
61
|
+
continue
|
|
62
|
+
current_path = path + [r.key]
|
|
62
63
|
specs = sorted(r.specs, reverse=True)
|
|
63
64
|
specs_str = ",".join(["".join(sp) for sp in specs]) if specs else ""
|
|
64
65
|
dreqs = d.requires()
|
|
@@ -67,10 +68,9 @@ def find_deps(idx, visited, reqs, traverse_count):
|
|
|
67
68
|
"name": r.project_name,
|
|
68
69
|
"version": importlib_metadata.version(r.key),
|
|
69
70
|
"versionSpecifiers": specs_str,
|
|
70
|
-
"dependencies": find_deps(idx,
|
|
71
|
+
"dependencies": find_deps(idx, current_path, dreqs, traverse_count + 1) if dreqs and traverse_count < 200 else [],
|
|
71
72
|
}
|
|
72
73
|
)
|
|
73
|
-
visited[r.project_name] = visited.get(r.project_name, 0) + 1
|
|
74
74
|
return freqs
|
|
75
75
|
|
|
76
76
|
|
|
@@ -79,7 +79,6 @@ def main(argv):
|
|
|
79
79
|
tree = []
|
|
80
80
|
pkgs = get_installed_distributions()
|
|
81
81
|
idx = {p.key: p for p in pkgs}
|
|
82
|
-
visited = {}
|
|
83
82
|
traverse_count = 0
|
|
84
83
|
for p in pkgs:
|
|
85
84
|
fr = frozen_req_from_dist(p)
|
|
@@ -98,7 +97,7 @@ def main(argv):
|
|
|
98
97
|
{
|
|
99
98
|
"name": name.split(" ")[0],
|
|
100
99
|
"version": version,
|
|
101
|
-
"dependencies": find_deps(idx,
|
|
100
|
+
"dependencies": find_deps(idx, [p.key], p.requires(), traverse_count + 1),
|
|
102
101
|
}
|
|
103
102
|
)
|
|
104
103
|
all_deps = {}
|
|
@@ -117,7 +116,15 @@ if __name__ == "__main__":
|
|
|
117
116
|
`;
|
|
118
117
|
|
|
119
118
|
/**
|
|
120
|
-
* Execute the piptree plugin and return the generated tree as json object
|
|
119
|
+
* Execute the piptree plugin and return the generated tree as json object.
|
|
120
|
+
* The resulting tree would also include dependencies belonging to pip.
|
|
121
|
+
* Usage analysis is performed at a later stage to mark many of these packages as optional.
|
|
122
|
+
*
|
|
123
|
+
* @param {Object} env Environment variables to use
|
|
124
|
+
* @param {String} python_cmd Python command to use
|
|
125
|
+
* @param {String} basePath Current working directory
|
|
126
|
+
*
|
|
127
|
+
* @returns {Object} Dependency tree
|
|
121
128
|
*/
|
|
122
129
|
export const getTreeWithPlugin = (env, python_cmd, basePath) => {
|
|
123
130
|
let tree = [];
|
package/server.js
CHANGED
|
@@ -131,10 +131,11 @@ const start = (options) => {
|
|
|
131
131
|
if (!filePath) {
|
|
132
132
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
133
133
|
return res.end(
|
|
134
|
-
|
|
134
|
+
JSON.stringify({
|
|
135
|
+
error: "path or url is required.",
|
|
136
|
+
}),
|
|
135
137
|
);
|
|
136
138
|
}
|
|
137
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
138
139
|
let srcDir = filePath;
|
|
139
140
|
if (filePath.startsWith("http") || filePath.startsWith("git")) {
|
|
140
141
|
srcDir = gitClone(filePath, reqOptions.gitBranch);
|
|
@@ -145,6 +146,21 @@ const start = (options) => {
|
|
|
145
146
|
if (reqOptions.requiredOnly || reqOptions["filter"] || reqOptions["only"]) {
|
|
146
147
|
bomNSData = postProcess(bomNSData, reqOptions);
|
|
147
148
|
}
|
|
149
|
+
if (reqOptions.serverUrl && reqOptions.apiKey) {
|
|
150
|
+
console.log("Publishing SBOM to Dependency Track");
|
|
151
|
+
const response = await submitBom(reqOptions, bomNSData.bomJson);
|
|
152
|
+
const errorMessages = response?.errors;
|
|
153
|
+
if (errorMessages) {
|
|
154
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
155
|
+
return res.end(
|
|
156
|
+
JSON.stringify({
|
|
157
|
+
error: "Unable to submit the SBOM to the Dependency-Track server",
|
|
158
|
+
details: errorMessages,
|
|
159
|
+
}),
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
148
164
|
if (bomNSData.bomJson) {
|
|
149
165
|
if (
|
|
150
166
|
typeof bomNSData.bomJson === "string" ||
|
|
@@ -155,10 +171,6 @@ const start = (options) => {
|
|
|
155
171
|
res.write(JSON.stringify(bomNSData.bomJson, null, null));
|
|
156
172
|
}
|
|
157
173
|
}
|
|
158
|
-
if (reqOptions.serverUrl && reqOptions.apiKey) {
|
|
159
|
-
console.log("Publishing SBOM to Dependency Track");
|
|
160
|
-
submitBom(reqOptions, bomNSData.bomJson);
|
|
161
|
-
}
|
|
162
174
|
res.end("\n");
|
|
163
175
|
if (cleanup && srcDir && srcDir.startsWith(os.tmpdir()) && fs.rmSync) {
|
|
164
176
|
console.log(`Cleaning up ${srcDir}`);
|