@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
@@ -1,4 +1,5 @@
1
1
  import { getDistroInfo } from "./osinfo.js";
2
+ import { buildOsPackagePurl, osPackageVersion } from "./osPackagePurl.js";
2
3
  import { tryBuildPurl } from "./purl.js";
3
4
 
4
5
  /**
@@ -62,6 +63,8 @@ export function deriveOsQueryPublisher(res) {
62
63
  res.creator ||
63
64
  res.manufacturer ||
64
65
  res.provider ||
66
+ res.packager ||
67
+ res.developer_name ||
65
68
  "";
66
69
  return publisher === "null" ? "" : publisher;
67
70
  }
@@ -174,7 +177,10 @@ export function shouldCreateOsQueryPurl(componentType) {
174
177
 
175
178
  // Purl types whose type rules require a namespace. For OS package types the
176
179
  // namespace is the distro vendor (`pkg:deb/debian/...`, `pkg:rpm/redhat/...`),
177
- // which osquery does not report but /etc/os-release does.
180
+ // which osquery does not report but /etc/os-release does. deb, rpm and apk rows
181
+ // are named by createOsQueryPackageIdentity; this covers the other types that
182
+ // need a vendor (portage's ebuild) and a caller that asks for one of the three
183
+ // directly.
178
184
  const NAMESPACE_REQUIRED_TYPES = new Set([
179
185
  "deb",
180
186
  "rpm",
@@ -216,10 +222,9 @@ export function createOsQueryPurl(
216
222
  if (type === "swid" && !finalQualifiers.tag_id) {
217
223
  finalQualifiers.tag_id = name;
218
224
  }
219
- // The Linux OS package queries (deb_packages, rpm_packages, portage) yield no
220
- // group, but their purl types require a namespace. Derive the vendor from
221
- // /etc/os-release the same way osPackageResolver does, so these keep a valid
222
- // purl instead of losing one.
225
+ // Rows with no group whose purl type requires a namespace get the vendor from
226
+ // /etc/os-release, the way osPackageResolver does, so they keep a valid purl
227
+ // instead of losing one.
223
228
  let namespace = group || null;
224
229
  if (!namespace && NAMESPACE_REQUIRED_TYPES.has(type)) {
225
230
  namespace = getDistroInfo().namespace || null;
@@ -237,6 +242,110 @@ export function createOsQueryPurl(
237
242
  });
238
243
  }
239
244
 
245
+ /**
246
+ * The version and purl of a row from an OS package table: `deb_packages`,
247
+ * `rpm_packages`, `pacman_packages`, `homebrew_packages`, `macports_packages`,
248
+ * `snap_packages`, `flatpak_packages` or the rows of the apk database.
249
+ *
250
+ * Such a row is named the way the package manager's own scan names it: deb,
251
+ * rpm and apk with the vendor and the `arch`, `distro`, `distro_name` and
252
+ * `epoch` qualifiers of the container scan, an rpm version joined to its
253
+ * release, a MacPorts version joined to its revision, a flatpak by its
254
+ * application ID with the `arch` and `branch` of its ref. A row's `path`,
255
+ * `admindir` or source file is not a path inside the package, so none of them
256
+ * becomes a subpath.
257
+ *
258
+ * @param {string} purlType One of OS_PACKAGE_PURL_TYPES
259
+ * @param {Object} res osquery row
260
+ * @param {string} name Component name derived from the row
261
+ * @param {Object} [distro] What getDistroInfo() reports; the host's by default
262
+ * @returns {{version: string, purl: string|null}} The component version and the purl, which is null when the row cannot be expressed as one
263
+ */
264
+ export function createOsQueryPackageIdentity(purlType, res, name, distro) {
265
+ const pkg = {
266
+ type: purlType,
267
+ name,
268
+ version: res.version,
269
+ arch: res.arch,
270
+ };
271
+ if (purlType === "rpm") {
272
+ pkg.release = res.release;
273
+ pkg.epoch = res.epoch;
274
+ } else if (purlType === "macports") {
275
+ pkg.revision = res.revision;
276
+ } else if (purlType === "flatpak") {
277
+ pkg.branch = res.branch;
278
+ }
279
+ return {
280
+ version: osPackageVersion(pkg),
281
+ purl: buildOsPackagePurl(pkg, distro),
282
+ };
283
+ }
284
+
285
+ // Columns of an OS package table that are named or dropped on their way to a
286
+ // property. A bare `source` is the source package of a deb, the source rpm file
287
+ // of an rpm and the pkgbase of a pacman package, and `type` says whether a
288
+ // Homebrew entry is a formula or a cask, a snap an app or a base, a flatpak an
289
+ // app or a runtime, so each takes the name that says which. The dpkg
290
+ // administrative directory and the pacman database path are the same for every
291
+ // package and say nothing about one.
292
+ const OS_PACKAGE_PROPERTY_NAMES = {
293
+ deb: { source: "cdx:deb:source", admindir: undefined },
294
+ rpm: { source: "cdx:rpm:sourceRpm" },
295
+ apk: { origin: "cdx:apk:origin" },
296
+ alpm: { source: "cdx:alpm:pkgbase", dbpath: undefined },
297
+ brew: { type: "cdx:homebrew:type" },
298
+ macports: {
299
+ variants: "cdx:macports:variants",
300
+ requested: "cdx:macports:requested",
301
+ },
302
+ snap: {
303
+ type: "cdx:snap:type",
304
+ revision: "cdx:snap:revision",
305
+ channel: "cdx:snap:channel",
306
+ confinement: "cdx:snap:confinement",
307
+ },
308
+ flatpak: {
309
+ display_name: "cdx:flatpak:displayName",
310
+ commit_hash: "cdx:flatpak:commit",
311
+ origin: "cdx:flatpak:origin",
312
+ installation: "cdx:flatpak:installation",
313
+ type: "cdx:flatpak:type",
314
+ runtime: "cdx:flatpak:runtime",
315
+ },
316
+ };
317
+
318
+ /**
319
+ * The columns of an OS package row that give its licence and its home page.
320
+ * They become the component's `licenses` and a `website` reference rather than
321
+ * properties. `separator` splits a licence column that lists several: pacman
322
+ * keeps one licence per entry, which osquery joins with commas.
323
+ */
324
+ export const OS_PACKAGE_LICENSE_COLUMNS = {
325
+ alpm: { license: "licenses", separator: ",", website: "url" },
326
+ snap: { license: "license" },
327
+ flatpak: { license: "license", website: "homepage" },
328
+ };
329
+
330
+ /**
331
+ * The property name an OS package table column is emitted under.
332
+ *
333
+ * @param {string|undefined} purlType Purl type of the table
334
+ * @param {string} column Column name
335
+ * @returns {string|undefined} The property name, or undefined when the column is not emitted
336
+ */
337
+ export function osPackagePropertyName(purlType, column) {
338
+ const fields = OS_PACKAGE_LICENSE_COLUMNS[purlType];
339
+ if (fields && (column === fields.license || column === fields.website)) {
340
+ return undefined;
341
+ }
342
+ const names = OS_PACKAGE_PROPERTY_NAMES[purlType];
343
+ if (names && Object.hasOwn(names, column)) {
344
+ return names[column];
345
+ }
346
+ return column;
347
+ }
348
+
240
349
  function _deriveCertificateFormat(certificateFileExtension) {
241
350
  switch ((certificateFileExtension || "").toLowerCase()) {
242
351
  case "pem":
@@ -0,0 +1,438 @@
1
+ // Bounded PHP build-version reader for unpackaged PHP binaries and
2
+ // libraries. Recovers the version a source-built PHP carries, so a BOM can
3
+ // answer "does this image ship a PHP affected by CVE-2026-109618/109619".
4
+ //
5
+ // The primary marker comes from php-src: SAPI_PHP_VERSION_HEADER is
6
+ // "X-Powered-By: PHP/" PHP_VERSION and is compiled into every SAPI binary and
7
+ // libphp, e.g. the literal `X-Powered-By: PHP/8.4.26`:
8
+ // https://github.com/php/php-src/blob/php-8.4.27/main/SAPI.h#L309
9
+ // The same definition is present in 7.4 through 8.5 (main/SAPI.h at each tag).
10
+ // If a stripped or unusual build lacks it, the installed php_version.h is read
11
+ // from the same prefix. configure.ac generates its PHP_VERSION line:
12
+ // https://github.com/php/php-src/blob/php-8.4.27/configure.ac#L83-L90
13
+ // and install-headers copies it under $(phpincludedir)/main:
14
+ // https://github.com/php/php-src/blob/php-8.4.27/build/Makefile.global#L52
15
+ //
16
+ // Every file in an image is hostile: only regular files inside the extracted
17
+ // root are opened, reads are capped, and a missing/odd file yields no result
18
+ // rather than throwing.
19
+ //
20
+ // The runtime component helpers at the end are the one place a PHP version
21
+ // becomes a component, for the container scan, the OS scan and formulation.
22
+
23
+ import { closeSync, readSync } from "node:fs";
24
+ import { basename, join, resolve, sep } from "node:path";
25
+
26
+ import { openRegularFile } from "../core/boundedFile.js";
27
+ import { applyPurl, tryBuildPurl } from "./purl.js";
28
+
29
+ // The version grammar the contract accepts: M.m.p, optionally with -dev or an
30
+ // alpha/beta/RC number.
31
+ const VERSION_BODY = "\\d+\\.\\d+\\.\\d+(?:-dev|(?:alpha|beta|RC)\\d+)?";
32
+ const MARKER = Buffer.from("X-Powered-By: PHP/");
33
+ const MAX_VERSION_BYTES = 32;
34
+ // Overlap is at least the marker length plus 32 bytes, per the contract, so a
35
+ // marker split across two reads is seen whole by the second one.
36
+ const OVERLAP_BYTES = MARKER.length + 32;
37
+ const READ_CHUNK_BYTES = 1024 * 1024;
38
+ const MAX_BINARY_BYTES = 256 * 1024 * 1024;
39
+ const MAX_HEADER_BYTES = 64 * 1024;
40
+
41
+ /**
42
+ * The most PHP-named files one scan opens in search of a build version.
43
+ *
44
+ * @type {number}
45
+ */
46
+ export const MAX_PHP_PROBES = 64;
47
+
48
+ // The trailing lookahead rejects 8.4.26.1 and 8.4.26foo while accepting
49
+ // 8.4.26, 8.4.26-dev and 8.6.0RC3 followed by a non-version byte.
50
+ const VERSION_PREFIX_RE = new RegExp(`^(${VERSION_BODY})(?![0-9A-Za-z.-])`);
51
+ const HEADER_VERSION_RE = new RegExp(
52
+ `^#define\\s+PHP_VERSION\\s+"(${VERSION_BODY})"\\s*$`,
53
+ "m",
54
+ );
55
+
56
+ /**
57
+ * Read the version a text starts with, in the grammar the build-version reader
58
+ * accepts: `8.4.27`, `8.5.0-dev`, `8.6.0RC3`. What follows the version must not
59
+ * continue it, so `8.4.26.1` and `8.4.26foo` parse to nothing. A distro
60
+ * revision such as Ubuntu's `8.1.2-1ubuntu2.14` is not an upstream version
61
+ * either, and parses to nothing rather than to a guess.
62
+ *
63
+ * @param {string} text text that begins with the version
64
+ * @returns {string|undefined} the version, or undefined when the text does not
65
+ * begin with one
66
+ */
67
+ export function parsePhpVersion(text) {
68
+ if (typeof text !== "string") {
69
+ return undefined;
70
+ }
71
+ // One character past the maximum lets the lookahead see a too-long version
72
+ // instead of treating the end of the slice as a boundary.
73
+ const match = VERSION_PREFIX_RE.exec(text.slice(0, MAX_VERSION_BYTES + 1));
74
+ if (!match || match[1].length > MAX_VERSION_BYTES) {
75
+ return undefined;
76
+ }
77
+ return match[1];
78
+ }
79
+
80
+ // A distro package can append its revision to PHP_VERSION, as Ubuntu 22.04
81
+ // does. The digit after the hyphen tells it from PHP's own `-dev`.
82
+ const DISTRO_VERSION_RE =
83
+ /^\d+\.\d+\.\d+-\d[0-9A-Za-z.+~-]*(?![0-9A-Za-z.+~-])/;
84
+ const MAX_REPORTED_VERSION_LENGTH = 64;
85
+
86
+ /**
87
+ * Read the version a PHP reports about itself, as `php -v` prints it. An
88
+ * upstream version parses as parsePhpVersion does. A version with a distro
89
+ * revision, such as Ubuntu's `8.1.2-1ubuntu2.14`, is kept whole and marked: it
90
+ * is what that PHP reports, but the distro backports fixes without changing the
91
+ * upstream part, so it must not be matched against upstream advisories.
92
+ *
93
+ * @param {string} text text that begins with the version
94
+ * @returns {{version: string, distroRevision: boolean}|undefined} the version,
95
+ * or undefined when the text does not begin with one
96
+ */
97
+ export function parsePhpReportedVersion(text) {
98
+ const version = parsePhpVersion(text);
99
+ if (version) {
100
+ return { version, distroRevision: false };
101
+ }
102
+ if (typeof text !== "string") {
103
+ return undefined;
104
+ }
105
+ const match = DISTRO_VERSION_RE.exec(
106
+ text.slice(0, MAX_REPORTED_VERSION_LENGTH + 1),
107
+ );
108
+ if (!match || match[0].length > MAX_REPORTED_VERSION_LENGTH) {
109
+ return undefined;
110
+ }
111
+ return { version: match[0], distroRevision: true };
112
+ }
113
+
114
+ /**
115
+ * The package manager whose store a path lies in. Homebrew (`Cellar/<formula>/
116
+ * <version>/`), Nix (`nix/store/`) and Guix (`gnu/store/`) own everything under
117
+ * their stores, whatever links lead there, and a PHP they own is identified by
118
+ * their package, never as an unpackaged build.
119
+ *
120
+ * @param {string} filePath absolute or root-relative path, either separator
121
+ * @returns {string|undefined} `homebrew`, `nix` or `guix`, or undefined
122
+ */
123
+ export function packageStoreOf(filePath) {
124
+ if (typeof filePath !== "string" || !filePath) {
125
+ return undefined;
126
+ }
127
+ const parts = filePath.split(/[\\/]+/);
128
+ const cellar = parts.indexOf("Cellar");
129
+ if (cellar !== -1 && parts.length - cellar >= 4) {
130
+ return "homebrew";
131
+ }
132
+ for (let index = 0; index + 2 < parts.length; index++) {
133
+ if (parts[index + 1] !== "store") {
134
+ continue;
135
+ }
136
+ if (parts[index] === "nix") {
137
+ return "nix";
138
+ }
139
+ if (parts[index] === "gnu") {
140
+ return "guix";
141
+ }
142
+ }
143
+ return undefined;
144
+ }
145
+
146
+ // Installed names come from the per-SAPI Makefile.frag files at the 8.4.27
147
+ // tag: bin/php, bin/php-cgi, sbin/php-fpm, bin/phpdbg and libs/libphp.so,
148
+ // each with the optional $(program_prefix)/$(program_suffix):
149
+ // https://github.com/php/php-src/blob/php-8.4.27/sapi/cli/Makefile.frag#L9
150
+ // https://github.com/php/php-src/blob/php-8.4.27/sapi/cgi/Makefile.frag#L9
151
+ // https://github.com/php/php-src/blob/php-8.4.27/sapi/fpm/Makefile.frag#L11
152
+ // https://github.com/php/php-src/blob/php-8.4.27/sapi/phpdbg/Makefile.frag#L28
153
+ // The Apache module is libphp.so (libs/libphp.so in build/Makefile.global).
154
+ // FrankenPHP embeds PHP in its own binary.
155
+ const PHP_SAPI_BY_NAME = [
156
+ [/^php(\d+(\.\d+)?)?$/, "cli"],
157
+ [/^php-fpm(\d+(\.\d+)?)?$/, "fpm"],
158
+ [/^php-cgi(\d+(\.\d+)?)?$/, "cgi"],
159
+ [/^phpdbg(\d+(\.\d+)?)?$/, "phpdbg"],
160
+ [/^libphp(\d+(\.\d+)?)?\.so$/, "libphp"],
161
+ [/^frankenphp$/, "frankenphp"],
162
+ ];
163
+
164
+ /**
165
+ * Classify a file by its basename into a PHP SAPI, or undefined when the name
166
+ * is not one php-src installs.
167
+ *
168
+ * @param {string} fileName basename or path of the candidate
169
+ * @returns {string|undefined} cli, fpm, cgi, phpdbg, libphp or frankenphp
170
+ */
171
+ export function classifyPhpSapi(fileName) {
172
+ if (typeof fileName !== "string" || !fileName) {
173
+ return undefined;
174
+ }
175
+ const name = basename(fileName);
176
+ for (const [pattern, sapi] of PHP_SAPI_BY_NAME) {
177
+ if (pattern.test(name)) {
178
+ return sapi;
179
+ }
180
+ }
181
+ return undefined;
182
+ }
183
+
184
+ function matchVersionAt(buffer, start, readLength) {
185
+ if (start >= readLength) {
186
+ return undefined;
187
+ }
188
+ // One byte past the maximum lets the lookahead see a too-long version
189
+ // instead of treating the end of the slice as a boundary.
190
+ const end = Math.min(readLength, start + MAX_VERSION_BYTES + 1);
191
+ return parsePhpVersion(buffer.toString("latin1", start, end));
192
+ }
193
+
194
+ /**
195
+ * Scan a bounded prefix of a binary for the compiled-in marker. Files larger
196
+ * than the cap are skipped entirely: the contract treats them as hostile and
197
+ * emits no property.
198
+ *
199
+ * @param {number} fd open file descriptor
200
+ * @param {number} size file size from fstat
201
+ * @returns {string|undefined}
202
+ */
203
+ function scanBinaryVersion(fd, size) {
204
+ if (size <= 0 || size > MAX_BINARY_BYTES) {
205
+ return undefined;
206
+ }
207
+ const bufferSize = Math.min(size, READ_CHUNK_BYTES) + OVERLAP_BYTES;
208
+ const buffer = Buffer.allocUnsafe(bufferSize);
209
+ for (let position = 0; position < size; position += READ_CHUNK_BYTES) {
210
+ const want = Math.min(bufferSize, size - position);
211
+ const read = readSync(fd, buffer, 0, want, position);
212
+ if (read <= 0) {
213
+ return undefined;
214
+ }
215
+ // Only matches starting inside this window are new; the overlap exists so
216
+ // a version split at the window edge is readable. The last window has no
217
+ // next one, so everything it read is searched.
218
+ const isLast = position + read >= size;
219
+ const searchLimit = isLast ? read : Math.min(READ_CHUNK_BYTES, read);
220
+ // Bytes a previous, longer read left past `read` are never searched.
221
+ const window = buffer.subarray(0, read);
222
+ let offset = window.indexOf(MARKER);
223
+ while (offset !== -1 && offset < searchLimit) {
224
+ const version = matchVersionAt(buffer, offset + MARKER.length, read);
225
+ if (version) {
226
+ return version;
227
+ }
228
+ offset = window.indexOf(MARKER, offset + 1);
229
+ }
230
+ if (read < bufferSize) {
231
+ return undefined;
232
+ }
233
+ }
234
+ return undefined;
235
+ }
236
+
237
+ /**
238
+ * Derive the install prefix from a path installed under bin, sbin or lib:
239
+ * /usr/local/bin/php -> /usr/local, /usr/lib/apache2/modules/libphp.so -> /usr.
240
+ *
241
+ * @param {string} absPath absolute path
242
+ * @returns {string|undefined}
243
+ */
244
+ function installPrefix(absPath) {
245
+ const parts = resolve(absPath).split(sep);
246
+ for (let i = parts.length - 2; i >= 0; i--) {
247
+ if (parts[i] === "bin" || parts[i] === "sbin" || parts[i] === "lib") {
248
+ return parts.slice(0, i).join(sep) || sep;
249
+ }
250
+ }
251
+ return undefined;
252
+ }
253
+
254
+ /**
255
+ * Read PHP_VERSION from <prefix>/include/php/main/php_version.h. Only called
256
+ * for files installed under bin, sbin or lib, and only when the binary marker
257
+ * was absent.
258
+ *
259
+ * @param {string} absPath path of the PHP binary/library
260
+ * @param {string|undefined} rootDir extracted root
261
+ * @returns {string|undefined}
262
+ */
263
+ function readHeaderVersion(absPath, rootDir) {
264
+ const prefix = installPrefix(absPath);
265
+ if (!prefix) {
266
+ return undefined;
267
+ }
268
+ const headerPath = join(prefix, "include", "php", "main", "php_version.h");
269
+ const opened = openRegularFile(headerPath, rootDir);
270
+ if (!opened) {
271
+ return undefined;
272
+ }
273
+ try {
274
+ const length = Math.min(opened.size, MAX_HEADER_BYTES);
275
+ if (length <= 0) {
276
+ return undefined;
277
+ }
278
+ const buffer = Buffer.allocUnsafe(length);
279
+ const read = readSync(opened.fd, buffer, 0, length, 0);
280
+ if (read <= 0) {
281
+ return undefined;
282
+ }
283
+ const match = HEADER_VERSION_RE.exec(buffer.toString("latin1", 0, read));
284
+ return match ? match[1] : undefined;
285
+ } catch {
286
+ return undefined;
287
+ } finally {
288
+ try {
289
+ closeSync(opened.fd);
290
+ } catch {
291
+ // already closed
292
+ }
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Read the PHP build version of a binary or shared library.
298
+ *
299
+ * @param {string} absPath absolute path
300
+ * @param {string|undefined} rootDir extracted root, when the file came from one
301
+ * @returns {{version: string, source: string}|undefined} source is `binary`
302
+ * or `php_version.h`
303
+ */
304
+ export function readPhpBuildVersion(absPath, rootDir) {
305
+ const opened = openRegularFile(absPath, rootDir);
306
+ if (!opened) {
307
+ return undefined;
308
+ }
309
+ try {
310
+ const fromBinary = scanBinaryVersion(opened.fd, opened.size);
311
+ if (fromBinary) {
312
+ return { version: fromBinary, source: "binary" };
313
+ }
314
+ } finally {
315
+ try {
316
+ closeSync(opened.fd);
317
+ } catch {
318
+ // already closed
319
+ }
320
+ }
321
+ const fromHeader = readHeaderVersion(absPath, rootDir);
322
+ return fromHeader
323
+ ? { version: fromHeader, source: "php_version.h" }
324
+ : undefined;
325
+ }
326
+
327
+ /**
328
+ * The CPE that lets a downstream scanner match a PHP version. NVD puts a
329
+ * pre-release suffix in the update field, e.g.
330
+ * cpe:2.3:a:php:php:8.5.0:rc1:*:*:*:*:*:* (NVD CPE dictionary).
331
+ *
332
+ * @param {string} version PHP version in the build-version grammar
333
+ * @returns {string} CPE 2.3 string
334
+ */
335
+ export function phpCpe(version) {
336
+ const match = /^(\d+\.\d+\.\d+)(?:-(dev)|(alpha|beta|RC)(\d+))?$/.exec(
337
+ version,
338
+ );
339
+ if (!match) {
340
+ return `cpe:2.3:a:php:php:${version}:*:*:*:*:*:*:*`;
341
+ }
342
+ let update = "*";
343
+ if (match[2]) {
344
+ update = "dev";
345
+ } else if (match[3]) {
346
+ update = `${match[3].toLowerCase()}${match[4]}`;
347
+ }
348
+ return `cpe:2.3:a:php:php:${match[1]}:${update}:*:*:*:*:*:*`;
349
+ }
350
+
351
+ /**
352
+ * A PHP runtime is one `platform` component per distinct version, like the Go
353
+ * stdlib component. Its identity is `pkg:generic/php`: it is only used where no
354
+ * package manager owns PHP, or where a PATH probe cannot tell which one does.
355
+ *
356
+ * @param {string} version PHP version in the build-version grammar, or as a
357
+ * distro build reports it
358
+ * @param {Object} [options] options
359
+ * @param {boolean} [options.withCpe=true] false for a version with a distro
360
+ * revision, which no upstream CPE describes
361
+ * @returns {Object} component with `purl`, `bom-ref` and, unless left out, `cpe`
362
+ */
363
+ export function createPhpRuntimeComponent(version, { withCpe = true } = {}) {
364
+ const component = { type: "platform", name: "php", version };
365
+ if (withCpe) {
366
+ component.cpe = phpCpe(version);
367
+ }
368
+ return applyPurl(
369
+ component,
370
+ tryBuildPurl({ type: "generic", name: "php", version }),
371
+ );
372
+ }
373
+
374
+ /**
375
+ * The bom-ref of the runtime component for a version.
376
+ *
377
+ * @param {string} version PHP version in the build-version grammar
378
+ * @returns {string} bom-ref, which is the runtime's purl
379
+ */
380
+ export function phpRuntimeRef(version) {
381
+ return createPhpRuntimeComponent(version)["bom-ref"];
382
+ }
383
+
384
+ /**
385
+ * The properties a PHP file component carries once its build version is known.
386
+ * They belong on unpackaged `file` components only: a package-owned PHP is
387
+ * identified by its package, and distro builds carry backported fixes under
388
+ * old version numbers, so the PHP-RCE audit rules that match these properties
389
+ * would raise false positives there.
390
+ *
391
+ * @param {{version: string, source: string}} build result of readPhpBuildVersion
392
+ * @param {string} sapi result of classifyPhpSapi
393
+ * @returns {{name: string, value: string}[]} properties to append
394
+ */
395
+ export function phpBuildProperties(build, sapi) {
396
+ return [
397
+ { name: "cdx:php:buildVersion", value: build.version },
398
+ { name: "cdx:php:buildVersionSource", value: build.source },
399
+ { name: "cdx:php:sapi", value: sapi },
400
+ ];
401
+ }
402
+
403
+ /**
404
+ * Start collecting the runtime components and edges of one scan.
405
+ *
406
+ * @returns {{runtimeComponents: Object[], dependencies: Object[], runtimeRefs: Set<string>}}
407
+ */
408
+ export function createPhpRuntimeInventory() {
409
+ return { runtimeComponents: [], dependencies: [], runtimeRefs: new Set() };
410
+ }
411
+
412
+ /**
413
+ * Tag a PHP file component with its build facts and link it to the runtime
414
+ * component of its version, creating that component on first use. Mutates the
415
+ * component in place, as the Go collector does.
416
+ *
417
+ * @param {{runtimeComponents: Object[], dependencies: Object[], runtimeRefs: Set<string>}} inventory
418
+ * accumulator from createPhpRuntimeInventory
419
+ * @param {Object} component file component, with a `properties` array
420
+ * @param {{version: string, source: string}} build result of readPhpBuildVersion
421
+ * @param {string} sapi result of classifyPhpSapi
422
+ */
423
+ export function attachPhpBuild(inventory, component, build, sapi) {
424
+ component.properties.push(...phpBuildProperties(build, sapi));
425
+ const runtime = createPhpRuntimeComponent(build.version);
426
+ const runtimeRef = runtime["bom-ref"];
427
+ if (!inventory.runtimeRefs.has(runtimeRef)) {
428
+ inventory.runtimeRefs.add(runtimeRef);
429
+ inventory.runtimeComponents.push(runtime);
430
+ inventory.dependencies.push({ ref: runtimeRef, dependsOn: [] });
431
+ }
432
+ if (component["bom-ref"]) {
433
+ inventory.dependencies.push({
434
+ ref: component["bom-ref"],
435
+ dependsOn: [runtimeRef],
436
+ });
437
+ }
438
+ }