@cyclonedx/cdxgen 9.0.1 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,10 +2,14 @@
2
2
 
3
3
  ![cdxgen logo](cdxgen.png)
4
4
 
5
- This tool creates 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 XML and 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 and a library 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
7
  When used with plugins, cdxgen could generate an SBoM for Linux docker images and even VMs running Linux or Windows operating system.
8
8
 
9
+ NOTE:
10
+
11
+ CycloneDX 1.5 specification is brand new and unsupported by many downstream tools. Use version 8.6.0 for 1.4 compatibility.
12
+
9
13
  ## Supported languages and package format
10
14
 
11
15
  | Language/Platform | Package format | Transitive dependencies |
@@ -57,6 +61,8 @@ Footnotes:
57
61
  - [4] - See section on plugins
58
62
  - [5] - Powered by osquery. See section on plugins
59
63
 
64
+ ![cdxgen tree](./docs/cdxgen-tree.jpg)
65
+
60
66
  ### Automatic usage detection
61
67
 
62
68
  For node.js projects, lock files are parsed initially so the SBoM would include all dependencies including dev dependencies. 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 have their `scope` property set to `required` in the resulting SBoM. By passing the argument `--no-babel`, you can disable this analysis. Scope property would then be set based on the `dev` attribute in the lock file.
@@ -73,12 +79,35 @@ For go, `go mod why` command is used to identify required packages. For php, com
73
79
 
74
80
  ```shell
75
81
  sudo npm install -g @cyclonedx/cdxgen
82
+
83
+ # For CycloneDX 1.4 compatibility use version 8.6.0
84
+ sudo npm install -g @cyclonedx/cdxgen@8.6.0
85
+ ```
86
+
87
+ Deno install is also supported.
88
+
89
+ ```shell
90
+ deno install --allow-read --allow-env --allow-run --allow-sys=uid,systemMemoryInfo --allow-write --allow-net -n cdxgen "npm:@cyclonedx/cdxgen"
76
91
  ```
77
92
 
78
93
  You can also use the cdxgen container image
79
94
 
