@cdxgen/cdxgen 13.4.0 → 13.5.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 (208) hide show
  1. package/README.md +7 -7
  2. package/bin/cdxgen-secure.js +15 -0
  3. package/bin/cdxgen.js +25 -5
  4. package/bin/evinse.js +5 -0
  5. package/bin/hbom.js +15 -7
  6. package/bin/repl.js +29 -0
  7. package/data/cbomosdb-queries.json +13 -1
  8. package/data/component-tags.json +8 -0
  9. package/data/queries-darwin.json +25 -1
  10. package/data/queries-win.json +19 -1
  11. package/data/queries.json +41 -0
  12. package/data/remediations.json +283 -1
  13. package/data/rules/cbom-security.yaml +235 -4
  14. package/data/rules/go-binaries.yaml +46 -0
  15. package/data/rules/golem-go.yaml +80 -0
  16. package/data/rules/hbom-compliance.yaml +40 -22
  17. package/data/rules/hbom-performance.yaml +7 -1
  18. package/data/rules/host-topology.yaml +1 -1
  19. package/data/rules/obom-runtime.yaml +31 -2
  20. package/data/rules/php-runtime.yaml +143 -0
  21. package/lib/audit/index.js +14 -0
  22. package/lib/cli/bomAssembly.js +28 -3
  23. package/lib/cli/cliOptions.js +110 -3
  24. package/lib/cli/index.js +73 -4
  25. package/lib/cli/jvmBom.js +126 -20
  26. package/lib/cli/managedBom.js +711 -71
  27. package/lib/cli/nativeBom.js +225 -28
  28. package/lib/core/env.js +30 -8
  29. package/lib/core/fs.js +28 -19
  30. package/lib/core/httpClient.js +74 -45
  31. package/lib/core/projectConfig.js +1 -0
  32. package/lib/core/projectTooling.js +97 -0
  33. package/lib/core/propertySanitizer.js +72 -0
  34. package/lib/ecosystems/golem.js +550 -6
  35. package/lib/ecosystems/gradleutils.js +30 -20
  36. package/lib/ecosystems/parsers-dotnet.js +117 -18
  37. package/lib/ecosystems/parsers-go.js +1090 -367
  38. package/lib/ecosystems/parsers-jvm.js +259 -17
  39. package/lib/ecosystems/parsers-misc.js +229 -44
  40. package/lib/ecosystems/parsers-python.js +11 -10
  41. package/lib/ecosystems/rubyutils.js +6 -3
  42. package/lib/ecosystems/rusi.js +10 -0
  43. package/lib/ecosystems/scalacliutils.js +2 -1
  44. package/lib/ecosystems/scalasem.js +3 -1
  45. package/lib/evinser/evinser.js +126 -18
  46. package/lib/evinser/swiftsem.js +68 -5
  47. package/lib/inventory/agentSkillSpec.js +17 -0
  48. package/lib/inventory/apkInstalledDb.js +243 -0
  49. package/lib/inventory/atomUtils.js +33 -0
  50. package/lib/inventory/auditCategories.js +3 -3
  51. package/lib/inventory/cacheDir.js +52 -9
  52. package/lib/inventory/certificates.js +2371 -0
  53. package/lib/inventory/communityAiConfigParser.js +4 -9
  54. package/lib/inventory/deps.js +6 -4
  55. package/lib/inventory/display.js +200 -3
  56. package/lib/inventory/dosai.js +500 -58
  57. package/lib/inventory/dynamic.js +45 -3
  58. package/lib/inventory/envcontext.js +168 -5
  59. package/lib/inventory/evidenceUtils.js +436 -23
  60. package/lib/inventory/formulationParsers.js +3 -2
  61. package/lib/inventory/goBuildInfo.js +140 -0
  62. package/lib/inventory/hbom.js +67 -7
  63. package/lib/inventory/hbomAnalysis.js +16 -0
  64. package/lib/inventory/hostTopology.js +21 -0
  65. package/lib/inventory/osPackagePurl.js +244 -0
  66. package/lib/inventory/osPackageResolver.js +89 -87
  67. package/lib/inventory/osinfo.js +79 -31
  68. package/lib/inventory/osqueryTransform.js +114 -5
  69. package/lib/inventory/phpBuildInfo.js +487 -0
  70. package/lib/inventory/phpHostProbe.js +641 -0
  71. package/lib/inventory/rootfsCerts.js +426 -0
  72. package/lib/inventory/source.js +207 -3
  73. package/lib/inventory/spdx.js +144 -0
  74. package/lib/inventory/traceRunner.js +5 -1
  75. package/lib/inventory/tracedCrypto.js +129 -0
  76. package/lib/managers/binary.js +529 -183
  77. package/lib/managers/docker.js +166 -12
  78. package/lib/parsers/email.js +277 -0
  79. package/lib/parsers/largeJson.js +34 -2
  80. package/lib/server/server.js +186 -1
  81. package/lib/stages/postgen/annotator.js +17 -1
  82. package/lib/stages/postgen/introspection/reflect.js +4 -1
  83. package/lib/stages/postgen/postgen.js +5 -5
  84. package/lib/stages/postgen/ruleEngine.js +58 -1
  85. package/lib/stages/postgen/specVersionCompat.js +30 -0
  86. package/lib/stages/pregen/pregen.js +117 -5
  87. package/lib/validator/bomValidator.js +26 -5
  88. package/lib/validator/generated/runtime.mjs +13 -0
  89. package/lib/validator/generated/validate-1.6.mjs +26 -7
  90. package/lib/validator/generated/validate-1.7.mjs +31 -12
  91. package/lib/validator/generated/validate-2.0.mjs +30 -11
  92. package/package.json +17 -17
  93. package/types/bin/cdxgen-secure.d.ts +3 -0
  94. package/types/bin/cdxgen-secure.d.ts.map +1 -0
  95. package/types/bin/repl.d.ts.map +1 -1
  96. package/types/lib/audit/index.d.ts.map +1 -1
  97. package/types/lib/cli/bomAssembly.d.ts +9 -2
  98. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  99. package/types/lib/cli/cliOptions.d.ts +43 -0
  100. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  101. package/types/lib/cli/index.d.ts.map +1 -1
  102. package/types/lib/cli/jvmBom.d.ts +20 -0
  103. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  104. package/types/lib/cli/managedBom.d.ts.map +1 -1
  105. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  106. package/types/lib/core/env.d.ts +18 -2
  107. package/types/lib/core/env.d.ts.map +1 -1
  108. package/types/lib/core/fs.d.ts.map +1 -1
  109. package/types/lib/core/httpClient.d.ts.map +1 -1
  110. package/types/lib/core/projectConfig.d.ts.map +1 -1
  111. package/types/lib/core/projectTooling.d.ts +41 -0
  112. package/types/lib/core/projectTooling.d.ts.map +1 -0
  113. package/types/lib/core/propertySanitizer.d.ts +26 -0
  114. package/types/lib/core/propertySanitizer.d.ts.map +1 -1
  115. package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
  116. package/types/lib/ecosystems/golem.d.ts +81 -1
  117. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  118. package/types/lib/ecosystems/gradleutils.d.ts +10 -8
  119. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  120. package/types/lib/ecosystems/parsers-dotnet.d.ts +40 -4
  121. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  122. package/types/lib/ecosystems/parsers-go.d.ts +85 -13
  123. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  124. package/types/lib/ecosystems/parsers-jvm.d.ts +28 -2
  125. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  126. package/types/lib/ecosystems/parsers-misc.d.ts +18 -58
  127. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  128. package/types/lib/ecosystems/parsers-python.d.ts +4 -3
  129. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  130. package/types/lib/ecosystems/pythonutils.d.ts +1 -1
  131. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  132. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  133. package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
  134. package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
  135. package/types/lib/evinser/evinser.d.ts +25 -0
  136. package/types/lib/evinser/evinser.d.ts.map +1 -1
  137. package/types/lib/evinser/swiftsem.d.ts +10 -0
  138. package/types/lib/evinser/swiftsem.d.ts.map +1 -1
  139. package/types/lib/inventory/agentSkillSpec.d.ts +13 -0
  140. package/types/lib/inventory/agentSkillSpec.d.ts.map +1 -0
  141. package/types/lib/inventory/apkInstalledDb.d.ts +42 -0
  142. package/types/lib/inventory/apkInstalledDb.d.ts.map +1 -0
  143. package/types/lib/inventory/atomUtils.d.ts +14 -0
  144. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  145. package/types/lib/inventory/cacheDir.d.ts +12 -4
  146. package/types/lib/inventory/cacheDir.d.ts.map +1 -1
  147. package/types/lib/inventory/certificates.d.ts +205 -0
  148. package/types/lib/inventory/certificates.d.ts.map +1 -0
  149. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  150. package/types/lib/inventory/deps.d.ts +2 -1
  151. package/types/lib/inventory/deps.d.ts.map +1 -1
  152. package/types/lib/inventory/display.d.ts +18 -0
  153. package/types/lib/inventory/display.d.ts.map +1 -1
  154. package/types/lib/inventory/dosai.d.ts +145 -6
  155. package/types/lib/inventory/dosai.d.ts.map +1 -1
  156. package/types/lib/inventory/dynamic.d.ts +6 -1
  157. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  158. package/types/lib/inventory/envcontext.d.ts +40 -2
  159. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  160. package/types/lib/inventory/evidenceUtils.d.ts +29 -10
  161. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  162. package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
  163. package/types/lib/inventory/goBuildInfo.d.ts +10 -0
  164. package/types/lib/inventory/goBuildInfo.d.ts.map +1 -0
  165. package/types/lib/inventory/hbom.d.ts.map +1 -1
  166. package/types/lib/inventory/hbomAnalysis.d.ts +10 -0
  167. package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -1
  168. package/types/lib/inventory/hostTopology.d.ts.map +1 -1
  169. package/types/lib/inventory/osPackagePurl.d.ts +99 -0
  170. package/types/lib/inventory/osPackagePurl.d.ts.map +1 -0
  171. package/types/lib/inventory/osPackageResolver.d.ts +6 -2
  172. package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
  173. package/types/lib/inventory/osinfo.d.ts +31 -6
  174. package/types/lib/inventory/osinfo.d.ts.map +1 -1
  175. package/types/lib/inventory/osqueryTransform.d.ts +51 -0
  176. package/types/lib/inventory/osqueryTransform.d.ts.map +1 -1
  177. package/types/lib/inventory/phpBuildInfo.d.ts +142 -0
  178. package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -0
  179. package/types/lib/inventory/phpHostProbe.d.ts +35 -0
  180. package/types/lib/inventory/phpHostProbe.d.ts.map +1 -0
  181. package/types/lib/inventory/rootfsCerts.d.ts +27 -0
  182. package/types/lib/inventory/rootfsCerts.d.ts.map +1 -0
  183. package/types/lib/inventory/source.d.ts +28 -0
  184. package/types/lib/inventory/source.d.ts.map +1 -1
  185. package/types/lib/inventory/spdx.d.ts +15 -0
  186. package/types/lib/inventory/spdx.d.ts.map +1 -1
  187. package/types/lib/inventory/traceRunner.d.ts +4 -1
  188. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  189. package/types/lib/inventory/tracedCrypto.d.ts +22 -0
  190. package/types/lib/inventory/tracedCrypto.d.ts.map +1 -1
  191. package/types/lib/managers/binary.d.ts +10 -0
  192. package/types/lib/managers/binary.d.ts.map +1 -1
  193. package/types/lib/managers/docker.d.ts +12 -0
  194. package/types/lib/managers/docker.d.ts.map +1 -1
  195. package/types/lib/parsers/email.d.ts +19 -0
  196. package/types/lib/parsers/email.d.ts.map +1 -0
  197. package/types/lib/parsers/largeJson.d.ts +13 -0
  198. package/types/lib/parsers/largeJson.d.ts.map +1 -1
  199. package/types/lib/server/server.d.ts +8 -0
  200. package/types/lib/server/server.d.ts.map +1 -1
  201. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  202. package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -1
  203. package/types/lib/stages/postgen/ruleEngine.d.ts +7 -4
  204. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  205. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  206. package/types/lib/stages/pregen/pregen.d.ts +14 -1
  207. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  208. package/types/lib/validator/bomValidator.d.ts.map +1 -1
