@cdxgen/cdxgen 13.4.0 → 13.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/README.md +7 -7
  2. package/bin/cdxgen-secure.js +15 -0
  3. package/bin/cdxgen.js +25 -5
  4. package/bin/evinse.js +5 -0
  5. package/bin/hbom.js +15 -7
  6. package/bin/repl.js +29 -0
  7. package/data/cbomosdb-queries.json +13 -1
  8. package/data/component-tags.json +8 -0
  9. package/data/queries-darwin.json +25 -1
  10. package/data/queries-win.json +19 -1
  11. package/data/queries.json +41 -0
  12. package/data/remediations.json +283 -1
  13. package/data/rules/cbom-security.yaml +235 -4
  14. package/data/rules/go-binaries.yaml +46 -0
  15. package/data/rules/golem-go.yaml +80 -0
  16. package/data/rules/hbom-compliance.yaml +40 -22
  17. package/data/rules/hbom-performance.yaml +7 -1
  18. package/data/rules/host-topology.yaml +1 -1
  19. package/data/rules/obom-runtime.yaml +31 -2
  20. package/data/rules/php-runtime.yaml +143 -0
  21. package/lib/audit/index.js +14 -0
  22. package/lib/cli/bomAssembly.js +28 -3
  23. package/lib/cli/cliOptions.js +110 -3
  24. package/lib/cli/index.js +73 -4
  25. package/lib/cli/jvmBom.js +126 -20
  26. package/lib/cli/managedBom.js +711 -71
  27. package/lib/cli/nativeBom.js +225 -28
  28. package/lib/core/env.js +30 -8
  29. package/lib/core/fs.js +28 -19
  30. package/lib/core/httpClient.js +74 -45
  31. package/lib/core/projectConfig.js +1 -0
  32. package/lib/core/projectTooling.js +97 -0
  33. package/lib/core/propertySanitizer.js +72 -0
  34. package/lib/ecosystems/golem.js +550 -6
  35. package/lib/ecosystems/gradleutils.js +30 -20
  36. package/lib/ecosystems/parsers-dotnet.js +117 -18
  37. package/lib/ecosystems/parsers-go.js +1090 -367
  38. package/lib/ecosystems/parsers-jvm.js +259 -17
  39. package/lib/ecosystems/parsers-misc.js +229 -44
  40. package/lib/ecosystems/parsers-python.js +11 -10
  41. package/lib/ecosystems/rubyutils.js +6 -3
  42. package/lib/ecosystems/rusi.js +10 -0
  43. package/lib/ecosystems/scalacliutils.js +2 -1
  44. package/lib/ecosystems/scalasem.js +3 -1
  45. package/lib/evinser/evinser.js +126 -18
  46. package/lib/evinser/swiftsem.js +68 -5
  47. package/lib/inventory/agentSkillSpec.js +17 -0
  48. package/lib/inventory/apkInstalledDb.js +243 -0
  49. package/lib/inventory/atomUtils.js +33 -0
  50. package/lib/inventory/auditCategories.js +3 -3
  51. package/lib/inventory/cacheDir.js +52 -9
  52. package/lib/inventory/certificates.js +2371 -0
  53. package/lib/inventory/communityAiConfigParser.js +4 -9
  54. package/lib/inventory/deps.js +6 -4
  55. package/lib/inventory/display.js +200 -3
  56. package/lib/inventory/dosai.js +500 -58
  57. package/lib/inventory/dynamic.js +45 -3
  58. package/lib/inventory/envcontext.js +168 -5
  59. package/lib/inventory/evidenceUtils.js +436 -23
  60. package/lib/inventory/formulationParsers.js +3 -2
  61. package/lib/inventory/goBuildInfo.js +140 -0
  62. package/lib/inventory/hbom.js +67 -7
  63. package/lib/inventory/hbomAnalysis.js +16 -0
  64. package/lib/inventory/hostTopology.js +21 -0
  65. package/lib/inventory/osPackagePurl.js +244 -0
  66. package/lib/inventory/osPackageResolver.js +89 -87
  67. package/lib/inventory/osinfo.js +79 -31
  68. package/lib/inventory/osqueryTransform.js +114 -5
  69. package/lib/inventory/phpBuildInfo.js +487 -0
  70. package/lib/inventory/phpHostProbe.js +641 -0
  71. package/lib/inventory/rootfsCerts.js +426 -0
  72. package/lib/inventory/source.js +207 -3
  73. package/lib/inventory/spdx.js +144 -0
  74. package/lib/inventory/traceRunner.js +5 -1
  75. package/lib/inventory/tracedCrypto.js +129 -0
  76. package/lib/managers/binary.js +529 -183
  77. package/lib/managers/docker.js +166 -12
  78. package/lib/parsers/email.js +277 -0
  79. package/lib/parsers/largeJson.js +34 -2
  80. package/lib/server/server.js +186 -1
  81. package/lib/stages/postgen/annotator.js +17 -1
  82. package/lib/stages/postgen/introspection/reflect.js +4 -1
  83. package/lib/stages/postgen/postgen.js +5 -5
  84. package/lib/stages/postgen/ruleEngine.js +58 -1
  85. package/lib/stages/postgen/specVersionCompat.js +30 -0
  86. package/lib/stages/pregen/pregen.js +117 -5
  87. package/lib/validator/bomValidator.js +26 -5
  88. package/lib/validator/generated/runtime.mjs +13 -0
  89. package/lib/validator/generated/validate-1.6.mjs +26 -7
  90. package/lib/validator/generated/validate-1.7.mjs +31 -12
  91. package/lib/validator/generated/validate-2.0.mjs +30 -11
  92. package/package.json +17 -17
  93. package/types/bin/cdxgen-secure.d.ts +3 -0
  94. package/types/bin/cdxgen-secure.d.ts.map +1 -0
  95. package/types/bin/repl.d.ts.map +1 -1
  96. package/types/lib/audit/index.d.ts.map +1 -1
  97. package/types/lib/cli/bomAssembly.d.ts +9 -2
  98. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  99. package/types/lib/cli/cliOptions.d.ts +43 -0
  100. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  101. package/types/lib/cli/index.d.ts.map +1 -1
  102. package/types/lib/cli/jvmBom.d.ts +20 -0
  103. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  104. package/types/lib/cli/managedBom.d.ts.map +1 -1
  105. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  106. package/types/lib/core/env.d.ts +18 -2
  107. package/types/lib/core/env.d.ts.map +1 -1
  108. package/types/lib/core/fs.d.ts.map +1 -1
  109. package/types/lib/core/httpClient.d.ts.map +1 -1
  110. package/types/lib/core/projectConfig.d.ts.map +1 -1
  111. package/types/lib/core/projectTooling.d.ts +41 -0
  112. package/types/lib/core/projectTooling.d.ts.map +1 -0
  113. package/types/lib/core/propertySanitizer.d.ts +26 -0
  114. package/types/lib/core/propertySanitizer.d.ts.map +1 -1
  115. package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
  116. package/types/lib/ecosystems/golem.d.ts +81 -1
  117. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  118. package/types/lib/ecosystems/gradleutils.d.ts +10 -8
  119. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  120. package/types/lib/ecosystems/parsers-dotnet.d.ts +40 -4
  121. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  122. package/types/lib/ecosystems/parsers-go.d.ts +85 -13
  123. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  124. package/types/lib/ecosystems/parsers-jvm.d.ts +28 -2
  125. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  126. package/types/lib/ecosystems/parsers-misc.d.ts +18 -58
  127. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  128. package/types/lib/ecosystems/parsers-python.d.ts +4 -3
  129. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  130. package/types/lib/ecosystems/pythonutils.d.ts +1 -1
  131. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  132. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  133. package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
  134. package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
  135. package/types/lib/evinser/evinser.d.ts +25 -0
  136. package/types/lib/evinser/evinser.d.ts.map +1 -1
  137. package/types/lib/evinser/swiftsem.d.ts +10 -0
  138. package/types/lib/evinser/swiftsem.d.ts.map +1 -1
  139. package/types/lib/inventory/agentSkillSpec.d.ts +13 -0
  140. package/types/lib/inventory/agentSkillSpec.d.ts.map +1 -0
  141. package/types/lib/inventory/apkInstalledDb.d.ts +42 -0
  142. package/types/lib/inventory/apkInstalledDb.d.ts.map +1 -0
  143. package/types/lib/inventory/atomUtils.d.ts +14 -0
  144. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  145. package/types/lib/inventory/cacheDir.d.ts +12 -4
  146. package/types/lib/inventory/cacheDir.d.ts.map +1 -1
  147. package/types/lib/inventory/certificates.d.ts +205 -0
  148. package/types/lib/inventory/certificates.d.ts.map +1 -0
  149. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  150. package/types/lib/inventory/deps.d.ts +2 -1
  151. package/types/lib/inventory/deps.d.ts.map +1 -1
  152. package/types/lib/inventory/display.d.ts +18 -0
  153. package/types/lib/inventory/display.d.ts.map +1 -1
  154. package/types/lib/inventory/dosai.d.ts +145 -6
  155. package/types/lib/inventory/dosai.d.ts.map +1 -1
  156. package/types/lib/inventory/dynamic.d.ts +6 -1
  157. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  158. package/types/lib/inventory/envcontext.d.ts +40 -2
  159. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  160. package/types/lib/inventory/evidenceUtils.d.ts +29 -10
  161. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  162. package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
  163. package/types/lib/inventory/goBuildInfo.d.ts +10 -0
  164. package/types/lib/inventory/goBuildInfo.d.ts.map +1 -0
  165. package/types/lib/inventory/hbom.d.ts.map +1 -1
  166. package/types/lib/inventory/hbomAnalysis.d.ts +10 -0
  167. package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -1
  168. package/types/lib/inventory/hostTopology.d.ts.map +1 -1
  169. package/types/lib/inventory/osPackagePurl.d.ts +99 -0
  170. package/types/lib/inventory/osPackagePurl.d.ts.map +1 -0
  171. package/types/lib/inventory/osPackageResolver.d.ts +6 -2
  172. package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
  173. package/types/lib/inventory/osinfo.d.ts +31 -6
  174. package/types/lib/inventory/osinfo.d.ts.map +1 -1
  175. package/types/lib/inventory/osqueryTransform.d.ts +51 -0
  176. package/types/lib/inventory/osqueryTransform.d.ts.map +1 -1
  177. package/types/lib/inventory/phpBuildInfo.d.ts +142 -0
  178. package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -0
  179. package/types/lib/inventory/phpHostProbe.d.ts +35 -0
  180. package/types/lib/inventory/phpHostProbe.d.ts.map +1 -0
  181. package/types/lib/inventory/rootfsCerts.d.ts +27 -0
  182. package/types/lib/inventory/rootfsCerts.d.ts.map +1 -0
  183. package/types/lib/inventory/source.d.ts +28 -0
  184. package/types/lib/inventory/source.d.ts.map +1 -1
  185. package/types/lib/inventory/spdx.d.ts +15 -0
  186. package/types/lib/inventory/spdx.d.ts.map +1 -1
  187. package/types/lib/inventory/traceRunner.d.ts +4 -1
  188. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  189. package/types/lib/inventory/tracedCrypto.d.ts +22 -0
  190. package/types/lib/inventory/tracedCrypto.d.ts.map +1 -1
  191. package/types/lib/managers/binary.d.ts +10 -0
  192. package/types/lib/managers/binary.d.ts.map +1 -1
  193. package/types/lib/managers/docker.d.ts +12 -0
  194. package/types/lib/managers/docker.d.ts.map +1 -1
  195. package/types/lib/parsers/email.d.ts +19 -0
  196. package/types/lib/parsers/email.d.ts.map +1 -0
  197. package/types/lib/parsers/largeJson.d.ts +13 -0
  198. package/types/lib/parsers/largeJson.d.ts.map +1 -1
  199. package/types/lib/server/server.d.ts +8 -0
  200. package/types/lib/server/server.d.ts.map +1 -1
  201. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  202. package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -1
  203. package/types/lib/stages/postgen/ruleEngine.d.ts +7 -4
  204. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  205. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  206. package/types/lib/stages/pregen/pregen.d.ts +14 -1
  207. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  208. package/types/lib/validator/bomValidator.d.ts.map +1 -1
