@cyclonedx/cdxgen 9.8.4 → 9.8.6

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
@@ -2,9 +2,9 @@
2
2
 
3
3
  ![cdxgen logo](cdxgen.png)
4
4
 
5
- cdxgen is a cli tool, library, [REPL](./ADVANCED.md), and server to create a valid and compliant [CycloneDX][cyclonedx-homepage] Software Bill-of-Materials (SBOM) containing an aggregate of all project dependencies for c/c++, node.js, php, python, ruby, rust, java, .Net, dart, haskell, elixir, and Go projects in JSON format. CycloneDX 1.5 is a lightweight SBOM specification that is easily created, human and machine-readable, and simple to parse.
5
+ cdxgen is a cli tool, library, [REPL](./ADVANCED.md), and server to create a valid and compliant [CycloneDX][cyclonedx-homepage] Software Bill of Materials (SBOM) containing an aggregate of all project dependencies for c/c++, node.js, php, python, ruby, rust, java, .Net, dart, haskell, elixir, and Go projects in JSON format. CycloneDX 1.5 is a lightweight SBOM specification that is easily created, human and machine-readable, and simple to parse.
6
6
 
7
- When used with plugins, cdxgen could generate an OBoM for Linux docker images and even VMs running Linux or Windows operating systems. cdxgen also includes an evinse tool to generate component evidence and SaaSBoM for some languages.
7
+ When used with plugins, cdxgen could generate an OBoM for Linux docker images and even VMs running Linux or Windows operating systems. cdxgen also includes an evinse tool to generate component evidence and SaaSBOM for some languages.
8
8
 
9
9
  NOTE:
10
10
 
@@ -12,7 +12,7 @@ CycloneDX 1.5 specification is new and unsupported by many downstream tools. Use
12
12
 
13
13
  ## Why cdxgen?
14
14
 
15
- A typical application might have several repos, components, and libraries. Traditional techniques to generate a single SBoM per language or package manifest do not work in enterprise environments. So we built cdxgen - the universal polyglot SBoM generator!
15
+ A typical application might have several repos, components, and libraries. Traditional techniques to generate a single SBOM per language or package manifest do not work in enterprise environments. So we built cdxgen - the universal polyglot SBOM generator!
16
16
 
17
17
  <img src="./docs/why-cdxgen.jpg" alt="why cdxgen" width="256">
18
18
 
@@ -71,11 +71,11 @@ Footnotes:
71
71
 
72
72
  ### Automatic usage detection
73
73
 
74
- For node.js projects, lock files are parsed initially, so the SBoM would include all dependencies, including dev ones. An AST parser powered by babel-parser is then used to detect packages that are imported and used by non-test code. Such imported packages would automatically set their scope property to `required` in the resulting SBoM. You can turn off this analysis by passing the argument `--no-babel`. Scope property would then be set based on the `dev` attribute in the lock file.
74
+ For node.js projects, lock files are parsed initially, so the SBOM would include all dependencies, including dev ones. An AST parser powered by babel-parser is then used to detect packages that are imported and used by non-test code. Such imported packages would automatically set their scope property to `required` in the resulting SBOM. You can turn off this analysis by passing the argument `--no-babel`. Scope property would then be set based on the `dev` attribute in the lock file.
75
75
 