@@ -23,11 +23,16 @@ import {
23
23
  import {
24
24
  checksumFile,
25
25
  getAllFiles,
26
+ getTmpDir,
26
27
  safeExistsSync,
27
28
  safeSpawnSync,
28
29
  } from "../core/fs.js";
29
30
  import { thoughtLog } from "../core/logger.js";
30
31
  import { dirNameStr, isMac } from "../core/paths.js";
32
+ import {
33
+ isProjectToolingAllowed,
34
+ reportProjectToolingSkipped,
35
+ } from "../core/projectTooling.js";
31
36
  import { readCrateArchiveValues } from "../ecosystems/cargoCache.js";
32
37
  import {
33
38
  buildDependentPurl,
@@ -55,6 +60,7 @@ import {
55
60
  } from "../ecosystems/parsers-elm.js";
56
61
  import { parseGleamProject } from "../ecosystems/parsers-gleam.js";
57
62
  import {
63
+ compareGoVersions,
58
64
  isGoModWhyNotNeeded,
59
65
  parseGoListDep,
60
66
  parseGoModData,
@@ -198,6 +204,30 @@ export function getCargoCacheDir() {
198
204
  );
199
205
  }
200
206
 
207
+ /**
208
+ * Add a Go module of the project, a nested go.mod or a go.work member, under
209
+ * the parent component. A module that is already the parent, one of its
210
+ * subcomponents, or a component (a directory replacement inside the project)
211
+ * is not added again, so no bom-ref appears twice.
212
+ *
213
+ * @param {Object} parentComponent The project's parent component
214
+ * @param {Object} moduleComponent The module to add
215
+ * @param {Object[]} pkgList Components found so far
216
+ */
217
+ function addGoSubComponent(parentComponent, moduleComponent, pkgList) {
218
+ const ref = moduleComponent?.["bom-ref"];
219
+ if (
220
+ !ref ||
221
+ ref === parentComponent["bom-ref"] ||
222
+ parentComponent.components?.some((c) => c["bom-ref"] === ref) ||
223
+ pkgList.some((c) => c["bom-ref"] === ref)
224
+ ) {
225
+ return;
226
+ }
227
+ parentComponent.components = parentComponent.components || [];
228
+ parentComponent.components.push(moduleComponent);
229
+ }
230
+
201
231
  /**
202
232
  * Function to create bom string for Go projects
203
233
  *
@@ -313,6 +343,20 @@ export async function createGoBom(path, options) {
313
343
  }
314
344
  }
315
345
  }
346
+ // go mod why answers for a module path, but go.sum also names versions
347
+ // that only took part in version selection. Only a version whose content
348
+ // go.sum holds a checksum for was ever downloaded, and of those, MVS
349
+ // builds the highest.
350
+ const builtVersions = {};
351
+ for (const apkg of pkgList) {
352
+ const built = builtVersions[apkg.name];
353
+ if (
354
+ apkg._integrity &&
355
+ (!built || compareGoVersions(apkg.version, built) > 0)
356
+ ) {
357
+ builtVersions[apkg.name] = apkg.version;
358
+ }
359
+ }
316
360
  // A module go reports as unneeded is optional, but only when every module
317
361
  // was checked: once go mod why fails, the answers are incomplete, and a
318
362
  // tool failure is not evidence that a module is unused.
@@ -321,7 +365,9 @@ export async function createGoBom(path, options) {
321
365
  continue;
322
366
  }
323
367
  if (requiredModules.has(apkg.name)) {
324
- apkg.scope = "required";
368
+ if (builtVersions[apkg.name] === apkg.version) {
369
+ apkg.scope = "required";
370
+ }
325
371
  } else if (!circuitBreak && unneededModules.has(apkg.name)) {
326
372
  apkg.scope = "optional";
327
373
  }
@@ -384,6 +430,9 @@ export async function createGoBom(path, options) {
384
430
  });
385
431
 
386
432
  let rootParentComponent = null;
433
+ // Whether the go toolchain resolved a module graph. A module without
434
+ // dependencies resolves to no components, and that is a complete answer.
435
+ let resolvedByToolchain = false;
387
436
  // Use the go list -deps and go mod why commands to generate a good quality BOM for non-docker invocations
388
437
  if (
389
438
  !hasAnyProjectType(["docker", "oci", "container", "os"], options, false)
@@ -403,13 +452,15 @@ export async function createGoBom(path, options) {
403
452
  console.log("Executing go list -deps in", basePath);
404
453
  }
405
454
  // TODO: Replacing this with -json gives us more interesting data points such as GoFiles, Imports, and Deps
455
+ // The fields after Dir name the module content checksum and, for a
456
+ // module a replace directive swapped out, the replacement Go builds.
406
457
  let result = safeSpawnSync(
407
458
  "go",
408
459
  [
409
460
  "list",
410
461
  "-deps",
411
462
  "-f",
412
- "'{{with .Module}}{{.Path}}|{{.Version}}|{{.Indirect}}|{{.GoMod}}|{{.GoVersion}}|{{.Main}}|{{.Time}}|{{.Deprecated}}|{{.GoModSum}}|{{.Dir}}{{end}}'",
463
+ "'{{with .Module}}{{.Path}}|{{.Version}}|{{.Indirect}}|{{.GoMod}}|{{.GoVersion}}|{{.Main}}|{{.Time}}|{{.Deprecated}}|{{.GoModSum}}|{{.Dir}}|{{.Sum}}|{{with .Replace}}{{.Path}}{{end}}|{{with .Replace}}{{.Version}}{{end}}|{{with .Replace}}{{.Sum}}{{end}}{{end}}'",
413
464
  "./...",
414
465
  ],
415
466
  {
@@ -453,7 +504,9 @@ export async function createGoBom(path, options) {
453
504
  const stdout = result.stdout;
454
505
  if (stdout) {
455
506
  let cmdOutput = Buffer.from(stdout).toString();
456
- const retMap = await parseGoListDep(cmdOutput, gosumMap);
507
+ const retMap = await parseGoListDep(cmdOutput, gosumMap, {
508
+ mainModuleDir: basePath,
509
+ });
457
510
  if (retMap.pkgList?.length) {
458
511
  pkgList = pkgList.concat(retMap.pkgList);
459
512
  }
@@ -462,6 +515,7 @@ export async function createGoBom(path, options) {
462
515
  retMap.parentComponent &&
463
516
  Object.keys(retMap.parentComponent).length
464
517
  ) {
518
+ resolvedByToolchain = true;
465
519
  if (!rootParentComponent) {
466
520
  // First (shallowest) module becomes the root
467
521
  rootParentComponent = retMap.parentComponent;
@@ -469,12 +523,18 @@ export async function createGoBom(path, options) {
469
523
  parentComponent = rootParentComponent;
470
524
  } else {
471
525
  // Subsequent modules become subcomponents
472
- if (!parentComponent.components) {
473
- parentComponent.components = [];
474
- }
475
- parentComponent.components.push(retMap.parentComponent);
526
+ addGoSubComponent(
527
+ parentComponent,
528
+ retMap.parentComponent,
529
+ pkgList,
530
+ );
476
531
  }
477
532
  }
533
+ // The other members of a go.work workspace are modules of the same
534
+ // project, which no registry holds.
535
+ for (const workspaceModule of retMap.workspaceModules || []) {
536
+ addGoSubComponent(parentComponent, workspaceModule, pkgList);
537
+ }
478
538
  if (DEBUG_MODE) {
479
539
  console.log("Executing go mod graph in", basePath);
480
540
  }
@@ -524,8 +584,11 @@ export async function createGoBom(path, options) {
524
584
  }
525
585
  // Retain the parent component hierarchy
526
586
  if (Object.keys(retMap.parentComponent).length) {
527
- parentComponent.components = parentComponent.components || [];
528
- parentComponent.components.push(retMap.parentComponent);
587
+ addGoSubComponent(
588
+ parentComponent,
589
+ retMap.parentComponent,
590
+ pkgList,
591
+ );
529
592
  }
530
593
  }
531
594
  } else {
@@ -538,6 +601,7 @@ export async function createGoBom(path, options) {
538
601
  shell: isWin,
539
602
  });
540
603
  if (result.stdout) {
604
+ resolvedByToolchain = true;
541
605
  const cmdOutput = Buffer.from(result.stdout).toString();
542
606
  // The arguments to parseGoModGraph are slightly different to force inclusion of all packages
543
607
  const retMap = await parseGoModGraph(
@@ -566,8 +630,11 @@ export async function createGoBom(path, options) {
566
630
  parentComponent = rootParentComponent;
567
631
  } else {
568
632
  // Subsequent modules become subcomponents
569
- parentComponent.components = parentComponent.components || [];
570
- parentComponent.components.push(retMap.parentComponent);
633
+ addGoSubComponent(
634
+ parentComponent,
635
+ retMap.parentComponent,
636
+ pkgList,
637
+ );
571
638
  }
572
639
  }
573
640
  } else {
@@ -589,7 +656,7 @@ export async function createGoBom(path, options) {
589
656
  }
590
657
  }
591
658
  }
592
- if (pkgList.length && !shouldManuallyParse) {
659
+ if ((pkgList.length || resolvedByToolchain) && !shouldManuallyParse) {
593
660
  return buildBomNSData(options, pkgList, "golang", {
594
661
  dependencies,
595
662
  parentComponent,
@@ -732,11 +799,19 @@ function cargoAnswersVersion(cwd) {
732
799
  * @param {string} [filterPlatform] Target triple to resolve the graph for
733
800
  * @returns {object|undefined} Parsed `cargo metadata` output
734
801
  */
735
- function runCargoMetadata(cargoTomlFile, filterPlatform) {
736
- const basePath = dirname(cargoTomlFile);
802
+ function runCargoMetadata(cargoTomlFile, filterPlatform, options = {}) {
803
+ // cargo reads `.cargo/config.toml` (rustc, rustc-wrapper, [env]) and rustup
804
+ // reads `rust-toolchain.toml` from the working directory, and `cargo
805
+ // metadata` runs rustc to learn the target. Without permission to run the
806
+ // project's tooling, cargo starts outside the project and stays offline.
807
+ const toolingAllowed = isProjectToolingAllowed(options);
808
+ const basePath = toolingAllowed ? dirname(cargoTomlFile) : getTmpDir();
737
809
  const platformArgs = filterPlatform
738
810
  ? ["--filter-platform", filterPlatform]
739
811
  : [];
812
+ if (!toolingAllowed) {
813
+ platformArgs.push("--offline");
814
+ }
740
815
  // `--locked` keeps the user's lock file untouched; a workspace that is out of
741
816
  // date with its lock file needs the retry without it.
742
817
  for (const lockArgs of [["--locked"], []]) {
@@ -810,7 +885,7 @@ function annotateCargoDependencyKinds(
810
885
  let anyMetadata = false;
811
886
  let everyMetadata = true;
812
887
  for (const cargoTomlFile of cargoFiles) {
813
- const metadata = runCargoMetadata(cargoTomlFile);
888
+ const metadata = runCargoMetadata(cargoTomlFile, undefined, options);
814
889
  if (!metadata) {
815
890
  everyMetadata = false;
816
891
  continue;
@@ -824,7 +899,11 @@ function annotateCargoDependencyKinds(
824
899
  visitedWorkspaceRoots.add(workspaceRoot);
825
900
  anyMetadata = true;
826
901
  if (CARGO_TARGET) {
827
- const targetMetadata = runCargoMetadata(cargoTomlFile, CARGO_TARGET);
902
+ const targetMetadata = runCargoMetadata(
903
+ cargoTomlFile,
904
+ CARGO_TARGET,
905
+ options,
906
+ );
828
907
  if (targetMetadata) {
829
908
  for (const key of parseCargoMetadataResolve(targetMetadata)
830
909
  .resolvedKeys) {
@@ -962,13 +1041,31 @@ export async function createRustBom(path, options) {
962
1041
  `${options.multiProject ? "**/" : ""}Cargo.toml`,
963
1042
  options,
964
1043
  );
1044
+ // `cargo check` compiles the project, running its build scripts and
1045
+ // procedural macros, so --deep runs it only when the project's own tooling
1046
+ // may run.
1047
+ const cargoToolingAllowed = isProjectToolingAllowed(options);
1048
+ if (
1049
+ options.deep &&
1050
+ cargoFiles.length &&
1051
+ !cargoToolingAllowed &&
1052
+ !hasAnyProjectType(["oci"], options, false)
1053
+ ) {
1054
+ reportProjectToolingSkipped(
1055
+ options,
1056
+ null,
1057
+ "Skipping 'cargo check' since dependency installation is disabled. Build scripts and procedural macros are not run, so the files each crate uses are not recorded.",
1058
+ );
1059
+ }
965
1060
  // Attempt to build or generate lock files automatically
966
1061
  for (const f of cargoFiles) {
967
1062
  // If there are no cargo.lock files, we can attempt a cargo install when
968
1063
  // options.deep is true or options.lifecycle == build or post-build with installDeps
969
1064
  // Why the need for installDeps? It currently defaults to true, so let's obey if someone wants no installs
970
1065
  if (
971
- (options.deep && !hasAnyProjectType(["oci"], options, false)) ||
1066
+ (options.deep &&
1067
+ cargoToolingAllowed &&
1068
+ !hasAnyProjectType(["oci"], options, false)) ||
972
1069
  (options.installDeps &&
973
1070
  !safeExistsSync(f.replace(".toml", ".lock")) &&
974
1071
  ["build", "post-build"].includes(options.lifecycle))
@@ -1870,11 +1967,35 @@ export function createClojureBom(path, options) {
1870
1967
  if (readEnvironmentVariable("LEIN_ARGS")) {
1871
1968
  LEIN_ARGS = readEnvironmentVariable("LEIN_ARGS").split(" ");
1872
1969
  }
1970
+ // `lein deps :tree-data` evaluates project.clj, which is a Clojure
1971
+ // program, so it belongs to the dependency-tooling surface disabled by
1972
+ // --no-install-deps and the pre-build lifecycle.
1973
+ const leinToolingAllowed = isProjectToolingAllowed(options);
1873
1974
  for (const f of leinFiles) {
1874
1975
  if (DEBUG_MODE) {
1875
1976
  console.log(`Parsing ${f}`);
1876
1977
  }
1877
1978
  const basePath = dirname(f);
1979
+ if (!leinToolingAllowed) {
1980
+ reportProjectToolingSkipped(
1981
+ options,
1982
+ "clojure.lein.tooling-disabled",
1983
+ "Skipping 'lein deps :tree-data' since dependency installation is disabled. Only the dependencies declared in project.clj will be used.",
1984
+ {
1985
+ ecosystem: "clojure",
1986
+ tool: "lein",
1987
+ impact: "transitive-deps",
1988
+ detail:
1989
+ "Dependency installation is disabled, so project.clj was not evaluated and only its directly declared dependencies are available.",
1990
+ },
1991
+ );
1992
+ const leinData = readFileSync(f, { encoding: "utf-8" });
1993
+ const dlist = parseLeiningenData(leinData);
1994
+ if (dlist?.length) {
1995
+ pkgList = pkgList.concat(dlist);
1996
+ }
1997
+ continue;
1998
+ }
1878
1999
  console.log("Executing", LEIN_CMD, "in", basePath);
1879
2000
  const result = safeSpawnSync(LEIN_CMD, LEIN_ARGS, {
1880
2001
  cwd: basePath,
@@ -2235,7 +2356,39 @@ export async function createSwiftBom(path, options) {
2235
2356
  );
2236
2357
  }
2237
2358
  if (swiftFiles.length) {
2359
+ // A SwiftPM manifest is a Swift program, so `swift package dump-package`
2360
+ // and `show-dependencies` execute the scanned project's code (and fetch
2361
+ // missing dependencies). They belong to the dependency-tooling surface
2362
+ // disabled by --no-install-deps and the pre-build lifecycle; the BOM then
2363
+ // comes from Package.resolved alone.
2364
+ const swiftToolingAllowed = isProjectToolingAllowed(options);
2365
+ if (!swiftToolingAllowed) {
2366
+ reportProjectToolingSkipped(
2367
+ options,
2368
+ "swift.tooling-disabled",
2369
+ "Skipping 'swift package show-dependencies' since dependency installation is disabled. Only the packages in Package.resolved will be used.",
2370
+ {
2371
+ ecosystem: "swift",
2372
+ tool: "swift",
2373
+ impact: "transitive-deps",
2374
+ detail:
2375
+ "Dependency installation is disabled, so the Package.swift manifest was not evaluated and only the packages from Package.resolved are available.",
2376
+ },
2377
+ );
2378
+ // The manifest is not run, so a project without Package.resolved still
2379
+ // gets a parent component named after its directory.
2380
+ if (!Object.keys(parentComponent).length) {
2381
+ parentComponent = createDefaultParentComponent(
2382
+ swiftFiles[0],
2383
+ "swift",
2384
+ options,
2385
+ );
2386
+ }
2387
+ }
2238
2388
  for (const f of swiftFiles) {
2389
+ if (!swiftToolingAllowed) {
2390
+ break;
2391
+ }
2239
2392
  const basePath = dirname(f);
2240
2393
  if (completedPath.includes(basePath)) {
2241
2394
  continue;
@@ -2461,10 +2614,33 @@ export async function createCocoaBom(path, options) {
2461
2614
  allDependencies.set(name, podDependencies);
2462
2615
  }
2463
2616
  const targetDependencies = new Map();
2464
- if (
2465
- !readEnvironmentVariable("COCOA_INCLUDED_TARGETS") &&
2466
- !readEnvironmentVariable("COCOA_EXCLUDED_TARGETS")
2467
- ) {
2617
+ // `pod ipc podfile-json` evaluates the Podfile, which is a Ruby program,
2618
+ // so per-target filtering through it belongs to the dependency-tooling
2619
+ // surface disabled by --no-install-deps and the pre-build lifecycle. A
2620
+ // gated scan then keeps every pod the lockfile resolved (the union across
2621
+ // targets) instead of the configured target subset.
2622
+ const cocoaTargetsConfigured =
2623
+ readEnvironmentVariable("COCOA_INCLUDED_TARGETS") ||
2624
+ readEnvironmentVariable("COCOA_EXCLUDED_TARGETS");
2625
+ const cocoaToolingAllowed = isProjectToolingAllowed(options);
2626
+ // True only when target names the Podfile actually resolved are known, so
2627
+ // the include/exclude filters below can be applied to real targets.
2628
+ let targetFilteringActive = false;
2629
+ if (!cocoaTargetsConfigured || !cocoaToolingAllowed) {
2630
+ if (cocoaTargetsConfigured) {
2631
+ reportProjectToolingSkipped(
2632
+ options,
2633
+ "cocoa.podfile-json.tooling-disabled",
2634
+ "Skipping 'pod ipc podfile-json' since dependency installation is disabled. Every pod from Podfile.lock is included without per-target filtering.",
2635
+ {
2636
+ ecosystem: "cocoa",
2637
+ tool: "pod",
2638
+ impact: "components",
2639
+ detail:
2640
+ "Dependency installation is disabled, so the Podfile was not evaluated and target filtering fell back to all pods resolved in Podfile.lock.",
2641
+ },
2642
+ );
2643
+ }
2468
2644
  targetDependencies.set("Pods", podfileLock["DEPENDENCIES"]);
2469
2645
  } else {
2470
2646
  const result = executePodCommand(
@@ -2472,18 +2648,39 @@ export async function createCocoaBom(path, options) {
2472
2648
  projectPath,
2473
2649
  options,
2474
2650
  );
2475
- const resolvedPodFile = JSON.parse(result.stdout);
2476
- parsePodfileTargets(
2477
- resolvedPodFile["target_definitions"][0],
2478
- targetDependencies,
2479
- );
2651
+ let resolvedPodFile;
2652
+ try {
2653
+ resolvedPodFile = JSON.parse(result.stdout);
2654
+ } catch (_e) {
2655
+ resolvedPodFile = undefined;
2656
+ }
2657
+ if (resolvedPodFile?.target_definitions?.length) {
2658
+ parsePodfileTargets(
2659
+ resolvedPodFile["target_definitions"][0],
2660
+ targetDependencies,
2661
+ );
2662
+ targetFilteringActive = true;
2663
+ } else {
2664
+ // pod failed or printed nothing usable (it refuses to run as root,
2665
+ // for one), and an unparseable Podfile used to abort the whole scan.
2666
+ // Keep every locked pod rather than none.
2667
+ console.log(
2668
+ "The Podfile could not be resolved with CocoaPods, so all pods from Podfile.lock are included without per-target filtering.",
2669
+ );
2670
+ targetDependencies.set("Pods", podfileLock["DEPENDENCIES"]);
2671
+ }
2480
2672
  }
2481
2673
  const usedTargets = new Set(
2482
- readEnvironmentVariable("COCOA_INCLUDED_TARGETS")
2674
+ targetFilteringActive && readEnvironmentVariable("COCOA_INCLUDED_TARGETS")
2483
2675
  ? readEnvironmentVariable("COCOA_INCLUDED_TARGETS").split(",")
2484
2676
  : targetDependencies.keys(),
2485
2677
  );
2486
- if (readEnvironmentVariable("COCOA_EXCLUDED_TARGETS")) {
2678
+ // Without the resolved Podfile there are no target names to exclude, and
2679
+ // excluding the lockfile's stand-in "Pods" target would drop every pod.
2680
+ if (
2681
+ targetFilteringActive &&
2682
+ readEnvironmentVariable("COCOA_EXCLUDED_TARGETS")
2683
+ ) {
2487
2684
  readEnvironmentVariable("COCOA_EXCLUDED_TARGETS")
2488
2685
  .split(",")
2489
2686
  .forEach((excludedTarget) => {
package/lib/core/env.js CHANGED
@@ -1,4 +1,4 @@
1
- import { join } from "node:path";
1
+ import { join, win32 } from "node:path";
2
2
 
3
3
  import {
4
4
  buildReadCountSuffix,
@@ -301,6 +301,13 @@ export const SWIFT_CMD = readEnvironmentVariable("SWIFT_CMD") || "swift";
301
301
  /** Resolved Ruby executable command (RUBY_CMD env var, or "ruby"). */
302
302
  export const RUBY_CMD = readEnvironmentVariable("RUBY_CMD") || "ruby";
303
303
 
304
+ /** Resolved PHP executable command (PHP_CMD env var, or "php"). */
305
+ export const PHP_CMD = readEnvironmentVariable("PHP_CMD") || "php";
306
+
307
+ /** Resolved Composer executable command (COMPOSER_CMD env var, or "composer"). */
308
+ export const COMPOSER_CMD =
309
+ readEnvironmentVariable("COMPOSER_CMD") || "composer";
310
+
304
311
  /** Python package components that can be excluded from the generated BOM. */
305
312
  export const PYTHON_EXCLUDED_COMPONENTS = [
306
313
  "pip",
@@ -707,23 +714,38 @@ export function hasAnyProjectType(projectTypes, options, defaultStatus = true) {
707
714
  return shouldInclude;
708
715
  }
709
716
 
717
+ /**
718
+ * Extract the lowercase command name from an invoked command path, without
719
+ * directories or a trailing `.js`, `.cjs`, `.mjs` or `.exe` extension. Both
720
+ * `/` and `\\` separators are handled, so a Windows path is inspected the
721
+ * same way on any host.
722
+ *
723
+ * @param {string} [commandPath] Invoked command path or name
724
+ * @returns {string} Lowercase command name
725
+ */
726
+ export function getCommandName(commandPath) {
727
+ return win32
728
+ .basename(`${commandPath || ""}`)
729
+ .toLowerCase()
730
+ .replace(/\.(?:[cm]?js|exe)$/u, "");
731
+ }
732
+
710
733
  /**
711
734
  * Determine whether the predictive dependency audit should run for the current
712
735
  * CLI invocation.
713
736
  *
714
- * OBOM-focused runs (`obom` or explicit `-t os` / OS aliases only) should keep
715
- * the direct BOM audit findings but skip the predictive dependency audit.
737
+ * OBOM and HBOM runs (commands named `obom` or `hbom`, including `hbom-slim`)
738
+ * and explicit `-t os` / OS / hardware project types alone keep the direct BOM
739
+ * audit findings but skip the predictive dependency audit. Only the command
740
+ * name is tested, never the directories leading to it.
716
741
  *
717
742
  * @param {object} options CLI options
718
743
  * @param {string} [commandPath] Invoked command path or name
719
744
  * @returns {boolean} True when predictive dependency audit should run
720
745
  */
721
746
  export function shouldRunPredictiveBomAudit(options, commandPath) {
722
- const normalizedCommandPath = `${commandPath || ""}`.toLowerCase();
723
- if (normalizedCommandPath.includes("obom")) {
724
- return false;
725
- }
726
- if (normalizedCommandPath.includes("hbom")) {
747
+ const commandName = getCommandName(commandPath);
748
+ if (commandName.startsWith("obom") || commandName.startsWith("hbom")) {
727
749
  return false;
728
750
  }
729
751
  const projectTypes = Array.isArray(options?.projectType)
package/lib/core/fs.js CHANGED
@@ -522,8 +522,13 @@ function isWindowsShellHijackRisk(command, options) {
522
522
  const absCwd = resolve(cwd);
523
523
  for (const candidate of candidates) {
524
524
  const candidatePath = path.join(absCwd, candidate);
525
- if (existsSync(candidatePath)) {
526
- return true;
525
+ // cmd.exe only runs files, so a directory named like the tool is harmless.
526
+ try {
527
+ if (statSync(candidatePath).isFile()) {
528
+ return true;
529
+ }
530
+ } catch (_err) {
531
+ // Not present.
527
532
  }
528
533
  }
529
534
  return false;
@@ -831,24 +836,28 @@ export function safeSpawnSync(command, args, options) {
831
836
  error: new Error("No execute permission"),
832
837
  };
833
838
  }
839
+ // cmd.exe looks in the working directory before PATH, so a scanned project
840
+ // could otherwise replace the tool cdxgen means to run. This holds outside
841
+ // secure mode too, since --no-install-deps scans still run version probes
842
+ // and read-only tools with the project as the working directory.
843
+ if (isWindowsShellHijackRisk(command, options)) {
844
+ const blockedReason = `${command} matches local file in cwd (Windows shell hijack risk)`;
845
+ console.warn(`\x1b[1;31mSecurity Alert: ${blockedReason}\x1b[0m`);
846
+ recordActivity({
847
+ kind: activityDescriptor.kind,
848
+ ...activityDescriptor.metadata,
849
+ reason: blockedReason,
850
+ status: "blocked",
851
+ target: activityDescriptor.target,
852
+ });
853
+ return {
854
+ status: 1,
855
+ stdout: undefined,
856
+ stderr: undefined,
857
+ error: new Error(blockedReason),
858
+ };
859
+ }
834
860
  if (isSecureMode) {
835
- if (isWindowsShellHijackRisk(command, options)) {
836
- const blockedReason = `${command} matches local file in cwd (Windows shell hijack risk)`;
837
- console.warn(`\x1b[1;31mSecurity Alert: ${blockedReason}\x1b[0m`);
838
- recordActivity({
839
- kind: activityDescriptor.kind,
840
- ...activityDescriptor.metadata,
841
- reason: blockedReason,
842
- status: "blocked",
843
- target: activityDescriptor.target,
844
- });
845
- return {
846
- status: 1,
847
- stdout: undefined,
848
- stderr: undefined,
849
- error: new Error(blockedReason),
850
- };
851
- }
852
861
  if (options?.cwd && options.cwd !== resolve(options.cwd)) {
853
862
  if (DEBUG_MODE) {
854
863
  console.log(