@@ -1,13 +1,13 @@
1
1
  import { readFileSync, realpathSync } from "node:fs";
2
2
  import { join, relative } from "node:path";
3
3
 
4
- import { build } from "@cdxgen/cdx-purl";
5
4
  import { globSync } from "glob";
6
5
 
7
6
  import { DEBUG_MODE, recordSymlinkResolution } from "../core/activity.js";
8
7
  import { safeExistsSync, safeSpawnSync } from "../core/fs.js";
9
8
  import { thoughtLog } from "../core/logger.js";
10
9
  import { getDistroInfo } from "./osinfo.js";
10
+ import { buildOsPackagePurl } from "./osPackagePurl.js";
11
11
 
12
12
  // ---------------------------------------------------------------------------
13
13
  // Caches
@@ -48,58 +48,34 @@ function parseAlpinePackage(pkgStr) {
48
48
  // ---------------------------------------------------------------------------
49
49
  // Build a PackageURL string from resolved package info.
50
50
  //
51
- // Distro qualifiers (distro, distro_name) are taken from /etc/os-release via
52
- // getDistroInfo() so they are always accurate, never hardcoded.
51
+ // deb, rpm, apk and Homebrew packages are named by buildOsPackagePurl, the
52
+ // function the OBOM rows use as well, so a file and the package that owns it
53
+ // share one identity with the package an OBOM or a container scan lists. The
54
+ // distro qualifiers come from /etc/os-release via getDistroInfo(), never from
55
+ // a hard-coded table.
53
56
  //
54
- // "brew" is not an official PackageURL type — Homebrew packages are emitted
55
- // as pkg:generic with a package_manager=homebrew qualifier.
57
+ // Homebrew has no official purl type yet, but cdx-purl builds pkg:brew, as the
58
+ // OBOM does for its homebrew_packages rows. A brew purl carries no qualifiers:
59
+ // this used to pass an `arch` that the generic type it fell back to does not
60
+ // allow, so build() threw and a file of a Homebrew package got no purl at all.
56
61
  // ---------------------------------------------------------------------------
57
62
  function buildPurl(pkgInfo) {
58
- let purlType = pkgInfo.type;
59
- let namespace;
60
- const qualifiers = {};
61
-
62
- if (pkgInfo.arch) {
63
- qualifiers.arch = pkgInfo.arch;
64
- }
65
-
66
- if (purlType === "deb" || purlType === "apk" || purlType === "rpm") {
67
- const di = getDistroInfo();
68
- namespace = di.namespace;
69
-
70
- // distro qualifier: ID-VERSION_ID (e.g. "fedora-25", "alpine-3.17")
71
- if (di.distroId) {
72
- qualifiers.distro = di.distroId;
73
- }
74
- // distro_name qualifier: codename (e.g. "jammy", "bookworm")
75
- if (di.distroName) {
76
- qualifiers.distro_name = di.distroName;
77
- }
78
- } else if (purlType === "brew") {
79
- // No official brew purl type — fall back to generic. This used to add a
80
- // `package_manager=homebrew` qualifier, which generic does not allow, so
81
- // build() threw and the catch below swallowed it: Homebrew packages silently
82
- // received no purl at all on macOS. The provenance is instead recorded as a
83
- // property by the caller.
84
- purlType = "generic";
85
- }
63
+ const distro =
64
+ pkgInfo.type === "brew" || !pkgInfo.type ? undefined : getDistroInfo();
65
+ return buildOsPackagePurl(pkgInfo, distro) || "";
66
+ }
86
67
 
87
- const finalQualifiers = Object.keys(qualifiers).length
88
- ? qualifiers
89
- : undefined;
68
+ // apk names an architecture its own way, which is not the way node does.
69
+ const APK_ARCHITECTURES = {
70
+ x64: "x86_64",
71
+ arm64: "aarch64",
72
+ ia32: "x86",
73
+ ppc64: "ppc64le",
74
+ loong64: "loongarch64",
75
+ };
90
76
 
91
- try {
92
- return build({
93
- type: purlType,
94
- namespace: namespace || undefined || null,
95
- name: pkgInfo.name,
96
- version: pkgInfo.version || undefined || null,
97
- qualifiers: finalQualifiers || null,
98
- subpath: undefined || null,
99
- });
100
- } catch (_e) {
101
- return "";
102
- }
77
+ function apkArchitecture() {
78
+ return APK_ARCHITECTURES[process.arch] || process.arch;
103
79
  }
104
80
 
105
81
  // ---------------------------------------------------------------------------
@@ -111,7 +87,7 @@ function buildPurl(pkgInfo) {
111
87
  * correctly computed purl with distro qualifiers derived from /etc/os-release.
112
88
  *
113
89
  * @param {string} filePath - Absolute path to the library file
114
- * @returns {{ name: string, version: string, arch: string, type: string, purl: string } | undefined}
90
+ * @returns {{ name: string, version: string, arch: string, type: string, epoch?: string, purl: string } | undefined}
115
91
  */
116
92
  export function resolvePackageForFile(filePath) {
117
93
  if (!filePath) {
@@ -190,27 +166,30 @@ function _resolveLinux(filePath) {
190
166
  return {
191
167
  name,
192
168
  version,
193
- arch: process.arch === "x64" ? "x86_64" : process.arch,
169
+ arch: apkArchitecture(),
194
170
  type: "apk",
195
171
  };
196
172
  }
197
173
  }
198
174
 
199
- // 3. RPM-based (rpm -qf)
175
+ // 3. RPM-based (rpm -qf). The epoch is part of an rpm's identity; %{EPOCH} is
176
+ // (none) for a package without one, so the conditional makes that a 0, which
177
+ // means the same.
200
178
  const rpmRes = safeSpawnSync("rpm", [
201
179
  "-qf",
202
180
  "--qf",
203
- "%{NAME} %{VERSION}-%{RELEASE} %{ARCH}\n",
181
+ "%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION}-%{RELEASE} %{ARCH}\n",
204
182
  filePath,
205
183
  ]);
206
184
  if (rpmRes && rpmRes.status === 0 && rpmRes.stdout) {
207
185
  const line = rpmRes.stdout.split("\n")[0].trim();
208
186
  const parts = line.split(" ");
209
- if (parts.length >= 3) {
187
+ if (parts.length >= 4) {
210
188
  return {
211
189
  name: parts[0],
212
- version: parts[1],
213
- arch: parts[2],
190
+ epoch: parts[1],
191
+ version: parts[2],
192
+ arch: parts[3],
214
193
  type: "rpm",
215
194
  };
216
195
  }
@@ -233,7 +212,7 @@ function _resolveDarwin(filePath) {
233
212
  name: segments[0],
234
213
  version: segments[1],
235
214
  arch: process.arch,
236
- type: "brew", // remapped to pkg:generic in buildPurl
215
+ type: "brew",
237
216
  };
238
217
  }
239
218
  }
@@ -379,6 +358,9 @@ function shouldPreferUsrMergedExecutablePath(file, existingFile) {
379
358
  * @param ldConf {String} Config file used by ldconfig to locate additional paths
380
359
  * @param ldConfDirPattern {String} Config directory that can contain more .conf files for ldconfig
381
360
  * @param excludePaths {Array[String]} Container-relative paths that should be excluded from the result set
361
+ * @param extraLibGlobs {Array[String]} Extra container-relative globs for files
362
+ * the default paths miss, such as libphp*.so under Apache's modules
363
+ * directory. The existing globs are unchanged; this adds candidates only.
382
364
  *
383
365
  * @return {Array[String]} List of executables
384
366
  */
@@ -388,6 +370,7 @@ export function collectSharedLibs(
388
370
  ldConf,
389
371
  ldConfDirPattern,
390
372
  excludePaths = [],
373
+ extraLibGlobs = [],
391
374
  ) {
392
375
  if (!libPaths) {
393
376
  return [];
@@ -399,6 +382,37 @@ export function collectSharedLibs(
399
382
  const ignoreList = [
400
383
  "**/*.{h,c,cpp,hpp,man,txt,md,htm,html,jar,ear,war,zip,tar,egg,keepme,gitignore,json,js,py,pyc}",
401
384
  ];
385
+ const addCandidate = (file) => {
386
+ let resolvedFile = file;
387
+ try {
388
+ resolvedFile = relative(basePath, realpathSync(join(basePath, file)));
389
+ recordSymlinkResolution(join(basePath, file), resolvedFile, {
390
+ basePath,
391
+ metadata: {
392
+ resolutionKind: "shared-library",
393
+ },
394
+ reason: `Resolved shared library candidate ${file} to ${resolvedFile}.`,
395
+ });
396
+ } catch (err) {
397
+ recordSymlinkResolution(join(basePath, file), undefined, {
398
+ basePath,
399
+ errorCode: err?.code || err?.name,
400
+ metadata: {
401
+ resolutionKind: "shared-library",
402
+ },
403
+ reason: `Failed to resolve shared library candidate ${file}.`,
404
+ status: "failed",
405
+ });
406
+ // Broken symlinks or permission errors can prevent realpath resolution.
407
+ }
408
+ if (
409
+ excludedPathSet.has(file.replace(/^\/+/, "").replace(/\\/g, "/")) ||
410
+ excludedPathSet.has(resolvedFile.replace(/^\/+/, "").replace(/\\/g, "/"))
411
+ ) {
412
+ return;
413
+ }
414
+ sharedLibs.push(file);
415
+ };
402
416
  const allLdConfDirs = ldConfDirPattern ? [ldConfDirPattern] : [];
403
417
  collectAllLdConfs(basePath, ldConf, allLdConfDirs, libPaths);
404
418
  if (allLdConfDirs.length) {
@@ -428,37 +442,25 @@ export function collectSharedLibs(
428
442
  ignore: ignoreList,
429
443
  });
430
444
  for (const file of files) {
431
- let resolvedFile = file;
432
- try {
433
- resolvedFile = relative(basePath, realpathSync(join(basePath, file)));
434
- recordSymlinkResolution(join(basePath, file), resolvedFile, {
435
- basePath,
436
- metadata: {
437
- resolutionKind: "shared-library",
438
- },
439
- reason: `Resolved shared library candidate ${file} to ${resolvedFile}.`,
440
- });
441
- } catch (err) {
442
- recordSymlinkResolution(join(basePath, file), undefined, {
443
- basePath,
444
- errorCode: err?.code || err?.name,
445
- metadata: {
446
- resolutionKind: "shared-library",
447
- },
448
- reason: `Failed to resolve shared library candidate ${file}.`,
449
- status: "failed",
450
- });
451
- // Broken symlinks or permission errors can prevent realpath resolution.
452
- }
453
- if (
454
- excludedPathSet.has(file.replace(/^\/+/, "").replace(/\\/g, "/")) ||
455
- excludedPathSet.has(
456
- resolvedFile.replace(/^\/+/, "").replace(/\\/g, "/"),
457
- )
458
- ) {
459
- continue;
460
- }
461
- sharedLibs.push(file);
445
+ addCandidate(file);
446
+ }
447
+ } catch (_err) {
448
+ // ignore
449
+ }
450
+ }
451
+ for (const pattern of extraLibGlobs) {
452
+ try {
453
+ const files = globSync(pattern, {
454
+ cwd: basePath,
455
+ absolute: false,
456
+ nocase: true,
457
+ nodir: true,
458
+ dot: true,
459
+ follow: true,
460
+ ignore: ignoreList,
461
+ });
462
+ for (const file of files) {
463
+ addCandidate(file);
462
464
  }
463
465
  } catch (_err) {
464
466
  // ignore
@@ -49,14 +49,34 @@ export const OS_DISTRO_ALIAS = {
49
49
  "ubuntu-24.10": "oracular",
50
50
  "ubuntu-25.04": "plucky",
51
51
  "ubuntu-25.10": "questing",
52
+ "ubuntu-26.04": "resolute",
53
+ "ubuntu-26.10": "stonking",
52
54
  "debian-15": "duke",
53
55
  "debian-14": "forky",
54
56
  "debian-14.5": "forky",
55
57
  "debian-13": "trixie",
58
+ "debian-13.7": "trixie",
59
+ "debian-13.6": "trixie",
56
60
  "debian-13.5": "trixie",
61
+ "debian-13.4": "trixie",
62
+ "debian-13.3": "trixie",
63
+ "debian-13.2": "trixie",
64
+ "debian-13.1": "trixie",
57
65
  "debian-12": "bookworm",
58
- "debian-12.5": "bookworm",
66
+ "debian-12.15": "bookworm",
67
+ "debian-12.14": "bookworm",
68
+ "debian-12.13": "bookworm",
69
+ "debian-12.12": "bookworm",
70
+ "debian-12.11": "bookworm",
71
+ "debian-12.10": "bookworm",
72
+ "debian-12.9": "bookworm",
73
+ "debian-12.8": "bookworm",
74
+ "debian-12.7": "bookworm",
59
75
  "debian-12.6": "bookworm",
76
+ "debian-12.5": "bookworm",
77
+ "debian-12.4": "bookworm",
78
+ "debian-12.2": "bookworm",
79
+ "debian-12.1": "bookworm",
60
80
  "debian-11": "bullseye",
61
81
  "debian-11.5": "bullseye",
62
82
  "debian-10": "buster",
@@ -256,15 +276,20 @@ export function _resetOsReleaseCache() {
256
276
  * Derive structured distro information from an os-release file.
257
277
  *
258
278
  * Returns an object with:
259
- * - purlType {string} "deb" | "apk" | "rpm"
279
+ * - purlType {string} "deb" | "apk" | "rpm" | "alpm" (Arch and the
280
+ * distros built on it)
260
281
  * - namespace {string} canonical purl namespace (e.g. "ubuntu", "alpine",
261
282
  * "azure-linux" for both Azure Linux and CBL-Mariner)
262
283
  * - distroId {string} ID + "-" + VERSION_ID with a canonical vendor
263
- * prefix (e.g. "ubuntu-22.04", "azure-linux-3.0")
264
- * - distroName {string} codename/alias (e.g. "jammy")
284
+ * prefix (e.g. "ubuntu-22.04", "azure-linux-3.0",
285
+ * "alpine-3.24.3")
286
+ * - distroName {string} release codename, or a token OS_DISTRO_ALIAS knows
287
+ * (e.g. "jammy", "rhel", "alpine-3.24"); empty when
288
+ * the distro has neither
265
289
  *
266
- * Mirrors the logic in lib/managers/binary.js getOSPackages() so that both
267
- * callers share a single implementation.
290
+ * lib/managers/binary.js getOSPackages() reads its distro from here, so a
291
+ * container scan and an OBOM run on the same system give a package the same
292
+ * `distro` and `distro_name` qualifiers.
268
293
  *
269
294
  * @param {string} [root="/"] - Filesystem root to look for os-release.
270
295
  * @returns {{ purlType: string, namespace: string, distroId: string, distroName: string }}
@@ -290,6 +315,8 @@ export function getDistroInfo(root = "/") {
290
315
  case "alpine":
291
316
  case "alpaquita":
292
317
  case "openwrt":
318
+ case "wolfi":
319
+ case "chainguard":
293
320
  purlType = "apk";
294
321
  break;
295
322
  case "sles":
@@ -312,11 +339,23 @@ export function getDistroInfo(root = "/") {
312
339
  case "photon":
313
340
  purlType = "rpm";
314
341
  break;
342
+ case "arch":
343
+ case "archarm":
344
+ case "manjaro":
345
+ case "endeavouros":
346
+ case "cachyos":
347
+ case "steamos":
348
+ case "artix":
349
+ purlType = "alpm";
350
+ break;
315
351
  default:
316
352
  if (idLike.includes("debian") || idLike.includes("ubuntu")) {
317
353
  purlType = "deb";
318
354
  } else if (idLike.includes("alpine")) {
319
355
  purlType = "apk";
356
+ } else if (idLike.split(/\s+/u).includes("arch")) {
357
+ // A token, not a substring: ID_LIKE is a space-separated list.
358
+ purlType = "alpm";
320
359
  } else if (
321
360
  idLike.includes("rhel") ||
322
361
  idLike.includes("centos") ||
@@ -343,41 +382,50 @@ export function getDistroInfo(root = "/") {
343
382
  namespace = "debian";
344
383
  } else if (idLike.includes("ubuntu")) {
345
384
  namespace = "ubuntu";
385
+ } else if (purlType === "alpm") {
386
+ namespace = "arch";
346
387
  } else {
347
388
  namespace = "linux";
348
389
  }
349
390
  }
350
391
 
351
- // Build distroId = ID-VERSION_ID (e.g. "fedora-25", "alpine-3.17"),
352
- // canonicalising the vendor prefix so one release has a single qualifier
353
- // value (e.g. "mariner-2.0" and "azurelinux-3.0" -> "azure-linux-2.0"/
354
- // "azure-linux-3.0")
355
- let distroId = versionId ? `${rawId}-${versionId}` : rawId;
356
- distroId = canonicalDistroQualifier(distroId);
392
+ // Build distroId = ID-VERSION_ID as os-release writes it (e.g. "fedora-25",
393
+ // "alpine-3.24.3"), canonicalising the vendor prefix so one release has a
394
+ // single qualifier value (e.g. "mariner-2.0" and "azurelinux-3.0" ->
395
+ // "azure-linux-2.0"/"azure-linux-3.0")
396
+ const releaseId = versionId ? `${rawId}-${versionId}` : rawId;
397
+ const distroId = canonicalDistroQualifier(releaseId);
357
398
 
358
- // Special-case Alpine: truncate to major.minor
359
- if (purlType === "apk" && versionId) {
399
+ // distro_name is a release codename or a token OS_DISTRO_ALIAS knows
400
+ // ("bookworm", "jammy", "rhel", "alpine-3.24"), never a product display
401
+ // name: "AlmaLinux" or "Rocky Linux 9" has no place in a purl, since RPM
402
+ // distros track packages flat and a display name there breaks vulnerability
403
+ // matching. A product name counts only when it resolves through the alias
404
+ // table, which holds the RHEL names.
405
+ let distroName = (info.VERSION_CODENAME || "").toLowerCase();
406
+ if (!distroName) {
407
+ const productName = (
408
+ info.CENTOS_MANTISBT_PROJECT ||
409
+ info.REDHAT_BUGZILLA_PRODUCT ||
410
+ info.REDHAT_SUPPORT_PRODUCT ||
411
+ ""
412
+ ).toLowerCase();
413
+ if (productName && Object.hasOwn(OS_DISTRO_ALIAS, productName)) {
414
+ distroName = OS_DISTRO_ALIAS[productName];
415
+ }
416
+ }
417
+ // Alpine names its release series: 3.24.3 belongs to alpine-3.24, the unit
418
+ // its security data is kept in.
419
+ if (rawId === "alpine" && versionId) {
360
420
  const parts = versionId.split(".");
361
421
  if (parts.length >= 2) {
362
- distroId = `${rawId}-${parts[0]}.${parts[1]}`;
422
+ distroName = `alpine-${parts[0]}.${parts[1]}`;
363
423
  }
364
424
  }
365
-
366
- // Determine codename / alias
367
- let distroName =
368
- info.VERSION_CODENAME ||
369
- info.CENTOS_MANTISBT_PROJECT ||
370
- info.REDHAT_BUGZILLA_PRODUCT ||
371
- info.REDHAT_SUPPORT_PRODUCT ||
372
- "";
373
- distroName = distroName.toLowerCase();
374
-
375
- // Resolve well-known display name aliases
376
- if (distroName.includes(" ") && OS_DISTRO_ALIAS[distroName]) {
377
- distroName = OS_DISTRO_ALIAS[distroName];
378
- }
379
- if (!distroName && OS_DISTRO_ALIAS[distroId]) {
380
- distroName = OS_DISTRO_ALIAS[distroId];
425
+ // The alias of the release itself (ubuntu-24.04 -> noble) wins over the
426
+ // codename the file carries.
427
+ if (versionId && Object.hasOwn(OS_DISTRO_ALIAS, releaseId)) {
428
+ distroName = OS_DISTRO_ALIAS[releaseId];
381
429
  }
382
430
 
383
431
  return { purlType, namespace, distroId, distroName };
@@ -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":