@cdxgen/cdxgen 13.3.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 (372) hide show
  1. package/README.md +12 -9
  2. package/bin/cdxgen-secure.js +15 -0
  3. package/bin/cdxgen.js +115 -31
  4. package/bin/evinse.js +23 -1
  5. package/bin/hbom.js +15 -7
  6. package/bin/repl.js +115 -0
  7. package/bin/sign.js +45 -15
  8. package/bin/tracebom.js +5 -1
  9. package/bin/validate.js +84 -18
  10. package/bin/verify.js +155 -86
  11. package/data/cbomosdb-queries.json +13 -1
  12. package/data/component-tags.json +8 -0
  13. package/data/helpers/init.gradle +46 -21
  14. package/data/queries-darwin.json +25 -1
  15. package/data/queries-win.json +19 -1
  16. package/data/queries.json +41 -0
  17. package/data/remediations.json +344 -1
  18. package/data/rules/cbom-security.yaml +235 -4
  19. package/data/rules/dependency-sources.yaml +67 -0
  20. package/data/rules/go-binaries.yaml +46 -0
  21. package/data/rules/golem-go.yaml +80 -0
  22. package/data/rules/hbom-compliance.yaml +40 -22
  23. package/data/rules/hbom-performance.yaml +7 -1
  24. package/data/rules/host-topology.yaml +1 -1
  25. package/data/rules/obom-runtime.yaml +31 -2
  26. package/data/rules/php-runtime.yaml +143 -0
  27. package/data/rules/scala-evidence.yaml +115 -0
  28. package/lib/audit/index.js +52 -6
  29. package/lib/cli/bomAssembly.js +28 -3
  30. package/lib/cli/cliOptions.js +110 -3
  31. package/lib/cli/index.js +186 -58
  32. package/lib/cli/jsBom.js +50 -29
  33. package/lib/cli/jvmBom.js +779 -146
  34. package/lib/cli/managedBom.js +1021 -261
  35. package/lib/cli/nativeBom.js +364 -58
  36. package/lib/core/activity.js +12 -0
  37. package/lib/core/env.js +47 -17
  38. package/lib/core/fs.js +31 -21
  39. package/lib/core/httpClient.js +233 -69
  40. package/lib/core/pluginRun.js +184 -0
  41. package/lib/core/processTree.js +100 -0
  42. package/lib/core/projectConfig.js +1 -0
  43. package/lib/core/projectTooling.js +97 -0
  44. package/lib/core/propertySanitizer.js +72 -0
  45. package/lib/core/runState.js +39 -0
  46. package/lib/core/toml.js +72 -0
  47. package/lib/ecosystems/asarutils.js +4 -2
  48. package/lib/ecosystems/bunutils.js +5 -7
  49. package/lib/ecosystems/cargoCache.js +231 -8
  50. package/lib/ecosystems/cmakeResolver.js +10 -21
  51. package/lib/ecosystems/cppBuildContext.js +631 -0
  52. package/lib/ecosystems/cppEvidence.js +337 -22
  53. package/lib/ecosystems/cppIncludeResolver.js +294 -0
  54. package/lib/ecosystems/denoutils.js +93 -66
  55. package/lib/ecosystems/ecosystems.js +1967 -788
  56. package/lib/ecosystems/golem.js +578 -18
  57. package/lib/ecosystems/gradleutils.js +340 -46
  58. package/lib/ecosystems/jsEvidence.js +85 -24
  59. package/lib/ecosystems/kosi.js +21 -4
  60. package/lib/ecosystems/npmutils.js +64 -0
  61. package/lib/ecosystems/parsers-dotnet.js +668 -153
  62. package/lib/ecosystems/parsers-elm.js +46 -28
  63. package/lib/ecosystems/parsers-gleam.js +3 -4
  64. package/lib/ecosystems/parsers-go.js +1283 -297
  65. package/lib/ecosystems/parsers-js.js +92 -26
  66. package/lib/ecosystems/parsers-julia.js +2 -3
  67. package/lib/ecosystems/parsers-jvm.js +463 -29
  68. package/lib/ecosystems/parsers-misc.js +405 -93
  69. package/lib/ecosystems/parsers-python.js +152 -20
  70. package/lib/ecosystems/parsers-rust.js +4 -4
  71. package/lib/ecosystems/parsers-terraform.js +1573 -19
  72. package/lib/ecosystems/pythonutils.js +102 -6
  73. package/lib/ecosystems/remote/huggingface.js +3 -0
  74. package/lib/ecosystems/rubyutils.js +73 -20
  75. package/lib/ecosystems/rusi.js +32 -9
  76. package/lib/ecosystems/sbtutils.js +243 -187
  77. package/lib/ecosystems/scalacliutils.js +343 -0
  78. package/lib/ecosystems/scalasem.js +2620 -0
  79. package/lib/ecosystems/terraformRegistry.js +288 -0
  80. package/lib/ecosystems/utils.js +2 -0
  81. package/lib/ecosystems/vendoredCode.js +156 -0
  82. package/lib/evinser/evinser.js +695 -78
  83. package/lib/evinser/swiftsem.js +68 -5
  84. package/lib/helpers/agenticToolutils.js +2 -2
  85. package/lib/helpers/bomSigner.js +576 -128
  86. package/lib/inventory/agentSkillSpec.js +17 -0
  87. package/lib/inventory/aiCollector.js +4 -1
  88. package/lib/inventory/analyzer.js +110 -127
  89. package/lib/inventory/apkInstalledDb.js +243 -0
  90. package/lib/inventory/atomUtils.js +204 -48
  91. package/lib/inventory/auditCategories.js +10 -3
  92. package/lib/inventory/buildToolRateLimit.js +144 -0
  93. package/lib/inventory/cacheCommands.js +2 -1
  94. package/lib/inventory/cacheDir.js +52 -9
  95. package/lib/inventory/certificates.js +2371 -0
  96. package/lib/inventory/cmakeBuildDirs.js +254 -0
  97. package/lib/inventory/communityAiConfigParser.js +4 -9
  98. package/lib/inventory/deps.js +507 -128
  99. package/lib/inventory/display.js +200 -3
  100. package/lib/inventory/dosai.js +747 -101
  101. package/lib/inventory/dosaiParsers.js +310 -15
  102. package/lib/inventory/dynamic.js +96 -8
  103. package/lib/inventory/envcontext.js +168 -5
  104. package/lib/inventory/evidenceUtils.js +590 -132
  105. package/lib/inventory/fetchBatch.js +574 -38
  106. package/lib/inventory/fetchDiskCache.js +597 -0
  107. package/lib/inventory/forgeEnricher.js +14 -5
  108. package/lib/inventory/formulationParsers.js +3 -2
  109. package/lib/inventory/goBuildInfo.js +140 -0
  110. package/lib/inventory/hbom.js +67 -7
  111. package/lib/inventory/hbomAnalysis.js +16 -0
  112. package/lib/inventory/hostTopology.js +21 -0
  113. package/lib/inventory/jvmLocalRepos.js +917 -0
  114. package/lib/inventory/mcpConfigParser.js +2 -3
  115. package/lib/inventory/osPackagePurl.js +244 -0
  116. package/lib/inventory/osPackageResolver.js +89 -87
  117. package/lib/inventory/osinfo.js +79 -31
  118. package/lib/inventory/osqueryTransform.js +114 -5
  119. package/lib/inventory/parentSelection.js +146 -0
  120. package/lib/inventory/phpBuildInfo.js +487 -0
  121. package/lib/inventory/phpHostProbe.js +641 -0
  122. package/lib/inventory/rootfsCerts.js +426 -0
  123. package/lib/inventory/rustFormulationParser.js +3 -4
  124. package/lib/inventory/scalaCoords.js +153 -0
  125. package/lib/inventory/source.js +215 -11
  126. package/lib/inventory/spdx.js +245 -0
  127. package/lib/inventory/traceRunner.js +93 -8
  128. package/lib/inventory/tracedCrypto.js +423 -0
  129. package/lib/managers/binary.js +529 -183
  130. package/lib/managers/docker.js +199 -44
  131. package/lib/managers/oci.js +24 -2
  132. package/lib/parsers/cmakeCache.js +69 -0
  133. package/lib/parsers/cmakeLists.js +16 -2
  134. package/lib/parsers/cmakePresets.js +400 -0
  135. package/lib/parsers/compileCommands.js +557 -0
  136. package/lib/parsers/email.js +277 -0
  137. package/lib/parsers/largeJson.js +34 -2
  138. package/lib/parsers/strictJson.js +322 -0
  139. package/lib/parsers/terraformConfig.js +1073 -0
  140. package/lib/parsers/terraformSource.js +652 -0
  141. package/lib/server/server.js +209 -1
  142. package/lib/stages/postgen/annotator.js +17 -1
  143. package/lib/stages/postgen/introspection/markers.js +1 -1
  144. package/lib/stages/postgen/introspection/reflect.js +4 -1
  145. package/lib/stages/postgen/postgen.js +20 -6
  146. package/lib/stages/postgen/ruleEngine.js +58 -1
  147. package/lib/stages/postgen/specVersionCompat.js +62 -0
  148. package/lib/stages/pregen/pregen.js +117 -5
  149. package/lib/validator/bomValidator.js +26 -5
  150. package/lib/validator/generated/runtime.mjs +13 -0
  151. package/lib/validator/generated/validate-1.6.mjs +26 -7
  152. package/lib/validator/generated/validate-1.7.mjs +31 -12
  153. package/lib/validator/generated/validate-2.0.mjs +30 -11
  154. package/lib/validator/index.js +25 -10
  155. package/lib/validator/reporters/json.js +1 -0
  156. package/package.json +23 -23
  157. package/types/bin/cdxgen-secure.d.ts +3 -0
  158. package/types/bin/cdxgen-secure.d.ts.map +1 -0
  159. package/types/bin/repl.d.ts.map +1 -1
  160. package/types/lib/audit/index.d.ts.map +1 -1
  161. package/types/lib/cli/bomAssembly.d.ts +9 -2
  162. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  163. package/types/lib/cli/cliOptions.d.ts +43 -0
  164. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  165. package/types/lib/cli/index.d.ts.map +1 -1
  166. package/types/lib/cli/jsBom.d.ts.map +1 -1
  167. package/types/lib/cli/jvmBom.d.ts +44 -1
  168. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  169. package/types/lib/cli/managedBom.d.ts +7 -4
  170. package/types/lib/cli/managedBom.d.ts.map +1 -1
  171. package/types/lib/cli/nativeBom.d.ts +3 -1
  172. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  173. package/types/lib/core/activity.d.ts.map +1 -1
  174. package/types/lib/core/env.d.ts +27 -2
  175. package/types/lib/core/env.d.ts.map +1 -1
  176. package/types/lib/core/fs.d.ts.map +1 -1
  177. package/types/lib/core/httpClient.d.ts +13 -8
  178. package/types/lib/core/httpClient.d.ts.map +1 -1
  179. package/types/lib/core/pluginRun.d.ts +97 -0
  180. package/types/lib/core/pluginRun.d.ts.map +1 -0
  181. package/types/lib/core/processTree.d.ts +36 -0
  182. package/types/lib/core/processTree.d.ts.map +1 -0
  183. package/types/lib/core/projectConfig.d.ts.map +1 -1
  184. package/types/lib/core/projectTooling.d.ts +41 -0
  185. package/types/lib/core/projectTooling.d.ts.map +1 -0
  186. package/types/lib/core/propertySanitizer.d.ts +26 -0
  187. package/types/lib/core/propertySanitizer.d.ts.map +1 -1
  188. package/types/lib/core/runState.d.ts +29 -0
  189. package/types/lib/core/runState.d.ts.map +1 -0
  190. package/types/lib/core/toml.d.ts +20 -0
  191. package/types/lib/core/toml.d.ts.map +1 -0
  192. package/types/lib/ecosystems/asarutils.d.ts.map +1 -1
  193. package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
  194. package/types/lib/ecosystems/cargoCache.d.ts +12 -0
  195. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -1
  196. package/types/lib/ecosystems/cmakeResolver.d.ts +5 -3
  197. package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
  198. package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
  199. package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
  200. package/types/lib/ecosystems/cppEvidence.d.ts +19 -3
  201. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  202. package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
  203. package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
  204. package/types/lib/ecosystems/denoutils.d.ts +9 -3
  205. package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
  206. package/types/lib/ecosystems/ecosystems.d.ts +62 -13
  207. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  208. package/types/lib/ecosystems/golem.d.ts +81 -1
  209. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  210. package/types/lib/ecosystems/gradleutils.d.ts +59 -7
  211. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  212. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  213. package/types/lib/ecosystems/kosi.d.ts.map +1 -1
  214. package/types/lib/ecosystems/npmutils.d.ts +15 -0
  215. package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
  216. package/types/lib/ecosystems/parsers-dotnet.d.ts +155 -5
  217. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  218. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
  219. package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
  220. package/types/lib/ecosystems/parsers-go.d.ts +119 -5
  221. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  222. package/types/lib/ecosystems/parsers-js.d.ts +6 -1
  223. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  224. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
  225. package/types/lib/ecosystems/parsers-jvm.d.ts +74 -3
  226. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  227. package/types/lib/ecosystems/parsers-misc.d.ts +21 -59
  228. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  229. package/types/lib/ecosystems/parsers-python.d.ts +57 -6
  230. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  231. package/types/lib/ecosystems/parsers-terraform.d.ts +38 -21
  232. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
  233. package/types/lib/ecosystems/pythonutils.d.ts +26 -1
  234. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  235. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
  236. package/types/lib/ecosystems/rubyutils.d.ts +21 -1
  237. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  238. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  239. package/types/lib/ecosystems/sbtutils.d.ts +88 -2
  240. package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
  241. package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
  242. package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
  243. package/types/lib/ecosystems/scalasem.d.ts +311 -0
  244. package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
  245. package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
  246. package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
  247. package/types/lib/ecosystems/utils.d.ts +2 -2
  248. package/types/lib/ecosystems/utils.d.ts.map +1 -1
  249. package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
  250. package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
  251. package/types/lib/evinser/evinser.d.ts +100 -8
  252. package/types/lib/evinser/evinser.d.ts.map +1 -1
  253. package/types/lib/evinser/swiftsem.d.ts +10 -0
  254. package/types/lib/evinser/swiftsem.d.ts.map +1 -1
  255. package/types/lib/helpers/bomSigner.d.ts +80 -4
  256. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  257. package/types/lib/inventory/agentSkillSpec.d.ts +13 -0
  258. package/types/lib/inventory/agentSkillSpec.d.ts.map +1 -0
  259. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  260. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  261. package/types/lib/inventory/apkInstalledDb.d.ts +42 -0
  262. package/types/lib/inventory/apkInstalledDb.d.ts.map +1 -0
  263. package/types/lib/inventory/atomUtils.d.ts +96 -10
  264. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  265. package/types/lib/inventory/auditCategories.d.ts +3 -0
  266. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  267. package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
  268. package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
  269. package/types/lib/inventory/cacheCommands.d.ts +2 -1
  270. package/types/lib/inventory/cacheCommands.d.ts.map +1 -1
  271. package/types/lib/inventory/cacheDir.d.ts +12 -4
  272. package/types/lib/inventory/cacheDir.d.ts.map +1 -1
  273. package/types/lib/inventory/certificates.d.ts +205 -0
  274. package/types/lib/inventory/certificates.d.ts.map +1 -0
  275. package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
  276. package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
  277. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  278. package/types/lib/inventory/deps.d.ts +61 -2
  279. package/types/lib/inventory/deps.d.ts.map +1 -1
  280. package/types/lib/inventory/display.d.ts +18 -0
  281. package/types/lib/inventory/display.d.ts.map +1 -1
  282. package/types/lib/inventory/dosai.d.ts +202 -14
  283. package/types/lib/inventory/dosai.d.ts.map +1 -1
  284. package/types/lib/inventory/dosaiParsers.d.ts +53 -7
  285. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
  286. package/types/lib/inventory/dynamic.d.ts +16 -1
  287. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  288. package/types/lib/inventory/envcontext.d.ts +40 -2
  289. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  290. package/types/lib/inventory/evidenceUtils.d.ts +48 -14
  291. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  292. package/types/lib/inventory/fetchBatch.d.ts +83 -14
  293. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  294. package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
  295. package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
  296. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  297. package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
  298. package/types/lib/inventory/goBuildInfo.d.ts +10 -0
  299. package/types/lib/inventory/goBuildInfo.d.ts.map +1 -0
  300. package/types/lib/inventory/hbom.d.ts.map +1 -1
  301. package/types/lib/inventory/hbomAnalysis.d.ts +10 -0
  302. package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -1
  303. package/types/lib/inventory/hostTopology.d.ts.map +1 -1
  304. package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
  305. package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
  306. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  307. package/types/lib/inventory/osPackagePurl.d.ts +99 -0
  308. package/types/lib/inventory/osPackagePurl.d.ts.map +1 -0
  309. package/types/lib/inventory/osPackageResolver.d.ts +6 -2
  310. package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
  311. package/types/lib/inventory/osinfo.d.ts +31 -6
  312. package/types/lib/inventory/osinfo.d.ts.map +1 -1
  313. package/types/lib/inventory/osqueryTransform.d.ts +51 -0
  314. package/types/lib/inventory/osqueryTransform.d.ts.map +1 -1
  315. package/types/lib/inventory/parentSelection.d.ts +34 -0
  316. package/types/lib/inventory/parentSelection.d.ts.map +1 -0
  317. package/types/lib/inventory/phpBuildInfo.d.ts +142 -0
  318. package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -0
  319. package/types/lib/inventory/phpHostProbe.d.ts +35 -0
  320. package/types/lib/inventory/phpHostProbe.d.ts.map +1 -0
  321. package/types/lib/inventory/rootfsCerts.d.ts +27 -0
  322. package/types/lib/inventory/rootfsCerts.d.ts.map +1 -0
  323. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
  324. package/types/lib/inventory/scalaCoords.d.ts +80 -0
  325. package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
  326. package/types/lib/inventory/source.d.ts +28 -0
  327. package/types/lib/inventory/source.d.ts.map +1 -1
  328. package/types/lib/inventory/spdx.d.ts +24 -0
  329. package/types/lib/inventory/spdx.d.ts.map +1 -1
  330. package/types/lib/inventory/traceRunner.d.ts +16 -1
  331. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  332. package/types/lib/inventory/tracedCrypto.d.ts +64 -0
  333. package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
  334. package/types/lib/managers/binary.d.ts +10 -0
  335. package/types/lib/managers/binary.d.ts.map +1 -1
  336. package/types/lib/managers/docker.d.ts +12 -0
  337. package/types/lib/managers/docker.d.ts.map +1 -1
  338. package/types/lib/managers/oci.d.ts +3 -1
  339. package/types/lib/managers/oci.d.ts.map +1 -1
  340. package/types/lib/parsers/cmakeCache.d.ts +16 -0
  341. package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
  342. package/types/lib/parsers/cmakeLists.d.ts +2 -1
  343. package/types/lib/parsers/cmakeLists.d.ts.map +1 -1
  344. package/types/lib/parsers/cmakePresets.d.ts +78 -0
  345. package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
  346. package/types/lib/parsers/compileCommands.d.ts +115 -0
  347. package/types/lib/parsers/compileCommands.d.ts.map +1 -0
  348. package/types/lib/parsers/email.d.ts +19 -0
  349. package/types/lib/parsers/email.d.ts.map +1 -0
  350. package/types/lib/parsers/largeJson.d.ts +13 -0
  351. package/types/lib/parsers/largeJson.d.ts.map +1 -1
  352. package/types/lib/parsers/strictJson.d.ts +46 -0
  353. package/types/lib/parsers/strictJson.d.ts.map +1 -0
  354. package/types/lib/parsers/terraformConfig.d.ts +70 -0
  355. package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
  356. package/types/lib/parsers/terraformSource.d.ts +66 -0
  357. package/types/lib/parsers/terraformSource.d.ts.map +1 -0
  358. package/types/lib/server/server.d.ts +8 -0
  359. package/types/lib/server/server.d.ts.map +1 -1
  360. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  361. package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -1
  362. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  363. package/types/lib/stages/postgen/ruleEngine.d.ts +7 -4
  364. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  365. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  366. package/types/lib/stages/pregen/pregen.d.ts +14 -1
  367. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  368. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  369. package/types/lib/validator/index.d.ts +8 -0
  370. package/types/lib/validator/index.d.ts.map +1 -1
  371. package/types/lib/validator/reporters/json.d.ts.map +1 -1
  372. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
