@cdxgen/cdxgen 13.3.0 → 13.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. package/README.md +9 -6
  2. package/bin/cdxgen.js +90 -26
  3. package/bin/evinse.js +18 -1
  4. package/bin/repl.js +86 -0
  5. package/bin/sign.js +45 -15
  6. package/bin/tracebom.js +5 -1
  7. package/bin/validate.js +84 -18
  8. package/bin/verify.js +155 -86
  9. package/data/helpers/init.gradle +46 -21
  10. package/data/remediations.json +61 -0
  11. package/data/rules/dependency-sources.yaml +67 -0
  12. package/data/rules/scala-evidence.yaml +115 -0
  13. package/lib/audit/index.js +38 -6
  14. package/lib/cli/index.js +113 -54
  15. package/lib/cli/jsBom.js +50 -29
  16. package/lib/cli/jvmBom.js +665 -138
  17. package/lib/cli/managedBom.js +320 -200
  18. package/lib/cli/nativeBom.js +139 -30
  19. package/lib/core/activity.js +12 -0
  20. package/lib/core/env.js +17 -9
  21. package/lib/core/fs.js +3 -2
  22. package/lib/core/httpClient.js +159 -24
  23. package/lib/core/pluginRun.js +184 -0
  24. package/lib/core/processTree.js +100 -0
  25. package/lib/core/runState.js +39 -0
  26. package/lib/core/toml.js +72 -0
  27. package/lib/ecosystems/asarutils.js +4 -2
  28. package/lib/ecosystems/bunutils.js +5 -7
  29. package/lib/ecosystems/cargoCache.js +231 -8
  30. package/lib/ecosystems/cmakeResolver.js +10 -21
  31. package/lib/ecosystems/cppBuildContext.js +631 -0
  32. package/lib/ecosystems/cppEvidence.js +337 -22
  33. package/lib/ecosystems/cppIncludeResolver.js +294 -0
  34. package/lib/ecosystems/denoutils.js +93 -66
  35. package/lib/ecosystems/ecosystems.js +1967 -788
  36. package/lib/ecosystems/golem.js +28 -12
  37. package/lib/ecosystems/gradleutils.js +314 -30
  38. package/lib/ecosystems/jsEvidence.js +85 -24
  39. package/lib/ecosystems/kosi.js +21 -4
  40. package/lib/ecosystems/npmutils.js +64 -0
  41. package/lib/ecosystems/parsers-dotnet.js +567 -151
  42. package/lib/ecosystems/parsers-elm.js +46 -28
  43. package/lib/ecosystems/parsers-gleam.js +3 -4
  44. package/lib/ecosystems/parsers-go.js +324 -61
  45. package/lib/ecosystems/parsers-js.js +92 -26
  46. package/lib/ecosystems/parsers-julia.js +2 -3
  47. package/lib/ecosystems/parsers-jvm.js +204 -12
  48. package/lib/ecosystems/parsers-misc.js +182 -55
  49. package/lib/ecosystems/parsers-python.js +141 -10
  50. package/lib/ecosystems/parsers-rust.js +4 -4
  51. package/lib/ecosystems/parsers-terraform.js +1573 -19
  52. package/lib/ecosystems/pythonutils.js +102 -6
  53. package/lib/ecosystems/remote/huggingface.js +3 -0
  54. package/lib/ecosystems/rubyutils.js +67 -17
  55. package/lib/ecosystems/rusi.js +22 -9
  56. package/lib/ecosystems/sbtutils.js +243 -187
  57. package/lib/ecosystems/scalacliutils.js +342 -0
  58. package/lib/ecosystems/scalasem.js +2618 -0
  59. package/lib/ecosystems/terraformRegistry.js +288 -0
  60. package/lib/ecosystems/utils.js +2 -0
  61. package/lib/ecosystems/vendoredCode.js +156 -0
  62. package/lib/evinser/evinser.js +578 -69
  63. package/lib/helpers/agenticToolutils.js +2 -2
  64. package/lib/helpers/bomSigner.js +576 -128
  65. package/lib/inventory/aiCollector.js +4 -1
  66. package/lib/inventory/analyzer.js +110 -127
  67. package/lib/inventory/atomUtils.js +171 -48
  68. package/lib/inventory/auditCategories.js +7 -0
  69. package/lib/inventory/buildToolRateLimit.js +144 -0
  70. package/lib/inventory/cacheCommands.js +2 -1
  71. package/lib/inventory/cmakeBuildDirs.js +254 -0
  72. package/lib/inventory/deps.js +501 -124
  73. package/lib/inventory/dosai.js +248 -44
  74. package/lib/inventory/dosaiParsers.js +310 -15
  75. package/lib/inventory/dynamic.js +52 -6
  76. package/lib/inventory/evidenceUtils.js +154 -109
  77. package/lib/inventory/fetchBatch.js +574 -38
  78. package/lib/inventory/fetchDiskCache.js +597 -0
  79. package/lib/inventory/forgeEnricher.js +14 -5
  80. package/lib/inventory/jvmLocalRepos.js +917 -0
  81. package/lib/inventory/mcpConfigParser.js +2 -3
  82. package/lib/inventory/parentSelection.js +146 -0
  83. package/lib/inventory/rustFormulationParser.js +3 -4
  84. package/lib/inventory/scalaCoords.js +153 -0
  85. package/lib/inventory/source.js +8 -8
  86. package/lib/inventory/spdx.js +101 -0
  87. package/lib/inventory/traceRunner.js +88 -7
  88. package/lib/inventory/tracedCrypto.js +294 -0
  89. package/lib/managers/docker.js +33 -32
  90. package/lib/managers/oci.js +24 -2
  91. package/lib/parsers/cmakeCache.js +69 -0
  92. package/lib/parsers/cmakeLists.js +16 -2
  93. package/lib/parsers/cmakePresets.js +400 -0
  94. package/lib/parsers/compileCommands.js +557 -0
  95. package/lib/parsers/strictJson.js +322 -0
  96. package/lib/parsers/terraformConfig.js +1073 -0
  97. package/lib/parsers/terraformSource.js +652 -0
  98. package/lib/server/server.js +23 -0
  99. package/lib/stages/postgen/introspection/markers.js +1 -1
  100. package/lib/stages/postgen/postgen.js +15 -1
  101. package/lib/stages/postgen/specVersionCompat.js +32 -0
  102. package/lib/validator/index.js +25 -10
  103. package/lib/validator/reporters/json.js +1 -0
  104. package/package.json +21 -21
  105. package/types/lib/audit/index.d.ts.map +1 -1
  106. package/types/lib/cli/index.d.ts.map +1 -1
  107. package/types/lib/cli/jsBom.d.ts.map +1 -1
  108. package/types/lib/cli/jvmBom.d.ts +24 -1
  109. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  110. package/types/lib/cli/managedBom.d.ts +7 -4
  111. package/types/lib/cli/managedBom.d.ts.map +1 -1
  112. package/types/lib/cli/nativeBom.d.ts +3 -1
  113. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  114. package/types/lib/core/activity.d.ts.map +1 -1
  115. package/types/lib/core/env.d.ts +9 -0
  116. package/types/lib/core/env.d.ts.map +1 -1
  117. package/types/lib/core/fs.d.ts.map +1 -1
  118. package/types/lib/core/httpClient.d.ts +13 -8
  119. package/types/lib/core/httpClient.d.ts.map +1 -1
  120. package/types/lib/core/pluginRun.d.ts +97 -0
  121. package/types/lib/core/pluginRun.d.ts.map +1 -0
  122. package/types/lib/core/processTree.d.ts +36 -0
  123. package/types/lib/core/processTree.d.ts.map +1 -0
  124. package/types/lib/core/runState.d.ts +29 -0
  125. package/types/lib/core/runState.d.ts.map +1 -0
  126. package/types/lib/core/toml.d.ts +20 -0
  127. package/types/lib/core/toml.d.ts.map +1 -0
  128. package/types/lib/ecosystems/asarutils.d.ts.map +1 -1
  129. package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
  130. package/types/lib/ecosystems/cargoCache.d.ts +12 -0
  131. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -1
  132. package/types/lib/ecosystems/cmakeResolver.d.ts +5 -3
  133. package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
  134. package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
  135. package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
  136. package/types/lib/ecosystems/cppEvidence.d.ts +18 -2
  137. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  138. package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
  139. package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
  140. package/types/lib/ecosystems/denoutils.d.ts +9 -3
  141. package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
  142. package/types/lib/ecosystems/ecosystems.d.ts +62 -13
  143. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  144. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  145. package/types/lib/ecosystems/gradleutils.d.ts +50 -0
  146. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  147. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  148. package/types/lib/ecosystems/kosi.d.ts.map +1 -1
  149. package/types/lib/ecosystems/npmutils.d.ts +15 -0
  150. package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
  151. package/types/lib/ecosystems/parsers-dotnet.d.ts +118 -4
  152. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  153. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
  154. package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
  155. package/types/lib/ecosystems/parsers-go.d.ts +44 -2
  156. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  157. package/types/lib/ecosystems/parsers-js.d.ts +6 -1
  158. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  159. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
  160. package/types/lib/ecosystems/parsers-jvm.d.ts +46 -1
  161. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  162. package/types/lib/ecosystems/parsers-misc.d.ts +3 -1
  163. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  164. package/types/lib/ecosystems/parsers-python.d.ts +53 -3
  165. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  166. package/types/lib/ecosystems/parsers-terraform.d.ts +38 -21
  167. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
  168. package/types/lib/ecosystems/pythonutils.d.ts +27 -2
  169. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  170. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
  171. package/types/lib/ecosystems/rubyutils.d.ts +21 -1
  172. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  173. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  174. package/types/lib/ecosystems/sbtutils.d.ts +88 -2
  175. package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
  176. package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
  177. package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
  178. package/types/lib/ecosystems/scalasem.d.ts +311 -0
  179. package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
  180. package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
  181. package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
  182. package/types/lib/ecosystems/utils.d.ts +2 -2
  183. package/types/lib/ecosystems/utils.d.ts.map +1 -1
  184. package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
  185. package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
  186. package/types/lib/evinser/evinser.d.ts +75 -8
  187. package/types/lib/evinser/evinser.d.ts.map +1 -1
  188. package/types/lib/helpers/bomSigner.d.ts +80 -4
  189. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  190. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  191. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  192. package/types/lib/inventory/atomUtils.d.ts +82 -10
  193. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  194. package/types/lib/inventory/auditCategories.d.ts +3 -0
  195. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  196. package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
  197. package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
  198. package/types/lib/inventory/cacheCommands.d.ts +2 -1
  199. package/types/lib/inventory/cacheCommands.d.ts.map +1 -1
  200. package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
  201. package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
  202. package/types/lib/inventory/deps.d.ts +59 -1
  203. package/types/lib/inventory/deps.d.ts.map +1 -1
  204. package/types/lib/inventory/dosai.d.ts +57 -8
  205. package/types/lib/inventory/dosai.d.ts.map +1 -1
  206. package/types/lib/inventory/dosaiParsers.d.ts +53 -7
  207. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
  208. package/types/lib/inventory/dynamic.d.ts +10 -0
  209. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  210. package/types/lib/inventory/evidenceUtils.d.ts +19 -4
  211. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  212. package/types/lib/inventory/fetchBatch.d.ts +83 -14
  213. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  214. package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
  215. package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
  216. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  217. package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
  218. package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
  219. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  220. package/types/lib/inventory/parentSelection.d.ts +34 -0
  221. package/types/lib/inventory/parentSelection.d.ts.map +1 -0
  222. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
  223. package/types/lib/inventory/scalaCoords.d.ts +80 -0
  224. package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
  225. package/types/lib/inventory/source.d.ts.map +1 -1
  226. package/types/lib/inventory/spdx.d.ts +9 -0
  227. package/types/lib/inventory/spdx.d.ts.map +1 -1
  228. package/types/lib/inventory/traceRunner.d.ts +12 -0
  229. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  230. package/types/lib/inventory/tracedCrypto.d.ts +42 -0
  231. package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
  232. package/types/lib/managers/docker.d.ts.map +1 -1
  233. package/types/lib/managers/oci.d.ts +3 -1
  234. package/types/lib/managers/oci.d.ts.map +1 -1
  235. package/types/lib/parsers/cmakeCache.d.ts +16 -0
  236. package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
  237. package/types/lib/parsers/cmakeLists.d.ts +2 -1
  238. package/types/lib/parsers/cmakeLists.d.ts.map +1 -1
  239. package/types/lib/parsers/cmakePresets.d.ts +78 -0
  240. package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
  241. package/types/lib/parsers/compileCommands.d.ts +115 -0
  242. package/types/lib/parsers/compileCommands.d.ts.map +1 -0
  243. package/types/lib/parsers/strictJson.d.ts +46 -0
  244. package/types/lib/parsers/strictJson.d.ts.map +1 -0
  245. package/types/lib/parsers/terraformConfig.d.ts +70 -0
  246. package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
  247. package/types/lib/parsers/terraformSource.d.ts +66 -0
  248. package/types/lib/parsers/terraformSource.d.ts.map +1 -0
  249. package/types/lib/server/server.d.ts.map +1 -1
  250. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  251. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  252. package/types/lib/validator/index.d.ts +8 -0
  253. package/types/lib/validator/index.d.ts.map +1 -1
  254. package/types/lib/validator/reporters/json.d.ts.map +1 -1
  255. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
