@cyclonedx/cdxgen 12.3.2 → 12.4.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 (182) hide show
  1. package/README.md +70 -22
  2. package/bin/audit.js +21 -7
  3. package/bin/cdxgen.js +238 -116
  4. package/bin/convert.js +28 -13
  5. package/bin/hbom.js +490 -0
  6. package/bin/repl.js +580 -29
  7. package/bin/validate.js +34 -4
  8. package/bin/verify.js +40 -5
  9. package/data/README.md +298 -25
  10. package/data/component-tags.json +6 -0
  11. package/data/crypto-oid.json +16 -0
  12. package/data/predictive-audit-allowlist.json +11 -0
  13. package/data/queries-darwin.json +12 -1
  14. package/data/queries-win.json +7 -1
  15. package/data/queries.json +39 -2
  16. package/data/rules/ai-agent-governance.yaml +16 -0
  17. package/data/rules/asar-archives.yaml +150 -0
  18. package/data/rules/chrome-extensions.yaml +8 -0
  19. package/data/rules/ci-permissions.yaml +171 -15
  20. package/data/rules/container-risk.yaml +14 -7
  21. package/data/rules/dependency-sources.yaml +76 -5
  22. package/data/rules/hbom-compliance.yaml +325 -0
  23. package/data/rules/hbom-performance.yaml +307 -0
  24. package/data/rules/hbom-security.yaml +248 -0
  25. package/data/rules/host-topology.yaml +165 -0
  26. package/data/rules/mcp-servers.yaml +18 -3
  27. package/data/rules/obom-runtime.yaml +907 -22
  28. package/data/rules/package-integrity.yaml +36 -0
  29. package/data/rules/rootfs-hardening.yaml +179 -0
  30. package/data/rules/vscode-extensions.yaml +9 -0
  31. package/lib/audit/index.js +209 -8
  32. package/lib/audit/index.poku.js +332 -0
  33. package/lib/audit/reporters.js +222 -0
  34. package/lib/audit/targets.js +146 -1
  35. package/lib/audit/targets.poku.js +186 -0
  36. package/lib/cli/asar.poku.js +328 -0
  37. package/lib/cli/index.js +647 -127
  38. package/lib/cli/index.poku.js +1905 -187
  39. package/lib/evinser/evinser.js +14 -9
  40. package/lib/helpers/agentFormulationParser.js +6 -2
  41. package/lib/helpers/agentFormulationParser.poku.js +42 -0
  42. package/lib/helpers/analyzer.js +1444 -38
  43. package/lib/helpers/analyzer.poku.js +409 -0
  44. package/lib/helpers/analyzerScope.js +712 -0
  45. package/lib/helpers/asarutils.js +1556 -0
  46. package/lib/helpers/asarutils.poku.js +443 -0
  47. package/lib/helpers/auditCategories.js +12 -0
  48. package/lib/helpers/auditCategories.poku.js +32 -0
  49. package/lib/helpers/cbomutils.js +271 -1
  50. package/lib/helpers/cbomutils.poku.js +248 -5
  51. package/lib/helpers/chromextutils.js +25 -3
  52. package/lib/helpers/chromextutils.poku.js +68 -0
  53. package/lib/helpers/ciParsers/githubActions.js +79 -0
  54. package/lib/helpers/ciParsers/githubActions.poku.js +103 -0
  55. package/lib/helpers/communityAiConfigParser.js +15 -5
  56. package/lib/helpers/communityAiConfigParser.poku.js +71 -0
  57. package/lib/helpers/depsUtils.js +5 -0
  58. package/lib/helpers/depsUtils.poku.js +55 -0
  59. package/lib/helpers/display.js +336 -23
  60. package/lib/helpers/display.poku.js +179 -43
  61. package/lib/helpers/evidenceUtils.js +58 -0
  62. package/lib/helpers/evidenceUtils.poku.js +54 -0
  63. package/lib/helpers/exportUtils.js +9 -0
  64. package/lib/helpers/gtfobins.js +142 -8
  65. package/lib/helpers/gtfobins.poku.js +24 -1
  66. package/lib/helpers/hbom.js +710 -0
  67. package/lib/helpers/hbom.poku.js +496 -0
  68. package/lib/helpers/hbomAnalysis.js +268 -0
  69. package/lib/helpers/hbomAnalysis.poku.js +249 -0
  70. package/lib/helpers/hbomLoader.js +35 -0
  71. package/lib/helpers/hostTopology.js +803 -0
  72. package/lib/helpers/hostTopology.poku.js +363 -0
  73. package/lib/helpers/inventoryStats.js +69 -0
  74. package/lib/helpers/inventoryStats.poku.js +86 -0
  75. package/lib/helpers/lolbas.js +19 -1
  76. package/lib/helpers/lolbas.poku.js +23 -0
  77. package/lib/helpers/mcpConfigParser.js +21 -5
  78. package/lib/helpers/mcpConfigParser.poku.js +39 -2
  79. package/lib/helpers/osqueryTransform.js +47 -0
  80. package/lib/helpers/osqueryTransform.poku.js +47 -0
  81. package/lib/helpers/plugins.js +349 -0
  82. package/lib/helpers/plugins.poku.js +57 -0
  83. package/lib/helpers/propertySanitizer.js +121 -0
  84. package/lib/helpers/protobom.js +156 -45
  85. package/lib/helpers/protobom.poku.js +140 -5
  86. package/lib/helpers/remote/dependency-track.js +36 -3
  87. package/lib/helpers/remote/dependency-track.poku.js +44 -0
  88. package/lib/helpers/source.js +24 -0
  89. package/lib/helpers/source.poku.js +32 -0
  90. package/lib/helpers/utils.js +2454 -198
  91. package/lib/helpers/utils.poku.js +1798 -74
  92. package/lib/managers/binary.e2e.poku.js +367 -0
  93. package/lib/managers/binary.js +2306 -350
  94. package/lib/managers/binary.poku.js +1700 -1
  95. package/lib/managers/docker.js +441 -95
  96. package/lib/managers/docker.poku.js +1479 -14
  97. package/lib/server/server.js +2 -24
  98. package/lib/server/server.poku.js +36 -1
  99. package/lib/stages/postgen/annotator.js +38 -0
  100. package/lib/stages/postgen/annotator.poku.js +107 -1
  101. package/lib/stages/postgen/auditBom.js +121 -18
  102. package/lib/stages/postgen/auditBom.poku.js +2967 -990
  103. package/lib/stages/postgen/hostTopologyAudit.poku.js +186 -0
  104. package/lib/stages/postgen/postgen.js +192 -1
  105. package/lib/stages/postgen/postgen.poku.js +321 -0
  106. package/lib/stages/postgen/ruleEngine.js +116 -0
  107. package/lib/stages/pregen/envAudit.js +14 -3
  108. package/package.json +24 -21
  109. package/types/bin/hbom.d.ts +3 -0
  110. package/types/bin/hbom.d.ts.map +1 -0
  111. package/types/bin/repl.d.ts.map +1 -1
  112. package/types/lib/audit/index.d.ts +44 -0
  113. package/types/lib/audit/index.d.ts.map +1 -1
  114. package/types/lib/audit/reporters.d.ts +16 -0
  115. package/types/lib/audit/reporters.d.ts.map +1 -1
  116. package/types/lib/audit/targets.d.ts.map +1 -1
  117. package/types/lib/cli/index.d.ts +16 -0
  118. package/types/lib/cli/index.d.ts.map +1 -1
  119. package/types/lib/evinser/evinser.d.ts +4 -0
  120. package/types/lib/evinser/evinser.d.ts.map +1 -1
  121. package/types/lib/helpers/agentFormulationParser.d.ts.map +1 -1
  122. package/types/lib/helpers/analyzer.d.ts +33 -0
  123. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  124. package/types/lib/helpers/analyzerScope.d.ts +11 -0
  125. package/types/lib/helpers/analyzerScope.d.ts.map +1 -0
  126. package/types/lib/helpers/asarutils.d.ts +34 -0
  127. package/types/lib/helpers/asarutils.d.ts.map +1 -0
  128. package/types/lib/helpers/auditCategories.d.ts +5 -0
  129. package/types/lib/helpers/auditCategories.d.ts.map +1 -1
  130. package/types/lib/helpers/cbomutils.d.ts +3 -2
  131. package/types/lib/helpers/cbomutils.d.ts.map +1 -1
  132. package/types/lib/helpers/chromextutils.d.ts.map +1 -1
  133. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
  134. package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -1
  135. package/types/lib/helpers/depsUtils.d.ts.map +1 -1
  136. package/types/lib/helpers/display.d.ts +1 -0
  137. package/types/lib/helpers/display.d.ts.map +1 -1
  138. package/types/lib/helpers/evidenceUtils.d.ts +8 -0
  139. package/types/lib/helpers/evidenceUtils.d.ts.map +1 -0
  140. package/types/lib/helpers/exportUtils.d.ts.map +1 -1
  141. package/types/lib/helpers/gtfobins.d.ts +8 -0
  142. package/types/lib/helpers/gtfobins.d.ts.map +1 -1
  143. package/types/lib/helpers/hbom.d.ts +49 -0
  144. package/types/lib/helpers/hbom.d.ts.map +1 -0
  145. package/types/lib/helpers/hbomAnalysis.d.ts +62 -0
  146. package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -0
  147. package/types/lib/helpers/hbomLoader.d.ts +7 -0
  148. package/types/lib/helpers/hbomLoader.d.ts.map +1 -0
  149. package/types/lib/helpers/hostTopology.d.ts +12 -0
  150. package/types/lib/helpers/hostTopology.d.ts.map +1 -0
  151. package/types/lib/helpers/inventoryStats.d.ts +11 -0
  152. package/types/lib/helpers/inventoryStats.d.ts.map +1 -0
  153. package/types/lib/helpers/lolbas.d.ts.map +1 -1
  154. package/types/lib/helpers/mcpConfigParser.d.ts +1 -1
  155. package/types/lib/helpers/mcpConfigParser.d.ts.map +1 -1
  156. package/types/lib/helpers/osqueryTransform.d.ts +3 -0
  157. package/types/lib/helpers/osqueryTransform.d.ts.map +1 -1
  158. package/types/lib/helpers/plugins.d.ts +58 -0
  159. package/types/lib/helpers/plugins.d.ts.map +1 -0
  160. package/types/lib/helpers/propertySanitizer.d.ts +3 -0
  161. package/types/lib/helpers/propertySanitizer.d.ts.map +1 -0
  162. package/types/lib/helpers/protobom.d.ts +3 -4
  163. package/types/lib/helpers/protobom.d.ts.map +1 -1
  164. package/types/lib/helpers/remote/dependency-track.d.ts +10 -3
  165. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
  166. package/types/lib/helpers/source.d.ts.map +1 -1
  167. package/types/lib/helpers/utils.d.ts +74 -8
  168. package/types/lib/helpers/utils.d.ts.map +1 -1
  169. package/types/lib/managers/binary.d.ts +5 -0
  170. package/types/lib/managers/binary.d.ts.map +1 -1
  171. package/types/lib/managers/docker.d.ts +3 -0
  172. package/types/lib/managers/docker.d.ts.map +1 -1
  173. package/types/lib/server/server.d.ts +2 -0
  174. package/types/lib/server/server.d.ts.map +1 -1
  175. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  176. package/types/lib/stages/postgen/auditBom.d.ts +26 -1
  177. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  178. package/types/lib/stages/postgen/postgen.d.ts +2 -1
  179. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  180. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  181. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  182. package/data/spdx-model-v3.0.1.jsonld +0 -15999