@@ -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
@@ -1,4 +1,4 @@
1
- import { join } from "node:path";
1
+ import { join, win32 } from "node:path";
2
2
 
3
3
  import {
4
4
  buildReadCountSuffix,
@@ -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
  /**
@@ -293,6 +301,13 @@ export const SWIFT_CMD = readEnvironmentVariable("SWIFT_CMD") || "swift";
293
301
  /** Resolved Ruby executable command (RUBY_CMD env var, or "ruby"). */
294
302
  export const RUBY_CMD = readEnvironmentVariable("RUBY_CMD") || "ruby";
295
303
 
304
+ /** Resolved PHP executable command (PHP_CMD env var, or "php"). */
305
+ export const PHP_CMD = readEnvironmentVariable("PHP_CMD") || "php";
306
+
307
+ /** Resolved Composer executable command (COMPOSER_CMD env var, or "composer"). */
308
+ export const COMPOSER_CMD =
309
+ readEnvironmentVariable("COMPOSER_CMD") || "composer";
310
+
296
311
  /** Python package components that can be excluded from the generated BOM. */
297
312
  export const PYTHON_EXCLUDED_COMPONENTS = [
298
313
  "pip",
@@ -457,7 +472,7 @@ export const PROJECT_TYPE_ALIASES = {
457
472
  perl: ["perl", "cpan", "carton", "cpanfile"],
458
473
  spack: ["spack"],
459
474
  terraform: ["terraform", "opentofu", "tf"],
460
- c: ["c", "cpp", "c++", "conan", "collider"],
475
+ c: ["c", "cpp", "c++", "conan", "collider", "cmake", "meson", "vcpkg"],
461
476
  clojure: ["clojure", "edn", "clj", "leiningen"],
462
477
  github: ["github", "actions"],
463
478
  hbom: ["hbom", "hardware"],
@@ -493,7 +508,7 @@ export const PROJECT_TYPE_ALIASES = {
493
508
  binary: ["binary", "blint"],
494
509
  oci: ["docker", "image", "oci", "container", "podman", "rootfs", "oci-dir"],
495
510
  cocoa: ["cocoa", "cocoapods", "objective-c", "swift", "ios"],
496
- scala: ["scala", "scala3", "sbt", "mill"],
511
+ scala: ["scala", "scala3", "sbt", "mill", "scala-cli"],
497
512
  nix: ["nix", "nixos", "flake"],
498
513
  zig: ["zig", "zon"],
499
514
  gleam: ["gleam"],
@@ -699,23 +714,38 @@ export function hasAnyProjectType(projectTypes, options, defaultStatus = true) {
699
714
  return shouldInclude;
700
715
  }
701
716
 
717
+ /**
718
+ * Extract the lowercase command name from an invoked command path, without
719
+ * directories or a trailing `.js`, `.cjs`, `.mjs` or `.exe` extension. Both
720
+ * `/` and `\\` separators are handled, so a Windows path is inspected the
721
+ * same way on any host.
722
+ *
723
+ * @param {string} [commandPath] Invoked command path or name
724
+ * @returns {string} Lowercase command name
725
+ */
726
+ export function getCommandName(commandPath) {
727
+ return win32
728
+ .basename(`${commandPath || ""}`)
729
+ .toLowerCase()
730
+ .replace(/\.(?:[cm]?js|exe)$/u, "");
731
+ }
732
+
702
733
  /**
703
734
  * Determine whether the predictive dependency audit should run for the current
704
735
  * CLI invocation.
705
736
  *
706
- * OBOM-focused runs (`obom` or explicit `-t os` / OS aliases only) should keep
707
- * the direct BOM audit findings but skip the predictive dependency audit.
737
+ * OBOM and HBOM runs (commands named `obom` or `hbom`, including `hbom-slim`)
738
+ * and explicit `-t os` / OS / hardware project types alone keep the direct BOM
739
+ * audit findings but skip the predictive dependency audit. Only the command
740
+ * name is tested, never the directories leading to it.
708
741
  *
709
742
  * @param {object} options CLI options
710
743
  * @param {string} [commandPath] Invoked command path or name
711
744
  * @returns {boolean} True when predictive dependency audit should run
712
745
  */
713
746
  export function shouldRunPredictiveBomAudit(options, commandPath) {
714
- const normalizedCommandPath = `${commandPath || ""}`.toLowerCase();
715
- if (normalizedCommandPath.includes("obom")) {
716
- return false;
717
- }
718
- if (normalizedCommandPath.includes("hbom")) {
747
+ const commandName = getCommandName(commandPath);
748
+ if (commandName.startsWith("obom") || commandName.startsWith("hbom")) {
719
749
  return false;
720
750
  }
721
751
  const projectTypes = Array.isArray(options?.projectType)
package/lib/core/fs.js CHANGED
@@ -522,8 +522,13 @@ function isWindowsShellHijackRisk(command, options) {
522
522
  const absCwd = resolve(cwd);
523
523
  for (const candidate of candidates) {
524
524
  const candidatePath = path.join(absCwd, candidate);
525
- if (existsSync(candidatePath)) {
526
- return true;
525
+ // cmd.exe only runs files, so a directory named like the tool is harmless.
526
+ try {
527
+ if (statSync(candidatePath).isFile()) {
528
+ return true;
529
+ }
530
+ } catch (_err) {
531
+ // Not present.
527
532
  }
528
533
  }
529
534
  return false;
@@ -577,7 +582,7 @@ export function shellQuoteArgument(arg, forWindows = isWin) {
577
582
  backslashes++;
578
583
  quoted += ch;
579
584
  } else if (ch === '"') {
580
- quoted += "\\".repeat(backslashes) + '\\"';
585
+ quoted += `${"\\".repeat(backslashes)}\\"`;
581
586
  backslashes = 0;
582
587
  } else {
583
588
  quoted += ch;
@@ -831,24 +836,28 @@ export function safeSpawnSync(command, args, options) {
831
836
  error: new Error("No execute permission"),
832
837
  };
833
838
  }
839
+ // cmd.exe looks in the working directory before PATH, so a scanned project
840
+ // could otherwise replace the tool cdxgen means to run. This holds outside
841
+ // secure mode too, since --no-install-deps scans still run version probes
842
+ // and read-only tools with the project as the working directory.
843
+ if (isWindowsShellHijackRisk(command, options)) {
844
+ const blockedReason = `${command} matches local file in cwd (Windows shell hijack risk)`;
845
+ console.warn(`\x1b[1;31mSecurity Alert: ${blockedReason}\x1b[0m`);
846
+ recordActivity({
847
+ kind: activityDescriptor.kind,
848
+ ...activityDescriptor.metadata,
849
+ reason: blockedReason,
850
+ status: "blocked",
851
+ target: activityDescriptor.target,
852
+ });
853
+ return {
854
+ status: 1,
855
+ stdout: undefined,
856
+ stderr: undefined,
857
+ error: new Error(blockedReason),
858
+ };
859
+ }
834
860
  if (isSecureMode) {
835
- if (isWindowsShellHijackRisk(command, options)) {
836
- const blockedReason = `${command} matches local file in cwd (Windows shell hijack risk)`;
837
- console.warn(`\x1b[1;31mSecurity Alert: ${blockedReason}\x1b[0m`);
838
- recordActivity({
839
- kind: activityDescriptor.kind,
840
- ...activityDescriptor.metadata,
841
- reason: blockedReason,
842
- status: "blocked",
843
- target: activityDescriptor.target,
844
- });
845
- return {
846
- status: 1,
847
- stdout: undefined,
848
- stderr: undefined,
849
- error: new Error(blockedReason),
850
- };
851
- }
852
861
  if (options?.cwd && options.cwd !== resolve(options.cwd)) {
853
862
  if (DEBUG_MODE) {
854
863
  console.log(
@@ -1025,7 +1034,8 @@ export const MAX_BUFFER =
1025
1034
  export function getAllFiles(dirPath, pattern, options = {}) {
1026
1035
  let ignoreList = [];
1027
1036
  if (!options.noIgnore) {
1028
- ignoreList.push("**/.hg/**", "**/.git/**");
1037
+ // .chen holds chen's AST cache, which atom writes into a C/C++ source tree
1038
+ ignoreList.push("**/.hg/**", "**/.git/**", "**/.chen/**");
1029
1039
  // Only ignore node_modules if the caller is not looking for package.json
1030
1040
  if (!pattern.includes("package.json") && !options.includeNodeModulesDir) {
1031
1041
  ignoreList.push("**/node_modules/**");
@@ -1,13 +1,11 @@
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
 
5
- import {
6
- Agent,
7
- EnvHttpProxyAgent,
8
- interceptors,
9
- request as undiciRequest,
10
- } from "undici";
6
+ import { Agent, EnvHttpProxyAgent, request as undiciRequest } from "undici";
7
+
8
+ import { registerRunReset } from "./runState.js";
11
9
 
12
10
  // Environment variables here are read with raw `process.env` rather than
13
11
  // `readEnvironmentVariable`: that helper lives in activity.js, which imports
@@ -41,9 +39,8 @@ import {
41
39
  * `Content-Encoding` header, matching `got`'s transparent decoding.
42
40
  * - Proxy support via the standard `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY`
43
41
  * 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.
42
+ * - An in-memory GET response cache that replaces the got + Keyv cache cdxgen
43
+ * previously relied on. A request opts out with `cache: false`.
47
44
  * - Response objects exposing `statusCode`, `headers`, `body`, `rawBody`,
48
45
  * `url` and `request.options`.
49
46
  * - A lazily-resolved `.json()` method on the returned promise, mirroring
@@ -137,7 +134,12 @@ function mergeHeaders(base = {}, override = {}) {
137
134
  * `beforeError` arrays.
138
135
  */
139
136
  function mergeHooks(base = {}, override = {}) {
140
- const hookNames = ["beforeRequest", "afterResponse", "beforeError"];
137
+ const hookNames = [
138
+ "beforeRequest",
139
+ "beforeNetwork",
140
+ "afterResponse",
141
+ "beforeError",
142
+ ];
141
143
  const merged = {};
142
144
  for (const name of hookNames) {
143
145
  merged[name] = [...(base?.[name] || []), ...(override?.[name] || [])];
@@ -253,6 +255,11 @@ function decompressBody(rawBody, contentEncoding) {
253
255
  * Read and decode an undici response body according to the requested
254
256
  * `responseType`.
255
257
  *
258
+ * An error response often carries a body in another format than the one asked
259
+ * for, such as the HTML page a CDN sends with a 429. Its JSON body is decoded
260
+ * when it parses and kept as text otherwise, so the caller sees the status
261
+ * rather than a parse error.
262
+ *
256
263
  * @param {Object} undiciResponse Response returned by `undici.request`.
257
264
  * @param {string} [responseType] One of `"json"`, `"buffer"` or `"text"`.
258
265
  * @returns {Promise<{body: any, rawBody: Buffer}>} Decoded body and raw bytes.
@@ -272,7 +279,17 @@ async function readBody(undiciResponse, responseType) {
272
279
  body = rawBody;
273
280
  } else if (responseType === "json") {
274
281
  const text = rawBody.toString("utf-8");
275
- body = text.length ? JSON.parse(text) : undefined;
282
+ if (!text.length) {
283
+ body = undefined;
284
+ } else if (undiciResponse.statusCode >= 400) {
285
+ try {
286
+ body = JSON.parse(text);
287
+ } catch (_err) {
288
+ body = text;
289
+ }
290
+ } else {
291
+ body = JSON.parse(text);
292
+ }
276
293
  } else {
277
294
  body = rawBody.toString("utf-8");
278
295
  }
@@ -304,20 +321,100 @@ function resolveRequestBody(options) {
304
321
 
305
322
  // In-memory cache of successful GET responses, mirroring the HTTP cache cdxgen
306
323
  // 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.
324
+ // scan are served from here instead of hitting the network again. It lasts for
325
+ // one run; `CDXGEN_NO_CACHE` and `--no-cache` turn off only the on-disk caches,
326
+ // because nothing gains from asking a registry the same question twice in one
327
+ // scan.
328
+ //
329
+ // Entries expire after an hour and the cache is bounded in size, least
330
+ // recently used first, so a long-running server neither serves stale registry
331
+ // documents nor grows without limit.
309
332
  const responseCache = new Map();
333
+ const RESPONSE_CACHE_TTL_MS = 60 * 60 * 1000;
334
+ const RESPONSE_CACHE_MAX_BYTES = 512 * 1024 * 1024;
335
+ let responseCacheLimits = {
336
+ ttlMs: RESPONSE_CACHE_TTL_MS,
337
+ maxBytes: RESPONSE_CACHE_MAX_BYTES,
338
+ };
339
+ let responseCacheBytes = 0;
340
+
341
+ /**
342
+ * Approximate the memory a cached response holds. The raw bytes are kept,
343
+ * plus the decoded body for text and JSON responses.
344
+ *
345
+ * @param {Object} response Response object.
346
+ * @returns {number} Size in bytes.
347
+ */
348
+ function cachedResponseBytes(response) {
349
+ const raw = response?.rawBody?.length || 0;
350
+ return response?.body === response?.rawBody ? raw : raw * 3;
351
+ }
352
+
353
+ /**
354
+ * Read a cached response, dropping it when it has expired and marking it as
355
+ * recently used otherwise.
356
+ *
357
+ * @param {string} key Cache key.
358
+ * @returns {Object|undefined} The cached response.
359
+ */
360
+ function cacheGet(key) {
361
+ const entry = responseCache.get(key);
362
+ if (!entry) {
363
+ return undefined;
364
+ }
365
+ responseCache.delete(key);
366
+ if (entry.expiresAt <= Date.now()) {
367
+ responseCacheBytes -= entry.bytes;
368
+ return undefined;
369
+ }
370
+ responseCache.set(key, entry);
371
+ return entry.response;
372
+ }
373
+
374
+ /**
375
+ * Store a response, evicting the least recently used entries beyond the size
376
+ * bound.
377
+ *
378
+ * @param {string} key Cache key.
379
+ * @param {Object} response Response object.
380
+ * @returns {void}
381
+ */
382
+ function cacheSet(key, response) {
383
+ const bytes = cachedResponseBytes(response);
384
+ if (bytes > responseCacheLimits.maxBytes) {
385
+ return;
386
+ }
387
+ const previous = responseCache.get(key);
388
+ if (previous) {
389
+ responseCache.delete(key);
390
+ responseCacheBytes -= previous.bytes;
391
+ }
392
+ responseCache.set(key, {
393
+ response,
394
+ bytes,
395
+ expiresAt: Date.now() + responseCacheLimits.ttlMs,
396
+ });
397
+ responseCacheBytes += bytes;
398
+ for (const [oldestKey, oldest] of responseCache) {
399
+ if (responseCacheBytes <= responseCacheLimits.maxBytes) {
400
+ break;
401
+ }
402
+ responseCache.delete(oldestKey);
403
+ responseCacheBytes -= oldest.bytes;
404
+ }
405
+ }
310
406
 
311
407
  /**
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.
408
+ * Override the response cache bounds. Returns the previous bounds so tests can
409
+ * restore them. Not part of the public API.
315
410
  *
316
- * @returns {boolean} True when caching should be skipped.
411
+ * @param {{ttlMs?: number, maxBytes?: number}} limits New bounds.
412
+ * @returns {{ttlMs: number, maxBytes: number}} Previous bounds.
317
413
  */
318
- export function isCacheDisabled() {
319
- const value = process.env.CDXGEN_NO_CACHE;
320
- return value === "true" || value === "1";
414
+ export function _setHttpCacheLimits(limits) {
415
+ const previous = responseCacheLimits;
416
+ responseCacheLimits = { ...responseCacheLimits, ...limits };
417
+ return previous;
321
418
  }
322
419
 
323
420
  /**
@@ -327,8 +424,11 @@ export function isCacheDisabled() {
327
424
  */
328
425
  export function clearHttpCache() {
329
426
  responseCache.clear();
427
+ responseCacheBytes = 0;
330
428
  }
331
429
 
430
+ registerRunReset("http:responses", clearHttpCache);
431
+
332
432
  // ---------------------------------------------------------------------------
333
433
  // Test cassette interceptor — the single network seam.
334
434
  //
@@ -397,34 +497,45 @@ export function clearHttpInterceptor() {
397
497
  }
398
498
 
399
499
  /**
400
- * Build the cache key for a request. Only the method and normalized URL are
401
- * used, matching got's default cache key behaviour.
500
+ * Build the cache key for a request.
501
+ *
502
+ * Besides the method and URL, the key carries the response type, which
503
+ * changes how the same bytes are decoded, and a digest of every request
504
+ * header. A response fetched with one credential is therefore never served to
505
+ * a request made with another credential or with none, and a JSON request
506
+ * never receives a text body. Header values only enter the key as a SHA-256
507
+ * digest, so no credential is held in it.
402
508
  *
403
509
  * @param {string} method Uppercased HTTP method.
404
510
  * @param {URL} url Request URL.
511
+ * @param {Object} [headers] Request headers as sent.
512
+ * @param {string} [responseType] Requested body decoding.
405
513
  * @returns {string} Cache key.
406
514
  */
407
- function cacheKeyFor(method, url) {
408
- return `${method}:${url.toString()}`;
515
+ function cacheKeyFor(method, url, headers = {}, responseType = undefined) {
516
+ const headerEntries = Object.entries(headers || {})
517
+ .filter(([, value]) => value !== undefined && value !== null)
518
+ .map(([name, value]) => [
519
+ name.toLowerCase(),
520
+ Array.isArray(value) ? value.join(", ") : String(value),
521
+ ])
522
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
523
+ const headerDigest = headerEntries.length
524
+ ? createHash("sha256").update(JSON.stringify(headerEntries)).digest("hex")
525
+ : "";
526
+ return `${method}:${responseType || "text"}:${url.toString()}:${headerDigest}`;
409
527
  }
410
528
 
411
529
  // The maximum number of redirects to follow when `followRedirect` is enabled.
412
530
  const MAX_REDIRECTIONS = 10;
413
531
 
414
- // Bun ships its own bundled implementation of the `undici` module which, as of
415
- // Bun 1.2, does not expose the `Agent.prototype.compose` composition API nor the
416
- // `interceptors.redirect` interceptor that undici v7 uses for redirect handling
417
- // (see cdxgen issue #4245). Detect the Bun runtime so we can fall back to plain
418
- // dispatchers and follow redirects manually instead of crashing at module load.
532
+ // Bun ships its own bundled implementation of the `undici` module, which lacks
533
+ // `EnvHttpProxyAgent` (see cdxgen issue #4245).
419
534
  const isBun = typeof globalThis.Bun !== "undefined";
420
535
 
421
- // Reusable pooled dispatchers. On Node.js, undici composes redirect handling as
422
- // an interceptor (there is no per-request `maxRedirections` option in undici v7),
423
- // so we pre-build the small set of dispatcher variants we need instead of
424
- // allocating a fresh Agent per request (which would defeat connection pooling).
425
- // On Bun, `compose`/`interceptors.redirect` are unavailable, so the redirect
426
- // dispatchers are just the plain agents and redirects are handled in
427
- // `requestWithRedirects` below.
536
+ // Reusable pooled dispatchers, one per TLS verification setting, so requests
537
+ // share connections instead of allocating a fresh Agent each time. Redirects
538
+ // are followed in `requestWithRedirects` below on every runtime.
428
539
  // Whether standard proxy environment variables are set. When they are, requests
429
540
  // must be routed through the proxy. Because this client always passes an
430
541
  // explicit per-request `dispatcher`, it overrides Node's global (potentially
@@ -461,51 +572,72 @@ const defaultAgent = createBaseAgent();
461
572
  const insecureAgent = createBaseAgent({
462
573
  connect: { rejectUnauthorized: false },
463
574
  });
464
- const redirectDispatcher = isBun
465
- ? defaultAgent
466
- : defaultAgent.compose(
467
- interceptors.redirect({ maxRedirections: MAX_REDIRECTIONS }),
468
- );
469
- const insecureRedirectDispatcher = isBun
470
- ? insecureAgent
471
- : insecureAgent.compose(
472
- interceptors.redirect({ maxRedirections: MAX_REDIRECTIONS }),
473
- );
474
575
 
475
576
  /**
476
- * Pick the undici dispatcher matching the request's redirect and TLS
477
- * verification preferences.
577
+ * Pick the undici dispatcher matching the request's TLS verification
578
+ * preference. Redirects are followed by `requestWithRedirects`, not by the
579
+ * dispatcher.
478
580
  *
479
- * @param {boolean} followRedirect Whether HTTP redirects should be followed.
480
581
  * @param {boolean} insecure Whether TLS certificate verification is disabled.
481
582
  * @returns {import("undici").Dispatcher} The dispatcher to use for the request.
482
583
  */
483
- function selectDispatcher(followRedirect, insecure) {
484
- if (insecure) {
485
- return followRedirect ? insecureRedirectDispatcher : insecureAgent;
486
- }
487
- return followRedirect ? redirectDispatcher : defaultAgent;
584
+ function selectDispatcher(insecure) {
585
+ return insecure ? insecureAgent : defaultAgent;
488
586
  }
489
587
 
490
588
  // HTTP status codes that represent a redirect with a `location` header.
491
589
  const REDIRECT_STATUS_CODES = new Set([301, 302, 303, 307, 308]);
492
590
 
591
+ // Request headers that may follow a redirect to a different origin. Everything
592
+ // else, such as Authorization, cookies and registry API keys like X-Api-Key or
593
+ // PRIVATE-TOKEN, stays with the origin the caller addressed.
594
+ const CROSS_ORIGIN_REDIRECT_HEADERS = new Set([
595
+ "accept",
596
+ "accept-encoding",
597
+ "accept-language",
598
+ "user-agent",
599
+ ]);
600
+
493
601
  /**
494
- * Issue an undici request, following redirects manually when running under Bun.
602
+ * Keep only the request headers that may be sent to a different origin.
495
603
  *
496
- * On Node.js the redirect-following interceptor baked into the dispatcher does
497
- * the work, so this simply forwards to `undiciRequest`. On Bun (where the
498
- * interceptor API is unavailable) we loop on 3xx responses, draining each
499
- * intermediate body and resolving relative `location` headers, up to
604
+ * @param {Object} headers Request headers.
605
+ * @returns {Object} Headers safe to send cross-origin.
606
+ */
607
+ function crossOriginRedirectHeaders(headers = {}) {
608
+ const kept = {};
609
+ for (const [name, value] of Object.entries(headers)) {
610
+ if (CROSS_ORIGIN_REDIRECT_HEADERS.has(name.toLowerCase())) {
611
+ kept[name] = value;
612
+ }
613
+ }
614
+ return kept;
615
+ }
616
+
617
+ /**
618
+ * Issue an undici request and follow redirects one hop at a time.
619
+ *
620
+ * Redirects are followed here rather than by undici's redirect interceptor so
621
+ * that every hop goes through `beforeRedirect`, which runs the same host
622
+ * allowlist, protocol and dry-run checks as the first request, and so that
623
+ * credentials are not forwarded to another origin. Each intermediate body is
624
+ * drained and relative `location` headers are resolved, up to
500
625
  * `MAX_REDIRECTIONS` hops.
501
626
  *
502
627
  * @param {URL} url Request URL.
503
628
  * @param {Object} requestOptions Options passed to `undiciRequest`.
504
629
  * @param {boolean} followRedirect Whether redirects should be followed.
630
+ * @param {function(URL): Promise<void>} beforeRedirect Called with the next URL
631
+ * before each hop; throws to stop the request.
505
632
  * @returns {Promise<Object>} The final undici response.
506
633
  */
507
- async function requestWithRedirects(url, requestOptions, followRedirect) {
508
- if (!isBun || !followRedirect) {
634
+ async function requestWithRedirects(
635
+ url,
636
+ requestOptions,
637
+ followRedirect,
638
+ beforeRedirect,
639
+ ) {
640
+ if (!followRedirect) {
509
641
  return await undiciRequest(url, requestOptions);
510
642
  }
511
643
  let currentUrl = url;
@@ -526,7 +658,14 @@ async function requestWithRedirects(url, requestOptions, followRedirect) {
526
658
  } catch {
527
659
  // Ignore drain failures; the socket will be discarded.
528
660
  }
529
- currentUrl = new URL(location, currentUrl);
661
+ const nextUrl = new URL(location, currentUrl);
662
+ await beforeRedirect(nextUrl);
663
+ if (nextUrl.origin !== currentUrl.origin) {
664
+ requestOptions.headers = crossOriginRedirectHeaders(
665
+ requestOptions.headers,
666
+ );
667
+ }
668
+ currentUrl = nextUrl;
530
669
  redirections += 1;
531
670
  // 303, and 301/302 for anything other than GET/HEAD, must switch to GET
532
671
  // without a body, matching how undici's redirect interceptor behaves.
@@ -587,11 +726,23 @@ async function doRequest(mergedOptions) {
587
726
  const { body, headers } = resolveRequestBody(options);
588
727
  const method = (options.method || "GET").toUpperCase();
589
728
 
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);
729
+ // Serve cacheable GET requests from the in-memory cache when enabled. A
730
+ // request opts out with `cache: false`, as token exchanges do.
731
+ const cacheable = method === "GET" && options.cache !== false;
732
+ const cacheKey = cacheable
733
+ ? cacheKeyFor(method, url, headers, options.responseType)
734
+ : undefined;
735
+ if (cacheable) {
736
+ const cached = cacheGet(cacheKey);
737
+ if (cached) {
738
+ return cached;
739
+ }
740
+ }
741
+
742
+ // Runs only when the request is about to reach the network, so a caller
743
+ // that rate-limits a host does not wait for a response already cached.
744
+ for (const hook of options.hooks?.beforeNetwork || []) {
745
+ await hook(options);
595
746
  }
596
747
 
597
748
  const timeoutMs = resolveTimeout(options.timeout);
@@ -603,9 +754,21 @@ async function doRequest(mergedOptions) {
603
754
  method,
604
755
  headers,
605
756
  body: method === "GET" || method === "HEAD" ? undefined : body,
606
- dispatcher: selectDispatcher(followRedirect, insecure),
757
+ dispatcher: selectDispatcher(insecure),
607
758
  signal,
608
759
  };
760
+ // A redirect target must pass the same beforeRequest checks, such as the
761
+ // host allowlist, as the URL the caller asked for.
762
+ const beforeRedirect = async (nextUrl) => {
763
+ const hopOptions = {
764
+ ...options,
765
+ url: nextUrl,
766
+ context: { ...options.context },
767
+ };
768
+ for (const hook of options.hooks?.beforeRequest || []) {
769
+ await hook(hopOptions);
770
+ }
771
+ };
609
772
 
610
773
  let undiciResponse;
611
774
  try {
@@ -613,6 +776,7 @@ async function doRequest(mergedOptions) {
613
776
  url,
614
777
  requestOptions,
615
778
  followRedirect,
779
+ beforeRedirect,
616
780
  );
617
781
  } catch (err) {
618
782
  const requestError = new RequestError(err, options);
@@ -652,7 +816,7 @@ async function doRequest(mergedOptions) {
652
816
 
653
817
  // Only successful responses are cached, matching HTTP cache semantics.
654
818
  if (cacheable && finalResponse.statusCode < 400) {
655
- responseCache.set(cacheKey, finalResponse);
819
+ cacheSet(cacheKey, finalResponse);
656
820
  }
657
821
  return finalResponse;
658
822
  }