@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
@@ -0,0 +1,185 @@
1
+ // Bounded, read-only probe for the PHP no package manager owns on a live host,
2
+ // for `-t os` / obom. osquery's package tables list what dpkg, rpm and the like
3
+ // installed, so a PHP built from source (a Docker Official image, FrankenPHP, a
4
+ // /usr/local or /opt build) is invisible to them, and with it the version a PHP
5
+ // advisory is read against.
6
+ //
7
+ // Candidates come from a few fixed directories, one level deep, never from a
8
+ // walk of the file system. A candidate is opened only after the package
9
+ // databases have said they do not own it: a package manager's file is
10
+ // identified by its package, and distros backport fixes under old PHP version
11
+ // numbers, which the cdx:php properties would misstate. Only files nothing owns
12
+ // are read, for their bytes; nothing is executed.
13
+ //
14
+ // A package database that exists but cannot be read leaves ownership unknown.
15
+ // Unknown is not unpackaged: it emits nothing and records a degradation.
16
+
17
+ import { dirname, join } from "node:path";
18
+
19
+ import {
20
+ isDryRun,
21
+ readEnvironmentVariable,
22
+ recordActivity,
23
+ } from "../core/activity.js";
24
+ import { recordDegradation } from "../core/buildLedger.js";
25
+ import {
26
+ isDirectory,
27
+ listDirectory,
28
+ probeHostRuntime,
29
+ } from "./hostRuntimeProbe.js";
30
+ import {
31
+ attachPhpBuild,
32
+ classifyPhpSapi,
33
+ createPhpRuntimeInventory,
34
+ MAX_PHP_PROBES,
35
+ readPhpBuildVersion,
36
+ } from "./phpBuildInfo.js";
37
+
38
+ // Where the PHP of a source build is installed, relative to the root. `only`
39
+ // narrows a directory to one SAPI. `opt/*/bin` and `opt/*/sbin` read /opt one
40
+ // level down and nothing below that.
41
+ const POSIX_DIRECTORIES = {
42
+ linux: [
43
+ { dir: "usr/local/bin" },
44
+ { dir: "usr/local/sbin" },
45
+ { dir: "usr/local/lib", only: "libphp" },
46
+ { dir: "usr/lib/apache2/modules", only: "libphp" },
47
+ { dir: "usr/lib/httpd/modules", only: "libphp" },
48
+ { dir: "usr/lib64/httpd/modules", only: "libphp" },
49
+ { dir: "opt/*/bin" },
50
+ { dir: "opt/*/sbin" },
51
+ ],
52
+ darwin: [
53
+ { dir: "usr/local/bin" },
54
+ { dir: "usr/local/sbin" },
55
+ { dir: "opt/homebrew/bin" },
56
+ { dir: "opt/homebrew/sbin" },
57
+ ],
58
+ };
59
+
60
+ // The programs of a php.net Windows build. The CGI and debugger executables
61
+ // hold no version of their own; the core DLL beside them does (php8.dll, or
62
+ // php8ts.dll for a thread-safe build).
63
+ const WINDOWS_SAPIS = {
64
+ "php.exe": "cli",
65
+ "php-win.exe": "cli",
66
+ "php-cgi.exe": "cgi",
67
+ "phpdbg.exe": "phpdbg",
68
+ };
69
+
70
+ function isWindowsCoreLibrary(name) {
71
+ const lower = name.toLowerCase();
72
+ if (!lower.startsWith("php") || !lower.endsWith(".dll")) {
73
+ return false;
74
+ }
75
+ const stem = lower.slice("php".length, -".dll".length);
76
+ return /^\d+$/.test(stem.endsWith("ts") ? stem.slice(0, -2) : stem);
77
+ }
78
+
79
+ // The directories to look in, as absolute paths.
80
+ function candidateDirectories(context) {
81
+ const { rootPath, platform } = context;
82
+ const directories = [];
83
+ if (platform === "win32") {
84
+ const programFiles =
85
+ context.readEnv("ProgramFiles") || join(rootPath, "Program Files");
86
+ for (const parent of [rootPath, join(rootPath, "tools"), programFiles]) {
87
+ for (const name of listDirectory(parent)) {
88
+ const directory = join(parent, name);
89
+ if (name.toLowerCase().startsWith("php") && isDirectory(directory)) {
90
+ directories.push({ directory });
91
+ }
92
+ }
93
+ }
94
+ return directories;
95
+ }
96
+ for (const { dir, only } of POSIX_DIRECTORIES[platform] || []) {
97
+ if (!dir.startsWith("opt/*/")) {
98
+ directories.push({ directory: join(rootPath, dir), only });
99
+ continue;
100
+ }
101
+ const leaf = dir.slice("opt/*/".length);
102
+ for (const name of listDirectory(join(rootPath, "opt"))) {
103
+ const directory = join(rootPath, "opt", name, leaf);
104
+ if (isDirectory(directory)) {
105
+ directories.push({ directory, only });
106
+ }
107
+ }
108
+ }
109
+ return directories;
110
+ }
111
+
112
+ // On Windows the CGI and debugger executables hold no version; the core DLL in
113
+ // the same directory does.
114
+ function readBuild(context, candidate) {
115
+ const build = readPhpBuildVersion(candidate.real, context.realRoot);
116
+ if (build || context.platform !== "win32") {
117
+ return build;
118
+ }
119
+ const directory = dirname(candidate.real);
120
+ for (const name of listDirectory(directory)) {
121
+ if (isWindowsCoreLibrary(name)) {
122
+ const fromLibrary = readPhpBuildVersion(
123
+ join(directory, name),
124
+ context.realRoot,
125
+ );
126
+ if (fromLibrary) {
127
+ return fromLibrary;
128
+ }
129
+ }
130
+ }
131
+ return undefined;
132
+ }
133
+
134
+ /**
135
+ * Look for PHP no package manager owns, in the fixed directories of the host.
136
+ * Components come back only for the files nothing owns: a `file` component for
137
+ * each PHP binary or library with the cdx:php properties, one `platform`
138
+ * component per version with its CPE, and the edges between them.
139
+ *
140
+ * @param {Object} [options] probe options
141
+ * @param {string} [options.rootPath] directory the fixed directories hang off:
142
+ * `/`, or the system drive on Windows. Tests point it at a built tree.
143
+ * @param {string} [options.platform] `linux`, `darwin` or `win32`
144
+ * @param {Object} [options.env] environment read for ProgramFiles, SystemDrive
145
+ * and ChocolateyInstall, instead of the process's
146
+ * @param {{dpkgInfoDirectory?: string, apkInstalledFile?: string, rpmDatabaseDirectories?: string[]}} [options.packageDatabases]
147
+ * where the package databases are, instead of under `rootPath`
148
+ * @param {function(string): (Object[]|undefined)} [options.executeOsQuery] runs
149
+ * an osquery query; the rpm database is only asked through it
150
+ * @param {Object[]} [options.packageComponents] the packages the OS scan has
151
+ * inventoried, from which Chocolatey's are known
152
+ * @param {number} [options.maxProbes] most candidate files to look at
153
+ * @returns {{components: Object[], dependencies: Object[]}}
154
+ */
155
+ export function probeHostPhp(options = {}) {
156
+ return probeHostRuntime(options, {
157
+ label: "PHP",
158
+ ecosystem: "php",
159
+ capability: "php-host-probe",
160
+ remediationId: "php.host-probe.package-db-unreadable",
161
+ maxProbes: MAX_PHP_PROBES,
162
+ chocolateyPackages: ["php"],
163
+ // Chocolatey's php package unpacks under C:\tools by default.
164
+ chocolateyDirectories: (context) => [join(context.rootPath, "tools")],
165
+ directories: candidateDirectories,
166
+ classify: (name, platform) =>
167
+ platform === "win32"
168
+ ? WINDOWS_SAPIS[name.toLowerCase()]
169
+ : classifyPhpSapi(name),
170
+ readBuild,
171
+ createInventory: createPhpRuntimeInventory,
172
+ attach: (inventory, component, build, candidate) =>
173
+ attachPhpBuild(inventory, component, build, candidate.kind),
174
+ finish: (inventory) => ({
175
+ components: inventory.runtimeComponents,
176
+ dependencies: inventory.dependencies,
177
+ }),
178
+ hooks: {
179
+ isDryRun,
180
+ readEnvironmentVariable,
181
+ recordActivity,
182
+ recordDegradation,
183
+ },
184
+ });
185
+ }
@@ -0,0 +1,426 @@
1
+ import {
2
+ closeSync,
3
+ lstatSync,
4
+ openSync,
5
+ readdirSync,
6
+ readSync,
7
+ realpathSync,
8
+ } from "node:fs";
9
+ import { basename, join, relative, sep } from "node:path";
10
+
11
+ import {
12
+ androidNetworkSecurityConfigPins,
13
+ CertificateCollector,
14
+ certRecordFromDer,
15
+ classifyCryptoFile,
16
+ kubernetesTlsSecretRecords,
17
+ linkFallbackIssuers,
18
+ } from "./certificates.js";
19
+
20
+ // Bounds mirror trustinspector's rootfs mode (PLAN section 4.1): 16 MiB per
21
+ // file, 20,000 files read and 500,000 entries visited per tree, depth 12.
22
+ export const ROOTFS_CERT_LIMITS = Object.freeze({
23
+ maxFileBytes: 16 * 1024 * 1024,
24
+ maxFiles: 20000,
25
+ maxEntries: 500000,
26
+ maxDepth: 12,
27
+ });
28
+
29
+ // Coverage table: the same locations and sourceTypes as trustinspector's
30
+ // rootfs mode (thirdparty/trustinspector/rootfs.go candidateDirs).
31
+ const CA_STORE_DIRS = [
32
+ "etc/ssl/certs",
33
+ "usr/local/share/ca-certificates",
34
+ "usr/share/ca-certificates",
35
+ "etc/pki/tls/certs",
36
+ "etc/pki/ca-trust/source/anchors",
37
+ "etc/pki/ca-trust/extracted",
38
+ "var/lib/ca-certificates",
39
+ "usr/share/pki/ca-trust-source/anchors",
40
+ ];
41
+ const CA_STORE_FILES = ["etc/ssl/cert.pem"];
42
+ const CA_BLOCKLIST_DIRS = [
43
+ "etc/pki/ca-trust/source/blocklist",
44
+ "etc/pki/ca-trust/source/blacklist",
45
+ "usr/share/pki/ca-trust-source/blocklist",
46
+ "usr/share/pki/ca-trust-source/blacklist",
47
+ ];
48
+ const SERVER_CONFIG_DIRS = [
49
+ "etc/nginx",
50
+ "etc/apache2",
51
+ "etc/httpd",
52
+ "etc/haproxy",
53
+ "etc/letsencrypt/live",
54
+ "etc/letsencrypt/archive",
55
+ "etc/kubernetes/pki",
56
+ "var/lib/kubelet/pki",
57
+ "etc/docker/certs.d",
58
+ "etc/containerd/certs.d",
59
+ "etc/envoy",
60
+ "etc/traefik",
61
+ ];
62
+ const JAVA_DIRS = [
63
+ "usr/lib/jvm",
64
+ "opt",
65
+ "etc/ssl/certs/java",
66
+ "etc/pki/java",
67
+ "usr/share/elasticsearch",
68
+ ];
69
+ const LANGUAGE_BUNDLE_DIRS = ["usr/lib", "usr/local/lib", "opt"];
70
+ // Kubernetes secrets and Android configs are cdxgen-side producers: the
71
+ // plugin has no YAML or Android parser.
72
+ const SECRET_YAML_DIRS = [
73
+ "etc/kubernetes",
74
+ "var/run/secrets",
75
+ "run/secrets",
76
+ "var/lib/kubelet",
77
+ "srv",
78
+ "app",
79
+ "opt",
80
+ "home",
81
+ "root",
82
+ ];
83
+ const ANDROID_DIRS = ["app", "opt", "srv", "data", "home", "root"];
84
+
85
+ const CERT_SUFFIXES = [
86
+ ".pem",
87
+ ".crt",
88
+ ".cer",
89
+ ".der",
90
+ ".p7b",
91
+ ".p7c",
92
+ ".p12",
93
+ ".pfx",
94
+ ".jks",
95
+ ".jceks",
96
+ ".truststore",
97
+ ".key",
98
+ ];
99
+
100
+ function isJavaKeystoreName(base) {
101
+ if (base === "cacerts" || base === "jssecacerts") {
102
+ return true;
103
+ }
104
+ return [".jks", ".jceks", ".truststore", ".p12", ".pfx"].some((suffix) =>
105
+ base.endsWith(suffix),
106
+ );
107
+ }
108
+
109
+ // Mirrors looksLikeTrustMaterialPath in trustinspector: private-key
110
+ // directories are never read.
111
+ function looksLikeTrustMaterialPath(rel, sourceType) {
112
+ const lower = rel.toLowerCase();
113
+ if (
114
+ lower.startsWith("etc/ssl/private/") ||
115
+ lower.startsWith("etc/pki/tls/private/")
116
+ ) {
117
+ return false;
118
+ }
119
+ const base = basename(lower);
120
+ if (sourceType === "language-bundle") {
121
+ return base === "cacert.pem";
122
+ }
123
+ if (sourceType === "java-truststore") {
124
+ return isJavaKeystoreName(base);
125
+ }
126
+ if (isJavaKeystoreName(base)) {
127
+ return true;
128
+ }
129
+ return (
130
+ CERT_SUFFIXES.some((suffix) => lower.endsWith(suffix)) ||
131
+ lower.includes("/certs/")
132
+ );
133
+ }
134
+
135
+ /**
136
+ * Read at most `maxBytes` of a regular file. The cap applies to the read
137
+ * itself, so a file that grows after the size check cannot exceed it. Files
138
+ * that are not regular (FIFOs, devices, sockets) are never opened.
139
+ */
140
+ function readCapped(absolute, maxBytes) {
141
+ const before = lstatSync(absolute);
142
+ if (!before.isFile() || before.size > maxBytes) {
143
+ return undefined;
144
+ }
145
+ const fd = openSync(absolute, "r");
146
+ try {
147
+ const buffer = Buffer.alloc(Math.min(before.size, maxBytes) + 1);
148
+ let total = 0;
149
+ while (total < buffer.length) {
150
+ const n = readSync(fd, buffer, total, buffer.length - total, null);
151
+ if (n === 0) {
152
+ break;
153
+ }
154
+ total += n;
155
+ }
156
+ if (total > maxBytes) {
157
+ return undefined;
158
+ }
159
+ return buffer.subarray(0, total);
160
+ } finally {
161
+ closeSync(fd);
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Pure-JS inventory of the certificate locations trustinspector's rootfs
167
+ * mode covers, plus Kubernetes TLS secrets and Android pins. Used when the
168
+ * plugin is not available. Produces components through the shared
169
+ * CertificateCollector, so bom-refs, names and deduplication match the other
170
+ * producers; locations are root-relative ("/etc/ssl/certs/x.crt").
171
+ *
172
+ * Every access stays inside the root: symlinks (files or directories) are
173
+ * skipped, only regular files are opened, and the walk is bounded by entry,
174
+ * file, size and depth limits.
175
+ *
176
+ * @param {string} rootDir extracted root filesystem
177
+ * @param {object} [limits] overrides for ROOTFS_CERT_LIMITS (tests)
178
+ * @returns {{components: object[], dependencies: object[], warnings: string[]}}
179
+ */
180
+ export function collectRootfsCertificates(rootDir, limits = {}) {
181
+ const bounds = { ...ROOTFS_CERT_LIMITS, ...limits };
182
+ const warnings = [];
183
+ let realRoot;
184
+ try {
185
+ realRoot = realpathSync(rootDir);
186
+ } catch {
187
+ return { components: [], dependencies: [], warnings };
188
+ }
189
+ const collector = new CertificateCollector();
190
+ const records = [];
191
+ const components = [];
192
+ const seenFiles = new Set();
193
+ let filesRead = 0;
194
+
195
+ // Resolve a root-relative directory to an absolute path that is proven to
196
+ // stay inside the root; undefined when it does not exist, is not a
197
+ // directory, or any symlink on the way leaves the root.
198
+ const resolveInside = (rel) => {
199
+ const absolute = join(realRoot, rel);
200
+ let real;
201
+ try {
202
+ real = realpathSync(absolute);
203
+ } catch {
204
+ return undefined;
205
+ }
206
+ const inside = relative(realRoot, real);
207
+ if (inside.startsWith("..") || inside.split(sep).includes("..")) {
208
+ return undefined;
209
+ }
210
+ return real;
211
+ };
212
+
213
+ const walk = (relBase, visit) => {
214
+ const start = resolveInside(relBase);
215
+ if (!start) {
216
+ return;
217
+ }
218
+ let startStat;
219
+ try {
220
+ startStat = lstatSync(start);
221
+ } catch {
222
+ return;
223
+ }
224
+ if (!startStat.isDirectory()) {
225
+ return;
226
+ }
227
+ let entries = 0;
228
+ const stack = [{ abs: start, rel: relBase, depth: 0 }];
229
+ while (stack.length) {
230
+ const { abs, rel, depth } = stack.pop();
231
+ let dirents;
232
+ try {
233
+ dirents = readdirSync(abs, { withFileTypes: true });
234
+ } catch {
235
+ continue;
236
+ }
237
+ dirents.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
238
+ for (const dirent of dirents) {
239
+ entries += 1;
240
+ if (entries > bounds.maxEntries) {
241
+ warnings.push(
242
+ `entry limit (${bounds.maxEntries}) reached in /${relBase}`,
243
+ );
244
+ return;
245
+ }
246
+ // Symlinks and special files are skipped outright.
247
+ if (dirent.isSymbolicLink()) {
248
+ continue;
249
+ }
250
+ const childRel = `${rel}/${dirent.name}`;
251
+ if (dirent.isDirectory()) {
252
+ if (depth + 1 >= bounds.maxDepth) {
253
+ warnings.push(
254
+ `depth limit (${bounds.maxDepth}) exceeded in /${relBase}`,
255
+ );
256
+ continue;
257
+ }
258
+ stack.push({
259
+ abs: join(abs, dirent.name),
260
+ rel: childRel,
261
+ depth: depth + 1,
262
+ });
263
+ } else if (dirent.isFile()) {
264
+ if (visit(childRel, join(abs, dirent.name)) === false) {
265
+ return;
266
+ }
267
+ }
268
+ }
269
+ }
270
+ };
271
+
272
+ const readRel = (rel, abs) => {
273
+ if (filesRead >= bounds.maxFiles) {
274
+ warnings.push(`file count limit (${bounds.maxFiles}) reached`);
275
+ return undefined;
276
+ }
277
+ filesRead += 1;
278
+ try {
279
+ const bytes = readCapped(abs, bounds.maxFileBytes);
280
+ if (!bytes) {
281
+ warnings.push(
282
+ `file over ${bounds.maxFileBytes} bytes skipped: /${rel}`,
283
+ );
284
+ }
285
+ return bytes;
286
+ } catch {
287
+ return undefined;
288
+ }
289
+ };
290
+
291
+ const consumeMaterial = (rel, abs, sourceType) => {
292
+ if (seenFiles.has(rel) || !looksLikeTrustMaterialPath(rel, sourceType)) {
293
+ return;
294
+ }
295
+ const bytes = readRel(rel, abs);
296
+ if (!bytes?.length) {
297
+ return;
298
+ }
299
+ seenFiles.add(rel);
300
+ const location = `/${rel}`;
301
+ const classification = classifyCryptoFile(bytes);
302
+ if (classification.kind === "certificates") {
303
+ const dot = basename(rel).lastIndexOf(".");
304
+ for (const der of classification.ders) {
305
+ const record = certRecordFromDer(der, { location, sourceType });
306
+ if (record) {
307
+ record.fileExtension =
308
+ dot > 0 ? basename(rel).slice(dot + 1) : undefined;
309
+ records.push(record);
310
+ }
311
+ }
312
+ } else if (classification.kind === "private-key") {
313
+ components.push(
314
+ rootfsMaterialComponent("private-key", location, {
315
+ format: classification.format,
316
+ sourceType,
317
+ }),
318
+ );
319
+ } else if (classification.kind === "keystore") {
320
+ components.push(
321
+ rootfsMaterialComponent("keystore", location, {
322
+ format: classification.format,
323
+ sourceType,
324
+ }),
325
+ );
326
+ }
327
+ };
328
+
329
+ // Most specific location first, so a file under two candidates takes the
330
+ // sourceType of the narrower one (same rule as the plugin).
331
+ const candidates = [
332
+ ...CA_STORE_DIRS.map((rel) => ({ rel, sourceType: "ca-store" })),
333
+ ...CA_BLOCKLIST_DIRS.map((rel) => ({ rel, sourceType: "ca-blocklist" })),
334
+ ...SERVER_CONFIG_DIRS.map((rel) => ({ rel, sourceType: "server-config" })),
335
+ ...JAVA_DIRS.map((rel) => ({ rel, sourceType: "java-truststore" })),
336
+ ...LANGUAGE_BUNDLE_DIRS.map((rel) => ({
337
+ rel,
338
+ sourceType: "language-bundle",
339
+ })),
340
+ ].sort((a, b) => b.rel.split("/").length - a.rel.split("/").length);
341
+ for (const file of CA_STORE_FILES) {
342
+ const abs = resolveInside(file);
343
+ if (abs && lstatSync(abs).isFile()) {
344
+ consumeMaterial(file, abs, "ca-store");
345
+ }
346
+ }
347
+ for (const { rel, sourceType } of candidates) {
348
+ walk(rel, (childRel, abs) => {
349
+ consumeMaterial(childRel, abs, sourceType);
350
+ });
351
+ }
352
+
353
+ const secretFiles = new Set();
354
+ for (const dir of SECRET_YAML_DIRS) {
355
+ walk(dir, (childRel, abs) => {
356
+ if (/\.ya?ml$/i.test(childRel) && !secretFiles.has(childRel)) {
357
+ secretFiles.add(childRel);
358
+ const bytes = readRel(childRel, abs);
359
+ const text = bytes?.toString("utf-8");
360
+ // Cheap pre-filter, as in the source scan.
361
+ if (text?.includes("kubernetes.io/tls")) {
362
+ records.push(
363
+ ...kubernetesTlsSecretRecords(text, { file: `/${childRel}` }),
364
+ );
365
+ }
366
+ }
367
+ });
368
+ }
369
+ const androidFiles = new Set();
370
+ for (const dir of ANDROID_DIRS) {
371
+ walk(dir, (childRel, abs) => {
372
+ if (
373
+ /\/res\/xml\/[^/]+\.xml$/i.test(childRel) &&
374
+ !androidFiles.has(childRel)
375
+ ) {
376
+ androidFiles.add(childRel);
377
+ const text = readRel(childRel, abs)?.toString("utf-8");
378
+ if (text?.includes("<network-security-config")) {
379
+ for (const pin of androidNetworkSecurityConfigPins(text, {
380
+ file: `/${childRel}`,
381
+ })) {
382
+ collector.addPin(pin);
383
+ }
384
+ }
385
+ }
386
+ });
387
+ }
388
+
389
+ if (records.length) {
390
+ linkFallbackIssuers(records);
391
+ collector.addRecords(records);
392
+ }
393
+ const built = collector.build();
394
+ return {
395
+ components: [...built.components, ...components],
396
+ dependencies: built.dependencies,
397
+ warnings,
398
+ };
399
+ }
400
+
401
+ // Same shape as the keystore and private-key components the other producers
402
+ // emit (managedBom sourceKeystoreComponent / sourcePrivateKeyComponent).
403
+ function rootfsMaterialComponent(kind, location, { format, sourceType }) {
404
+ return {
405
+ name: basename(location),
406
+ type: "cryptographic-asset",
407
+ "bom-ref": `crypto/related-crypto-material/${kind}/${encodeURIComponent(location)}`,
408
+ cryptoProperties: {
409
+ assetType: "related-crypto-material",
410
+ relatedCryptoMaterialProperties: {
411
+ type: kind === "keystore" ? "other" : "private-key",
412
+ ...(format ? { format } : {}),
413
+ },
414
+ },
415
+ evidence: { occurrences: [{ location }] },
416
+ properties: [
417
+ { name: "internal:SrcFile", value: location },
418
+ ...(sourceType
419
+ ? [{ name: "cdx:crypto:sourceType", value: sourceType }]
420
+ : []),
421
+ ...(kind === "keystore"
422
+ ? [{ name: "cdx:crypto:keystoreReadable", value: "false" }]
423
+ : []),
424
+ ],
425
+ };
426
+ }