@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
@@ -18,7 +18,7 @@ import {
18
18
  readEnvironmentVariable,
19
19
  } from "../core/activity.js";
20
20
  import { PYTHON_CMD, PYTHON_EXCLUDED_COMPONENTS } from "../core/env.js";
21
- import { safeExistsSync, safeSpawnSync } from "../core/fs.js";
21
+ import { getAllFiles, safeExistsSync, safeSpawnSync } from "../core/fs.js";
22
22
  import { thoughtLog } from "../core/logger.js";
23
23
  import { isWin } from "../core/paths.js";
24
24
  import {
@@ -331,6 +331,70 @@ export function get_python_command_from_env(env) {
331
331
  return fallbackCmd;
332
332
  }
333
333
 
334
+ /** Directories that hold installed packages, not the project's own code. */
335
+ const INSTALLED_PACKAGE_DIRS = [
336
+ "**/site-packages/**",
337
+ "**/dist-packages/**",
338
+ "**/.venv/**",
339
+ "**/venv/**",
340
+ "**/.tox/**",
341
+ "**/.nox/**",
342
+ "**/__pypackages__/**",
343
+ "**/node_modules/**",
344
+ ];
345
+
346
+ const projectModuleNamesCache = new Map();
347
+
348
+ /**
349
+ * The top-level packages and modules a project provides, wherever they sit
350
+ * in its tree: each outermost package directory (one with an `__init__.py`
351
+ * whose parent has none), and the module files beside such packages, since
352
+ * their directory is a source root. Directories of installed packages
353
+ * (virtual environments, `site-packages`) are not the project's.
354
+ *
355
+ * @param {string} src Path to the project being analysed
356
+ * @returns {Set<string>} Module names
357
+ */
358
+ export function projectModuleNames(src) {
359
+ const root = resolve(src);
360
+ if (projectModuleNamesCache.has(root)) {
361
+ return projectModuleNamesCache.get(root);
362
+ }
363
+ const names = new Set();
364
+ const sourceRoots = new Set();
365
+ const inits = getAllFiles(root, "**/__init__.py", {
366
+ exclude: INSTALLED_PACKAGE_DIRS,
367
+ });
368
+ for (const init of inits) {
369
+ let pkgDir = dirname(resolve(init));
370
+ while (
371
+ dirname(pkgDir) !== root &&
372
+ dirname(pkgDir).startsWith(root) &&
373
+ safeExistsSync(join(dirname(pkgDir), "__init__.py"))
374
+ ) {
375
+ pkgDir = dirname(pkgDir);
376
+ }
377
+ if (pkgDir === root) {
378
+ continue;
379
+ }
380
+ names.add(basename(pkgDir));
381
+ sourceRoots.add(dirname(pkgDir));
382
+ }
383
+ for (const dir of sourceRoots) {
384
+ try {
385
+ for (const f of readdirSync(dir)) {
386
+ if (f.endsWith(".py") && f !== "__init__.py") {
387
+ names.add(f.slice(0, -3));
388
+ }
389
+ }
390
+ } catch {
391
+ // an unreadable directory provides nothing
392
+ }
393
+ }
394
+ projectModuleNamesCache.set(root, names);
395
+ return names;
396
+ }
397
+
334
398
  /**
335
399
  * Is this import satisfied by a module that lives in the project itself?
336
400
  *
@@ -344,7 +408,9 @@ export function get_python_command_from_env(env) {
344
408
  * filesystem whether the module is ours answers it exactly.
345
409
  *
346
410
  * Checks the project root and a `src/` layout for a module file, a package directory,
347
- * and a PEP 420 namespace package directory.
411
+ * and a PEP 420 namespace package directory, then the top-level packages found
412
+ * anywhere in the project and the modules beside them (tool-heavy repositories
413
+ * keep their Python libraries in places such as `dev_tools/pylibs/`).
348
414
  *
349
415
  * @param {string} src Path to the project being analysed
350
416
  * @param {string} name Imported top-level module name
@@ -354,6 +420,9 @@ export function isFirstPartyModule(src, name) {
354
420
  if (!src || !name || name.includes("/") || name.includes("\\")) {
355
421
  return false;
356
422
  }
423
+ if (projectModuleNames(src).has(name)) {
424
+ return true;
425
+ }
357
426
  for (const root of [src, join(src, "src")]) {
358
427
  if (
359
428
  safeExistsSync(join(root, `${name}.py`)) ||
@@ -378,6 +447,21 @@ export function isFirstPartyModule(src, name) {
378
447
  return false;
379
448
  }
380
449
 
450
+ /**
451
+ * Whether a directory holds Python sources for atom to parse.
452
+ *
453
+ * The search skips the directories the manifest searches skip, such as
454
+ * node_modules and dot directories, and honours the exclude patterns, so a
455
+ * Python file shipped inside an npm package does not count.
456
+ *
457
+ * @param {string} src directory
458
+ * @param {Object} options CLI options
459
+ * @returns {boolean} true when the directory holds at least one Python file
460
+ */
461
+ export function hasPythonSources(src, options = {}) {
462
+ return getAllFiles(src, "**/*.py", options).length > 0;
463
+ }
464
+
381
465
  /**
382
466
  * Method to find python modules by parsing the imports and then checking with PyPI to obtain the latest version
383
467
  *
@@ -390,9 +474,11 @@ export async function getPyModules(src, epkgList, options) {
390
474
  const allImports = {};
391
475
  const dependenciesList = [];
392
476
  let modList = [];
393
- const slicesFile = resolve(
394
- options.depsSlicesFile || options.usagesSlicesFile,
395
- );
477
+ const requestedSlicesFile =
478
+ options.depsSlicesFile || options.usagesSlicesFile;
479
+ let slicesFile = requestedSlicesFile
480
+ ? resolve(requestedSlicesFile)
481
+ : undefined;
396
482
  // Issue: 615 fix. Reuse existing slices file
397
483
  if (slicesFile && safeExistsSync(slicesFile)) {
398
484
  const slicesData = JSON.parse(readFileSync(slicesFile, "utf-8"));
@@ -401,8 +487,18 @@ export async function getPyModules(src, epkgList, options) {
401
487
  } else {
402
488
  modList = slicesData;
403
489
  }
404
- } else {
490
+ } else if (hasPythonSources(src, options)) {
491
+ // A new slices file is kept where it was asked for only when the evidence
492
+ // step will read it again; otherwise atom writes it to a temporary
493
+ // directory instead of the working directory.
494
+ if (!options.evidence) {
495
+ slicesFile = undefined;
496
+ }
405
497
  modList = findAppModules(src, "python", "parsedeps", slicesFile, options);
498
+ } else {
499
+ thoughtLog(
500
+ `There are no Python sources at '${src}', so atom parsedeps has nothing to read.`,
501
+ );
406
502
  }
407
503
  const pyDefaultModules = new Set(PYTHON_STD_MODULES);
408
504
  modList = modList.filter(
@@ -5,6 +5,7 @@ import {
5
5
  recordActivity,
6
6
  } from "../../core/activity.js";
7
7
  import { sanitizeStructuredValueForBom } from "../../core/propertySanitizer.js";
8
+ import { registerRunReset } from "../../core/runState.js";
8
9
  import {
9
10
  detectAiModelVariants,
10
11
  normalizeDetectedVariants,
@@ -167,6 +168,8 @@ export function resetHuggingFaceRemoteCaches() {
167
168
  payloadCache.clear();
168
169
  }
169
170
 
171
+ registerRunReset("huggingface:responses", resetHuggingFaceRemoteCaches);
172
+
170
173
  const normalizeLicenseFilePath = (licensePath) =>
171
174
  String(licensePath || "")
172
175
  .trim()
@@ -15,17 +15,14 @@ import { build, Purl } from "@cdxgen/cdx-purl";
15
15
 
16
16
  // utils.js does not import rubyutils.js, so importing from it here is safe and
17
17
  // does not introduce a cyclic dependency.
18
- import {
19
- cdxgenAgent,
20
- DEBUG_MODE,
21
- readEnvironmentVariable,
22
- } from "../core/activity.js";
18
+ import { DEBUG_MODE, readEnvironmentVariable } from "../core/activity.js";
23
19
  import { shouldFetchLicense } from "../core/env.js";
24
20
  import { getAllFiles, safeExistsSync, safeSpawnSync } from "../core/fs.js";
25
21
  import { thoughtLog } from "../core/logger.js";
26
22
  import { dirNameStr, isWin } from "../core/paths.js";
27
23
  import { mergeDependencies } from "../inventory/depsUtils.js";
28
24
  import {
25
+ gatedGet,
29
26
  prefetchEnabled,
30
27
  prefetchedResponse,
31
28
  prefetchJson,
@@ -913,6 +910,9 @@ export async function enrichGemsFromLocalCache(pkgList, options = {}) {
913
910
  installedPkgs = await parseGemspecData(
914
911
  readFileSync(gemspecFile, { encoding: "utf-8" }),
915
912
  gemspecFile,
913
+ // The gemspec is read only for the fields it holds; the registry
914
+ // round the caller is about to make answers the rest.
915
+ { noFetch: true },
916
916
  );
917
917
  } catch (_err) {
918
918
  // pass
@@ -983,16 +983,21 @@ function applyGemMetadata(p, body) {
983
983
  p.properties.push({ name, value: `${value}` });
984
984
  }
985
985
  };
986
- p.description = body.description || body.summary || "";
987
- if (body.licenses) {
986
+ // The registry fills only what the lockfile, the gemspec and the caches on
987
+ // this machine left open, so a local answer survives the round.
988
+ if (!p.description?.length && (body.description || body.summary)) {
989
+ p.description = body.description || body.summary;
990
+ }
991
+ if (body.licenses && !p.license) {
988
992
  p.license = body.licenses;
989
993
  }
990
994
  if (body.metadata) {
991
- if (body.metadata.source_code_uri) {
995
+ if (body.metadata.source_code_uri && !p.repository) {
992
996
  p.repository = { url: body.metadata.source_code_uri };
993
997
  if (
994
998
  body.homepage_uri &&
995
- body.homepage_uri !== body.metadata.source_code_uri
999
+ body.homepage_uri !== body.metadata.source_code_uri &&
1000
+ !p.homepage
996
1001
  ) {
997
1002
  p.homepage = { url: body.homepage_uri };
998
1003
  }
@@ -1033,7 +1038,7 @@ function applyGemMetadata(p, body) {
1033
1038
  if (!p.externalReferences.length) {
1034
1039
  p.externalReferences = undefined;
1035
1040
  }
1036
- if (body.sha) {
1041
+ if (body.sha && !p._integrity && !p.hashes?.length) {
1037
1042
  p._integrity = `sha256-${body.sha}`;
1038
1043
  }
1039
1044
  // RubyGems reports the authors as a comma separated string. It is split into
@@ -1173,7 +1178,17 @@ export async function getRubyGemsMetadata(pkgList) {
1173
1178
  for (const [name, pkgs] of Object.entries(pkgsByName)) {
1174
1179
  const versionedPkgs = pkgs.filter((p) => p.version);
1175
1180
  if (versionedPkgs.length > 1) {
1181
+ // The listing answers every versioned variant of this gem in one
1182
+ // request. A direct URL is asked for only an entry the listing does
1183
+ // not answer, so it is not prefetched here: the serial path below
1184
+ // requests one when the listing misses.
1176
1185
  addBatchUrl(`${RUBYGEMS_V1_VERSIONS_URL}${name}.json`);
1186
+ for (const p of pkgs) {
1187
+ if (!p.version) {
1188
+ addBatchUrl(gemDirectUrl(p, RUBYGEMS_V2_URL, RUBYGEMS_V1_URL));
1189
+ }
1190
+ }
1191
+ continue;
1177
1192
  }
1178
1193
  for (const p of pkgs) {
1179
1194
  addBatchUrl(gemDirectUrl(p, RUBYGEMS_V2_URL, RUBYGEMS_V1_URL));
@@ -1201,7 +1216,7 @@ export async function getRubyGemsMetadata(pkgList) {
1201
1216
  }
1202
1217
  const res =
1203
1218
  prefetchedResponse(prefetched, versionsUrl) ||
1204
- (await cdxgenAgent.get(versionsUrl, apiOptions));
1219
+ (await gatedGet(versionsUrl, apiOptions));
1205
1220
  versions = res.body;
1206
1221
  }
1207
1222
  if (Array.isArray(versions) && versions.length) {
@@ -1280,7 +1295,7 @@ async function enrichGemFromVersionEndpoint(
1280
1295
  }
1281
1296
  const res =
1282
1297
  prefetchedResponse(prefetched, fullUrl) ||
1283
- (await cdxgenAgent.get(fullUrl, apiOptions));
1298
+ (await gatedGet(fullUrl, apiOptions));
1284
1299
  let body = res.body;
1285
1300
  if (body?.length) {
1286
1301
  body = body[0];
@@ -1565,6 +1580,12 @@ function listGemBindirFiles(gemspecFile, bindir = "bin") {
1565
1580
  * @param {Object} [options] Parse options
1566
1581
  * @param {Object<string, string>} [options.lockedVersions] Locked versions of
1567
1582
  * the gems the project directory serves, by gem name
1583
+ * @param {boolean} [options.projectGemspec] The gemspec describes the project
1584
+ * being scanned. No registry holds the project, so its gem is never looked
1585
+ * up and keeps what the gemspec says.
1586
+ * @param {boolean} [options.noFetch] Read the gemspec for the fields it holds
1587
+ * without asking the registry about the gem. The local cache enricher uses
1588
+ * this, since the caller is responsible for the registry round.
1568
1589
  */
1569
1590
  export async function parseGemspecData(gemspecData, gemspecFile, options = {}) {
1570
1591
  let pkgList = [];
@@ -1983,7 +2004,7 @@ export async function parseGemspecData(gemspecData, gemspecFile, options = {}) {
1983
2004
  } else {
1984
2005
  console.log("Unable to parse", gemspecData, gemspecFile);
1985
2006
  }
1986
- if (shouldFetchLicense()) {
2007
+ if (shouldFetchLicense() && !options.projectGemspec && !options.noFetch) {
1987
2008
  return await getRubyGemsMetadata(pkgList);
1988
2009
  }
1989
2010
  return pkgList;
@@ -1997,11 +2018,24 @@ export async function parseGemspecData(gemspecData, gemspecFile, options = {}) {
1997
2018
  * directory (`PATH` with `remote: .`), which is how Bundler records the gem a
1998
2019
  * project builds when its Gemfile uses the `gemspec` directive.
1999
2020
  *
2021
+ * Gems the lockfile serves from a git remote or from a path outside the
2022
+ * project are never sent to rubygems.org: no public registry holds the release
2023
+ * they pinned. Before the registry round, the caches on this machine are read
2024
+ * first, so a gem whose installed gemspec or compact index entry is present
2025
+ * keeps those answers.
2026
+ *
2000
2027
  * @param {object} gemLockData Gemfile.lock data
2001
2028
  * @param {string} lockFile Lock file
2029
+ * @param {object} [options] Options
2030
+ * @param {string} [options.gemHome] GEM_HOME to read installed gemspecs from
2031
+ * @param {string} [options.compactIndexCacheDir] Bundler compact index cache
2002
2032
  */
2003
- export async function parseGemfileLockData(gemLockData, lockFile) {
2004
- let pkgList = [];
2033
+ export async function parseGemfileLockData(
2034
+ gemLockData,
2035
+ lockFile,
2036
+ options = {},
2037
+ ) {
2038
+ const pkgList = [];
2005
2039
  const pkgnames = {};
2006
2040
  const dependenciesList = [];
2007
2041
  const dependenciesMap = {};
@@ -2207,13 +2241,16 @@ export async function parseGemfileLockData(gemLockData, lockFile) {
2207
2241
  }
2208
2242
  // Bundler locks every gem against exactly one source, so the gem's
2209
2243
  // own top-level line is where its remote belongs. A later duplicate
2210
- // sighting of the same name would be the same release.
2244
+ // sighting of the same name would be the same release. The section
2245
+ // is kept as well: a gem under GIT or PATH is served from a source
2246
+ // no public registry holds.
2211
2247
  if (!gemSourceInfo[name]) {
2212
2248
  gemSourceInfo[name] = {
2213
2249
  remote: lastRemote,
2214
2250
  revision: lastRevision,
2215
2251
  branch: lastBranch,
2216
2252
  tag: lastTag,
2253
+ section: lastSection,
2217
2254
  };
2218
2255
  }
2219
2256
  }
@@ -2369,7 +2406,20 @@ export async function parseGemfileLockData(gemLockData, lockFile) {
2369
2406
  });
2370
2407
  }
2371
2408
  if (shouldFetchLicense()) {
2372
- pkgList = await getRubyGemsMetadata(pkgList);
2409
+ // The caches on this machine answer first: the installed gemspec holds
2410
+ // the licence the registry would be asked for, and the compact index the
2411
+ // checksum, so the registry round below only fills what is still missing.
2412
+ await enrichGemsFromLocalCache(pkgList, options);
2413
+ // The gem the lockfile serves from the project's own directory is the
2414
+ // project itself, and a gem under GIT or PATH is served from a source no
2415
+ // public registry holds; only the rest are looked up.
2416
+ const lookupList = pkgList.filter(
2417
+ (p) =>
2418
+ !projectGemRefs.includes(p["bom-ref"]) &&
2419
+ gemSourceInfo[p.name]?.section !== "GIT" &&
2420
+ gemSourceInfo[p.name]?.section !== "PATH",
2421
+ );
2422
+ await getRubyGemsMetadata(lookupList);
2373
2423
  }
2374
2424
  return { pkgList, dependenciesList, rootList, projectGemRefs };
2375
2425
  }
@@ -10,6 +10,10 @@ import {
10
10
  safeSpawnSync,
11
11
  } from "../core/fs.js";
12
12
  import { dirNameStr } from "../core/paths.js";
13
+ import {
14
+ reportPluginRunFailure,
15
+ resolvePluginSourceDir,
16
+ } from "../core/pluginRun.js";
13
17
  import { applyAlgorithmProperties } from "../inventory/cryptoAlgorithmFamily.js";
14
18
  import { resolvePluginBinary } from "../inventory/plugins.js";
15
19
 
@@ -818,8 +822,14 @@ export function runRusiAnalysis(src, outputFile, options = {}) {
818
822
  return false;
819
823
  }
820
824
 
825
+ // cdxgen may run with its working directory already inside the project
826
+ // while the source argument is relative to a repository root; a plain
827
+ // resolve would join the two into a directory that does not exist. The
828
+ // helper checks the result and falls back to the working directory when it
829
+ // is itself the cargo project under analysis.
830
+ const analysisDir = resolvePluginSourceDir(src, ["Cargo.toml"]);
821
831
  const rusiMode = options.rusiMode === "cryptos" ? "cryptos" : "analyze";
822
- const args = [rusiMode, "--dir", resolve(src), "--out", resolve(outputFile)];
832
+ const args = [rusiMode, "--dir", analysisDir, "--out", resolve(outputFile)];
823
833
 
824
834
  // Set callgraph and data-flow args
825
835
  args.push("--callgraph", options.rusiCallgraph || "static");
@@ -850,18 +860,21 @@ export function runRusiAnalysis(src, outputFile, options = {}) {
850
860
  }
851
861
 
852
862
  const result = safeSpawnSync(executable, args, {
853
- cwd: resolve(src),
863
+ cwd: analysisDir,
854
864
  shell: false,
855
865
  });
856
866
 
857
867
  if (result?.status !== 0 || result?.error || !safeExistsSync(outputFile)) {
858
- if (DEBUG_MODE) {
859
- if (result?.stdout || result?.stderr) {
860
- console.error(result.stdout, result.stderr);
861
- } else {
862
- console.log("Check if the rusi plugin was installed successfully.");
863
- }
864
- }
868
+ reportPluginRunFailure({
869
+ tool: "rusi",
870
+ ecosystem: "rust",
871
+ executable,
872
+ args,
873
+ dir: analysisDir,
874
+ result,
875
+ outputFile,
876
+ options,
877
+ });
865
878
  return false;
866
879
  }
867
880
  return true;