@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 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
- corepack pnpm install
515
+ corepack enable pnpm
516
+ pnpm install
515
517
  # Generate types using jsdoc syntax
516
- corepack pnpm run gen-types
518
+ pnpm run gen-types
517
519
  # Run biomejs formatter and linter with auto fix
518
- corepack pnpm run lint
520
+ pnpm run lint
519
521
  # Run jest tests
520
- corepack pnpm test
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
- const dbody = await submitBom(options, bomNSData.bomJson);
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({ components, dependencies: [] });
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);
@@ -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
- export const printTable = (bomJson, filterTypes = undefined) => {
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.components.length,
79
+ bomJson?.components?.length || 0,
71
80
  "components and",
72
- bomJson.dependencies.length,
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 = (bomJson, mode = "dependsOn") => {
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(table([[treeGraphics.join("\n")]], config));
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
- // Collect build environment details
446
- const infoComponents = collectEnvInfo(options.path);
447
- if (infoComponents?.length) {
448
- components = components.concat(infoComponents);
449
- }
450
- // Should we include the OS crypto libraries
451
- if (options.includeCrypto) {
452
- const cryptoLibs = collectOSCryptoLibs(options);
453
- if (cryptoLibs?.length) {
454
- components = components.concat(cryptoLibs);
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
- if (!environmentVars.length) {
479
- environmentVars = undefined;
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
- if (!ptype && pkg.qualifiers && pkg.qualifiers.type === "jar") {
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.6.1",
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.6",
62
- "@babel/traverse": "^7.24.6",
63
- "@npmcli/arborist": "7.5.2",
64
- "ajv": "^8.14.0",
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.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.3.0",
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": "^9.0.1",
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.12",
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.0",
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, visited, reqs, traverse_count):
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 len(visited) > 100 or visited.get(r.project_name, 0) > 5:
61
- return freqs
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, visited, dreqs, traverse_count + 1) if dreqs and traverse_count < 200 else [],
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, visited, p.requires(), traverse_count + 1),
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
- "{'error': 'true', 'message': 'path or url is required.'}\n",
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}`);