@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 +13 -4
- package/bin/cdxgen.js +8 -1
- package/bin/repl.js +154 -26
- package/data/queries-win.json +9 -3
- package/data/queries.json +3 -9
- package/display.js +44 -6
- package/index.js +21 -2
- package/package.json +6 -5
- package/utils.js +63 -6
- package/utils.test.js +31 -2
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
|
-
|
|
165
|
-
-
|
|
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
|
|
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
|
-
.
|
|
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("✅
|
|
113
|
-
console.log("💭 Type .print to view the
|
|
113
|
+
console.log("✅ BoM imported successfully.");
|
|
114
|
+
console.log("💭 Type .print to view the BoM as a table");
|
|
114
115
|
} else {
|
|
115
|
-
console.log("
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(`
|
|
288
|
+
console.log(`BoM saved successfully to ${saveToFile}`);
|
|
288
289
|
} else {
|
|
289
290
|
console.log(
|
|
290
|
-
"⚠ No
|
|
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
|
|
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("
|
|
316
|
+
console.log("BoM updated successfully.");
|
|
316
317
|
} else {
|
|
317
318
|
console.log(
|
|
318
|
-
"⚠ No
|
|
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
|
|
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
|
|
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
|
+
});
|
package/data/queries-win.json
CHANGED
|
@@ -162,10 +162,10 @@
|
|
|
162
162
|
"componentType": "data"
|
|
163
163
|
},
|
|
164
164
|
"listening_ports": {
|
|
165
|
-
"query": "SELECT
|
|
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": "
|
|
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
|
|
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": "
|
|
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
|
-
|
|
20
|
-
|
|
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 ? "
|
|
47
|
-
aservice.xTrustBoundary ? "
|
|
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
|
-
{
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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:
|
|
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
|
-
|
|
1882
|
-
|
|
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
|
-
|
|
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: "
|
|
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=
|
|
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
|