@cyclonedx/cdxgen 12.3.3 → 12.4.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.
Files changed (157) hide show
  1. package/README.md +64 -22
  2. package/bin/audit.js +21 -7
  3. package/bin/cdxgen.js +238 -116
  4. package/bin/convert.js +28 -13
  5. package/bin/hbom.js +490 -0
  6. package/bin/repl.js +580 -29
  7. package/bin/validate.js +34 -4
  8. package/bin/verify.js +40 -5
  9. package/data/README.md +298 -25
  10. package/data/component-tags.json +6 -0
  11. package/data/crypto-oid.json +16 -0
  12. package/data/predictive-audit-allowlist.json +11 -0
  13. package/data/queries-darwin.json +12 -1
  14. package/data/queries-win.json +7 -1
  15. package/data/queries.json +39 -2
  16. package/data/rules/ai-agent-governance.yaml +16 -0
  17. package/data/rules/asar-archives.yaml +150 -0
  18. package/data/rules/chrome-extensions.yaml +8 -0
  19. package/data/rules/ci-permissions.yaml +42 -18
  20. package/data/rules/container-risk.yaml +14 -7
  21. package/data/rules/dependency-sources.yaml +11 -0
  22. package/data/rules/hbom-compliance.yaml +325 -0
  23. package/data/rules/hbom-performance.yaml +307 -0
  24. package/data/rules/hbom-security.yaml +248 -0
  25. package/data/rules/host-topology.yaml +165 -0
  26. package/data/rules/mcp-servers.yaml +18 -3
  27. package/data/rules/obom-runtime.yaml +907 -22
  28. package/data/rules/package-integrity.yaml +14 -0
  29. package/data/rules/rootfs-hardening.yaml +179 -0
  30. package/data/rules/vscode-extensions.yaml +9 -0
  31. package/lib/audit/index.js +209 -8
  32. package/lib/audit/index.poku.js +332 -0
  33. package/lib/audit/reporters.js +222 -0
  34. package/lib/audit/targets.js +146 -1
  35. package/lib/audit/targets.poku.js +186 -0
  36. package/lib/cli/asar.poku.js +328 -0
  37. package/lib/cli/index.js +506 -88
  38. package/lib/cli/index.poku.js +1352 -212
  39. package/lib/evinser/evinser.js +14 -9
  40. package/lib/helpers/analyzer.js +1406 -29
  41. package/lib/helpers/analyzer.poku.js +342 -0
  42. package/lib/helpers/analyzerScope.js +712 -0
  43. package/lib/helpers/asarutils.js +1556 -0
  44. package/lib/helpers/asarutils.poku.js +443 -0
  45. package/lib/helpers/auditCategories.js +12 -0
  46. package/lib/helpers/auditCategories.poku.js +32 -0
  47. package/lib/helpers/cbomutils.js +271 -1
  48. package/lib/helpers/cbomutils.poku.js +248 -5
  49. package/lib/helpers/display.js +291 -1
  50. package/lib/helpers/display.poku.js +149 -0
  51. package/lib/helpers/evidenceUtils.js +58 -0
  52. package/lib/helpers/evidenceUtils.poku.js +54 -0
  53. package/lib/helpers/exportUtils.js +9 -0
  54. package/lib/helpers/gtfobins.js +142 -8
  55. package/lib/helpers/gtfobins.poku.js +24 -1
  56. package/lib/helpers/hbom.js +710 -0
  57. package/lib/helpers/hbom.poku.js +496 -0
  58. package/lib/helpers/hbomAnalysis.js +268 -0
  59. package/lib/helpers/hbomAnalysis.poku.js +249 -0
  60. package/lib/helpers/hbomLoader.js +35 -0
  61. package/lib/helpers/hostTopology.js +803 -0
  62. package/lib/helpers/hostTopology.poku.js +363 -0
  63. package/lib/helpers/inventoryStats.js +69 -0
  64. package/lib/helpers/inventoryStats.poku.js +86 -0
  65. package/lib/helpers/lolbas.js +19 -1
  66. package/lib/helpers/lolbas.poku.js +23 -0
  67. package/lib/helpers/osqueryTransform.js +47 -0
  68. package/lib/helpers/osqueryTransform.poku.js +47 -0
  69. package/lib/helpers/plugins.js +349 -0
  70. package/lib/helpers/plugins.poku.js +57 -0
  71. package/lib/helpers/protobom.js +156 -45
  72. package/lib/helpers/protobom.poku.js +140 -5
  73. package/lib/helpers/remote/dependency-track.js +36 -3
  74. package/lib/helpers/remote/dependency-track.poku.js +44 -0
  75. package/lib/helpers/source.js +24 -0
  76. package/lib/helpers/source.poku.js +32 -0
  77. package/lib/helpers/utils.js +1438 -93
  78. package/lib/helpers/utils.poku.js +846 -4
  79. package/lib/managers/binary.e2e.poku.js +367 -0
  80. package/lib/managers/binary.js +2293 -353
  81. package/lib/managers/binary.poku.js +1699 -1
  82. package/lib/managers/docker.js +201 -79
  83. package/lib/managers/docker.poku.js +337 -12
  84. package/lib/server/server.js +2 -27
  85. package/lib/stages/postgen/annotator.js +38 -0
  86. package/lib/stages/postgen/annotator.poku.js +107 -1
  87. package/lib/stages/postgen/auditBom.js +121 -18
  88. package/lib/stages/postgen/auditBom.poku.js +1366 -31
  89. package/lib/stages/postgen/hostTopologyAudit.poku.js +186 -0
  90. package/lib/stages/postgen/postgen.js +192 -1
  91. package/lib/stages/postgen/postgen.poku.js +321 -0
  92. package/lib/stages/postgen/ruleEngine.js +116 -0
  93. package/lib/stages/pregen/envAudit.js +14 -3
  94. package/package.json +23 -21
  95. package/types/bin/hbom.d.ts +3 -0
  96. package/types/bin/hbom.d.ts.map +1 -0
  97. package/types/bin/repl.d.ts.map +1 -1
  98. package/types/lib/audit/index.d.ts +44 -0
  99. package/types/lib/audit/index.d.ts.map +1 -1
  100. package/types/lib/audit/reporters.d.ts +16 -0
  101. package/types/lib/audit/reporters.d.ts.map +1 -1
  102. package/types/lib/audit/targets.d.ts.map +1 -1
  103. package/types/lib/cli/index.d.ts +16 -0
  104. package/types/lib/cli/index.d.ts.map +1 -1
  105. package/types/lib/evinser/evinser.d.ts +4 -0
  106. package/types/lib/evinser/evinser.d.ts.map +1 -1
  107. package/types/lib/helpers/analyzer.d.ts +33 -0
  108. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  109. package/types/lib/helpers/analyzerScope.d.ts +11 -0
  110. package/types/lib/helpers/analyzerScope.d.ts.map +1 -0
  111. package/types/lib/helpers/asarutils.d.ts +34 -0
  112. package/types/lib/helpers/asarutils.d.ts.map +1 -0
  113. package/types/lib/helpers/auditCategories.d.ts +5 -0
  114. package/types/lib/helpers/auditCategories.d.ts.map +1 -1
  115. package/types/lib/helpers/cbomutils.d.ts +3 -2
  116. package/types/lib/helpers/cbomutils.d.ts.map +1 -1
  117. package/types/lib/helpers/display.d.ts.map +1 -1
  118. package/types/lib/helpers/evidenceUtils.d.ts +8 -0
  119. package/types/lib/helpers/evidenceUtils.d.ts.map +1 -0
  120. package/types/lib/helpers/exportUtils.d.ts.map +1 -1
  121. package/types/lib/helpers/gtfobins.d.ts +8 -0
  122. package/types/lib/helpers/gtfobins.d.ts.map +1 -1
  123. package/types/lib/helpers/hbom.d.ts +49 -0
  124. package/types/lib/helpers/hbom.d.ts.map +1 -0
  125. package/types/lib/helpers/hbomAnalysis.d.ts +62 -0
  126. package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -0
  127. package/types/lib/helpers/hbomLoader.d.ts +7 -0
  128. package/types/lib/helpers/hbomLoader.d.ts.map +1 -0
  129. package/types/lib/helpers/hostTopology.d.ts +12 -0
  130. package/types/lib/helpers/hostTopology.d.ts.map +1 -0
  131. package/types/lib/helpers/inventoryStats.d.ts +11 -0
  132. package/types/lib/helpers/inventoryStats.d.ts.map +1 -0
  133. package/types/lib/helpers/lolbas.d.ts.map +1 -1
  134. package/types/lib/helpers/osqueryTransform.d.ts +3 -0
  135. package/types/lib/helpers/osqueryTransform.d.ts.map +1 -1
  136. package/types/lib/helpers/plugins.d.ts +58 -0
  137. package/types/lib/helpers/plugins.d.ts.map +1 -0
  138. package/types/lib/helpers/protobom.d.ts +3 -4
  139. package/types/lib/helpers/protobom.d.ts.map +1 -1
  140. package/types/lib/helpers/remote/dependency-track.d.ts +10 -3
  141. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
  142. package/types/lib/helpers/source.d.ts.map +1 -1
  143. package/types/lib/helpers/utils.d.ts +45 -8
  144. package/types/lib/helpers/utils.d.ts.map +1 -1
  145. package/types/lib/managers/binary.d.ts +5 -0
  146. package/types/lib/managers/binary.d.ts.map +1 -1
  147. package/types/lib/managers/docker.d.ts.map +1 -1
  148. package/types/lib/server/server.d.ts +2 -1
  149. package/types/lib/server/server.d.ts.map +1 -1
  150. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  151. package/types/lib/stages/postgen/auditBom.d.ts +26 -1
  152. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  153. package/types/lib/stages/postgen/postgen.d.ts +2 -1
  154. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  155. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  156. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  157. package/data/spdx-model-v3.0.1.jsonld +0 -15999
