@cyclonedx/cdxgen 9.7.0 → 9.7.1

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
@@ -90,6 +90,12 @@ sudo npm install -g @cyclonedx/cdxgen
90
90
  sudo npm install -g @cyclonedx/cdxgen@8.6.0
91
91
  ```
92
92
 
93
+ If you are a [Homebrew](https://brew.sh/) user, you can also install [cdxgen](https://formulae.brew.sh/formula/cdxgen) via:
94
+
95
+ ```shell
96
+ $ brew install cdxgen
97
+ ```
98
+
93
99
  Deno install is also supported.
94
100
 
95
101
  ```shell
@@ -161,8 +167,8 @@ Options:
161
167
  [boolean] [default: true]
162
168
  --spec-version CycloneDX Specification version to use. Defaults
163
169
  to 1.5 [default: 1.5]
164
- --version Show version number [boolean]
165
- -h Show help [boolean]
170
+ -h, --help Show help [boolean]
171
+ -v, --version Show version number [boolean]
166
172
  ```
167
173
 
168
174
  All boolean arguments accepts `--no` prefix to toggle the behavior.
@@ -293,6 +299,7 @@ cdxgen can retain the dependency tree under the `dependencies` attribute for a s
293
299
  | MVN_ARGS | Set to pass additional arguments such as profile or settings to maven |
294
300
  | MAVEN_HOME | Specify maven home |
295
301
  | MAVEN_CENTRAL_URL | Specify URL of Maven Central for metadata fetching (e.g. when private repo is used) |
302
+ | BAZEL_STRIP_MAVEN_PREFIX | Strip Maven group prefix (e.g. useful when private repo is used, defaults to `/maven2/`) |
296
303
  | GRADLE_CACHE_DIR | Specify gradle cache directory. Useful for class name resolving |
297
304
  | GRADLE_MULTI_PROJECT_MODE | Unused. Automatically handled |
298
305
  | GRADLE_ARGS | Set to pass additional arguments such as profile or settings to gradle (all tasks). Eg: --configuration runtimeClassPath |
@@ -367,10 +374,12 @@ podman system service -t 0 &
367
374
 
368
375
  ### Generate OBoM for a live system
369
376
 
370
- You can use cdxgen to generate an OBoM for a live system or a VM for compliance and vulnerability management purposes by passing the argument `-t os`. Windows and Linux operating systems are supported in this mode.
377
+ You can use the `obom` command to generate an OBoM for a live system or a VM for compliance and vulnerability management purposes. Windows and Linux operating systems are supported in this mode.
371
378
 
372
379
  ```shell
373
- cdxgen -t os
380
+ # obom is an alias for cdxgen -t os
381
+ obom
382
+ # cdxgen -t os
374
383
  ```
375
384
 
376
385
  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.
package/bin/cdxgen.js CHANGED
@@ -121,7 +121,9 @@ const args = yargs(hideBin(process.argv))
121
121
  })
122
122
  .scriptName("cdxgen")
123
123
  .version()
124
- .help("h").argv;
124
+ .alias("v", "version")
125
+ .help("h")
126
+ .alias("h", "help").argv;
125
127
 
126
128
  if (args.version) {
127
129
  const packageJsonAsString = fs.readFileSync(
@@ -156,6 +158,11 @@ if (args.serverUrl || args.apiKey) {
156
158
  args.specVersion = 1.4;
157
159
  }
158
160
 
161
+ // Support for obom aliases
162
+ if (process.argv[1].includes("obom") && !args.type) {
163
+ args.type = "os";
164
+ }
165
+
159
166
  /**
160
167
  * projectType: python, nodejs, java, golang
161
168
  * multiProject: Boolean to indicate monorepo or multi-module projects
package/bin/repl.js CHANGED
@@ -12,6 +12,7 @@ import { validateBom } from "../validator.js";
12
12
  import {
13
13
  printCallStack,
14
14
  printOccurrences,
15
+ printOSTable,
15
16
  printTable,
16
17
  printDependencyTree,
17
18
  printServices
@@ -32,8 +33,8 @@ process.env.NODE_NO_READLINE = 1;
32
33
  const cdxArt = `
33
34
  ██████╗██████╗ ██╗ ██╗
34
35
  ██╔════╝██╔══██╗╚██╗██╔╝
35
- ██║ ██║ ██║ ╚███╔╝
36
- ██║ ██║ ██║ ██╔██╗
36
+ ██║ ██║ ██║ ╚███╔╝
37
+ ██║ ██║ ██║ ██╔██╗
37
38
  ╚██████╗██████╔╝██╔╝ ██╗
38
39
  ╚═════╝╚═════╝ ╚═╝ ╚═╝
