@cdxgen/cdxgen 13.0.1 → 13.2.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 +30 -16
- package/bin/audit.js +6 -0
- package/bin/cdxgen.js +141 -7
- package/bin/evinse.js +3 -1
- package/data/component-tags.json +8 -1
- package/data/crypto-oid.json +88 -12
- package/data/frameworks-list.json +86 -1
- package/data/remediations.json +1167 -0
- package/data/rules/ai-bom.yaml +78 -0
- package/data/rules/asar-archives.yaml +1 -1
- package/data/rules/build-fidelity.yaml +782 -0
- package/data/rules/cargo-rust.yaml +132 -0
- package/data/rules/mcp-servers.yaml +122 -0
- package/data/rules/package-integrity.yaml +13 -13
- package/lib/audit/index.js +48 -4
- package/lib/audit/scoring.js +2 -8
- package/lib/audit/targets.js +6 -1
- package/lib/cli/bomAssembly.js +32 -1
- package/lib/cli/cliOptions.js +94 -7
- package/lib/cli/index.js +268 -49
- package/lib/cli/jsBom.js +91 -5
- package/lib/cli/jvmBom.js +1515 -1314
- package/lib/cli/managedBom.js +249 -28
- package/lib/cli/nativeBom.js +986 -10
- package/lib/core/activity.js +120 -2
- package/lib/core/buildLedger.js +799 -0
- package/lib/core/deferredExit.js +141 -0
- package/lib/core/env.js +108 -2
- package/lib/core/fs.js +60 -0
- package/lib/core/logger.js +12 -99
- package/lib/core/severity.js +16 -0
- package/lib/core/syncFileWriter.js +106 -0
- package/lib/core/userAgent.js +151 -0
- package/lib/ecosystems/cargoCache.js +364 -0
- package/lib/ecosystems/caxa.js +13 -5
- package/lib/ecosystems/cppEvidence.js +40 -2
- package/lib/ecosystems/ecosystems.js +250 -91
- package/lib/ecosystems/golem.js +11 -1
- package/lib/ecosystems/gradleutils.js +498 -17
- package/lib/ecosystems/kosi.js +1256 -0
- package/lib/ecosystems/parsers-crystal.js +179 -0
- package/lib/ecosystems/parsers-dotnet.js +69 -39
- package/lib/ecosystems/parsers-elm.js +816 -0
- package/lib/ecosystems/parsers-gleam.js +1 -1
- package/lib/ecosystems/parsers-js.js +26 -0
- package/lib/ecosystems/parsers-julia.js +268 -0
- package/lib/ecosystems/parsers-jvm.js +9 -0
- package/lib/ecosystems/parsers-lua.js +218 -0
- package/lib/ecosystems/parsers-misc.js +188 -2
- 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 +172 -36
- package/lib/ecosystems/parsers-r.js +148 -0
- package/lib/ecosystems/parsers-rebar.js +411 -0
- package/lib/ecosystems/parsers-rust.js +944 -19
- 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/pypiClassifiers.js +326 -0
- package/lib/ecosystems/pythonutils.js +49 -1
- package/lib/ecosystems/rubyutils.js +13 -0
- package/lib/ecosystems/rusi.js +126 -6
- package/lib/evinser/evinser.js +141 -13
- package/lib/helpers/agenticToolutils.js +925 -0
- package/lib/helpers/remote/dependency-track.js +81 -14
- 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/auditCategories.js +17 -0
- package/lib/inventory/cbomutils.js +30 -0
- package/lib/inventory/cdxrs.js +7 -2
- package/lib/inventory/communityAiConfigParser.js +215 -2
- package/lib/inventory/depsUtils.js +72 -0
- package/lib/inventory/dosai.js +508 -22
- package/lib/inventory/envcontext.js +215 -24
- package/lib/inventory/evidenceUtils.js +13 -0
- package/lib/inventory/fetchBatch.js +131 -2
- package/lib/inventory/fetchRate.js +74 -11
- package/lib/inventory/forgeEnricher.js +3 -2
- package/lib/inventory/jvmToolEnv.js +594 -0
- 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/osinfo.js +1 -0
- package/lib/inventory/plugins.js +3 -0
- package/lib/inventory/protobom.js +54 -88
- package/lib/inventory/protobomLoader.js +4 -0
- package/lib/inventory/rustFormulationParser.js +1 -1
- package/lib/inventory/source.js +10 -4
- package/lib/inventory/stylePackageRefs.js +41 -0
- package/lib/inventory/svelteUtils.js +524 -0
- package/lib/inventory/toolRequirements.js +903 -0
- package/lib/managers/binary.js +24 -0
- package/lib/managers/docker.js +26 -5
- package/lib/parsers/huggingfaceUtils.js +15 -7
- package/lib/parsers/onnx.js +294 -0
- package/lib/parsers/safetensors.js +114 -0
- package/lib/server/server.js +1 -1
- package/lib/stages/postgen/annotationBuilder.js +97 -0
- package/lib/stages/postgen/auditBom.js +76 -79
- package/lib/stages/postgen/introspection/annotate.js +320 -0
- package/lib/stages/postgen/introspection/commandFacts.js +293 -0
- package/lib/stages/postgen/introspection/emit.js +192 -0
- package/lib/stages/postgen/introspection/formulationEvidence.js +427 -0
- package/lib/stages/postgen/introspection/markers.js +250 -0
- package/lib/stages/postgen/introspection/reflect.js +1214 -0
- package/lib/stages/postgen/introspection/report.js +1208 -0
- package/lib/stages/postgen/introspection/score.js +1067 -0
- package/lib/stages/postgen/introspection/shapeCommand.js +304 -0
- package/lib/stages/postgen/parentEdge.js +156 -0
- package/lib/stages/postgen/postgen.js +284 -3
- package/lib/stages/postgen/ruleEngine.js +17 -0
- package/lib/stages/pregen/envAudit.js +42 -2
- package/lib/stages/pregen/pregen.js +1171 -6
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
- package/lib/validator/bomValidator.js +11 -2
- package/package.json +26 -26
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/audit/scoring.d.ts +2 -8
- package/types/lib/audit/scoring.d.ts.map +1 -1
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +2 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +10 -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/buildLedger.d.ts +272 -0
- package/types/lib/core/buildLedger.d.ts.map +1 -0
- package/types/lib/core/deferredExit.d.ts +84 -0
- package/types/lib/core/deferredExit.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +43 -1
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +21 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/logger.d.ts +2 -11
- package/types/lib/core/logger.d.ts.map +1 -1
- package/types/lib/core/severity.d.ts +11 -0
- package/types/lib/core/severity.d.ts.map +1 -0
- package/types/lib/core/syncFileWriter.d.ts +33 -0
- package/types/lib/core/syncFileWriter.d.ts.map +1 -0
- package/types/lib/core/userAgent.d.ts +56 -0
- package/types/lib/core/userAgent.d.ts.map +1 -0
- 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.map +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
- 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/gradleutils.d.ts +53 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +73 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -0
- 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-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-jvm.d.ts.map +1 -1
- 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 +23 -0
- 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/pypiClassifiers.d.ts +80 -0
- package/types/lib/ecosystems/pypiClassifiers.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +21 -1
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +34 -1
- package/types/lib/evinser/evinser.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/remote/dependency-track.d.ts +45 -4
- package/types/lib/helpers/remote/dependency-track.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/auditCategories.d.ts +12 -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/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +43 -0
- package/types/lib/inventory/dosai.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 +43 -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 +190 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -0
- 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/osinfo.d.ts.map +1 -1
- 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/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/toolRequirements.d.ts +216 -0
- package/types/lib/inventory/toolRequirements.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
- package/types/lib/parsers/huggingfaceUtils.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/annotationBuilder.d.ts +53 -0
- package/types/lib/stages/postgen/annotationBuilder.d.ts.map +1 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/introspection/annotate.d.ts +81 -0
- package/types/lib/stages/postgen/introspection/annotate.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts +32 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts +71 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts +154 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts +58 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts +77 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/report.d.ts +85 -0
- package/types/lib/stages/postgen/introspection/report.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/score.d.ts +149 -0
- package/types/lib/stages/postgen/introspection/score.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts +79 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts.map +1 -0
- package/types/lib/stages/postgen/parentEdge.d.ts +35 -0
- package/types/lib/stages/postgen/parentEdge.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +35 -2
- 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/stages/pregen/pregen.d.ts +131 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
|
@@ -10,6 +10,14 @@ import {
|
|
|
10
10
|
isSecureMode,
|
|
11
11
|
readEnvironmentVariable,
|
|
12
12
|
} from "../core/activity.js";
|
|
13
|
+
import {
|
|
14
|
+
commandOutputText,
|
|
15
|
+
isLedgerEnabled,
|
|
16
|
+
LEDGER_EVENT_IMPACTS,
|
|
17
|
+
LEDGER_EVENT_KINDS,
|
|
18
|
+
recordDegradation,
|
|
19
|
+
recordLedgerEvent,
|
|
20
|
+
} from "../core/buildLedger.js";
|
|
13
21
|
import {
|
|
14
22
|
CARGO_CMD,
|
|
15
23
|
DOTNET_CMD,
|
|
@@ -33,7 +41,12 @@ import {
|
|
|
33
41
|
} from "../core/fs.js";
|
|
34
42
|
import { diagnosticStream } from "../core/logger.js";
|
|
35
43
|
import { dirNameStr, isMac, isWin } from "../core/paths.js";
|
|
44
|
+
import { isValidSdkmanVersion } from "./jvmToolEnv.js";
|
|
36
45
|
import { applyPurl, tryBuildPurl } from "./purl.js";
|
|
46
|
+
import {
|
|
47
|
+
classifySpawnRestriction,
|
|
48
|
+
describeSpawnRestriction,
|
|
49
|
+
} from "./toolRequirements.js";
|
|
37
50
|
|
|
38
51
|
/**
|
|
39
52
|
* Git executable name used for all git invocations, overridable via GIT_CMD.
|
|
@@ -88,15 +101,18 @@ function hardenedGitEnv() {
|
|
|
88
101
|
* @type {Object<string, string>}
|
|
89
102
|
*/
|
|
90
103
|
export const SDKMAN_JAVA_TOOL_ALIASES = {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
104
|
+
// Identifiers must be downloadable from the sdkman broker: the `sdk list`
|
|
105
|
+
// output advertises Temurin respin ids such as 21.0.12+1.1-tem, but the
|
|
106
|
+
// broker cannot serve ids containing '+', so the plain ids are pinned.
|
|
107
|
+
java8: readEnvironmentVariable("JAVA8_TOOL") || "8.0.504-zulu", // Temurin no longer offers java8 :(
|
|
108
|
+
java11: readEnvironmentVariable("JAVA11_TOOL") || "11.0.32-tem",
|
|
109
|
+
java17: readEnvironmentVariable("JAVA17_TOOL") || "17.0.20-tem",
|
|
110
|
+
java21: readEnvironmentVariable("JAVA21_TOOL") || "21.0.12-tem",
|
|
95
111
|
java22: readEnvironmentVariable("JAVA22_TOOL") || "22.0.2-tem",
|
|
96
112
|
java23: readEnvironmentVariable("JAVA23_TOOL") || "23.0.2-tem",
|
|
97
113
|
java24: readEnvironmentVariable("JAVA24_TOOL") || "24.0.2-tem",
|
|
98
|
-
java25: readEnvironmentVariable("JAVA25_TOOL") || "25.0.
|
|
99
|
-
java26: readEnvironmentVariable("JAVA26_TOOL") || "26.0.
|
|
114
|
+
java25: readEnvironmentVariable("JAVA25_TOOL") || "25.0.4-tem",
|
|
115
|
+
java26: readEnvironmentVariable("JAVA26_TOOL") || "26.0.2-tem",
|
|
100
116
|
};
|
|
101
117
|
|
|
102
118
|
/**
|
|
@@ -205,12 +221,17 @@ export function execGitCommand(dir, args) {
|
|
|
205
221
|
// Prepend hardening `-c` overrides before the subcommand and run git with a
|
|
206
222
|
// hardened environment so a scanned/untrusted repo cannot execute hooks,
|
|
207
223
|
// fsmonitor commands, or config-driven scripts.
|
|
224
|
+
// git is a real executable, so Windows can spawn it without a shell. Doing
|
|
225
|
+
// so keeps the pretty-format arguments intact: `%H`, `%n` and `%x1e` are
|
|
226
|
+
// cmd.exe variable-expansion syntax, which the shell-execution guard rightly
|
|
227
|
+
// refuses to pass through a shell.
|
|
208
228
|
return getCommandOutput(
|
|
209
229
|
GIT_COMMAND,
|
|
210
230
|
dir,
|
|
211
231
|
[...GIT_HARDENING_CONFIG_ARGS, ...args],
|
|
212
232
|
{
|
|
213
233
|
env: hardenedGitEnv(),
|
|
234
|
+
shell: false,
|
|
214
235
|
},
|
|
215
236
|
);
|
|
216
237
|
}
|
|
@@ -292,6 +313,60 @@ export function gitLogTrailers(dir, maxCount = 20) {
|
|
|
292
313
|
return commits;
|
|
293
314
|
}
|
|
294
315
|
|
|
316
|
+
/**
|
|
317
|
+
* Record a successful version probe with the version the tool reported.
|
|
318
|
+
*
|
|
319
|
+
* @param {string} tool Tool name
|
|
320
|
+
* @param {string} ecosystem Ecosystem the tool belongs to
|
|
321
|
+
* @param {string} found Version string reported by the tool
|
|
322
|
+
*/
|
|
323
|
+
function recordToolProbeResolved(tool, ecosystem, found) {
|
|
324
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_RESOLVED, {
|
|
325
|
+
ecosystem,
|
|
326
|
+
tool,
|
|
327
|
+
found,
|
|
328
|
+
source: "PATH",
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Record a failed version probe. A probe the environment did not let run is
|
|
334
|
+
* degraded evidence — the tool may well be present — while only a probe that
|
|
335
|
+
* ran and found nothing counts as a missing tool.
|
|
336
|
+
*
|
|
337
|
+
* @param {string} tool Tool name
|
|
338
|
+
* @param {string} ecosystem Ecosystem the tool belongs to
|
|
339
|
+
* @param {string} command Probe command that failed
|
|
340
|
+
*/
|
|
341
|
+
function recordToolProbeFailure(tool, ecosystem, command) {
|
|
342
|
+
if (!isLedgerEnabled()) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const restriction = describeSpawnRestriction(command);
|
|
346
|
+
if (restriction) {
|
|
347
|
+
// A dry-run block is the user's own request, so it costs nothing; any
|
|
348
|
+
// other policy restriction hides a version the tool may well have.
|
|
349
|
+
const isDryRunBlock = classifySpawnRestriction(command) === "dry-run";
|
|
350
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.EVIDENCE_DEGRADED, {
|
|
351
|
+
ecosystem,
|
|
352
|
+
tool,
|
|
353
|
+
source: "PATH",
|
|
354
|
+
remediationId: isDryRunBlock ? "policy.dry-run" : "policy.secure-mode",
|
|
355
|
+
impact: isDryRunBlock
|
|
356
|
+
? LEDGER_EVENT_IMPACTS.NONE
|
|
357
|
+
: LEDGER_EVENT_IMPACTS.VERSIONS,
|
|
358
|
+
detail: `The ${tool} version probe could not run: ${restriction}.`,
|
|
359
|
+
});
|
|
360
|
+
} else {
|
|
361
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_MISSING, {
|
|
362
|
+
ecosystem,
|
|
363
|
+
tool,
|
|
364
|
+
source: "PATH",
|
|
365
|
+
detail: `No ${tool} command answered the version probe.`,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
295
370
|
/**
|
|
296
371
|
* Collect Java version and installed modules
|
|
297
372
|
*
|
|
@@ -303,7 +378,7 @@ export function collectJavaInfo(dir) {
|
|
|
303
378
|
const moduleDesc =
|
|
304
379
|
getCommandOutput(getJavaCommand(), dir, ["--list-modules"]) || "";
|
|
305
380
|
if (versionDesc) {
|
|
306
|
-
|
|
381
|
+
const info = {
|
|
307
382
|
type: "platform",
|
|
308
383
|
name: "java",
|
|
309
384
|
version: versionDesc.split("\n")[0].replace("java ", ""),
|
|
@@ -315,7 +390,10 @@ export function collectJavaInfo(dir) {
|
|
|
315
390
|
},
|
|
316
391
|
],
|
|
317
392
|
};
|
|
393
|
+
recordToolProbeResolved("java", "java", info.version);
|
|
394
|
+
return info;
|
|
318
395
|
}
|
|
396
|
+
recordToolProbeFailure("java", "java", getJavaCommand());
|
|
319
397
|
return undefined;
|
|
320
398
|
}
|
|
321
399
|
|
|
@@ -330,13 +408,16 @@ export function collectDotnetInfo(dir) {
|
|
|
330
408
|
const moduleDesc =
|
|
331
409
|
getCommandOutput(DOTNET_CMD, dir, ["--list-runtimes"]) || "";
|
|
332
410
|
if (versionDesc) {
|
|
333
|
-
|
|
411
|
+
const info = {
|
|
334
412
|
type: "platform",
|
|
335
413
|
name: "dotnet",
|
|
336
414
|
version: versionDesc.trim(),
|
|
337
415
|
description: moduleDesc.replaceAll("\n", "\\n"),
|
|
338
416
|
};
|
|
417
|
+
recordToolProbeResolved("dotnet", "csharp", info.version);
|
|
418
|
+
return info;
|
|
339
419
|
}
|
|
420
|
+
recordToolProbeFailure("dotnet", "csharp", DOTNET_CMD);
|
|
340
421
|
return undefined;
|
|
341
422
|
}
|
|
342
423
|
|
|
@@ -347,25 +428,21 @@ export function collectDotnetInfo(dir) {
|
|
|
347
428
|
* @returns Object containing python details
|
|
348
429
|
*/
|
|
349
430
|
export function collectPythonInfo(dir) {
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
"--version",
|
|
353
|
-
]);
|
|
431
|
+
const pythonCmd = getPythonCommand();
|
|
432
|
+
const versionDesc = getCommandOutput(pythonCmd, dir, ["-S", "--version"]);
|
|
354
433
|
const moduleDesc =
|
|
355
|
-
getCommandOutput(
|
|
356
|
-
"-I",
|
|
357
|
-
"-m",
|
|
358
|
-
"pip",
|
|
359
|
-
"--version",
|
|
360
|
-
]) || "";
|
|
434
|
+
getCommandOutput(pythonCmd, dir, ["-I", "-m", "pip", "--version"]) || "";
|
|
361
435
|
if (versionDesc) {
|
|
362
|
-
|
|
436
|
+
const info = {
|
|
363
437
|
type: "platform",
|
|
364
438
|
name: "python",
|
|
365
439
|
version: versionDesc.replace("Python ", ""),
|
|
366
440
|
description: moduleDesc.replaceAll("\n", "\\n"),
|
|
367
441
|
};
|
|
442
|
+
recordToolProbeResolved("python", "python", info.version);
|
|
443
|
+
return info;
|
|
368
444
|
}
|
|
445
|
+
recordToolProbeFailure("python", "python", pythonCmd);
|
|
369
446
|
return undefined;
|
|
370
447
|
}
|
|
371
448
|
|
|
@@ -386,6 +463,9 @@ export function collectNodeInfo(dir) {
|
|
|
386
463
|
const moduleDesc = getCommandOutput(NPM_CMD, dir, ["--version"]);
|
|
387
464
|
if (moduleDesc) {
|
|
388
465
|
nodeInfo.description = `npm: ${moduleDesc}`;
|
|
466
|
+
recordToolProbeResolved("npm", "npm", moduleDesc);
|
|
467
|
+
} else {
|
|
468
|
+
recordToolProbeFailure("npm", "npm", NPM_CMD);
|
|
389
469
|
}
|
|
390
470
|
return nodeInfo;
|
|
391
471
|
}
|
|
@@ -400,13 +480,16 @@ export function collectGccInfo(dir) {
|
|
|
400
480
|
const versionDesc = getCommandOutput(GCC_CMD, dir, ["--version"]);
|
|
401
481
|
const moduleDesc = getCommandOutput(GCC_CMD, dir, ["-print-search-dirs"]);
|
|
402
482
|
if (versionDesc) {
|
|
403
|
-
|
|
483
|
+
const info = {
|
|
404
484
|
type: "platform",
|
|
405
485
|
name: "gcc",
|
|
406
486
|
version: versionDesc.split("\n")[0],
|
|
407
487
|
description: (moduleDesc || "").replaceAll("\n", "\\n"),
|
|
408
488
|
};
|
|
489
|
+
recordToolProbeResolved("gcc", "c", info.version);
|
|
490
|
+
return info;
|
|
409
491
|
}
|
|
492
|
+
recordToolProbeFailure("gcc", "c", GCC_CMD);
|
|
410
493
|
return undefined;
|
|
411
494
|
}
|
|
412
495
|
|
|
@@ -420,13 +503,16 @@ export function collectRustInfo(dir) {
|
|
|
420
503
|
const versionDesc = getCommandOutput(RUSTC_CMD, dir, ["--version"]);
|
|
421
504
|
const moduleDesc = getCommandOutput(CARGO_CMD, dir, ["--version"]);
|
|
422
505
|
if (versionDesc) {
|
|
423
|
-
|
|
506
|
+
const info = {
|
|
424
507
|
type: "platform",
|
|
425
508
|
name: "rustc",
|
|
426
509
|
version: versionDesc.trim(),
|
|
427
510
|
description: (moduleDesc || "").trim(),
|
|
428
511
|
};
|
|
512
|
+
recordToolProbeResolved("rustc", "rust", info.version);
|
|
513
|
+
return info;
|
|
429
514
|
}
|
|
515
|
+
recordToolProbeFailure("rustc", "rust", RUSTC_CMD);
|
|
430
516
|
return undefined;
|
|
431
517
|
}
|
|
432
518
|
|
|
@@ -439,12 +525,15 @@ export function collectRustInfo(dir) {
|
|
|
439
525
|
export function collectGoInfo(dir) {
|
|
440
526
|
const versionDesc = getCommandOutput(GO_CMD, dir, ["version"]);
|
|
441
527
|
if (versionDesc) {
|
|
442
|
-
|
|
528
|
+
const info = {
|
|
443
529
|
type: "platform",
|
|
444
530
|
name: "go",
|
|
445
531
|
version: versionDesc.trim(),
|
|
446
532
|
};
|
|
533
|
+
recordToolProbeResolved("go", "go", info.version);
|
|
534
|
+
return info;
|
|
447
535
|
}
|
|
536
|
+
recordToolProbeFailure("go", "go", GO_CMD);
|
|
448
537
|
return undefined;
|
|
449
538
|
}
|
|
450
539
|
|
|
@@ -457,12 +546,15 @@ export function collectGoInfo(dir) {
|
|
|
457
546
|
export function collectSwiftInfo(dir) {
|
|
458
547
|
const versionDesc = getCommandOutput(SWIFT_CMD, dir, ["--version"]);
|
|
459
548
|
if (versionDesc) {
|
|
460
|
-
|
|
549
|
+
const info = {
|
|
461
550
|
type: "platform",
|
|
462
551
|
name: "swift",
|
|
463
552
|
version: versionDesc.trim(),
|
|
464
553
|
};
|
|
554
|
+
recordToolProbeResolved("swift", "swift", info.version);
|
|
555
|
+
return info;
|
|
465
556
|
}
|
|
557
|
+
recordToolProbeFailure("swift", "swift", SWIFT_CMD);
|
|
466
558
|
return undefined;
|
|
467
559
|
}
|
|
468
560
|
|
|
@@ -475,12 +567,15 @@ export function collectSwiftInfo(dir) {
|
|
|
475
567
|
export function collectRubyInfo(dir) {
|
|
476
568
|
const versionDesc = getCommandOutput(RUBY_CMD, dir, ["--version"]);
|
|
477
569
|
if (versionDesc) {
|
|
478
|
-
|
|
570
|
+
const info = {
|
|
479
571
|
type: "platform",
|
|
480
572
|
name: "ruby",
|
|
481
573
|
version: versionDesc.trim(),
|
|
482
574
|
};
|
|
575
|
+
recordToolProbeResolved("ruby", "ruby", info.version);
|
|
576
|
+
return info;
|
|
483
577
|
}
|
|
578
|
+
recordToolProbeFailure("ruby", "ruby", RUBY_CMD);
|
|
484
579
|
return undefined;
|
|
485
580
|
}
|
|
486
581
|
|
|
@@ -667,6 +762,17 @@ export function installSdkmanTool(toolType, toolName) {
|
|
|
667
762
|
return false;
|
|
668
763
|
}
|
|
669
764
|
toolName = getSdkmanToolFullname(toolName);
|
|
765
|
+
// The version is interpolated into an interactive shell command, so reject
|
|
766
|
+
// any identifier that is not a single safe token before spawning.
|
|
767
|
+
if (
|
|
768
|
+
!/^[a-z][a-z0-9-]*$/.test(`${toolType}`) ||
|
|
769
|
+
!isValidSdkmanVersion(toolName)
|
|
770
|
+
) {
|
|
771
|
+
console.log(
|
|
772
|
+
`Unable to install ${toolType} with an invalid version identifier '${toolName}'. Use exact sdkman version identifiers such as '3.9.9' or '21.0.7-tem'.`,
|
|
773
|
+
);
|
|
774
|
+
return false;
|
|
775
|
+
}
|
|
670
776
|
let result;
|
|
671
777
|
if (!isSdkmanToolAvailable(toolType, toolName)) {
|
|
672
778
|
let installDir = "";
|
|
@@ -968,6 +1074,14 @@ export function installRubyVersion(rubyVersion, filePath) {
|
|
|
968
1074
|
if (result.stderr) {
|
|
969
1075
|
console.log(result.stderr);
|
|
970
1076
|
}
|
|
1077
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.COMMAND_FAILED, {
|
|
1078
|
+
ecosystem: "ruby",
|
|
1079
|
+
tool: "rbenv",
|
|
1080
|
+
command: `rbenv local ${rubyVersion}`,
|
|
1081
|
+
exitCode: typeof result.status === "number" ? result.status : undefined,
|
|
1082
|
+
detail: `Selecting Ruby ${rubyVersion} with rbenv local did not succeed.`,
|
|
1083
|
+
outputExcerpt: commandOutputText(result),
|
|
1084
|
+
});
|
|
971
1085
|
}
|
|
972
1086
|
if (result.status === 0) {
|
|
973
1087
|
return { fullToolBinDir, status: true };
|
|
@@ -975,6 +1089,14 @@ export function installRubyVersion(rubyVersion, filePath) {
|
|
|
975
1089
|
}
|
|
976
1090
|
// Check if we're trying to install Ruby 1.x or 2.x
|
|
977
1091
|
if (rubyVersion.startsWith("1.")) {
|
|
1092
|
+
recordDegradation("ruby.version.unsupported-host", {
|
|
1093
|
+
ecosystem: "ruby",
|
|
1094
|
+
tool: "ruby",
|
|
1095
|
+
impact: "transitive-deps",
|
|
1096
|
+
wanted: rubyVersion,
|
|
1097
|
+
detail:
|
|
1098
|
+
"Building Ruby 1.x on this host is unsupported; a matching legacy container image is required to build this project.",
|
|
1099
|
+
});
|
|
978
1100
|
console.log(
|
|
979
1101
|
`Ruby version ${rubyVersion} requires very old versions of Linux such as debian:8. Consider using the container image "ghcr.io/cdxgen/debian-ruby18:master" to build the application first and then invoke cdxgen with the arguments "--lifecycle pre-build".`,
|
|
980
1102
|
);
|
|
@@ -983,6 +1105,14 @@ export function installRubyVersion(rubyVersion, filePath) {
|
|
|
983
1105
|
rubyVersion.startsWith("2.") &&
|
|
984
1106
|
readEnvironmentVariable("CDXGEN_IN_CONTAINER") !== "true"
|
|
985
1107
|
) {
|
|
1108
|
+
recordDegradation("ruby.build-deps.missing", {
|
|
1109
|
+
ecosystem: "ruby",
|
|
1110
|
+
tool: "ruby",
|
|
1111
|
+
impact: "transitive-deps",
|
|
1112
|
+
wanted: rubyVersion,
|
|
1113
|
+
detail:
|
|
1114
|
+
"Building Ruby 2.x on this host requires development libraries that are not installed.",
|
|
1115
|
+
});
|
|
986
1116
|
console.log(
|
|
987
1117
|
`Installing Ruby version ${rubyVersion} requires specific development libraries. Consider using the custom container image "ghcr.io/cdxgen/cdxgen-debian-ruby26:v13" instead.`,
|
|
988
1118
|
);
|
|
@@ -996,6 +1126,12 @@ export function installRubyVersion(rubyVersion, filePath) {
|
|
|
996
1126
|
`To speed up this step, use bind mounts. Example: "--mount type=bind,src=/tmp/rbenv,dst=/root/.rbenv/versions/${rubyVersion}"`,
|
|
997
1127
|
);
|
|
998
1128
|
}
|
|
1129
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.COMMAND_ATTEMPTED, {
|
|
1130
|
+
ecosystem: "ruby",
|
|
1131
|
+
tool: "rbenv",
|
|
1132
|
+
command: `rbenv install ${rubyVersion}`,
|
|
1133
|
+
detail: `Installing Ruby ${rubyVersion} with rbenv.`,
|
|
1134
|
+
});
|
|
999
1135
|
const result = safeSpawnSync(
|
|
1000
1136
|
readEnvironmentVariable("RBENV_CMD") || "rbenv",
|
|
1001
1137
|
["install", rubyVersion],
|
|
@@ -1004,6 +1140,14 @@ export function installRubyVersion(rubyVersion, filePath) {
|
|
|
1004
1140
|
},
|
|
1005
1141
|
);
|
|
1006
1142
|
if (result.error || result.status !== 0) {
|
|
1143
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.COMMAND_FAILED, {
|
|
1144
|
+
ecosystem: "ruby",
|
|
1145
|
+
tool: "rbenv",
|
|
1146
|
+
command: `rbenv install ${rubyVersion}`,
|
|
1147
|
+
exitCode: typeof result.status === "number" ? result.status : undefined,
|
|
1148
|
+
detail: `The rbenv install of Ruby ${rubyVersion} did not succeed.`,
|
|
1149
|
+
outputExcerpt: commandOutputText(result),
|
|
1150
|
+
});
|
|
1007
1151
|
if (result.stdout) {
|
|
1008
1152
|
console.log(result.stdout);
|
|
1009
1153
|
}
|
|
@@ -1129,6 +1273,13 @@ export function performBundleInstall(
|
|
|
1129
1273
|
console.log(
|
|
1130
1274
|
`Invoking ${bundleCommand} ${installArgs.join(" ")} from ${basePath} with GEM_HOME ${cdxgenGemHome}. Please wait ...`,
|
|
1131
1275
|
);
|
|
1276
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.COMMAND_ATTEMPTED, {
|
|
1277
|
+
ecosystem: "ruby",
|
|
1278
|
+
tool: "bundler",
|
|
1279
|
+
command: `${bundleCommand} ${installArgs.join(" ")}`,
|
|
1280
|
+
path: basePath,
|
|
1281
|
+
detail: "Installing the project dependencies with bundler.",
|
|
1282
|
+
});
|
|
1132
1283
|
let result = safeSpawnSync(bundleCommand, installArgs, {
|
|
1133
1284
|
shell: isWin,
|
|
1134
1285
|
cwd: basePath,
|
|
@@ -1138,6 +1289,15 @@ export function performBundleInstall(
|
|
|
1138
1289
|
},
|
|
1139
1290
|
});
|
|
1140
1291
|
if (result.error || result.status !== 0) {
|
|
1292
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.COMMAND_FAILED, {
|
|
1293
|
+
ecosystem: "ruby",
|
|
1294
|
+
tool: "bundler",
|
|
1295
|
+
command: `${bundleCommand} ${installArgs.join(" ")}`,
|
|
1296
|
+
path: basePath,
|
|
1297
|
+
exitCode: typeof result.status === "number" ? result.status : undefined,
|
|
1298
|
+
detail: "The bundle install did not succeed.",
|
|
1299
|
+
outputExcerpt: commandOutputText(result),
|
|
1300
|
+
});
|
|
1141
1301
|
let pythonWarningShown = false;
|
|
1142
1302
|
let rubyVersionWarningShown = false;
|
|
1143
1303
|
if (
|
|
@@ -1184,6 +1344,13 @@ export function performBundleInstall(
|
|
|
1184
1344
|
);
|
|
1185
1345
|
}
|
|
1186
1346
|
console.log(`${bundleCommand}`);
|
|
1347
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.COMMAND_ATTEMPTED, {
|
|
1348
|
+
ecosystem: "ruby",
|
|
1349
|
+
tool: "bundler",
|
|
1350
|
+
command: `${bundleCommand} ${updateArgs.join(" ")}`,
|
|
1351
|
+
path: basePath,
|
|
1352
|
+
detail: "Updating the outdated lockfile with bundler.",
|
|
1353
|
+
});
|
|
1187
1354
|
result = safeSpawnSync(bundleCommand, updateArgs, {
|
|
1188
1355
|
shell: isWin,
|
|
1189
1356
|
cwd: basePath,
|
|
@@ -1193,6 +1360,16 @@ export function performBundleInstall(
|
|
|
1193
1360
|
},
|
|
1194
1361
|
});
|
|
1195
1362
|
if (result.error || result.status !== 0) {
|
|
1363
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.COMMAND_FAILED, {
|
|
1364
|
+
ecosystem: "ruby",
|
|
1365
|
+
tool: "bundler",
|
|
1366
|
+
command: `${bundleCommand} ${updateArgs.join(" ")}`,
|
|
1367
|
+
path: basePath,
|
|
1368
|
+
exitCode:
|
|
1369
|
+
typeof result.status === "number" ? result.status : undefined,
|
|
1370
|
+
detail: "The bundle update did not succeed.",
|
|
1371
|
+
outputExcerpt: commandOutputText(result),
|
|
1372
|
+
});
|
|
1196
1373
|
console.log("------------");
|
|
1197
1374
|
if (result.stdout) {
|
|
1198
1375
|
console.log(result.stdout);
|
|
@@ -1740,6 +1917,20 @@ export function getRuntimeInformation() {
|
|
|
1740
1917
|
runtimeInfo.version = "N/A";
|
|
1741
1918
|
}
|
|
1742
1919
|
|
|
1920
|
+
// The runtime hosting the cdxgen run is part of the report's
|
|
1921
|
+
// reproducibility context, so it is recorded alongside the scanned tools.
|
|
1922
|
+
const runtimeTools = { Deno: "deno", Bun: "bun", "Node.js": "node" };
|
|
1923
|
+
const runtimeTool = runtimeTools[runtimeInfo.runtime];
|
|
1924
|
+
if (runtimeTool && runtimeInfo.version && runtimeInfo.version !== "N/A") {
|
|
1925
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_RESOLVED, {
|
|
1926
|
+
ecosystem: "generic",
|
|
1927
|
+
tool: runtimeTool,
|
|
1928
|
+
found: runtimeInfo.version,
|
|
1929
|
+
source: "runtime",
|
|
1930
|
+
detail:
|
|
1931
|
+
"The runtime hosting this cdxgen run, recorded for report reproducibility.",
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1743
1934
|
return runtimeInfo;
|
|
1744
1935
|
}
|
|
1745
1936
|
|
|
@@ -6,6 +6,7 @@ import { build } from "@cdxgen/cdx-purl";
|
|
|
6
6
|
|
|
7
7
|
import { safeExistsSync } from "../core/fs.js";
|
|
8
8
|
import { thoughtLog } from "../core/logger.js";
|
|
9
|
+
import { addDosaiReachabilityProperties } from "./dosai.js";
|
|
9
10
|
import {
|
|
10
11
|
addDosaiSetValue,
|
|
11
12
|
buildDosaiPurlAliasMap,
|
|
@@ -256,6 +257,7 @@ export function addEvidenceForDotnet(pkgList, slicesFile) {
|
|
|
256
257
|
const purlLocationMap = {};
|
|
257
258
|
const purlModulesMap = {};
|
|
258
259
|
const purlMethodsMap = {};
|
|
260
|
+
const purlReachabilityMap = {};
|
|
259
261
|
const purlAliasMap = buildDosaiPurlAliasMap(pkgList);
|
|
260
262
|
for (const apkg of pkgList) {
|
|
261
263
|
if (apkg.properties && Array.isArray(apkg.properties)) {
|
|
@@ -340,6 +342,9 @@ export function addEvidenceForDotnet(pkgList, slicesFile) {
|
|
|
340
342
|
if (!modPurl) {
|
|
341
343
|
continue;
|
|
342
344
|
}
|
|
345
|
+
// Confidence/evidence facts for this purl, applied to the component below so an
|
|
346
|
+
// unbuilt tree stays distinguishable from an only-imported package.
|
|
347
|
+
purlReachabilityMap[modPurl] ??= reachability;
|
|
343
348
|
let hasExplicitSourceLocations = false;
|
|
344
349
|
for (const sourceLocation of reachability.SourceLocations || []) {
|
|
345
350
|
const location = dosaiSourceLocation(sourceLocation);
|
|
@@ -456,6 +461,14 @@ export function addEvidenceForDotnet(pkgList, slicesFile) {
|
|
|
456
461
|
"I didn't find any occurrence evidence or detailed imported modules, even though there is good dependency slice data from dosai. This is surprising.",
|
|
457
462
|
);
|
|
458
463
|
}
|
|
464
|
+
// Reachability confidence facts (kind, confidence, evidence, reasons) as properties.
|
|
465
|
+
// Applied outside the location guard: a Low-confidence unbuilt tree must stay
|
|
466
|
+
// distinguishable from an only-imported package even when no locations were found.
|
|
467
|
+
for (const apkg of pkgList) {
|
|
468
|
+
if (purlReachabilityMap[apkg.purl]) {
|
|
469
|
+
addDosaiReachabilityProperties(apkg, purlReachabilityMap[apkg.purl]);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
459
472
|
return pkgList;
|
|
460
473
|
}
|
|
461
474
|
|
|
@@ -34,10 +34,13 @@
|
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
import {
|
|
37
|
+
BLOCKED_HOST_ERROR_CODE,
|
|
37
38
|
cdxgenAgent,
|
|
38
39
|
DEBUG_MODE,
|
|
40
|
+
DRY_RUN_ERROR_CODE,
|
|
39
41
|
readEnvironmentVariable,
|
|
40
42
|
} from "../core/activity.js";
|
|
43
|
+
import { isLedgerEnabled, recordDegradation } from "../core/buildLedger.js";
|
|
41
44
|
import { resolveCacheDir } from "./cacheDir.js";
|
|
42
45
|
import { cdxrsAvailable, runCdxrs } from "./cdxrs.js";
|
|
43
46
|
import {
|
|
@@ -245,21 +248,58 @@ export async function prefetchJson(requests, options = {}) {
|
|
|
245
248
|
* - a non-JSON `responseType`, since the envelope has nowhere to put HTML;
|
|
246
249
|
* - request-specific `headers`, since cdxrs derives its own Authorization from
|
|
247
250
|
* `GITHUB_TOKEN` and would silently drop any other credential, turning an
|
|
248
|
-
* authenticated lookup into an anonymous one
|
|
251
|
+
* authenticated lookup into an anonymous one;
|
|
252
|
+
* - anything addressed to a host whose rate policy only one transport knows.
|
|
253
|
+
* crates.io's crawler policy allows one request per second with no
|
|
254
|
+
* parallelism, and package.elm-lang.org has a JS-only allowance because
|
|
255
|
+
* rate.rs carries no row for it. Both transports keep separate gates, so a
|
|
256
|
+
* batch split across the two would issue twice the agreed rate; the JS pool
|
|
257
|
+
* owns these hosts so there is one gate to reason about.
|
|
249
258
|
*
|
|
250
259
|
* The pool applies the same global and per-host concurrency and rate policy, so
|
|
251
260
|
* the batching win is unaffected; only the on-disk cache is skipped.
|
|
252
261
|
*
|
|
253
|
-
* @param {{responseType?: string, headers?: Object}} request
|
|
262
|
+
* @param {{url?: string, responseType?: string, headers?: Object}} request
|
|
254
263
|
* @returns {boolean}
|
|
255
264
|
*/
|
|
256
265
|
function cdxrsCanServe(request) {
|
|
257
266
|
if (request.responseType && request.responseType !== "json") {
|
|
258
267
|
return false;
|
|
259
268
|
}
|
|
269
|
+
if (isSingleGateHost(extractHost(request.url))) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
260
272
|
return !request.headers || !Object.keys(request.headers).length;
|
|
261
273
|
}
|
|
262
274
|
|
|
275
|
+
/**
|
|
276
|
+
* Whether a hostname belongs to crates.io, including its index and CDN hosts.
|
|
277
|
+
*
|
|
278
|
+
* @param {string|null} host Hostname, or null when the URL did not parse.
|
|
279
|
+
* @returns {boolean}
|
|
280
|
+
*/
|
|
281
|
+
export function isCratesHost(host) {
|
|
282
|
+
if (!host) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
const lower = host.toLowerCase();
|
|
286
|
+
return lower === "crates.io" || lower.endsWith(".crates.io");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Whether a host's rate policy lives in the JS table alone and therefore needs
|
|
291
|
+
* every request to pass through the one gate the JS pool holds.
|
|
292
|
+
*
|
|
293
|
+
* @param {string|null} host Hostname, or null when the URL did not parse.
|
|
294
|
+
* @returns {boolean}
|
|
295
|
+
*/
|
|
296
|
+
export function isSingleGateHost(host) {
|
|
297
|
+
if (!host) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
return isCratesHost(host) || host.toLowerCase() === "package.elm-lang.org";
|
|
301
|
+
}
|
|
302
|
+
|
|
263
303
|
/**
|
|
264
304
|
* Run the batch through the `cdxrs fetch` subprocess and translate its
|
|
265
305
|
* envelope into the shared result shape.
|
|
@@ -545,6 +585,59 @@ async function issueWithRetries(request, limiter) {
|
|
|
545
585
|
}
|
|
546
586
|
}
|
|
547
587
|
|
|
588
|
+
/** Policy and connectivity degradations already recorded this run; these describe the run, not one URL. */
|
|
589
|
+
const recordedPolicyDegradations = new Set();
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Record a run-level policy or connectivity degradation once per process. A
|
|
593
|
+
* batch touches many URLs, but "dry-run blocked the network", "a host was not
|
|
594
|
+
* permitted" and "the network is unreachable" are properties of the run, so
|
|
595
|
+
* only the first occurrence is recorded and the report reads one line, not
|
|
596
|
+
* one per URL.
|
|
597
|
+
*
|
|
598
|
+
* @param {string} remediationId Remediation id from data/remediations.json
|
|
599
|
+
* @param {Object} fields Ledger event fields
|
|
600
|
+
* @returns {void}
|
|
601
|
+
*/
|
|
602
|
+
function recordPolicyDegradationOnce(remediationId, fields) {
|
|
603
|
+
if (!isLedgerEnabled() || recordedPolicyDegradations.has(remediationId)) {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
recordedPolicyDegradations.add(remediationId);
|
|
607
|
+
recordDegradation(remediationId, fields);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Record the run-level policy or connectivity condition carried by a caught
|
|
612
|
+
* fetch error, if it names one. Shared by the batch retry loop and by the
|
|
613
|
+
* serial enrichment paths that catch and swallow the same typed errors.
|
|
614
|
+
*
|
|
615
|
+
* @param {Error} err Error caught from a `cdxgenAgent` request.
|
|
616
|
+
* @returns {void}
|
|
617
|
+
*/
|
|
618
|
+
export function recordPolicyDegradationFromError(err) {
|
|
619
|
+
if (err?.code === DRY_RUN_ERROR_CODE) {
|
|
620
|
+
recordPolicyDegradationOnce("policy.dry-run", {
|
|
621
|
+
ecosystem: "generic",
|
|
622
|
+
impact: "none",
|
|
623
|
+
detail: `Dry-run mode blocked a registry request: ${err.message}`,
|
|
624
|
+
});
|
|
625
|
+
} else if (err?.code === BLOCKED_HOST_ERROR_CODE) {
|
|
626
|
+
recordPolicyDegradationOnce("policy.host-blocked", {
|
|
627
|
+
ecosystem: "generic",
|
|
628
|
+
impact: "licenses",
|
|
629
|
+
detail: `A registry request was blocked by policy: ${err.message}`,
|
|
630
|
+
});
|
|
631
|
+
} else if (err?.name === "RequestError") {
|
|
632
|
+
recordPolicyDegradationOnce("policy.offline", {
|
|
633
|
+
ecosystem: "generic",
|
|
634
|
+
impact: "licenses",
|
|
635
|
+
detail:
|
|
636
|
+
"A registry request failed with a transport error, so registry metadata and license enrichment may be missing.",
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
548
641
|
/**
|
|
549
642
|
* Classify an error thrown by `cdxgenAgent.get` into a retry decision and the
|
|
550
643
|
* batch entry to record when retries are exhausted.
|
|
@@ -553,6 +646,7 @@ async function issueWithRetries(request, limiter) {
|
|
|
553
646
|
* @returns {{retryable: boolean, delay: number|null, entry: BatchEntry}}
|
|
554
647
|
*/
|
|
555
648
|
function classifyAgentError(err) {
|
|
649
|
+
recordPolicyDegradationFromError(err);
|
|
556
650
|
// Secure-mode and dry-run blocks must not be retried, and the caller's own
|
|
557
651
|
// fallback `cdxgenAgent.get` will surface the same error. Recording this as
|
|
558
652
|
// non-definite lets the caller's catch run exactly as it would on the serial
|
|
@@ -643,6 +737,41 @@ async function fetchOne(request, gauge) {
|
|
|
643
737
|
}
|
|
644
738
|
}
|
|
645
739
|
|
|
740
|
+
/**
|
|
741
|
+
* Run a caller's own request under the same per-host gate the batch pool uses.
|
|
742
|
+
*
|
|
743
|
+
* Not every registry lookup goes through the pool: a caller that misses the
|
|
744
|
+
* prefetch, or runs with prefetching disabled, issues `cdxgenAgent.get`
|
|
745
|
+
* directly. Those requests reach the same host and count against the same
|
|
746
|
+
* published budget, so they have to queue behind the same limiter rather than
|
|
747
|
+
* a second one — crates.io's one-request-per-second policy is not honoured by
|
|
748
|
+
* two independent gates that each allow one per second.
|
|
749
|
+
*
|
|
750
|
+
* The per-host semaphore and limiter are module state, so this shares one gate
|
|
751
|
+
* per host for the life of the process.
|
|
752
|
+
*
|
|
753
|
+
* @template T
|
|
754
|
+
* @param {string} url The URL about to be requested.
|
|
755
|
+
* @param {() => Promise<T>} issue Issues the request and resolves its result.
|
|
756
|
+
* @returns {Promise<T>} Whatever `issue` resolves to.
|
|
757
|
+
*/
|
|
758
|
+
export async function withHostRateLimit(url, issue) {
|
|
759
|
+
const host = extractHost(url);
|
|
760
|
+
if (!host) {
|
|
761
|
+
return await issue();
|
|
762
|
+
}
|
|
763
|
+
const controls = getHostControls(host);
|
|
764
|
+
const releaseGlobal = await _globalSemaphore.acquire();
|
|
765
|
+
const releaseHost = await controls.sem.acquire();
|
|
766
|
+
try {
|
|
767
|
+
await controls.limiter.wait();
|
|
768
|
+
return await issue();
|
|
769
|
+
} finally {
|
|
770
|
+
releaseHost();
|
|
771
|
+
releaseGlobal();
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
646
775
|
/**
|
|
647
776
|
* Dedupe key for an in-flight request: the URL, plus everything that changes
|
|
648
777
|
* which response the caller gets back. That is the headers which change what
|