@@ -241,6 +241,280 @@ it("postProcess passes formulationList from bomNSData into the formulation secti
241
241
  );
242
242
  });
243
243
 
244
+ it("postProcess downgrades certificate crypto properties for spec version 1.6", () => {
245
+ const bomNSData = {
246
+ bomJson: {
247
+ bomFormat: "CycloneDX",
248
+ specVersion: "1.6",
249
+ components: [
250
+ {
251
+ type: "cryptographic-asset",
252
+ name: "demo-cert",
253
+ cryptoProperties: {
254
+ assetType: "certificate",
255
+ certificateProperties: {
256
+ serialNumber: "1234",
257
+ subjectName: "CN=demo",
258
+ issuerName: "CN=demo",
259
+ notValidBefore: "2024-01-01T00:00:00.000Z",
260
+ notValidAfter: "2034-01-01T00:00:00.000Z",
261
+ certificateFormat: "X.509",
262
+ certificateFileExtension: "crt",
263
+ fingerprint: { alg: "SHA-1", content: "a".repeat(40) },
264
+ },
265
+ },
266
+ },
267
+ ],
268
+ dependencies: [],
269
+ formulation: [
270
+ {
271
+ components: [
272
+ {
273
+ type: "cryptographic-asset",
274
+ name: "formulation-cert",
275
+ cryptoProperties: {
276
+ assetType: "certificate",
277
+ certificateProperties: {
278
+ serialNumber: "5678",
279
+ subjectName: "CN=formulation",
280
+ certificateFileExtension: "pem",
281
+ fingerprint: { alg: "SHA-1", content: "b".repeat(40) },
282
+ },
283
+ },
284
+ },
285
+ ],
286
+ },
287
+ ],
288
+ metadata: {
289
+ properties: [],
290
+ tools: {
291
+ components: [{ name: "cdxgen" }],
292
+ },
293
+ },
294
+ },
295
+ };
296
+ const result = postProcess(bomNSData, { specVersion: 1.6 });
297
+ const componentCert =
298
+ result.bomJson.components[0].cryptoProperties.certificateProperties;
299
+ const formulationCert =
300
+ result.bomJson.formulation[0].components[0].cryptoProperties
301
+ .certificateProperties;
302
+
303
+ assert.deepStrictEqual(componentCert, {
304
+ subjectName: "CN=demo",
305
+ issuerName: "CN=demo",
306
+ notValidBefore: "2024-01-01T00:00:00.000Z",
307
+ notValidAfter: "2034-01-01T00:00:00.000Z",
308
+ certificateFormat: "X.509",
309
+ certificateExtension: "crt",
310
+ });
311
+ assert.deepStrictEqual(formulationCert, {
312
+ subjectName: "CN=formulation",
313
+ certificateExtension: "pem",
314
+ });
315
+ });
316
+
317
+ it("postProcess removes remaining 1.7-only fields from metadata, components, and formulation inventories for spec version 1.6", () => {
318
+ const bomNSData = {
319
+ bomJson: {
320
+ bomFormat: "CycloneDX",
321
+ specVersion: "1.6",
322
+ components: [
323
+ {
324
+ type: "library",
325
+ name: "demo-lib",
326
+ version: "1.0.0",
327
+ isExternal: true,
328
+ patentAssertions: [{ patentNumber: "US-123" }],
329
+ versionRange: "vers:npm/>=1.0.0|<2.0.0",
330
+ },
331
+ ],
332
+ dependencies: [],
333
+ formulation: [
334
+ {
335
+ components: [
336
+ {
337
+ type: "library",
338
+ name: "formulation-lib",
339
+ version: "2.0.0",
340
+ isExternal: true,
341
+ versionRange: "vers:npm/>=2.0.0|<3.0.0",
342
+ },
343
+ ],
344
+ services: [
345
+ {
346
+ name: "formulation-service",
347
+ patentAssertions: [{ patentNumber: "US-456" }],
348
+ },
349
+ ],
350
+ },
351
+ ],
352
+ metadata: {
353
+ distributionConstraints: { tlp: "GREEN" },
354
+ component: {
355
+ type: "application",
356
+ name: "demo-app",
357
+ version: "1.0.0",
358
+ isExternal: true,
359
+ versionRange: "vers:npm/>=1.0.0|<2.0.0",
360
+ },
361
+ properties: [],
362
+ tools: {
363
+ components: [{ name: "cdxgen" }],
364
+ },
365
+ },
366
+ services: [
367
+ {
368
+ name: "demo-service",
369
+ patentAssertions: [{ patentNumber: "US-789" }],
370
+ },
371
+ ],
372
+ },
373
+ };
374
+
375
+ const result = postProcess(bomNSData, { specVersion: 1.6 });
376
+ const rootComponent = result.bomJson.components[0];
377
+ const formulationComponent = result.bomJson.formulation[0].components[0];
378
+ const rootService = result.bomJson.services[0];
379
+ const formulationService = result.bomJson.formulation[0].services[0];
380
+ const metadataComponent = result.bomJson.metadata.component;
381
+
382
+ assert.strictEqual(
383
+ result.bomJson.metadata.distributionConstraints,
384
+ undefined,
385
+ );
386
+ assert.strictEqual(rootComponent.isExternal, undefined);
387
+ assert.strictEqual(rootComponent.patentAssertions, undefined);
388
+ assert.strictEqual(rootComponent.versionRange, undefined);
389
+ assert.strictEqual(formulationComponent.isExternal, undefined);
390
+ assert.strictEqual(formulationComponent.versionRange, undefined);
391
+ assert.strictEqual(rootService.patentAssertions, undefined);
392
+ assert.strictEqual(formulationService.patentAssertions, undefined);
393
+ assert.strictEqual(metadataComponent.isExternal, undefined);
394
+ assert.strictEqual(metadataComponent.versionRange, undefined);
395
+ });
396
+
397
+ it("postProcess removes remaining 1.6-only fields from metadata, components, and formulation inventories for spec version 1.5", () => {
398
+ const bomNSData = {
399
+ bomJson: {
400
+ bomFormat: "CycloneDX",
401
+ specVersion: "1.5",
402
+ components: [
403
+ {
404
+ type: "library",
405
+ name: "demo-lib",
406
+ version: "1.0.0",
407
+ authors: [{ name: "Alice" }],
408
+ manufacturer: { name: "Acme" },
409
+ omniborId: ["gitoid:blob:sha1:abc"],
410
+ swhid: ["swh:1:rev:def"],
411
+ tags: ["demo"],
412
+ },
413
+ ],
414
+ dependencies: [],
415
+ formulation: [
416
+ {
417
+ components: [
418
+ {
419
+ type: "library",
420
+ name: "formulation-lib",
421
+ version: "2.0.0",
422
+ authors: [{ name: "Bob" }],
423
+ manufacturer: { name: "Builder" },
424
+ omniborId: ["gitoid:blob:sha1:ghi"],
425
+ swhid: ["swh:1:dir:jkl"],
426
+ tags: ["workflow"],
427
+ },
428
+ ],
429
+ services: [
430
+ {
431
+ name: "formulation-service",
432
+ tags: ["ci"],
433
+ },
434
+ ],
435
+ },
436
+ ],
437
+ metadata: {
438
+ manufacturer: { name: "BOM Factory" },
439
+ component: {
440
+ type: "application",
441
+ name: "demo-app",
442
+ version: "1.0.0",
443
+ authors: [{ name: "Carol" }],
444
+ manufacturer: { name: "Acme" },
445
+ tags: ["root"],
446
+ },
447
+ properties: [],
448
+ },
449
+ services: [
450
+ {
451
+ name: "demo-service",
452
+ tags: ["runtime"],
453
+ },
454
+ ],
455
+ },
456
+ };
457
+
458
+ const result = postProcess(bomNSData, { specVersion: 1.5 });
459
+ const rootComponent = result.bomJson.components[0];
460
+ const formulationComponent = result.bomJson.formulation[0].components[0];
461
+ const rootService = result.bomJson.services[0];
462
+ const formulationService = result.bomJson.formulation[0].services[0];
463
+ const metadataComponent = result.bomJson.metadata.component;
464
+
465
+ assert.strictEqual(result.bomJson.metadata.manufacturer, undefined);
466
+ assert.strictEqual(rootComponent.authors, undefined);
467
+ assert.strictEqual(rootComponent.manufacturer, undefined);
468
+ assert.strictEqual(rootComponent.omniborId, undefined);
469
+ assert.strictEqual(rootComponent.swhid, undefined);
470
+ assert.strictEqual(rootComponent.tags, undefined);
471
+ assert.strictEqual(formulationComponent.authors, undefined);
472
+ assert.strictEqual(formulationComponent.manufacturer, undefined);
473
+ assert.strictEqual(formulationComponent.omniborId, undefined);
474
+ assert.strictEqual(formulationComponent.swhid, undefined);
475
+ assert.strictEqual(formulationComponent.tags, undefined);
476
+ assert.strictEqual(rootService.tags, undefined);
477
+ assert.strictEqual(formulationService.tags, undefined);
478
+ assert.strictEqual(metadataComponent.authors, undefined);
479
+ assert.strictEqual(metadataComponent.manufacturer, undefined);
480
+ assert.strictEqual(metadataComponent.tags, undefined);
481
+ });
482
+
483
+ it("postProcess removes unsupported evidence occurrence details for spec version 1.5", () => {
484
+ const bomNSData = {
485
+ bomJson: {
486
+ bomFormat: "CycloneDX",
487
+ specVersion: "1.5",
488
+ components: [
489
+ {
490
+ type: "file",
491
+ name: "deviceTypeManager.js",
492
+ evidence: {
493
+ occurrences: [
494
+ {
495
+ location: "source/microservices/lib/deviceTypeManager.js",
496
+ line: 11,
497
+ offset: 2,
498
+ symbol: "deviceTypeManager",
499
+ additionalContext: "source-import",
500
+ },
501
+ ],
502
+ },
503
+ },
504
+ ],
505
+ dependencies: [],
506
+ metadata: { properties: [] },
507
+ },
508
+ };
509
+ const result = postProcess(bomNSData, { specVersion: 1.5 });
510
+
511
+ assert.deepStrictEqual(result.bomJson.components[0].evidence.occurrences, [
512
+ {
513
+ location: "source/microservices/lib/deviceTypeManager.js",
514
+ },
515
+ ]);
516
+ });
517
+
244
518
  it("postProcess merges formulation-discovered MCP config services into bomJson.services", () => {
245
519
  const tmpDir = join(tmpdir(), `cdxgen-postgen-${Date.now()}`);
246
520
  mkdirSync(join(tmpDir, ".vscode"), { recursive: true });
@@ -372,6 +646,53 @@ it("postProcess attaches releaseNotes to cdxgen metadata tool component", () =>
372
646
  }
373
647
  });