76
76
  This attribute can be later used for various purposes. For example, [dep-scan](https://github.com/cyclonedx/dep-scan) uses this attribute to prioritize vulnerabilities. Unfortunately, tools such as dependency track, do not include this feature and might over-report the CVEs.
77
77
 
78
- By passing the argument `--required-only`, you can limit the SBoM only to include packages with the scope "required", commonly called production or non-dev dependencies. Combine with `--no-babel` to limit this list to only non-dev dependencies based on the `dev` attribute being false in the lock files.
78
+ By passing the argument `--required-only`, you can limit the SBOM only to include packages with the scope "required", commonly called production or non-dev dependencies. Combine with `--no-babel` to limit this list to only non-dev dependencies based on the `dev` attribute being false in the lock files.
79
79
 
80
80
  For go, `go mod why` command is used to identify required packages. For php, composer lock file is parsed to distinguish required (packages) from optional (packages-dev).
81
81
 
@@ -132,7 +132,7 @@ $ cdxgen -h
132
132
  -r, --recurse Recurse mode suitable for mono-repos. Defaults to
133
133
  true. Pass --no-recurse to disable.
134
134
  [boolean] [default: true]
135
- -p, --print Print the SBoM as a table with tree. [boolean]
135
+ -p, --print Print the SBOM as a table with tree. [boolean]
136
136
  -c, --resolve-class Resolve class names for packages. jars only for n
137
137
  ow. [boolean]
138
138
  --deep Perform deep searches for components. Useful whil
@@ -149,12 +149,12 @@ $ cdxgen -h
149
149
  d or the project name and version together
150
150
  --parent-project-id Dependency track parent project id
151
151
  --required-only Include only the packages with required scope on
152
- the SBoM. [boolean]
152
+ the SBOM. [boolean]
153
153
  --fail-on-error Fail if any dependency extractor fails. [boolean]
154
154
  --no-babel Do not use babel to perform usage analysis for Ja
155
155
  vaScript/TypeScript projects. [boolean]
156
156
  --generate-key-and-sign Generate an RSA public/private key pair and then
157
- sign the generated SBoM using JSON Web Signatures
157
+ sign the generated SBOM using JSON Web Signatures
158
158
  . [boolean]
159
159
  --server Run cdxgen as a server [boolean]
160
160
  --server-host Listen address [default: "127.0.0.1"]
@@ -163,7 +163,7 @@ $ cdxgen -h
163
163
  cts. Defaults to true but disabled for containers
164
164
  and oci scans. Use --no-install-deps to disable
165
165
  this feature. [boolean] [default: true]
166
- --validate Validate the generated SBoM using json schema. De
166
+ --validate Validate the generated SBOM using json schema. De
167
167
  faults to true. Pass --no-validate to disable.
168
168
  [boolean] [default: true]
169
169
  --usages-slices-file Path for the usages slice file created by atom.
@@ -191,7 +191,7 @@ For a java project. cdxgen would automatically detect maven, gradle, or sbt and
191
191
  cdxgen -t java -o bom.json
192
192
  ```
193
193
 
194
- To print the SBoM as a table pass `-p` argument.
194
+ To print the SBOM as a table pass `-p` argument.
195
195
 
196
196
  ```shell
197
197
  cdxgen -t java -o bom.json -p
@@ -203,13 +203,13 @@ To recursively generate a single BoM for all languages pass `-r` argument.
203
203
  cdxgen -r -o bom.json
204
204
  ```
205
205
 
206
- To generate SBoM for an older specification version, such as 1.4, pass the version number using the `--spec-version` argument.
206
+ To generate SBOM for an older specification version, such as 1.4, pass the version number using the `--spec-version` argument.
207
207
 
208
208
  ```shell
209
209
  cdxgen -r -o bom.json --spec-version 1.4
210
210
  ```
211
211
 
212
- To generate SBoM for C or Python, ensure Java >= 17 is installed.
212
+ To generate SBOM for C or Python, ensure Java >= 17 is installed.
213
213
 
214
214
  ```shell
215
215
  # Install java >= 17
@@ -218,11 +218,11 @@ cdxgen -t c -o bom.json
218
218
 
219
219
  NOTE: cdxgen is known to freeze with Java 8 or 11, so ensure >= 17 is installed and JAVA_HOME environment variable is configured correctly. If in doubt, use the cdxgen container image.
220
220
 
221
- ## Universal SBoM
221
+ ## Universal SBOM
222
222
 
223
- By passing the type argument `-t universal`, cdxgen could be forced to opportunistically collect as many components and services as possible by scanning all package, container, and Kubernetes manifests. The resulting SBoM could have over a thousand components, thus requiring additional triaging before use with traditional SCA tools.
223
+ By passing the type argument `-t universal`, cdxgen could be forced to opportunistically collect as many components and services as possible by scanning all package, container, and Kubernetes manifests. The resulting SBOM could have over a thousand components, thus requiring additional triaging before use with traditional SCA tools.
224
224
 
225
- ## SBoM server
225
+ ## SBOM server
226
226
 
227
227
  Invoke cdxgen with `--server` argument to run it in server mode. By default, it listens to port `9090`, which can be customized with the arguments `--server-host` and `--server-port`.
228
228
 
@@ -246,7 +246,7 @@ Arguments can be passed either via the query string or as a JSON body. The follo
246
246
  | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
247
247
  | type | Project type |
248
248
  | multiProject | [boolean] |
249
- | requiredOnly | Include only the packages with required scope on the SBoM. [boolean] |
249
+ | requiredOnly | Include only the packages with required scope on the SBOM. [boolean] |
250
250
  | noBabel | Do not use babel to perform usage analysis for JavaScript/TypeScript projects. [boolean] |
251
251
  | installDeps | Install dependencies automatically for some projects. Defaults to true but disabled for containers and oci scans. [boolean] [default: true] |
252
252
  | project | |
@@ -349,7 +349,7 @@ cdxgen can retain the dependency tree under the `dependencies` attribute for a s
349
349
  | LEIN_CMD | Set to override the leiningen command |
350
350
  | SBOM_SIGN_ALGORITHM | Signature algorithm. Some valid values are RS256, RS384, RS512, PS256, PS384, PS512, ES256 etc |
351
351
  | SBOM_SIGN_PRIVATE_KEY | Private key to use for signing |
352
- | SBOM_SIGN_PUBLIC_KEY | Optional. Public key to include in the SBoM signature |
352
+ | SBOM_SIGN_PUBLIC_KEY | Optional. Public key to include in the SBOM signature |
353
353
  | CDX_MAVEN_PLUGIN | CycloneDX Maven plugin to use. Default "org.cyclonedx:cyclonedx-maven-plugin:2.7.8" |
354
354
  | CDX_MAVEN_GOAL | CycloneDX Maven plugin goal to use. Default makeAggregateBom. Other options: makeBom, makePackageBom |
355
355
  | CDX_MAVEN_INCLUDE_TEST_SCOPE | Whether test scoped dependencies should be included from Maven projects, Default: true |
@@ -358,7 +358,7 @@ cdxgen can retain the dependency tree under the `dependencies` attribute for a s
358
358
 
359
359
  ## Plugins
360
360
 
361
- cdxgen could be extended with external binary plugins to support more SBoM use cases. These are now installed as an optional dependency.
361
+ cdxgen could be extended with external binary plugins to support more SBOM use cases. These are now installed as an optional dependency.
362
362
 
363
363
  ```shell
364
364
  sudo npm install -g @cyclonedx/cdxgen-plugins-bin
@@ -409,9 +409,9 @@ obom
409
409
  # cdxgen -t os
410
410
  ```
411
411
 
412
- This feature is powered by osquery, which is [installed](https://github.com/cyclonedx/cdxgen-plugins-bin/blob/main/build.sh#L8) along with the binary plugins. cdxgen would opportunistically try to detect as many components, apps, and extensions as possible using the [default queries](queries.json). The process would take several minutes and result in an SBoM file with thousands of components of various types, such as operating-system, device-drivers, files, and data.
412
+ This feature is powered by osquery, which is [installed](https://github.com/cyclonedx/cdxgen-plugins-bin/blob/main/build.sh#L8) along with the binary plugins. cdxgen would opportunistically try to detect as many components, apps, and extensions as possible using the [default queries](queries.json). The process would take several minutes and result in an SBOM file with thousands of components of various types, such as operating-system, device-drivers, files, and data.
413
413
 
414
- ## Generating SaaSBoM and component evidences
414
+ ## Generating SaaSBOM and component evidences
415
415
 
416
416
  See [evinse mode](./ADVANCED.md) in the advanced documentation.
417
417
 
@@ -425,7 +425,7 @@ cdxgen can sign the generated BoM json file to increase authenticity and non-rep
425
425
 
426
426
  To generate test public/private key pairs, you can run cdxgen by passing the argument `--generate-key-and-sign`. The generated json file would have an attribute called `signature`, which could be used for validation. [jwt.io](https://jwt.io) is a known site that could be used for such signature validation.
427
427
 
428
- ![SBoM signing](sbom-sign.jpg)
428
+ ![SBOM signing](sbom-sign.jpg)
429
429
 
430
430
  ### Verifying the signature
431
431
 
@@ -444,7 +444,7 @@ There are many [libraries](https://jwt.io/#libraries-io) available to validate J
444
444
  # npm install jws
445
445
  const jws = require("jws");
446
446
  const fs = require("fs");
447
- // Location of the SBoM json file
447
+ // Location of the SBOM json file
448
448
  const bomJsonFile = "bom.json";
449
449
  // Location of the public key
450
450
  const publicKeyFile = "public.key";
@@ -455,13 +455,13 @@ const validationResult = jws.verify(bomSignature, bomJson.signature.algorithm, f
455
455
  if (validationResult) {
456
456
  console.log("Signature is valid!");
457
457
  } else {
458
- console.log("SBoM signature is invalid :(");
458
+ console.log("SBOM signature is invalid :(");
459
459
  }
460
460
  ```
461
461
 
462
462
  ## Automatic services detection
463
463
 
464
- cdxgen can automatically detect names of services from YAML manifests such as docker-compose, Kubernetes, or Skaffold manifests. These would be populated under the `services` attribute in the generated SBoM. With [evinse](./ADVANCED.md), additional services could be detected by parsing common annotations from the source code.
464
+ cdxgen can automatically detect names of services from YAML manifests such as docker-compose, Kubernetes, or Skaffold manifests. These would be populated under the `services` attribute in the generated SBOM. With [evinse](./ADVANCED.md), additional services could be detected by parsing common annotations from the source code.
465
465
 
466
466
  ## Conversion to SPDX format
467
467
 
package/bin/cdxgen.js CHANGED
@@ -41,7 +41,7 @@ const args = yargs(hideBin(process.argv))
41
41
  .option("print", {
42
42
  alias: "p",
43
43
  type: "boolean",
44
- description: "Print the SBoM as a table with tree."
44
+ description: "Print the SBOM as a table with tree."
45
45
  })
46
46
  .option("resolve-class", {
47
47
  alias: "c",
@@ -78,7 +78,7 @@ const args = yargs(hideBin(process.argv))
78
78
  })
79
79
  .option("required-only", {
80
80
  type: "boolean",
81
- description: "Include only the packages with required scope on the SBoM."
81
+ description: "Include only the packages with required scope on the SBOM."
82
82
  })
83
83
  .option("fail-on-error", {
84
84
  type: "boolean",
@@ -92,7 +92,7 @@ const args = yargs(hideBin(process.argv))
92
92
  .option("generate-key-and-sign", {
93
93
  type: "boolean",
94
94
  description:
95
- "Generate an RSA public/private key pair and then sign the generated SBoM using JSON Web Signatures."
95
+ "Generate an RSA public/private key pair and then sign the generated SBOM using JSON Web Signatures."
96
96
  })
97
97
  .option("server", {
98
98
  type: "boolean",
@@ -116,12 +116,12 @@ const args = yargs(hideBin(process.argv))
116
116
  type: "boolean",
117
117
  default: true,
118
118
  description:
119
- "Validate the generated SBoM using json schema. Defaults to true. Pass --no-validate to disable."
119
+ "Validate the generated SBOM using json schema. Defaults to true. Pass --no-validate to disable."
120
120
  })
121
121
  .option("evidence", {
122
122
  type: "boolean",
123
123
  default: false,
124
- description: "Generate SBoM with evidence for supported languages. WIP"
124
+ description: "Generate SBOM with evidence for supported languages. WIP"
125
125
  })
126
126
  .option("usages-slices-file", {
127
127
  description: "Path for the usages slice file created by atom."
@@ -241,7 +241,7 @@ const checkPermissions = (filePath) => {
241
241
  * Method to start the bom creation process
242
242
  */
243
243
  (async () => {
244
- // Start SBoM server
244
+ // Start SBOM server
245
245
  if (args.server) {
246
246
  return await _serverStart(options);
247
247
  }
@@ -384,12 +384,12 @@ const checkPermissions = (filePath) => {
384
384
  );
385
385
  if (signatureVerification) {
386
386
  console.log(
387
- "SBoM signature is verifiable with the public key and the algorithm",
387
+ "SBOM signature is verifiable with the public key and the algorithm",
388
388
  publicKeyFile,
389
389
  alg
390
390
  );
391
391
  } else {
392
- console.log("SBoM signature verification was unsuccessful");
392
+ console.log("SBOM signature verification was unsuccessful");
393
393
  console.log(
394
394
  "Check if the public key was exported in PEM format"
395
395
  );
@@ -397,7 +397,7 @@ const checkPermissions = (filePath) => {
397
397
  }
398
398
  }
399
399
  } catch (ex) {
400
- console.log("SBoM signing was unsuccessful", ex);
400
+ console.log("SBOM signing was unsuccessful", ex);
401
401
  console.log("Check if the private key was exported in PEM format");
402
402
  }
403
403
  }
package/bin/evinse.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // Evinse (Evinse Verification Is Nearly SBoM Evidence)
3
+ // Evinse (Evinse Verification Is Nearly SBOM Evidence)
4
4
  import yargs from "yargs";
5
5
  import { hideBin } from "yargs/helpers";
6
6
  import { join } from "node:path";
@@ -30,7 +30,7 @@ if (!process.env.ATOM_DB && !fs.existsSync(ATOM_DB)) {
30
30
  const args = yargs(hideBin(process.argv))
31
31
  .option("input", {
32
32
  alias: "i",
33
- description: "Input SBoM file. Default bom.json",
33
+ description: "Input SBOM file. Default bom.json",
34
34
  default: "bom.json"
35
35
  })
36
36
  .option("output", {
@@ -108,9 +108,9 @@ console.log(evinseArt);
108
108
  if (dbObjMap) {
109
109
  // Analyze the project using atom. Convert package namespaces to purl using the db
110
110
  const sliceArtefacts = await analyzeProject(dbObjMap, args);
111
- // Create the SBoM with Evidence
111
+ // Create the SBOM with Evidence
112
112
  const bomJson = createEvinseFile(sliceArtefacts, args);
113
- // Validate our final SBoM
113
+ // Validate our final SBOM
114
114
  if (!validateBom(bomJson)) {
115
115
  process.exit(1);
116
116
  }
package/bin/repl.js CHANGED
@@ -61,10 +61,10 @@ export const importSbom = (sbomOrPath) => {
61
61
  if (sbomOrPath && sbomOrPath.endsWith(".json") && fs.existsSync(sbomOrPath)) {
62
62
  try {
63
63
  sbom = JSON.parse(fs.readFileSync(sbomOrPath, "utf-8"));
64
- console.log(`✅ SBoM imported successfully from ${sbomOrPath}`);
64
+ console.log(`✅ SBOM imported successfully from ${sbomOrPath}`);
65
65
  } catch (e) {
66
66
  console.log(
67
- `⚠ Unable to import the SBoM from ${sbomOrPath} due to ${e}`
67
+ `⚠ Unable to import the SBOM from ${sbomOrPath} due to ${e}`
68
68
  );
69
69
  }
70
70
  } else {
@@ -74,13 +74,13 @@ export const importSbom = (sbomOrPath) => {
74
74
  // Load any sbom passed from the command line
75
75
  if (process.argv.length > 2) {
76
76
  importSbom(process.argv[process.argv.length - 1]);
77
- console.log("💭 Type .print to view the SBoM as a table");
77
+ console.log("💭 Type .print to view the SBOM as a table");
78
78
  } else if (fs.existsSync("bom.json")) {
79
79
  // If the current directory has a bom.json load it
80
80
  importSbom("bom.json");
81
81
  } else {
82
- console.log("💭 Use .create <path> to create an SBoM for the given path.");
83
- console.log("💭 Use .import <json> to import an existing SBoM.");
82
+ console.log("💭 Use .create <path> to create an SBOM for the given path.");
83
+ console.log("💭 Use .import <json> to import an existing SBOM.");
84
84
  console.log("💭 Type .exit or press ctrl+d to close.");
85
85
  }
86
86
 
@@ -98,7 +98,7 @@ if (historyFile) {
98
98
  );
99
99
  }
100
100
  cdxgenRepl.defineCommand("create", {
101
- help: "create an SBoM for the given path",
101
+ help: "create an SBOM for the given path",
102
102
  async action(sbomOrPath) {
103
103
  this.clearBufferedCommand();
104
104
  const tempDir = fs.mkdtempSync(join(tmpdir(), "cdxgen-repl-"));
@@ -267,7 +267,7 @@ cdxgenRepl.defineCommand("validate", {
267
267
  if (sbom) {
268
268
  const result = validateBom(sbom);
269
269
  if (result) {
270
- console.log("SBoM is valid!");
270
+ console.log("SBOM is valid!");
271
271
  }
272
272
  } else {
273
273
  console.log(
@@ -379,7 +379,7 @@ cdxgenRepl.defineCommand("callstack", {
379
379
  let components = await expression.evaluate(sbom);
380
380
  if (!components) {
381
381
  console.log(
382
- "callstack evidence was not found. Use evinse command to generate an SBoM with evidence."
382
+ "callstack evidence was not found. Use evinse command to generate an SBOM with evidence."
383
383
  );
384
384
  } else {
385
385
  if (!Array.isArray(components)) {
@@ -392,7 +392,7 @@ cdxgenRepl.defineCommand("callstack", {
392
392
  }
393
393
  } else {
394
394
  console.log(
395
- "⚠ No SBoM is loaded. Use .import command to import an evinse SBoM"
395
+ "⚠ No SBOM is loaded. Use .import command to import an evinse SBOM"
396
396
  );
397
397
  }
398
398
  this.displayPrompt();
@@ -407,7 +407,7 @@ cdxgenRepl.defineCommand("services", {
407
407
  let services = await expression.evaluate(sbom);
408
408
  if (!services) {
409
409
  console.log(
410
- "No services found. Use evinse command to generate an SBoM with evidence."
410
+ "No services found. Use evinse command to generate an SBOM with evidence."
411
411
  );
412
412
  } else {
413
413
  if (!Array.isArray(services)) {
@@ -420,7 +420,7 @@ cdxgenRepl.defineCommand("services", {
420
420
  }
421
421
  } else {
422
422
  console.log(
423
- "⚠ No SBoM is loaded. Use .import command to import an evinse SBoM"
423
+ "⚠ No SBOM is loaded. Use .import command to import an evinse SBOM"
424
424
  );
425
425
  }
426
426
  this.displayPrompt();
package/bin/verify.js CHANGED
@@ -74,7 +74,7 @@ if (!bomSignature) {
74
74
  if (validationResult) {
75
75
  console.log("Signature is valid!");
76
76
  } else {
77
- console.log("SBoM signature is invalid!");
77
+ console.log("SBOM signature is invalid!");
78
78
  process.exit(1);
79
79
  }
80
80
  }
package/evinser.js CHANGED
@@ -31,7 +31,7 @@ export const prepareDB = async (options) => {
31
31
  const bomJson = JSON.parse(fs.readFileSync(bomJsonFile, "utf8"));
32
32
  if (bomJson.specVersion < 1.5) {
33
33
  console.log(
34
- "Evinse requires the input SBoM in CycloneDX 1.5 format or above. You can generate one by invoking cdxgen without any --spec-version argument."
34
+ "Evinse requires the input SBOM in CycloneDX 1.5 format or above. You can generate one by invoking cdxgen without any --spec-version argument."
35
35
  );
36
36
  process.exit(0);
37
37
  }
@@ -741,7 +741,7 @@ export const isSlicingRequired = (purl) => {
741
741
  };
742
742
 
743
743
  /**
744
- * Method to create the SBoM with evidence file called evinse file.
744
+ * Method to create the SBOM with evidence file called evinse file.
745
745
  *
746
746
  * @param {object} sliceArtefacts Various artefacts from the slice operation
747
747
  * @param {object} options Command line options
@@ -841,7 +841,7 @@ export const createEvinseFile = (sliceArtefacts, options) => {
841
841
  console.log(evinseOutFile, "created successfully.");
842
842
  } else {
843
843
  console.log(
844
- "Unable to identify component evidence for the input SBoM. Only java, javascript and python projects are supported by evinse."
844
+ "Unable to identify component evidence for the input SBOM. Only java, javascript and python projects are supported by evinse."
845
845
  );
846
846
  }
847
847
  if (tempDir && tempDir.startsWith(tmpdir())) {
package/index.js CHANGED
@@ -686,7 +686,6 @@ function addComponent(
686
686
  encodeForPurl(pkg.subpath)
687
687
  );
688
688
  let purlString = purl.toString();
689
- purlString = decodeURIComponent(purlString);
690
689
  let description = { "#cdata": pkg.description };
691
690
  if (format === "json") {
692
691
  description = pkg.description || undefined;
@@ -1128,17 +1127,11 @@ export const createJavaBom = async (path, options) => {
1128
1127
  console.log(`Retrieving packages from ${path}`);
1129
1128
  }
1130
1129
  const tempDir = mkdtempSync(join(tmpdir(), "war-deps-"));
1131
- pkgList = extractJarArchive(path, tempDir);
1130
+ jarNSMapping = collectJarNS(tempDir);
1131
+ pkgList = extractJarArchive(path, tempDir, jarNSMapping);
1132
1132
  if (pkgList.length) {
1133
1133
  pkgList = await getMvnMetadata(pkgList);
1134
1134
  }
1135
- // Should we attempt to resolve class names
1136
- if (options.resolveClass) {
1137
- console.log(
1138
- "Creating class names list based on available jars. This might take a few mins ..."
1139
- );
1140
- jarNSMapping = collectJarNS(tempDir);
1141
- }
1142
1135
  // Clean up
1143
1136
  if (tempDir && tempDir.startsWith(tmpdir()) && rmSync) {
1144
1137
  console.log(`Cleaning up ${tempDir}`);
@@ -1193,7 +1186,7 @@ export const createJavaBom = async (path, options) => {
1193
1186
  }
1194
1187
  const mavenCmd = getMavenCommand(basePath, path);
1195
1188
  // Should we attempt to resolve class names
1196
- if (options.resolveClass) {
1189
+ if (options.resolveClass || options.deep) {
1197
1190
  console.log(
1198
1191
  "Creating class names list based on available jars. This might take a few mins ..."
1199
1192
  );
@@ -1352,7 +1345,7 @@ export const createJavaBom = async (path, options) => {
1352
1345
  }
1353
1346
  if (pkgList) {
1354
1347
  pkgList = trimComponents(pkgList, "json");
1355
- pkgList = await getMvnMetadata(pkgList);
1348
+ pkgList = await getMvnMetadata(pkgList, jarNSMapping);
1356
1349
  return buildBomNSData(options, pkgList, "maven", {
1357
1350
  src: path,
1358
1351
  filename: pomFiles.join(", "),
@@ -1552,15 +1545,14 @@ export const createJavaBom = async (path, options) => {
1552
1545
  );
1553
1546
  options.failOnError && process.exit(1);
1554
1547
  }
1555
-
1556
- pkgList = await getMvnMetadata(pkgList);
1557
1548
  // Should we attempt to resolve class names
1558
- if (options.resolveClass) {
1549
+ if (options.resolveClass || options.deep) {
1559
1550
  console.log(
1560
1551
  "Creating class names list based on available jars. This might take a few mins ..."
1561
1552
  );
1562
1553
  jarNSMapping = collectJarNS(GRADLE_CACHE_DIR);
1563
1554
  }
1555
+ pkgList = await getMvnMetadata(pkgList, jarNSMapping);
1564
1556
  return buildBomNSData(options, pkgList, "maven", {
1565
1557
  src: path,
1566
1558
  filename: gradleFiles.join(", "),
@@ -1655,7 +1647,8 @@ export const createJavaBom = async (path, options) => {
1655
1647
  console.log("Bazel unexpectedly didn't produce any output");
1656
1648
  options.failOnError && process.exit(1);
1657
1649
  }
1658
- pkgList = await getMvnMetadata(pkgList);
1650
+ // FIXME: How do we retrieve jarNSMapping for bazel projects?
1651
+ pkgList = await getMvnMetadata(pkgList, jarNSMapping);
1659
1652
  return buildBomNSData(options, pkgList, "maven", {
1660
1653
  src: path,
1661
1654
  filename: "BUILD",
@@ -1836,14 +1829,14 @@ export const createJavaBom = async (path, options) => {
1836
1829
  if (DEBUG_MODE) {
1837
1830
  console.log(`Found ${pkgList.length} packages`);
1838
1831
  }
1839
- pkgList = await getMvnMetadata(pkgList);
1840
1832
  // Should we attempt to resolve class names
1841
- if (options.resolveClass) {
1833
+ if (options.resolveClass || options.deep) {
1842
1834
  console.log(
1843
1835
  "Creating class names list based on available jars. This might take a few mins ..."
1844
1836
  );
1845
1837
  jarNSMapping = collectJarNS(SBT_CACHE_DIR);
1846
1838
  }
1839
+ pkgList = await getMvnMetadata(pkgList, jarNSMapping);
1847
1840
  return buildBomNSData(options, pkgList, "maven", {
1848
1841
  src: path,
1849
1842
  filename: sbtProjects.join(", "),
@@ -3584,7 +3577,7 @@ export const createContainerSpecLikeBom = async (path, options) => {
3584
3577
  // img could have .service, .ociSpec or .image
3585
3578
  if (img.ociSpec) {
3586
3579
  console.log(
3587
- `NOTE: ${img.ociSpec} needs to built using docker or podman and referred with a name to get included in this SBoM.`
3580
+ `NOTE: ${img.ociSpec} needs to built using docker or podman and referred with a name to get included in this SBOM.`
3588
3581
  );
3589
3582
  ociSpecs.push({
3590
3583
  group: "",
@@ -3706,7 +3699,7 @@ export const createContainerSpecLikeBom = async (path, options) => {
3706
3699
  // Parse privado files
3707
3700
  if (privadoFiles.length) {
3708
3701
  console.log(
3709
- "Enriching your SBoM with information from privado.ai scan reports"
3702
+ "Enriching your SBOM with information from privado.ai scan reports"
3710
3703
  );
3711
3704
  let rows = [["Classification", "Flow"]];
3712
3705
  const config = {
@@ -5294,12 +5287,12 @@ export async function submitBom(args, bomContents) {
5294
5287
  }).json();
5295
5288
  } catch (error) {
5296
5289
  console.log(
5297
- "Unable to submit the SBoM to the Dependency-Track server using POST method"
5290
+ "Unable to submit the SBOM to the Dependency-Track server using POST method"
5298
5291
  );
5299
5292
  console.log(error);
5300
5293
  }
5301
5294
  } else {
5302
- console.log("Unable to submit the SBoM to the Dependency-Track server");
5295
+ console.log("Unable to submit the SBOM to the Dependency-Track server");
5303
5296
  console.log(error);
5304
5297
  }
5305
5298
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cyclonedx/cdxgen",
3
- "version": "9.8.4",
4
- "description": "Creates CycloneDX Software Bill-of-Materials (SBOM) from source or container image",
3
+ "version": "9.8.6",
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>",
7
7
  "license": "Apache-2.0",
@@ -42,7 +42,7 @@
42
42
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --inject-globals false docker.test.js utils.test.js display.test.js",
43
43
  "watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch --inject-globals false",
44
44
  "lint": "eslint *.js *.test.js bin/*.js",
45
- "pretty": "prettier --write *.js data/*.json bin/*.js --trailing-comma=none"
45
+ "pretty": "prettier --write *.js data/*.json bin/*.js"
46
46
  },
47
47
  "engines": {
48
48
  "node": ">=16"
@@ -55,21 +55,21 @@
55
55
  "url": "https://github.com/cyclonedx/cdxgen/issues"
56
56
  },
57
57
  "dependencies": {
58
- "@babel/parser": "^7.22.16",
59
- "@babel/traverse": "^7.22.19",
58
+ "@babel/parser": "^7.23.0",
59
+ "@babel/traverse": "^7.23.0",
60
60
  "@npmcli/arborist": "^7.1.0",
61
61
  "ajv": "^8.12.0",
62
62
  "ajv-formats": "^2.1.1",
63
63
  "cheerio": "^1.0.0-rc.12",
64
64
  "edn-data": "^1.0.0",
65
- "glob": "^10.3.4",
65
+ "glob": "^10.3.10",
66
66
  "global-agent": "^3.0.0",
67
67
  "got": "^13.0.0",
68
68
  "iconv-lite": "^0.6.3",
69
69
  "js-yaml": "^4.1.0",
70
70
  "jws": "^4.0.0",
71
71
  "node-stream-zip": "^1.15.0",
72
- "packageurl-js": "^1.0.2",
72
+ "packageurl-js": "1.0.2",
73
73
  "prettify-xml": "^1.2.0",
74
74
  "properties-reader": "^2.3.0",
75
75
  "semver": "^7.5.3",
@@ -82,7 +82,7 @@
82
82
  "yargs": "^17.7.2"
83
83
  },
84
84
  "optionalDependencies": {
85
- "@appthreat/atom": "^1.2.1",
85
+ "@appthreat/atom": "^1.2.3",
86
86
  "@cyclonedx/cdxgen-plugins-bin": "^1.4.0",
87
87
  "@cyclonedx/cdxgen-plugins-bin-arm64": "^1.4.0",
88
88
  "@cyclonedx/cdxgen-plugins-bin-ppc64": "^1.4.0",
@@ -101,7 +101,9 @@
101
101
  "devDependencies": {
102
102
  "caxa": "^3.0.1",
103
103
  "docsify-cli": "^4.4.4",
104
- "eslint": "^8.49.0",
104
+ "eslint": "^8.50.0",
105
+ "eslint-config-prettier": "^9.0.0",
106
+ "eslint-plugin-prettier": "^5.0.0",
105
107
  "jest": "^29.7.0",
106
108
  "prettier": "3.0.3"
107
109
  }
package/server.js CHANGED
@@ -55,7 +55,8 @@ const parseQueryString = (q, body, options = {}) => {
55
55
  "projectVersion",
56
56
  "parentUUID",
57
57
  "serverUrl",
58
- "apiKey"
58
+ "apiKey",
59
+ "specVersion"
59
60
  ];
60
61
 
61
62
  for (const param of queryParams) {
@@ -88,6 +89,12 @@ const start = (options) => {
88
89
  .createServer(app)
89
90
  .listen(options.serverPort, options.serverHost);
90
91
  configureServer(cdxgenServer);
92
+
93
+ app.use("/health", async function (req, res) {
94
+ res.setHeader("Content-Type", "application/json");
95
+ res.end(JSON.stringify({ status: "OK" }, null, 2));
96
+ });
97
+
91
98
  app.use("/sbom", async function (req, res) {
92
99
  const q = url.parse(req.url, true).query;
93
100
  let cleanup = false;
@@ -105,7 +112,7 @@ const start = (options) => {
105
112
  srcDir = gitClone(filePath);
106
113
  cleanup = true;
107
114
  }
108
- console.log("Generating SBoM for", srcDir);
115
+ console.log("Generating SBOM for", srcDir);
109
116
  const bomNSData = (await createBom(srcDir, options)) || {};
110
117
  if (bomNSData.bomJson) {
111
118
  if (
@@ -118,7 +125,7 @@ const start = (options) => {
118
125
  }
119
126
  }
120
127
  if (options.serverUrl && options.apiKey) {
121
- console.log("Publishing SBoM to Dependency Track");
128
+ console.log("Publishing SBOM to Dependency Track");
122
129
  submitBom(options, bomNSData.bomJson);
123
130
  }
124
131
  res.end("\n");
package/utils.js CHANGED
@@ -1886,7 +1886,7 @@ export const executeGradleProperties = function (dir, rootPath, subProject) {
1886
1886
  }
1887
1887
  if (result.stderr.includes("not get unknown property")) {
1888
1888
  console.log(
1889
- "2. Check if the SBoM is generated for the correct root project for your application."
1889
+ "2. Check if the SBOM is generated for the correct root project for your application."
1890
1890
  );
1891
1891
  }
1892
1892
  }
@@ -2104,8 +2104,9 @@ export const guessLicenseId = function (content) {
2104
2104
  * Method to retrieve metadata for maven packages by querying maven central
2105
2105
  *
2106
2106
  * @param {Array} pkgList Package list
2107
+ * @param {Object} jarNSMapping Jar Namespace mapping object
2107
2108
  */
2108
- export const getMvnMetadata = async function (pkgList) {
2109
+ export const getMvnMetadata = async function (pkgList, jarNSMapping = {}) {
2109
2110
  const MAVEN_CENTRAL_URL =
2110
2111
  process.env.MAVEN_CENTRAL_URL || "https://repo1.maven.org/maven2/";
2111
2112
  const ANDROID_MAVEN = "https://maven.google.com/";
@@ -2117,6 +2118,36 @@ export const getMvnMetadata = async function (pkgList) {
2117
2118
  console.log(`About to query maven for ${pkgList.length} packages`);
2118
2119
  }
2119
2120
  for (const p of pkgList) {
2121
+ // Reuse any namespace data from jarNSMapping
2122
+ if (jarNSMapping && p.purl && jarNSMapping[p.purl]) {
2123
+ if (jarNSMapping[p.purl].jarFile) {
2124
+ p.evidence = {
2125
+ identity: {
2126
+ field: "purl",
2127
+ confidence: 0.8,
2128
+ methods: [
2129
+ {
2130
+ technique: "binary-analysis",
2131
+ confidence: 0.8,
2132
+ value: jarNSMapping[p.purl].jarFile
2133
+ }
2134
+ ]
2135
+ }
2136
+ };
2137
+ }
2138
+ if (
2139
+ jarNSMapping[p.purl].namespaces &&
2140
+ jarNSMapping[p.purl].namespaces.length
2141
+ ) {
2142
+ if (!p.properties) {
2143
+ p.properties = [];
2144
+ }
2145
+ p.properties.push({
2146
+ name: "Namespaces",
2147
+ value: jarNSMapping[p.purl].namespaces.join("\n")
2148
+ });
2149
+ }
2150
+ }
2120
2151
  let group = p.group || "";
2121
2152
  // If the package already has key metadata skip querying maven
2122
2153
  if (group && p.name && p.version && !FETCH_LICENSE) {
@@ -2577,89 +2608,66 @@ export const parsePoetrylockData = async function (lockData, lockFile) {
2577
2608
  export async function parseReqFile(reqData, fetchDepsInfo) {
2578
2609
  const pkgList = [];
2579
2610
  let compScope = undefined;
2580
- reqData.split("\n").forEach((l) => {
2581
- l = l.replace("\r", "");
2582
- l = l.trim();
2583
- if (l.startsWith("Skipping line") || l.startsWith("(add")) {
2584
- return;
2585
- }
2586
- if (l.includes("# Basic requirements")) {
2587
- compScope = "required";
2588
- } else if (l.includes("added by pip freeze")) {
2589
- compScope = undefined;
2590
- }
2591
- if (!l.startsWith("#") && !l.startsWith("-")) {
2592
- if (l.includes(" ")) {
2593
- l = l.split(" ")[0];
2594
- }
2595
- if (l.indexOf("=") > -1) {
2596
- let tmpA = l.split(/(==|<=|~=|>=)/);
2597
- if (tmpA.includes("#")) {
2598
- tmpA = tmpA.split("#")[0];
2599
- }
2600
- let versionStr = tmpA[tmpA.length - 1].trim().replace("*", "0");
2601
- if (versionStr.indexOf(" ") > -1) {
2602
- versionStr = versionStr.split(" ")[0];
2611
+ reqData
2612
+ .replace(/\r/g, "")
2613
+ .replace(/ [\\]\n/g, "")
2614
+ .replace(/ {4}/g, " ")
2615
+ .split("\n")
2616
+ .forEach((l) => {
2617
+ l = l.trim();
2618
+ let markers = undefined;
2619
+ if (l.includes(" ; ")) {
2620
+ const tmpA = l.split(" ; ");
2621
+ if (tmpA && tmpA.length === 2) {
2622
+ l = tmpA[0];
2623
+ markers = tmpA[1];
2603
2624
  }
2604
- if (versionStr === "0") {
2605
- versionStr = null;
2625
+ }
2626
+ if (l.startsWith("Skipping line") || l.startsWith("(add")) {
2627
+ return;
2628
+ }
2629
+ if (l.includes("# Basic requirements")) {
2630
+ compScope = "required";
2631
+ } else if (l.includes("added by pip freeze")) {
2632
+ compScope = undefined;
2633
+ }
2634
+ if (!l.startsWith("#") && !l.startsWith("-")) {
2635
+ if (l.includes(" ")) {
2636
+ l = l.split(" ")[0];
2606
2637
  }
2607
- if (!tmpA[0].includes("=") && !tmpA[0].trim().includes(" ")) {
2608
- const name = tmpA[0].trim().replace(";", "");
2609
- if (!PYTHON_STD_MODULES.includes(name)) {
2610
- pkgList.push({
2611
- name,
2612
- version: versionStr,
2613
- scope: compScope
2614
- });
2638
+ if (l.indexOf("=") > -1) {
2639
+ let tmpA = l.split(/(==|<=|~=|>=)/);
2640
+ if (tmpA.includes("#")) {
2641
+ tmpA = tmpA.split("#")[0];
2615
2642
  }
2616
- }
2617
- } else if (l.includes("<") && l.includes(">")) {
2618
- const tmpA = l.split(">");
2619
- const name = tmpA[0].trim().replace(";", "");
2620
- const versionSpecifiers = l.replace(name, "");
2621
- if (!PYTHON_STD_MODULES.includes(name)) {
2622
- pkgList.push({
2623
- name,
2624
- version: undefined,
2625
- scope: compScope,
2626
- properties: [
2627
- {
2628
- name: "cdx:pypi:versionSpecifiers",
2629
- value: versionSpecifiers
2643
+ let versionStr = tmpA[tmpA.length - 1].trim().replace("*", "0");
2644
+ if (versionStr.indexOf(" ") > -1) {
2645
+ versionStr = versionStr.split(" ")[0];
2646
+ }
2647
+ if (versionStr === "0") {
2648
+ versionStr = null;
2649
+ }
2650
+ if (!tmpA[0].includes("=") && !tmpA[0].trim().includes(" ")) {
2651
+ const name = tmpA[0].trim().replace(";", "");
2652
+ if (!PYTHON_STD_MODULES.includes(name)) {
2653
+ const apkg = {
2654
+ name,
2655
+ version: versionStr,
2656
+ scope: compScope
2657
+ };
2658
+ if (markers) {
2659
+ apkg.properties = [
2660
+ {
2661
+ name: "cdx:pip:markers",
2662
+ value: markers
2663
+ }
2664
+ ];
2630
2665
  }
2631
- ]
2632
- });
2633
- }
2634
- } else if (/[>|[|@]/.test(l)) {
2635
- let tmpA = l.split(/(>|\[|@)/);
2636
- if (tmpA.includes("#")) {
2637
- tmpA = tmpA.split("#")[0];
2638
- }
2639
- if (!tmpA[0].trim().includes(" ")) {
2640
- const name = tmpA[0].trim().replace(";", "");
2641
- const versionSpecifiers = l.replace(name, "");
2642
- if (!PYTHON_STD_MODULES.includes(name)) {
2643
- pkgList.push({
2644
- name,
2645
- version: undefined,
2646
- scope: compScope,
2647
- properties: [
2648
- {
2649
- name: "cdx:pypi:versionSpecifiers",
2650
- value: versionSpecifiers
2651
- }
2652
- ]
2653
- });
2666
+ pkgList.push(apkg);
2667
+ }
2654
2668
  }
2655
- }
2656
- } else if (l) {
2657
- if (l.includes("#")) {
2658
- l = l.split("#")[0];
2659
- }
2660
- l = l.trim();
2661
- const tmpA = l.split(/(<|>)/);
2662
- if (tmpA && tmpA.length === 3) {
2669
+ } else if (l.includes("<") && l.includes(">")) {
2670
+ const tmpA = l.split(">");
2663
2671
  const name = tmpA[0].trim().replace(";", "");
2664
2672
  const versionSpecifiers = l.replace(name, "");
2665
2673
  if (!PYTHON_STD_MODULES.includes(name)) {
@@ -2675,26 +2683,70 @@ export async function parseReqFile(reqData, fetchDepsInfo) {
2675
2683
  ]
2676
2684
  });
2677
2685
  }
2678
- } else if (!l.includes(" ")) {
2679
- const name = l.replace(";", "");
2680
- const versionSpecifiers = l.replace(name, "");
2681
- if (!PYTHON_STD_MODULES.includes(name)) {
2682
- pkgList.push({
2683
- name,
2684
- version: null,
2685
- scope: compScope,
2686
- properties: [
2687
- {
2688
- name: "cdx:pypi:versionSpecifiers",
2689
- value: versionSpecifiers
2690
- }
2691
- ]
2692
- });
2686
+ } else if (/[>|[|@]/.test(l)) {
2687
+ let tmpA = l.split(/(>|\[|@)/);
2688
+ if (tmpA.includes("#")) {
2689
+ tmpA = tmpA.split("#")[0];
2690
+ }
2691
+ if (!tmpA[0].trim().includes(" ")) {
2692
+ const name = tmpA[0].trim().replace(";", "");
2693
+ const versionSpecifiers = l.replace(name, "");
2694
+ if (!PYTHON_STD_MODULES.includes(name)) {
2695
+ pkgList.push({
2696
+ name,
2697
+ version: undefined,
2698
+ scope: compScope,
2699
+ properties: [
2700
+ {
2701
+ name: "cdx:pypi:versionSpecifiers",
2702
+ value: versionSpecifiers
2703
+ }
2704
+ ]
2705
+ });
2706
+ }
2707
+ }
2708
+ } else if (l) {
2709
+ if (l.includes("#")) {
2710
+ l = l.split("#")[0];
2711
+ }
2712
+ l = l.trim();
2713
+ const tmpA = l.split(/(<|>)/);
2714
+ if (tmpA && tmpA.length === 3) {
2715
+ const name = tmpA[0].trim().replace(";", "");
2716
+ const versionSpecifiers = l.replace(name, "");
2717
+ if (!PYTHON_STD_MODULES.includes(name)) {
2718
+ pkgList.push({
2719
+ name,
2720
+ version: undefined,
2721
+ scope: compScope,
2722
+ properties: [
2723
+ {
2724
+ name: "cdx:pypi:versionSpecifiers",
2725
+ value: versionSpecifiers
2726
+ }
2727
+ ]
2728
+ });
2729
+ }
2730
+ } else if (!l.includes(" ")) {
2731
+ const name = l.replace(";", "");
2732
+ const versionSpecifiers = l.replace(name, "");
2733
+ if (!PYTHON_STD_MODULES.includes(name)) {
2734
+ pkgList.push({
2735
+ name,
2736
+ version: null,
2737
+ scope: compScope,
2738
+ properties: [
2739
+ {
2740
+ name: "cdx:pypi:versionSpecifiers",
2741
+ value: versionSpecifiers
2742
+ }
2743
+ ]
2744
+ });
2745
+ }
2693
2746
  }
2694
2747
  }
2695
2748
  }
2696
- }
2697
- });
2749
+ });
2698
2750
  return await getPyMetadata(pkgList, fetchDepsInfo);
2699
2751
  }
2700
2752
 
@@ -5710,10 +5762,15 @@ export const encodeForPurl = (s) => {
5710
5762
  *
5711
5763
  * @param {string} jarFile Path to jar file
5712
5764
  * @param {string} tempDir Temporary directory to use for extraction
5765
+ * @param {object} jarNSMapping Jar class names mapping object
5713
5766
  *
5714
5767
  * @return pkgList Package list
5715
5768
  */
5716
- export const extractJarArchive = function (jarFile, tempDir) {
5769
+ export const extractJarArchive = function (
5770
+ jarFile,
5771
+ tempDir,
5772
+ jarNSMapping = {}
5773
+ ) {
5717
5774
  const pkgList = [];
5718
5775
  let jarFiles = [];
5719
5776
  const fname = basename(jarFile);
@@ -5873,10 +5930,19 @@ export const extractJarArchive = function (jarFile, tempDir) {
5873
5930
  if (group == name) {
5874
5931
  group = "";
5875
5932
  }
5876
- pkgList.push({
5877
- group: group === "." ? "" : encodeForPurl(group || "") || "",
5933
+ group = group === "." ? "" : encodeForPurl(group || "") || "";
5934
+ let apkg = {
5935
+ group,
5878
5936
  name: name ? encodeForPurl(name) : "",
5879
5937
  version,
5938
+ purl: new PackageURL(
5939
+ "maven",
5940
+ group,
5941
+ name,
5942
+ version,
5943
+ { type: "jar" },
5944
+ null
5945
+ ).toString(),
5880
5946
  evidence: {
5881
5947
  identity: {
5882
5948
  field: "purl",
@@ -5896,7 +5962,18 @@ export const extractJarArchive = function (jarFile, tempDir) {
5896
5962
  value: jarname
5897
5963
  }
5898
5964
  ]
5899
- });
5965
+ };
5966
+ if (
5967
+ jarNSMapping &&
5968
+ jarNSMapping[apkg.purl] &&
5969
+ jarNSMapping[apkg.purl].namespaces
5970
+ ) {
5971
+ apkg.properties.push({
5972
+ name: "Namespaces",
5973
+ value: jarNSMapping[apkg.purl].namespaces.join("\n")
5974
+ });
5975
+ }
5976
+ pkgList.push(apkg);
5900
5977
  } else {
5901
5978
  if (DEBUG_MODE) {
5902
5979
  console.log(`Ignored jar ${jarname}`, jarMetadata, name, version);
@@ -6225,7 +6302,7 @@ export const executeAtom = (src, args) => {
6225
6302
  result.stderr.includes("Error: Could not create the Java Virtual Machine")
6226
6303
  ) {
6227
6304
  console.log(
6228
- "Atom requires Java 17 or above. To improve the SBoM accuracy, please install a suitable version, set the JAVA_HOME environment variable, and re-run cdxgen.\nAlternatively, use the cdxgen container image."
6305
+ "Atom requires Java 17 or above. To improve the SBOM accuracy, please install a suitable version, set the JAVA_HOME environment variable, and re-run cdxgen.\nAlternatively, use the cdxgen container image."
6229
6306
  );
6230
6307
  console.log(`Current JAVA_HOME: ${env["JAVA_HOME"] || ""}`);
6231
6308
  } else if (result.stderr.includes("astgen")) {
@@ -6510,7 +6587,7 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
6510
6587
  ) {
6511
6588
  versionRelatedError = true;
6512
6589
  console.log(
6513
- "The version or the version specifiers used for a dependency is invalid. Resolve the below error to improve SBoM accuracy."
6590
+ "The version or the version specifiers used for a dependency is invalid. Resolve the below error to improve SBOM accuracy."
6514
6591
  );
6515
6592
  console.log(result.stderr);
6516
6593
  }
@@ -6518,7 +6595,7 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
6518
6595
  console.log("args used:", pipInstallArgs);
6519
6596
  console.log(result.stdout, result.stderr);
6520
6597
  console.log(
6521
- "Possible build errors detected. The resulting list in the SBoM would therefore be incomplete.\nTry installing any missing build tools or development libraries to improve the accuracy."
6598
+ "Possible build errors detected. The resulting list in the SBOM would therefore be incomplete.\nTry installing any missing build tools or development libraries to improve the accuracy."
6522
6599
  );
6523
6600
  if (platform() === "win32") {
6524
6601
  console.log(
@@ -6541,7 +6618,7 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
6541
6618
  if (env.VIRTUAL_ENV && env.VIRTUAL_ENV.length) {
6542
6619
  /**
6543
6620
  * At this point, the previous attempt to do a pip install might have failed and we might have an unclean virtual environment with an incomplete list
6544
- * The position taken by cdxgen is "Some SBoM is better than no SBoM", so we proceed to collecting the dependencies that got installed with pip freeze
6621
+ * The position taken by cdxgen is "Some SBOM is better than no SBOM", so we proceed to collecting the dependencies that got installed with pip freeze
6545
6622
  */
6546
6623
  if (DEBUG_MODE) {
6547
6624
  console.log(
@@ -6597,7 +6674,7 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
6597
6674
  } else {
6598
6675
  if (DEBUG_MODE) {
6599
6676
  console.log(
6600
- "NOTE: Setup and activate a python virtual environment for this project prior to invoking cdxgen to improve SBoM accuracy."
6677
+ "NOTE: Setup and activate a python virtual environment for this project prior to invoking cdxgen to improve SBOM accuracy."
6601
6678
  );
6602
6679
  }
6603
6680
  }
package/utils.test.js CHANGED
@@ -1143,7 +1143,7 @@ test("parse github actions workflow data", async () => {
1143
1143
  dep_list = parseGitHubWorkflowData(
1144
1144
  readFileSync("./.github/workflows/repotests.yml", { encoding: "utf-8" })
1145
1145
  );
1146
- expect(dep_list.length).toEqual(6);
1146
+ expect(dep_list.length).toEqual(7);
1147
1147
  expect(dep_list[0]).toEqual({
1148
1148
  group: "actions",
1149
1149
  name: "checkout",
@@ -1270,7 +1270,7 @@ test("get nget metadata", async () => {
1270
1270
  "pkg:nuget/NUnit.Console@3.11.1",
1271
1271
  "pkg:nuget/NUnit3TestAdapter@3.16.1",
1272
1272
  "pkg:nuget/NUnitLite@3.13.3",
1273
- "pkg:nuget/Serilog@0.0.0",
1273
+ "pkg:nuget/Serilog@3.0.1",
1274
1274
  "pkg:nuget/Serilog.Sinks.TextWriter@2.0.0",
1275
1275
  "pkg:nuget/System.Security.Permissions@4.7.0",
1276
1276
  "pkg:nuget/log4net@2.0.13",
@@ -1295,7 +1295,7 @@ test("get nget metadata", async () => {
1295
1295
  "pkg:nuget/System.Text.RegularExpressions@4.1.0",
1296
1296
  "pkg:nuget/System.Threading@4.0.11"
1297
1297
  ],
1298
- ref: "pkg:nuget/Serilog@0.0.0"
1298
+ ref: "pkg:nuget/Serilog@3.0.1"
1299
1299
  }
1300
1300
  ];
1301
1301
  let pkg_list = [
@@ -1308,8 +1308,8 @@ test("get nget metadata", async () => {
1308
1308
  {
1309
1309
  group: "",
1310
1310
  name: "Serilog",
1311
- version: "0.0.0",
1312
- "bom-ref": "pkg:nuget/Serilog@0.0.0"
1311
+ version: "3.0.1",
1312
+ "bom-ref": "pkg:nuget/Serilog@3.0.1"
1313
1313
  }
1314
1314
  ];
1315
1315
  const { pkgList, dependencies } = await getNugetMetadata(pkg_list, dep_list);
@@ -2261,6 +2261,25 @@ test("parse requirements.txt", async () => {
2261
2261
  version: "8.6.2",
2262
2262
  scope: "required"
2263
2263
  });
2264
+ deps = await parseReqFile(
2265
+ readFileSync("./test/data/chen-science-requirements.txt", {
2266
+ encoding: "utf-8"
2267
+ }),
2268
+ false
2269
+ );
2270
+ expect(deps.length).toEqual(87);
2271
+ expect(deps[0]).toEqual({
2272
+ name: "aiofiles",
2273
+ version: "23.2.1",
2274
+ scope: undefined,
2275
+ properties: [
2276
+ {
2277
+ name: "cdx:pip:markers",
2278
+ value:
2279
+ 'python_full_version >= "3.8.1" and python_version < "3.12" --hash=sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107 --hash=sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a'
2280
+ }
2281
+ ]
2282
+ });
2264
2283
  });
2265
2284
 
2266
2285
  test("parse pyproject.toml", async () => {