@@ -1,7 +1,7 @@
1
1
  import { Buffer } from "node:buffer";
2
2
  import { readFileSync, statSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
- import { basename, dirname, join, relative, resolve } from "node:path";
4
+ import { basename, dirname, join, relative, resolve, sep } from "node:path";
5
5
  import process from "node:process";
6
6
 
7
7
  import { build } from "@cdxgen/cdx-purl";
@@ -28,6 +28,7 @@ import {
28
28
  } from "../core/fs.js";
29
29
  import { thoughtLog } from "../core/logger.js";
30
30
  import { dirNameStr, isMac } from "../core/paths.js";
31
+ import { readCrateArchiveValues } from "../ecosystems/cargoCache.js";
31
32
  import {
32
33
  buildDependentPurl,
33
34
  collapseCmakeVersions,
@@ -35,7 +36,11 @@ import {
35
36
  resolveCmakeContext,
36
37
  withoutScrapedFetchDependency,
37
38
  } from "../ecosystems/cmakeResolver.js";
38
- import { getCppModules } from "../ecosystems/cppEvidence.js";
39
+ import {
40
+ emptyCppBuildContext,
41
+ resolveCppBuildContext,
42
+ } from "../ecosystems/cppBuildContext.js";
43
+ import { getCppModules, hasCppSources } from "../ecosystems/cppEvidence.js";
39
44
  import {
40
45
  getCratesMetadata,
41
46
  getSwiftPackageMetadata,
@@ -58,6 +63,7 @@ import {
58
63
  parseGoModWhy,
59
64
  parseGopkgData,
60
65
  parseGosumData,
66
+ parseGosumHashes,
61
67
  } from "../ecosystems/parsers-go.js";
62
68
  import { parseJuliaProject } from "../ecosystems/parsers-julia.js";
63
69
  import {
@@ -115,7 +121,9 @@ import {
115
121
  } from "../ecosystems/parsers-rust.js";
116
122
  import { parseSpackLock } from "../ecosystems/parsers-spack.js";
117
123
  import { parseStackProject } from "../ecosystems/parsers-stack.js";
124
+ import { findVendoredCode } from "../ecosystems/vendoredCode.js";
118
125
  import { resolveZigGraph } from "../ecosystems/zigResolver.js";
126
+ import { cmakeBuildDirCandidates } from "../inventory/cmakeBuildDirs.js";
119
127
  import { mergeDependencies, trimComponents } from "../inventory/depsUtils.js";
120
128
  import { convertOSQueryResults } from "../inventory/evidenceUtils.js";
121
129
  import { applyPurl, nixBomRef, tryBuildPurl } from "../inventory/purl.js";
@@ -339,12 +347,9 @@ export async function createGoBom(path, options) {
339
347
  console.log(`Parsing ${f}`);
340
348
  }
341
349
  const gosumData = readFileSync(f, { encoding: "utf-8" });
342
- const dlist = await parseGosumData(gosumData);
343
- if (dlist?.length) {
344
- dlist.forEach((pkg) => {
345
- gosumMap[`${pkg.name}@${pkg.version}`] = pkg._integrity;
346
- });
347
- }
350
+ // Only the hashes are needed here; reading them costs no registry
351
+ // round trip. The components come from go.mod and the go list output.
352
+ Object.assign(gosumMap, parseGosumHashes(gosumData));
348
353
  }
349
354
  }
350
355
 
@@ -1163,7 +1168,9 @@ export function buildCargoCacheComponent(crateFile) {
1163
1168
 
1164
1169
  /**
1165
1170
  * Enriches a cargo cache component with a SHA-256 hash and filename-based
1166
- * identity evidence (confidence 0.5). Hash computation failures are ignored.
1171
+ * identity evidence (confidence 0.5), and with what the archive's own manifest
1172
+ * says: its licence, description and repository, read from the archive without
1173
+ * extracting it anywhere. Hash computation failures are ignored.
1167
1174
  *
1168
1175
  * @param {string} crateFile Absolute path to the `.crate` archive
1169
1176
  * @param {object} [component] Component produced by `buildCargoCacheComponent`
@@ -1183,6 +1190,33 @@ export async function enrichCargoCacheComponent(crateFile, component) {
1183
1190
  } catch {
1184
1191
  // continue without hashes
1185
1192
  }
1193
+ const archive = await readCrateArchiveValues(
1194
+ crateFile,
1195
+ component.name,
1196
+ component.version,
1197
+ );
1198
+ if (archive) {
1199
+ if (archive.values.license && !component.license) {
1200
+ component.license = archive.values.license;
1201
+ }
1202
+ if (archive.values.licenses && !component.licenses) {
1203
+ component.licenses = archive.values.licenses;
1204
+ }
1205
+ if (archive.values.description && !component.description) {
1206
+ component.description = archive.values.description;
1207
+ }
1208
+ if (archive.values.repository && !component.repository) {
1209
+ component.repository = { url: archive.values.repository };
1210
+ }
1211
+ if (archive.values.homepage && !component.homepage) {
1212
+ component.homepage = { url: archive.values.homepage };
1213
+ }
1214
+ component.properties = component.properties || [];
1215
+ component.properties.push({
1216
+ name: "cdx:cargo:metadataSource",
1217
+ value: "local-registry",
1218
+ });
1219
+ }
1186
1220
  component.evidence = {
1187
1221
  identity: {
1188
1222
  field: "purl",
@@ -1365,6 +1399,7 @@ export function createCppBom(path, options) {
1365
1399
  }
1366
1400
  let pkgList = [];
1367
1401
  let parentComponentDependencies = [];
1402
+ const subProjects = [];
1368
1403
  if (conanLockFiles.length) {
1369
1404
  for (const f of conanLockFiles) {
1370
1405
  if (DEBUG_MODE) {
@@ -1477,8 +1512,17 @@ export function createCppBom(path, options) {
1477
1512
  Object.keys(retMap.parentComponent).length &&
1478
1513
  !addedParentComponentsMap[retMap.parentComponent.name]
1479
1514
  ) {
1515
+ // a project() below the scan root is a part of this project, not a
1516
+ // dependency of it
1480
1517
  retMap.parentComponent.type = "library";
1481
- pkgList.push(retMap.parentComponent);
1518
+ const subprojectDir = relative(scanRoot, resolve(dirname(f)))
1519
+ .split(sep)
1520
+ .join("/");
1521
+ retMap.parentComponent.properties = [
1522
+ ...(retMap.parentComponent.properties || []),
1523
+ { name: "cdx:cmake:subprojectDir", value: subprojectDir || "." },
1524
+ ];
1525
+ subProjects.push(retMap.parentComponent);
1482
1526
  addedParentComponentsMap[retMap.parentComponent.name] = true;
1483
1527
  }
1484
1528
  // Retain the dependency tree from cmake
@@ -1522,6 +1566,29 @@ export function createCppBom(path, options) {
1522
1566
  boundaries: new Map(),
1523
1567
  }
1524
1568
  : resolveCmakeContext(path, options);
1569
+ // How the project is built: its presets and compilers (formulation), its
1570
+ // hardening options (project properties) and its own include directories.
1571
+ const isImageScan = hasAnyProjectType(
1572
+ ["docker", "oci", "container", "os"],
1573
+ options,
1574
+ false,
1575
+ );
1576
+ // Code carried in the tree under a license of its own, outside the sources
1577
+ // of the dependencies CMake already knows and the build trees
1578
+ const vendored = isImageScan
1579
+ ? { components: [], dirs: [] }
1580
+ : findVendoredCode(path, options, [
1581
+ ...[...(cmakeContext.boundaries || new Map())]
1582
+ .filter(([, e]) => e?.kind === "fetch" || e?.kind === "submodule")
1583
+ .map(([dir]) => resolve(path, dir)),
1584
+ ...cmakeBuildDirCandidates(path, options),
1585
+ ]);
1586
+ if (vendored.components.length) {
1587
+ pkgList = pkgList.concat(vendored.components);
1588
+ }
1589
+ const buildContext = isImageScan
1590
+ ? emptyCppBuildContext()
1591
+ : resolveCppBuildContext(path, options, cmakeContext, vendored.dirs);
1525
1592
  if (cmakeContext.rootProject?.version && parentComponent) {
1526
1593
  const resolvedVersion = cmakeContext.rootProject.version;
1527
1594
  const scrapedVersion = parentComponent.version;
@@ -1651,30 +1718,44 @@ export function createCppBom(path, options) {
1651
1718
  // We collect OS packages with the word dev in the name using osquery here
1652
1719
  // rpm, deb and ebuild are supported
1653
1720
  // TODO: For archlinux and alpine users we need a different mechanism to collect this information
1654
- for (const queryCategory of Object.keys(cosDbQueries)) {
1655
- const queryObj = cosDbQueries[queryCategory];
1656
- const results = executeOsQuery(queryObj.query);
1657
- const dlist = convertOSQueryResults(
1658
- queryCategory,
1659
- queryObj,
1660
- results,
1661
- true,
1662
- {
1663
- deb: executeDpkgList,
1664
- rpm: executeRpmList,
1665
- apk: executeApkList,
1666
- ebuild: executeEqueryList,
1667
- alpm: executeAlpmList,
1668
- },
1669
- );
1670
- if (dlist?.length) {
1671
- osPkgsList = osPkgsList.concat(dlist);
1721
+ // The packages only resolve the headers named by the usages slice, so
1722
+ // they are not queried when there is neither a slice to reuse nor a C or
1723
+ // C++ source for atom to read.
1724
+ if (
1725
+ (options.usagesSlicesFile && safeExistsSync(options.usagesSlicesFile)) ||
1726
+ hasCppSources(path, options)
1727
+ ) {
1728
+ for (const queryCategory of Object.keys(cosDbQueries)) {
1729
+ const queryObj = cosDbQueries[queryCategory];
1730
+ const results = executeOsQuery(queryObj.query);
1731
+ const dlist = convertOSQueryResults(
1732
+ queryCategory,
1733
+ queryObj,
1734
+ results,
1735
+ true,
1736
+ {
1737
+ deb: executeDpkgList,
1738
+ rpm: executeRpmList,
1739
+ apk: executeApkList,
1740
+ ebuild: executeEqueryList,
1741
+ alpm: executeAlpmList,
1742
+ },
1743
+ );
1744
+ if (dlist?.length) {
1745
+ osPkgsList = osPkgsList.concat(dlist);
1746
+ }
1672
1747
  }
1673
1748
  }
1674
1749
  // Now we check with atom and attempt to detect all external modules via usages
1675
1750
  // We pass the current list of packages so that we enhance the current list and replace
1676
1751
  // components inadvertently. For example, we might resolved a name, version and url information already via cmake
1677
- const retMap = getCppModules(path, options, osPkgsList, pkgList);
1752
+ const retMap = getCppModules(
1753
+ path,
1754
+ options,
1755
+ osPkgsList,
1756
+ pkgList,
1757
+ buildContext.isFirstPartyHeader,
1758
+ );
1678
1759
  if (retMap.pkgList?.length) {
1679
1760
  pkgList = pkgList.concat(retMap.pkgList);
1680
1761
  }
@@ -1718,6 +1799,33 @@ export function createCppBom(path, options) {
1718
1799
  ]);
1719
1800
  }
1720
1801
 
1802
+ if (subProjects.length) {
1803
+ if (parentComponent) {
1804
+ parentComponent.components = parentComponent.components || [];
1805
+ for (const sub of subProjects) {
1806
+ if (
1807
+ !parentComponent.components.some(
1808
+ (c) => c["bom-ref"] === sub["bom-ref"],
1809
+ ) &&
1810
+ sub["bom-ref"] !== parentComponent["bom-ref"]
1811
+ ) {
1812
+ parentComponent.components.push(sub);
1813
+ }
1814
+ }
1815
+ } else {
1816
+ // with no project at the scan root there is nothing to nest them under
1817
+ pkgList = pkgList.concat(subProjects);
1818
+ }
1819
+ }
1820
+ if (parentComponent && buildContext.parentProperties.length) {
1821
+ parentComponent.properties = (parentComponent.properties || []).filter(
1822
+ (p) =>
1823
+ !buildContext.parentProperties.some(
1824
+ (bp) => bp.name === p.name && bp.value === p.value,
1825
+ ),
1826
+ );
1827
+ parentComponent.properties.push(...buildContext.parentProperties);
1828
+ }
1721
1829
  if (parentComponent) {
1722
1830
  sanitizeUnresolvedPurl(parentComponent);
1723
1831
  if (parentComponent.components) {
@@ -1734,6 +1842,7 @@ export function createCppBom(path, options) {
1734
1842
  src: path,
1735
1843
  parentComponent,
1736
1844
  dependencies,
1845
+ formulationList: buildContext.formulationComponents,
1737
1846
  });
1738
1847
  }
1739
1848
 
@@ -2328,7 +2437,7 @@ export async function createCocoaBom(path, options) {
2328
2437
  "application",
2329
2438
  );
2330
2439
  const podfileLock = loadYaml(readFileSync(lockFile, "utf-8"));
2331
- const pods = await parsePodfileLock(podfileLock, projectPath);
2440
+ const pods = await parsePodfileLock(podfileLock, projectPath, path);
2332
2441
  const allObjects = new Map();
2333
2442
  if (options) {
2334
2443
  // Every remote podspec the scan below reads is known from the lockfile,
@@ -285,6 +285,18 @@ const MANIFEST_ACTIVITY_HINTS = new Map([
285
285
  ],
286
286
  ]);
287
287
  const SENSITIVE_CONFIG_ACTIVITY_HINTS = [
288
+ {
289
+ matcher: (_lowerPath, baseName) =>
290
+ baseName === ".terraformrc" ||
291
+ baseName === "terraform.rc" ||
292
+ baseName === "credentials.tfrc.json",
293
+ metadata: {
294
+ classification: "credential",
295
+ ecosystem: "terraform",
296
+ label: "Terraform CLI configuration",
297
+ sensitive: true,
298
+ },
299
+ },
288
300
  {
289
301
  matcher: (lowerPath, _baseName) =>
290
302
  lowerPath.includes("/.cargo/config.toml") ||
package/lib/core/env.js CHANGED
@@ -111,19 +111,27 @@ export function shouldFetchLicense() {
111
111
  );
112
112
  }
113
113
 
114
+ /**
115
+ * Determines whether registry provenance, such as publish times, publishers
116
+ * and trusted-publishing signals, is wanted. `--bom-audit` turns it on through
117
+ * CDXGEN_FETCH_PKG_METADATA. Local data cannot answer these questions, so a
118
+ * package whose licence and description are known locally is still looked up.
119
+ *
120
+ * @returns {boolean} True if CDXGEN_FETCH_PKG_METADATA is "true" or "1"
121
+ */
122
+ export function shouldFetchRegistryProvenance() {
123
+ return ["true", "1"].includes(
124
+ readEnvironmentVariable("CDXGEN_FETCH_PKG_METADATA"),
125
+ );
126
+ }
127
+
114
128
  /**
115
129
  * Determines whether remote package metadata should be fetched for enrichment.
116
130
  *
117
131
  * @returns {boolean} True when registry metadata enrichment is enabled.
118
132
  */
119
133
  export function shouldFetchPackageMetadata() {
120
- return (
121
- shouldFetchLicense() ||
122
- (readEnvironmentVariable("CDXGEN_FETCH_PKG_METADATA") &&
123
- ["true", "1"].includes(
124
- readEnvironmentVariable("CDXGEN_FETCH_PKG_METADATA"),
125
- ))
126
- );
134
+ return shouldFetchLicense() || shouldFetchRegistryProvenance();
127
135
  }
128
136
 
129
137
  /**
@@ -457,7 +465,7 @@ export const PROJECT_TYPE_ALIASES = {
457
465
  perl: ["perl", "cpan", "carton", "cpanfile"],
458
466
  spack: ["spack"],
459
467
  terraform: ["terraform", "opentofu", "tf"],
460
- c: ["c", "cpp", "c++", "conan", "collider"],
468
+ c: ["c", "cpp", "c++", "conan", "collider", "cmake", "meson", "vcpkg"],
461
469
  clojure: ["clojure", "edn", "clj", "leiningen"],
462
470
  github: ["github", "actions"],
463
471
  hbom: ["hbom", "hardware"],
@@ -493,7 +501,7 @@ export const PROJECT_TYPE_ALIASES = {
493
501
  binary: ["binary", "blint"],
494
502
  oci: ["docker", "image", "oci", "container", "podman", "rootfs", "oci-dir"],
495
503
  cocoa: ["cocoa", "cocoapods", "objective-c", "swift", "ios"],
496
- scala: ["scala", "scala3", "sbt", "mill"],
504
+ scala: ["scala", "scala3", "sbt", "mill", "scala-cli"],
497
505
  nix: ["nix", "nixos", "flake"],
498
506
  zig: ["zig", "zon"],
499
507
  gleam: ["gleam"],
package/lib/core/fs.js CHANGED
@@ -577,7 +577,7 @@ export function shellQuoteArgument(arg, forWindows = isWin) {
577
577
  backslashes++;
578
578
  quoted += ch;
579
579
  } else if (ch === '"') {
580
- quoted += "\\".repeat(backslashes) + '\\"';
580
+ quoted += `${"\\".repeat(backslashes)}\\"`;
581
581
  backslashes = 0;
582
582
  } else {
583
583
  quoted += ch;
@@ -1025,7 +1025,8 @@ export const MAX_BUFFER =
1025
1025
  export function getAllFiles(dirPath, pattern, options = {}) {
1026
1026
  let ignoreList = [];
1027
1027
  if (!options.noIgnore) {
1028
- ignoreList.push("**/.hg/**", "**/.git/**");
1028
+ // .chen holds chen's AST cache, which atom writes into a C/C++ source tree
1029
+ ignoreList.push("**/.hg/**", "**/.git/**", "**/.chen/**");
1029
1030
  // Only ignore node_modules if the caller is not looking for package.json
1030
1031
  if (!pattern.includes("package.json") && !options.includeNodeModulesDir) {
1031
1032
  ignoreList.push("**/node_modules/**");
@@ -1,4 +1,5 @@
1
1
  import { Buffer } from "node:buffer";
2
+ import { createHash } from "node:crypto";
2
3
  import process from "node:process";
3
4
  import { brotliDecompressSync, gunzipSync, inflateSync } from "node:zlib";
4
5
 
@@ -9,6 +10,8 @@ import {
9
10
  request as undiciRequest,
10
11
  } from "undici";
11
12
 
13
+ import { registerRunReset } from "./runState.js";
14
+
12
15
  // Environment variables here are read with raw `process.env` rather than
13
16
  // `readEnvironmentVariable`: that helper lives in activity.js, which imports
14
17
  // this module, so routing these through it would form an import cycle.
@@ -41,9 +44,8 @@ import {
41
44
  * `Content-Encoding` header, matching `got`'s transparent decoding.
42
45
  * - Proxy support via the standard `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY`
43
46
  * environment variables (through undici's `EnvHttpProxyAgent`).
44
- * - An in-memory GET response cache (enabled by default, disabled by setting
45
- * the `CDXGEN_NO_CACHE` environment variable) that replaces the got + Keyv
46
- * cache cdxgen previously relied on.
47
+ * - An in-memory GET response cache that replaces the got + Keyv cache cdxgen
48
+ * previously relied on. A request opts out with `cache: false`.
47
49
  * - Response objects exposing `statusCode`, `headers`, `body`, `rawBody`,
48
50
  * `url` and `request.options`.
49
51
  * - A lazily-resolved `.json()` method on the returned promise, mirroring
@@ -137,7 +139,12 @@ function mergeHeaders(base = {}, override = {}) {
137
139
  * `beforeError` arrays.
138
140
  */
139
141
  function mergeHooks(base = {}, override = {}) {
140
- const hookNames = ["beforeRequest", "afterResponse", "beforeError"];
142
+ const hookNames = [
143
+ "beforeRequest",
144
+ "beforeNetwork",
145
+ "afterResponse",
146
+ "beforeError",
147
+ ];
141
148
  const merged = {};
142
149
  for (const name of hookNames) {
143
150
  merged[name] = [...(base?.[name] || []), ...(override?.[name] || [])];
@@ -253,6 +260,11 @@ function decompressBody(rawBody, contentEncoding) {
253
260
  * Read and decode an undici response body according to the requested
254
261
  * `responseType`.
255
262
  *
263
+ * An error response often carries a body in another format than the one asked
264
+ * for, such as the HTML page a CDN sends with a 429. Its JSON body is decoded
265
+ * when it parses and kept as text otherwise, so the caller sees the status
266
+ * rather than a parse error.
267
+ *
256
268
  * @param {Object} undiciResponse Response returned by `undici.request`.
257
269
  * @param {string} [responseType] One of `"json"`, `"buffer"` or `"text"`.
258
270
  * @returns {Promise<{body: any, rawBody: Buffer}>} Decoded body and raw bytes.
@@ -272,7 +284,17 @@ async function readBody(undiciResponse, responseType) {
272
284
  body = rawBody;
273
285
  } else if (responseType === "json") {
274
286
  const text = rawBody.toString("utf-8");
275
- body = text.length ? JSON.parse(text) : undefined;
287
+ if (!text.length) {
288
+ body = undefined;
289
+ } else if (undiciResponse.statusCode >= 400) {
290
+ try {
291
+ body = JSON.parse(text);
292
+ } catch (_err) {
293
+ body = text;
294
+ }
295
+ } else {
296
+ body = JSON.parse(text);
297
+ }
276
298
  } else {
277
299
  body = rawBody.toString("utf-8");
278
300
  }
@@ -304,20 +326,100 @@ function resolveRequestBody(options) {
304
326
 
305
327
  // In-memory cache of successful GET responses, mirroring the HTTP cache cdxgen
306
328
  // historically kept via got + Keyv. Repeated metadata/license lookups during a
307
- // scan are served from here instead of hitting the network again. Set the
308
- // CDXGEN_NO_CACHE environment variable to "true" or "1" to disable it.
329
+ // scan are served from here instead of hitting the network again. It lasts for
330
+ // one run; `CDXGEN_NO_CACHE` and `--no-cache` turn off only the on-disk caches,
331
+ // because nothing gains from asking a registry the same question twice in one
332
+ // scan.
333
+ //
334
+ // Entries expire after an hour and the cache is bounded in size, least
335
+ // recently used first, so a long-running server neither serves stale registry
336
+ // documents nor grows without limit.
309
337
  const responseCache = new Map();
338
+ const RESPONSE_CACHE_TTL_MS = 60 * 60 * 1000;
339
+ const RESPONSE_CACHE_MAX_BYTES = 512 * 1024 * 1024;
340
+ let responseCacheLimits = {
341
+ ttlMs: RESPONSE_CACHE_TTL_MS,
342
+ maxBytes: RESPONSE_CACHE_MAX_BYTES,
343
+ };
344
+ let responseCacheBytes = 0;
345
+
346
+ /**
347
+ * Approximate the memory a cached response holds. The raw bytes are kept,
348
+ * plus the decoded body for text and JSON responses.
349
+ *
350
+ * @param {Object} response Response object.
351
+ * @returns {number} Size in bytes.
352
+ */
353
+ function cachedResponseBytes(response) {
354
+ const raw = response?.rawBody?.length || 0;
355
+ return response?.body === response?.rawBody ? raw : raw * 3;
356
+ }
310
357
 
311
358
  /**
312
- * Determine whether the in-memory HTTP response cache is disabled via the
313
- * CDXGEN_NO_CACHE environment variable. Evaluated per request so tests and
314
- * callers can toggle it at runtime.
359
+ * Read a cached response, dropping it when it has expired and marking it as
360
+ * recently used otherwise.
315
361
  *
316
- * @returns {boolean} True when caching should be skipped.
362
+ * @param {string} key Cache key.
363
+ * @returns {Object|undefined} The cached response.
317
364
  */
318
- export function isCacheDisabled() {
319
- const value = process.env.CDXGEN_NO_CACHE;
320
- return value === "true" || value === "1";
365
+ function cacheGet(key) {
366
+ const entry = responseCache.get(key);
367
+ if (!entry) {
368
+ return undefined;
369
+ }
370
+ responseCache.delete(key);
371
+ if (entry.expiresAt <= Date.now()) {
372
+ responseCacheBytes -= entry.bytes;
373
+ return undefined;
374
+ }
375
+ responseCache.set(key, entry);
376
+ return entry.response;
377
+ }
378
+
379
+ /**
380
+ * Store a response, evicting the least recently used entries beyond the size
381
+ * bound.
382
+ *
383
+ * @param {string} key Cache key.
384
+ * @param {Object} response Response object.
385
+ * @returns {void}
386
+ */
387
+ function cacheSet(key, response) {
388
+ const bytes = cachedResponseBytes(response);
389
+ if (bytes > responseCacheLimits.maxBytes) {
390
+ return;
391
+ }
392
+ const previous = responseCache.get(key);
393
+ if (previous) {
394
+ responseCache.delete(key);
395
+ responseCacheBytes -= previous.bytes;
396
+ }
397
+ responseCache.set(key, {
398
+ response,
399
+ bytes,
400
+ expiresAt: Date.now() + responseCacheLimits.ttlMs,
401
+ });
402
+ responseCacheBytes += bytes;
403
+ for (const [oldestKey, oldest] of responseCache) {
404
+ if (responseCacheBytes <= responseCacheLimits.maxBytes) {
405
+ break;
406
+ }
407
+ responseCache.delete(oldestKey);
408
+ responseCacheBytes -= oldest.bytes;
409
+ }
410
+ }
411
+
412
+ /**
413
+ * Override the response cache bounds. Returns the previous bounds so tests can
414
+ * restore them. Not part of the public API.
415
+ *
416
+ * @param {{ttlMs?: number, maxBytes?: number}} limits New bounds.
417
+ * @returns {{ttlMs: number, maxBytes: number}} Previous bounds.
418
+ */
419
+ export function _setHttpCacheLimits(limits) {
420
+ const previous = responseCacheLimits;
421
+ responseCacheLimits = { ...responseCacheLimits, ...limits };
422
+ return previous;
321
423
  }
322
424
 
323
425
  /**
@@ -327,8 +429,11 @@ export function isCacheDisabled() {
327
429
  */
328
430
  export function clearHttpCache() {
329
431
  responseCache.clear();
432
+ responseCacheBytes = 0;
330
433
  }
331
434
 
435
+ registerRunReset("http:responses", clearHttpCache);
436
+
332
437
  // ---------------------------------------------------------------------------
333
438
  // Test cassette interceptor — the single network seam.
334
439
  //
@@ -397,15 +502,33 @@ export function clearHttpInterceptor() {
397
502
  }
398
503
 
399
504
  /**
400
- * Build the cache key for a request. Only the method and normalized URL are
401
- * used, matching got's default cache key behaviour.
505
+ * Build the cache key for a request.
506
+ *
507
+ * Besides the method and URL, the key carries the response type, which
508
+ * changes how the same bytes are decoded, and a digest of every request
509
+ * header. A response fetched with one credential is therefore never served to
510
+ * a request made with another credential or with none, and a JSON request
511
+ * never receives a text body. Header values only enter the key as a SHA-256
512
+ * digest, so no credential is held in it.
402
513
  *
403
514
  * @param {string} method Uppercased HTTP method.
404
515
  * @param {URL} url Request URL.
516
+ * @param {Object} [headers] Request headers as sent.
517
+ * @param {string} [responseType] Requested body decoding.
405
518
  * @returns {string} Cache key.
406
519
  */
407
- function cacheKeyFor(method, url) {
408
- return `${method}:${url.toString()}`;
520
+ function cacheKeyFor(method, url, headers = {}, responseType = undefined) {
521
+ const headerEntries = Object.entries(headers || {})
522
+ .filter(([, value]) => value !== undefined && value !== null)
523
+ .map(([name, value]) => [
524
+ name.toLowerCase(),
525
+ Array.isArray(value) ? value.join(", ") : String(value),
526
+ ])
527
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
528
+ const headerDigest = headerEntries.length
529
+ ? createHash("sha256").update(JSON.stringify(headerEntries)).digest("hex")
530
+ : "";
531
+ return `${method}:${responseType || "text"}:${url.toString()}:${headerDigest}`;
409
532
  }
410
533
 
411
534
  // The maximum number of redirects to follow when `followRedirect` is enabled.
@@ -587,11 +710,23 @@ async function doRequest(mergedOptions) {
587
710
  const { body, headers } = resolveRequestBody(options);
588
711
  const method = (options.method || "GET").toUpperCase();
589
712
 
590
- // Serve cacheable GET requests from the in-memory cache when enabled.
591
- const cacheable = method === "GET" && !isCacheDisabled();
592
- const cacheKey = cacheable ? cacheKeyFor(method, url) : undefined;
593
- if (cacheable && responseCache.has(cacheKey)) {
594
- return responseCache.get(cacheKey);
713
+ // Serve cacheable GET requests from the in-memory cache when enabled. A
714
+ // request opts out with `cache: false`, as token exchanges do.
715
+ const cacheable = method === "GET" && options.cache !== false;
716
+ const cacheKey = cacheable
717
+ ? cacheKeyFor(method, url, headers, options.responseType)
718
+ : undefined;
719
+ if (cacheable) {
720
+ const cached = cacheGet(cacheKey);
721
+ if (cached) {
722
+ return cached;
723
+ }
724
+ }
725
+
726
+ // Runs only when the request is about to reach the network, so a caller
727
+ // that rate-limits a host does not wait for a response already cached.
728
+ for (const hook of options.hooks?.beforeNetwork || []) {
729
+ await hook(options);
595
730
  }
596
731
 
597
732
  const timeoutMs = resolveTimeout(options.timeout);
@@ -652,7 +787,7 @@ async function doRequest(mergedOptions) {
652
787
 
653
788
  // Only successful responses are cached, matching HTTP cache semantics.
654
789
  if (cacheable && finalResponse.statusCode < 400) {
655
- responseCache.set(cacheKey, finalResponse);
790
+ cacheSet(cacheKey, finalResponse);
656
791
  }
657
792
  return finalResponse;
658
793
  }