@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,6 +23,7 @@ import {
23
23
  import { getAllFiles, safeExistsSync, safeSpawnSync } from "../core/fs.js";
24
24
  import { thoughtLog } from "../core/logger.js";
25
25
  import { isWin } from "../core/paths.js";
26
+ import { isProjectToolingAllowed } from "../core/projectTooling.js";
26
27
  import { parseToml } from "../core/toml.js";
27
28
  import {
28
29
  isBuildToolRateLimited,
@@ -44,13 +45,16 @@ function isJvmToolPinned() {
44
45
  );
45
46
  }
46
47
 
48
+ // The gate lives in lib/core so that every layer can reach it; it stays
49
+ // exported here for existing callers.
50
+ export { isProjectToolingAllowed };
51
+
47
52
  /**
48
53
  * Method to return the gradle command to use.
49
54
  *
50
55
  * Project-local wrapper scripts (`gradlew`) are attacker-controlled files in
51
- * the scanned project, so they are only used when dependency tooling is
52
- * allowed (`options.installDeps !== false`); otherwise the system Gradle is
53
- * used.
56
+ * the scanned project, so they are only used when the project's own tooling
57
+ * may run (see isProjectToolingAllowed); otherwise the system Gradle is used.
54
58
  *
55
59
  * @param {string} srcPath Path to look for gradlew wrapper
56
60
  * @param {string|null} rootPath Root directory to look for gradlew wrapper
@@ -59,10 +63,7 @@ function isJvmToolPinned() {
59
63
  export function getGradleCommand(srcPath, rootPath, options = {}) {
60
64
  let gradleCmd = "gradle";
61
65
  const pinnedToolSelected = isJvmToolPinned();
62
- // Introspection mode exists to observe the project's own toolchain, so it
63
- // keeps using wrappers even when dependency installation is disabled.
64
- const allowProjectWrappers =
65
- options.installDeps !== false || options.introspect === true;
66
+ const allowProjectWrappers = isProjectToolingAllowed(options);
66
67
 
67
68
  let findGradleFile = "gradlew";
68
69
  if (platform() === "win32") {
@@ -1349,15 +1350,18 @@ export async function collectGradleDependencies(
1349
1350
  * Method to return the mill command to use.
1350
1351
  *
1351
1352
  * The project-local `mill` script is an attacker-controlled file in the
1352
- * scanned project, so it is only used when dependency tooling is allowed
1353
- * (`options.installDeps !== false`).
1353
+ * scanned project, so it is only used when the project's own tooling may run
1354
+ * (see isProjectToolingAllowed).
1354
1355
  *
1355
1356
  * @param {string} srcPath Path to look for mill wrapper
1356
1357
  * @param {Object} [options] CLI options (`installDeps` gates wrapper use)
1357
1358
  */
1358
1359
  export function getMillCommand(srcPath, options = {}) {
1359
1360
  let millCmd = `mill${platform() === "win32" ? ".bat" : ""}`;
1360
- if (options.installDeps !== false && safeExistsSync(join(srcPath, millCmd))) {
1361
+ if (
1362
+ isProjectToolingAllowed(options) &&
1363
+ safeExistsSync(join(srcPath, millCmd))
1364
+ ) {
1361
1365
  // Use local mill wrapper if available
1362
1366
  // Enable execute permission
1363
1367
  try {
@@ -1510,16 +1514,25 @@ function isProcessAlive(pid) {
1510
1514
  * @param {string} rootPath Scanned tree
1511
1515
  * @param {Map<string, Set<number>>} [runningBefore] Result of
1512
1516
  * {@link findMillDaemons} taken before the scan
1517
+ * @param {Object} [options] CLI options
1513
1518
  */
1514
- export function stopMillDaemons(rootPath, runningBefore = new Map()) {
1519
+ export function stopMillDaemons(
1520
+ rootPath,
1521
+ runningBefore = new Map(),
1522
+ options = {},
1523
+ ) {
1524
+ // The daemon PID files live in the scanned tree. When the scan was not
1525
+ // allowed to run Mill it started no daemon, and running the build's own
1526
+ // launcher here would execute a script from the scanned project.
1527
+ if (!isProjectToolingAllowed(options)) {
1528
+ return;
1529
+ }
1515
1530
  for (const [buildRoot, pids] of findMillDaemons(rootPath)) {
1516
1531
  const before = runningBefore.get(buildRoot) || new Set();
1517
1532
  if ([...pids].every((pid) => before.has(pid))) {
1518
1533
  continue;
1519
1534
  }
1520
- const launcher = safeExistsSync(join(buildRoot, "mill"))
1521
- ? join(buildRoot, "mill")
1522
- : "mill";
1535
+ const launcher = getMillCommand(buildRoot, options);
1523
1536
  console.log("Stopping the Mill daemon the scan started in", buildRoot);
1524
1537
  safeSpawnSync(launcher, ["shutdown"], {
1525
1538
  cwd: buildRoot,
@@ -1532,8 +1545,8 @@ export function stopMillDaemons(rootPath, runningBefore = new Map()) {
1532
1545
  * Method to return the maven command to use.
1533
1546
  *
1534
1547
  * Project-local wrapper scripts (`mvnw`) are attacker-controlled files in the
1535
- * scanned project, so they are only used when dependency tooling is allowed
1536
- * (`options.installDeps !== false`); otherwise the installed Maven is used.
1548
+ * scanned project, so they are only used when the project's own tooling may
1549
+ * run (see isProjectToolingAllowed); otherwise the installed Maven is used.
1537
1550
  *
1538
1551
  * @param {string} srcPath Path to look for maven wrapper
1539
1552
  * @param {string} rootPath Root directory to look for maven wrapper
@@ -1545,10 +1558,7 @@ export function getMavenCommand(srcPath, rootPath, options = {}) {
1545
1558
  let isWrapperReady = false;
1546
1559
  let isWrapperFound = false;
1547
1560
  const pinnedToolSelected = isJvmToolPinned();
1548
- // Introspection mode exists to observe the project's own toolchain, so it
1549
- // keeps using wrappers even when dependency installation is disabled.
1550
- const allowProjectWrappers =
1551
- options.installDeps !== false || options.introspect === true;
1561
+ const allowProjectWrappers = isProjectToolingAllowed(options);
1552
1562
  let findMavenFile = "mvnw";
1553
1563
  let mavenWrapperCmd = null;
1554
1564
  if (platform() === "win32") {
@@ -1,5 +1,13 @@
1
1
  import { readFileSync } from "node:fs";
2
- import { basename, dirname, join, resolve } from "node:path";
2
+ import {
3
+ basename,
4
+ dirname,
5
+ isAbsolute,
6
+ join,
7
+ posix,
8
+ resolve,
9
+ win32,
10
+ } from "node:path";
3
11
 
4
12
  import { build } from "@cdxgen/cdx-purl";
5
13
 
@@ -182,7 +190,10 @@ export function resolveInstalledNugetVersion(range, installedVersions = []) {
182
190
  * @returns {string} Project directory
183
191
  */
184
192
  export function dotnetManifestProjectDir(manifestFile, projectPath) {
185
- if (projectPath) {
193
+ // A path recorded on another operating system, such as `D:\src\App.csproj`
194
+ // read on Linux, is not absolute here and would resolve against the working
195
+ // directory.
196
+ if (projectPath && isAbsolute(projectPath)) {
186
197
  const projectDir = dirname(resolve(projectPath));
187
198
  if (safeExistsSync(projectDir)) {
188
199
  return projectDir;
@@ -192,6 +203,55 @@ export function dotnetManifestProjectDir(manifestFile, projectPath) {
192
203
  return basename(dir) === "obj" ? dirname(dir) : dir;
193
204
  }
194
205
 
206
+ /**
207
+ * The path, on this machine, of the project file a project.assets.json belongs to.
208
+ *
209
+ * Restore records the project file and the directory it wrote the assets file
210
+ * to (`outputPath`) as absolute paths on the machine that ran it, which can be
211
+ * a CI agent, a container, another operating system, or this tree reached
212
+ * through another path. Where the project file sits relative to that
213
+ * directory does not change when the tree moves, so the project file is that
214
+ * offset applied to the directory the assets file is in. An assets file that
215
+ * records no output path is taken to be in its project's `obj/`. The recorded
216
+ * path itself is used only when no offset can be worked out, as when restore
217
+ * wrote to another drive than the project's.
218
+ *
219
+ * Nothing is read from disk: the name is spelled as restore recorded it, which
220
+ * can differ in case from the file a case-insensitive file system holds.
221
+ *
222
+ * @param {string} assetsFile project.assets.json path
223
+ * @param {string} [projectPath] Project file restore recorded
224
+ * @param {string} [outputPath] Directory restore recorded writing the assets file to
225
+ * @returns {string|undefined} Project file path, or undefined when none can be named
226
+ */
227
+ export function dotnetRestoredProjectFile(assetsFile, projectPath, outputPath) {
228
+ if (!assetsFile || typeof projectPath !== "string" || !projectPath.trim()) {
229
+ return undefined;
230
+ }
231
+ // Read the recorded paths the way the machine that wrote them would
232
+ const recorded =
233
+ win32.isAbsolute(projectPath) && !posix.isAbsolute(projectPath)
234
+ ? win32
235
+ : posix;
236
+ const assetsDir = dirname(resolve(assetsFile));
237
+ let offset;
238
+ if (typeof outputPath === "string" && outputPath.trim()) {
239
+ const relativePath = recorded.relative(outputPath, projectPath);
240
+ // Across two drives there is no relative path, only the recorded one
241
+ if (!recorded.isAbsolute(relativePath)) {
242
+ offset = relativePath.split(recorded.sep);
243
+ }
244
+ } else if (basename(assetsDir) === "obj") {
245
+ offset = ["..", recorded.basename(projectPath)];
246
+ }
247
+ // The offset must end at a file, not at the output directory or a parent
248
+ const fileName = offset?.at(-1);
249
+ if (fileName && fileName !== "." && fileName !== "..") {
250
+ return resolve(assetsDir, ...offset);
251
+ }
252
+ return isAbsolute(projectPath) ? resolve(projectPath) : undefined;
253
+ }
254
+
195
255
  /**
196
256
  * Index the MSBuild properties that props files define, by the directory each
197
257
  * file sits in and across the whole scan, so the candidates for every project
@@ -853,8 +913,12 @@ function evaluateMsbuildProperties(projFile, names) {
853
913
  * @param {Object} restoredNames Package ids as this project's restore output spells them,
854
914
  * keyed by lowercased id. NuGet ids are case-insensitive, so a reference spelled
855
915
  * differently names the same package and takes the restored spelling.
916
+ * @param {{name: string, version: (string|undefined)}} [projectIdentity] Package id and
917
+ * version this project's project.assets.json records for the project. They name the
918
+ * project in place of what the project file states.
856
919
  *
857
- * @returns {Object} Containing parent component, package, and dependencies
920
+ * @returns {Object} Containing parent component, package, dependencies, and the
921
+ * `$(Name)` version labels left unresolved without msbuild
858
922
  */
859
923
  export function parseCsProjData(
860
924
  csProjData,
@@ -865,6 +929,7 @@ export function parseCsProjData(
865
929
  centralVersions = {},
866
930
  fallbackVersions = {},
867
931
  restoredNames = {},
932
+ projectIdentity = undefined,
868
933
  ) {
869
934
  const pkgList = [];
870
935
  const parentComponent = { type: "application", properties: [] };
@@ -909,21 +974,21 @@ export function parseCsProjData(
909
974
  ? pkgVersionLabelCandidates[label][0]
910
975
  : undefined);
911
976
  let msbuildValues = {};
912
- if (msbuildInstalled) {
913
- const unresolved = new Set();
914
- for (const item of project.ItemGroup || []) {
915
- for (const reference of item.PackageReference || []) {
916
- const label = labelOf(
917
- reference.$?.VersionOverride ||
918
- reference.VersionOverride?.[0]._?.[0] ||
919
- reference.$?.Version ||
920
- reference.Version?.[0]._?.[0],
921
- );
922
- if (label && staticLabelValue(label) === undefined) {
923
- unresolved.add(label);
924
- }
977
+ const unresolved = new Set();
978
+ for (const item of project.ItemGroup || []) {
979
+ for (const reference of item.PackageReference || []) {
980
+ const label = labelOf(
981
+ reference.$?.VersionOverride ||
982
+ reference.VersionOverride?.[0]._?.[0] ||
983
+ reference.$?.Version ||
984
+ reference.Version?.[0]._?.[0],
985
+ );
986
+ if (label && staticLabelValue(label) === undefined) {
987
+ unresolved.add(label);
925
988
  }
926
989
  }
990
+ }
991
+ if (msbuildInstalled) {
927
992
  msbuildValues = evaluateMsbuildProperties(projFile, [...unresolved].sort());
928
993
  }
929
994
  // SDK-style projects declare their version through the MSBuild properties
@@ -1351,6 +1416,32 @@ export function parseCsProjData(
1351
1416
  // version set from a build-time property is unknown, not 1.0.0.
1352
1417
  parentComponent.version = "1.0.0";
1353
1418
  }
1419
+ // Only a build knows the value of a property that references another one
1420
+ const literal = (value) =>
1421
+ value && !value.includes("$(") ? value : undefined;
1422
+ // NuGet names a project's package by its PackageId, which defaults to the
1423
+ // assembly name, so a PackageId the project file states names the project.
1424
+ const declaredPackageId = literal(ownProperties.PackageId);
1425
+ if (declaredPackageId) {
1426
+ parentComponent.name = declaredPackageId;
1427
+ }
1428
+ // Restore evaluated the project with everything MSBuild imports and every
1429
+ // property it was given: a Version from Directory.Build.props, `-p:Version`
1430
+ // or `$(BuildNumber)`, a PackageId. Its project.assets.json roots the
1431
+ // restored dependency tree at that identity, so naming the project from its
1432
+ // file alone would make it two components.
1433
+ if (projectIdentity?.name) {
1434
+ parentComponent.name = projectIdentity.name;
1435
+ parentComponent.version = projectIdentity.version;
1436
+ }
1437
+ // The assembly the project builds keeps its name when the package differs
1438
+ const declaredAssemblyName = literal(ownProperties.AssemblyName);
1439
+ if (declaredAssemblyName && declaredAssemblyName !== parentComponent.name) {
1440
+ parentComponent.properties.push({
1441
+ name: "cdx:dotnet:assembly_name",
1442
+ value: declaredAssemblyName,
1443
+ });
1444
+ }
1354
1445
  // The purl is built once every PropertyGroup has been read, so a version
1355
1446
  // declared after OutputType is not lost. A project that states no version
1356
1447
  // gets a versionless purl: "latest" is not a version.
@@ -1370,6 +1461,8 @@ export function parseCsProjData(
1370
1461
  pkgList,
1371
1462
  parentComponent,
1372
1463
  dependencies,
1464
+ // Version labels no static source settled, which only msbuild could
1465
+ unresolvedVersionLabels: msbuildInstalled ? [] : [...unresolved].sort(),
1373
1466
  };
1374
1467
  }
1375
1468
 
@@ -1417,8 +1510,10 @@ export function isReferencedDotnetPackageFile(packageFile) {
1417
1510
  *
1418
1511
  * @param {string} csProjData Raw JSON string of the project.assets.json file
1419
1512
  * @param {string} assetsJsonFile Path to the project.assets.json file, used for evidence properties
1420
- * @returns {{ pkgList: Object[], dependenciesList: Object[], projectPath: (string|undefined) }}
1421
- * `projectPath` is the project file the restore output belongs to, as restore recorded it
1513
+ * @returns {{ pkgList: Object[], dependenciesList: Object[], projectPath: (string|undefined), outputPath: (string|undefined), projectIdentity: ({name: string, version: (string|undefined)}|undefined) }}
1514
+ * `projectPath` is the project file the restore output belongs to and `outputPath` the
1515
+ * directory restore wrote it to, both as restore recorded them. `projectIdentity` is the
1516
+ * package id and version restore evaluated for the project, which root the dependency tree.
1422
1517
  */
1423
1518
  export function parseCsProjAssetsData(csProjData, assetsJsonFile) {
1424
1519
  // extract name, operator, version from .NET package representation
@@ -1646,6 +1741,10 @@ export function parseCsProjAssetsData(csProjData, assetsJsonFile) {
1646
1741
  pkgList,
1647
1742
  dependenciesList,
1648
1743
  projectPath,
1744
+ outputPath: csProjData.project?.restore?.outputPath,
1745
+ projectIdentity: rootRef
1746
+ ? { name: rootPkg.name, version: rootPkg.version }
1747
+ : undefined,
1649
1748
  };
1650
1749
  }
1651
1750