39
40
  `;
@@ -109,16 +110,16 @@ cdxgenRepl.defineCommand("create", {
109
110
  });
110
111
  if (bomNSData) {
111
112
  sbom = bomNSData.bomJson;
112
- console.log("✅ SBoM imported successfully.");
113
- console.log("💭 Type .print to view the SBoM as a table");
113
+ console.log("✅ BoM imported successfully.");
114
+ console.log("💭 Type .print to view the BoM as a table");
114
115
  } else {
115
- console.log("SBoM was not generated successfully");
116
+ console.log("BoM was not generated successfully");
116
117
  }
117
118
  this.displayPrompt();
118
119
  }
119
120
  });
120
121
  cdxgenRepl.defineCommand("import", {
121
- help: "import an existing SBoM",
122
+ help: "import an existing BoM",
122
123
  action(sbomOrPath) {
123
124
  this.clearBufferedCommand();
124
125
  importSbom(sbomOrPath);
@@ -138,14 +139,14 @@ cdxgenRepl.defineCommand("sbom", {
138
139
  console.log(sbom);
139
140
  } else {
140
141
  console.log(
141
- "⚠ No SBoM is loaded. Use .import command to import an existing SBoM"
142
+ "⚠ No BoM is loaded. Use .import command to import an existing BoM"
142
143
  );
143
144
  }
144
145
  this.displayPrompt();
145
146
  }
146
147
  });
147
148
  cdxgenRepl.defineCommand("search", {
148
- help: "search the current sbom. performs case insensitive search on various attributes.",
149
+ help: "search the current bom. performs case insensitive search on various attributes.",
149
150
  async action(searchStr) {
150
151
  if (sbom) {
151
152
  if (searchStr) {
@@ -170,14 +171,14 @@ cdxgenRepl.defineCommand("search", {
170
171
  }
171
172
  } else {
172
173
  console.log(
173
- "⚠ No SBoM is loaded. Use .import command to import an existing SBoM"
174
+ "⚠ No BoM is loaded. Use .import command to import an existing BoM"
174
175
  );
175
176
  }
176
177
  this.displayPrompt();
177
178
  }
178
179
  });
179
180
  cdxgenRepl.defineCommand("sort", {
180
- help: "sort the current sbom based on the attribute",
181
+ help: "sort the current bom based on the attribute",
181
182
  async action(sortStr) {
182
183
  if (sbom) {
183
184
  if (sortStr) {
@@ -204,14 +205,14 @@ cdxgenRepl.defineCommand("sort", {
204
205
  }
205
206
  } else {
206
207
  console.log(
207
- "⚠ No SBoM is loaded. Use .import command to import an existing SBoM"
208
+ "⚠ No BoM is loaded. Use .import command to import an existing BoM"
208
209
  );
209
210
  }
210
211
  this.displayPrompt();
211
212
  }
212
213
  });
213
214
  cdxgenRepl.defineCommand("query", {
214
- help: "query the current sbom using jsonata expression",
215
+ help: "query the current bom using jsonata expression",
215
216
  async action(querySpec) {
216
217
  if (sbom) {
217
218
  if (querySpec) {
@@ -228,20 +229,20 @@ cdxgenRepl.defineCommand("query", {
228
229
  }
229
230
  } else {
230
231
  console.log(
231
- "⚠ No SBoM is loaded. Use .import command to import an existing SBoM"
232
+ "⚠ No BoM is loaded. Use .import command to import an existing BoM"
232
233
  );
233
234
  }
234
235
  this.displayPrompt();
235
236
  }
236
237
  });
237
238
  cdxgenRepl.defineCommand("print", {
238
- help: "print the current sbom as a table",
239
+ help: "print the current bom as a table",
239
240
  action() {
240
241
  if (sbom) {
241
242
  printTable(sbom);
242
243
  } else {
243
244
  console.log(
244
- "⚠ No SBoM is loaded. Use .import command to import an existing SBoM"
245
+ "⚠ No BoM is loaded. Use .import command to import an existing BoM"
245
246
  );
246
247
  }
247
248
  this.displayPrompt();
@@ -254,14 +255,14 @@ cdxgenRepl.defineCommand("tree", {
254
255
  printDependencyTree(sbom);
255
256
  } else {
256
257
  console.log(
257
- "⚠ No SBoM is loaded. Use .import command to import an existing SBoM"
258
+ "⚠ No BoM is loaded. Use .import command to import an existing BoM"
258
259
  );
259
260
  }
260
261
  this.displayPrompt();
261
262
  }
262
263
  });
263
264
  cdxgenRepl.defineCommand("validate", {
264
- help: "validate the sbom using jsonschema",
265
+ help: "validate the bom using jsonschema",
265
266
  action() {
266
267
  if (sbom) {
267
268
  const result = validateBom(sbom);
@@ -270,31 +271,31 @@ cdxgenRepl.defineCommand("validate", {
270
271
  }
271
272
  } else {
272
273
  console.log(
273
- "⚠ No SBoM is loaded. Use .import command to import an existing SBoM"
274
+ "⚠ No BoM is loaded. Use .import command to import an existing BoM"
274
275
  );
275
276
  }
276
277
  this.displayPrompt();
277
278
  }
278
279
  });
279
280
  cdxgenRepl.defineCommand("save", {
280
- help: "save the sbom to a new file",
281
+ help: "save the bom to a new file",
281
282
  action(saveToFile) {
282
283
  if (sbom) {
283
284
  if (!saveToFile) {
284
285
  saveToFile = "bom.json";
285
286
  }
286
287
  fs.writeFileSync(saveToFile, JSON.stringify(sbom, null, 2));
287
- console.log(`SBoM saved successfully to ${saveToFile}`);
288
+ console.log(`BoM saved successfully to ${saveToFile}`);
288
289
  } else {
289
290
  console.log(
290
- "⚠ No SBoM is loaded. Use .import command to import an existing SBoM"
291
+ "⚠ No BoM is loaded. Use .import command to import an existing BoM"
291
292
  );
292
293
  }
293
294
  this.displayPrompt();
294
295
  }
295
296
  });
296
297
  cdxgenRepl.defineCommand("update", {
297
- help: "update the sbom components based on the given query",
298
+ help: "update the bom components based on the given query",
298
299
  async action(updateSpec) {
299
300
  if (sbom) {
300
301
  if (!updateSpec) {
@@ -312,10 +313,10 @@ cdxgenRepl.defineCommand("update", {
312
313
  if (newSbom && newSbom.components.length <= sbom.components.length) {
313
314
  sbom = newSbom;
314
315
  }
315
- console.log("SBoM updated successfully.");
316
+ console.log("BoM updated successfully.");
316
317
  } else {
317
318
  console.log(
318
- "⚠ No SBoM is loaded. Use .import command to import an existing SBoM"
319
+ "⚠ No BoM is loaded. Use .import command to import an existing BoM"
319
320
  );
320
321
  }
321
322
  this.displayPrompt();
@@ -332,7 +333,7 @@ cdxgenRepl.defineCommand("occurrences", {
332
333
  let components = await expression.evaluate(sbom);
333
334
  if (!components) {
334
335
  console.log(
335
- "No results found. Use evinse command to generate an SBoM with evidence."
336
+ "No results found. Use evinse command to generate an BoM with evidence."
336
337
  );
337
338
  } else {
338
339
  if (!Array.isArray(components)) {
@@ -345,7 +346,7 @@ cdxgenRepl.defineCommand("occurrences", {
345
346
  }
346
347
  } else {
347
348
  console.log(
348
- "⚠ No SBoM is loaded. Use .import command to import an evinse SBoM"
349
+ "⚠ No BoM is loaded. Use .import command to import an evinse BoM"
349
350
  );
350
351
  }
351
352
  this.displayPrompt();
@@ -425,3 +426,130 @@ cdxgenRepl.defineCommand("services", {
425
426
  this.displayPrompt();
426
427
  }
427
428
  });
429
+ cdxgenRepl.defineCommand("osinfocategories", {
430
+ help: "view the category names for the OS info from the obom",
431
+ async action() {
432
+ if (sbom) {
433
+ try {
434
+ const expression = jsonata(
435
+ '$distinct(components.properties[name="cdx:osquery:category"].value)'
436
+ );
437
+ let catgories = await expression.evaluate(sbom);
438
+ if (!catgories) {
439
+ console.log(
440
+ "Unable to retrieve the os info categories. Only OBoMs generated by cdxgen are supported by this tool."
441
+ );
442
+ } else {
443
+ console.log(catgories.join("\n"));
444
+ }
445
+ } catch (e) {
446
+ console.log(e);
447
+ }
448
+ } else {
449
+ console.log(
450
+ "⚠ No OBoM is loaded. Use .import command to import an OBoM"
451
+ );
452
+ }
453
+ this.displayPrompt();
454
+ }
455
+ });
456
+
457
+ // Let's dynamically define more commands from the queries
458
+ [
459
+ "apt_sources",
460
+ "behavioral_reverse_shell",
461
+ "certificates",
462
+ "chrome_extensions",
463
+ "crontab_snapshot",
464
+ "deb_packages",
465
+ "docker_container_ports",
466
+ "docker_containers",
467
+ "docker_networks",
468
+ "docker_volumes",
469
+ "etc_hosts",
470
+ "firefox_addons",
471
+ "homebrew_packages",
472
+ "installed_applications",
473
+ "interface_addresses",
474
+ "kernel_info",
475
+ "kernel_integrity",
476
+ "kernel_modules",
477
+ "ld_preload",
478
+ "listening_ports",
479
+ "os_version",
480
+ "pipes",
481
+ "pipes_snapshot",
482
+ "portage_packages",
483
+ "process_events",
484
+ "processes",
485
+ "python_packages",
486
+ "rpm_packages",
487
+ "scheduled_tasks",
488
+ "services_snapshot",
489
+ "startup_items",
490
+ "system_info_snapshot",
491
+ "windows_drivers",
492
+ "windows_patches",
493
+ "windows_programs",
494
+ "windows_shared_resources",
495
+ "yum_sources",
496
+ "appcompat_shims",
497
+ "atom_packages",
498
+ "browser_plugins",
499
+ "certificates",
500
+ "chocolatey_packages",
501
+ "chrome_extensions",
502
+ "etc_hosts",
503
+ "firefox_addons",
504
+ "ie_extensions",
505
+ "kernel_info",
506
+ "npm_packages",
507
+ "opera_extensions",
508
+ "pipes_snapshot",
509
+ "process_open_sockets",
510
+ "safari_extensions",
511
+ "scheduled_tasks",
512
+ "services_snapshot",
513
+ "startup_items",
514
+ "routes",
515
+ "system_info_snapshot",
516
+ "win_version",
517
+ "windows_firewall_rules",
518
+ "windows_optional_features",
519
+ "windows_programs",
520
+ "windows_shared_resources",
521
+ "windows_update_history",
522
+ "wmi_cli_event_consumers",
523
+ "wmi_cli_event_consumers_snapshot",
524
+ "wmi_event_filters",
525
+ "wmi_filter_consumer_binding"
526
+ ].forEach((c) => {
527
+ cdxgenRepl.defineCommand(c, {
528
+ help: `query the ${c} category from the OS info`,
529
+ async action() {
530
+ if (sbom) {
531
+ try {
532
+ const expression = jsonata(
533
+ `components[properties[name="cdx:osquery:category" and value="${c}"]]`
534
+ );
535
+ let components = await expression.evaluate(sbom);
536
+ if (!components) {
537
+ console.log("No results found.");
538
+ } else {
539
+ if (!Array.isArray(components)) {
540
+ components = [components];
541
+ }
542
+ printOSTable({ components });
543
+ }
544
+ } catch (e) {
545
+ console.log(e);
546
+ }
547
+ } else {
548
+ console.log(
549
+ "⚠ No OBoM is loaded. Use .import command to import an OBoM"
550
+ );
551
+ }
552
+ this.displayPrompt();
553
+ }
554
+ });
555
+ });
@@ -162,10 +162,10 @@
162
162
  "componentType": "data"
163
163
  },
164
164
  "listening_ports": {
165
- "query": "SELECT * FROM listening_ports;",
166
- "description": "List all listening_ports.",
165
+ "query": "SELECT DISTINCT process.name, listening.port, listening.protocol, listening.family, listening.address, process.pid, process.path, process.on_disk, process.parent, process.start_time FROM processes AS process JOIN listening_ports AS listening ON process.pid = listening.pid;",
166
+ "description": "List all processes and their listening_ports.",
167
167
  "purlType": "swid",
168
- "componentType": "data"
168
+ "componentType": "application"
169
169
  },
170
170
  "processes": {
171
171
  "query": "SELECT * FROM processes;",
@@ -196,5 +196,11 @@
196
196
  "description": "List all windows_firewall_rules.",
197
197
  "purlType": "swid",
198
198
  "componentType": "data"
199
+ },
200
+ "logical_drives": {
201
+ "query": "SELECT * FROM logical_drives;",
202
+ "description": "List all logical_drives.",
203
+ "purlType": "swid",
204
+ "componentType": "device"
199
205
  }
200
206
  }
package/data/queries.json CHANGED
@@ -96,12 +96,6 @@
96
96
  "purlType": "swid",
97
97
  "componentType": "data"
98
98
  },
99
- "pipes": {
100
- "query": "SELECT processes.path, processes.cmdline, processes.uid, processes.on_disk, pipes.name, pid FROM pipes JOIN processes USING (pid);",
101
- "description": "Named and Anonymous pipes.",
102
- "purlType": "swid",
103
- "componentType": "data"
104
- },
105
99
  "etc_hosts": {
106
100
  "query": "SELECT * FROM etc_hosts;",
107
101
  "description": "List the contents of the Windows hosts file.",
@@ -181,10 +175,10 @@
181
175
  "componentType": "data"
182
176
  },
183
177
  "listening_ports": {
184
- "query": "SELECT * FROM listening_ports;",
185
- "description": "List all listening_ports.",
178
+ "query": "SELECT DISTINCT process.name, listening.port, listening.protocol, listening.family, listening.address, process.pid, process.path, process.on_disk, process.parent, process.start_time FROM processes AS process JOIN listening_ports AS listening ON process.pid = listening.pid;",
179
+ "description": "List all processes and their listening_ports.",
186
180
  "purlType": "swid",
187
- "componentType": "data"
181
+ "componentType": "application"
188
182
  },
189
183
  "interface_addresses": {
190
184
  "query": "SELECT * FROM interface_addresses;",
package/display.js CHANGED
@@ -1,4 +1,4 @@
1
- import { table } from "table";
1
+ import { createStream, table } from "table";
2
2
 
3
3
  // https://github.com/yangshun/tree-node-cli/blob/master/src/index.js
4
4
  const SYMBOLS_ANSI = {
@@ -12,19 +12,31 @@ const SYMBOLS_ANSI = {
12
12
  const MAX_TREE_DEPTH = 6;
13
13
 
14
14
  export const printTable = (bomJson) => {
15
- const data = [["Group", "Name", "Version", "Scope"]];
16
15
  if (!bomJson || !bomJson.components) {
17
16
  return;
18
17
  }
19
- for (const comp of bomJson.components) {
20
- data.push([comp.group || "", comp.name, comp.version, comp.scope || ""]);
18
+ if (
19
+ bomJson.metadata &&
20
+ bomJson.metadata.component &&
21
+ ["operating-system", "platform"].includes(bomJson.metadata.component.type)
22
+ ) {
23
+ return printOSTable(bomJson);
21
24
  }
25
+ const data = [["Group", "Name", "Version", "Scope"]];
22
26
  const config = {
23
27
  header: {
24
28
  alignment: "center",
25
29
  content: "Software Bill-of-Materials\nGenerated by cdxgen"
26
30
  }
27
31
  };
32
+ for (const comp of bomJson.components) {
33
+ data.push([
34
+ comp.group || "",
35
+ comp.name,
36
+ `\x1b[1;35m${comp.version}\x1b[0m`,
37
+ comp.scope || ""
38
+ ]);
39
+ }
28
40
  console.log(table(data, config));
29
41
  console.log(
30
42
  "BOM includes",
@@ -34,6 +46,32 @@ export const printTable = (bomJson) => {
34
46
  "dependencies"
35
47
  );
36
48
  };
49
+ const formatProps = (props) => {
50
+ const retList = [];
51
+ for (const p of props) {
52
+ retList.push(`\x1b[0;32m${p.name}\x1b[0m ${p.value}`);
53
+ }
54
+ return retList.join("\n");
55
+ };
56
+ export const printOSTable = (bomJson) => {
57
+ const config = {
58
+ columnDefault: {
59
+ width: 50
60
+ },
61
+ columnCount: 3,
62
+ columns: [{ width: 20 }, { width: 40 }, { width: 50 }]
63
+ };
64
+ const stream = createStream(config);
65
+ stream.write(["Type", "Title", "Properties"]);
66
+ for (const comp of bomJson.components) {
67
+ stream.write([
68
+ comp.type,
69
+ `\x1b[1;35m${comp.name.replace(/\+/g, " ").replace(/--/g, "::")}\x1b[0m`,
70
+ formatProps(comp.properties || [])
71
+ ]);
72
+ }
73
+ console.log();
74
+ };
37
75
  export const printServices = (bomJson) => {
38
76
  const data = [["Name", "Endpoints", "Authenticated", "X Trust Boundary"]];
39
77
  if (!bomJson || !bomJson.services) {
@@ -43,8 +81,8 @@ export const printServices = (bomJson) => {
43
81
  data.push([
44
82
  aservice.name || "",
45
83
  aservice.endpoints ? aservice.endpoints.join("\n") : "",
46
- aservice.authenticated ? "Yes" : "",
47
- aservice.xTrustBoundary ? "Yes" : ""
84
+ aservice.authenticated ? "\x1b[1;35mYes\x1b[0m" : "",
85
+ aservice.xTrustBoundary ? "\x1b[1;35mYes\x1b[0m" : ""
48
86
  ]);
49
87
  }
50
88
  const config = {
package/index.js CHANGED
@@ -1247,6 +1247,15 @@ export const createJavaBom = async (path, options) => {
1247
1247
  console.log(
1248
1248
  "1. Try building the project with 'mvn package -Dmaven.test.skip=true' using the correct version of Java and maven before invoking cdxgen."
1249
1249
  );
1250
+ } else if (
1251
+ result.stdout &&
1252
+ result.stdout.includes(
1253
+ "Could not resolve target platform specification"
1254
+ )
1255
+ ) {
1256
+ console.log(
1257
+ "1. Some projects can be built only from the root directory. Invoke cdxgen with --no-recurse option"
1258
+ );
1250
1259
  } else {
1251
1260
  console.log(
1252
1261
  "1. Java version requirement: cdxgen container image bundles Java 20 with maven 3.9 which might be incompatible."
@@ -1306,6 +1315,7 @@ export const createJavaBom = async (path, options) => {
1306
1315
  !Object.keys(parentComponent).length
1307
1316
  ) {
1308
1317
  parentComponent = bomJsonObj.metadata.component;
1318
+ options.parentComponent = parentComponent;
1309
1319
  pkgList = [];
1310
1320
  }
1311
1321
  if (bomJsonObj.components) {
@@ -1594,7 +1604,12 @@ export const createJavaBom = async (path, options) => {
1594
1604
  result = spawnSync(
1595
1605
  BAZEL_CMD,
1596
1606
  ["aquery", "--output=textproto", "--skyframe_state"],
1597
- { cwd: basePath, encoding: "utf-8", timeout: TIMEOUT_MS }
1607
+ {
1608
+ cwd: basePath,
1609
+ encoding: "utf-8",
1610
+ timeout: TIMEOUT_MS,
1611
+ maxBuffer: 1024 * 1024 * 100
1612
+ }
1598
1613
  );
1599
1614
  if (result.status !== 0 || result.error) {
1600
1615
  console.error(result.stdout, result.stderr);
@@ -3390,7 +3405,7 @@ export const createContainerSpecLikeBom = async (path, options) => {
3390
3405
  const ociSpecs = [];
3391
3406
  let components = [];
3392
3407
  let componentsXmls = [];
3393
- const parentComponent = {};
3408
+ let parentComponent = {};
3394
3409
  let dependencies = [];
3395
3410
  const doneimages = [];
3396
3411
  const doneservices = [];
@@ -3629,6 +3644,10 @@ export const createContainerSpecLikeBom = async (path, options) => {
3629
3644
  if (mbomData.componentsXmls && mbomData.componentsXmls.length) {
3630
3645
  componentsXmls = componentsXmls.concat(mbomData.componentsXmls);
3631
3646
  }
3647
+ // We need to retain the parentComponent. See #527
3648
+ // Parent component returned by multi X search is usually good
3649
+ parentComponent = mbomData.parentComponent;
3650
+ options.parentComponent = parentComponent;
3632
3651
  if (mbomData.bomJson) {
3633
3652
  if (mbomData.bomJson.dependencies) {
3634
3653
  dependencies = mergeDependencies(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyclonedx/cdxgen",
3
- "version": "9.7.0",
3
+ "version": "9.7.1",
4
4
  "description": "Creates CycloneDX Software Bill-of-Materials (SBOM) from source or container image",
5
5
  "homepage": "http://github.com/cyclonedx/cdxgen",
6
6
  "author": "Prabhu Subramanian <prabhu@appthreat.com>",
@@ -32,6 +32,7 @@
32
32
  "exports": "./index.js",
33
33
  "bin": {
34
34
  "cdxgen": "./bin/cdxgen.js",
35
+ "obom": "./bin/cdxgen.js",
35
36
  "cdxi": "./bin/repl.js",
36
37
  "evinse": "./bin/evinse.js",
37
38
  "cdx-verify": "./bin/verify.js"
@@ -53,13 +54,13 @@
53
54
  "url": "https://github.com/cyclonedx/cdxgen/issues"
54
55
  },
55
56
  "dependencies": {
56
- "@babel/parser": "^7.22.11",
57
+ "@babel/parser": "^7.22.14",
57
58
  "@babel/traverse": "^7.22.11",
58
59
  "ajv": "^8.12.0",
59
60
  "ajv-formats": "^2.1.1",
60
61
  "cheerio": "^1.0.0-rc.12",
61
62
  "edn-data": "^1.0.0",
62
- "glob": "^10.3.0",
63
+ "glob": "^10.3.4",
63
64
  "global-agent": "^3.0.0",
64
65
  "got": "^13.0.0",
65
66
  "iconv-lite": "^0.6.3",
@@ -79,7 +80,7 @@
79
80
  "yargs": "^17.7.2"
80
81
  },
81
82
  "optionalDependencies": {
82
- "@appthreat/atom": "^1.1.4",
83
+ "@appthreat/atom": "^1.1.6",
83
84
  "@cyclonedx/cdxgen-plugins-bin": "^1.4.0",
84
85
  "@cyclonedx/cdxgen-plugins-bin-arm64": "^1.4.0",
85
86
  "@cyclonedx/cdxgen-plugins-bin-ppc64": "^1.4.0",
@@ -99,6 +100,6 @@
99
100
  "caxa": "^3.0.1",
100
101
  "eslint": "^8.48.0",
101
102
  "jest": "^29.6.4",
102
- "prettier": "3.0.2"
103
+ "prettier": "3.0.3"
103
104
  }
104
105
  }
package/utils.js CHANGED
@@ -1339,7 +1339,7 @@ export const parseMavenTree = function (rawOutput) {
1339
1339
  pkgArr[0],
1340
1340
  pkgArr[1],
1341
1341
  versionStr,
1342
- { type: "jar" },
1342
+ { type: pkgArr[2] },
1343
1343
  null
1344
1344
  ).toString();
1345
1345
  purlString = decodeURIComponent(purlString);
@@ -1347,7 +1347,7 @@ export const parseMavenTree = function (rawOutput) {
1347
1347
  group: pkgArr[0],
1348
1348
  name: pkgArr[1],
1349
1349
  version: versionStr,
1350
- qualifiers: { type: "jar" }
1350
+ qualifiers: { type: pkgArr[2] }
1351
1351
  });
1352
1352
  if (!level_trees[purlString]) {
1353
1353
  level_trees[purlString] = [];
@@ -1878,8 +1878,9 @@ export const parseBazelSkyframe = function (rawOutput) {
1878
1878
  // Remove the protocol, registry url and then file name
1879
1879
  let prefix_slice_count = 2;
1880
1880
  // Bug: #169
1881
- if (l.includes("/maven2/")) {
1882
- prefix_slice_count = 3;
1881
+ const prefix = process.env.BAZEL_STRIP_MAVEN_PREFIX || "/maven2/";
1882
+ if (l.includes(prefix)) {
1883
+ prefix_slice_count = prefix.split("/").length;
1883
1884
  }
1884
1885
  jarPathParts = jarPathParts.slice(prefix_slice_count, -1);
1885
1886
  // The last part would be the version
@@ -5203,11 +5204,18 @@ export const collectJarNS = function (jarPath, pomPathMap = {}) {
5203
5204
  if (jarFiles && jarFiles.length) {
5204
5205
  for (const jf of jarFiles) {
5205
5206
  const jarname = jf;
5206
- const pomname =
5207
+ let pomname =
5207
5208
  pomPathMap[basename(jf).replace(".jar", ".pom")] ||
5208
5209
  jarname.replace(".jar", ".pom");
5209
5210
  let pomData = undefined;
5210
5211
  let purl = undefined;
5212
+ // In some cases, the pom name might be slightly different to the jar name
5213
+ if (!existsSync(pomname)) {
5214
+ const pomSearch = getAllFiles(dirname(jf), "*.pom");
5215
+ if (pomSearch && pomSearch.length === 1) {
5216
+ pomname = pomSearch[0];
5217
+ }
5218
+ }
5211
5219
  if (existsSync(pomname)) {
5212
5220
  pomData = parsePomXml(readFileSync(pomname, "utf-8"));
5213
5221
  if (pomData) {
@@ -5221,6 +5229,52 @@ export const collectJarNS = function (jarPath, pomPathMap = {}) {
5221
5229
  );
5222
5230
  purl = purlObj.toString();
5223
5231
  }
5232
+ } else if (jf.includes(join(".m2", "repository"))) {
5233
+ // Let's try our best to construct a purl for .m2 cache entries of the form
5234
+ // .m2/repository/org/apache/logging/log4j/log4j-web/3.0.0-SNAPSHOT/log4j-web-3.0.0-SNAPSHOT.jar
5235
+ const tmpA = jf.split(join(".m2", "repository", ""));
5236
+ if (tmpA && tmpA.length) {
5237
+ let tmpJarPath = tmpA[tmpA.length - 1];
5238
+ // This would yield log4j-web-3.0.0-SNAPSHOT.jar
5239
+ const jarFileName = basename(tmpJarPath).replace(".jar", "");
5240
+ let tmpDirParts = dirname(tmpJarPath).split(_sep);
5241
+ // Retrieve the version
5242
+ let jarVersion = tmpDirParts.pop();
5243
+ if (jarVersion === "plugins") {
5244
+ jarVersion = tmpDirParts.pop();
5245
+ if (jarVersion === "eclipse") {
5246
+ jarVersion = tmpDirParts.pop();
5247
+ }
5248
+ }
5249
+ // The result would form the group name
5250
+ let jarGroupName = tmpDirParts.join(".").replace(/^\./, "");
5251
+ let qualifierType = "jar";
5252
+ // Support for p2 bundles and plugins
5253
+ // See https://github.com/CycloneDX/cyclonedx-maven-plugin/issues/137
5254
+ // See https://github.com/CycloneDX/cdxgen/pull/510#issuecomment-1702551615
5255
+ if (jarGroupName.startsWith("p2.osgi.bundle")) {
5256
+ jarGroupName = "p2.osgi.bundle";
5257
+ qualifierType = "osgi-bundle";
5258
+ } else if (jarGroupName.startsWith("p2.eclipse.plugin")) {
5259
+ jarGroupName = "p2.eclipse.plugin";
5260
+ qualifierType = "eclipse-plugin";
5261
+ } else if (jarGroupName.startsWith("p2.binary")) {
5262
+ jarGroupName = "p2.binary";
5263
+ qualifierType = "eclipse-executable";
5264
+ } else if (jarGroupName.startsWith("p2.org.eclipse.update.feature")) {
5265
+ jarGroupName = "p2.org.eclipse.update.feature";
5266
+ qualifierType = "eclipse-feature";
5267
+ }
5268
+ const purlObj = new PackageURL(
5269
+ "maven",
5270
+ jarGroupName,
5271
+ jarFileName.replace(`-${jarVersion}`, ""),
5272
+ jarVersion,
5273
+ { type: qualifierType },
5274
+ null
5275
+ );
5276
+ purl = purlObj.toString();
5277
+ }
5224
5278
  } else if (jf.includes(join(".gradle", "caches"))) {
5225
5279
  // Let's try our best to construct a purl for gradle cache entries of the form
5226
5280
  // .gradle/caches/modules-2/files-2.1/org.xmlresolver/xmlresolver/4.2.0/f4dbdaa83d636dcac91c9003ffa7fb173173fe8d/xmlresolver-4.2.0-data.jar
@@ -5228,7 +5282,7 @@ export const collectJarNS = function (jarPath, pomPathMap = {}) {
5228
5282
  if (tmpA && tmpA.length) {
5229
5283
  let tmpJarPath = tmpA[tmpA.length - 1];
5230
5284
  // This would yield xmlresolver-4.2.0-data.jar
5231
- const jarFileName = basename(tmpJarPath);
5285
+ const jarFileName = basename(tmpJarPath).replace(".jar", "");
5232
5286
  let tmpDirParts = dirname(tmpJarPath).split(_sep);
5233
5287
  // This would remove the hash from the end of the directory name
5234
5288
  tmpDirParts.pop();
@@ -5294,6 +5348,9 @@ export const convertJarNSToPackages = (jarNSMapping) => {
5294
5348
  }
5295
5349
  const name = pom.artifactId || purlObj.name;
5296
5350
  if (!name) {
5351
+ console.warn(
5352
+ `Unable to identify the metadata for ${purl}. This will be skipped.`
5353
+ );
5297
5354
  continue;
5298
5355
  }
5299
5356
  const apackage = {
package/utils.test.js CHANGED
@@ -502,10 +502,10 @@ test("parse maven tree", () => {
502
502
  group: "com.pogeyan.cmis",
503
503
  name: "copper-server",
504
504
  version: "1.15.2",
505
- qualifiers: { type: "jar" }
505
+ qualifiers: { type: "war" }
506
506
  });
507
507
  expect(parsedList.dependenciesList[0]).toEqual({
508
- ref: "pkg:maven/com.pogeyan.cmis/copper-server@1.15.2?type=jar",
508
+ ref: "pkg:maven/com.pogeyan.cmis/copper-server@1.15.2?type=war",
509
509
  dependsOn: [
510
510
  "pkg:maven/javax/javaee-web-api@7.0?type=jar",
511
511
  "pkg:maven/org.apache.chemistry.opencmis/chemistry-opencmis-server-support@1.0.0?type=jar",
@@ -553,6 +553,35 @@ test("parse maven tree", () => {
553
553
  "pkg:maven/org.apache.geode/geode-core@1.1.1?type=jar"
554
554
  ]
555
555
  });
556
+ parsedList = parseMavenTree(
557
+ readFileSync("./test/data/mvn-p2-plugin.txt", {
558
+ encoding: "utf-8"
559
+ })
560
+ );
561
+ expect(parsedList.pkgList.length).toEqual(79);
562
+ expect(parsedList.pkgList[0]).toEqual({
563
+ group: "example.group",
564
+ name: "eclipse-repository",
565
+ version: "1.0.0-SNAPSHOT",
566
+ qualifiers: { type: "eclipse-repository" }
567
+ });
568
+ expect(parsedList.pkgList[4]).toEqual({
569
+ group: "p2.eclipse.plugin",
570
+ name: "com.ibm.icu",
571
+ version: "67.1.0.v20200706-1749",
572
+ qualifiers: { type: "eclipse-plugin" }
573
+ });
574
+ expect(parsedList.dependenciesList.length).toEqual(79);
575
+ expect(parsedList.dependenciesList[0]).toEqual({
576
+ ref: "pkg:maven/example.group/eclipse-repository@1.0.0-SNAPSHOT?type=eclipse-repository",
577
+ dependsOn: [
578
+ "pkg:maven/example.group/example-feature@0.1.0-SNAPSHOT?type=eclipse-feature",
579
+ "pkg:maven/example.group/example-feature-2@0.2.0-SNAPSHOT?type=eclipse-feature",
580
+ "pkg:maven/example.group/example-bundle@0.1.0-SNAPSHOT?type=eclipse-plugin",
581
+ "pkg:maven/example.group/org.tycho.demo.rootfiles@1.0.0?type=p2-installable-unit",
582
+ "pkg:maven/example.group/org.tycho.demo.rootfiles.win@1.0.0-SNAPSHOT?type=p2-installable-unit"
583
+ ]
584
+ });
556
585
  });
557
586
 
558
587
  // Slow test