@cdxgen/cdxgen 13.4.0 → 13.6.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 (305) hide show
  1. package/README.md +9 -9
  2. package/bin/cdxgen-secure.js +15 -0
  3. package/bin/cdxgen.js +29 -5
  4. package/bin/evinse.js +8 -0
  5. package/bin/hbom.js +15 -7
  6. package/bin/repl.js +29 -0
  7. package/bin/validate.js +6 -0
  8. package/data/cbomosdb-queries.json +13 -1
  9. package/data/component-tags.json +8 -0
  10. package/data/license-aliases.json +24 -2
  11. package/data/queries-darwin.json +25 -1
  12. package/data/queries-win.json +19 -1
  13. package/data/queries.json +41 -0
  14. package/data/remediations.json +407 -1
  15. package/data/rules/ai-bom.yaml +1 -1
  16. package/data/rules/brotli-decoder.yaml +107 -0
  17. package/data/rules/cbom-security.yaml +235 -4
  18. package/data/rules/go-binaries.yaml +46 -0
  19. package/data/rules/golem-go.yaml +80 -0
  20. package/data/rules/hbom-compliance.yaml +40 -22
  21. package/data/rules/hbom-performance.yaml +7 -1
  22. package/data/rules/host-topology.yaml +1 -1
  23. package/data/rules/obom-runtime.yaml +31 -2
  24. package/data/rules/php-runtime.yaml +143 -0
  25. package/data/rules/vscode-extensions.yaml +75 -0
  26. package/data/spdx-license-list.json +121 -7
  27. package/data/spdx-licenses.json +69 -0
  28. package/data/spdx.schema.json +845 -2
  29. package/lib/audit/index.js +14 -0
  30. package/lib/cli/bomAssembly.js +120 -6
  31. package/lib/cli/cliOptions.js +110 -3
  32. package/lib/cli/index.js +182 -6
  33. package/lib/cli/jsBom.js +153 -64
  34. package/lib/cli/jvmBom.js +363 -66
  35. package/lib/cli/managedBom.js +853 -87
  36. package/lib/cli/nativeBom.js +267 -33
  37. package/lib/core/boundedFile.js +344 -0
  38. package/lib/core/brotliProbe.js +182 -0
  39. package/lib/core/env.js +38 -8
  40. package/lib/core/fs.js +40 -19
  41. package/lib/core/httpClient.js +172 -46
  42. package/lib/core/lockPaths.js +38 -0
  43. package/lib/core/paths.js +24 -1
  44. package/lib/core/processTree.js +5 -0
  45. package/lib/core/projectConfig.js +1 -0
  46. package/lib/core/projectTooling.js +97 -0
  47. package/lib/core/propertySanitizer.js +72 -0
  48. package/lib/core/toolRegistry.js +170 -0
  49. package/lib/ecosystems/cppEvidence.js +263 -106
  50. package/lib/ecosystems/cppIncludeResolver.js +16 -5
  51. package/lib/ecosystems/ecosystems.js +25 -10
  52. package/lib/ecosystems/gemHostProbe.js +301 -0
  53. package/lib/ecosystems/golem.js +550 -6
  54. package/lib/ecosystems/gradleutils.js +858 -244
  55. package/lib/ecosystems/parsers-bazel.js +107 -6
  56. package/lib/ecosystems/parsers-dotnet.js +117 -18
  57. package/lib/ecosystems/parsers-go.js +1090 -367
  58. package/lib/ecosystems/parsers-js.js +505 -22
  59. package/lib/ecosystems/parsers-jvm.js +259 -17
  60. package/lib/ecosystems/parsers-misc.js +438 -64
  61. package/lib/ecosystems/parsers-python.js +100 -12
  62. package/lib/ecosystems/pythonutils.js +128 -2
  63. package/lib/ecosystems/rubyutils.js +544 -34
  64. package/lib/ecosystems/rusi.js +10 -0
  65. package/lib/ecosystems/scalacliutils.js +4 -1
  66. package/lib/ecosystems/scalasem.js +798 -151
  67. package/lib/ecosystems/vendoredCode.js +237 -3
  68. package/lib/ecosystems/venvProbe.js +360 -0
  69. package/lib/evinser/evinser.js +472 -61
  70. package/lib/evinser/swiftsem.js +68 -5
  71. package/lib/helpers/agenticToolutils.js +413 -93
  72. package/lib/helpers/vsixutils.js +1574 -315
  73. package/lib/inventory/agentSkillSpec.js +17 -0
  74. package/lib/inventory/apkInstalledDb.js +243 -0
  75. package/lib/inventory/atomUtils.js +110 -0
  76. package/lib/inventory/auditCategories.js +3 -3
  77. package/lib/inventory/brotliDecoder.js +142 -0
  78. package/lib/inventory/buildToolRateLimit.js +1 -1
  79. package/lib/inventory/cacheDir.js +52 -9
  80. package/lib/inventory/cbomutils.js +56 -7
  81. package/lib/inventory/certificates.js +2371 -0
  82. package/lib/inventory/communityAiConfigParser.js +4 -9
  83. package/lib/inventory/deps.js +41 -4
  84. package/lib/inventory/display.js +200 -3
  85. package/lib/inventory/dosai.js +639 -63
  86. package/lib/inventory/dotnetRuntime.js +217 -0
  87. package/lib/inventory/dynamic.js +45 -3
  88. package/lib/inventory/envcontext.js +475 -18
  89. package/lib/inventory/evidenceUtils.js +447 -23
  90. package/lib/inventory/fetchBatch.js +1 -1
  91. package/lib/inventory/formulationParsers.js +32 -3
  92. package/lib/inventory/goBuildInfo.js +140 -0
  93. package/lib/inventory/gtfobins.js +13 -0
  94. package/lib/inventory/hbom.js +67 -7
  95. package/lib/inventory/hbomAnalysis.js +16 -0
  96. package/lib/inventory/hostRuntimeProbe.js +686 -0
  97. package/lib/inventory/hostTopology.js +21 -0
  98. package/lib/inventory/jvmLocalRepos.js +9 -2
  99. package/lib/inventory/jvmToolEnv.js +29 -0
  100. package/lib/inventory/licenseEnhancer.js +12 -0
  101. package/lib/inventory/mavenCentralMirror.js +534 -0
  102. package/lib/inventory/nodeBuildInfo.js +218 -0
  103. package/lib/inventory/nodeHostProbe.js +202 -0
  104. package/lib/inventory/osPackagePurl.js +244 -0
  105. package/lib/inventory/osPackageResolver.js +271 -87
  106. package/lib/inventory/osinfo.js +79 -31
  107. package/lib/inventory/osqueryTransform.js +114 -5
  108. package/lib/inventory/phpBuildInfo.js +438 -0
  109. package/lib/inventory/phpHostProbe.js +185 -0
  110. package/lib/inventory/rootfsCerts.js +426 -0
  111. package/lib/inventory/rubyBuildInfo.js +972 -0
  112. package/lib/inventory/rubyHostProbe.js +218 -0
  113. package/lib/inventory/scalaCoords.js +4 -3
  114. package/lib/inventory/scalaDedupe.js +440 -0
  115. package/lib/inventory/source.js +256 -5
  116. package/lib/inventory/spdx.js +144 -0
  117. package/lib/inventory/traceRunner.js +5 -1
  118. package/lib/inventory/tracedCrypto.js +129 -0
  119. package/lib/managers/binary.js +866 -186
  120. package/lib/managers/docker.js +245 -12
  121. package/lib/parsers/email.js +277 -0
  122. package/lib/parsers/largeJson.js +62 -13
  123. package/lib/parsers/vscodeExtensionId.js +183 -0
  124. package/lib/server/server.js +198 -2
  125. package/lib/stages/postgen/annotator.js +17 -1
  126. package/lib/stages/postgen/introspection/reflect.js +4 -1
  127. package/lib/stages/postgen/postgen.js +5 -5
  128. package/lib/stages/postgen/ruleEngine.js +71 -6
  129. package/lib/stages/postgen/specVersionCompat.js +30 -0
  130. package/lib/stages/pregen/envAudit.js +1 -0
  131. package/lib/stages/pregen/pregen.js +231 -62
  132. package/lib/validator/bomValidator.js +32 -6
  133. package/lib/validator/generated/runtime.mjs +13 -0
  134. package/lib/validator/generated/validate-1.6.mjs +27 -8
  135. package/lib/validator/generated/validate-1.7.mjs +32 -13
  136. package/lib/validator/generated/validate-2.0.mjs +31 -12
  137. package/lib/validator/index.js +1 -1
  138. package/package.json +19 -18
  139. package/types/bin/cdxgen-secure.d.ts +3 -0
  140. package/types/bin/cdxgen-secure.d.ts.map +1 -0
  141. package/types/bin/repl.d.ts.map +1 -1
  142. package/types/lib/audit/index.d.ts.map +1 -1
  143. package/types/lib/cli/bomAssembly.d.ts +22 -3
  144. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  145. package/types/lib/cli/cliOptions.d.ts +43 -0
  146. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  147. package/types/lib/cli/index.d.ts.map +1 -1
  148. package/types/lib/cli/jsBom.d.ts +1 -17
  149. package/types/lib/cli/jsBom.d.ts.map +1 -1
  150. package/types/lib/cli/jvmBom.d.ts +20 -0
  151. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  152. package/types/lib/cli/managedBom.d.ts.map +1 -1
  153. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  154. package/types/lib/core/boundedFile.d.ts +70 -0
  155. package/types/lib/core/boundedFile.d.ts.map +1 -0
  156. package/types/lib/core/brotliProbe.d.ts +37 -0
  157. package/types/lib/core/brotliProbe.d.ts.map +1 -0
  158. package/types/lib/core/env.d.ts +22 -2
  159. package/types/lib/core/env.d.ts.map +1 -1
  160. package/types/lib/core/fs.d.ts.map +1 -1
  161. package/types/lib/core/httpClient.d.ts +24 -0
  162. package/types/lib/core/httpClient.d.ts.map +1 -1
  163. package/types/lib/core/lockPaths.d.ts +19 -0
  164. package/types/lib/core/lockPaths.d.ts.map +1 -0
  165. package/types/lib/core/processTree.d.ts +2 -0
  166. package/types/lib/core/processTree.d.ts.map +1 -1
  167. package/types/lib/core/projectConfig.d.ts.map +1 -1
  168. package/types/lib/core/projectTooling.d.ts +41 -0
  169. package/types/lib/core/projectTooling.d.ts.map +1 -0
  170. package/types/lib/core/propertySanitizer.d.ts +26 -0
  171. package/types/lib/core/propertySanitizer.d.ts.map +1 -1
  172. package/types/lib/core/toolRegistry.d.ts +36 -0
  173. package/types/lib/core/toolRegistry.d.ts.map +1 -0
  174. package/types/lib/ecosystems/cppEvidence.d.ts +40 -13
  175. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  176. package/types/lib/ecosystems/cppIncludeResolver.d.ts +2 -1
  177. package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -1
  178. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  179. package/types/lib/ecosystems/gemHostProbe.d.ts +28 -0
  180. package/types/lib/ecosystems/gemHostProbe.d.ts.map +1 -0
  181. package/types/lib/ecosystems/golem.d.ts +81 -1
  182. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  183. package/types/lib/ecosystems/gradleutils.d.ts +57 -21
  184. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  185. package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -1
  186. package/types/lib/ecosystems/parsers-dotnet.d.ts +40 -4
  187. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  188. package/types/lib/ecosystems/parsers-go.d.ts +85 -13
  189. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  190. package/types/lib/ecosystems/parsers-js.d.ts +17 -20
  191. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  192. package/types/lib/ecosystems/parsers-jvm.d.ts +28 -2
  193. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  194. package/types/lib/ecosystems/parsers-misc.d.ts +37 -58
  195. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  196. package/types/lib/ecosystems/parsers-python.d.ts +14 -3
  197. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  198. package/types/lib/ecosystems/pythonutils.d.ts +19 -1
  199. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  200. package/types/lib/ecosystems/rubyutils.d.ts +144 -12
  201. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  202. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  203. package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
  204. package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
  205. package/types/lib/ecosystems/vendoredCode.d.ts +26 -0
  206. package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -1
  207. package/types/lib/ecosystems/venvProbe.d.ts +40 -0
  208. package/types/lib/ecosystems/venvProbe.d.ts.map +1 -0
  209. package/types/lib/evinser/evinser.d.ts +88 -8
  210. package/types/lib/evinser/evinser.d.ts.map +1 -1
  211. package/types/lib/evinser/swiftsem.d.ts +10 -0
  212. package/types/lib/evinser/swiftsem.d.ts.map +1 -1
  213. package/types/lib/inventory/agentSkillSpec.d.ts +13 -0
  214. package/types/lib/inventory/agentSkillSpec.d.ts.map +1 -0
  215. package/types/lib/inventory/apkInstalledDb.d.ts +42 -0
  216. package/types/lib/inventory/apkInstalledDb.d.ts.map +1 -0
  217. package/types/lib/inventory/atomUtils.d.ts +33 -0
  218. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  219. package/types/lib/inventory/brotliDecoder.d.ts +58 -0
  220. package/types/lib/inventory/brotliDecoder.d.ts.map +1 -0
  221. package/types/lib/inventory/cacheDir.d.ts +12 -4
  222. package/types/lib/inventory/cacheDir.d.ts.map +1 -1
  223. package/types/lib/inventory/cbomutils.d.ts +17 -0
  224. package/types/lib/inventory/cbomutils.d.ts.map +1 -1
  225. package/types/lib/inventory/certificates.d.ts +205 -0
  226. package/types/lib/inventory/certificates.d.ts.map +1 -0
  227. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  228. package/types/lib/inventory/deps.d.ts +2 -1
  229. package/types/lib/inventory/deps.d.ts.map +1 -1
  230. package/types/lib/inventory/display.d.ts +18 -0
  231. package/types/lib/inventory/display.d.ts.map +1 -1
  232. package/types/lib/inventory/dosai.d.ts +162 -7
  233. package/types/lib/inventory/dosai.d.ts.map +1 -1
  234. package/types/lib/inventory/dotnetRuntime.d.ts +44 -0
  235. package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -0
  236. package/types/lib/inventory/dynamic.d.ts +6 -1
  237. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  238. package/types/lib/inventory/envcontext.d.ts +92 -8
  239. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  240. package/types/lib/inventory/evidenceUtils.d.ts +29 -10
  241. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  242. package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
  243. package/types/lib/inventory/goBuildInfo.d.ts +10 -0
  244. package/types/lib/inventory/goBuildInfo.d.ts.map +1 -0
  245. package/types/lib/inventory/gtfobins.d.ts.map +1 -1
  246. package/types/lib/inventory/hbom.d.ts.map +1 -1
  247. package/types/lib/inventory/hbomAnalysis.d.ts +10 -0
  248. package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -1
  249. package/types/lib/inventory/hostRuntimeProbe.d.ts +44 -0
  250. package/types/lib/inventory/hostRuntimeProbe.d.ts.map +1 -0
  251. package/types/lib/inventory/hostTopology.d.ts.map +1 -1
  252. package/types/lib/inventory/jvmToolEnv.d.ts +12 -0
  253. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
  254. package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -1
  255. package/types/lib/inventory/mavenCentralMirror.d.ts +170 -0
  256. package/types/lib/inventory/mavenCentralMirror.d.ts.map +1 -0
  257. package/types/lib/inventory/nodeBuildInfo.d.ts +95 -0
  258. package/types/lib/inventory/nodeBuildInfo.d.ts.map +1 -0
  259. package/types/lib/inventory/nodeHostProbe.d.ts +15 -0
  260. package/types/lib/inventory/nodeHostProbe.d.ts.map +1 -0
  261. package/types/lib/inventory/osPackagePurl.d.ts +99 -0
  262. package/types/lib/inventory/osPackagePurl.d.ts.map +1 -0
  263. package/types/lib/inventory/osPackageResolver.d.ts +44 -2
  264. package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
  265. package/types/lib/inventory/osinfo.d.ts +31 -6
  266. package/types/lib/inventory/osinfo.d.ts.map +1 -1
  267. package/types/lib/inventory/osqueryTransform.d.ts +51 -0
  268. package/types/lib/inventory/osqueryTransform.d.ts.map +1 -1
  269. package/types/lib/inventory/phpBuildInfo.d.ts +142 -0
  270. package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -0
  271. package/types/lib/inventory/phpHostProbe.d.ts +35 -0
  272. package/types/lib/inventory/phpHostProbe.d.ts.map +1 -0
  273. package/types/lib/inventory/rootfsCerts.d.ts +27 -0
  274. package/types/lib/inventory/rootfsCerts.d.ts.map +1 -0
  275. package/types/lib/inventory/rubyBuildInfo.d.ts +271 -0
  276. package/types/lib/inventory/rubyBuildInfo.d.ts.map +1 -0
  277. package/types/lib/inventory/rubyHostProbe.d.ts +47 -0
  278. package/types/lib/inventory/rubyHostProbe.d.ts.map +1 -0
  279. package/types/lib/inventory/source.d.ts +38 -0
  280. package/types/lib/inventory/source.d.ts.map +1 -1
  281. package/types/lib/inventory/spdx.d.ts +15 -0
  282. package/types/lib/inventory/spdx.d.ts.map +1 -1
  283. package/types/lib/inventory/traceRunner.d.ts +4 -1
  284. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  285. package/types/lib/inventory/tracedCrypto.d.ts +22 -0
  286. package/types/lib/inventory/tracedCrypto.d.ts.map +1 -1
  287. package/types/lib/managers/binary.d.ts +10 -0
  288. package/types/lib/managers/binary.d.ts.map +1 -1
  289. package/types/lib/managers/docker.d.ts +24 -0
  290. package/types/lib/managers/docker.d.ts.map +1 -1
  291. package/types/lib/parsers/email.d.ts +19 -0
  292. package/types/lib/parsers/email.d.ts.map +1 -0
  293. package/types/lib/parsers/largeJson.d.ts +17 -3
  294. package/types/lib/parsers/largeJson.d.ts.map +1 -1
  295. package/types/lib/server/server.d.ts +8 -0
  296. package/types/lib/server/server.d.ts.map +1 -1
  297. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  298. package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -1
  299. package/types/lib/stages/postgen/ruleEngine.d.ts +7 -4
  300. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  301. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  302. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  303. package/types/lib/stages/pregen/pregen.d.ts +26 -9
  304. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  305. package/types/lib/validator/bomValidator.d.ts.map +1 -1