80
95
  ```bash
81
96
  docker run --rm -v /tmp:/tmp -v $(pwd):/app:rw -t ghcr.io/cyclonedx/cdxgen -r /app -o /app/bom.json
97
+
98
+ docker run --rm -v /tmp:/tmp -v $(pwd):/app:rw -t ghcr.io/cyclonedx/cdxgen:v8.6.0 -r /app -o /app/bom.json
99
+ ```
100
+
101
+ To use the deno version, use `ghcr.io/cyclonedx/cdxgen-deno` as the image name.
102
+
103
+ ```bash
104
+ docker run --rm -v /tmp:/tmp -v $(pwd):/app:rw -t ghcr.io/cyclonedx/cdxgen-deno -r /app -o /app/bom.json
105
+ ```
106
+
107
+ In deno applications, cdxgen could be directly imported without any conversion. Please see the section on [integration as library](#integration-as-library)
108
+
109
+ ```ts
110
+ import { createBom, submitBom } from "npm:@cyclonedx/cdxgen@^9.0.1";
82
111
  ```
83
112
 
84
113
  ## Getting Help
@@ -86,40 +115,43 @@ docker run --rm -v /tmp:/tmp -v $(pwd):/app:rw -t ghcr.io/cyclonedx/cdxgen -r /a
86
115
  ```text
87
116
  $ cdxgen -h
88
117
  Options:
89
- -o, --output Output file for bom.xml or bom.json. Default
90
- bom.json
118
+ -o, --output Output file for bom.xml or bom.json. Default bom.
119
+ json
91
120
  -t, --type Project type
92
121
  -r, --recurse Recurse mode suitable for mono-repos [boolean]
93
- -p, --print Print the SBoM as a table. Defaults to true if
94
- output file is not specified with -o [boolean]
95
- -c, --resolve-class Resolve class names for packages. jars only for
96
- now. [boolean]
97
- --deep Perform deep searches for components. Useful
98
- while scanning live OS and oci images. [boolean]
99
- --server-url Dependency track url. Eg:
100
- https://deptrack.cyclonedx.io
122
+ -p, --print Print the SBoM as a table with tree. Defaults to
123
+ true if output file is not specified with -o
124
+ [boolean]
125
+ -c, --resolve-class Resolve class names for packages. jars only for n
126
+ ow. [boolean]
127
+ --deep Perform deep searches for components. Useful whil
128
+ e scanning live OS and oci images. [boolean]
129
+ --server-url Dependency track url. Eg: https://deptrack.cyclon
130
+ edx.io
101
131
  --api-key Dependency track api key
102
132
  --project-group Dependency track project group
103
- --project-name Dependency track project name. Default use the
104
- directory name
133
+ --project-name Dependency track project name. Default use the di
134
+ rectory name
105
135
  --project-version Dependency track project version [default: ""]
106
- --project-id Dependency track project id. Either provide the
107
- id or the project name and version together
136
+ --project-id Dependency track project id. Either provide the i
137
+ d or the project name and version together
108
138
  --required-only Include only the packages with required scope on
109
139
  the SBoM. [boolean]
110
140
  --fail-on-error Fail if any dependency extractor fails. [boolean]
111
- --no-babel Do not use babel to perform usage analysis for
112
- JavaScript/TypeScript projects. [boolean]
141
+ --no-babel Do not use babel to perform usage analysis for Ja
142
+ vaScript/TypeScript projects. [boolean]
113
143
  --generate-key-and-sign Generate an RSA public/private key pair and then
114
- sign the generated SBoM using JSON Web
115
- Signatures. [boolean]
144
+ sign the generated SBoM using JSON Web Signatures
145
+ . [boolean]
116
146
  --server Run cdxgen as a server [boolean]
117
147
  --server-host Listen address [default: "127.0.0.1"]
118
148
  --server-port Listen port [default: "9090"]
119
- --install-deps Install dependencies automatically for some
120
- projects. Defaults to true but disabled for
121
- containers and oci scans. Use --no-install-deps
122
- to disable this feature.[boolean] [default: true]
149
+ --install-deps Install dependencies automatically for some proje
150
+ cts. Defaults to true but disabled for containers
151
+ and oci scans. Use --no-install-deps to disable
152
+ this feature. [boolean] [default: true]
153
+ --validate Validate the generated SBoM using json schema.
154
+ [boolean] [default: false]
123
155
  --version Show version number [boolean]
124
156
  -h Show help [boolean]
125
157
  ```
@@ -231,6 +263,7 @@ cdxgen can retain the dependency tree under the `dependencies` attribute for a s
231
263
  - pnpm-lock.yaml
232
264
  - Maven (pom.xml)
233
265
  - Gradle
266
+ - Python (requirements.txt, setup.py, pyproject.toml)
234
267
 
235
268
  ## Environment variables
236
269
 
@@ -348,16 +381,22 @@ Permission to modify and redistribute is granted under the terms of the Apache 2
348
381
 
349
382
  ## Integration as library
350
383
 
351
- This project is [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c) and requires Node.js >= 16
384
+ cdxgen is [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c) and could be imported and used with both deno and Node.js >= 16
352
385
 
353
386
  Minimal example:
354
387
 
388
+ ```ts
389
+ import { createBom, submitBom } from "npm:@cyclonedx/cdxgen@^9.0.1";
390
+ ```
391
+
392
+ See the [Deno Readme](./contrib/deno/README.md) for detailed instruction.
393
+
355
394
  ```javascript
356
395
  import { createBom, submitBom } from "@cyclonedx/cdxgen";
357
396
  // bomNSData would contain bomJson, bomXml
358
397
  const bomNSData = await createBom(filePath, options);
359
398
  // Submission to dependency track server
360
- const dbody = await submitBom(args, bomNSData.bomXml);
399
+ const dbody = await submitBom(args, bomNSData.bomJson);
361
400
  ```
362
401
 
363
402
  ## Node.js >= 20 permission model
package/analyzer.js CHANGED
@@ -36,7 +36,7 @@ const getAllFiles = (dir, extn, files, result, regex) => {
36
36
  if (IGNORE_FILE_PATTERN.test(files[i])) {
37
37
  continue;
38
38
  }
39
- let file = join(dir, files[i]);
39
+ const file = join(dir, files[i]);
40
40
  if (statSync(file).isDirectory()) {
41
41
  // Ignore directories
42
42
  const dirName = basename(file);
package/bin/cdxgen.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { createBom, submitBom } from "../index.js";
3
+ import { createBom, submitBom, validateBom } from "../index.js";
4
4
  import fs from "node:fs";
5
5
  import { tmpdir } from "node:os";
6
6
  import { basename, dirname, join, resolve } from "node:path";
@@ -9,8 +9,8 @@ import crypto from "crypto";
9
9
  import { start as _serverStart } from "../server.js";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import globalAgent from "global-agent";
12
- import { table } from "table";
13
12
  import process from "node:process";
13
+ import { printTable, printDependencyTree } from "../display.js";
14
14
 
15
15
  let url = import.meta.url;
16
16
  if (!url.startsWith("file://")) {
@@ -39,7 +39,7 @@ const args = yargs(hideBin(process.argv))
39
39
  alias: "p",
40
40
  type: "boolean",
41
41
  description:
42
- "Print the SBoM as a table. Defaults to true if output file is not specified with -o"
42
+ "Print the SBoM as a table with tree. Defaults to true if output file is not specified with -o"
43
43
  })
44
44
  .option("resolve-class", {
45
45
  alias: "c",
@@ -107,6 +107,11 @@ const args = yargs(hideBin(process.argv))
107
107
  description:
108
108
  "Install dependencies automatically for some projects. Defaults to true but disabled for containers and oci scans. Use --no-install-deps to disable this feature."
109
109
  })
110
+ .option("validate", {
111
+ type: "boolean",
112
+ default: false,
113
+ description: "Validate the generated SBoM using json schema."
114
+ })
110
115
  .scriptName("cdxgen")
111
116
  .version()
112
117
  .help("h").argv;
@@ -130,7 +135,7 @@ if (process.env.GLOBAL_AGENT_HTTP_PROXY || process.env.HTTP_PROXY) {
130
135
  globalAgent.bootstrap();
131
136
  }
132
137
 
133
- let filePath = args._[0] || ".";
138
+ const filePath = args._[0] || ".";
134
139
  if (!args.projectName) {
135
140
  if (filePath !== ".") {
136
141
  args.projectName = basename(filePath);
@@ -143,7 +148,7 @@ if (!args.projectName) {
143
148
  * projectType: python, nodejs, java, golang
144
149
  * multiProject: Boolean to indicate monorepo or multi-module projects
145
150
  */
146
- let options = {
151
+ const options = {
147
152
  projectType: args.type,
148
153
  multiProject: args.recurse,
149
154
  output: args.output,
@@ -340,7 +345,12 @@ const checkPermissions = (filePath) => {
340
345
  console.log("Try running the command with -t <type> or -r argument");
341
346
  }
342
347
  }
343
-
348
+ // Perform automatic validation
349
+ if (args.validate) {
350
+ if (!validateBom(bomNSData.bomJson)) {
351
+ process.exit(1);
352
+ }
353
+ }
344
354
  // Automatically submit the bom data
345
355
  if (args.serverUrl && args.serverUrl != true && args.apiKey) {
346
356
  try {
@@ -352,23 +362,7 @@ const checkPermissions = (filePath) => {
352
362
  }
353
363
 
354
364
  if (args.print && bomNSData.bomJson && bomNSData.bomJson.components) {
355
- const data = [["Group", "Name", "Version", "Scope"]];
356
- for (let comp of bomNSData.bomJson.components) {
357
- data.push([comp.group || "", comp.name, comp.version, comp.scope || ""]);
358
- }
359
- const config = {
360
- header: {
361
- alignment: "center",
362
- content: "Software Bill-of-Materials\nGenerated by @cyclonedx/cdxgen"
363
- }
364
- };
365
- console.log(table(data, config));
366
- console.log(
367
- "BOM includes",
368
- bomNSData.bomJson.components.length,
369
- "components and",
370
- bomNSData.bomJson.dependencies.length,
371
- "dependencies"
372
- );
365
+ printDependencyTree(bomNSData.bomJson);
366
+ printTable(bomNSData.bomJson);
373
367
  }
374
368
  })();
package/binary.js CHANGED
@@ -78,7 +78,7 @@ if (
78
78
  if (!CDXGEN_PLUGINS_DIR) {
79
79
  let globalNodePath = process.env.GLOBAL_NODE_MODULES_PATH || undefined;
80
80
  if (!globalNodePath) {
81
- let result = spawnSync(
81
+ const result = spawnSync(
82
82
  isWin ? "npm.cmd" : "npm",
83
83
  ["root", "--quiet", "-g"],
84
84
  {
@@ -251,7 +251,7 @@ export const getGoBuildInfo = (src) => {
251
251
 
252
252
  export const getCargoAuditableInfo = (src) => {
253
253
  if (CARGO_AUDITABLE_BIN) {
254
- let result = spawnSync(CARGO_AUDITABLE_BIN, [src], {
254
+ const result = spawnSync(CARGO_AUDITABLE_BIN, [src], {
255
255
  encoding: "utf-8"
256
256
  });
257
257
  if (result.status !== 0 || result.error) {
@@ -278,7 +278,7 @@ export const getOSPackages = (src) => {
278
278
  if (existsSync(src)) {
279
279
  imageType = "rootfs";
280
280
  }
281
- let tempDir = mkdtempSync(join(tmpdir(), "trivy-cdxgen-"));
281
+ const tempDir = mkdtempSync(join(tmpdir(), "trivy-cdxgen-"));
282
282
  const bomJsonFile = join(tempDir, "trivy-bom.json");
283
283
  const args = [
284
284
  imageType,
@@ -299,7 +299,7 @@ export const getOSPackages = (src) => {
299
299
  if (DEBUG_MODE) {
300
300
  console.log("Executing", TRIVY_BIN, args.join(" "));
301
301
  }
302
- let result = spawnSync(TRIVY_BIN, args, {
302
+ const result = spawnSync(TRIVY_BIN, args, {
303
303
  encoding: "utf-8"
304
304
  });
305
305
  if (result.status !== 0 || result.error) {
@@ -350,7 +350,7 @@ export const getOSPackages = (src) => {
350
350
  }
351
351
  // Fix the group
352
352
  let group = dirname(comp.name);
353
- let name = basename(comp.name);
353
+ const name = basename(comp.name);
354
354
  let purlObj = undefined;
355
355
  let distro_codename = "";
356
356
  if (group === ".") {
@@ -461,7 +461,7 @@ export const getOSPackages = (src) => {
461
461
  pkgList.push(comp);
462
462
  // If there is a source package defined include it as well
463
463
  if (srcName && srcVersion && srcName !== comp.name) {
464
- let newComp = Object.assign({}, comp);
464
+ const newComp = Object.assign({}, comp);
465
465
  newComp.name = srcName;
466
466
  newComp.version = srcVersion;
467
467
  if (purlObj) {
@@ -495,7 +495,7 @@ export const executeOsQuery = (query) => {
495
495
  if (DEBUG_MODE) {
496
496
  console.log("Execuing", OSQUERY_BIN, args.join(" "));
497
497
  }
498
- let result = spawnSync(OSQUERY_BIN, args, {
498
+ const result = spawnSync(OSQUERY_BIN, args, {
499
499
  encoding: "utf-8"
500
500
  });
501
501
  if (result.status !== 0 || result.error) {