@cdxgen/cdxgen 13.1.0 → 13.3.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.
- package/README.md +32 -15
- package/bin/cdxgen.js +243 -122
- package/bin/evinse.js +7 -0
- package/bin/sign.js +88 -14
- package/bin/tracebom.js +64 -4
- package/bin/validate.js +29 -7
- package/bin/verify.js +79 -39
- package/data/crypto-oid.json +88 -12
- package/data/remediations.json +47 -0
- package/data/rules/ai-bom.yaml +78 -0
- package/data/rules/cargo-rust.yaml +132 -0
- package/data/rules/mcp-servers.yaml +122 -0
- package/lib/audit/targets.js +6 -1
- package/lib/cli/bomAssembly.js +117 -15
- package/lib/cli/cliOptions.js +93 -1
- package/lib/cli/index.js +235 -5
- package/lib/cli/jsBom.js +108 -26
- package/lib/cli/jvmBom.js +68 -12
- package/lib/cli/managedBom.js +264 -41
- package/lib/cli/nativeBom.js +923 -15
- package/lib/core/activity.js +120 -2
- package/lib/core/env.js +44 -7
- package/lib/core/fs.js +174 -6
- package/lib/core/metadataProperties.js +97 -0
- package/lib/core/projectConfig.js +31 -4
- package/lib/core/userAgent.js +151 -0
- package/lib/ecosystems/bunutils.js +269 -101
- package/lib/ecosystems/cargoCache.js +364 -0
- package/lib/ecosystems/caxa.js +447 -7
- package/lib/ecosystems/cmakeResolver.js +66 -0
- package/lib/ecosystems/cppEvidence.js +104 -33
- package/lib/ecosystems/ecosystems.js +260 -99
- package/lib/ecosystems/golem.js +11 -1
- package/lib/ecosystems/gradleutils.js +185 -9
- package/lib/ecosystems/jsEvidence.js +19 -20
- package/lib/ecosystems/kosi.js +1263 -0
- package/lib/ecosystems/npmutils.js +110 -9
- package/lib/ecosystems/parsers-crystal.js +179 -0
- package/lib/ecosystems/parsers-dotnet.js +120 -49
- package/lib/ecosystems/parsers-elm.js +816 -0
- package/lib/ecosystems/parsers-gleam.js +1 -1
- package/lib/ecosystems/parsers-go.js +47 -9
- package/lib/ecosystems/parsers-js.js +171 -50
- package/lib/ecosystems/parsers-julia.js +268 -0
- package/lib/ecosystems/parsers-lua.js +218 -0
- package/lib/ecosystems/parsers-misc.js +919 -161
- package/lib/ecosystems/parsers-nim.js +221 -0
- package/lib/ecosystems/parsers-opam.js +180 -0
- package/lib/ecosystems/parsers-perl.js +277 -0
- package/lib/ecosystems/parsers-python.js +27 -15
- package/lib/ecosystems/parsers-r.js +148 -0
- package/lib/ecosystems/parsers-rebar.js +411 -0
- package/lib/ecosystems/parsers-rust.js +954 -33
- package/lib/ecosystems/parsers-spack.js +204 -0
- package/lib/ecosystems/parsers-stack.js +264 -0
- package/lib/ecosystems/parsers-terraform.js +194 -0
- package/lib/ecosystems/rubyutils.js +1426 -166
- package/lib/ecosystems/rusi.js +158 -7
- package/lib/evinser/evinser.js +547 -129
- package/lib/evinser/swiftsem.js +1914 -302
- package/lib/helpers/agenticToolutils.js +925 -0
- package/lib/helpers/bomSigner.js +652 -221
- package/lib/inventory/agentFormulationParser.js +57 -4
- package/lib/inventory/aiCollector.js +504 -10
- package/lib/inventory/aiInventory.js +12 -0
- package/lib/inventory/analyzer.js +488 -30
- package/lib/inventory/astWorker.js +6 -0
- package/lib/inventory/atomUtils.js +568 -31
- package/lib/inventory/auditCategories.js +8 -0
- package/lib/inventory/cbomutils.js +30 -0
- package/lib/inventory/cdxrs.js +110 -14
- package/lib/inventory/communityAiConfigParser.js +215 -2
- package/lib/inventory/depsUtils.js +183 -11
- package/lib/inventory/display.js +4 -5
- package/lib/inventory/dosai.js +627 -13
- package/lib/inventory/dynamic.js +39 -1
- package/lib/inventory/envcontext.js +9 -6
- package/lib/inventory/evidenceUtils.js +26 -5
- package/lib/inventory/fetchBatch.js +190 -19
- package/lib/inventory/fetchRate.js +74 -11
- package/lib/inventory/forgeEnricher.js +3 -2
- package/lib/inventory/jvmToolEnv.js +6 -5
- package/lib/inventory/mcpConfigParser.js +166 -2
- package/lib/inventory/mcpDiscovery.js +11 -0
- package/lib/inventory/mcpServerManifest.js +389 -0
- package/lib/inventory/mcpbBundle.js +419 -0
- package/lib/inventory/plugins.js +3 -0
- package/lib/inventory/protobom.js +62 -89
- package/lib/inventory/protobomLoader.js +4 -0
- package/lib/inventory/purl.js +152 -3
- package/lib/inventory/rustFormulationParser.js +1 -1
- package/lib/inventory/sbomDocument.js +2 -70
- package/lib/inventory/source.js +47 -23
- package/lib/inventory/stylePackageRefs.js +41 -0
- package/lib/inventory/svelteUtils.js +524 -0
- package/lib/inventory/traceRunner.js +210 -21
- package/lib/managers/binary.js +115 -24
- package/lib/managers/docker.js +440 -20
- package/lib/managers/oci.js +39 -1
- package/lib/parsers/cmakeLists.js +452 -0
- package/lib/parsers/huggingfaceUtils.js +15 -7
- package/lib/parsers/largeJson.js +454 -0
- package/lib/parsers/npmrc.js +19 -1
- package/lib/parsers/onnx.js +294 -0
- package/lib/parsers/safetensors.js +114 -0
- package/lib/stages/postgen/introspection/markers.js +29 -6
- package/lib/stages/postgen/postgen.js +237 -16
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
- package/lib/validator/bomValidator.js +6 -16
- package/lib/validator/index.js +48 -10
- package/lib/validator/reporters/console.js +3 -0
- package/lib/validator/reporters/json.js +1 -0
- package/lib/validator/reporters/sarif.js +7 -0
- package/package.json +27 -27
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +2 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +33 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +2 -2
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +23 -0
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts +33 -0
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +88 -8
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/activity.d.ts.map +1 -1
- package/types/lib/core/env.d.ts +21 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +55 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/metadataProperties.d.ts +39 -0
- package/types/lib/core/metadataProperties.d.ts.map +1 -0
- package/types/lib/core/projectConfig.d.ts +6 -2
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/userAgent.d.ts +56 -0
- package/types/lib/core/userAgent.d.ts.map +1 -0
- package/types/lib/ecosystems/bunutils.d.ts +12 -5
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
- package/types/lib/ecosystems/cargoCache.d.ts +75 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
- package/types/lib/ecosystems/caxa.d.ts +14 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cmakeResolver.d.ts +23 -0
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts +2 -2
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +15 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +40 -4
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +86 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -0
- package/types/lib/ecosystems/npmutils.d.ts +42 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
- package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-go.d.ts +10 -0
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +10 -4
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
- package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-misc.d.ts +90 -3
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
- package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
- package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
- package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-r.d.ts +35 -0
- package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
- package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
- package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +143 -2
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts +13 -0
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +81 -2
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +280 -15
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/helpers/agenticToolutils.d.ts +87 -0
- package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
- package/types/lib/helpers/bomSigner.d.ts +106 -13
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/aiInventory.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +145 -3
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +4 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/cdxrs.d.ts +8 -3
- package/types/lib/inventory/cdxrs.d.ts.map +1 -1
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts +15 -0
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +87 -1
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +34 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchRate.d.ts +9 -6
- package/types/lib/inventory/fetchRate.d.ts.map +1 -1
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +5 -4
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
- package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
- package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
- package/types/lib/inventory/mcpbBundle.d.ts +19 -0
- package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
- package/types/lib/inventory/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts +18 -13
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
- package/types/lib/inventory/purl.d.ts +71 -2
- package/types/lib/inventory/purl.d.ts.map +1 -1
- package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
- package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
- package/types/lib/inventory/svelteUtils.d.ts +124 -0
- package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
- package/types/lib/inventory/traceRunner.d.ts +47 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +30 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +29 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +14 -0
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/parsers/cmakeLists.d.ts +76 -0
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
- package/types/lib/parsers/largeJson.d.ts +52 -0
- package/types/lib/parsers/largeJson.d.ts.map +1 -0
- package/types/lib/parsers/npmrc.d.ts +5 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- package/types/lib/parsers/onnx.d.ts +28 -0
- package/types/lib/parsers/onnx.d.ts.map +1 -0
- package/types/lib/parsers/safetensors.d.ts +22 -0
- package/types/lib/parsers/safetensors.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts +3 -2
- package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +33 -0
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +10 -1
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
|
@@ -20,6 +20,17 @@ try {
|
|
|
20
20
|
SaferExec = undefined;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Whether the @cdxgen/safer-exec tracing runtime could be loaded. When false,
|
|
25
|
+
* dynamic tracing cannot run at all, and every trace would otherwise exit 0
|
|
26
|
+
* with a valid but empty BOM, hiding the broken installation (#4378).
|
|
27
|
+
*
|
|
28
|
+
* @returns {boolean} True when the tracing runtime is available
|
|
29
|
+
*/
|
|
30
|
+
export function isSaferExecAvailable() {
|
|
31
|
+
return Boolean(SaferExec);
|
|
32
|
+
}
|
|
33
|
+
|
|
23
34
|
const __filename = fileURLToPath(import.meta.url);
|
|
24
35
|
const __dirname = dirname(__filename);
|
|
25
36
|
|
|
@@ -105,8 +116,10 @@ export function resolveSaferExecBinary() {
|
|
|
105
116
|
}
|
|
106
117
|
|
|
107
118
|
for (const modulesDir of searchDirs) {
|
|
108
|
-
// Direct structure under node_modules
|
|
109
|
-
|
|
119
|
+
// Direct structure under node_modules. The Go runtime inside the
|
|
120
|
+
// platform package is bin/safer-exec-rt; "safer-exec" is the JS
|
|
121
|
+
// dispatcher's npm bin name, not a file on disk.
|
|
122
|
+
const directPath = join(modulesDir, pkgName, "bin", "safer-exec-rt");
|
|
110
123
|
let realDirectPath;
|
|
111
124
|
try {
|
|
112
125
|
realDirectPath = realpathSync(directPath);
|
|
@@ -157,7 +170,19 @@ export function resolveSaferExecBinary() {
|
|
|
157
170
|
* @param {boolean} [options.traceExec] - Log every child process spawned
|
|
158
171
|
* @param {string[]} [options.allowExec] - Executables the command is allowed to run
|
|
159
172
|
* @param {string[]} [options.blockExec] - Executables to block from running
|
|
160
|
-
* @
|
|
173
|
+
* @param {boolean} [options.proxyEgress] - Route egress through safer-exec's hostname-pinning proxy (network stays enabled; only allowHosts are reachable)
|
|
174
|
+
* @param {boolean} [options.allowLoopback] - Allow loopback connections
|
|
175
|
+
* @param {boolean} [options.dryRun] - Deny every filesystem and network side effect and collect a dry-run report
|
|
176
|
+
* @param {string} [options.policy] - Named safer-exec ecosystem policy (npm, pypi, maven, ...)
|
|
177
|
+
* @param {string} [options.policyFile] - Path to a safer-exec JSON policy file
|
|
178
|
+
* @param {boolean} [options.blockInterpreters] - Block interpreters with sandbox or task-port exemptions (macOS)
|
|
179
|
+
* @param {boolean} [options.denyPersistenceWrites] - Deny writes to auto-execution and persistence locations
|
|
180
|
+
* @param {boolean} [options.privateTmp] - Private tmpfs /tmp and /var/tmp (Linux)
|
|
181
|
+
* @param {string} [options.protectHome] - Home isolation: read-only, tmpfs, or off (Linux)
|
|
182
|
+
* @returns {Promise<{libPaths: string[], httpAccessEntries: Object[], egressEntries?: Object[], dryRunSummary?: Object}>} Collected libraries, HTTP URLs, proxy egress decisions, and the dry-run summary
|
|
183
|
+
* @throws {Error} When the @cdxgen/safer-exec tracing runtime is not installed
|
|
184
|
+
* (for example after pruning it from a standalone payload), since the traced
|
|
185
|
+
* command could never run and the BOM would silently be empty
|
|
161
186
|
*/
|
|
162
187
|
export async function executeAndTrace(commandStr, workingDir, options = {}) {
|
|
163
188
|
const emptyResult = { libPaths: [], httpAccessEntries: [] };
|
|
@@ -174,11 +199,23 @@ export async function executeAndTrace(commandStr, workingDir, options = {}) {
|
|
|
174
199
|
);
|
|
175
200
|
|
|
176
201
|
if (!SaferExec) {
|
|
177
|
-
|
|
202
|
+
throw new Error(
|
|
203
|
+
"Dynamic tracing requires the @cdxgen/safer-exec package, which could not be loaded from this installation. Re-install with optional dependencies enabled, or use the official tracebom standalone binary.",
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const exec = new SaferExec();
|
|
208
|
+
// Policies are applied first so explicit options below take precedence, and
|
|
209
|
+
// outside the try so an unknown policy or unreadable policy file fails the
|
|
210
|
+
// run instead of producing an empty BOM.
|
|
211
|
+
if (options.policy) {
|
|
212
|
+
exec.applyPolicy(options.policy);
|
|
213
|
+
}
|
|
214
|
+
if (options.policyFile) {
|
|
215
|
+
exec.applyPolicyFile(options.policyFile);
|
|
178
216
|
}
|
|
179
217
|
|
|
180
218
|
try {
|
|
181
|
-
const exec = new SaferExec();
|
|
182
219
|
if (workingDir) {
|
|
183
220
|
exec.workingDir(workingDir);
|
|
184
221
|
}
|
|
@@ -192,9 +229,41 @@ export async function executeAndTrace(commandStr, workingDir, options = {}) {
|
|
|
192
229
|
exec.traceLibraries().suppressLibLoadStderr(true).enableAudit();
|
|
193
230
|
|
|
194
231
|
// Apply sandbox options
|
|
195
|
-
|
|
232
|
+
// The egress proxy runs on the host side and must be reachable, so it
|
|
233
|
+
// needs the network enabled (an isolated namespace would bypass it).
|
|
234
|
+
if (
|
|
235
|
+
options.disableNetwork !== false &&
|
|
236
|
+
!options.traceHTTPURLs &&
|
|
237
|
+
!options.proxyEgress
|
|
238
|
+
) {
|
|
196
239
|
exec.disableNetwork();
|
|
197
240
|
}
|
|
241
|
+
if (options.proxyEgress) {
|
|
242
|
+
if (!options.allowHosts?.length && !options.allowUrls?.length) {
|
|
243
|
+
console.warn(
|
|
244
|
+
"[cdxgen trace] egress proxy enabled without --allow-host: every outbound connection will be denied.",
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
exec.proxyEgress();
|
|
248
|
+
}
|
|
249
|
+
if (options.allowLoopback) {
|
|
250
|
+
exec.allowLoopback();
|
|
251
|
+
}
|
|
252
|
+
if (options.dryRun) {
|
|
253
|
+
exec.enableDryRun();
|
|
254
|
+
}
|
|
255
|
+
if (options.blockInterpreters) {
|
|
256
|
+
exec.blockInterpreters();
|
|
257
|
+
}
|
|
258
|
+
if (options.denyPersistenceWrites) {
|
|
259
|
+
exec.denyPersistenceWrites();
|
|
260
|
+
}
|
|
261
|
+
if (options.privateTmp) {
|
|
262
|
+
exec.privateTmp();
|
|
263
|
+
}
|
|
264
|
+
if (options.protectHome && options.protectHome !== "off") {
|
|
265
|
+
exec.protectHome(options.protectHome);
|
|
266
|
+
}
|
|
198
267
|
if (options.readPaths?.length) {
|
|
199
268
|
exec.readPaths(options.readPaths);
|
|
200
269
|
}
|
|
@@ -306,25 +375,53 @@ export async function executeAndTrace(commandStr, workingDir, options = {}) {
|
|
|
306
375
|
}
|
|
307
376
|
|
|
308
377
|
const result = await exec.run(cmd, args);
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
378
|
+
const stderr = result?.stderr || "";
|
|
379
|
+
if (result && result.exitCode !== 0) {
|
|
380
|
+
if (stderr.includes("[safer-exec] Error:")) {
|
|
381
|
+
console.error("Tracing launcher execution failed:", stderr.trim());
|
|
382
|
+
} else {
|
|
383
|
+
// A failing command usually means a partial trace (for example a
|
|
384
|
+
// sandbox denial), so the BOM may be incomplete.
|
|
385
|
+
console.warn(
|
|
386
|
+
`[cdxgen trace] traced command exited with code ${result.exitCode}; the BOM may be incomplete.`,
|
|
314
387
|
);
|
|
315
388
|
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
|
|
389
|
+
}
|
|
390
|
+
if (options.traceHTTPURLs) {
|
|
391
|
+
// eBPF URL tracing needs CAP_BPF/CAP_PERFMON. Surface why it was not
|
|
392
|
+
// available whatever the command's exit code, since the BOM otherwise
|
|
393
|
+
// silently lacks services.
|
|
394
|
+
const httpLines = stderr
|
|
395
|
+
.split("\n")
|
|
396
|
+
.filter(
|
|
397
|
+
(line) =>
|
|
398
|
+
line.includes("http-trace") ||
|
|
399
|
+
line.includes("httptrace") ||
|
|
400
|
+
line.includes("SSL/TLS libraries"),
|
|
401
|
+
);
|
|
319
402
|
if (
|
|
320
|
-
|
|
321
|
-
stderr.includes("httptrace") ||
|
|
322
|
-
stderr.includes("SSL/TLS libraries")
|
|
403
|
+
httpLines.some((line) => /warning|not supported|failed/i.test(line))
|
|
323
404
|
) {
|
|
324
|
-
console.warn(
|
|
405
|
+
console.warn(
|
|
406
|
+
"[cdxgen trace] HTTP URL tracing unavailable or degraded (it needs root or CAP_BPF and CAP_PERFMON):",
|
|
407
|
+
httpLines.join("\n"),
|
|
408
|
+
);
|
|
325
409
|
}
|
|
326
410
|
}
|
|
327
411
|
|
|
412
|
+
// Without an event source (strace on Linux) a dry run still denies every
|
|
413
|
+
// side effect but reports nothing, which must not read as "no activity".
|
|
414
|
+
let dryRunSummary = result?.dryRun?.summary;
|
|
415
|
+
if (
|
|
416
|
+
options.dryRun &&
|
|
417
|
+
result?.stderr?.includes("dry-run cannot capture attempted operations")
|
|
418
|
+
) {
|
|
419
|
+
console.warn(
|
|
420
|
+
"[cdxgen trace] dry run could not capture attempted operations on this host (install strace or relax ptrace_scope); the BOM records no counts.",
|
|
421
|
+
);
|
|
422
|
+
dryRunSummary = { captured: false };
|
|
423
|
+
}
|
|
424
|
+
|
|
328
425
|
// Read and parse CBOM file if temp/explicit path was used
|
|
329
426
|
let cryptoComponents = [];
|
|
330
427
|
const cbomPathToRead = options.cbom || tempCbomPath;
|
|
@@ -377,6 +474,8 @@ export async function executeAndTrace(commandStr, workingDir, options = {}) {
|
|
|
377
474
|
httpAccessEntries: [...collectedUrls, ...urls],
|
|
378
475
|
cryptoEntries: [...collectedCrypto, ...cryptoLogs],
|
|
379
476
|
cryptoComponents,
|
|
477
|
+
egressEntries: parseEgressEntries(result.auditLog),
|
|
478
|
+
dryRunSummary,
|
|
380
479
|
};
|
|
381
480
|
}
|
|
382
481
|
|
|
@@ -385,6 +484,8 @@ export async function executeAndTrace(commandStr, workingDir, options = {}) {
|
|
|
385
484
|
httpAccessEntries: collectedUrls,
|
|
386
485
|
cryptoEntries: collectedCrypto,
|
|
387
486
|
cryptoComponents,
|
|
487
|
+
egressEntries: [],
|
|
488
|
+
dryRunSummary,
|
|
388
489
|
};
|
|
389
490
|
} catch (err) {
|
|
390
491
|
console.error("Tracing command execution failed:", err);
|
|
@@ -392,6 +493,86 @@ export async function executeAndTrace(commandStr, workingDir, options = {}) {
|
|
|
392
493
|
return emptyResult;
|
|
393
494
|
}
|
|
394
495
|
|
|
496
|
+
/**
|
|
497
|
+
* Returns the sorted, de-duplicated parameter names of a query string.
|
|
498
|
+
*
|
|
499
|
+
* @param {string} query - Raw query string, with or without a leading "?"
|
|
500
|
+
* @returns {string} Comma-separated parameter names, or "" when there are none
|
|
501
|
+
*/
|
|
502
|
+
function queryParameterNames(query) {
|
|
503
|
+
if (!query) {
|
|
504
|
+
return "";
|
|
505
|
+
}
|
|
506
|
+
const names = new Set();
|
|
507
|
+
for (const pair of String(query).replace(/^\?/, "").split("&")) {
|
|
508
|
+
const eqIdx = pair.indexOf("=");
|
|
509
|
+
const name = (eqIdx >= 0 ? pair.substring(0, eqIdx) : pair).trim();
|
|
510
|
+
if (name) {
|
|
511
|
+
names.add(name);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return Array.from(names).sort().join(",");
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Extracts the egress proxy's decisions from a safer-exec audit log.
|
|
519
|
+
* "proxy-connect" targets are "host:port"; "proxy-violation" targets are
|
|
520
|
+
* "CONNECT host:port" or "METHOD http://host[:port]/path". Only the authority
|
|
521
|
+
* is kept, never a path or query.
|
|
522
|
+
*
|
|
523
|
+
* @param {Object[]} auditLog - safer-exec audit entries
|
|
524
|
+
* @returns {{host: string, port: number, protocol: string, decision: string}[]} Unique egress decisions
|
|
525
|
+
*/
|
|
526
|
+
export function parseEgressEntries(auditLog) {
|
|
527
|
+
const seen = new Set();
|
|
528
|
+
const entries = [];
|
|
529
|
+
for (const e of auditLog || []) {
|
|
530
|
+
if (e?.type !== "proxy-connect" && e?.type !== "proxy-violation") {
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
const target = String(e.target || "");
|
|
534
|
+
let authority = target;
|
|
535
|
+
let protocol = "https";
|
|
536
|
+
if (e.type === "proxy-connect") {
|
|
537
|
+
protocol = e.details === "http" ? "http" : "https";
|
|
538
|
+
} else {
|
|
539
|
+
const spaceIdx = target.indexOf(" ");
|
|
540
|
+
authority = spaceIdx >= 0 ? target.substring(spaceIdx + 1) : target;
|
|
541
|
+
const schemeIdx = authority.indexOf("://");
|
|
542
|
+
if (schemeIdx >= 0) {
|
|
543
|
+
protocol = authority.substring(0, schemeIdx);
|
|
544
|
+
authority = authority.substring(schemeIdx + 3);
|
|
545
|
+
const slashIdx = authority.indexOf("/");
|
|
546
|
+
if (slashIdx >= 0) {
|
|
547
|
+
authority = authority.substring(0, slashIdx);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
let host = authority;
|
|
552
|
+
let port = protocol === "http" ? 80 : 443;
|
|
553
|
+
const colonIdx = authority.lastIndexOf(":");
|
|
554
|
+
if (colonIdx > 0 && !authority.endsWith("]")) {
|
|
555
|
+
const parsedPort = Number.parseInt(authority.substring(colonIdx + 1), 10);
|
|
556
|
+
if (!Number.isNaN(parsedPort)) {
|
|
557
|
+
port = parsedPort;
|
|
558
|
+
host = authority.substring(0, colonIdx);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
host = host.replace(/^\[|\]$/g, "").toLowerCase();
|
|
562
|
+
if (!host) {
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
const decision = e.type === "proxy-connect" ? "allowed" : "denied";
|
|
566
|
+
const key = `${decision}|${protocol}|${host}|${port}`;
|
|
567
|
+
if (seen.has(key)) {
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
seen.add(key);
|
|
571
|
+
entries.push({ host, port, protocol, decision });
|
|
572
|
+
}
|
|
573
|
+
return entries;
|
|
574
|
+
}
|
|
575
|
+
|
|
395
576
|
/**
|
|
396
577
|
* Groups HTTP access entries into a CycloneDX services-ready map.
|
|
397
578
|
* Each unique (host, port, protocol) combination becomes a service.
|
|
@@ -409,7 +590,9 @@ export function groupHttpEntriesToServices(httpAccessEntries) {
|
|
|
409
590
|
properties: [],
|
|
410
591
|
};
|
|
411
592
|
}
|
|
412
|
-
const
|
|
593
|
+
const scheme = entry.protocol === "http" ? "http" : "https";
|
|
594
|
+
const defaultPort = scheme === "http" ? 80 : 443;
|
|
595
|
+
const endpoint = `${scheme}://${entry.host}${entry.port && entry.port !== defaultPort ? `:${entry.port}` : ""}${entry.path || "/"}`;
|
|
413
596
|
servicesMap[serviceName].endpoints.add(endpoint);
|
|
414
597
|
if (entry.method) {
|
|
415
598
|
const methodProp = {
|
|
@@ -424,8 +607,14 @@ export function groupHttpEntriesToServices(httpAccessEntries) {
|
|
|
424
607
|
servicesMap[serviceName].properties.push(methodProp);
|
|
425
608
|
}
|
|
426
609
|
}
|
|
427
|
-
|
|
428
|
-
|
|
610
|
+
// Query values can carry tokens and signatures, so only the parameter
|
|
611
|
+
// names are recorded.
|
|
612
|
+
const queryParams = queryParameterNames(entry.query);
|
|
613
|
+
if (queryParams) {
|
|
614
|
+
const queryProp = {
|
|
615
|
+
name: "cdx:dynamic:httpQueryParams",
|
|
616
|
+
value: queryParams,
|
|
617
|
+
};
|
|
429
618
|
if (
|
|
430
619
|
!servicesMap[serviceName].properties.some(
|
|
431
620
|
(p) => p.name === queryProp.name && p.value === queryProp.value,
|
package/lib/managers/binary.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from "node:fs";
|
|
8
8
|
import { platform as _platform, homedir } from "node:os";
|
|
9
9
|
import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
|
10
|
+
import process from "node:process";
|
|
10
11
|
|
|
11
12
|
import { build, Purl } from "@cdxgen/cdx-purl";
|
|
12
13
|
|
|
@@ -54,7 +55,11 @@ import {
|
|
|
54
55
|
resolvePluginBinary,
|
|
55
56
|
setPluginsPathEnv,
|
|
56
57
|
} from "../inventory/plugins.js";
|
|
57
|
-
import {
|
|
58
|
+
import {
|
|
59
|
+
genericPurl,
|
|
60
|
+
parsePurlLoose,
|
|
61
|
+
tryBuildPurl,
|
|
62
|
+
} from "../inventory/purl.js";
|
|
58
63
|
import {
|
|
59
64
|
adjustLicenseInformation,
|
|
60
65
|
findLicenseId,
|
|
@@ -531,28 +536,89 @@ export function getCargoAuditableInfo(src) {
|
|
|
531
536
|
return undefined;
|
|
532
537
|
}
|
|
533
538
|
|
|
539
|
+
/**
|
|
540
|
+
* Check that sourcekitten (bundled plugin or SOURCEKITTEN_CMD) answers a
|
|
541
|
+
* trivial syntax request.
|
|
542
|
+
*
|
|
543
|
+
* @param env {Object} Optional environment overrides merged over process.env
|
|
544
|
+
* @returns {String} `ok`; `unavailable` when there is no bundled plugin and
|
|
545
|
+
* SOURCEKITTEN_CMD is unset; `not-found` when the configured executable does
|
|
546
|
+
* not exist; `failed` when it ran but could not answer
|
|
547
|
+
*/
|
|
548
|
+
export function probeSourcekitten(env) {
|
|
549
|
+
if (!SOURCEKITTEN_BIN) {
|
|
550
|
+
return "unavailable";
|
|
551
|
+
}
|
|
552
|
+
const result = safeSpawnSync(
|
|
553
|
+
SOURCEKITTEN_BIN,
|
|
554
|
+
["syntax", "--text", "import Swift"],
|
|
555
|
+
env ? { env: { ...process.env, ...env } } : undefined,
|
|
556
|
+
);
|
|
557
|
+
if (result?.error?.code === "ENOENT") {
|
|
558
|
+
return "not-found";
|
|
559
|
+
}
|
|
560
|
+
if (result?.status === 0 && result.stdout) {
|
|
561
|
+
try {
|
|
562
|
+
JSON.parse(result.stdout);
|
|
563
|
+
return "ok";
|
|
564
|
+
} catch (_e) {
|
|
565
|
+
// Plain-text errors on stdout are failures
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (DEBUG_MODE) {
|
|
569
|
+
console.log(
|
|
570
|
+
"sourcekitten syntax probe failed:",
|
|
571
|
+
`${result?.stderr || result?.error || ""}`
|
|
572
|
+
.split("\n")
|
|
573
|
+
.slice(0, 5)
|
|
574
|
+
.map((l) => l.slice(0, 400))
|
|
575
|
+
.join("\n"),
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
return "failed";
|
|
579
|
+
}
|
|
580
|
+
|
|
534
581
|
/**
|
|
535
582
|
* Execute sourcekitten plugin with the given arguments
|
|
536
583
|
*
|
|
537
584
|
* @param args {Array} Arguments
|
|
585
|
+
* @param env {Object} Optional environment overrides merged over process.env,
|
|
586
|
+
* used to align sourcekitten with a specific Swift toolchain
|
|
538
587
|
* @returns {undefined|Object} Command output
|
|
539
588
|
*/
|
|
540
|
-
export function executeSourcekitten(args) {
|
|
541
|
-
if (SOURCEKITTEN_BIN) {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
589
|
+
export function executeSourcekitten(args, env) {
|
|
590
|
+
if (!SOURCEKITTEN_BIN) {
|
|
591
|
+
return undefined;
|
|
592
|
+
}
|
|
593
|
+
const result = safeSpawnSync(
|
|
594
|
+
SOURCEKITTEN_BIN,
|
|
595
|
+
args,
|
|
596
|
+
env ? { env: { ...process.env, ...env } } : undefined,
|
|
597
|
+
);
|
|
598
|
+
if ((result.status !== 0 || result.error) && DEBUG_MODE) {
|
|
599
|
+
// Failures echo whole clang invocations, so only the first diagnostic
|
|
600
|
+
// lines are worth showing
|
|
601
|
+
const diagnostics = `${result.stderr || ""}`.split("\n").slice(0, 5);
|
|
602
|
+
console.log(
|
|
603
|
+
`sourcekitten ${args?.[0]} failed:`,
|
|
604
|
+
diagnostics.map((l) => l.slice(0, 400)).join("\n"),
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
if (!result?.stdout) {
|
|
608
|
+
return undefined;
|
|
609
|
+
}
|
|
610
|
+
try {
|
|
611
|
+
return JSON.parse(result.stdout);
|
|
612
|
+
} catch (_e) {
|
|
613
|
+
// Oversized outputs are truncated at the spawn buffer limit, and
|
|
614
|
+
// sourcekitten prints plain-text errors to stdout on some failures
|
|
615
|
+
if (DEBUG_MODE) {
|
|
616
|
+
console.log(
|
|
617
|
+
`Ignoring unparseable sourcekitten ${args?.[0]} output of ${result.stdout.length} bytes.`,
|
|
618
|
+
);
|
|
553
619
|
}
|
|
620
|
+
return undefined;
|
|
554
621
|
}
|
|
555
|
-
return undefined;
|
|
556
622
|
}
|
|
557
623
|
|
|
558
624
|
/**
|
|
@@ -796,8 +862,16 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
796
862
|
);
|
|
797
863
|
}
|
|
798
864
|
}
|
|
799
|
-
|
|
800
|
-
|
|
865
|
+
// trivy-cdxgen only scans a root filesystem; it has no `image` command. The
|
|
866
|
+
// caller hands over the exported image directory, so a missing path means
|
|
867
|
+
// the export failed and there is nothing for trivy to scan.
|
|
868
|
+
const rootfsPresent = TRIVY_BIN ? safeExistsSync(src) : false;
|
|
869
|
+
if (TRIVY_BIN && !rootfsPresent) {
|
|
870
|
+
console.warn(
|
|
871
|
+
`Skipping the trivy OS package scan: the root filesystem ${src} does not exist.`,
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
if (TRIVY_BIN && rootfsPresent) {
|
|
801
875
|
const trivyCacheDir = join(homedir(), ".cache", "trivy");
|
|
802
876
|
try {
|
|
803
877
|
safeMkdirSync(join(trivyCacheDir, "db"), { recursive: true });
|
|
@@ -805,13 +879,10 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
805
879
|
} catch (_err) {
|
|
806
880
|
// ignore errors
|
|
807
881
|
}
|
|
808
|
-
if (safeExistsSync(src)) {
|
|
809
|
-
imageType = "rootfs";
|
|
810
|
-
}
|
|
811
882
|
const tempDir = safeMkdtempSync(join(getTmpDir(), "trivy-cdxgen-"));
|
|
812
883
|
const bomJsonFile = join(tempDir, "trivy-bom.json");
|
|
813
884
|
const args = [
|
|
814
|
-
|
|
885
|
+
"rootfs",
|
|
815
886
|
"--cache-dir",
|
|
816
887
|
trivyCacheDir,
|
|
817
888
|
"--output",
|
|
@@ -932,7 +1003,7 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
932
1003
|
? tmpBom.metadata.tools
|
|
933
1004
|
: tmpBom?.metadata?.tools?.components || []
|
|
934
1005
|
).filter((tool) => tool?.["bom-ref"] && tool?.name !== "cdxgen"),
|
|
935
|
-
["trivy"
|
|
1006
|
+
["trivy", "trustinspector"],
|
|
936
1007
|
);
|
|
937
1008
|
const toolRefs = extractToolRefs(
|
|
938
1009
|
{ components: tools },
|
|
@@ -2977,7 +3048,20 @@ function detectSdksRuntimes(comp, bundledSdks, bundledRuntimes) {
|
|
|
2977
3048
|
}
|
|
2978
3049
|
}
|
|
2979
3050
|
|
|
2980
|
-
|
|
3051
|
+
/**
|
|
3052
|
+
* Collect the dependency edges trivy recorded for one OS package component.
|
|
3053
|
+
*
|
|
3054
|
+
* Trivy keys its `dependencies` by its own bom-refs, which may leave the `+` of
|
|
3055
|
+
* an rpm or deb version unescaped (`...libstdc%2B%2B6@14.2.0+git10526-...`), so
|
|
3056
|
+
* each ref is read structurally and re-rendered in cdxgen's decoded bom-ref
|
|
3057
|
+
* form with the component's type, namespace and distro qualifiers.
|
|
3058
|
+
*
|
|
3059
|
+
* @param {Object<string, string[]>} tmpDependencies Trivy's refs mapped to the refs they depend on
|
|
3060
|
+
* @param {string} origBomRef The component's bom-ref as trivy wrote it
|
|
3061
|
+
* @param {object} comp The component after cdxgen rewrote its purl and bom-ref
|
|
3062
|
+
* @returns {{ref: string, dependsOn: string[]}|undefined} Dependency entry for the component
|
|
3063
|
+
*/
|
|
3064
|
+
export const retrieveDependencies = (tmpDependencies, origBomRef, comp) => {
|
|
2981
3065
|
try {
|
|
2982
3066
|
const tmpDependsOn =
|
|
2983
3067
|
tmpDependencies[origBomRef] || tmpDependencies[comp["bom-ref"]] || [];
|
|
@@ -2985,7 +3069,14 @@ const retrieveDependencies = (tmpDependencies, origBomRef, comp) => {
|
|
|
2985
3069
|
tmpDependsOn.forEach((d) => {
|
|
2986
3070
|
try {
|
|
2987
3071
|
const compPurl = Purl.parse(comp.purl);
|
|
2988
|
-
|
|
3072
|
+
// Trivy's dependency refs are its bom-refs, which can leave the `+` of
|
|
3073
|
+
// an rpm or deb version unescaped; read them structurally so the edge
|
|
3074
|
+
// survives.
|
|
3075
|
+
const depParts = parsePurlLoose(d.replace("none", compPurl.type));
|
|
3076
|
+
if (!depParts) {
|
|
3077
|
+
return;
|
|
3078
|
+
}
|
|
3079
|
+
const tmpPurl = new Purl(depParts);
|
|
2989
3080
|
tmpPurl.type = compPurl.type;
|
|
2990
3081
|
// FIXME: Check if this hack is still needed with the latest trivy
|
|
2991
3082
|
if (OS_PURL_TYPES.includes(compPurl.type)) {
|