@cyclonedx/cdxgen 12.3.3 → 12.4.1
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.
- package/README.md +69 -25
- package/bin/audit.js +21 -7
- package/bin/cdxgen.js +270 -127
- package/bin/convert.js +34 -15
- package/bin/hbom.js +495 -0
- package/bin/repl.js +592 -37
- package/bin/validate.js +31 -4
- package/bin/verify.js +18 -5
- package/data/README.md +298 -25
- package/data/component-tags.json +6 -0
- package/data/crypto-oid.json +16 -0
- package/data/cyclonedx-2.0-bundled.schema.json +7182 -0
- package/data/predictive-audit-allowlist.json +11 -0
- package/data/queries-darwin.json +12 -1
- package/data/queries-win.json +7 -1
- package/data/queries.json +39 -2
- package/data/rules/ai-agent-governance.yaml +16 -0
- package/data/rules/asar-archives.yaml +150 -0
- package/data/rules/chrome-extensions.yaml +8 -0
- package/data/rules/ci-permissions.yaml +42 -18
- package/data/rules/container-risk.yaml +14 -7
- package/data/rules/dependency-sources.yaml +11 -0
- package/data/rules/hbom-compliance.yaml +325 -0
- package/data/rules/hbom-performance.yaml +307 -0
- package/data/rules/hbom-security.yaml +248 -0
- package/data/rules/host-topology.yaml +165 -0
- package/data/rules/mcp-servers.yaml +18 -3
- package/data/rules/obom-runtime.yaml +907 -22
- package/data/rules/package-integrity.yaml +14 -0
- package/data/rules/rootfs-hardening.yaml +179 -0
- package/data/rules/vscode-extensions.yaml +9 -0
- package/lib/audit/index.js +210 -8
- package/lib/audit/index.poku.js +332 -0
- package/lib/audit/reporters.js +222 -0
- package/lib/audit/targets.js +146 -1
- package/lib/audit/targets.poku.js +186 -0
- package/lib/cli/asar.poku.js +328 -0
- package/lib/cli/index.js +527 -99
- package/lib/cli/index.poku.js +1469 -212
- package/lib/evinser/evinser.js +14 -9
- package/lib/helpers/analyzer.js +1406 -29
- package/lib/helpers/analyzer.poku.js +342 -0
- package/lib/helpers/analyzerScope.js +712 -0
- package/lib/helpers/asarutils.js +1556 -0
- package/lib/helpers/asarutils.poku.js +443 -0
- package/lib/helpers/auditCategories.js +12 -0
- package/lib/helpers/auditCategories.poku.js +32 -0
- package/lib/helpers/bomUtils.js +155 -1
- package/lib/helpers/bomUtils.poku.js +79 -1
- package/lib/helpers/cbomutils.js +271 -1
- package/lib/helpers/cbomutils.poku.js +248 -5
- package/lib/helpers/display.js +291 -1
- package/lib/helpers/display.poku.js +149 -0
- package/lib/helpers/evidenceUtils.js +58 -0
- package/lib/helpers/evidenceUtils.poku.js +54 -0
- package/lib/helpers/exportUtils.js +9 -0
- package/lib/helpers/gtfobins.js +142 -8
- package/lib/helpers/gtfobins.poku.js +24 -1
- package/lib/helpers/hbom.js +710 -0
- package/lib/helpers/hbom.poku.js +496 -0
- package/lib/helpers/hbomAnalysis.js +268 -0
- package/lib/helpers/hbomAnalysis.poku.js +249 -0
- package/lib/helpers/hbomLoader.js +35 -0
- package/lib/helpers/hostTopology.js +803 -0
- package/lib/helpers/hostTopology.poku.js +363 -0
- package/lib/helpers/inventoryStats.js +69 -0
- package/lib/helpers/inventoryStats.poku.js +86 -0
- package/lib/helpers/lolbas.js +19 -1
- package/lib/helpers/lolbas.poku.js +23 -0
- package/lib/helpers/osqueryTransform.js +47 -0
- package/lib/helpers/osqueryTransform.poku.js +47 -0
- package/lib/helpers/plugins.js +350 -0
- package/lib/helpers/plugins.poku.js +57 -0
- package/lib/helpers/protobom.js +209 -45
- package/lib/helpers/protobom.poku.js +183 -5
- package/lib/helpers/protobomLoader.js +43 -0
- package/lib/helpers/protobomLoader.poku.js +31 -0
- package/lib/helpers/remote/dependency-track.js +36 -3
- package/lib/helpers/remote/dependency-track.poku.js +44 -0
- package/lib/helpers/source.js +24 -0
- package/lib/helpers/source.poku.js +32 -0
- package/lib/helpers/utils.js +1438 -93
- package/lib/helpers/utils.poku.js +846 -4
- package/lib/managers/binary.e2e.poku.js +367 -0
- package/lib/managers/binary.js +2293 -353
- package/lib/managers/binary.poku.js +1699 -1
- package/lib/managers/docker.js +201 -79
- package/lib/managers/docker.poku.js +337 -12
- package/lib/server/server.js +4 -28
- package/lib/stages/postgen/annotator.js +38 -0
- package/lib/stages/postgen/annotator.poku.js +107 -1
- package/lib/stages/postgen/auditBom.js +121 -18
- package/lib/stages/postgen/auditBom.poku.js +1366 -31
- package/lib/stages/postgen/hostTopologyAudit.poku.js +186 -0
- package/lib/stages/postgen/postgen.js +406 -8
- package/lib/stages/postgen/postgen.poku.js +484 -0
- package/lib/stages/postgen/ruleEngine.js +116 -0
- package/lib/stages/pregen/envAudit.js +14 -3
- package/lib/validator/bomValidator.js +90 -38
- package/lib/validator/bomValidator.poku.js +90 -0
- package/lib/validator/complianceRules.js +4 -2
- package/lib/validator/index.poku.js +14 -0
- package/package.json +23 -21
- package/types/bin/hbom.d.ts +3 -0
- package/types/bin/hbom.d.ts.map +1 -0
- package/types/bin/repl.d.ts +1 -1
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +44 -0
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/audit/reporters.d.ts +16 -0
- package/types/lib/audit/reporters.d.ts.map +1 -1
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +16 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +4 -0
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts +33 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/analyzerScope.d.ts +11 -0
- package/types/lib/helpers/analyzerScope.d.ts.map +1 -0
- package/types/lib/helpers/asarutils.d.ts +34 -0
- package/types/lib/helpers/asarutils.d.ts.map +1 -0
- package/types/lib/helpers/auditCategories.d.ts +5 -0
- package/types/lib/helpers/auditCategories.d.ts.map +1 -1
- package/types/lib/helpers/bomUtils.d.ts +10 -0
- package/types/lib/helpers/bomUtils.d.ts.map +1 -1
- package/types/lib/helpers/cbomutils.d.ts +3 -2
- package/types/lib/helpers/cbomutils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/evidenceUtils.d.ts +8 -0
- package/types/lib/helpers/evidenceUtils.d.ts.map +1 -0
- package/types/lib/helpers/exportUtils.d.ts.map +1 -1
- package/types/lib/helpers/gtfobins.d.ts +8 -0
- package/types/lib/helpers/gtfobins.d.ts.map +1 -1
- package/types/lib/helpers/hbom.d.ts +49 -0
- package/types/lib/helpers/hbom.d.ts.map +1 -0
- package/types/lib/helpers/hbomAnalysis.d.ts +76 -0
- package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -0
- package/types/lib/helpers/hbomLoader.d.ts +7 -0
- package/types/lib/helpers/hbomLoader.d.ts.map +1 -0
- package/types/lib/helpers/hostTopology.d.ts +12 -0
- package/types/lib/helpers/hostTopology.d.ts.map +1 -0
- package/types/lib/helpers/inventoryStats.d.ts +11 -0
- package/types/lib/helpers/inventoryStats.d.ts.map +1 -0
- package/types/lib/helpers/lolbas.d.ts.map +1 -1
- package/types/lib/helpers/osqueryTransform.d.ts +3 -0
- package/types/lib/helpers/osqueryTransform.d.ts.map +1 -1
- package/types/lib/helpers/plugins.d.ts +58 -0
- package/types/lib/helpers/plugins.d.ts.map +1 -0
- package/types/lib/helpers/protobom.d.ts +5 -4
- package/types/lib/helpers/protobom.d.ts.map +1 -1
- package/types/lib/helpers/protobomLoader.d.ts +17 -0
- package/types/lib/helpers/protobomLoader.d.ts.map +1 -0
- package/types/lib/helpers/remote/dependency-track.d.ts +10 -3
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
- package/types/lib/helpers/source.d.ts.map +1 -1
- package/types/lib/helpers/utils.d.ts +45 -8
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +5 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +2 -1
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts +26 -1
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +2 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/third-party/arborist/lib/node.d.ts +23 -0
- package/types/lib/third-party/arborist/lib/node.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/complianceRules.d.ts.map +1 -1
- package/data/spdx-model-v3.0.1.jsonld +0 -15999
|
@@ -0,0 +1,350 @@
|
|
|
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
|
+
function hasUsablePluginsDir(pluginsDir) {
|
|
28
|
+
return (
|
|
29
|
+
safeExistsSync(pluginsDir) &&
|
|
30
|
+
(safeExistsSync(join(pluginsDir, "plugins-manifest.json")) ||
|
|
31
|
+
[
|
|
32
|
+
"cargo-auditable",
|
|
33
|
+
"dosai",
|
|
34
|
+
"osquery",
|
|
35
|
+
"sourcekitten",
|
|
36
|
+
"trivy",
|
|
37
|
+
"trustinspector",
|
|
38
|
+
].some((pluginName) => safeExistsSync(join(pluginsDir, pluginName))))
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Determine the normalized plugin target tuple for the current runtime.
|
|
44
|
+
*
|
|
45
|
+
* @returns {{arch: string, extn: string, platform: string, pluginsBinSuffix: string}}
|
|
46
|
+
*/
|
|
47
|
+
export function getPluginsBinTarget() {
|
|
48
|
+
let platform = runtimePlatform();
|
|
49
|
+
let extn = "";
|
|
50
|
+
let pluginsBinSuffix = "";
|
|
51
|
+
if (platform === "win32") {
|
|
52
|
+
platform = "windows";
|
|
53
|
+
extn = ".exe";
|
|
54
|
+
} else if (platform === "linux" && isMusl()) {
|
|
55
|
+
platform = "linuxmusl";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let arch = `${runtimeArch()}`;
|
|
59
|
+
if (arch === "x32") {
|
|
60
|
+
arch = "386";
|
|
61
|
+
} else if (arch === "x64") {
|
|
62
|
+
arch = "amd64";
|
|
63
|
+
pluginsBinSuffix = `-${platform}-amd64`;
|
|
64
|
+
} else if (arch === "arm64") {
|
|
65
|
+
pluginsBinSuffix = `-${platform}-arm64`;
|
|
66
|
+
} else if (arch === "ppc64") {
|
|
67
|
+
arch = "ppc64le";
|
|
68
|
+
pluginsBinSuffix = "-ppc64";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
arch,
|
|
73
|
+
extn,
|
|
74
|
+
platform,
|
|
75
|
+
pluginsBinSuffix,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Resolve the cdxgen companion plugins directory for the current runtime.
|
|
81
|
+
*
|
|
82
|
+
* @returns {{
|
|
83
|
+
* arch: string,
|
|
84
|
+
* extn: string,
|
|
85
|
+
* extraNMBinPath: string|undefined,
|
|
86
|
+
* platform: string,
|
|
87
|
+
* pluginManifestFile: string|undefined,
|
|
88
|
+
* pluginVersion: string|undefined,
|
|
89
|
+
* pluginsBinSuffix: string,
|
|
90
|
+
* pluginsDir: string,
|
|
91
|
+
* }}
|
|
92
|
+
*/
|
|
93
|
+
export function resolveCdxgenPlugins() {
|
|
94
|
+
const target = getPluginsBinTarget();
|
|
95
|
+
const pluginVersion = retrieveCdxgenPluginVersion();
|
|
96
|
+
let pluginsDir = process.env.CDXGEN_PLUGINS_DIR || "";
|
|
97
|
+
let extraNMBinPath;
|
|
98
|
+
|
|
99
|
+
if (!pluginsDir && hasUsablePluginsDir(join(dirNameStr, "plugins"))) {
|
|
100
|
+
pluginsDir = join(dirNameStr, "plugins");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (
|
|
104
|
+
!pluginsDir &&
|
|
105
|
+
hasUsablePluginsDir(
|
|
106
|
+
join(
|
|
107
|
+
dirNameStr,
|
|
108
|
+
"node_modules",
|
|
109
|
+
"@cdxgen",
|
|
110
|
+
`cdxgen-plugins-bin${target.pluginsBinSuffix}`,
|
|
111
|
+
"plugins",
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
) {
|
|
115
|
+
pluginsDir = join(
|
|
116
|
+
dirNameStr,
|
|
117
|
+
"node_modules",
|
|
118
|
+
"@cdxgen",
|
|
119
|
+
`cdxgen-plugins-bin${target.pluginsBinSuffix}`,
|
|
120
|
+
"plugins",
|
|
121
|
+
);
|
|
122
|
+
if (safeExistsSync(join(dirNameStr, "node_modules", ".bin"))) {
|
|
123
|
+
extraNMBinPath = join(dirNameStr, "node_modules", ".bin");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!pluginsDir) {
|
|
128
|
+
let globalNodePath = process.env.GLOBAL_NODE_MODULES_PATH || undefined;
|
|
129
|
+
if (!globalNodePath) {
|
|
130
|
+
if (DEBUG_MODE) {
|
|
131
|
+
console.log(
|
|
132
|
+
'Trying to find the global node_modules path with "pnpm root -g" command.',
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
const result = safeSpawnSync(
|
|
136
|
+
target.platform === "windows" ? "pnpm.cmd" : "pnpm",
|
|
137
|
+
["root", "-g"],
|
|
138
|
+
);
|
|
139
|
+
if (result?.stdout) {
|
|
140
|
+
globalNodePath = `${result.stdout.trim()}/`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let globalPlugins;
|
|
145
|
+
if (globalNodePath) {
|
|
146
|
+
globalPlugins = join(
|
|
147
|
+
globalNodePath,
|
|
148
|
+
"@cdxgen",
|
|
149
|
+
`cdxgen-plugins-bin${target.pluginsBinSuffix}`,
|
|
150
|
+
"plugins",
|
|
151
|
+
);
|
|
152
|
+
extraNMBinPath = join(
|
|
153
|
+
globalNodePath,
|
|
154
|
+
"..",
|
|
155
|
+
".pnpm",
|
|
156
|
+
"node_modules",
|
|
157
|
+
".bin",
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let altGlobalPlugins;
|
|
162
|
+
if (
|
|
163
|
+
dirNameStr.includes(join("node_modules", ".pnpm", "@cyclonedx+cdxgen"))
|
|
164
|
+
) {
|
|
165
|
+
const tmpA = dirNameStr.split(join("node_modules", ".pnpm"));
|
|
166
|
+
altGlobalPlugins = join(
|
|
167
|
+
tmpA[0],
|
|
168
|
+
"node_modules",
|
|
169
|
+
".pnpm",
|
|
170
|
+
`@cdxgen+cdxgen-plugins-bin${target.pluginsBinSuffix}@${pluginVersion}`,
|
|
171
|
+
"node_modules",
|
|
172
|
+
"@cdxgen",
|
|
173
|
+
`cdxgen-plugins-bin${target.pluginsBinSuffix}`,
|
|
174
|
+
"plugins",
|
|
175
|
+
);
|
|
176
|
+
if (safeExistsSync(join(tmpA[0], "node_modules", ".bin"))) {
|
|
177
|
+
extraNMBinPath = join(tmpA[0], "node_modules", ".bin");
|
|
178
|
+
}
|
|
179
|
+
} else if (dirNameStr.includes(join(".pnpm", "@cyclonedx+cdxgen"))) {
|
|
180
|
+
const tmpA = dirNameStr.split(".pnpm");
|
|
181
|
+
altGlobalPlugins = join(
|
|
182
|
+
tmpA[0],
|
|
183
|
+
".pnpm",
|
|
184
|
+
`@cdxgen+cdxgen-plugins-bin${target.pluginsBinSuffix}@${pluginVersion}`,
|
|
185
|
+
"node_modules",
|
|
186
|
+
"@cdxgen",
|
|
187
|
+
`cdxgen-plugins-bin${target.pluginsBinSuffix}`,
|
|
188
|
+
"plugins",
|
|
189
|
+
);
|
|
190
|
+
if (safeExistsSync(join(tmpA[0], ".bin"))) {
|
|
191
|
+
extraNMBinPath = join(tmpA[0], ".bin");
|
|
192
|
+
}
|
|
193
|
+
} else if (dirNameStr.includes(join("caxa", "applications"))) {
|
|
194
|
+
altGlobalPlugins = join(
|
|
195
|
+
dirNameStr,
|
|
196
|
+
"node_modules",
|
|
197
|
+
"pnpm",
|
|
198
|
+
`@cdxgen+cdxgen-plugins-bin${target.pluginsBinSuffix}@${pluginVersion}`,
|
|
199
|
+
"node_modules",
|
|
200
|
+
"@cdxgen",
|
|
201
|
+
`cdxgen-plugins-bin${target.pluginsBinSuffix}`,
|
|
202
|
+
"plugins",
|
|
203
|
+
);
|
|
204
|
+
extraNMBinPath = join(dirNameStr, "node_modules", ".bin");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (globalPlugins && safeExistsSync(globalPlugins)) {
|
|
208
|
+
pluginsDir = globalPlugins;
|
|
209
|
+
if (DEBUG_MODE) {
|
|
210
|
+
console.log("Found global plugins", pluginsDir);
|
|
211
|
+
}
|
|
212
|
+
} else if (altGlobalPlugins && safeExistsSync(altGlobalPlugins)) {
|
|
213
|
+
pluginsDir = altGlobalPlugins;
|
|
214
|
+
if (DEBUG_MODE) {
|
|
215
|
+
console.log("Found global plugins", pluginsDir);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (!pluginsDir) {
|
|
221
|
+
if (DEBUG_MODE) {
|
|
222
|
+
console.warn(
|
|
223
|
+
"The optional cdxgen plugin was not found. Please install cdxgen without excluding optional dependencies if needed.",
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
pluginsDir = "";
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const pluginManifestFile = safeExistsSync(
|
|
230
|
+
join(pluginsDir, "plugins-manifest.json"),
|
|
231
|
+
)
|
|
232
|
+
? join(pluginsDir, "plugins-manifest.json")
|
|
233
|
+
: undefined;
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
...target,
|
|
237
|
+
extraNMBinPath,
|
|
238
|
+
pluginManifestFile,
|
|
239
|
+
pluginVersion,
|
|
240
|
+
pluginsDir,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function getPluginRuntimeCacheKey() {
|
|
245
|
+
return [
|
|
246
|
+
process.env.CDXGEN_PLUGINS_DIR || "",
|
|
247
|
+
process.env.GLOBAL_NODE_MODULES_PATH || "",
|
|
248
|
+
].join("\u0000");
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
let cachedPluginRuntime;
|
|
252
|
+
let cachedPluginRuntimeKey;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Retrieve the default plugin runtime, recomputing it only when the
|
|
256
|
+
* environment that influences plugin discovery changes.
|
|
257
|
+
*
|
|
258
|
+
* @returns {ReturnType<typeof resolveCdxgenPlugins>} The resolved plugin runtime.
|
|
259
|
+
*/
|
|
260
|
+
export function getDefaultPluginRuntime() {
|
|
261
|
+
const cacheKey = getPluginRuntimeCacheKey();
|
|
262
|
+
if (!cachedPluginRuntime || cachedPluginRuntimeKey !== cacheKey) {
|
|
263
|
+
cachedPluginRuntime = resolveCdxgenPlugins();
|
|
264
|
+
cachedPluginRuntimeKey = cacheKey;
|
|
265
|
+
}
|
|
266
|
+
return cachedPluginRuntime;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Add the detected node_modules binary directory to PATH when present.
|
|
271
|
+
*
|
|
272
|
+
* @param {ReturnType<typeof resolveCdxgenPlugins>} [pluginRuntime] Detected plugin runtime.
|
|
273
|
+
* @returns {ReturnType<typeof resolveCdxgenPlugins>} The resolved plugin runtime.
|
|
274
|
+
*/
|
|
275
|
+
export function setPluginsPathEnv(pluginRuntime = undefined) {
|
|
276
|
+
pluginRuntime ??= getDefaultPluginRuntime();
|
|
277
|
+
if (
|
|
278
|
+
pluginRuntime.extraNMBinPath &&
|
|
279
|
+
!process.env?.PATH?.includes(pluginRuntime.extraNMBinPath)
|
|
280
|
+
) {
|
|
281
|
+
process.env.PATH = `${pluginRuntime.extraNMBinPath}${delimiter}${process.env.PATH}`;
|
|
282
|
+
}
|
|
283
|
+
return pluginRuntime;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function resolveBundledPluginBinary(toolName, pluginRuntime) {
|
|
287
|
+
if (!pluginRuntime.pluginsDir) {
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
if (!safeExistsSync(join(pluginRuntime.pluginsDir, toolName))) {
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
switch (toolName) {
|
|
294
|
+
case "trivy":
|
|
295
|
+
return join(
|
|
296
|
+
pluginRuntime.pluginsDir,
|
|
297
|
+
"trivy",
|
|
298
|
+
`trivy-cdxgen-${pluginRuntime.platform}-${pluginRuntime.arch}${pluginRuntime.extn}`,
|
|
299
|
+
);
|
|
300
|
+
case "cargo-auditable":
|
|
301
|
+
return join(
|
|
302
|
+
pluginRuntime.pluginsDir,
|
|
303
|
+
"cargo-auditable",
|
|
304
|
+
`cargo-auditable-cdxgen-${pluginRuntime.platform}-${pluginRuntime.arch}${pluginRuntime.extn}`,
|
|
305
|
+
);
|
|
306
|
+
case "osquery": {
|
|
307
|
+
let osqueryBin = join(
|
|
308
|
+
pluginRuntime.pluginsDir,
|
|
309
|
+
"osquery",
|
|
310
|
+
`osqueryi-${pluginRuntime.platform}-${pluginRuntime.arch}${pluginRuntime.extn}`,
|
|
311
|
+
);
|
|
312
|
+
if (pluginRuntime.platform === "darwin") {
|
|
313
|
+
osqueryBin = `${osqueryBin}.app/Contents/MacOS/osqueryd`;
|
|
314
|
+
}
|
|
315
|
+
return osqueryBin;
|
|
316
|
+
}
|
|
317
|
+
case "dosai":
|
|
318
|
+
return join(
|
|
319
|
+
pluginRuntime.pluginsDir,
|
|
320
|
+
"dosai",
|
|
321
|
+
`dosai-${pluginRuntime.platform}-${pluginRuntime.arch}${pluginRuntime.extn}`,
|
|
322
|
+
);
|
|
323
|
+
case "trustinspector":
|
|
324
|
+
return join(
|
|
325
|
+
pluginRuntime.pluginsDir,
|
|
326
|
+
"trustinspector",
|
|
327
|
+
`trustinspector-cdxgen-${pluginRuntime.platform}-${pluginRuntime.arch}${pluginRuntime.extn}`,
|
|
328
|
+
);
|
|
329
|
+
case "sourcekitten":
|
|
330
|
+
return join(pluginRuntime.pluginsDir, "sourcekitten", "sourcekitten");
|
|
331
|
+
default:
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Resolve a known plugin binary path, honoring explicit environment overrides.
|
|
338
|
+
*
|
|
339
|
+
* @param {string} toolName Tool identifier.
|
|
340
|
+
* @param {ReturnType<typeof resolveCdxgenPlugins>} [pluginRuntime] Detected plugin runtime.
|
|
341
|
+
* @returns {string|undefined} Resolved binary path or configured override.
|
|
342
|
+
*/
|
|
343
|
+
export function resolvePluginBinary(toolName, pluginRuntime = undefined) {
|
|
344
|
+
pluginRuntime ??= getDefaultPluginRuntime();
|
|
345
|
+
const envCommandName = PLUGIN_ENV_COMMAND_NAMES[toolName];
|
|
346
|
+
if (envCommandName && process.env[envCommandName]) {
|
|
347
|
+
return process.env[envCommandName];
|
|
348
|
+
}
|
|
349
|
+
return resolveBundledPluginBinary(toolName, pluginRuntime);
|
|
350
|
+
}
|
|
@@ -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
|
+
});
|
package/lib/helpers/protobom.js
CHANGED
|
@@ -1,26 +1,203 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
|
|
3
|
-
import { cdx_16, cdx_17 } from "@appthreat/cdx-proto";
|
|
4
3
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
createBom,
|
|
5
|
+
decodeBomBinary,
|
|
6
|
+
decodeBomJson,
|
|
7
|
+
encodeBomBinary,
|
|
8
|
+
encodeBomJson,
|
|
9
|
+
parseBomBinary,
|
|
10
|
+
parseBomJson,
|
|
11
|
+
supportedSpecVersions,
|
|
12
|
+
} from "@appthreat/cdx-proto";
|
|
10
13
|
|
|
14
|
+
import { toCycloneDxSpecVersionString } from "./bomUtils.js";
|
|
11
15
|
import { safeExistsSync, safeWriteSync } from "./utils.js";
|
|
12
16
|
|
|
17
|
+
const JSON_READ_OPTIONS = {
|
|
18
|
+
ignoreUnknownFields: true,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const BINARY_READ_OPTIONS = {
|
|
22
|
+
readUnknownFields: true,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const BINARY_WRITE_OPTIONS = {
|
|
26
|
+
writeUnknownFields: true,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const PROTO_BOM_FILE_EXTENSIONS = [".cdx", ".cdx.bin", ".proto"];
|
|
30
|
+
|
|
31
|
+
const DEFAULT_SPEC_VERSION =
|
|
32
|
+
supportedSpecVersions[supportedSpecVersions.length - 1];
|
|
33
|
+
const PROTO_SUPPORTED_SPEC_VERSIONS = new Set(
|
|
34
|
+
supportedSpecVersions.map((specVersion) =>
|
|
35
|
+
toCycloneDxSpecVersionString(specVersion),
|
|
36
|
+
),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const isProtoMessageBom = (bom) =>
|
|
40
|
+
Boolean(
|
|
41
|
+
bom &&
|
|
42
|
+
typeof bom === "object" &&
|
|
43
|
+
!Array.isArray(bom) &&
|
|
44
|
+
typeof bom.$typeName === "string" &&
|
|
45
|
+
bom.specVersion,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const hasExplicitSpecVersion = (bomJson) =>
|
|
49
|
+
Boolean(
|
|
50
|
+
bomJson &&
|
|
51
|
+
typeof bomJson === "object" &&
|
|
52
|
+
!Array.isArray(bomJson) &&
|
|
53
|
+
(bomJson.specVersion !== undefined || bomJson.spec_version !== undefined),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const resolveExplicitSpecVersion = (bomJson) =>
|
|
57
|
+
bomJson?.specVersion ?? bomJson?.spec_version;
|
|
58
|
+
|
|
59
|
+
const hasProvidedSpecVersion = (specVersion) =>
|
|
60
|
+
specVersion !== undefined &&
|
|
61
|
+
specVersion !== null &&
|
|
62
|
+
`${specVersion}`.trim() !== "";
|
|
63
|
+
|
|
64
|
+
export const isProtoSupportedSpecVersion = (specVersion) => {
|
|
65
|
+
if (!hasProvidedSpecVersion(specVersion)) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
const normalizedSpecVersion = toCycloneDxSpecVersionString(specVersion);
|
|
69
|
+
return (
|
|
70
|
+
normalizedSpecVersion !== undefined &&
|
|
71
|
+
PROTO_SUPPORTED_SPEC_VERSIONS.has(normalizedSpecVersion)
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const assertProtoSupportedSpecVersion = (
|
|
76
|
+
specVersion,
|
|
77
|
+
operation = "protobuf operations",
|
|
78
|
+
) => {
|
|
79
|
+
if (!hasProvidedSpecVersion(specVersion)) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const normalizedSpecVersion = toCycloneDxSpecVersionString(specVersion);
|
|
83
|
+
if (isProtoSupportedSpecVersion(specVersion)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const displaySpecVersion = normalizedSpecVersion || `${specVersion}`.trim();
|
|
87
|
+
throw new Error(
|
|
88
|
+
`CycloneDX ${displaySpecVersion} is not currently supported for ${operation}. @appthreat/cdx-proto supports ${supportedSpecVersions.join(", ")} only.`,
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const OBJECT_WRAPPED_LIST_FIELDS = ["declarations", "definitions"];
|
|
93
|
+
|
|
94
|
+
const isPlainObject = (value) =>
|
|
95
|
+
Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
96
|
+
|
|
97
|
+
const normalizeObjectWrappedListsForProto = (bomJson) => {
|
|
98
|
+
if (!isPlainObject(bomJson)) {
|
|
99
|
+
return bomJson;
|
|
100
|
+
}
|
|
101
|
+
const normalizedBomJson = { ...bomJson };
|
|
102
|
+
for (const fieldName of OBJECT_WRAPPED_LIST_FIELDS) {
|
|
103
|
+
if (isPlainObject(normalizedBomJson[fieldName])) {
|
|
104
|
+
normalizedBomJson[fieldName] = [normalizedBomJson[fieldName]];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return normalizedBomJson;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const mergeObjectWrappedListEntries = (entries) => {
|
|
111
|
+
const mergedEntry = {};
|
|
112
|
+
for (const entry of entries) {
|
|
113
|
+
if (!isPlainObject(entry)) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
for (const [key, value] of Object.entries(entry)) {
|
|
117
|
+
if (value === undefined) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (Array.isArray(value)) {
|
|
121
|
+
mergedEntry[key] = [...(mergedEntry[key] || []), ...value];
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (isPlainObject(value) && isPlainObject(mergedEntry[key])) {
|
|
125
|
+
mergedEntry[key] = { ...mergedEntry[key], ...value };
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (mergedEntry[key] === undefined) {
|
|
129
|
+
mergedEntry[key] = value;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return Object.keys(mergedEntry).length ? mergedEntry : undefined;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const normalizeObjectWrappedListsFromProto = (bomJson) => {
|
|
137
|
+
if (!isPlainObject(bomJson)) {
|
|
138
|
+
return bomJson;
|
|
139
|
+
}
|
|
140
|
+
const normalizedBomJson = { ...bomJson };
|
|
141
|
+
for (const fieldName of OBJECT_WRAPPED_LIST_FIELDS) {
|
|
142
|
+
if (!Array.isArray(normalizedBomJson[fieldName])) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const mergedEntry = mergeObjectWrappedListEntries(
|
|
146
|
+
normalizedBomJson[fieldName],
|
|
147
|
+
);
|
|
148
|
+
if (mergedEntry) {
|
|
149
|
+
normalizedBomJson[fieldName] = mergedEntry;
|
|
150
|
+
} else {
|
|
151
|
+
delete normalizedBomJson[fieldName];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return normalizedBomJson;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const resolveBomMessage = (bomJson, specVersion = DEFAULT_SPEC_VERSION) => {
|
|
158
|
+
if (isProtoMessageBom(bomJson)) {
|
|
159
|
+
return bomJson;
|
|
160
|
+
}
|
|
161
|
+
if (typeof bomJson === "string" || bomJson instanceof String) {
|
|
162
|
+
const parsedBomJson = normalizeObjectWrappedListsForProto(
|
|
163
|
+
JSON.parse(`${bomJson}`),
|
|
164
|
+
);
|
|
165
|
+
if (hasExplicitSpecVersion(parsedBomJson)) {
|
|
166
|
+
assertProtoSupportedSpecVersion(
|
|
167
|
+
resolveExplicitSpecVersion(parsedBomJson),
|
|
168
|
+
"protobuf serialization",
|
|
169
|
+
);
|
|
170
|
+
return parseBomJson(parsedBomJson, JSON_READ_OPTIONS);
|
|
171
|
+
}
|
|
172
|
+
assertProtoSupportedSpecVersion(specVersion, "protobuf serialization");
|
|
173
|
+
return decodeBomJson(specVersion, parsedBomJson, JSON_READ_OPTIONS);
|
|
174
|
+
}
|
|
175
|
+
if (bomJson && typeof bomJson === "object" && !Array.isArray(bomJson)) {
|
|
176
|
+
const normalizedBomJson = normalizeObjectWrappedListsForProto(bomJson);
|
|
177
|
+
if (hasExplicitSpecVersion(normalizedBomJson)) {
|
|
178
|
+
assertProtoSupportedSpecVersion(
|
|
179
|
+
resolveExplicitSpecVersion(normalizedBomJson),
|
|
180
|
+
"protobuf serialization",
|
|
181
|
+
);
|
|
182
|
+
return parseBomJson(normalizedBomJson, JSON_READ_OPTIONS);
|
|
183
|
+
}
|
|
184
|
+
assertProtoSupportedSpecVersion(specVersion, "protobuf serialization");
|
|
185
|
+
return decodeBomJson(specVersion, normalizedBomJson, JSON_READ_OPTIONS);
|
|
186
|
+
}
|
|
187
|
+
return createBom(specVersion);
|
|
188
|
+
};
|
|
189
|
+
|
|
13
190
|
/**
|
|
14
|
-
*
|
|
191
|
+
* Determine whether a path looks like a CycloneDX protobuf file.
|
|
15
192
|
*
|
|
16
|
-
* @param {string
|
|
17
|
-
* @returns {
|
|
193
|
+
* @param {string} filePath File path
|
|
194
|
+
* @returns {boolean} true when the path looks like a protobuf BOM file
|
|
18
195
|
*/
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
196
|
+
export const isProtoBomFile = (filePath) => {
|
|
197
|
+
const normalizedPath = `${filePath || ""}`.toLowerCase();
|
|
198
|
+
return PROTO_BOM_FILE_EXTENSIONS.some((extension) =>
|
|
199
|
+
normalizedPath.endsWith(extension),
|
|
200
|
+
);
|
|
24
201
|
};
|
|
25
202
|
|
|
26
203
|
/**
|
|
@@ -28,27 +205,16 @@ const stringifyIfNeeded = (bomJson) => {
|
|
|
28
205
|
*
|
|
29
206
|
* @param {string | Object} bomJson BOM Json
|
|
30
207
|
* @param {string} binFile Binary file name
|
|
208
|
+
* @param {string | number} [specVersion] CycloneDX spec version fallback for BOMs without specVersion
|
|
31
209
|
*/
|
|
32
|
-
export const writeBinary = (
|
|
210
|
+
export const writeBinary = (
|
|
211
|
+
bomJson,
|
|
212
|
+
binFile,
|
|
213
|
+
specVersion = DEFAULT_SPEC_VERSION,
|
|
214
|
+
) => {
|
|
33
215
|
if (bomJson && binFile) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
bomSchema = cdx_17.BomSchema;
|
|
37
|
-
} else {
|
|
38
|
-
bomSchema = cdx_16.BomSchema;
|
|
39
|
-
}
|
|
40
|
-
safeWriteSync(
|
|
41
|
-
binFile,
|
|
42
|
-
toBinary(
|
|
43
|
-
bomSchema,
|
|
44
|
-
fromJsonString(bomSchema, stringifyIfNeeded(bomJson), {
|
|
45
|
-
ignoreUnknownFields: true,
|
|
46
|
-
}),
|
|
47
|
-
),
|
|
48
|
-
{
|
|
49
|
-
writeUnknownFields: true,
|
|
50
|
-
},
|
|
51
|
-
);
|
|
216
|
+
const bomMessage = resolveBomMessage(bomJson, specVersion);
|
|
217
|
+
safeWriteSync(binFile, encodeBomBinary(bomMessage, BINARY_WRITE_OPTIONS));
|
|
52
218
|
}
|
|
53
219
|
};
|
|
54
220
|
|
|
@@ -57,23 +223,21 @@ export const writeBinary = (bomJson, binFile) => {
|
|
|
57
223
|
*
|
|
58
224
|
* @param {string} binFile Binary file name
|
|
59
225
|
* @param {boolean} asJson Convert to JSON
|
|
60
|
-
* @param {number} specVersion
|
|
226
|
+
* @param {string | number} [specVersion] Optional specification version. When omitted, cdxgen auto-detects the matching schema.
|
|
61
227
|
*/
|
|
62
|
-
export const readBinary = (binFile, asJson
|
|
228
|
+
export const readBinary = (binFile, asJson, specVersion) => {
|
|
229
|
+
asJson = asJson ?? true;
|
|
230
|
+
assertProtoSupportedSpecVersion(specVersion, "protobuf decoding");
|
|
63
231
|
if (!safeExistsSync(binFile)) {
|
|
64
232
|
return undefined;
|
|
65
233
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
const bomObject = fromBinary(bomSchema, readFileSync(binFile), {
|
|
73
|
-
readUnknownFields: true,
|
|
74
|
-
});
|
|
234
|
+
const binaryData = readFileSync(binFile);
|
|
235
|
+
const bomObject =
|
|
236
|
+
specVersion !== undefined && specVersion !== null && specVersion !== ""
|
|
237
|
+
? decodeBomBinary(specVersion, binaryData, BINARY_READ_OPTIONS)
|
|
238
|
+
: parseBomBinary(binaryData, BINARY_READ_OPTIONS);
|
|
75
239
|
if (asJson) {
|
|
76
|
-
return
|
|
240
|
+
return normalizeObjectWrappedListsFromProto(encodeBomJson(bomObject));
|
|
77
241
|
}
|
|
78
242
|
return bomObject;
|
|
79
243
|
};
|