@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
@@ -19,6 +19,7 @@ import {
19
19
  METADATA_PROPERTY_CONFIG_KEYS,
20
20
  parseMetadataProperties,
21
21
  } from "../core/metadataProperties.js";
22
+ import { toCamel } from "../core/paths.js";
22
23
  import {
23
24
  DEFAULT_CDX_SPEC_VERSION,
24
25
  normalizeCycloneDxComponentTypeFilter,
@@ -124,6 +125,106 @@ export function isUserProvided(value, yaDefault) {
124
125
  return value !== undefined && value !== yaDefault;
125
126
  }
126
127
 
128
+ /**
129
+ * The value the operator gave an option on the command line or through its
130
+ * CDXGEN_* environment variable. yargs also fills options in from defaults
131
+ * and from a config file, which cannot be told apart in its output.
132
+ *
133
+ * @param {string} option kebab-case option name
134
+ * @param {object} [context]
135
+ * @param {string[]} [context.argv] Raw command-line arguments
136
+ * @param {object} [context.env] Environment variables
137
+ * @param {boolean} [context.boolean] Read the option as a boolean flag
138
+ * @returns {string|boolean|undefined} The operator's value, or undefined
139
+ */
140
+ export function operatorOptionChoice(
141
+ option,
142
+ { argv = process.argv.slice(2), env = process.env, boolean = false } = {},
143
+ ) {
144
+ const spellings = [...new Set([option, toCamel(option)])];
145
+ const toBoolean = (value) =>
146
+ ["true", "1"].includes(`${value}`.trim().toLowerCase());
147
+ let choice;
148
+ for (let i = 0; i < argv.length; i++) {
149
+ const arg = `${argv[i]}`;
150
+ if (arg === "--") {
151
+ break;
152
+ }
153
+ for (const name of spellings) {
154
+ if (boolean && arg === `--no-${name}`) {
155
+ choice = false;
156
+ } else if (arg === `--${name}`) {
157
+ const next = argv[i + 1] === undefined ? "" : `${argv[i + 1]}`;
158
+ if (!boolean) {
159
+ choice = next;
160
+ i++;
161
+ } else if (["true", "false"].includes(next.toLowerCase())) {
162
+ choice = toBoolean(next);
163
+ i++;
164
+ } else {
165
+ choice = true;
166
+ }
167
+ } else if (arg.startsWith(`--${name}=`)) {
168
+ const value = arg.slice(name.length + 3);
169
+ choice = boolean ? toBoolean(value) : value;
170
+ }
171
+ }
172
+ }
173
+ if (choice !== undefined) {
174
+ return choice;
175
+ }
176
+ const envValue =
177
+ env[`CDXGEN_${option.toUpperCase().replaceAll("-", "_")}`] ?? "";
178
+ if (`${envValue}`.trim() === "") {
179
+ return undefined;
180
+ }
181
+ return boolean ? toBoolean(envValue) : `${envValue}`;
182
+ }
183
+
184
+ /**
185
+ * Make the operator's choice about running the scanned project's own code
186
+ * final. A config file is read from the working directory, which is usually
187
+ * the scanned project, so it carries that project's trust. Neither the config
188
+ * file nor a profile or lifecycle may turn installs on when the operator
189
+ * disabled them, and secure mode keeps installs off unless the operator
190
+ * passes --install-deps. Introspection keeps the project's build tools
191
+ * running with installs off, which is likewise the operator's decision, so
192
+ * introspection asked for only by the config file does not do that.
193
+ *
194
+ * **Mutates `options` in place.**
195
+ *
196
+ * @param {object} options The options object, after applyAdvancedOptions
197
+ * @param {object} [context]
198
+ * @param {object} [context.projectConfig] Sanitized config file contents
199
+ * @param {boolean} [context.isSecureMode] Whether cdxgen runs in secure mode
200
+ * @param {string[]} [context.argv] Raw command-line arguments
201
+ * @param {object} [context.env] Environment variables
202
+ * @returns {void}
203
+ */
204
+ export function applyOperatorToolingChoice(options, context = {}) {
205
+ const { projectConfig, argv, env } = context;
206
+ const secureMode = context.isSecureMode ?? isSecureMode;
207
+ const installChoice = operatorOptionChoice("install-deps", {
208
+ argv,
209
+ env,
210
+ boolean: true,
211
+ });
212
+ if (installChoice === false || (secureMode && installChoice !== true)) {
213
+ options.installDeps = false;
214
+ }
215
+ const configAsksIntrospect =
216
+ ["true", "1"].includes(`${projectConfig?.introspect}`.toLowerCase()) ||
217
+ projectConfig?.profile === "introspect";
218
+ if (
219
+ options.introspect === true &&
220
+ configAsksIntrospect &&
221
+ operatorOptionChoice("introspect", { argv, env, boolean: true }) !== true &&
222
+ operatorOptionChoice("profile", { argv, env }) !== "introspect"
223
+ ) {
224
+ options.introspectFromConfigFile = true;
225
+ }
226
+ }
227
+
127
228
  /**
128
229
  * Combine the custom metadata properties of every source (discussion 4391):
129
230
  * the `--metadata-property` flag, the CDXGEN_METADATA_PROPERTY and
@@ -588,7 +689,9 @@ export function applyAdvancedOptions(options, context = {}) {
588
689
  options.deep = true;
589
690
  options.evidence = false;
590
691
  options.includeCrypto = false;
591
- options.installDeps = !secureMode;
692
+ // A profile may skip installs but never turn them back on once they
693
+ // were disabled, which would run the scanned project's code.
694
+ options.installDeps = !secureMode && options.installDeps !== false;
592
695
  break;
593
696
  case "deep-learning":
594
697
  case "ml-deep":
@@ -596,7 +699,7 @@ export function applyAdvancedOptions(options, context = {}) {
596
699
  options.deep = true;
597
700
  options.evidence = true;
598
701
  options.includeCrypto = true;
599
- options.installDeps = !secureMode;
702
+ options.installDeps = !secureMode && options.installDeps !== false;
600
703
  options.bomAudit = true;
601
704
  break;
602
705
  default:
@@ -619,7 +722,11 @@ export function applyAdvancedOptions(options, context = {}) {
619
722
  "PREVIEW: post-build lifecycle SBOM generation is supported only for limited project types.",
620
723
  });
621
724
  }
622
- options.installDeps = true;
725
+ // The project is already built, so installs are on only where they
726
+ // would have been anyway: a disabled install stays disabled.
727
+ if (options.installDeps !== false) {
728
+ options.installDeps = true;
729
+ }
623
730
  break;
624
731
  default:
625
732
  break;
package/lib/cli/index.js CHANGED
@@ -51,6 +51,7 @@ import {
51
51
  getDependencyTrackBomApiUrl,
52
52
  } from "../helpers/remote/dependency-track.js";
53
53
  import { optionIncludesAiInventoryProjectType } from "../inventory/aiInventory.js";
54
+ import { readApkInstalledDb } from "../inventory/apkInstalledDb.js";
54
55
  import { isCycloneDxComponentTypeEnabled } from "../inventory/bomUtils.js";
55
56
  import {
56
57
  mergeDependencies,
@@ -58,7 +59,10 @@ import {
58
59
  trimComponents,
59
60
  } from "../inventory/depsUtils.js";
60
61
  import { buildDynamicComponents } from "../inventory/dynamic.js";
61
- import { convertOSQueryResults } from "../inventory/evidenceUtils.js";
62
+ import {
63
+ convertApkPackages,
64
+ convertOSQueryResults,
65
+ } from "../inventory/evidenceUtils.js";
62
66
  import {
63
67
  createHbomDocument,
64
68
  ensureHbomRuntimeSupport,
@@ -67,6 +71,7 @@ import {
67
71
  } from "../inventory/hbom.js";
68
72
  import { mergeHostInventoryBoms } from "../inventory/hostTopology.js";
69
73
  import { orderParentCandidates } from "../inventory/parentSelection.js";
74
+ import { probeHostPhp } from "../inventory/phpHostProbe.js";
70
75
  // Imported directly rather than through the utils.js barrel: purl validity is
71
76
  // checked in a hot path and the barrel is already the root of several import
72
77
  // cycles.
@@ -86,6 +91,7 @@ import {
86
91
  exportArchive,
87
92
  exportImage,
88
93
  getPkgPathList,
94
+ isFollowableManifestImage,
89
95
  isImageReference,
90
96
  parseImageName,
91
97
  } from "../managers/docker.js";
@@ -272,6 +278,37 @@ export function createOSBom(_path, options) {
272
278
  );
273
279
  }
274
280
  } // for
281
+ // osquery has no apk table, so the packages of an Alpine, Wolfi or Chainguard
282
+ // host come from the apk installed database, after the osquery categories and
283
+ // as a category of their own. They are all components: the OS itself is the
284
+ // first row of the first osquery category, as before.
285
+ if (_platform() === "linux") {
286
+ const apkInventory = readApkInstalledDb();
287
+ if (apkInventory.state === "unreadable") {
288
+ recordDegradation("os.apk-installed-db.unreadable", {
289
+ ecosystem: "generic",
290
+ tool: "apk",
291
+ impact: "components",
292
+ path: apkInventory.path,
293
+ detail:
294
+ "The apk installed database exists but could not be read in full, so the packages it lists are missing from the OBOM.",
295
+ });
296
+ }
297
+ if (apkInventory.packages.length) {
298
+ pkgList = pkgList.concat(
299
+ convertApkPackages(apkInventory.packages).sort((a, b) =>
300
+ a.name.localeCompare(b.name),
301
+ ),
302
+ );
303
+ }
304
+ }
305
+ // The package tables cannot see a PHP that no package manager installed. Its
306
+ // edges stay out: an OBOM has no dependency graph, and a partial one makes
307
+ // post-processing infer the OS root's dependencies from these files alone.
308
+ const hostPhp = probeHostPhp({ executeOsQuery, packageComponents: pkgList });
309
+ if (hostPhp.components.length) {
310
+ pkgList = pkgList.concat(hostPhp.components);
311
+ }
275
312
  const hostTrustInventory = enrichOSComponentsWithTrustData(pkgList);
276
313
  if (hostTrustInventory?.components?.length) {
277
314
  pkgList = hostTrustInventory.components;
@@ -515,11 +552,14 @@ export async function createContainerSpecLikeBom(path, options) {
515
552
  if (imageBomData?.bomJson?.components) {
516
553
  components = components.concat(imageBomData.bomJson.components);
517
554
  }
555
+ doneimages.push(img.image);
556
+ if (!isFollowableManifestImage(img.image, imageObj)) {
557
+ continue;
558
+ }
518
559
  const bomData = await createBom(img.image, {
519
560
  specVersion: options.specVersion,
520
561
  projectType: ["oci"],
521
562
  });
522
- doneimages.push(img.image);
523
563
  if (bomData) {
524
564
  if (bomData.components?.length) {
525
565
  // Inject properties
@@ -734,6 +774,7 @@ export async function createMultiXBom(pathList, options) {
734
774
  let dependencies = [];
735
775
  let bomData;
736
776
  let formulationList = [];
777
+ let metadataProperties = [];
737
778
  let parentComponent = determineParentComponent(options) || {};
738
779
  let parentSubComponents = [];
739
780
  const setProjectTypeActivityContext = (projectType, sourcePath) => {
@@ -867,6 +908,7 @@ export async function createMultiXBom(pathList, options) {
867
908
  dependencies: [],
868
909
  options: { ...sharedOptions },
869
910
  parentSubComponents: [],
911
+ metadataProperties: [],
870
912
  };
871
913
  }
872
914
  };
@@ -879,6 +921,7 @@ export async function createMultiXBom(pathList, options) {
879
921
  let components = [];
880
922
  let dependencies = [];
881
923
  let parentSubComponents = [];
924
+ let metadataProperties = [];
882
925
  // Each builder detects the project of its own ecosystem. A builder that sets
883
926
  // options.parentComponent to force its project into its own BOM must not
884
927
  // make that project the next builder's too, or the next ecosystem's project
@@ -1163,6 +1206,12 @@ export async function createMultiXBom(pathList, options) {
1163
1206
  }
1164
1207
  setProjectTypeActivityContext("php", path);
1165
1208
  bomData = await withOwnParent(() => createPHPBom(path, options));
1209
+ // A lock with no packages can still hold the project's platform data.
1210
+ if (bomData?.metadataProperties?.length) {
1211
+ metadataProperties = metadataProperties.concat(
1212
+ bomData.metadataProperties,
1213
+ );
1214
+ }
1166
1215
  if (bomData?.bomJson?.components?.length) {
1167
1216
  thoughtLog(
1168
1217
  `I found ${bomData.bomJson.components.length} php packages.`,
@@ -1236,7 +1285,12 @@ export async function createMultiXBom(pathList, options) {
1236
1285
  }
1237
1286
  setProjectTypeActivityContext("csharp", path);
1238
1287
  bomData = await withOwnParent(() => createCsharpBom(path, options));
1239
- if (bomData?.bomJson?.components?.length) {
1288
+ // Every project the scan read has a dependency entry of its own, so a
1289
+ // project that uses no packages is kept along with its parent.
1290
+ if (
1291
+ bomData?.bomJson?.components?.length ||
1292
+ bomData?.bomJson?.dependencies?.length
1293
+ ) {
1240
1294
  thoughtLog(
1241
1295
  `There are ${bomData.bomJson.components.length} csharp packages.`,
1242
1296
  );
@@ -1753,9 +1807,18 @@ export async function createMultiXBom(pathList, options) {
1753
1807
  );
1754
1808
  }
1755
1809
  components = components.concat(bomData.bomJson.components);
1810
+ if (bomData.dependencies?.length) {
1811
+ dependencies = mergeDependencies(dependencies, bomData.dependencies);
1812
+ }
1756
1813
  }
1757
1814
  }
1758
- return { components, dependencies, options, parentSubComponents };
1815
+ return {
1816
+ components,
1817
+ dependencies,
1818
+ options,
1819
+ parentSubComponents,
1820
+ metadataProperties,
1821
+ };
1759
1822
  };
1760
1823
  const pathResults = [];
1761
1824
  // A container scan walks hundreds of paths, so the phase carries a bar and
@@ -1818,6 +1881,9 @@ export async function createMultiXBom(pathList, options) {
1818
1881
  parentSubComponents = parentSubComponents.concat(
1819
1882
  pathResult.parentSubComponents,
1820
1883
  );
1884
+ metadataProperties = metadataProperties.concat(
1885
+ pathResult.metadataProperties,
1886
+ );
1821
1887
  if (pathResult.options.services?.length) {
1822
1888
  sharedOptions.services = mergeServices(
1823
1889
  sharedOptions.services || [],
@@ -1907,6 +1973,7 @@ export async function createMultiXBom(pathList, options) {
1907
1973
  components,
1908
1974
  parentComponent,
1909
1975
  dependencies,
1976
+ metadataProperties,
1910
1977
  );
1911
1978
  if (formulationList.length) {
1912
1979
  multiResult.formulationList = formulationList;
@@ -1978,6 +2045,7 @@ export async function createDynamicBom(path, options) {
1978
2045
  const {
1979
2046
  components: pkgList,
1980
2047
  services: traceServices,
2048
+ dependencies: traceDependencies,
1981
2049
  dryRunSummary,
1982
2050
  } = await buildDynamicComponents(commandStr, workingDir, traceOptions);
1983
2051
 
@@ -1986,6 +2054,7 @@ export async function createDynamicBom(path, options) {
1986
2054
  projectType: "dynamic",
1987
2055
  filename: commandStr,
1988
2056
  services: traceServices,
2057
+ dependencies: traceDependencies || [],
1989
2058
  });
1990
2059
  // A dry run denies every side effect, so the attempted-operation counts are
1991
2060
  // the result. Only counts are recorded, never the paths or targets.
package/lib/cli/jvmBom.js CHANGED
@@ -7,11 +7,7 @@ import process from "node:process";
7
7
  import { build } from "@cdxgen/cdx-purl";
8
8
  import { gte, lte } from "semver";
9
9
 
10
- import {
11
- DEBUG_MODE,
12
- isSecureMode,
13
- readEnvironmentVariable,
14
- } from "../core/activity.js";
10
+ import { DEBUG_MODE, readEnvironmentVariable } from "../core/activity.js";
15
11
  import { commandOutputText, recordDegradation } from "../core/buildLedger.js";
16
12
  import {
17
13
  deferFailOnError,
@@ -39,6 +35,10 @@ import {
39
35
  } from "../core/fs.js";
40
36
  import { thoughtLog } from "../core/logger.js";
41
37
  import { dirNameStr } from "../core/paths.js";
38
+ import {
39
+ isSecureModeActive,
40
+ reportProjectToolingSkipped,
41
+ } from "../core/projectTooling.js";
42
42
  import { extractJarArchive, getMvnMetadata } from "../ecosystems/ecosystems.js";
43
43
  import {
44
44
  buildGradleCommandArguments,
@@ -51,6 +51,7 @@ import {
51
51
  getMillCommand,
52
52
  getMillVersion,
53
53
  hasGradleInvocationFailure,
54
+ isProjectToolingAllowed,
54
55
  parseGradleDep,
55
56
  parseGradleInfoLogsForUrls,
56
57
  parseGradleProperties,
@@ -72,6 +73,7 @@ import {
72
73
  parseMavenTreeJson,
73
74
  parseMillDependency,
74
75
  parsePom,
76
+ parsePomWithLocalParents,
75
77
  readMavenModuleTrees,
76
78
  } from "../ecosystems/parsers-jvm.js";
77
79
  import {
@@ -495,6 +497,63 @@ export function discoverSbtProjectsFromCmd(
495
497
  return discoverSbtProjects(basePath);
496
498
  }
497
499
 
500
+ /**
501
+ * Read one pom.xml without running Maven: the module it declares and its
502
+ * direct dependencies. Versions resolve from the pom's properties and
503
+ * dependencyManagement and from parent poms inside the scanned directory.
504
+ * Versions that an imported BOM or a parent outside the directory manages
505
+ * stay empty, and transitive dependencies are unknown.
506
+ *
507
+ * No dependency edges are produced. The build-fidelity rules (BF-JVM-001,
508
+ * BF-GEN-001) recognise a pom read without Maven by its missing graph, and a
509
+ * graph of direct edges alone would pass for a resolved one.
510
+ *
511
+ * @param {string} pomFile pom.xml to read
512
+ * @param {string} rootPath Scanned directory
513
+ * @param {Map} [cache] Parent pom results shared by the poms of one scan
514
+ * @returns {{moduleComponent: Object|undefined, pkgList: Object[]}}
515
+ */
516
+ export function readMavenModuleStatically(pomFile, rootPath, cache) {
517
+ let pomMap;
518
+ try {
519
+ pomMap = parsePomWithLocalParents(pomFile, rootPath, cache);
520
+ } catch (_err) {
521
+ return { moduleComponent: undefined, pkgList: [] };
522
+ }
523
+ const pkgList = [];
524
+ for (const dep of pomMap?.dependencies || []) {
525
+ const purl = mavenPurl(
526
+ dep.group || undefined,
527
+ dep.name,
528
+ dep.version || undefined,
529
+ dep.qualifiers,
530
+ );
531
+ pkgList.push(
532
+ purl ? { ...dep, purl, "bom-ref": decodeURIComponent(purl) } : dep,
533
+ );
534
+ }
535
+ const { artifactId, groupId, packaging, version } = pomMap?.properties || {};
536
+ const modulePurl = artifactId
537
+ ? mavenPurl(groupId || undefined, artifactId, version || undefined, {
538
+ type: packaging || "jar",
539
+ })
540
+ : undefined;
541
+ if (!modulePurl) {
542
+ return { moduleComponent: undefined, pkgList };
543
+ }
544
+ const moduleComponent = {
545
+ group: groupId || "",
546
+ name: artifactId,
547
+ version: version || "",
548
+ qualifiers: { type: packaging || "jar" },
549
+ type: "application",
550
+ purl: modulePurl,
551
+ "bom-ref": decodeURIComponent(modulePurl),
552
+ properties: [{ name: "internal:SrcFile", value: pomFile }],
553
+ };
554
+ return { moduleComponent, pkgList };
555
+ }
556
+
498
557
  /**
499
558
  * Function to create bom string for Java projects
500
559
  *
@@ -654,6 +713,55 @@ export async function createJavaBom(path, options) {
654
713
  }
655
714
  const firstPom = pomFiles.length ? pomFiles[0] : undefined;
656
715
  let mavenCmd = getMavenCommand(path, path, options);
716
+ // Executing Maven evaluates the pom's build section, loads `.mvn/`
717
+ // configuration such as jvm.config or extensions.xml, and resolves
718
+ // build extensions, so it belongs to the dependency-tooling surface
719
+ // disabled by --no-install-deps and the pre-build lifecycle. The scan
720
+ // then keeps only the direct dependencies each pom declares.
721
+ const mavenToolingAllowed = isProjectToolingAllowed(options);
722
+ if (!mavenToolingAllowed) {
723
+ reportProjectToolingSkipped(
724
+ options,
725
+ "jvm.maven.tooling-disabled",
726
+ "Skipping Maven since dependency installation is disabled. Only the direct dependencies declared in pom.xml files will be used.",
727
+ {
728
+ ecosystem: "java",
729
+ tool: "maven",
730
+ impact: "transitive-deps",
731
+ detail:
732
+ "Dependency installation is disabled, so Maven was not run and only the direct dependencies parsed from pom.xml are available.",
733
+ },
734
+ );
735
+ }
736
+ // Poms read without Maven: what their local parents define is shared
737
+ // across the scan, and each pom adds its module and direct dependencies.
738
+ const staticPomCache = new Map();
739
+ const addStaticMavenModule = (pomFile) => {
740
+ const staticModule = readMavenModuleStatically(
741
+ pomFile,
742
+ path,
743
+ staticPomCache,
744
+ );
745
+ if (staticModule.pkgList.length) {
746
+ pkgList = pkgList.concat(staticModule.pkgList);
747
+ }
748
+ const moduleComponent = staticModule.moduleComponent;
749
+ if (!moduleComponent) {
750
+ return;
751
+ }
752
+ if (!Object.keys(parentComponent).length) {
753
+ parentComponent = moduleComponent;
754
+ parentComponent.components = [];
755
+ } else if (
756
+ parentComponent["bom-ref"] !== moduleComponent["bom-ref"] &&
757
+ !parentComponent.components?.some(
758
+ (c) => c["bom-ref"] === moduleComponent["bom-ref"],
759
+ )
760
+ ) {
761
+ parentComponent.components = parentComponent.components || [];
762
+ parentComponent.components.push(moduleComponent);
763
+ }
764
+ };
657
765
  // Pom files whose own tree a recursive run of their aggregator already
658
766
  // returned. Each is skipped when its turn comes, so a reactor is run
659
767
  // once from its aggregator instead of once per member.
@@ -668,6 +776,12 @@ export async function createJavaBom(path, options) {
668
776
  }
669
777
  continue;
670
778
  }
779
+ // Without Maven every pom is read on its own, Quarkus modules too:
780
+ // the plugin run that would have covered them does not happen.
781
+ if (!mavenToolingAllowed) {
782
+ addStaticMavenModule(f);
783
+ continue;
784
+ }
671
785
  if (
672
786
  isQuarkus &&
673
787
  !options.deep &&
@@ -688,10 +802,7 @@ export async function createJavaBom(path, options) {
688
802
  `Parsing ${f} without Maven: the repository rate limited Maven in this scan.`,
689
803
  );
690
804
  }
691
- const dlist = parsePom(f)?.dependencies || [];
692
- if (dlist.length) {
693
- pkgList = pkgList.concat(dlist);
694
- }
805
+ addStaticMavenModule(f);
695
806
  continue;
696
807
  }
697
808
  if (mavenCmd?.endsWith("mvn")) {
@@ -701,7 +812,7 @@ export async function createJavaBom(path, options) {
701
812
  // user's own settings.xml, with its mirrors and credentials, still
702
813
  // applies on top of it.
703
814
  const projectSettings = projectSettingsArgs(basePath, {
704
- secureMode: isSecureMode,
815
+ secureMode: isSecureModeActive(),
705
816
  });
706
817
  if (projectSettings.args.length) {
707
818
  console.log(
@@ -1031,11 +1142,7 @@ export async function createJavaBom(path, options) {
1031
1142
  thoughtLog(
1032
1143
  "**MAVEN**: There appear to be build errors, so the SBOM will be incomplete.",
1033
1144
  );
1034
- const pomMap = parsePom(f);
1035
- const dlist = pomMap?.dependencies || [];
1036
- if (dlist.length) {
1037
- pkgList = pkgList.concat(dlist);
1038
- }
1145
+ addStaticMavenModule(f);
1039
1146
  } else {
1040
1147
  // The text trees exist only when the JSON run failed and was
1041
1148
  // retried. Each reactor module has its own tree, and a member
@@ -1312,8 +1419,7 @@ export async function createJavaBom(path, options) {
1312
1419
  // `gradle properties` evaluates the project's build scripts, so it is
1313
1420
  // part of the dependency-tooling surface gated by --no-install-deps
1314
1421
  // (and the pre-build lifecycle).
1315
- const gradleToolingAllowed =
1316
- options.installDeps !== false || options.introspect === true;
1422
+ const gradleToolingAllowed = isProjectToolingAllowed(options);
1317
1423
  if (gradleToolingAllowed) {
1318
1424
  parallelPropTaskOut = executeParallelGradleProperties(
1319
1425
  gradleRootPath,
@@ -1941,7 +2047,7 @@ export async function createJavaBom(path, options) {
1941
2047
  pkgList = pkgList.concat(dlist);
1942
2048
  }
1943
2049
  }
1944
- } else if (options.installDeps === false && !options.introspect) {
2050
+ } else if (!isProjectToolingAllowed(options)) {
1945
2051
  // Executing sbt evaluates the project's build definition (arbitrary
1946
2052
  // Scala), so it belongs to the dependency-tooling surface disabled
1947
2053
  // by --no-install-deps and the pre-build lifecycle.
@@ -2363,7 +2469,7 @@ export async function createJavaBom(path, options) {
2363
2469
 
2364
2470
  if (
2365
2471
  millFiles?.length &&
2366
- (options.installDeps !== false || options.introspect === true) &&
2472
+ isProjectToolingAllowed(options) &&
2367
2473
  isPackageManagerAllowed(
2368
2474
  "mill",
2369
2475
  ["bazel", "sbt", "gradle", "maven"],
@@ -2572,7 +2678,7 @@ export async function createJavaBom(path, options) {
2572
2678
  } finally {
2573
2679
  // A build this scan ran can have started a Mill daemon of its own
2574
2680
  // without --no-server; stop those even when a later step failed.
2575
- stopMillDaemons(path, millDaemonsBefore);
2681
+ stopMillDaemons(path, millDaemonsBefore, options);
2576
2682
  }
2577
2683
  // A Scala.js build bundles npm packages: scalajs-bundler installs them
2578
2684
  // under target, and a bundler workspace keeps its lock file beside the