@@ -69,7 +69,7 @@ describe("mcpConfigParser", () => {
69
69
  args: [
70
70
  "--token",
71
71
  "sk_test_super_secret_value",
72
- "https://docs.example.com/mcp",
72
+ "https://user:pass@docs.example.com/mcp?access_token=secret#frag",
73
73
  ],
74
74
  command: "npx",
75
75
  env: {
@@ -99,7 +99,7 @@ describe("mcpConfigParser", () => {
99
99
  );
100
100
  assert.strictEqual(
101
101
  getProp(service, "cdx:mcp:credentialExposureFieldCount"),
102
- "3",
102
+ "4",
103
103
  );
104
104
  assert.strictEqual(
105
105
  getProp(service, "cdx:mcp:credentialReferenceCount"),
@@ -109,6 +109,12 @@ describe("mcpConfigParser", () => {
109
109
  getProp(component, "cdx:mcp:credentialExposedServiceCount"),
110
110
  "1",
111
111
  );
112
+ assert.strictEqual(getProp(service, "cdx:mcp:command"), "npx");
113
+ assert.deepStrictEqual(service.endpoints, ["https://docs.example.com/mcp"]);
114
+ assert.strictEqual(
115
+ getProp(component, "cdx:mcp:configuredEndpoints"),
116
+ "https://docs.example.com/mcp",
117
+ );
112
118
  assert.strictEqual(
113
119
  getProp(service, "cdx:mcp:credentialRiskIndicators"),
114
120
  undefined,
@@ -123,4 +129,35 @@ describe("mcpConfigParser", () => {
123
129
  undefined,
124
130
  );
125
131
  });
132
+
133
+ it("summarizes Windows executable paths with spaces safely", async () => {
134
+ const readFileSync = sinon.stub();
135
+ const scanTextForHiddenUnicode = sinon.stub().returns({
136
+ hasHiddenUnicode: false,
137
+ });
138
+ readFileSync.withArgs("/repo/.vscode/mcp.json", "utf-8").returns(
139
+ JSON.stringify({
140
+ mcpServers: {
141
+ releaseDocs: {
142
+ args: ["--inspect"],
143
+ command: "C:\\Program Files\\nodejs\\node.exe --inspect",
144
+ mcp: true,
145
+ transport: "stdio",
146
+ },
147
+ },
148
+ }),
149
+ );
150
+ const { mcpConfigParser } = await esmock("./mcpConfigParser.js", {
151
+ "node:fs": { readFileSync },
152
+ "./unicodeScan.js": { scanTextForHiddenUnicode },
153
+ });
154
+
155
+ const result = mcpConfigParser.parse(["/repo/.vscode/mcp.json"]);
156
+
157
+ assert.strictEqual(
158
+ getProp(result.services[0], "cdx:mcp:command") ||
159
+ getProp(result.components[0], "cdx:mcp:command"),
160
+ "node.exe",
161
+ );
162
+ });
126
163
  });
@@ -65,6 +65,53 @@ export function sanitizeOsQueryIdentity(value) {
65
65
  .replace(/[}]$/g, "");
66
66
  }
67
67
 
68
+ export function sanitizeOsQueryBomRefValue(value, fallback = "unknown") {
69
+ const normalizedValue = String(value || "")
70
+ .replace(/[\r\n\t]+/g, " ")
71
+ .replace(/\s+/g, " ")
72
+ .trim();
73
+ if (!normalizedValue || normalizedValue === "null") {
74
+ return fallback;
75
+ }
76
+ return normalizedValue.replace(/[:@#\[\]=]/g, "-");
77
+ }
78
+
79
+ export function createOsQueryFallbackBomRef(
80
+ queryCategory,
81
+ componentType,
82
+ name,
83
+ version,
84
+ identityField,
85
+ identityValue,
86
+ ) {
87
+ const categoryRef = sanitizeOsQueryBomRefValue(queryCategory, "component");
88
+ const componentTypeRef = sanitizeOsQueryBomRefValue(
89
+ componentType,
90
+ "component",
91
+ );
92
+ const nameRef = sanitizeOsQueryBomRefValue(
93
+ name || queryCategory,
94
+ "component",
95
+ );
96
+ const versionRef = sanitizeOsQueryBomRefValue(version, "unknown");
97
+ const baseBomRef = `osquery:${categoryRef}:${componentTypeRef}:${nameRef}@${versionRef}`;
98
+ if (!identityField || !identityValue) {
99
+ return baseBomRef;
100
+ }
101
+ const identityFieldRef = sanitizeOsQueryBomRefValue(
102
+ identityField,
103
+ "identity",
104
+ );
105
+ const identityValueRef = sanitizeOsQueryBomRefValue(identityValue, "unknown");
106
+ return `${baseBomRef}[${identityFieldRef}=${identityValueRef}]`;
107
+ }
108
+
109
+ export function shouldCreateOsQueryPurl(componentType) {
110
+ return !["cryptographic-asset", "data", "device", "information"].includes(
111
+ componentType || "",
112
+ );
113
+ }
114
+
68
115
  export function createOsQueryPurl(
69
116
  purlType,
70
117
  group,
@@ -1,12 +1,15 @@
1
1
  import { assert, describe, it } from "poku";
2
2
 
3
3
  import {
4
+ createOsQueryFallbackBomRef,
4
5
  createOsQueryPurl,
5
6
  deriveOsQueryDescription,
6
7
  deriveOsQueryName,
7
8
  deriveOsQueryPublisher,
8
9
  deriveOsQueryVersion,
10
+ sanitizeOsQueryBomRefValue,
9
11
  sanitizeOsQueryIdentity,
12
+ shouldCreateOsQueryPurl,
10
13
  } from "./osqueryTransform.js";
11
14
 
12
15
  describe("osqueryTransform helpers", () => {
@@ -46,4 +49,48 @@ describe("osqueryTransform helpers", () => {
46
49
  assert.ok(purl.startsWith("pkg:swid/microsoft/"));
47
50
  assert.ok(purl.includes("@22H2"));
48
51
  });
52
+
53
+ it("creates readable fallback bom-ref strings for non-package osquery rows", () => {
54
+ const bomRef = createOsQueryFallbackBomRef(
55
+ "authorized_keys_snapshot",
56
+ "data",
57
+ "root",
58
+ "ssh-ed25519",
59
+ "key_file",
60
+ "/root/.ssh/authorized_keys",
61
+ );
62
+ assert.strictEqual(
63
+ bomRef,
64
+ "osquery:authorized_keys_snapshot:data:root@ssh-ed25519[key_file=/root/.ssh/authorized_keys]",
65
+ );
66
+ });
67
+
68
+ it("omits the bracketed suffix when no extra identity field exists", () => {
69
+ const bomRef = createOsQueryFallbackBomRef(
70
+ "windows_run_keys",
71
+ "data",
72
+ "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\Updater",
73
+ undefined,
74
+ );
75
+ assert.strictEqual(
76
+ bomRef,
77
+ "osquery:windows_run_keys:data:HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\Updater@unknown",
78
+ );
79
+ });
80
+
81
+ it("sanitizes fallback bom-ref fragments without percent-encoding them", () => {
82
+ assert.strictEqual(
83
+ sanitizeOsQueryBomRefValue(" launchd: updater@[user]=#1\n"),
84
+ "launchd- updater--user---1",
85
+ );
86
+ });
87
+
88
+ it("limits purl generation to package-like osquery component types", () => {
89
+ assert.strictEqual(shouldCreateOsQueryPurl(undefined), true);
90
+ assert.strictEqual(shouldCreateOsQueryPurl("application"), true);
91
+ assert.strictEqual(shouldCreateOsQueryPurl("operating-system"), true);
92
+ assert.strictEqual(shouldCreateOsQueryPurl("data"), false);
93
+ assert.strictEqual(shouldCreateOsQueryPurl("device"), false);
94
+ assert.strictEqual(shouldCreateOsQueryPurl("cryptographic-asset"), false);
95
+ });
49
96
  });
@@ -0,0 +1,349 @@
1
+ import { arch as runtimeArch, platform as runtimePlatform } from "node:os";
2
+ import { delimiter, join } from "node:path";
3
+ import process from "node:process";
4
+
5
+ import {
6
+ DEBUG_MODE,
7
+ dirNameStr,
8
+ retrieveCdxgenPluginVersion,
9
+ safeExistsSync,
10
+ safeSpawnSync,
11
+ } from "./utils.js";
12
+
13
+ const PLUGIN_ENV_COMMAND_NAMES = {
14
+ "cargo-auditable": "CARGO_AUDITABLE_CMD",
15
+ dosai: "DOSAI_CMD",
16
+ osquery: "OSQUERY_CMD",
17
+ sourcekitten: "SOURCEKITTEN_CMD",
18
+ trivy: "TRIVY_CMD",
19
+ trustinspector: "TRUSTINSPECTOR_CMD",
20
+ };
21
+
22
+ function isMusl() {
23
+ const result = safeSpawnSync("ldd", ["--version"]);
24
+ return result?.stdout?.includes("musl") || result?.stderr?.includes("musl");
25
+ }
26
+
27
+ /**
28
+ * Determine the normalized plugin target tuple for the current runtime.
29
+ *
30
+ * @returns {{arch: string, extn: string, platform: string, pluginsBinSuffix: string}}
31
+ */
32
+ export function getPluginsBinTarget() {
33
+ let platform = runtimePlatform();
34
+ let extn = "";
35
+ let pluginsBinSuffix = "";
36
+ if (platform === "win32") {
37
+ platform = "windows";
38
+ extn = ".exe";
39
+ } else if (platform === "linux" && isMusl()) {
40
+ platform = "linuxmusl";
41
+ }
42
+
43
+ let arch = `${runtimeArch()}`;
44
+ if (arch === "x32") {
45
+ arch = "386";
46
+ } else if (arch === "x64") {
47
+ arch = "amd64";
48
+ pluginsBinSuffix = `-${platform}-amd64`;
49
+ } else if (arch === "arm64") {
50
+ pluginsBinSuffix = `-${platform}-arm64`;
51
+ } else if (arch === "ppc64") {
52
+ arch = "ppc64le";
53
+ pluginsBinSuffix = "-ppc64";
54
+ }
55
+
56
+ return {
57
+ arch,
58
+ extn,
59
+ platform,
60
+ pluginsBinSuffix,
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Resolve the cdxgen companion plugins directory for the current runtime.
66
+ *
67
+ * @returns {{
68
+ * arch: string,
69
+ * extn: string,
70
+ * extraNMBinPath: string|undefined,
71
+ * platform: string,
72
+ * pluginManifestFile: string|undefined,
73
+ * pluginVersion: string|undefined,
74
+ * pluginsBinSuffix: string,
75
+ * pluginsDir: string,
76
+ * }}
77
+ */
78
+ export function resolveCdxgenPlugins() {
79
+ const target = getPluginsBinTarget();
80
+ const pluginVersion = retrieveCdxgenPluginVersion();
81
+ let pluginsDir = process.env.CDXGEN_PLUGINS_DIR || "";
82
+ let extraNMBinPath;
83
+
84
+ if (
85
+ !pluginsDir &&
86
+ safeExistsSync(join(dirNameStr, "plugins")) &&
87
+ safeExistsSync(join(dirNameStr, "plugins", "trivy"))
88
+ ) {
89
+ pluginsDir = join(dirNameStr, "plugins");
90
+ }
91
+
92
+ if (
93
+ !pluginsDir &&
94
+ safeExistsSync(
95
+ join(
96
+ dirNameStr,
97
+ "node_modules",
98
+ "@cdxgen",
99
+ `cdxgen-plugins-bin${target.pluginsBinSuffix}`,
100
+ "plugins",
101
+ ),
102
+ ) &&
103
+ safeExistsSync(
104
+ join(
105
+ dirNameStr,
106
+ "node_modules",
107
+ "@cdxgen",
108
+ `cdxgen-plugins-bin${target.pluginsBinSuffix}`,
109
+ "plugins",
110
+ "trivy",
111
+ ),
112
+ )
113
+ ) {
114
+ pluginsDir = join(
115
+ dirNameStr,
116
+ "node_modules",
117
+ "@cdxgen",
118
+ `cdxgen-plugins-bin${target.pluginsBinSuffix}`,
119
+ "plugins",
120
+ );
121
+ if (safeExistsSync(join(dirNameStr, "node_modules", ".bin"))) {
122
+ extraNMBinPath = join(dirNameStr, "node_modules", ".bin");
123
+ }
124
+ }
125
+
126
+ if (!pluginsDir) {
127
+ let globalNodePath = process.env.GLOBAL_NODE_MODULES_PATH || undefined;
128
+ if (!globalNodePath) {
129
+ if (DEBUG_MODE) {
130
+ console.log(
131
+ 'Trying to find the global node_modules path with "pnpm root -g" command.',
132
+ );
133
+ }
134
+ const result = safeSpawnSync(
135
+ target.platform === "windows" ? "pnpm.cmd" : "pnpm",
136
+ ["root", "-g"],
137
+ );
138
+ if (result?.stdout) {
139
+ globalNodePath = `${result.stdout.trim()}/`;
140
+ }
141
+ }
142
+
143
+ let globalPlugins;
144
+ if (globalNodePath) {
145
+ globalPlugins = join(
146
+ globalNodePath,
147
+ "@cdxgen",
148
+ `cdxgen-plugins-bin${target.pluginsBinSuffix}`,
149
+ "plugins",
150
+ );
151
+ extraNMBinPath = join(
152
+ globalNodePath,
153
+ "..",
154
+ ".pnpm",
155
+ "node_modules",
156
+ ".bin",
157
+ );
158
+ }
159
+
160
+ let altGlobalPlugins;
161
+ if (
162
+ dirNameStr.includes(join("node_modules", ".pnpm", "@cyclonedx+cdxgen"))
163
+ ) {
164
+ const tmpA = dirNameStr.split(join("node_modules", ".pnpm"));
165
+ altGlobalPlugins = join(
166
+ tmpA[0],
167
+ "node_modules",
168
+ ".pnpm",
169
+ `@cdxgen+cdxgen-plugins-bin${target.pluginsBinSuffix}@${pluginVersion}`,
170
+ "node_modules",
171
+ "@cdxgen",
172
+ `cdxgen-plugins-bin${target.pluginsBinSuffix}`,
173
+ "plugins",
174
+ );
175
+ if (safeExistsSync(join(tmpA[0], "node_modules", ".bin"))) {
176
+ extraNMBinPath = join(tmpA[0], "node_modules", ".bin");
177
+ }
178
+ } else if (dirNameStr.includes(join(".pnpm", "@cyclonedx+cdxgen"))) {
179
+ const tmpA = dirNameStr.split(".pnpm");
180
+ altGlobalPlugins = join(
181
+ tmpA[0],
182
+ ".pnpm",
183
+ `@cdxgen+cdxgen-plugins-bin${target.pluginsBinSuffix}@${pluginVersion}`,
184
+ "node_modules",
185
+ "@cdxgen",
186
+ `cdxgen-plugins-bin${target.pluginsBinSuffix}`,
187
+ "plugins",
188
+ );
189
+ if (safeExistsSync(join(tmpA[0], ".bin"))) {
190
+ extraNMBinPath = join(tmpA[0], ".bin");
191
+ }
192
+ } else if (dirNameStr.includes(join("caxa", "applications"))) {
193
+ altGlobalPlugins = join(
194
+ dirNameStr,
195
+ "node_modules",
196
+ "pnpm",
197
+ `@cdxgen+cdxgen-plugins-bin${target.pluginsBinSuffix}@${pluginVersion}`,
198
+ "node_modules",
199
+ "@cdxgen",
200
+ `cdxgen-plugins-bin${target.pluginsBinSuffix}`,
201
+ "plugins",
202
+ );
203
+ extraNMBinPath = join(dirNameStr, "node_modules", ".bin");
204
+ }
205
+
206
+ if (globalPlugins && safeExistsSync(globalPlugins)) {
207
+ pluginsDir = globalPlugins;
208
+ if (DEBUG_MODE) {
209
+ console.log("Found global plugins", pluginsDir);
210
+ }
211
+ } else if (altGlobalPlugins && safeExistsSync(altGlobalPlugins)) {
212
+ pluginsDir = altGlobalPlugins;
213
+ if (DEBUG_MODE) {
214
+ console.log("Found global plugins", pluginsDir);
215
+ }
216
+ }
217
+ }
218
+
219
+ if (!pluginsDir) {
220
+ if (DEBUG_MODE) {
221
+ console.warn(
222
+ "The optional cdxgen plugin was not found. Please install cdxgen without excluding optional dependencies if needed.",
223
+ );
224
+ }
225
+ pluginsDir = "";
226
+ }
227
+
228
+ const pluginManifestFile = safeExistsSync(
229
+ join(pluginsDir, "plugins-manifest.json"),
230
+ )
231
+ ? join(pluginsDir, "plugins-manifest.json")
232
+ : undefined;
233
+
234
+ return {
235
+ ...target,
236
+ extraNMBinPath,
237
+ pluginManifestFile,
238
+ pluginVersion,
239
+ pluginsDir,
240
+ };
241
+ }
242
+
243
+ function getPluginRuntimeCacheKey() {
244
+ return [
245
+ process.env.CDXGEN_PLUGINS_DIR || "",
246
+ process.env.GLOBAL_NODE_MODULES_PATH || "",
247
+ ].join("\u0000");
248
+ }
249
+
250
+ let cachedPluginRuntime;
251
+ let cachedPluginRuntimeKey;
252
+
253
+ /**
254
+ * Retrieve the default plugin runtime, recomputing it only when the
255
+ * environment that influences plugin discovery changes.
256
+ *
257
+ * @returns {ReturnType<typeof resolveCdxgenPlugins>} The resolved plugin runtime.
258
+ */
259
+ export function getDefaultPluginRuntime() {
260
+ const cacheKey = getPluginRuntimeCacheKey();
261
+ if (!cachedPluginRuntime || cachedPluginRuntimeKey !== cacheKey) {
262
+ cachedPluginRuntime = resolveCdxgenPlugins();
263
+ cachedPluginRuntimeKey = cacheKey;
264
+ }
265
+ return cachedPluginRuntime;
266
+ }
267
+
268
+ /**
269
+ * Add the detected node_modules binary directory to PATH when present.
270
+ *
271
+ * @param {ReturnType<typeof resolveCdxgenPlugins>} [pluginRuntime] Detected plugin runtime.
272
+ * @returns {ReturnType<typeof resolveCdxgenPlugins>} The resolved plugin runtime.
273
+ */
274
+ export function setPluginsPathEnv(pluginRuntime = undefined) {
275
+ pluginRuntime ??= getDefaultPluginRuntime();
276
+ if (
277
+ pluginRuntime.extraNMBinPath &&
278
+ !process.env?.PATH?.includes(pluginRuntime.extraNMBinPath)
279
+ ) {
280
+ process.env.PATH = `${pluginRuntime.extraNMBinPath}${delimiter}${process.env.PATH}`;
281
+ }
282
+ return pluginRuntime;
283
+ }
284
+
285
+ function resolveBundledPluginBinary(toolName, pluginRuntime) {
286
+ if (!pluginRuntime.pluginsDir) {
287
+ return undefined;
288
+ }
289
+ if (!safeExistsSync(join(pluginRuntime.pluginsDir, toolName))) {
290
+ return undefined;
291
+ }
292
+ switch (toolName) {
293
+ case "trivy":
294
+ return join(
295
+ pluginRuntime.pluginsDir,
296
+ "trivy",
297
+ `trivy-cdxgen-${pluginRuntime.platform}-${pluginRuntime.arch}${pluginRuntime.extn}`,
298
+ );
299
+ case "cargo-auditable":
300
+ return join(
301
+ pluginRuntime.pluginsDir,
302
+ "cargo-auditable",
303
+ `cargo-auditable-cdxgen-${pluginRuntime.platform}-${pluginRuntime.arch}${pluginRuntime.extn}`,
304
+ );
305
+ case "osquery": {
306
+ let osqueryBin = join(
307
+ pluginRuntime.pluginsDir,
308
+ "osquery",
309
+ `osqueryi-${pluginRuntime.platform}-${pluginRuntime.arch}${pluginRuntime.extn}`,
310
+ );
311
+ if (pluginRuntime.platform === "darwin") {
312
+ osqueryBin = `${osqueryBin}.app/Contents/MacOS/osqueryd`;
313
+ }
314
+ return osqueryBin;
315
+ }
316
+ case "dosai":
317
+ return join(
318
+ pluginRuntime.pluginsDir,
319
+ "dosai",
320
+ `dosai-${pluginRuntime.platform}-${pluginRuntime.arch}${pluginRuntime.extn}`,
321
+ );
322
+ case "trustinspector":
323
+ return join(
324
+ pluginRuntime.pluginsDir,
325
+ "trustinspector",
326
+ `trustinspector-cdxgen-${pluginRuntime.platform}-${pluginRuntime.arch}${pluginRuntime.extn}`,
327
+ );
328
+ case "sourcekitten":
329
+ return join(pluginRuntime.pluginsDir, "sourcekitten", "sourcekitten");
330
+ default:
331
+ return undefined;
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Resolve a known plugin binary path, honoring explicit environment overrides.
337
+ *
338
+ * @param {string} toolName Tool identifier.
339
+ * @param {ReturnType<typeof resolveCdxgenPlugins>} [pluginRuntime] Detected plugin runtime.
340
+ * @returns {string|undefined} Resolved binary path or configured override.
341
+ */
342
+ export function resolvePluginBinary(toolName, pluginRuntime = undefined) {
343
+ pluginRuntime ??= getDefaultPluginRuntime();
344
+ const envCommandName = PLUGIN_ENV_COMMAND_NAMES[toolName];
345
+ if (envCommandName && process.env[envCommandName]) {
346
+ return process.env[envCommandName];
347
+ }
348
+ return resolveBundledPluginBinary(toolName, pluginRuntime);
349
+ }
@@ -0,0 +1,57 @@
1
+ import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import process from "node:process";
5
+
6
+ import { assert, describe, it } from "poku";
7
+
8
+ import { resolveCdxgenPlugins, resolvePluginBinary } from "./plugins.js";
9
+
10
+ describe("plugins helper", () => {
11
+ it("resolvePluginBinary() prefers explicit OSQUERY_CMD overrides", () => {
12
+ const previousOsqueryCmd = process.env.OSQUERY_CMD;
13
+ try {
14
+ process.env.OSQUERY_CMD = "/tmp/osqueryd";
15
+ assert.strictEqual(resolvePluginBinary("osquery"), "/tmp/osqueryd");
16
+ } finally {
17
+ if (previousOsqueryCmd === undefined) {
18
+ delete process.env.OSQUERY_CMD;
19
+ } else {
20
+ process.env.OSQUERY_CMD = previousOsqueryCmd;
21
+ }
22
+ }
23
+ });
24
+
25
+ it("resolveCdxgenPlugins() honors CDXGEN_PLUGINS_DIR for bundled osquery binaries", () => {
26
+ const pluginsDir = mkdtempSync(join(tmpdir(), "cdxgen-plugins-helper-"));
27
+ const previousPluginsDir = process.env.CDXGEN_PLUGINS_DIR;
28
+ try {
29
+ mkdirSync(join(pluginsDir, "osquery"), { recursive: true });
30
+ process.env.CDXGEN_PLUGINS_DIR = pluginsDir;
31
+ const pluginRuntime = resolveCdxgenPlugins();
32
+ const osqueryBinary = resolvePluginBinary("osquery", pluginRuntime);
33
+ const expectedPrefix = join(
34
+ pluginsDir,
35
+ "osquery",
36
+ `osqueryi-${pluginRuntime.platform}-${pluginRuntime.arch}${pluginRuntime.extn}`,
37
+ );
38
+
39
+ assert.strictEqual(pluginRuntime.pluginsDir, pluginsDir);
40
+ if (pluginRuntime.platform === "darwin") {
41
+ assert.strictEqual(
42
+ osqueryBinary,
43
+ `${expectedPrefix}.app/Contents/MacOS/osqueryd`,
44
+ );
45
+ } else {
46
+ assert.strictEqual(osqueryBinary, expectedPrefix);
47
+ }
48
+ } finally {
49
+ rmSync(pluginsDir, { force: true, recursive: true });
50
+ if (previousPluginsDir === undefined) {
51
+ delete process.env.CDXGEN_PLUGINS_DIR;
52
+ } else {
53
+ process.env.CDXGEN_PLUGINS_DIR = previousPluginsDir;
54
+ }
55
+ }
56
+ });
57
+ });