@@ -54,9 +54,20 @@ function applySpec(value, spec) {
54
54
  return value;
55
55
  }
56
56
  if (Array.isArray(value)) {
57
+ let elements = value;
58
+ if (spec.map) {
59
+ const mapped = [];
60
+ for (const element of elements) {
61
+ const transformed = spec.map(element);
62
+ if (transformed !== undefined) {
63
+ mapped.push(transformed);
64
+ }
65
+ }
66
+ elements = mapped;
67
+ }
57
68
  return spec.filter
58
- ? value.filter((element) => spec.filter(element))
59
- : value;
69
+ ? elements.filter((element) => spec.filter(element))
70
+ : elements;
60
71
  }
61
72
  if (!spec.members) {
62
73
  return value;
@@ -256,8 +267,9 @@ class ByteCursor {
256
267
 
257
268
  // The members of the object at the cursor, as { key, start, end } byte ranges
258
269
  // of their values, in file order. A repeated key keeps its first position and
259
- // its last range, which is what JSON.parse does.
260
- function scanObjectMembers(cursor) {
270
+ // its last range, which is what JSON.parse does. With `stopAt`, the scan ends
271
+ // at the first member of that name, leaving the rest of the object unread.
272
+ function scanObjectMembers(cursor, stopAt = undefined) {
261
273
  if (cursor.nextToken() !== OPEN_BRACE) {
262
274
  cursor.fail("Expected an object");
263
275
  }
@@ -280,6 +292,9 @@ function scanObjectMembers(cursor) {
280
292
  const start = cursor.offset;
281
293
  cursor.skipValue();
282
294
  members.set(key, { start, end: cursor.offset });
295
+ if (key === stopAt) {
296
+ return members;
297
+ }
283
298
  const delimiter = cursor.nextToken();
284
299
  if (delimiter === CLOSE_BRACE) {
285
300
  return members;
@@ -290,9 +305,10 @@ function scanObjectMembers(cursor) {
290
305
  }
291
306
  }
292
307
 
293
- // Decode the array at the cursor in runs of whole elements, keeping those that
294
- // `filter` accepts.
295
- function readArrayElements(cursor, filter, batchBytes) {
308
+ // Decode the array at the cursor in runs of whole elements, mapping each one
309
+ // through `spec.map` (an undefined result drops it) and keeping those that
310
+ // `spec.filter` accepts.
311
+ function readArrayElements(cursor, spec, batchBytes) {
296
312
  if (cursor.nextToken() !== OPEN_BRACKET) {
297
313
  cursor.fail("Expected an array");
298
314
  }
@@ -306,8 +322,12 @@ function readArrayElements(cursor, filter, batchBytes) {
306
322
  for (const element of JSON.parse(
307
323
  `[${cursor.endCapture(cursor.pos - 1)}]`,
308
324
  )) {
309
- if (!filter || filter(element)) {
310
- elements.push(element);
325
+ const mapped = spec?.map ? spec.map(element) : element;
326
+ if (mapped === undefined) {
327
+ continue;
328
+ }
329
+ if (!spec?.filter || spec.filter(mapped)) {
330
+ elements.push(mapped);
311
331
  }
312
332
  }
313
333
  };
@@ -363,7 +383,7 @@ function decodeRange(fd, start, end, spec, options) {
363
383
  );
364
384
  }
365
385
  } else if (first === OPEN_BRACKET) {
366
- value = readArrayElements(cursor, spec?.filter, options.batchBytes);
386
+ value = readArrayElements(cursor, spec, options.batchBytes);
367
387
  } else {
368
388
  // A string or number this large has no smaller parts; JSON.parse reports
369
389
  // it the way it would for the whole file.
@@ -412,14 +432,43 @@ export function indexJsonObject(filePath, options = {}) {
412
432
  });
413
433
  }
414
434
 
435
+ /**
436
+ * Decode one member of the top-level object in a JSON file, reading the file
437
+ * only up to the end of that member. A member near the start of a large file
438
+ * costs a few bounded reads, however large the rest of the file is; the rest
439
+ * is not checked for well-formedness.
440
+ *
441
+ * @param {string} filePath JSON file whose top-level value is an object
442
+ * @param {string} name Member to decode
443
+ * @param {Object} [options] Batch and chunk size overrides, for tests
444
+ * @returns {*} The decoded value, or undefined when the object has no such member
445
+ * @throws {SyntaxError} When the object is malformed before the member ends
446
+ */
447
+ export function readJsonMember(filePath, name, options = {}) {
448
+ const resolved = resolveOptions(options);
449
+ return withFile(filePath, (fd) => {
450
+ const cursor = new ByteCursor(
451
+ fd,
452
+ 0,
453
+ statSync(filePath).size,
454
+ resolved.chunkBytes,
455
+ );
456
+ const range = scanObjectMembers(cursor, name).get(name);
457
+ return range
458
+ ? decodeRange(fd, range.start, range.end, undefined, resolved)
459
+ : undefined;
460
+ });
461
+ }
462
+
415
463
  /**
416
464
  * Decode the JSON value stored in a byte range of a file, without ever holding
417
465
  * more than a bounded run of it in one string.
418
466
  *
419
467
  * `spec` narrows what is kept. `{ members: { name: spec } }` keeps only the
420
- * named members of an object, each narrowed by its own spec. `{ filter }`
421
- * keeps the elements of an array that `filter(element)` accepts. Anything else
422
- * keeps the whole value.
468
+ * named members of an object, each narrowed by its own spec. `{ map }`
469
+ * transforms each element of an array, an undefined result dropping it, and
470
+ * `{ filter }` keeps the mapped elements that `filter(element)` accepts.
471
+ * Anything else keeps the whole value.
423
472
  *
424
473
  * @param {string} filePath JSON file
425
474
  * @param {{start: number, end: number}} range Byte range of the value, such as one returned by indexJsonObject
@@ -0,0 +1,183 @@
1
+ // The one place that reads the identity of an installed VS Code-family
2
+ // extension from the name of its folder.
3
+ //
4
+ // An editor installs an extension into `<publisher>.<name>-<version>`, and a
5
+ // platform-specific build into `<publisher>.<name>-<version>-<platform>`, for
6
+ // example `anthropic.claude-code-2.1.313-darwin-arm64`. The extension scan and
7
+ // the osquery `vscode_extensions` rows both name the extension from this, so an
8
+ // extension found by `cdxgen -t vscode-extension` and by `obom` is the same
9
+ // component with the same purl.
10
+
11
+ /**
12
+ * Target platforms a VS Code-family marketplace publishes an extension for.
13
+ * `universal` and `web` builds are the same artefact on every platform and are
14
+ * not part of the extension's identity.
15
+ */
16
+ export const VSCODE_TARGET_PLATFORMS = new Set([
17
+ "win32-x64",
18
+ "win32-arm64",
19
+ "win32-ia32",
20
+ "linux-x64",
21
+ "linux-arm64",
22
+ "linux-armhf",
23
+ "alpine-x64",
24
+ "alpine-arm64",
25
+ "darwin-x64",
26
+ "darwin-arm64",
27
+ ]);
28
+
29
+ // Folder suffixes for builds that run on every platform.
30
+ const NEUTRAL_PLATFORMS = ["universal", "web"];
31
+
32
+ /**
33
+ * Normalize a target platform value from a manifest, an `extensions.json`
34
+ * record, or a folder name. Values that do not name a specific platform, such
35
+ * as `universal`, `web`, `undefined`, or an empty string, return an empty
36
+ * string.
37
+ *
38
+ * @param {string} value Raw target platform
39
+ * @returns {string} A value from VSCODE_TARGET_PLATFORMS, or an empty string
40
+ */
41
+ export function normalizeVscodeTargetPlatform(value) {
42
+ const platform = `${value || ""}`.trim().toLowerCase();
43
+ return VSCODE_TARGET_PLATFORMS.has(platform) ? platform : "";
44
+ }
45
+
46
+ /**
47
+ * Split an installed extension folder name into its publisher, name, version
48
+ * and target platform.
49
+ *
50
+ * @param {string} dirName Folder name, such as `ms-python.python-2025.4.0` or
51
+ * `anthropic.claude-code-2.1.313-darwin-arm64`
52
+ * @returns {{publisher: string, name: string, version: string, platform: string}|undefined}
53
+ * The parts, or undefined when the name does not follow the pattern
54
+ */
55
+ export function parseVscodeExtensionDirName(dirName) {
56
+ let value = `${dirName || ""}`.trim();
57
+ while (value.endsWith("/") || value.endsWith("\\")) {
58
+ value = value.slice(0, -1);
59
+ }
60
+ const dotIdx = value.indexOf(".");
61
+ if (dotIdx < 1) {
62
+ return undefined;
63
+ }
64
+ const publisher = value.substring(0, dotIdx);
65
+ let rest = value.substring(dotIdx + 1);
66
+ let platform = "";
67
+ for (const candidate of [...VSCODE_TARGET_PLATFORMS, ...NEUTRAL_PLATFORMS]) {
68
+ if (rest.toLowerCase().endsWith(`-${candidate}`)) {
69
+ platform = normalizeVscodeTargetPlatform(candidate);
70
+ rest = rest.substring(0, rest.length - candidate.length - 1);
71
+ break;
72
+ }
73
+ }
74
+ // Marketplaces require semver versions, so the version starts at the first
75
+ // hyphen whose remainder is a whole semver: `python-3.11-tools-1.0.0` is the
76
+ // name `python-3.11-tools` at 1.0.0, and `1.0.0-beta-2` stays one version.
77
+ // A folder whose version is not semver falls back to the first hyphen
78
+ // followed by digits and a dot.
79
+ let versionStart = -1;
80
+ let fallbackStart = -1;
81
+ for (let i = 1; i < rest.length - 2; i++) {
82
+ if (rest[i] !== "-" || !isDigit(rest[i + 1])) {
83
+ continue;
84
+ }
85
+ if (isSemver(rest.substring(i + 1))) {
86
+ versionStart = i;
87
+ break;
88
+ }
89
+ let j = i + 1;
90
+ while (j < rest.length && isDigit(rest[j])) {
91
+ j++;
92
+ }
93
+ if (fallbackStart < 0 && rest[j] === ".") {
94
+ fallbackStart = i;
95
+ }
96
+ }
97
+ if (versionStart < 0) {
98
+ versionStart = fallbackStart;
99
+ }
100
+ if (versionStart < 1) {
101
+ return undefined;
102
+ }
103
+ const name = rest.substring(0, versionStart);
104
+ const version = rest.substring(versionStart + 1);
105
+ if (!name || !version) {
106
+ return undefined;
107
+ }
108
+ return { publisher, name, version, platform };
109
+ }
110
+
111
+ function isDigit(character) {
112
+ return character >= "0" && character <= "9";
113
+ }
114
+
115
+ function isIdentifierList(text, numericNoLeadingZero) {
116
+ if (!text) {
117
+ return false;
118
+ }
119
+ return text.split(".").every((part) => {
120
+ if (!part.length) {
121
+ return false;
122
+ }
123
+ for (const character of part) {
124
+ const ok =
125
+ isDigit(character) ||
126
+ character === "-" ||
127
+ (character >= "a" && character <= "z") ||
128
+ (character >= "A" && character <= "Z");
129
+ if (!ok) {
130
+ return false;
131
+ }
132
+ }
133
+ if (numericNoLeadingZero && [...part].every(isDigit)) {
134
+ return part === "0" || part[0] !== "0";
135
+ }
136
+ return true;
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Check a string is a semantic version: three numeric parts, then an optional
142
+ * `-prerelease` and `+build`.
143
+ *
144
+ * @param {string} text Candidate version
145
+ * @returns {boolean} true for a semver
146
+ */
147
+ export function isSemver(text) {
148
+ const plus = text.indexOf("+");
149
+ const core = plus >= 0 ? text.substring(0, plus) : text;
150
+ if (plus >= 0 && !isIdentifierList(text.substring(plus + 1), false)) {
151
+ return false;
152
+ }
153
+ const dash = core.indexOf("-");
154
+ const numbers = dash >= 0 ? core.substring(0, dash) : core;
155
+ if (dash >= 0 && !isIdentifierList(core.substring(dash + 1), true)) {
156
+ return false;
157
+ }
158
+ const parts = numbers.split(".");
159
+ return (
160
+ parts.length === 3 &&
161
+ parts.every(
162
+ (part) =>
163
+ part.length > 0 &&
164
+ [...part].every(isDigit) &&
165
+ (part === "0" || part[0] !== "0"),
166
+ )
167
+ );
168
+ }
169
+
170
+ /**
171
+ * Read the target platform from the path of an installed extension folder.
172
+ *
173
+ * @param {string} extensionPath Path to the extension folder, with either
174
+ * separator
175
+ * @returns {string} The target platform, or an empty string
176
+ */
177
+ export function vscodeExtensionPlatformFromPath(extensionPath) {
178
+ const segments = `${extensionPath || ""}`
179
+ .split(/[/\\]/u)
180
+ .filter((segment) => segment.length);
181
+ const dirName = segments[segments.length - 1];
182
+ return parseVscodeExtensionDirName(dirName)?.platform || "";
183
+ }
@@ -14,7 +14,9 @@ import {
14
14
  isSecureMode,
15
15
  readEnvironmentVariable,
16
16
  } from "../core/activity.js";
17
+ import { PROJECT_TYPE_ALIASES } from "../core/env.js";
17
18
  import { isWin } from "../core/paths.js";
19
+ import { isSecureModeActive } from "../core/projectTooling.js";
18
20
  import { resetRunState } from "../core/runState.js";
19
21
  import { CDXGEN_VERSION } from "../core/state.js";
20
22
  import { installConsoleShim, ui } from "../core/ui.js";
@@ -27,6 +29,7 @@ import {
27
29
  getGitAllowProtocol,
28
30
  gitClone,
29
31
  isAllowedPath,
32
+ isAllowedRealPath,
30
33
  isAllowedWinPath,
31
34
  maybePurlSource,
32
35
  maybeRemotePath,
@@ -82,6 +85,11 @@ const ALLOWED_PARAMS = [
82
85
  "tlpClassification",
83
86
  ];
84
87
 
88
+ // Options that install packages or run the scanned project's own build tools
89
+ // (for example `cargo check`, which runs build.rs), and so execute code from
90
+ // the scanned repository.
91
+ const CODE_EXECUTION_PARAMS = ["installDeps", "deep"];
92
+
85
93
  const app = connect();
86
94
 
87
95
  export { isAllowedHttpHost };
@@ -156,6 +164,7 @@ export function parseValue(raw) {
156
164
  * @returns {Object} Populated options object
157
165
  */
158
166
  export function parseQueryString(q, body = {}, options = {}) {
167
+ const operatorOptions = { ...options };
159
168
  // Priority is query params followed by body
160
169
  for (const param of ALLOWED_PARAMS) {
161
170
  const raw = q[param] ?? body[param];
@@ -163,7 +172,7 @@ export function parseQueryString(q, body = {}, options = {}) {
163
172
  options[param] = parseValue(raw);
164
173
  }
165
174
  }
166
- options.projectType = options.type?.split(",");
175
+ options.projectType = toProjectTypeList(options.type);
167
176
  delete options.type;
168
177
  if (options.lifecycle === "pre-build") {
169
178
  options.installDeps = false;
@@ -171,9 +180,170 @@ export function parseQueryString(q, body = {}, options = {}) {
171
180
  if (options.profile) {
172
181
  applyProfileOptions(options);
173
182
  }
183
+ // failOnError ends the process, so one failing request would stop the server
184
+ // for every client.
185
+ options.failOnError = false;
186
+ restrictRequestOptions(options, operatorOptions);
174
187
  return options;
175
188
  }
176
189
 
190
+ /**
191
+ * Normalise a `type` value from the request or the start-up options, which may
192
+ * be a comma separated string or an array, into a list of project types.
193
+ *
194
+ * @param {string|string[]|undefined} type Project type value
195
+ * @returns {string[]|undefined} Project types, or undefined when none are given
196
+ */
197
+ function toProjectTypeList(type) {
198
+ if (type === undefined || type === null || type === "") {
199
+ return undefined;
200
+ }
201
+ const types = (Array.isArray(type) ? type : [type])
202
+ .flatMap((t) => String(t).split(","))
203
+ .map((t) => t.trim())
204
+ .filter(Boolean);
205
+ return types.length ? types : undefined;
206
+ }
207
+
208
+ /**
209
+ * Stop a request from turning on options that execute code from the scanned
210
+ * repository when the operator has not. In secure mode, or when the server was
211
+ * started with --no-install-deps, a request may turn these options off but
212
+ * never on.
213
+ *
214
+ * @param {Object} options Options parsed from the request
215
+ * @param {Object} operatorOptions Options the server was started with
216
+ * @returns {void}
217
+ */
218
+ function restrictRequestOptions(options, operatorOptions) {
219
+ if (!isSecureModeActive() && operatorOptions.installDeps !== false) {
220
+ return;
221
+ }
222
+ for (const param of CODE_EXECUTION_PARAMS) {
223
+ // Only an explicit false from the request changes the operator's choice.
224
+ // Values such as "" or 0 would otherwise pass gates that test `!== false`.
225
+ options[param] =
226
+ operatorOptions[param] === true && options[param] !== false;
227
+ }
228
+ }
229
+
230
+ // Project types that inventory the host the server runs on, such as installed
231
+ // packages, users, shell history, IDE and browser extensions, or the Maven,
232
+ // Gradle, sbt, Cargo and Helm caches in the server user's home directory.
233
+ // They ignore the scan path, so the path allowlist does not limit them.
234
+ const HOST_SCOPE_PROJECT_TYPES = new Set(
235
+ [
236
+ "os",
237
+ "hbom",
238
+ "cargo-cache",
239
+ "helm-index",
240
+ "gradle-index",
241
+ "sbt-index",
242
+ "maven-index",
243
+ "vscode-extension",
244
+ "chrome-extension",
245
+ "agentic",
246
+ ]
247
+ .flatMap((key) => PROJECT_TYPE_ALIASES[key] || [key])
248
+ .concat(["maven-repo"]),
249
+ );
250
+
251
+ /**
252
+ * Check request options that the server must refuse outright.
253
+ *
254
+ * @param {Object} reqOptions Options parsed from the request
255
+ * @param {Object} operatorOptions Options the server was started with
256
+ * @returns {Object|null} Error object with status, error and details, or null
257
+ */
258
+ export function validateRequestOptions(reqOptions, operatorOptions = {}) {
259
+ const operatorTypes = new Set(
260
+ toProjectTypeList(operatorOptions.type ?? operatorOptions.projectType) ||
261
+ [],
262
+ );
263
+ const hostScopeType = (reqOptions.projectType || []).find(
264
+ (t) => HOST_SCOPE_PROJECT_TYPES.has(t) && !operatorTypes.has(t),
265
+ );
266
+ if (hostScopeType) {
267
+ return {
268
+ status: 403,
269
+ error: "Project Type Not Allowed",
270
+ details: `The '${hostScopeType}' project type scans the server host. Start the server with this type to allow it.`,
271
+ };
272
+ }
273
+ const excludes = Array.isArray(reqOptions.exclude)
274
+ ? reqOptions.exclude
275
+ : [reqOptions.exclude];
276
+ // Brace patterns expand before matching, so one short pattern such as
277
+ // {1..100000} can keep the server busy for minutes.
278
+ if (
279
+ reqOptions.exclude !== operatorOptions.exclude &&
280
+ excludes.some(
281
+ (e) => typeof e === "string" && (e.includes("{") || e.length > 512),
282
+ )
283
+ ) {
284
+ return {
285
+ status: 400,
286
+ error: "Invalid Exclude Pattern",
287
+ details:
288
+ "Exclude patterns sent to the server cannot use braces or exceed 512 characters.",
289
+ };
290
+ }
291
+ if (
292
+ reqOptions.serverUrl &&
293
+ reqOptions.serverUrl !== operatorOptions.serverUrl
294
+ ) {
295
+ const requestedOrigin = urlOrigin(reqOptions.serverUrl);
296
+ if (!requestedOrigin) {
297
+ return {
298
+ status: 400,
299
+ error: "Invalid Server URL",
300
+ details: "The Dependency-Track server URL is invalid.",
301
+ };
302
+ }
303
+ if (requestedOrigin !== urlOrigin(operatorOptions.serverUrl)) {
304
+ // The operator's API key only ever goes to the operator's server.
305
+ if (
306
+ operatorOptions.apiKey &&
307
+ reqOptions.apiKey === operatorOptions.apiKey
308
+ ) {
309
+ return {
310
+ status: 400,
311
+ error: "API Key Required",
312
+ details:
313
+ "A request that names its own Dependency-Track server must send its own API key.",
314
+ };
315
+ }
316
+ // CDXGEN_SERVER_ALLOWED_HOSTS only covers git hosts, so in secure mode a
317
+ // server the operator did not configure must be on CDXGEN_ALLOWED_HOSTS.
318
+ if (isSecureMode && !readEnvironmentVariable("CDXGEN_ALLOWED_HOSTS")) {
319
+ return {
320
+ status: 403,
321
+ error: "Host Not Allowed",
322
+ details:
323
+ "Set CDXGEN_ALLOWED_HOSTS to let requests choose a Dependency-Track server.",
324
+ };
325
+ }
326
+ }
327
+ }
328
+ if (isSecureMode && reqOptions.includeFormulation && reqOptions.serverUrl) {
329
+ return {
330
+ status: 400,
331
+ error: "Formulation Not Allowed",
332
+ details:
333
+ "Formulation data may contain secrets and is not submitted to Dependency-Track in secure mode.",
334
+ };
335
+ }
336
+ return null;
337
+ }
338
+
339
+ function urlOrigin(value) {
340
+ try {
341
+ return new URL(value).origin;
342
+ } catch (_err) {
343
+ return undefined;
344
+ }
345
+ }
346
+
177
347
  /**
178
348
  * Extracts query parameters from an incoming HTTP request object.
179
349
  * Handles repeated keys by collecting their values into an array.
@@ -391,6 +561,18 @@ const start = (options) => {
391
561
  }),
392
562
  );
393
563
  }
564
+ const optionsError = validateRequestOptions(reqOptions, options);
565
+ if (optionsError) {
566
+ res.writeHead(optionsError.status, {
567
+ "Content-Type": "application/json",
568
+ });
569
+ return res.end(
570
+ JSON.stringify({
571
+ error: optionsError.error,
572
+ details: optionsError.details,
573
+ }),
574
+ );
575
+ }
394
576
  // Enforce the v13 CycloneDX spec floor on the HTTP entry point too, so the
395
577
  // server cannot be used to request a generation target the CLI rejects.
396
578
  if (reqOptions.specVersion !== undefined) {
@@ -477,7 +659,18 @@ const start = (options) => {
477
659
  );
478
660
  }
479
661
  }
480
- cloneDir = gitClone(sourcePath, gitRef);
662
+ try {
663
+ cloneDir = gitClone(sourcePath, gitRef);
664
+ } catch (_err) {
665
+ console.log("Unable to clone repository. See the server log.");
666
+ res.writeHead(500, { "Content-Type": "application/json" });
667
+ return res.end(
668
+ JSON.stringify({
669
+ error: "Unable to clone repository",
670
+ details: "Cloning the repository failed. See the server log.",
671
+ }),
672
+ );
673
+ }
481
674
  srcDir = cloneDir;
482
675
  if (purlResolution?.type === "npm") {
483
676
  const cloneRootDir = cloneDir;
@@ -502,8 +695,11 @@ const start = (options) => {
502
695
  cleanup = true;
503
696
  } else {
504
697
  srcDir = sourcePath;
698
+ // Check where a symlinked path really points as well, so a link inside
699
+ // an allowed directory cannot open a scan of a directory outside it.
505
700
  if (
506
701
  !isAllowedPath(path.resolve(srcDir)) ||
702
+ !isAllowedRealPath(srcDir) ||
507
703
  (isWin && !isAllowedWinPath(srcDir))
508
704
  ) {
509
705
  res.writeHead(403, { "Content-Type": "application/json" });
@@ -463,6 +463,15 @@ export function textualMetadata(bomJson) {
463
463
  const swidCount = bomJson?.components?.filter((c) =>
464
464
  c?.purl?.startsWith("pkg:swid"),
465
465
  ).length;
466
+ // Homebrew packages are pkg:brew, not pkg:swid, so they are not counted as
467
+ // applications; formulae and casks are told apart by a property.
468
+ const brewComponents =
469
+ bomJson?.components?.filter((c) => c?.purl?.startsWith("pkg:brew/")) || [];
470
+ const brewCaskCount = brewComponents.filter((c) =>
471
+ c.properties?.some(
472
+ (p) => p.name === "cdx:homebrew:type" && p.value === "cask",
473
+ ),
474
+ ).length;
466
475
  const { unpackagedExecutableCount, unpackagedSharedLibraryCount } =
467
476
  getContainerFileInventoryStats(bomJson?.components);
468
477
  const githubStats = getGitHubWorkflowStats(bomJson?.components);
@@ -711,7 +720,14 @@ export function textualMetadata(bomJson) {
711
720
  text = `${text} The system appears to be set up for remote development, with ${vsixCount} Visual Studio Code extensions installed.`;
712
721
  }
713
722
  if (swidCount > 0) {
714
- text = `${text} In addition, there are ${swidCount} applications installed on the system.`;
723
+ text = `${text} In addition, there ${swidCount === 1 ? "is 1 application" : `are ${swidCount} applications`} installed on the system.`;
724
+ }
725
+ if (brewComponents.length > 0) {
726
+ const brewCount = brewComponents.length;
727
+ const casks = brewCaskCount
728
+ ? `, of which ${brewCaskCount} ${brewCaskCount === 1 ? "is a cask" : "are casks"}`
729
+ : "";
730
+ text = `${text} The system also has ${brewCount} Homebrew package${brewCount === 1 ? "" : "s"} installed${casks}.`;
715
731
  }
716
732
  }
717
733
  if (bomType === "HBOM" && hbomSummary) {
@@ -36,6 +36,7 @@ import {
36
36
  isVersionedJvmToolProjectType,
37
37
  PROJECT_TYPE_ALIASES,
38
38
  } from "../../../core/env.js";
39
+ import { safeExistsSync } from "../../../core/fs.js";
39
40
  import { dirNameStr } from "../../../core/paths.js";
40
41
  import { CDXGEN_VERSION } from "../../../core/state.js";
41
42
  import { evaluateRules, loadRules } from "../ruleEngine.js";
@@ -389,7 +390,9 @@ function resolveLedgerEvents(context) {
389
390
  };
390
391
  }
391
392
  const sidecarPath = readEnvironmentVariable("CDXGEN_INTROSPECT_LEDGER");
392
- if (sidecarPath) {
393
+ // A sidecar the run could not create (the permission model denies writes
394
+ // outside the granted paths) holds nothing; the buffer still has the events.
395
+ if (sidecarPath && safeExistsSync(sidecarPath)) {
393
396
  return { events: loadLedgerFile(sidecarPath), source: "sidecar" };
394
397
  }
395
398
  const events = getLedgerEvents();
@@ -1,5 +1,5 @@
1
1
  import { readFileSync } from "node:fs";
2
- import { basename, join, relative, sep } from "node:path";
2
+ import { join, relative } from "node:path";
3
3
  import process from "node:process";
4
4
 
5
5
  import { Purl } from "@cdxgen/cdx-purl";
@@ -943,10 +943,10 @@ function applyCustomMetadataProperties(bomJson, options) {
943
943
  export function applyStandards(bomJson, options) {
944
944
  if (options.standard && Array.isArray(options.standard)) {
945
945
  for (let astandard of options.standard) {
946
- // See issue: #1953
947
- if (astandard.includes(sep)) {
948
- astandard = basename(astandard);
949
- }
946
+ // See issue: #1953. Server requests can send either separator, and on
947
+ // Windows both "/" and "\" separate path segments, so keep only the
948
+ // last segment of either kind.
949
+ astandard = String(astandard).split(/[\\/]/).pop();
950
950
  const templateFile = join(
951
951
  dirNameStr,
952
952
  "data",