374
648
 
649
+ it("postProcess refreshes unpackaged native file inventory counts from the final BOM", () => {
650
+ const bomNSData = {
651
+ bomJson: {
652
+ bomFormat: "CycloneDX",
653
+ specVersion: "1.7",
654
+ components: [
655
+ {
656
+ name: "demo",
657
+ type: "file",
658
+ properties: [{ name: "internal:is_executable", value: "true" }],
659
+ },
660
+ {
661
+ name: "libdemo.so",
662
+ type: "file",
663
+ properties: [{ name: "internal:is_shared_library", value: "true" }],
664
+ },
665
+ ],
666
+ dependencies: [],
667
+ metadata: {
668
+ properties: [
669
+ { name: "cdx:container:unpackagedExecutableCount", value: "0" },
670
+ {
671
+ name: "cdx:container:unpackagedSharedLibraryCount",
672
+ value: "0",
673
+ },
674
+ ],
675
+ tools: {
676
+ components: [
677
+ { group: "@cyclonedx", name: "cdxgen", version: "test" },
678
+ ],
679
+ },
680
+ },
681
+ },
682
+ };
683
+
684
+ const result = postProcess(bomNSData, { specVersion: 1.7 });
685
+ assert.deepStrictEqual(
686
+ result.bomJson.metadata.properties.filter((property) =>
687
+ property.name.startsWith("cdx:container:unpackaged"),
688
+ ),
689
+ [
690
+ { name: "cdx:container:unpackagedExecutableCount", value: "1" },
691
+ { name: "cdx:container:unpackagedSharedLibraryCount", value: "1" },
692
+ ],
693
+ );
694
+ });
695
+
375
696
  it("postProcess fails for weak TLP when sensitive property values are present", () => {
376
697
  const bomNSData = {
377
698
  bomJson: {
@@ -136,6 +136,24 @@ function normalizeStandardsMetadata(rule) {
136
136
  return Object.keys(normalized).length ? normalized : undefined;
137
137
  }
138
138
 
139
+ function normalizeDryRunSupport(rule) {
140
+ const rawValue =
141
+ typeof rule?.["dry-run-support"] === "string"
142
+ ? rule["dry-run-support"]
143
+ : typeof rule?.dryRunSupport === "string"
144
+ ? rule.dryRunSupport
145
+ : undefined;
146
+ if (rawValue !== undefined && !["no", "partial", "full"].includes(rawValue)) {
147
+ if (DEBUG_MODE) {
148
+ console.warn(
149
+ `Rule ${rule?.id || "unknown"} has invalid dry-run-support '${rawValue}'; defaulting to 'partial'`,
150
+ );
151
+ }
152
+ return "partial";
153
+ }
154
+ return ["no", "partial", "full"].includes(rawValue) ? rawValue : "partial";
155
+ }
156
+
139
157
  /**
140
158
  * Helper: Check if property exists and equals expected value
141
159
  * Usage: $hasProp(component, 'cdx:foo', 'bar')
@@ -247,6 +265,103 @@ function registerCdxHelpers(expression) {
247
265
  expression.registerFunction("safeStr", (val) => {
248
266
  return val === null || val === undefined ? "" : String(val).trim();
249
267
  });
268
+ expression.registerFunction("parseSizeBytes", (val) => {
269
+ if (val === null || val === undefined || val === "") {
270
+ return null;
271
+ }
272
+ if (typeof val === "number") {
273
+ return Number.isFinite(val) ? val : null;
274
+ }
275
+ const normalizedValue = String(val).trim();
276
+ if (!normalizedValue) {
277
+ return null;
278
+ }
279
+ const matchedValue = normalizedValue.match(
280
+ /^(\d+(?:\.\d+)?)\s*([kmgtpe]?i?b)?$/iu,
281
+ );
282
+ if (!matchedValue) {
283
+ return null;
284
+ }
285
+ const numericValue = Number.parseFloat(matchedValue[1]);
286
+ if (!Number.isFinite(numericValue)) {
287
+ return null;
288
+ }
289
+ const unit = (matchedValue[2] || "").toLowerCase();
290
+ const unitMap = {
291
+ b: 1,
292
+ kb: 1000,
293
+ kib: 1024,
294
+ mb: 1000 ** 2,
295
+ mib: 1024 ** 2,
296
+ gb: 1000 ** 3,
297
+ gib: 1024 ** 3,
298
+ tb: 1000 ** 4,
299
+ tib: 1024 ** 4,
300
+ pb: 1000 ** 5,
301
+ pib: 1024 ** 5,
302
+ eb: 1000 ** 6,
303
+ eib: 1024 ** 6,
304
+ };
305
+ const multiplier = unitMap[unit || "b"];
306
+ if (!multiplier) {
307
+ return null;
308
+ }
309
+ return numericValue * multiplier;
310
+ });
311
+ expression.registerFunction("firstNonEmpty", (...values) => {
312
+ for (const value of values) {
313
+ if (value === null || value === undefined) {
314
+ continue;
315
+ }
316
+ if (Array.isArray(value)) {
317
+ const candidate = value
318
+ .map((entry) =>
319
+ entry === null || entry === undefined ? "" : String(entry).trim(),
320
+ )
321
+ .filter(Boolean)
322
+ .join(", ");
323
+ if (candidate) {
324
+ return candidate;
325
+ }
326
+ continue;
327
+ }
328
+ const candidate = String(value).trim();
329
+ if (candidate) {
330
+ return candidate;
331
+ }
332
+ }
333
+ return "";
334
+ });
335
+ expression.registerFunction("isDarwinSystemPath", (value) => {
336
+ if (typeof value !== "string") {
337
+ return false;
338
+ }
339
+ const normalized = value.trim();
340
+ return (
341
+ normalized.startsWith("/bin/") ||
342
+ normalized.startsWith("/sbin/") ||
343
+ normalized.startsWith("/System/") ||
344
+ normalized.startsWith("/usr/bin/") ||
345
+ normalized.startsWith("/usr/libexec/") ||
346
+ normalized.startsWith("/usr/sbin/") ||
347
+ normalized.startsWith("/Library/Apple/System/") ||
348
+ normalized.startsWith("/System/Volumes/Preboot/Cryptexes/")
349
+ );
350
+ });
351
+ expression.registerFunction("isWindowsUserControlledPath", (value) => {
352
+ if (typeof value !== "string") {
353
+ return false;
354
+ }
355
+ const normalized = value.trim().replaceAll("/", "\\").toLowerCase();
356
+ return (
357
+ normalized.includes("\\users\\") ||
358
+ normalized.includes("\\programdata\\") ||
359
+ normalized.includes("\\appdata\\") ||
360
+ normalized.includes("\\downloads\\") ||
361
+ normalized.includes("\\desktop\\") ||
362
+ normalized.includes("\\temp\\")
363
+ );
364
+ });
250
365
  expression.registerFunction("auditComponents", (bomJson) =>
251
366
  getAuditComponents(bomJson),
252
367
  );
@@ -331,6 +446,7 @@ export async function loadRules(rulesDir) {
331
446
  }
332
447
  rule.severity = rule.severity || "medium";
333
448
  rule.category = rule.category || "unknown";
449
+ rule.dryRunSupport = normalizeDryRunSupport(rule);
334
450
  const attack = normalizeAttackMetadata(rule);
335
451
  if (attack.tactics.length || attack.techniques.length) {
336
452
  rule.attack = attack;
@@ -1,5 +1,7 @@
1
1
  import process from "node:process";
2
2
 
3
+ import { recordEnvironmentRead } from "../../helpers/utils.js";
4
+
3
5
  const PERMISSION_FLAGS = [
4
6
  "--permission",
5
7
  "--allow-fs-read",
@@ -57,8 +59,13 @@ const PERMISSION_FLAG_PATTERNS = PERMISSION_FLAGS.map(
57
59
 
58
60
  export function auditEnvironment(env = process.env) {
59
61
  const findings = [];
60
- const nodeOptions = env.NODE_OPTIONS || "";
61
- const cdxgenNodeOptions = env.CDXGEN_NODE_OPTIONS || "";
62
+ const envSource = env === process.env ? "process.env" : "env";
63
+ const readEnv = (varName) => {
64
+ recordEnvironmentRead(varName, { source: envSource });
65
+ return env[varName];
66
+ };
67
+ const nodeOptions = readEnv("NODE_OPTIONS") || "";
68
+ const cdxgenNodeOptions = readEnv("CDXGEN_NODE_OPTIONS") || "";
62
69
  const hasPermission = PERMISSION_FLAG_PATTERNS.some((re) =>
63
70
  re.test(nodeOptions),
64
71
  );
@@ -93,7 +100,7 @@ export function auditEnvironment(env = process.env) {
93
100
  });
94
101
  }
95
102
  // NODE_TLS_REJECT_UNAUTHORIZED=0 disables TLS verification; any other value is benign.
96
- if (env.NODE_TLS_REJECT_UNAUTHORIZED === "0") {
103
+ if (readEnv("NODE_TLS_REJECT_UNAUTHORIZED") === "0") {
97
104
  findings.push({
98
105
  type: "environment-variable",
99
106
  variable: "NODE_TLS_REJECT_UNAUTHORIZED",
@@ -107,6 +114,7 @@ export function auditEnvironment(env = process.env) {
107
114
 
108
115
  for (const varName of RISKY_PRESENCE_VARS) {
109
116
  if (env[varName] != null && env[varName] !== "") {
117
+ recordEnvironmentRead(varName, { source: envSource });
110
118
  const messages = {
111
119
  NODE_PATH:
112
120
  "NODE_PATH is set and may cause unexpected modules to be loaded, enabling module-resolution poisoning.",
@@ -166,6 +174,7 @@ export function auditEnvironment(env = process.env) {
166
174
  "JDK_JAVA_OPTIONS",
167
175
  ]) {
168
176
  const jvmOptions = env[jvmVar] || "";
177
+ recordEnvironmentRead(jvmVar, { source: envSource });
169
178
  if (jvmOptions) {
170
179
  for (const pattern of JVM_CODE_EXECUTION_PATTERNS) {
171
180
  if (pattern.test(jvmOptions)) {
@@ -184,6 +193,7 @@ export function auditEnvironment(env = process.env) {
184
193
  // Proxy interception — informational
185
194
  const activeProxy = PROXY_VARS.find((v) => env[v] != null && env[v] !== "");
186
195
  if (activeProxy) {
196
+ recordEnvironmentRead(activeProxy, { source: envSource });
187
197
  findings.push({
188
198
  type: "network-interception",
189
199
  variable: activeProxy,
@@ -197,6 +207,7 @@ export function auditEnvironment(env = process.env) {
197
207
  // Credential exposure — detect any env var whose name follows a credential-naming convention.
198
208
  for (const [varName, varValue] of Object.entries(env)) {
199
209
  if (varValue && CREDENTIAL_VAR_PATTERN.test(varName)) {
210
+ recordEnvironmentRead(varName, { source: envSource });
200
211
  findings.push({
201
212
  type: "credential-exposure",
202
213
  variable: varName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyclonedx/cdxgen",
3
- "version": "12.3.3",
3
+ "version": "12.4.0",
4
4
  "description": "Creates CycloneDX Software Bill of Materials (SBOM) from source or container image",
5
5
  "keywords": [
6
6
  "sbom",
@@ -91,6 +91,7 @@
91
91
  "cdxgen-secure": "bin/cdxgen.js",
92
92
  "cdxi": "bin/repl.js",
93
93
  "evinse": "bin/evinse.js",
94
+ "hbom": "bin/hbom.js",
94
95
  "obom": "bin/cdxgen.js",
95
96
  "saasbom": "bin/cdxgen.js",
96
97
  "spdxgen": "bin/cdxgen.js"
@@ -105,7 +106,7 @@
105
106
  "index.cjs"
106
107
  ],
107
108
  "dependencies": {
108
- "@babel/parser": "7.29.2",
109
+ "@babel/parser": "7.29.3",
109
110
  "@babel/traverse": "7.29.0",
110
111
  "@iarna/toml": "2.2.5",
111
112
  "@isaacs/string-locale-compare": "1.1.0",
@@ -131,40 +132,41 @@
131
132
  "proc-log": "6.1.0",
132
133
  "properties-reader": "3.0.1",
133
134
  "read-package-json-fast": "5.0.0",
134
- "semver": "7.7.4",
135
+ "semver": "7.8.0",
135
136
  "ssri": "13.0.1",
136
- "tar": "7.5.13",
137
+ "tar": "7.5.15",
137
138
  "treeverse": "3.0.0",
138
139
  "uuid": "14.0.0",
139
140
  "walk-up-path": "4.0.0",
140
141
  "xml-js": "1.6.11",
141
- "yaml": "2.8.3",
142
+ "yaml": "2.8.4",
142
143
  "yargs": "18.0.0",
143
144
  "yoctocolors": "2.1.2"
144
145
  },
145
146
  "devDependencies": {
146
- "@biomejs/biome": "2.4.13",
147
- "esmock": "2.7.3",
147
+ "@biomejs/biome": "2.4.15",
148
+ "esmock": "2.7.5",
148
149
  "poku": "4.3.0",
149
- "sinon": "21.1.2",
150
+ "sinon": "22.0.0",
150
151
  "typescript": "6.0.3"
151
152
  },
152
153
  "optionalDependencies": {
153
154
  "@appthreat/atom": "2.5.2",
154
155
  "@appthreat/atom-parsetools": "1.1.4",
155
- "@appthreat/cdx-proto": "1.3.0",
156
- "@bufbuild/protobuf": "2.11.0",
157
- "@cdxgen/cdxgen-plugins-bin": "2.0.3",
158
- "@cdxgen/cdxgen-plugins-bin-darwin-amd64": "2.0.3",
159
- "@cdxgen/cdxgen-plugins-bin-darwin-arm64": "2.0.3",
160
- "@cdxgen/cdxgen-plugins-bin-linux-amd64": "2.0.3",
161
- "@cdxgen/cdxgen-plugins-bin-linux-arm": "2.0.3",
162
- "@cdxgen/cdxgen-plugins-bin-linux-arm64": "2.0.3",
163
- "@cdxgen/cdxgen-plugins-bin-linux-ppc64": "2.0.3",
164
- "@cdxgen/cdxgen-plugins-bin-linuxmusl-amd64": "2.0.3",
165
- "@cdxgen/cdxgen-plugins-bin-linuxmusl-arm64": "2.0.3",
166
- "@cdxgen/cdxgen-plugins-bin-windows-amd64": "2.0.3",
167
- "@cdxgen/cdxgen-plugins-bin-windows-arm64": "2.0.3",
156
+ "@appthreat/cdx-proto": "2.0.1",
157
+ "@bufbuild/protobuf": "2.12.0",
158
+ "@cdxgen/cdx-hbom": "0.5.0",
159
+ "@cdxgen/cdxgen-plugins-bin": "2.1.1",
160
+ "@cdxgen/cdxgen-plugins-bin-darwin-amd64": "2.1.1",
161
+ "@cdxgen/cdxgen-plugins-bin-darwin-arm64": "2.1.1",
162
+ "@cdxgen/cdxgen-plugins-bin-linux-amd64": "2.1.1",
163
+ "@cdxgen/cdxgen-plugins-bin-linux-arm": "2.1.1",
164
+ "@cdxgen/cdxgen-plugins-bin-linux-arm64": "2.1.1",
165
+ "@cdxgen/cdxgen-plugins-bin-linux-ppc64": "2.1.1",
166
+ "@cdxgen/cdxgen-plugins-bin-linuxmusl-amd64": "2.1.1",
167
+ "@cdxgen/cdxgen-plugins-bin-linuxmusl-arm64": "2.1.1",
168
+ "@cdxgen/cdxgen-plugins-bin-windows-amd64": "2.1.1",
169
+ "@cdxgen/cdxgen-plugins-bin-windows-arm64": "2.1.1",
168
170
  "body-parser": "2.2.2",
169
171
  "compression": "1.8.1",
170
172
  "connect": "3.7.0",
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=hbom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hbom.d.ts","sourceRoot":"","sources":["../../bin/hbom.js"],"names":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../../bin/repl.js"],"names":[],"mappings":";AA4OO,kDAwDN"}
1
+ {"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../../bin/repl.js"],"names":[],"mappings":";AA0WO,kDAmEN"}
@@ -22,6 +22,50 @@ export function loadInputBoms(options: object): {
22
22
  source: string;
23
23
  bomJson: object;
24
24
  }[];
25
+ export function runDirectBomAuditFromBoms(inputBoms: any, options?: {}): Promise<{
26
+ auditMode: string;
27
+ generatedAt: string;
28
+ inputs: any;
29
+ results: {
30
+ auditOptions: {
31
+ bomAuditCategories: any;
32
+ bomAuditMinSeverity: any;
33
+ bomAuditRulesDir: any;
34
+ };
35
+ bomFormat: any;
36
+ findings: any[];
37
+ serialNumber: any;
38
+ source: any;
39
+ specVersion: any;
40
+ status: string;
41
+ summary: {
42
+ findingsBySeverity: {
43
+ critical: number;
44
+ high: number;
45
+ low: number;
46
+ medium: number;
47
+ };
48
+ findingsCount: number;
49
+ maxSeverity: string;
50
+ };
51
+ }[];
52
+ summary: {
53
+ findingsBySeverity: {
54
+ critical: number;
55
+ high: number;
56
+ low: number;
57
+ medium: number;
58
+ };
59
+ inputBomCount: any;
60
+ maxSeverity: string;
61
+ totalFindings: number;
62
+ bomsWithFindings: number;
63
+ };
64
+ tool: {
65
+ name: string;
66
+ version: string;
67
+ };
68
+ }>;
25
69
  /**
26
70
  * Build low-noise provenance-aware contextual findings from the root BOM target.
27
71
  *