@cyclonedx/cdxgen 9.8.5 → 9.8.7

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
 
@@ -27,7 +27,7 @@ A typical application might have several repos, components, and libraries. Tradi
27
27
  | go | binary, go.mod, go.sum, Gopkg.lock | Yes except binary |
28
28
  | ruby | Gemfile.lock, gemspec | Only for Gemfile.lock |
29
29
  | rust | binary, Cargo.toml, Cargo.lock | Only for Cargo.lock |
30
- | .Net | .csproj, packages.config, project.assets.json [3], packages.lock.json, .nupkg | Only for project.assets.json, packages.lock.json |
30
+ | .Net | .csproj, packages.config, project.assets.json [3], packages.lock.json, .nupkg, paket.lock | Only for project.assets.json, packages.lock.json, paket.lock |
31
31
  | dart | pubspec.lock, pubspec.yaml | Only for pubspec.lock |
32
32
  | haskell | cabal.project.freeze | Yes |
33
33
  | elixir | mix.lock | Yes |
@@ -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
@@ -92,6 +92,7 @@ import {
92
92
  parseCsPkgLockData,
93
93
  parseCsPkgData,
94
94
  parseCsProjData,
95
+ parsePaketLockData,
95
96
  DEBUG_MODE,
96
97
  parsePyProjectToml,
97
98
  addEvidenceForImports,
@@ -1127,17 +1128,11 @@ export const createJavaBom = async (path, options) => {
1127
1128
  console.log(`Retrieving packages from ${path}`);
1128
1129
  }
1129
1130
  const tempDir = mkdtempSync(join(tmpdir(), "war-deps-"));
1130
- pkgList = extractJarArchive(path, tempDir);
1131
+ jarNSMapping = collectJarNS(tempDir);
1132
+ pkgList = extractJarArchive(path, tempDir, jarNSMapping);
1131
1133
  if (pkgList.length) {
1132
1134
  pkgList = await getMvnMetadata(pkgList);
1133
1135
  }
1134
- // Should we attempt to resolve class names
1135
- if (options.resolveClass) {
1136
- console.log(
1137
- "Creating class names list based on available jars. This might take a few mins ..."
1138
- );
1139
- jarNSMapping = collectJarNS(tempDir);
1140
- }
1141
1136
  // Clean up
1142
1137
  if (tempDir && tempDir.startsWith(tmpdir()) && rmSync) {
1143
1138
  console.log(`Cleaning up ${tempDir}`);
@@ -1192,7 +1187,7 @@ export const createJavaBom = async (path, options) => {
1192
1187
  }
1193
1188
  const mavenCmd = getMavenCommand(basePath, path);
1194
1189
  // Should we attempt to resolve class names
1195
- if (options.resolveClass) {
1190
+ if (options.resolveClass || options.deep) {
1196
1191
  console.log(
1197
1192
  "Creating class names list based on available jars. This might take a few mins ..."
1198
1193
  );
@@ -1351,7 +1346,7 @@ export const createJavaBom = async (path, options) => {
1351
1346
  }
1352
1347
  if (pkgList) {
1353
1348
  pkgList = trimComponents(pkgList, "json");
1354
- pkgList = await getMvnMetadata(pkgList);
1349
+ pkgList = await getMvnMetadata(pkgList, jarNSMapping);
1355
1350
  return buildBomNSData(options, pkgList, "maven", {
1356
1351
  src: path,
1357
1352
  filename: pomFiles.join(", "),
@@ -1551,15 +1546,14 @@ export const createJavaBom = async (path, options) => {
1551
1546
  );
1552
1547
  options.failOnError && process.exit(1);
1553
1548
  }
1554
-
1555
- pkgList = await getMvnMetadata(pkgList);
1556
1549
  // Should we attempt to resolve class names
1557
- if (options.resolveClass) {
1550
+ if (options.resolveClass || options.deep) {
1558
1551
  console.log(
1559
1552
  "Creating class names list based on available jars. This might take a few mins ..."
1560
1553
  );
1561
1554
  jarNSMapping = collectJarNS(GRADLE_CACHE_DIR);
1562
1555
  }
1556
+ pkgList = await getMvnMetadata(pkgList, jarNSMapping);
1563
1557
  return buildBomNSData(options, pkgList, "maven", {
1564
1558
  src: path,
1565
1559
  filename: gradleFiles.join(", "),
@@ -1654,7 +1648,8 @@ export const createJavaBom = async (path, options) => {
1654
1648
  console.log("Bazel unexpectedly didn't produce any output");
1655
1649
  options.failOnError && process.exit(1);
1656
1650
  }
1657
- pkgList = await getMvnMetadata(pkgList);
1651
+ // FIXME: How do we retrieve jarNSMapping for bazel projects?
1652
+ pkgList = await getMvnMetadata(pkgList, jarNSMapping);
1658
1653
  return buildBomNSData(options, pkgList, "maven", {
1659
1654
  src: path,
1660
1655
  filename: "BUILD",
@@ -1835,14 +1830,14 @@ export const createJavaBom = async (path, options) => {
1835
1830
  if (DEBUG_MODE) {
1836
1831
  console.log(`Found ${pkgList.length} packages`);
1837
1832
  }
1838
- pkgList = await getMvnMetadata(pkgList);
1839
1833
  // Should we attempt to resolve class names
1840
- if (options.resolveClass) {
1834
+ if (options.resolveClass || options.deep) {
1841
1835
  console.log(
1842
1836
  "Creating class names list based on available jars. This might take a few mins ..."
1843
1837
  );
1844
1838
  jarNSMapping = collectJarNS(SBT_CACHE_DIR);
1845
1839
  }
1840
+ pkgList = await getMvnMetadata(pkgList, jarNSMapping);
1846
1841
  return buildBomNSData(options, pkgList, "maven", {
1847
1842
  src: path,
1848
1843
  filename: sbtProjects.join(", "),
@@ -3583,7 +3578,7 @@ export const createContainerSpecLikeBom = async (path, options) => {
3583
3578
  // img could have .service, .ociSpec or .image
3584
3579
  if (img.ociSpec) {
3585
3580
  console.log(
3586
- `NOTE: ${img.ociSpec} needs to built using docker or podman and referred with a name to get included in this SBoM.`
3581
+ `NOTE: ${img.ociSpec} needs to built using docker or podman and referred with a name to get included in this SBOM.`
3587
3582
  );
3588
3583
  ociSpecs.push({
3589
3584
  group: "",
@@ -3705,7 +3700,7 @@ export const createContainerSpecLikeBom = async (path, options) => {
3705
3700
  // Parse privado files
3706
3701
  if (privadoFiles.length) {
3707
3702
  console.log(
3708
- "Enriching your SBoM with information from privado.ai scan reports"
3703
+ "Enriching your SBOM with information from privado.ai scan reports"
3709
3704
  );
3710
3705
  let rows = [["Classification", "Flow"]];
3711
3706
  const config = {
@@ -3969,6 +3964,10 @@ export const createCsharpBom = async (
3969
3964
  path,
3970
3965
  (options.multiProject ? "**/" : "") + "packages.lock.json"
3971
3966
  );
3967
+ const paketLockFiles = getAllFiles(
3968
+ path,
3969
+ (options.multiProject ? "**/" : "") + "paket.lock"
3970
+ );
3972
3971
  const nupkgFiles = getAllFiles(
3973
3972
  path,
3974
3973
  (options.multiProject ? "**/" : "") + "*.nupkg"
@@ -4052,6 +4051,20 @@ export const createCsharpBom = async (
4052
4051
  }
4053
4052
  }
4054
4053
  }
4054
+ if (paketLockFiles.length) {
4055
+ manifestFiles = manifestFiles.concat(paketLockFiles);
4056
+ // paket.lock parsing
4057
+ for (const f of paketLockFiles) {
4058
+ if (DEBUG_MODE) {
4059
+ console.log(`Parsing ${f}`);
4060
+ }
4061
+ pkgData = readFileSync(f, { encoding: "utf-8" });
4062
+ const dlist = await parsePaketLockData(pkgData);
4063
+ if (dlist && dlist.length) {
4064
+ pkgList = pkgList.concat(dlist);
4065
+ }
4066
+ }
4067
+ }
4055
4068
  if (!parentComponent) {
4056
4069
  parentComponent = createDefaultParentComponent(path, options.type, options);
4057
4070
  }
@@ -4116,13 +4129,16 @@ export const trimComponents = (components, format) => {
4116
4129
  const filteredComponents = [];
4117
4130
  for (const comp of components) {
4118
4131
  if (format === "xml" && comp.component) {
4119
- const key = comp.component.purl || comp.component["bom-ref"];
4132
+ const key =
4133
+ comp.component.purl ||
4134
+ comp.component["bom-ref"] ||
4135
+ comp.name + comp.version;
4120
4136
  if (!keyCache[key]) {
4121
4137
  keyCache[key] = true;
4122
4138
  filteredComponents.push(comp);
4123
4139
  }
4124
4140
  } else {
4125
- const key = comp.purl || comp["bom-ref"];
4141
+ const key = comp.purl || comp["bom-ref"] || comp.name + comp.version;
4126
4142
  if (!keyCache[key]) {
4127
4143
  keyCache[key] = true;
4128
4144
  filteredComponents.push(comp);
@@ -5293,12 +5309,12 @@ export async function submitBom(args, bomContents) {
5293
5309
  }).json();
5294
5310
  } catch (error) {
5295
5311
  console.log(
5296
- "Unable to submit the SBoM to the Dependency-Track server using POST method"
5312
+ "Unable to submit the SBOM to the Dependency-Track server using POST method"
5297
5313
  );
5298
5314
  console.log(error);
5299
5315
  }
5300
5316
  } else {
5301
- console.log("Unable to submit the SBoM to the Dependency-Track server");
5317
+ console.log("Unable to submit the SBOM to the Dependency-Track server");
5302
5318
  console.log(error);
5303
5319
  }
5304
5320
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cyclonedx/cdxgen",
3
- "version": "9.8.5",
4
- "description": "Creates CycloneDX Software Bill-of-Materials (SBOM) from source or container image",
3
+ "version": "9.8.7",
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.5",
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,8 +101,10 @@
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
  }
108
- }
110
+ }
package/server.js CHANGED
@@ -8,6 +8,7 @@ import fs from "node:fs";
8
8
  import path from "node:path";
9
9
  import { createBom, submitBom } from "./index.js";
10
10
  import compression from "compression";
11
+ import { URL } from "url";
11
12
 
12
13
  // Timeout milliseconds. Default 10 mins
13
14
  const TIMEOUT_MS =
@@ -24,10 +25,14 @@ app.use(
24
25
  app.use(compression());
25
26
 
26
27
  const gitClone = (repoUrl) => {
28
+ const parsedUrl = new URL(repoUrl);
29
+
30
+ const sanitizedRepoUrl = `${parsedUrl.protocol}//${parsedUrl.host}${parsedUrl.pathname}`;
31
+
27
32
  const tempDir = fs.mkdtempSync(
28
- path.join(os.tmpdir(), path.basename(repoUrl))
33
+ path.join(os.tmpdir(), path.basename(parsedUrl.pathname))
29
34
  );
30
- console.log("Cloning", repoUrl, "to", tempDir);
35
+ console.log("Cloning", sanitizedRepoUrl, "to", tempDir);
31
36
  const result = spawnSync("git", ["clone", repoUrl, "--depth", "1", tempDir], {
32
37
  encoding: "utf-8",
33
38
  shell: false
@@ -89,6 +94,12 @@ const start = (options) => {
89
94
  .createServer(app)
90
95
  .listen(options.serverPort, options.serverHost);
91
96
  configureServer(cdxgenServer);
97
+
98
+ app.use("/health", async function (req, res) {
99
+ res.setHeader("Content-Type", "application/json");
100
+ res.end(JSON.stringify({ status: "OK" }, null, 2));
101
+ });
102
+
92
103
  app.use("/sbom", async function (req, res) {
93
104
  const q = url.parse(req.url, true).query;
94
105
  let cleanup = false;
@@ -106,7 +117,7 @@ const start = (options) => {
106
117
  srcDir = gitClone(filePath);
107
118
  cleanup = true;
108
119
  }
109
- console.log("Generating SBoM for", srcDir);
120
+ console.log("Generating SBOM for", srcDir);
110
121
  const bomNSData = (await createBom(srcDir, options)) || {};
111
122
  if (bomNSData.bomJson) {
112
123
  if (
@@ -119,7 +130,7 @@ const start = (options) => {
119
130
  }
120
131
  }
121
132
  if (options.serverUrl && options.apiKey) {
122
- console.log("Publishing SBoM to Dependency Track");
133
+ console.log("Publishing SBOM to Dependency Track");
123
134
  submitBom(options, bomNSData.bomJson);
124
135
  }
125
136
  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
 
@@ -4587,6 +4639,28 @@ export const parseCsPkgLockData = async function (csLockData) {
4587
4639
  return pkgList;
4588
4640
  };
4589
4641
 
4642
+ export const parsePaketLockData = async function (paketLockData) {
4643
+ const pkgList = [];
4644
+ let pkg = null;
4645
+ if (!paketLockData) {
4646
+ return pkgList;
4647
+ }
4648
+ const pkgRegex = /\s+([a-zA-Z0-9-.]+) \(((?=.*?\.)[a-zA-Z0-9-.]+)\)/g;
4649
+ for (const [, name, version] of paketLockData.matchAll(pkgRegex)) {
4650
+ const purl = decodeURIComponent(
4651
+ new PackageURL("nuget", "", name, version, null, null).toString()
4652
+ );
4653
+ pkg = {
4654
+ group: "",
4655
+ name: name,
4656
+ version: version,
4657
+ purl: purl
4658
+ };
4659
+ pkgList.push(pkg);
4660
+ }
4661
+ return pkgList;
4662
+ };
4663
+
4590
4664
  /**
4591
4665
  * Parse composer lock file
4592
4666
  *
@@ -5329,11 +5403,7 @@ export const collectMvnDependencies = function (
5329
5403
  "-Dmdep.stripVersion=" + (process.env.MAVEN_STRIP_VERSION || "false")
5330
5404
  ];
5331
5405
  if (basePath && basePath !== MAVEN_CACHE_DIR) {
5332
- console.log(
5333
- `Executing '${mavenCmd} dependency:copy-dependencies ${copyArgs.join(
5334
- " "
5335
- )}' in ${basePath}`
5336
- );
5406
+ console.log(`Executing '${mavenCmd} ${copyArgs.join(" ")}' in ${basePath}`);
5337
5407
  const result = spawnSync(mavenCmd, copyArgs, {
5338
5408
  cwd: basePath,
5339
5409
  encoding: "utf-8",
@@ -5710,10 +5780,15 @@ export const encodeForPurl = (s) => {
5710
5780
  *
5711
5781
  * @param {string} jarFile Path to jar file
5712
5782
  * @param {string} tempDir Temporary directory to use for extraction
5783
+ * @param {object} jarNSMapping Jar class names mapping object
5713
5784
  *
5714
5785
  * @return pkgList Package list
5715
5786
  */
5716
- export const extractJarArchive = function (jarFile, tempDir) {
5787
+ export const extractJarArchive = function (
5788
+ jarFile,
5789
+ tempDir,
5790
+ jarNSMapping = {}
5791
+ ) {
5717
5792
  const pkgList = [];
5718
5793
  let jarFiles = [];
5719
5794
  const fname = basename(jarFile);
@@ -5873,10 +5948,19 @@ export const extractJarArchive = function (jarFile, tempDir) {
5873
5948
  if (group == name) {
5874
5949
  group = "";
5875
5950
  }
5876
- pkgList.push({
5877
- group: group === "." ? "" : encodeForPurl(group || "") || "",
5951
+ group = group === "." ? "" : encodeForPurl(group || "") || "";
5952
+ let apkg = {
5953
+ group,
5878
5954
  name: name ? encodeForPurl(name) : "",
5879
5955
  version,
5956
+ purl: new PackageURL(
5957
+ "maven",
5958
+ group,
5959
+ name,
5960
+ version,
5961
+ { type: "jar" },
5962
+ null
5963
+ ).toString(),
5880
5964
  evidence: {
5881
5965
  identity: {
5882
5966
  field: "purl",
@@ -5896,7 +5980,18 @@ export const extractJarArchive = function (jarFile, tempDir) {
5896
5980
  value: jarname
5897
5981
  }
5898
5982
  ]
5899
- });
5983
+ };
5984
+ if (
5985
+ jarNSMapping &&
5986
+ jarNSMapping[apkg.purl] &&
5987
+ jarNSMapping[apkg.purl].namespaces
5988
+ ) {
5989
+ apkg.properties.push({
5990
+ name: "Namespaces",
5991
+ value: jarNSMapping[apkg.purl].namespaces.join("\n")
5992
+ });
5993
+ }
5994
+ pkgList.push(apkg);
5900
5995
  } else {
5901
5996
  if (DEBUG_MODE) {
5902
5997
  console.log(`Ignored jar ${jarname}`, jarMetadata, name, version);
@@ -6225,7 +6320,7 @@ export const executeAtom = (src, args) => {
6225
6320
  result.stderr.includes("Error: Could not create the Java Virtual Machine")
6226
6321
  ) {
6227
6322
  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."
6323
+ "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
6324
  );
6230
6325
  console.log(`Current JAVA_HOME: ${env["JAVA_HOME"] || ""}`);
6231
6326
  } else if (result.stderr.includes("astgen")) {
@@ -6510,7 +6605,7 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
6510
6605
  ) {
6511
6606
  versionRelatedError = true;
6512
6607
  console.log(
6513
- "The version or the version specifiers used for a dependency is invalid. Resolve the below error to improve SBoM accuracy."
6608
+ "The version or the version specifiers used for a dependency is invalid. Resolve the below error to improve SBOM accuracy."
6514
6609
  );
6515
6610
  console.log(result.stderr);
6516
6611
  }
@@ -6518,7 +6613,7 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
6518
6613
  console.log("args used:", pipInstallArgs);
6519
6614
  console.log(result.stdout, result.stderr);
6520
6615
  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."
6616
+ "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
6617
  );
6523
6618
  if (platform() === "win32") {
6524
6619
  console.log(
@@ -6541,7 +6636,7 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
6541
6636
  if (env.VIRTUAL_ENV && env.VIRTUAL_ENV.length) {
6542
6637
  /**
6543
6638
  * 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
6639
+ * 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
6640
  */
6546
6641
  if (DEBUG_MODE) {
6547
6642
  console.log(
@@ -6597,7 +6692,7 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
6597
6692
  } else {
6598
6693
  if (DEBUG_MODE) {
6599
6694
  console.log(
6600
- "NOTE: Setup and activate a python virtual environment for this project prior to invoking cdxgen to improve SBoM accuracy."
6695
+ "NOTE: Setup and activate a python virtual environment for this project prior to invoking cdxgen to improve SBOM accuracy."
6601
6696
  );
6602
6697
  }
6603
6698
  }
package/utils.test.js CHANGED
@@ -32,6 +32,7 @@ import {
32
32
  parseCsProjData,
33
33
  parseCsProjAssetsData,
34
34
  parseCsPkgLockData,
35
+ parsePaketLockData,
35
36
  getNugetMetadata,
36
37
  parsePom,
37
38
  getMvnMetadata,
@@ -1246,6 +1247,20 @@ test("parse packages.lock.json", async () => {
1246
1247
  });
1247
1248
  });
1248
1249
 
1250
+ test("parse paket.lock", async () => {
1251
+ expect(await parsePaketLockData(null)).toEqual([]);
1252
+ const dep_list = await parsePaketLockData(
1253
+ readFileSync("./test/data/paket.lock", { encoding: "utf-8" })
1254
+ );
1255
+ expect(dep_list.length).toEqual(13);
1256
+ expect(dep_list[0]).toEqual({
1257
+ group: "",
1258
+ name: "0x53A.ReferenceAssemblies.Paket",
1259
+ version: "0.2",
1260
+ purl: "pkg:nuget/0x53A.ReferenceAssemblies.Paket@0.2"
1261
+ });
1262
+ });
1263
+
1249
1264
  test("parse .net cs proj", async () => {
1250
1265
  expect(await parseCsProjData(null)).toEqual([]);
1251
1266
  const dep_list = await parseCsProjData(
@@ -1270,7 +1285,7 @@ test("get nget metadata", async () => {
1270
1285
  "pkg:nuget/NUnit.Console@3.11.1",
1271
1286
  "pkg:nuget/NUnit3TestAdapter@3.16.1",
1272
1287
  "pkg:nuget/NUnitLite@3.13.3",
1273
- "pkg:nuget/Serilog@0.0.0",
1288
+ "pkg:nuget/Serilog@3.0.1",
1274
1289
  "pkg:nuget/Serilog.Sinks.TextWriter@2.0.0",
1275
1290
  "pkg:nuget/System.Security.Permissions@4.7.0",
1276
1291
  "pkg:nuget/log4net@2.0.13",
@@ -1295,7 +1310,7 @@ test("get nget metadata", async () => {
1295
1310
  "pkg:nuget/System.Text.RegularExpressions@4.1.0",
1296
1311
  "pkg:nuget/System.Threading@4.0.11"
1297
1312
  ],
1298
- ref: "pkg:nuget/Serilog@0.0.0"
1313
+ ref: "pkg:nuget/Serilog@3.0.1"
1299
1314
  }
1300
1315
  ];
1301
1316
  let pkg_list = [
@@ -1308,8 +1323,8 @@ test("get nget metadata", async () => {
1308
1323
  {
1309
1324
  group: "",
1310
1325
  name: "Serilog",
1311
- version: "0.0.0",
1312
- "bom-ref": "pkg:nuget/Serilog@0.0.0"
1326
+ version: "3.0.1",
1327
+ "bom-ref": "pkg:nuget/Serilog@3.0.1"
1313
1328
  }
1314
1329
  ];
1315
1330
  const { pkgList, dependencies } = await getNugetMetadata(pkg_list, dep_list);
@@ -2261,6 +2276,25 @@ test("parse requirements.txt", async () => {
2261
2276
  version: "8.6.2",
2262
2277
  scope: "required"
2263
2278
  });
2279
+ deps = await parseReqFile(
2280
+ readFileSync("./test/data/chen-science-requirements.txt", {
2281
+ encoding: "utf-8"
2282
+ }),
2283
+ false
2284
+ );
2285
+ expect(deps.length).toEqual(87);
2286
+ expect(deps[0]).toEqual({
2287
+ name: "aiofiles",
2288
+ version: "23.2.1",
2289
+ scope: undefined,
2290
+ properties: [
2291
+ {
2292
+ name: "cdx:pip:markers",
2293
+ value:
2294
+ 'python_full_version >= "3.8.1" and python_version < "3.12" --hash=sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107 --hash=sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a'
2295
+ }
2296
+ ]
2297
+ });
2264
2298
  });
2265
2299
 
2266
2300
  test("parse pyproject.toml", async () => {