@cdxgen/cdxgen 13.4.0 → 13.6.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 +9 -9
- package/bin/cdxgen-secure.js +15 -0
- package/bin/cdxgen.js +29 -5
- package/bin/evinse.js +8 -0
- package/bin/hbom.js +15 -7
- package/bin/repl.js +29 -0
- package/bin/validate.js +6 -0
- package/data/cbomosdb-queries.json +13 -1
- package/data/component-tags.json +8 -0
- package/data/license-aliases.json +24 -2
- package/data/queries-darwin.json +25 -1
- package/data/queries-win.json +19 -1
- package/data/queries.json +41 -0
- package/data/remediations.json +407 -1
- package/data/rules/ai-bom.yaml +1 -1
- package/data/rules/brotli-decoder.yaml +107 -0
- package/data/rules/cbom-security.yaml +235 -4
- package/data/rules/go-binaries.yaml +46 -0
- package/data/rules/golem-go.yaml +80 -0
- package/data/rules/hbom-compliance.yaml +40 -22
- package/data/rules/hbom-performance.yaml +7 -1
- package/data/rules/host-topology.yaml +1 -1
- package/data/rules/obom-runtime.yaml +31 -2
- package/data/rules/php-runtime.yaml +143 -0
- package/data/rules/vscode-extensions.yaml +75 -0
- package/data/spdx-license-list.json +121 -7
- package/data/spdx-licenses.json +69 -0
- package/data/spdx.schema.json +845 -2
- package/lib/audit/index.js +14 -0
- package/lib/cli/bomAssembly.js +120 -6
- package/lib/cli/cliOptions.js +110 -3
- package/lib/cli/index.js +182 -6
- package/lib/cli/jsBom.js +153 -64
- package/lib/cli/jvmBom.js +363 -66
- package/lib/cli/managedBom.js +853 -87
- package/lib/cli/nativeBom.js +267 -33
- package/lib/core/boundedFile.js +344 -0
- package/lib/core/brotliProbe.js +182 -0
- package/lib/core/env.js +38 -8
- package/lib/core/fs.js +40 -19
- package/lib/core/httpClient.js +172 -46
- package/lib/core/lockPaths.js +38 -0
- package/lib/core/paths.js +24 -1
- package/lib/core/processTree.js +5 -0
- package/lib/core/projectConfig.js +1 -0
- package/lib/core/projectTooling.js +97 -0
- package/lib/core/propertySanitizer.js +72 -0
- package/lib/core/toolRegistry.js +170 -0
- package/lib/ecosystems/cppEvidence.js +263 -106
- package/lib/ecosystems/cppIncludeResolver.js +16 -5
- package/lib/ecosystems/ecosystems.js +25 -10
- package/lib/ecosystems/gemHostProbe.js +301 -0
- package/lib/ecosystems/golem.js +550 -6
- package/lib/ecosystems/gradleutils.js +858 -244
- package/lib/ecosystems/parsers-bazel.js +107 -6
- package/lib/ecosystems/parsers-dotnet.js +117 -18
- package/lib/ecosystems/parsers-go.js +1090 -367
- package/lib/ecosystems/parsers-js.js +505 -22
- package/lib/ecosystems/parsers-jvm.js +259 -17
- package/lib/ecosystems/parsers-misc.js +438 -64
- package/lib/ecosystems/parsers-python.js +100 -12
- package/lib/ecosystems/pythonutils.js +128 -2
- package/lib/ecosystems/rubyutils.js +544 -34
- package/lib/ecosystems/rusi.js +10 -0
- package/lib/ecosystems/scalacliutils.js +4 -1
- package/lib/ecosystems/scalasem.js +798 -151
- package/lib/ecosystems/vendoredCode.js +237 -3
- package/lib/ecosystems/venvProbe.js +360 -0
- package/lib/evinser/evinser.js +472 -61
- package/lib/evinser/swiftsem.js +68 -5
- package/lib/helpers/agenticToolutils.js +413 -93
- package/lib/helpers/vsixutils.js +1574 -315
- package/lib/inventory/agentSkillSpec.js +17 -0
- package/lib/inventory/apkInstalledDb.js +243 -0
- package/lib/inventory/atomUtils.js +110 -0
- package/lib/inventory/auditCategories.js +3 -3
- package/lib/inventory/brotliDecoder.js +142 -0
- package/lib/inventory/buildToolRateLimit.js +1 -1
- package/lib/inventory/cacheDir.js +52 -9
- package/lib/inventory/cbomutils.js +56 -7
- package/lib/inventory/certificates.js +2371 -0
- package/lib/inventory/communityAiConfigParser.js +4 -9
- package/lib/inventory/deps.js +41 -4
- package/lib/inventory/display.js +200 -3
- package/lib/inventory/dosai.js +639 -63
- package/lib/inventory/dotnetRuntime.js +217 -0
- package/lib/inventory/dynamic.js +45 -3
- package/lib/inventory/envcontext.js +475 -18
- package/lib/inventory/evidenceUtils.js +447 -23
- package/lib/inventory/fetchBatch.js +1 -1
- package/lib/inventory/formulationParsers.js +32 -3
- package/lib/inventory/goBuildInfo.js +140 -0
- package/lib/inventory/gtfobins.js +13 -0
- package/lib/inventory/hbom.js +67 -7
- package/lib/inventory/hbomAnalysis.js +16 -0
- package/lib/inventory/hostRuntimeProbe.js +686 -0
- package/lib/inventory/hostTopology.js +21 -0
- package/lib/inventory/jvmLocalRepos.js +9 -2
- package/lib/inventory/jvmToolEnv.js +29 -0
- package/lib/inventory/licenseEnhancer.js +12 -0
- package/lib/inventory/mavenCentralMirror.js +534 -0
- package/lib/inventory/nodeBuildInfo.js +218 -0
- package/lib/inventory/nodeHostProbe.js +202 -0
- package/lib/inventory/osPackagePurl.js +244 -0
- package/lib/inventory/osPackageResolver.js +271 -87
- package/lib/inventory/osinfo.js +79 -31
- package/lib/inventory/osqueryTransform.js +114 -5
- package/lib/inventory/phpBuildInfo.js +438 -0
- package/lib/inventory/phpHostProbe.js +185 -0
- package/lib/inventory/rootfsCerts.js +426 -0
- package/lib/inventory/rubyBuildInfo.js +972 -0
- package/lib/inventory/rubyHostProbe.js +218 -0
- package/lib/inventory/scalaCoords.js +4 -3
- package/lib/inventory/scalaDedupe.js +440 -0
- package/lib/inventory/source.js +256 -5
- package/lib/inventory/spdx.js +144 -0
- package/lib/inventory/traceRunner.js +5 -1
- package/lib/inventory/tracedCrypto.js +129 -0
- package/lib/managers/binary.js +866 -186
- package/lib/managers/docker.js +245 -12
- package/lib/parsers/email.js +277 -0
- package/lib/parsers/largeJson.js +62 -13
- package/lib/parsers/vscodeExtensionId.js +183 -0
- package/lib/server/server.js +198 -2
- package/lib/stages/postgen/annotator.js +17 -1
- package/lib/stages/postgen/introspection/reflect.js +4 -1
- package/lib/stages/postgen/postgen.js +5 -5
- package/lib/stages/postgen/ruleEngine.js +71 -6
- package/lib/stages/postgen/specVersionCompat.js +30 -0
- package/lib/stages/pregen/envAudit.js +1 -0
- package/lib/stages/pregen/pregen.js +231 -62
- package/lib/validator/bomValidator.js +32 -6
- package/lib/validator/generated/runtime.mjs +13 -0
- package/lib/validator/generated/validate-1.6.mjs +27 -8
- package/lib/validator/generated/validate-1.7.mjs +32 -13
- package/lib/validator/generated/validate-2.0.mjs +31 -12
- package/lib/validator/index.js +1 -1
- package/package.json +19 -18
- package/types/bin/cdxgen-secure.d.ts +3 -0
- package/types/bin/cdxgen-secure.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +22 -3
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +43 -0
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +1 -17
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +20 -0
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/boundedFile.d.ts +70 -0
- package/types/lib/core/boundedFile.d.ts.map +1 -0
- package/types/lib/core/brotliProbe.d.ts +37 -0
- package/types/lib/core/brotliProbe.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +22 -2
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/httpClient.d.ts +24 -0
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/lockPaths.d.ts +19 -0
- package/types/lib/core/lockPaths.d.ts.map +1 -0
- package/types/lib/core/processTree.d.ts +2 -0
- package/types/lib/core/processTree.d.ts.map +1 -1
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/projectTooling.d.ts +41 -0
- package/types/lib/core/projectTooling.d.ts.map +1 -0
- package/types/lib/core/propertySanitizer.d.ts +26 -0
- package/types/lib/core/propertySanitizer.d.ts.map +1 -1
- package/types/lib/core/toolRegistry.d.ts +36 -0
- package/types/lib/core/toolRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +40 -13
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +2 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gemHostProbe.d.ts +28 -0
- package/types/lib/ecosystems/gemHostProbe.d.ts.map +1 -0
- package/types/lib/ecosystems/golem.d.ts +81 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +57 -21
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts +40 -4
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +85 -13
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +17 -20
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts +28 -2
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +37 -58
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +14 -3
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +19 -1
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +144 -12
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +26 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -1
- package/types/lib/ecosystems/venvProbe.d.ts +40 -0
- package/types/lib/ecosystems/venvProbe.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +88 -8
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +10 -0
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/inventory/agentSkillSpec.d.ts +13 -0
- package/types/lib/inventory/agentSkillSpec.d.ts.map +1 -0
- package/types/lib/inventory/apkInstalledDb.d.ts +42 -0
- package/types/lib/inventory/apkInstalledDb.d.ts.map +1 -0
- package/types/lib/inventory/atomUtils.d.ts +33 -0
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/brotliDecoder.d.ts +58 -0
- package/types/lib/inventory/brotliDecoder.d.ts.map +1 -0
- package/types/lib/inventory/cacheDir.d.ts +12 -4
- package/types/lib/inventory/cacheDir.d.ts.map +1 -1
- package/types/lib/inventory/cbomutils.d.ts +17 -0
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/certificates.d.ts +205 -0
- package/types/lib/inventory/certificates.d.ts.map +1 -0
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/deps.d.ts +2 -1
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts +18 -0
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +162 -7
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dotnetRuntime.d.ts +44 -0
- package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -0
- package/types/lib/inventory/dynamic.d.ts +6 -1
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts +92 -8
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts +29 -10
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
- package/types/lib/inventory/goBuildInfo.d.ts +10 -0
- package/types/lib/inventory/goBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/gtfobins.d.ts.map +1 -1
- package/types/lib/inventory/hbom.d.ts.map +1 -1
- package/types/lib/inventory/hbomAnalysis.d.ts +10 -0
- package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -1
- package/types/lib/inventory/hostRuntimeProbe.d.ts +44 -0
- package/types/lib/inventory/hostRuntimeProbe.d.ts.map +1 -0
- package/types/lib/inventory/hostTopology.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +12 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
- package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -1
- package/types/lib/inventory/mavenCentralMirror.d.ts +170 -0
- package/types/lib/inventory/mavenCentralMirror.d.ts.map +1 -0
- package/types/lib/inventory/nodeBuildInfo.d.ts +95 -0
- package/types/lib/inventory/nodeBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/nodeHostProbe.d.ts +15 -0
- package/types/lib/inventory/nodeHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/osPackagePurl.d.ts +99 -0
- package/types/lib/inventory/osPackagePurl.d.ts.map +1 -0
- package/types/lib/inventory/osPackageResolver.d.ts +44 -2
- package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
- package/types/lib/inventory/osinfo.d.ts +31 -6
- package/types/lib/inventory/osinfo.d.ts.map +1 -1
- package/types/lib/inventory/osqueryTransform.d.ts +51 -0
- package/types/lib/inventory/osqueryTransform.d.ts.map +1 -1
- package/types/lib/inventory/phpBuildInfo.d.ts +142 -0
- package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/phpHostProbe.d.ts +35 -0
- package/types/lib/inventory/phpHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/rootfsCerts.d.ts +27 -0
- package/types/lib/inventory/rootfsCerts.d.ts.map +1 -0
- package/types/lib/inventory/rubyBuildInfo.d.ts +271 -0
- package/types/lib/inventory/rubyBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/rubyHostProbe.d.ts +47 -0
- package/types/lib/inventory/rubyHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +38 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/spdx.d.ts +15 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +4 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/inventory/tracedCrypto.d.ts +22 -0
- package/types/lib/inventory/tracedCrypto.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +10 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +24 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/parsers/email.d.ts +19 -0
- package/types/lib/parsers/email.d.ts.map +1 -0
- package/types/lib/parsers/largeJson.d.ts +17 -3
- package/types/lib/parsers/largeJson.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +8 -0
- 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/introspection/reflect.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts +7 -4
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +26 -9
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
package/lib/managers/binary.js
CHANGED
|
@@ -6,7 +6,15 @@ import {
|
|
|
6
6
|
statSync,
|
|
7
7
|
} from "node:fs";
|
|
8
8
|
import { platform as _platform, homedir } from "node:os";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
basename,
|
|
11
|
+
dirname,
|
|
12
|
+
extname,
|
|
13
|
+
isAbsolute,
|
|
14
|
+
join,
|
|
15
|
+
relative,
|
|
16
|
+
resolve,
|
|
17
|
+
} from "node:path";
|
|
10
18
|
import process from "node:process";
|
|
11
19
|
|
|
12
20
|
import { build, Purl } from "@cdxgen/cdx-purl";
|
|
@@ -30,26 +38,62 @@ import {
|
|
|
30
38
|
safeRmSync,
|
|
31
39
|
safeSpawnSync,
|
|
32
40
|
} from "../core/fs.js";
|
|
41
|
+
import { thoughtLog } from "../core/logger.js";
|
|
33
42
|
import { mapWithConcurrency } from "../core/parallel.js";
|
|
34
43
|
import { isValidDriveRoot } from "../core/paths.js";
|
|
35
44
|
import { isCycloneDxComponentTypeEnabled } from "../inventory/bomUtils.js";
|
|
45
|
+
import {
|
|
46
|
+
attachBrotliDecoder,
|
|
47
|
+
createBrotliInventory,
|
|
48
|
+
finishBrotliInventory,
|
|
49
|
+
MAX_BROTLI_PROBE_BYTES,
|
|
50
|
+
MAX_BROTLI_PROBES,
|
|
51
|
+
readBrotliDecoder,
|
|
52
|
+
} from "../inventory/brotliDecoder.js";
|
|
53
|
+
import {
|
|
54
|
+
CertificateCollector,
|
|
55
|
+
classifyCryptoFile,
|
|
56
|
+
} from "../inventory/certificates.js";
|
|
36
57
|
import { createContainerRiskProperties } from "../inventory/containerRisk.js";
|
|
58
|
+
import { collectDotnetRuntimes } from "../inventory/dotnetRuntime.js";
|
|
37
59
|
import {
|
|
38
60
|
attachIdentityTools,
|
|
39
61
|
extractToolRefs,
|
|
40
62
|
} from "../inventory/evidenceUtils.js";
|
|
63
|
+
import {
|
|
64
|
+
PRE_1_18_VERSION,
|
|
65
|
+
readGoBuildVersion,
|
|
66
|
+
} from "../inventory/goBuildInfo.js";
|
|
41
67
|
import { createGtfoBinsProperties } from "../inventory/gtfobins.js";
|
|
68
|
+
import {
|
|
69
|
+
attachNodeBuild,
|
|
70
|
+
createNodeRuntimeInventory,
|
|
71
|
+
isNodeBinaryName,
|
|
72
|
+
MAX_NODE_PROBES,
|
|
73
|
+
readNodeBuildVersion,
|
|
74
|
+
} from "../inventory/nodeBuildInfo.js";
|
|
42
75
|
import {
|
|
43
76
|
canonicalDistroQualifier,
|
|
77
|
+
getDistroInfo,
|
|
44
78
|
isCleanDistroQualifier,
|
|
45
79
|
OS_DISTRO_ALIAS,
|
|
46
80
|
OS_NAMESPACE_ALIAS,
|
|
47
81
|
readOsRelease,
|
|
48
82
|
} from "../inventory/osinfo.js";
|
|
83
|
+
import { isRealArchitecture } from "../inventory/osPackagePurl.js";
|
|
49
84
|
import {
|
|
50
85
|
collectExecutables,
|
|
86
|
+
collectNativeExtensions,
|
|
51
87
|
collectSharedLibs,
|
|
52
88
|
} from "../inventory/osPackageResolver.js";
|
|
89
|
+
import {
|
|
90
|
+
attachPhpBuild,
|
|
91
|
+
classifyPhpSapi,
|
|
92
|
+
createPhpRuntimeInventory,
|
|
93
|
+
MAX_PHP_PROBES,
|
|
94
|
+
packageStoreOf,
|
|
95
|
+
readPhpBuildVersion,
|
|
96
|
+
} from "../inventory/phpBuildInfo.js";
|
|
53
97
|
import {
|
|
54
98
|
resolveCdxgenPlugins,
|
|
55
99
|
resolvePluginBinary,
|
|
@@ -60,11 +104,22 @@ import {
|
|
|
60
104
|
parsePurlLoose,
|
|
61
105
|
tryBuildPurl,
|
|
62
106
|
} from "../inventory/purl.js";
|
|
107
|
+
import { collectRootfsCertificates } from "../inventory/rootfsCerts.js";
|
|
108
|
+
import {
|
|
109
|
+
attachRubyBuild,
|
|
110
|
+
createRubyRuntimeInventory,
|
|
111
|
+
deriveOsPackagedRubyRuntimes,
|
|
112
|
+
isLibrubyName,
|
|
113
|
+
isRubyBinaryName,
|
|
114
|
+
MAX_RUBY_PROBES,
|
|
115
|
+
readRubyBuildVersion,
|
|
116
|
+
} from "../inventory/rubyBuildInfo.js";
|
|
63
117
|
import {
|
|
64
118
|
adjustLicenseInformation,
|
|
65
119
|
findLicenseId,
|
|
66
120
|
isSpdxLicenseExpression,
|
|
67
121
|
} from "../inventory/spdx.js";
|
|
122
|
+
import { isValidIdnEmail } from "../parsers/email.js";
|
|
68
123
|
import { getDirs } from "./containerutils.js";
|
|
69
124
|
|
|
70
125
|
const isWin = _platform() === "win32";
|
|
@@ -510,6 +565,15 @@ const COMMON_RUNTIMES = [
|
|
|
510
565
|
"openjdk25-jre",
|
|
511
566
|
];
|
|
512
567
|
|
|
568
|
+
// Runtime packages whose name carries the series, such as Debian's ruby3.1
|
|
569
|
+
// and libruby3.1, openSUSE's libruby3_4-3_4 and Wolfi's ruby-3.3.
|
|
570
|
+
const VERSIONED_RUNTIME_PATTERNS = [
|
|
571
|
+
/^(lib)?ruby\d+\.\d+(t64)?$/,
|
|
572
|
+
/^ruby-\d+\.\d+$/,
|
|
573
|
+
/^ruby-libs$/,
|
|
574
|
+
/^libruby\d+_\d+-\d+_\d+$/,
|
|
575
|
+
];
|
|
576
|
+
|
|
513
577
|
/**
|
|
514
578
|
* Run the `cargo-auditable` companion binary against the given source path and
|
|
515
579
|
* return its stdout.
|
|
@@ -654,7 +718,35 @@ export function canonicaliseOsPackageNamespace(
|
|
|
654
718
|
group,
|
|
655
719
|
distroId,
|
|
656
720
|
) {
|
|
657
|
-
if (!purlObj
|
|
721
|
+
if (!purlObj) {
|
|
722
|
+
return group;
|
|
723
|
+
}
|
|
724
|
+
// trivy invents a `bottlerocket` purl type for Bottlerocket host packages
|
|
725
|
+
// (trivy pkg/purl/purl.go, packageurlTypeBottlerocket); the purl spec has
|
|
726
|
+
// no such type — @cdxgen/cdx-purl has no generated type class for it, and
|
|
727
|
+
// no vulnerability feed stores under it. Rewrite to the form cdxgen's own
|
|
728
|
+
// os-release path produces for a Bottlerocket host: type rpm, namespace
|
|
729
|
+
// bottlerocket (the os-release ID; OS_NAMESPACE_ALIAS needs no entry).
|
|
730
|
+
// The distro qualifier trivy stamps (bottlerocket-<os version>) survives
|
|
731
|
+
// unchanged. The caller's group is returned as for any other rpm distro
|
|
732
|
+
// without an alias, so getOSPackages still records the rpm type and the
|
|
733
|
+
// distro in allTypes (oci:image:componentTypes).
|
|
734
|
+
if (purlObj.type === "bottlerocket") {
|
|
735
|
+
purlObj.type = "rpm";
|
|
736
|
+
purlObj.namespace = "bottlerocket";
|
|
737
|
+
comp.group = "bottlerocket";
|
|
738
|
+
comp.purl = build({
|
|
739
|
+
type: purlObj.type,
|
|
740
|
+
namespace: purlObj.namespace,
|
|
741
|
+
name: name,
|
|
742
|
+
version: purlObj.version || null,
|
|
743
|
+
qualifiers: purlObj.qualifiers || null,
|
|
744
|
+
subpath: purlObj.subpath || null,
|
|
745
|
+
});
|
|
746
|
+
comp["bom-ref"] = decodeURIComponent(comp.purl);
|
|
747
|
+
return group;
|
|
748
|
+
}
|
|
749
|
+
if (!OS_PURL_TYPES.includes(purlObj.type)) {
|
|
658
750
|
return group;
|
|
659
751
|
}
|
|
660
752
|
const os_ns = purlObj.namespace || group || "";
|
|
@@ -773,7 +865,7 @@ export function dropRedundantSourceComponents(pkgList) {
|
|
|
773
865
|
const installedPurls = new Set();
|
|
774
866
|
for (const comp of pkgList) {
|
|
775
867
|
if (comp?.purl && !comp?.tags?.includes("source")) {
|
|
776
|
-
installedPurls.add(comp.purl);
|
|
868
|
+
installedPurls.add(withoutArch(comp.purl));
|
|
777
869
|
}
|
|
778
870
|
}
|
|
779
871
|
if (!installedPurls.size) {
|
|
@@ -781,13 +873,33 @@ export function dropRedundantSourceComponents(pkgList) {
|
|
|
781
873
|
}
|
|
782
874
|
for (let i = pkgList.length - 1; i >= 0; i--) {
|
|
783
875
|
const comp = pkgList[i];
|
|
784
|
-
if (
|
|
876
|
+
if (
|
|
877
|
+
comp?.tags?.includes("source") &&
|
|
878
|
+
installedPurls.has(withoutArch(comp.purl))
|
|
879
|
+
) {
|
|
785
880
|
pkgList.splice(i, 1);
|
|
786
881
|
}
|
|
787
882
|
}
|
|
788
883
|
return pkgList;
|
|
789
884
|
}
|
|
790
885
|
|
|
886
|
+
// The arch qualifier deb and rpm purls give a source package.
|
|
887
|
+
const SOURCE_PACKAGE_ARCH = { deb: "source", rpm: "src" };
|
|
888
|
+
|
|
889
|
+
// A source shadow is named like an installed package of the same name and
|
|
890
|
+
// version, whatever arch either names, so arch is left out of the comparison.
|
|
891
|
+
function withoutArch(purl) {
|
|
892
|
+
const [base, query] = `${purl}`.split("?");
|
|
893
|
+
if (!query) {
|
|
894
|
+
return base;
|
|
895
|
+
}
|
|
896
|
+
const kept = query
|
|
897
|
+
.split("&")
|
|
898
|
+
.filter((pair) => !pair.startsWith("arch="))
|
|
899
|
+
.sort();
|
|
900
|
+
return kept.length ? `${base}?${kept.join("&")}` : base;
|
|
901
|
+
}
|
|
902
|
+
|
|
791
903
|
/**
|
|
792
904
|
* Get the packages installed in the container image filesystem.
|
|
793
905
|
*
|
|
@@ -923,80 +1035,20 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
923
1035
|
if (DEBUG_MODE) {
|
|
924
1036
|
console.log(osReleaseData);
|
|
925
1037
|
}
|
|
926
|
-
//
|
|
927
|
-
//
|
|
928
|
-
//
|
|
929
|
-
//
|
|
930
|
-
//
|
|
931
|
-
//
|
|
932
|
-
//
|
|
933
|
-
//
|
|
934
|
-
//
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
osReleaseData["CENTOS_MANTISBT_PROJECT"] ||
|
|
941
|
-
osReleaseData["REDHAT_BUGZILLA_PRODUCT"] ||
|
|
942
|
-
osReleaseData["REDHAT_SUPPORT_PRODUCT"] ||
|
|
943
|
-
""
|
|
944
|
-
).toLowerCase();
|
|
945
|
-
if (distro_display_name && OS_DISTRO_ALIAS[distro_display_name]) {
|
|
946
|
-
distro_codename = OS_DISTRO_ALIAS[distro_display_name];
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
let distro_id = osReleaseData["ID"] || "";
|
|
950
|
-
const distro_id_like = osReleaseData["ID_LIKE"] || "";
|
|
951
|
-
let purl_type = "rpm";
|
|
952
|
-
switch (distro_id) {
|
|
953
|
-
case "debian":
|
|
954
|
-
case "ubuntu":
|
|
955
|
-
case "pop":
|
|
956
|
-
purl_type = "deb";
|
|
957
|
-
break;
|
|
958
|
-
case "sles":
|
|
959
|
-
case "suse":
|
|
960
|
-
case "opensuse":
|
|
961
|
-
purl_type = "rpm";
|
|
962
|
-
break;
|
|
963
|
-
case "alpine":
|
|
964
|
-
purl_type = "apk";
|
|
965
|
-
if (osReleaseData.VERSION_ID) {
|
|
966
|
-
const versionParts = osReleaseData["VERSION_ID"].split(".");
|
|
967
|
-
if (versionParts.length >= 2) {
|
|
968
|
-
distro_codename = `alpine-${versionParts[0]}.${versionParts[1]}`;
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
break;
|
|
972
|
-
case "alpaquita":
|
|
973
|
-
purl_type = "apk";
|
|
974
|
-
break;
|
|
975
|
-
default:
|
|
976
|
-
if (distro_id_like.includes("debian")) {
|
|
977
|
-
purl_type = "deb";
|
|
978
|
-
} else if (distro_id_like.includes("alpine")) {
|
|
979
|
-
purl_type = "apk";
|
|
980
|
-
} else if (
|
|
981
|
-
distro_id_like.includes("rhel") ||
|
|
982
|
-
distro_id_like.includes("centos") ||
|
|
983
|
-
distro_id_like.includes("fedora")
|
|
984
|
-
) {
|
|
985
|
-
purl_type = "rpm";
|
|
986
|
-
}
|
|
987
|
-
break;
|
|
988
|
-
}
|
|
989
|
-
if (osReleaseData["VERSION_ID"]) {
|
|
990
|
-
distro_id = `${distro_id}-${osReleaseData["VERSION_ID"]}`;
|
|
991
|
-
if (OS_DISTRO_ALIAS[distro_id]) {
|
|
992
|
-
distro_codename = OS_DISTRO_ALIAS[distro_id];
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
// Canonicalise the distro qualifier vendor (e.g. "mariner-2.0" ->
|
|
996
|
-
// "azure-linux-2.0") so every spelling of one release yields a single
|
|
997
|
-
// qualifier value. This runs before distro_id is used to overwrite
|
|
998
|
-
// trivy's own qualifier below.
|
|
999
|
-
distro_id = canonicalDistroQualifier(distro_id);
|
|
1038
|
+
// The distro of the rootfs is decided in getDistroInfo, which an OBOM run
|
|
1039
|
+
// on the same system reads too, so a package gets the same qualifiers
|
|
1040
|
+
// from either scan. distro_name is a real release codename/token
|
|
1041
|
+
// (bookworm, jammy, rhel, alpine-3.24), never a distro *display name*:
|
|
1042
|
+
// "Rocky Linux" or "AlmaLinux" is dropped, since RPM distros track
|
|
1043
|
+
// packages flat and a display name there breaks downstream vuln-db
|
|
1044
|
+
// matching. distro is ID-VERSION_ID with the vendor canonicalised (e.g.
|
|
1045
|
+
// "mariner-2.0" -> "azure-linux-2.0"), so every spelling of one release
|
|
1046
|
+
// yields a single qualifier value; it overwrites trivy's own qualifier
|
|
1047
|
+
// below.
|
|
1048
|
+
const distroInfo = getDistroInfo(src);
|
|
1049
|
+
let distro_codename = distroInfo.distroName;
|
|
1050
|
+
const distro_id = distroInfo.distroId;
|
|
1051
|
+
const purl_type = distroInfo.purlType;
|
|
1000
1052
|
const tmpDependencies = {};
|
|
1001
1053
|
tools = enrichToolComponents(
|
|
1002
1054
|
(Array.isArray(tmpBom?.metadata?.tools)
|
|
@@ -1030,6 +1082,27 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
1030
1082
|
try {
|
|
1031
1083
|
purlObj = Purl.parse(comp.purl);
|
|
1032
1084
|
purlObj.qualifiers = purlObj.qualifiers || {};
|
|
1085
|
+
if (
|
|
1086
|
+
OS_PURL_TYPES.includes(purlObj.type) &&
|
|
1087
|
+
"arch" in purlObj.qualifiers &&
|
|
1088
|
+
!isRealArchitecture(purlObj.qualifiers.arch)
|
|
1089
|
+
) {
|
|
1090
|
+
// trivy writes arch=None for a package with no architecture,
|
|
1091
|
+
// such as an rpm's gpg-pubkey; a package has the architecture
|
|
1092
|
+
// it has, or none, as in an OBOM.
|
|
1093
|
+
delete purlObj.qualifiers.arch;
|
|
1094
|
+
comp.purl = build({
|
|
1095
|
+
type: purlObj.type,
|
|
1096
|
+
namespace: purlObj.namespace || null,
|
|
1097
|
+
name: name,
|
|
1098
|
+
version: purlObj.version || null,
|
|
1099
|
+
qualifiers: Object.keys(purlObj.qualifiers).length
|
|
1100
|
+
? purlObj.qualifiers
|
|
1101
|
+
: null,
|
|
1102
|
+
subpath: purlObj.subpath || null,
|
|
1103
|
+
});
|
|
1104
|
+
comp["bom-ref"] = decodeURIComponent(comp.purl);
|
|
1105
|
+
}
|
|
1033
1106
|
} catch (_err) {
|
|
1034
1107
|
// continue regardless of error
|
|
1035
1108
|
}
|
|
@@ -1115,16 +1188,20 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
1115
1188
|
if (distro_codename !== "") {
|
|
1116
1189
|
allTypes.add(distro_codename);
|
|
1117
1190
|
allTypes.add(purlObj.namespace);
|
|
1118
|
-
comp.purl = build({
|
|
1119
|
-
type: purlObj.type,
|
|
1120
|
-
namespace: purlObj.namespace || null,
|
|
1121
|
-
name: name,
|
|
1122
|
-
version: purlObj.version || null,
|
|
1123
|
-
qualifiers: purlObj.qualifiers || null,
|
|
1124
|
-
subpath: purlObj.subpath || null,
|
|
1125
|
-
});
|
|
1126
|
-
comp["bom-ref"] = decodeURIComponent(comp.purl);
|
|
1127
1191
|
}
|
|
1192
|
+
// Rebuilt whether or not the distro has a codename: the distro
|
|
1193
|
+
// stamped above is ID-VERSION_ID for every distro, and a purl
|
|
1194
|
+
// left as trivy wrote it keeps trivy's own spelling (a bare
|
|
1195
|
+
// 20230201 for a Wolfi image).
|
|
1196
|
+
comp.purl = build({
|
|
1197
|
+
type: purlObj.type,
|
|
1198
|
+
namespace: purlObj.namespace || null,
|
|
1199
|
+
name: name,
|
|
1200
|
+
version: purlObj.version || null,
|
|
1201
|
+
qualifiers: purlObj.qualifiers || null,
|
|
1202
|
+
subpath: purlObj.subpath || null,
|
|
1203
|
+
});
|
|
1204
|
+
comp["bom-ref"] = decodeURIComponent(comp.purl);
|
|
1128
1205
|
} catch (_err) {
|
|
1129
1206
|
// continue regardless of error
|
|
1130
1207
|
}
|
|
@@ -1325,6 +1402,14 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
1325
1402
|
if (epoch) {
|
|
1326
1403
|
newCompQualifiers.epoch = epoch;
|
|
1327
1404
|
}
|
|
1405
|
+
// A source package is built for no architecture. The deb and
|
|
1406
|
+
// rpm purl types name it `source` and `src`; copying the arch
|
|
1407
|
+
// of whichever binary led here (`all` from a -doc package)
|
|
1408
|
+
// misnames it.
|
|
1409
|
+
const sourceArch = SOURCE_PACKAGE_ARCH[purlObj.type];
|
|
1410
|
+
if (sourceArch && newCompQualifiers.arch) {
|
|
1411
|
+
newCompQualifiers.arch = sourceArch;
|
|
1412
|
+
}
|
|
1328
1413
|
newComp.purl = build({
|
|
1329
1414
|
type: purlObj.type,
|
|
1330
1415
|
namespace: purlObj.namespace || null,
|
|
@@ -1360,6 +1445,7 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
1360
1445
|
components: ownedFileComponents,
|
|
1361
1446
|
dependenciesList: ownedFileDependencies,
|
|
1362
1447
|
ownedFilePaths,
|
|
1448
|
+
ownerByPath,
|
|
1363
1449
|
services: ownedServices,
|
|
1364
1450
|
} = await createOSPackageFileComponents(src, osPackageEntries);
|
|
1365
1451
|
if (ownedFileComponents.length) {
|
|
@@ -1404,12 +1490,91 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
1404
1490
|
"/etc/ld.so.conf",
|
|
1405
1491
|
"/etc/ld.so.conf.d/*.conf",
|
|
1406
1492
|
ownedFilePaths,
|
|
1493
|
+
// Apache loads libphp*.so from a modules directory that is two levels
|
|
1494
|
+
// below /usr, which the default one-level globs miss.
|
|
1495
|
+
[
|
|
1496
|
+
"**/usr/lib/apache2/modules/libphp*.so",
|
|
1497
|
+
"**/usr/lib/httpd/modules/libphp*.so",
|
|
1498
|
+
"**/usr/lib64/httpd/modules/libphp*.so",
|
|
1499
|
+
],
|
|
1407
1500
|
),
|
|
1408
1501
|
"shared_library",
|
|
1409
1502
|
);
|
|
1503
|
+
// Go executables carry the toolchain they were built with in their
|
|
1504
|
+
// buildinfo header. The stdlib is statically linked, so that version
|
|
1505
|
+
// decides whether the binary applies Go's CVE-2026-111943 distrust list.
|
|
1506
|
+
const goBuildInventory = collectGoBuildInfo(executables, src);
|
|
1507
|
+
if (goBuildInventory.stdlibComponents.length) {
|
|
1508
|
+
pkgList.push(...goBuildInventory.stdlibComponents);
|
|
1509
|
+
}
|
|
1510
|
+
if (goBuildInventory.dependencies.length) {
|
|
1511
|
+
dependenciesList.push(...goBuildInventory.dependencies);
|
|
1512
|
+
}
|
|
1513
|
+
// Source-built PHP (the Docker Official image, FrankenPHP and custom
|
|
1514
|
+
// /usr/local builds) is unpackaged, so its version is only visible in the
|
|
1515
|
+
// binaries and the installed php_version.h.
|
|
1516
|
+
const phpBuildInventory = collectPhpBuildInfo(executables, sharedLibs, src);
|
|
1517
|
+
if (phpBuildInventory.runtimeComponents.length) {
|
|
1518
|
+
pkgList.push(...phpBuildInventory.runtimeComponents);
|
|
1519
|
+
}
|
|
1520
|
+
if (phpBuildInventory.dependencies.length) {
|
|
1521
|
+
dependenciesList.push(...phpBuildInventory.dependencies);
|
|
1522
|
+
}
|
|
1523
|
+
// Node.js from the Docker Official images, the release tarballs, nvm, fnm or
|
|
1524
|
+
// volta is unpackaged, so its version is only visible in the binary.
|
|
1525
|
+
const nodeBuildInventory = collectNodeBuildInfo(executables, sharedLibs, src);
|
|
1526
|
+
if (nodeBuildInventory.runtimeComponents.length) {
|
|
1527
|
+
pkgList.push(...nodeBuildInventory.runtimeComponents);
|
|
1528
|
+
}
|
|
1529
|
+
if (nodeBuildInventory.dependencies.length) {
|
|
1530
|
+
dependenciesList.push(...nodeBuildInventory.dependencies);
|
|
1531
|
+
}
|
|
1532
|
+
// Ruby from the Docker Official images, ruby-build, rbenv, rvm, asdf, mise
|
|
1533
|
+
// or chruby is unpackaged; its version is in rbconfig.rb and libruby.
|
|
1534
|
+
const rubyBuildInventory = collectRubyBuildInfo(executables, sharedLibs, src);
|
|
1535
|
+
if (rubyBuildInventory.runtimeComponents.length) {
|
|
1536
|
+
pkgList.push(...rubyBuildInventory.runtimeComponents);
|
|
1537
|
+
}
|
|
1538
|
+
if (rubyBuildInventory.dependencies.length) {
|
|
1539
|
+
dependenciesList.push(...rubyBuildInventory.dependencies);
|
|
1540
|
+
}
|
|
1541
|
+
// A Ruby an OS package installed is named by that package; it also gets
|
|
1542
|
+
// its upstream identity, without a CPE, so a BOM names the interpreter both
|
|
1543
|
+
// ways.
|
|
1544
|
+
pkgList.push(...deriveOsPackagedRubyRuntimes(pkgList));
|
|
1545
|
+
// The .NET images and dotnet-install.sh put the runtime under a dotnet root
|
|
1546
|
+
// without a package manager; its deps.json names the runtime pack.
|
|
1547
|
+
const dotnetRuntimes = collectDotnetRuntimes(src, ownedFilePaths);
|
|
1548
|
+
if (dotnetRuntimes.length) {
|
|
1549
|
+
pkgList.push(...dotnetRuntimes);
|
|
1550
|
+
for (const runtime of dotnetRuntimes) {
|
|
1551
|
+
dependenciesList.push({ ref: runtime["bom-ref"], dependsOn: [] });
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
// A Brotli decoder compiled into a native file that no package manager owns
|
|
1555
|
+
// (Node.js, Python wheels, PHP and Ruby extensions, the .NET runtime, static
|
|
1556
|
+
// binaries) is only visible by reading the file.
|
|
1557
|
+
const brotliInventory = await collectBrotliDecoders(
|
|
1558
|
+
executables,
|
|
1559
|
+
sharedLibs,
|
|
1560
|
+
src,
|
|
1561
|
+
ownedFilePaths,
|
|
1562
|
+
);
|
|
1563
|
+
|
|
1564
|
+
if (brotliInventory.components.length) {
|
|
1565
|
+
pkgList.push(...brotliInventory.components);
|
|
1566
|
+
}
|
|
1567
|
+
if (brotliInventory.dependencies.length) {
|
|
1568
|
+
dependenciesList.push(...brotliInventory.dependencies);
|
|
1569
|
+
}
|
|
1410
1570
|
return {
|
|
1411
1571
|
osPackages: pkgList,
|
|
1412
1572
|
osPackageFiles,
|
|
1573
|
+
// Extension modules that carry a Brotli decoder. They are reported apart
|
|
1574
|
+
// from sharedLibs so the unpackaged-library count still counts what the
|
|
1575
|
+
// loader's directories hold.
|
|
1576
|
+
nativeExtensionFiles: brotliInventory.extensionFiles,
|
|
1577
|
+
ownerByPath,
|
|
1413
1578
|
dependenciesList,
|
|
1414
1579
|
allTypes: Array.from(allTypes).sort(),
|
|
1415
1580
|
bundledSdks: Array.from(bundledSdks).sort(),
|
|
@@ -1545,17 +1710,24 @@ function mergeOrganizationalEntityField(component, fieldName, entityName) {
|
|
|
1545
1710
|
return { applied: false, represented: false };
|
|
1546
1711
|
}
|
|
1547
1712
|
|
|
1548
|
-
|
|
1713
|
+
/**
|
|
1714
|
+
* Split a `Name <address>` maintainer string into an `organizationalContact`.
|
|
1715
|
+
*
|
|
1716
|
+
* @param {string} value Maintainer or supplier string
|
|
1717
|
+
* @returns {{name: string, email?: string}|undefined} The contact, if any
|
|
1718
|
+
*/
|
|
1719
|
+
export function parseOrganizationalContact(value) {
|
|
1549
1720
|
const normalizedValue = `${value || ""}`.trim();
|
|
1550
1721
|
if (!normalizedValue) {
|
|
1551
1722
|
return undefined;
|
|
1552
1723
|
}
|
|
1553
1724
|
const match = normalizedValue.match(/^([^<>]+?)\s*<([^<>\s]+@[^<>\s]+)>$/);
|
|
1554
1725
|
if (match) {
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1726
|
+
const name = match[1].trim();
|
|
1727
|
+
const email = match[2].trim();
|
|
1728
|
+
// `organizationalContact.email` is an `idn-email`; an address that is not
|
|
1729
|
+
// one (say `a@b@c`) would fail schema validation, so keep only the name.
|
|
1730
|
+
return isValidIdnEmail(email) ? { name, email } : { name };
|
|
1559
1731
|
}
|
|
1560
1732
|
return { name: normalizedValue };
|
|
1561
1733
|
}
|
|
@@ -1644,13 +1816,34 @@ function promoteTrivyOsPackageIdentity(component, trivyMetadata) {
|
|
|
1644
1816
|
async function collectRootfsRepositoryInventory(basePath, options = {}) {
|
|
1645
1817
|
let trustedKeyComponents = [];
|
|
1646
1818
|
let refsByPath = new Map();
|
|
1819
|
+
let trustMaterialDependencies = [];
|
|
1647
1820
|
if (isCycloneDxComponentTypeEnabled("cryptographic-asset", options)) {
|
|
1648
1821
|
({ components: trustedKeyComponents, refsByPath } =
|
|
1649
1822
|
await collectTrustedKeyComponents(basePath));
|
|
1650
|
-
|
|
1823
|
+
({
|
|
1824
|
+
components: trustedKeyComponents,
|
|
1825
|
+
dependencies: trustMaterialDependencies,
|
|
1826
|
+
} = applyTrustMaterialEnhancements(
|
|
1651
1827
|
trustedKeyComponents,
|
|
1652
1828
|
collectTrustInspectorRootfsInventory(basePath),
|
|
1653
|
-
|
|
1829
|
+
basePath,
|
|
1830
|
+
));
|
|
1831
|
+
if (!TRUSTINSPECTOR_BIN) {
|
|
1832
|
+
// No plugin: walk the same locations in JavaScript. With the plugin
|
|
1833
|
+
// present its rootfs report is the only producer, so nothing is
|
|
1834
|
+
// counted twice.
|
|
1835
|
+
const fallback = collectRootfsCertificates(basePath);
|
|
1836
|
+
const knownRefs = new Set(trustedKeyComponents.map((c) => c["bom-ref"]));
|
|
1837
|
+
for (const component of fallback.components) {
|
|
1838
|
+
if (!knownRefs.has(component["bom-ref"])) {
|
|
1839
|
+
knownRefs.add(component["bom-ref"]);
|
|
1840
|
+
trustedKeyComponents.push(component);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
trustMaterialDependencies = trustMaterialDependencies.concat(
|
|
1844
|
+
fallback.dependencies,
|
|
1845
|
+
);
|
|
1846
|
+
}
|
|
1654
1847
|
}
|
|
1655
1848
|
refsByPath = new Map(
|
|
1656
1849
|
trustedKeyComponents
|
|
@@ -1692,6 +1885,7 @@ async function collectRootfsRepositoryInventory(basePath, options = {}) {
|
|
|
1692
1885
|
});
|
|
1693
1886
|
}
|
|
1694
1887
|
}
|
|
1888
|
+
dependenciesList.push(...trustMaterialDependencies);
|
|
1695
1889
|
return { components, dependenciesList };
|
|
1696
1890
|
}
|
|
1697
1891
|
|
|
@@ -1877,7 +2071,9 @@ function executeTrustInspector(args, activity) {
|
|
|
1877
2071
|
if (DEBUG_MODE) {
|
|
1878
2072
|
console.log("Executing", TRUSTINSPECTOR_BIN, args.join(" "));
|
|
1879
2073
|
}
|
|
1880
|
-
const result = safeSpawnSync(TRUSTINSPECTOR_BIN, args
|
|
2074
|
+
const result = safeSpawnSync(TRUSTINSPECTOR_BIN, args, {
|
|
2075
|
+
timeout: 10 * 60 * 1000,
|
|
2076
|
+
});
|
|
1881
2077
|
if (result?.status !== 0 || result?.error) {
|
|
1882
2078
|
if (DEBUG_MODE && (result?.stdout || result?.stderr)) {
|
|
1883
2079
|
console.error(result.stdout, result.stderr);
|
|
@@ -1991,91 +2187,54 @@ function createTrustMaterialComponent(material) {
|
|
|
1991
2187
|
? [{ name: "cdx:crypto:expiresAt", value: material.expiresAt }]
|
|
1992
2188
|
: []),
|
|
1993
2189
|
]);
|
|
1994
|
-
let component;
|
|
1995
2190
|
if (material.kind === "certificate") {
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
notValidBefore: material.createdAt || undefined,
|
|
2018
|
-
notValidAfter: material.expiresAt || undefined,
|
|
2019
|
-
certificateFormat: material.format || "X.509",
|
|
2020
|
-
certificateFileExtension: material.fileExtension || undefined,
|
|
2021
|
-
fingerprint: material.fingerprint
|
|
2022
|
-
? { alg: "SHA-256", content: material.fingerprint }
|
|
2023
|
-
: undefined,
|
|
2024
|
-
},
|
|
2191
|
+
// Certificates are built by the shared collector in
|
|
2192
|
+
// applyTrustMaterialEnhancements; this legacy builder no longer produces
|
|
2193
|
+
// them, so a certificate material reaching here is skipped.
|
|
2194
|
+
return undefined;
|
|
2195
|
+
}
|
|
2196
|
+
const component = {
|
|
2197
|
+
"bom-ref": `crypto/related-crypto-material/public-key/${encodeURIComponent(normalizedPath)}@${material.sha256 ? `sha256:${material.sha256}` : material.keyId || "unknown"}`,
|
|
2198
|
+
name: material.name || basename(normalizedPath),
|
|
2199
|
+
type: "cryptographic-asset",
|
|
2200
|
+
version: material.sha256 || material.keyId || normalizedPath,
|
|
2201
|
+
hashes,
|
|
2202
|
+
cryptoProperties: {
|
|
2203
|
+
assetType: "related-crypto-material",
|
|
2204
|
+
relatedCryptoMaterialProperties: {
|
|
2205
|
+
type: "public-key",
|
|
2206
|
+
id:
|
|
2207
|
+
material.keyId ||
|
|
2208
|
+
material.fingerprint ||
|
|
2209
|
+
material.sha256 ||
|
|
2210
|
+
normalizedPath,
|
|
2211
|
+
state: trustMaterialState(material),
|
|
2025
2212
|
},
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
),
|
|
2033
|
-
};
|
|
2034
|
-
} else {
|
|
2035
|
-
component = {
|
|
2036
|
-
"bom-ref": `crypto/related-crypto-material/public-key/${encodeURIComponent(normalizedPath)}@${material.sha256 ? `sha256:${material.sha256}` : material.keyId || "unknown"}`,
|
|
2037
|
-
name: material.name || basename(normalizedPath),
|
|
2038
|
-
type: "cryptographic-asset",
|
|
2039
|
-
version: material.sha256 || material.keyId || normalizedPath,
|
|
2040
|
-
hashes,
|
|
2041
|
-
cryptoProperties: {
|
|
2042
|
-
assetType: "related-crypto-material",
|
|
2043
|
-
relatedCryptoMaterialProperties: {
|
|
2044
|
-
type: "public-key",
|
|
2045
|
-
id:
|
|
2046
|
-
material.keyId ||
|
|
2047
|
-
material.fingerprint ||
|
|
2048
|
-
material.sha256 ||
|
|
2049
|
-
normalizedPath,
|
|
2050
|
-
state: trustMaterialState(material),
|
|
2213
|
+
},
|
|
2214
|
+
properties: uniqueProperties(
|
|
2215
|
+
sharedProperties.concat([
|
|
2216
|
+
{
|
|
2217
|
+
name: "cdx:crypto:trustDomain",
|
|
2218
|
+
value: material.trustDomain || deriveTrustedKeyDomain(normalizedPath),
|
|
2051
2219
|
},
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
{
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
: []),
|
|
2071
|
-
...(material?.userIds || []).map((value) => ({
|
|
2072
|
-
name: "cdx:crypto:userId",
|
|
2073
|
-
value,
|
|
2074
|
-
})),
|
|
2075
|
-
]),
|
|
2076
|
-
),
|
|
2077
|
-
};
|
|
2078
|
-
}
|
|
2220
|
+
{ name: "cdx:crypto:keyPath", value: normalizedPath },
|
|
2221
|
+
{
|
|
2222
|
+
name: "cdx:crypto:fileExtension",
|
|
2223
|
+
value:
|
|
2224
|
+
material.fileExtension ||
|
|
2225
|
+
extname(normalizedPath).replace(/^\./, "") ||
|
|
2226
|
+
"gpg",
|
|
2227
|
+
},
|
|
2228
|
+
...(material?.keyId
|
|
2229
|
+
? [{ name: "cdx:crypto:keyId", value: material.keyId }]
|
|
2230
|
+
: []),
|
|
2231
|
+
...(material?.userIds || []).map((value) => ({
|
|
2232
|
+
name: "cdx:crypto:userId",
|
|
2233
|
+
value,
|
|
2234
|
+
})),
|
|
2235
|
+
]),
|
|
2236
|
+
),
|
|
2237
|
+
};
|
|
2079
2238
|
attachIdentityTools(component, trustInspectorToolRefs());
|
|
2080
2239
|
return component;
|
|
2081
2240
|
}
|
|
@@ -2100,9 +2259,73 @@ function enhanceComponentFromTrustMaterial(component, material) {
|
|
|
2100
2259
|
return component;
|
|
2101
2260
|
}
|
|
2102
2261
|
|
|
2103
|
-
function
|
|
2262
|
+
function createKeystoreComponent(material) {
|
|
2263
|
+
const normalizedPath = normalizeContainerPath(material?.path);
|
|
2264
|
+
if (!normalizedPath) {
|
|
2265
|
+
return undefined;
|
|
2266
|
+
}
|
|
2267
|
+
const readable = (material?.properties || []).find(
|
|
2268
|
+
(property) => property.name === "cdx:crypto:keystoreReadable",
|
|
2269
|
+
)?.value;
|
|
2270
|
+
const component = {
|
|
2271
|
+
"bom-ref": `crypto/related-crypto-material/keystore/${encodeURIComponent(normalizedPath)}`,
|
|
2272
|
+
name: basename(normalizedPath),
|
|
2273
|
+
type: "cryptographic-asset",
|
|
2274
|
+
cryptoProperties: {
|
|
2275
|
+
assetType: "related-crypto-material",
|
|
2276
|
+
relatedCryptoMaterialProperties: {
|
|
2277
|
+
type: "other",
|
|
2278
|
+
...(material?.format ? { format: material.format } : {}),
|
|
2279
|
+
},
|
|
2280
|
+
},
|
|
2281
|
+
evidence: { occurrences: [{ location: normalizedPath }] },
|
|
2282
|
+
properties: [
|
|
2283
|
+
{ name: "internal:SrcFile", value: normalizedPath },
|
|
2284
|
+
...(material?.sourceType
|
|
2285
|
+
? [{ name: "cdx:crypto:sourceType", value: material.sourceType }]
|
|
2286
|
+
: []),
|
|
2287
|
+
...(readable
|
|
2288
|
+
? [{ name: "cdx:crypto:keystoreReadable", value: readable }]
|
|
2289
|
+
: []),
|
|
2290
|
+
],
|
|
2291
|
+
};
|
|
2292
|
+
attachIdentityTools(component, trustInspectorToolRefs());
|
|
2293
|
+
return component;
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
// Private keys are classified from the PEM header line only and never
|
|
2297
|
+
// decoded (PLAN section 1.7): the format travels, no key material does.
|
|
2298
|
+
function createPrivateKeyComponent(material) {
|
|
2299
|
+
const normalizedPath = normalizeContainerPath(material?.path);
|
|
2300
|
+
if (!normalizedPath) {
|
|
2301
|
+
return undefined;
|
|
2302
|
+
}
|
|
2303
|
+
const component = {
|
|
2304
|
+
"bom-ref": `crypto/related-crypto-material/private-key/${encodeURIComponent(normalizedPath)}`,
|
|
2305
|
+
name: basename(normalizedPath),
|
|
2306
|
+
type: "cryptographic-asset",
|
|
2307
|
+
cryptoProperties: {
|
|
2308
|
+
assetType: "related-crypto-material",
|
|
2309
|
+
relatedCryptoMaterialProperties: {
|
|
2310
|
+
type: "private-key",
|
|
2311
|
+
...(material?.format ? { format: material.format } : {}),
|
|
2312
|
+
},
|
|
2313
|
+
},
|
|
2314
|
+
evidence: { occurrences: [{ location: normalizedPath }] },
|
|
2315
|
+
properties: [
|
|
2316
|
+
{ name: "internal:SrcFile", value: normalizedPath },
|
|
2317
|
+
...(material?.sourceType
|
|
2318
|
+
? [{ name: "cdx:crypto:sourceType", value: material.sourceType }]
|
|
2319
|
+
: []),
|
|
2320
|
+
],
|
|
2321
|
+
};
|
|
2322
|
+
attachIdentityTools(component, trustInspectorToolRefs());
|
|
2323
|
+
return component;
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
function applyTrustMaterialEnhancements(components, materials, basePath) {
|
|
2104
2327
|
if (!materials?.length) {
|
|
2105
|
-
return components || [];
|
|
2328
|
+
return { components: components || [], dependencies: [] };
|
|
2106
2329
|
}
|
|
2107
2330
|
const componentList = [...(components || [])];
|
|
2108
2331
|
const bySrcFile = new Map();
|
|
@@ -2119,6 +2342,8 @@ function applyTrustMaterialEnhancements(components, materials) {
|
|
|
2119
2342
|
}
|
|
2120
2343
|
}
|
|
2121
2344
|
for (const material of materials) {
|
|
2345
|
+
// GPG-style keyring materials (report-1 `kind: public-key`) enhance the
|
|
2346
|
+
// components the native readers already produced.
|
|
2122
2347
|
const srcFile = normalizeContainerPath(material?.path);
|
|
2123
2348
|
const existing = bySrcFile.get(srcFile);
|
|
2124
2349
|
if (
|
|
@@ -2129,17 +2354,95 @@ function applyTrustMaterialEnhancements(components, materials) {
|
|
|
2129
2354
|
enhanceComponentFromTrustMaterial(existing, material);
|
|
2130
2355
|
continue;
|
|
2131
2356
|
}
|
|
2132
|
-
|
|
2357
|
+
if (material?.kind === "public-key") {
|
|
2358
|
+
const component = createTrustMaterialComponent(material);
|
|
2359
|
+
if (!component?.["bom-ref"] || seenRefs.has(component["bom-ref"])) {
|
|
2360
|
+
continue;
|
|
2361
|
+
}
|
|
2362
|
+
seenRefs.add(component["bom-ref"]);
|
|
2363
|
+
componentList.push(component);
|
|
2364
|
+
if (srcFile) {
|
|
2365
|
+
bySrcFile.set(srcFile, component);
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
// Certificates, their SPKI public keys, algorithm components and verified
|
|
2370
|
+
// issuer edges come from the shared collector; keystores and private keys
|
|
2371
|
+
// are their own components.
|
|
2372
|
+
const collector = new CertificateCollector();
|
|
2373
|
+
collector.addTrustInspectorMaterials(materials);
|
|
2374
|
+
const { components: cryptoComponents, dependencies } = collector.build();
|
|
2375
|
+
for (const material of materials) {
|
|
2376
|
+
if (material?.kind === "keystore") {
|
|
2377
|
+
cryptoComponents.push(createKeystoreComponent(material));
|
|
2378
|
+
} else if (material?.kind === "private-key") {
|
|
2379
|
+
cryptoComponents.push(createPrivateKeyComponent(material));
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
// A keystore the plugin could read is reported as the certificates it
|
|
2383
|
+
// holds; give it the keystore component the unreadable ones get and link
|
|
2384
|
+
// it to those certificates.
|
|
2385
|
+
if (basePath) {
|
|
2386
|
+
const certMaterialByPath = new Map();
|
|
2387
|
+
for (const material of materials) {
|
|
2388
|
+
if (material?.kind === "certificate" && material.path) {
|
|
2389
|
+
certMaterialByPath.set(normalizeContainerPath(material.path), material);
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
for (const [path, material] of certMaterialByPath) {
|
|
2393
|
+
let classification;
|
|
2394
|
+
try {
|
|
2395
|
+
const absolute = join(basePath, path.replace(/^\/+/, ""));
|
|
2396
|
+
// The path comes from the plugin report: it must stay inside the
|
|
2397
|
+
// scanned root, symlinks included.
|
|
2398
|
+
const realRoot = realpathSync(basePath);
|
|
2399
|
+
const realFile = realpathSync(absolute);
|
|
2400
|
+
const inside = relative(realRoot, realFile);
|
|
2401
|
+
if (inside.startsWith("..") || isAbsolute(inside)) {
|
|
2402
|
+
continue;
|
|
2403
|
+
}
|
|
2404
|
+
const stats = lstatSync(absolute);
|
|
2405
|
+
if (!stats.isFile() || stats.size > 16 * 1024 * 1024) {
|
|
2406
|
+
continue;
|
|
2407
|
+
}
|
|
2408
|
+
classification = classifyCryptoFile(readFileSync(absolute));
|
|
2409
|
+
} catch (_err) {
|
|
2410
|
+
continue;
|
|
2411
|
+
}
|
|
2412
|
+
if (classification.kind !== "keystore") {
|
|
2413
|
+
continue;
|
|
2414
|
+
}
|
|
2415
|
+
const keystore = createKeystoreComponent({
|
|
2416
|
+
path,
|
|
2417
|
+
format: classification.format,
|
|
2418
|
+
sourceType: material.sourceType,
|
|
2419
|
+
});
|
|
2420
|
+
cryptoComponents.push(keystore);
|
|
2421
|
+
const held = cryptoComponents
|
|
2422
|
+
.filter(
|
|
2423
|
+
(component) =>
|
|
2424
|
+
component.cryptoProperties?.assetType === "certificate" &&
|
|
2425
|
+
component.evidence?.occurrences?.some(
|
|
2426
|
+
(occurrence) =>
|
|
2427
|
+
normalizeContainerPath(occurrence.location) === path,
|
|
2428
|
+
),
|
|
2429
|
+
)
|
|
2430
|
+
.map((component) => component["bom-ref"])
|
|
2431
|
+
.sort();
|
|
2432
|
+
if (held.length) {
|
|
2433
|
+
dependencies.push({ ref: keystore["bom-ref"], dependsOn: held });
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
for (const component of cryptoComponents) {
|
|
2133
2438
|
if (!component?.["bom-ref"] || seenRefs.has(component["bom-ref"])) {
|
|
2134
2439
|
continue;
|
|
2135
2440
|
}
|
|
2136
2441
|
seenRefs.add(component["bom-ref"]);
|
|
2442
|
+
attachIdentityTools(component, trustInspectorToolRefs());
|
|
2137
2443
|
componentList.push(component);
|
|
2138
|
-
if (srcFile) {
|
|
2139
|
-
bySrcFile.set(srcFile, component);
|
|
2140
|
-
}
|
|
2141
2444
|
}
|
|
2142
|
-
return componentList;
|
|
2445
|
+
return { components: componentList, dependencies };
|
|
2143
2446
|
}
|
|
2144
2447
|
|
|
2145
2448
|
function collectTrustInspectorRootfsInventory(basePath) {
|
|
@@ -2510,6 +2813,7 @@ async function createOSPackageFileComponents(basePath, osPackageEntries) {
|
|
|
2510
2813
|
const components = [];
|
|
2511
2814
|
const dependenciesList = [];
|
|
2512
2815
|
const ownedFilePaths = new Set();
|
|
2816
|
+
const ownerByPath = new Map();
|
|
2513
2817
|
const services = [];
|
|
2514
2818
|
const componentByPath = new Map();
|
|
2515
2819
|
for (const packageEntry of osPackageEntries) {
|
|
@@ -2520,6 +2824,10 @@ async function createOSPackageFileComponents(basePath, osPackageEntries) {
|
|
|
2520
2824
|
continue;
|
|
2521
2825
|
}
|
|
2522
2826
|
ownedFilePaths.add(filePath);
|
|
2827
|
+
const ownerKey = filePath.replaceAll("\\", "/").replace(/^\/+/, "");
|
|
2828
|
+
if (!ownerByPath.has(ownerKey)) {
|
|
2829
|
+
ownerByPath.set(ownerKey, packageEntry.packageRef);
|
|
2830
|
+
}
|
|
2523
2831
|
let fileComponent = componentByPath.get(filePath);
|
|
2524
2832
|
if (!fileComponent) {
|
|
2525
2833
|
fileComponent = await createOSPackageFileComponent(
|
|
@@ -2558,6 +2866,7 @@ async function createOSPackageFileComponents(basePath, osPackageEntries) {
|
|
|
2558
2866
|
components,
|
|
2559
2867
|
dependenciesList,
|
|
2560
2868
|
ownedFilePaths: Array.from(ownedFilePaths).sort(),
|
|
2869
|
+
ownerByPath,
|
|
2561
2870
|
services: dedupeServices(services),
|
|
2562
2871
|
};
|
|
2563
2872
|
}
|
|
@@ -3043,7 +3352,10 @@ function detectSdksRuntimes(comp, bundledSdks, bundledRuntimes) {
|
|
|
3043
3352
|
bundledRuntimes.add(comp.name);
|
|
3044
3353
|
}
|
|
3045
3354
|
// TODO: Need to test this for a range of base images
|
|
3046
|
-
if (
|
|
3355
|
+
if (
|
|
3356
|
+
COMMON_RUNTIMES.includes(comp.name) ||
|
|
3357
|
+
VERSIONED_RUNTIME_PATTERNS.some((pattern) => pattern.test(comp.name))
|
|
3358
|
+
) {
|
|
3047
3359
|
bundledRuntimes.add(comp.name);
|
|
3048
3360
|
}
|
|
3049
3361
|
}
|
|
@@ -3299,10 +3611,37 @@ export function enrichOSComponentsWithTrustData(components = []) {
|
|
|
3299
3611
|
if (hostComponents.length) {
|
|
3300
3612
|
mergedComponents.push(...hostComponents);
|
|
3301
3613
|
}
|
|
3614
|
+
// report-2 host mode enumerates the platform trust stores as materials;
|
|
3615
|
+
// they flow through the shared certificate model like any other producer.
|
|
3616
|
+
const hostCollector = new CertificateCollector();
|
|
3617
|
+
hostCollector.addTrustInspectorMaterials(hostData?.materials);
|
|
3618
|
+
const { components: hostMaterialComponents } = hostCollector.build();
|
|
3619
|
+
for (const material of hostData?.materials || []) {
|
|
3620
|
+
if (material?.kind === "keystore") {
|
|
3621
|
+
const component = createKeystoreComponent(material);
|
|
3622
|
+
if (component) {
|
|
3623
|
+
hostMaterialComponents.push(component);
|
|
3624
|
+
}
|
|
3625
|
+
} else if (material?.kind === "private-key") {
|
|
3626
|
+
const component = createPrivateKeyComponent(material);
|
|
3627
|
+
if (component) {
|
|
3628
|
+
hostMaterialComponents.push(component);
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
for (const component of hostMaterialComponents) {
|
|
3633
|
+
if (!component?.["bom-ref"]) {
|
|
3634
|
+
continue;
|
|
3635
|
+
}
|
|
3636
|
+
attachIdentityTools(component, trustInspectorToolRefs());
|
|
3637
|
+
mergedComponents.push(component);
|
|
3638
|
+
}
|
|
3302
3639
|
return {
|
|
3303
3640
|
components: mergedComponents,
|
|
3304
3641
|
tools:
|
|
3305
|
-
pathInspectionCandidates.length ||
|
|
3642
|
+
pathInspectionCandidates.length ||
|
|
3643
|
+
hostComponents.length ||
|
|
3644
|
+
hostMaterialComponents.length
|
|
3306
3645
|
? getPluginToolComponents(["trustinspector"])
|
|
3307
3646
|
: [],
|
|
3308
3647
|
};
|
|
@@ -3473,6 +3812,347 @@ export function getBinaryBom(src, binaryBomFile, deepMode) {
|
|
|
3473
3812
|
return true;
|
|
3474
3813
|
}
|
|
3475
3814
|
|
|
3815
|
+
// The Go standard library has no module path, so `pkg:golang/stdlib@v<x>` is
|
|
3816
|
+
// not a purl cdx-purl will build (the same reason trivy's stdlib purl is
|
|
3817
|
+
// dropped further up). The bom-ref below is the decoded form of that string,
|
|
3818
|
+
// exactly the ref trivy-produced stdlib components already carry, so both
|
|
3819
|
+
// paths land on one component. Trivy reports the stdlib version without the
|
|
3820
|
+
// `go` prefix (`v1.27.2`) while buildinfo keeps it (`go1.27.2`).
|
|
3821
|
+
function goStdlibRef(goVersion) {
|
|
3822
|
+
// "go1.26.10 X:boringcrypto" names the toolchain go1.26.10.
|
|
3823
|
+
return `pkg:golang/stdlib@v${goVersion.split(/\s/)[0].replace(/^go/, "")}`;
|
|
3824
|
+
}
|
|
3825
|
+
|
|
3826
|
+
/**
|
|
3827
|
+
* Read the Go buildinfo version of each executable file component and link it
|
|
3828
|
+
* to (or create) the matching `pkg:golang/stdlib` component. Mutates the
|
|
3829
|
+
* components in place by adding `cdx:go:buildVersion`.
|
|
3830
|
+
*
|
|
3831
|
+
* @param {Object[]} executables file components from `fileComponents(.., "executable")`
|
|
3832
|
+
* @param {string} basePath extracted rootfs the paths are relative to
|
|
3833
|
+
* @returns {{stdlibComponents: Object[], dependencies: Object[]}}
|
|
3834
|
+
*/
|
|
3835
|
+
function collectGoBuildInfo(executables, basePath) {
|
|
3836
|
+
const stdlibComponents = [];
|
|
3837
|
+
const dependencies = [];
|
|
3838
|
+
if (!executables?.length) {
|
|
3839
|
+
return { stdlibComponents, dependencies };
|
|
3840
|
+
}
|
|
3841
|
+
const stdlibRefs = new Set();
|
|
3842
|
+
for (const component of executables) {
|
|
3843
|
+
if (!component?.properties) {
|
|
3844
|
+
continue;
|
|
3845
|
+
}
|
|
3846
|
+
const srcFile = component.properties.find(
|
|
3847
|
+
(property) => property.name === "internal:SrcFile",
|
|
3848
|
+
)?.value;
|
|
3849
|
+
if (!srcFile) {
|
|
3850
|
+
continue;
|
|
3851
|
+
}
|
|
3852
|
+
const goVersion = readGoBuildVersion(
|
|
3853
|
+
join(basePath, srcFile.replace(/^\/+/, "")),
|
|
3854
|
+
);
|
|
3855
|
+
if (!goVersion) {
|
|
3856
|
+
continue;
|
|
3857
|
+
}
|
|
3858
|
+
component.properties.push({
|
|
3859
|
+
name: "cdx:go:buildVersion",
|
|
3860
|
+
value: goVersion,
|
|
3861
|
+
});
|
|
3862
|
+
// A pre-1.18 toolchain has no version string to build a stdlib ref
|
|
3863
|
+
// from; the buildVersion property alone records the fact.
|
|
3864
|
+
if (goVersion === PRE_1_18_VERSION) {
|
|
3865
|
+
continue;
|
|
3866
|
+
}
|
|
3867
|
+
const stdlibRef = goStdlibRef(goVersion);
|
|
3868
|
+
if (!stdlibRefs.has(stdlibRef)) {
|
|
3869
|
+
stdlibRefs.add(stdlibRef);
|
|
3870
|
+
stdlibComponents.push({
|
|
3871
|
+
"bom-ref": stdlibRef,
|
|
3872
|
+
name: "stdlib",
|
|
3873
|
+
type: "library",
|
|
3874
|
+
version: stdlibRef.slice("pkg:golang/stdlib@".length),
|
|
3875
|
+
group: "",
|
|
3876
|
+
});
|
|
3877
|
+
dependencies.push({ ref: stdlibRef, dependsOn: [] });
|
|
3878
|
+
}
|
|
3879
|
+
if (component["bom-ref"]) {
|
|
3880
|
+
dependencies.push({
|
|
3881
|
+
ref: component["bom-ref"],
|
|
3882
|
+
dependsOn: [stdlibRef],
|
|
3883
|
+
});
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3886
|
+
return { stdlibComponents, dependencies };
|
|
3887
|
+
}
|
|
3888
|
+
|
|
3889
|
+
/**
|
|
3890
|
+
* Read the PHP build version of each PHP-named executable and shared library,
|
|
3891
|
+
* add the cdx:php:* properties, and link the components to one platform
|
|
3892
|
+
* component per distinct version. Mutates the components in place, as the Go
|
|
3893
|
+
* collector does.
|
|
3894
|
+
*
|
|
3895
|
+
* @param {Object[]} executables file components from `fileComponents(.., "executable")`
|
|
3896
|
+
* @param {Object[]} sharedLibs file components from `fileComponents(.., "shared_library")`
|
|
3897
|
+
* @param {string} basePath extracted rootfs the paths are relative to
|
|
3898
|
+
* @returns {{runtimeComponents: Object[], dependencies: Object[]}}
|
|
3899
|
+
*/
|
|
3900
|
+
function collectPhpBuildInfo(executables, sharedLibs, basePath) {
|
|
3901
|
+
const inventory = createPhpRuntimeInventory();
|
|
3902
|
+
let probes = 0;
|
|
3903
|
+
for (const component of [...(executables || []), ...(sharedLibs || [])]) {
|
|
3904
|
+
if (probes >= MAX_PHP_PROBES) {
|
|
3905
|
+
break;
|
|
3906
|
+
}
|
|
3907
|
+
if (!component?.properties) {
|
|
3908
|
+
continue;
|
|
3909
|
+
}
|
|
3910
|
+
const srcFile = component.properties.find(
|
|
3911
|
+
(property) => property.name === "internal:SrcFile",
|
|
3912
|
+
)?.value;
|
|
3913
|
+
if (!srcFile) {
|
|
3914
|
+
continue;
|
|
3915
|
+
}
|
|
3916
|
+
const sapi = classifyPhpSapi(srcFile);
|
|
3917
|
+
if (!sapi) {
|
|
3918
|
+
continue;
|
|
3919
|
+
}
|
|
3920
|
+
const absolutePath = join(basePath, srcFile.replace(/^\/+/, ""));
|
|
3921
|
+
let resolvedPath = srcFile;
|
|
3922
|
+
try {
|
|
3923
|
+
resolvedPath = relative(
|
|
3924
|
+
realpathSync(basePath),
|
|
3925
|
+
realpathSync(absolutePath),
|
|
3926
|
+
);
|
|
3927
|
+
} catch {
|
|
3928
|
+
// readPhpBuildVersion decides what an unresolvable path yields.
|
|
3929
|
+
}
|
|
3930
|
+
// Trivy does not list Nix, Guix or Homebrew packages, so a PHP in their
|
|
3931
|
+
// stores looks unpackaged here, but it is still theirs.
|
|
3932
|
+
if (packageStoreOf(srcFile) || packageStoreOf(resolvedPath)) {
|
|
3933
|
+
continue;
|
|
3934
|
+
}
|
|
3935
|
+
probes += 1;
|
|
3936
|
+
const build = readPhpBuildVersion(absolutePath, basePath);
|
|
3937
|
+
if (!build) {
|
|
3938
|
+
continue;
|
|
3939
|
+
}
|
|
3940
|
+
attachPhpBuild(inventory, component, build, sapi);
|
|
3941
|
+
}
|
|
3942
|
+
return {
|
|
3943
|
+
runtimeComponents: inventory.runtimeComponents,
|
|
3944
|
+
dependencies: inventory.dependencies,
|
|
3945
|
+
};
|
|
3946
|
+
}
|
|
3947
|
+
|
|
3948
|
+
/**
|
|
3949
|
+
* Read the Node.js build version of each node-named executable and libnode
|
|
3950
|
+
* shared library, add the cdx:node:* properties, and link the components to
|
|
3951
|
+
* one platform component per distinct version. Mutates the components in
|
|
3952
|
+
* place, as the PHP collector does.
|
|
3953
|
+
*
|
|
3954
|
+
* @param {Object[]} executables file components from `fileComponents(.., "executable")`
|
|
3955
|
+
* @param {Object[]} sharedLibs file components from `fileComponents(.., "shared_library")`
|
|
3956
|
+
* @param {string} basePath extracted rootfs the paths are relative to
|
|
3957
|
+
* @returns {{runtimeComponents: Object[], dependencies: Object[]}}
|
|
3958
|
+
*/
|
|
3959
|
+
function collectNodeBuildInfo(executables, sharedLibs, basePath) {
|
|
3960
|
+
const inventory = createNodeRuntimeInventory();
|
|
3961
|
+
let probes = 0;
|
|
3962
|
+
for (const component of [...(executables || []), ...(sharedLibs || [])]) {
|
|
3963
|
+
if (probes >= MAX_NODE_PROBES) {
|
|
3964
|
+
break;
|
|
3965
|
+
}
|
|
3966
|
+
const srcFile = component?.properties?.find(
|
|
3967
|
+
(property) => property.name === "internal:SrcFile",
|
|
3968
|
+
)?.value;
|
|
3969
|
+
if (!srcFile || !(isNodeBinaryName(srcFile) || isLibnode(srcFile))) {
|
|
3970
|
+
continue;
|
|
3971
|
+
}
|
|
3972
|
+
if (packageStoreOf(srcFile)) {
|
|
3973
|
+
continue;
|
|
3974
|
+
}
|
|
3975
|
+
probes += 1;
|
|
3976
|
+
const build = readNodeBuildVersion(
|
|
3977
|
+
join(basePath, srcFile.replace(/^\/+/, "")),
|
|
3978
|
+
basePath,
|
|
3979
|
+
);
|
|
3980
|
+
if (build) {
|
|
3981
|
+
attachNodeBuild(inventory, component, build);
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
return {
|
|
3985
|
+
runtimeComponents: inventory.runtimeComponents,
|
|
3986
|
+
dependencies: inventory.dependencies,
|
|
3987
|
+
};
|
|
3988
|
+
}
|
|
3989
|
+
|
|
3990
|
+
/**
|
|
3991
|
+
* Read the Ruby build version of each unpackaged ruby executable and libruby
|
|
3992
|
+
* shared library, add the cdx:ruby:* properties, and link the components to
|
|
3993
|
+
* one platform component per distinct version. Mutates the components in
|
|
3994
|
+
* place, as the PHP and Node.js collectors do.
|
|
3995
|
+
*
|
|
3996
|
+
* @param {Object[]} executables file components from `fileComponents(.., "executable")`
|
|
3997
|
+
* @param {Object[]} sharedLibs file components from `fileComponents(.., "shared_library")`
|
|
3998
|
+
* @param {string} basePath extracted rootfs the paths are relative to
|
|
3999
|
+
* @returns {{runtimeComponents: Object[], dependencies: Object[]}}
|
|
4000
|
+
*/
|
|
4001
|
+
function collectRubyBuildInfo(executables, sharedLibs, basePath) {
|
|
4002
|
+
const inventory = createRubyRuntimeInventory();
|
|
4003
|
+
let probes = 0;
|
|
4004
|
+
for (const component of [...(executables || []), ...(sharedLibs || [])]) {
|
|
4005
|
+
if (probes >= MAX_RUBY_PROBES) {
|
|
4006
|
+
break;
|
|
4007
|
+
}
|
|
4008
|
+
const srcFile = component?.properties?.find(
|
|
4009
|
+
(property) => property.name === "internal:SrcFile",
|
|
4010
|
+
)?.value;
|
|
4011
|
+
if (!srcFile || !(isRubyBinaryName(srcFile) || isLibrubyName(srcFile))) {
|
|
4012
|
+
continue;
|
|
4013
|
+
}
|
|
4014
|
+
if (packageStoreOf(srcFile)) {
|
|
4015
|
+
continue;
|
|
4016
|
+
}
|
|
4017
|
+
probes += 1;
|
|
4018
|
+
const build = readRubyBuildVersion(
|
|
4019
|
+
join(basePath, srcFile.replace(/^\/+/, "")),
|
|
4020
|
+
basePath,
|
|
4021
|
+
);
|
|
4022
|
+
if (build) {
|
|
4023
|
+
attachRubyBuild(inventory, component, build);
|
|
4024
|
+
}
|
|
4025
|
+
}
|
|
4026
|
+
return {
|
|
4027
|
+
runtimeComponents: inventory.runtimeComponents,
|
|
4028
|
+
dependencies: inventory.dependencies,
|
|
4029
|
+
};
|
|
4030
|
+
}
|
|
4031
|
+
|
|
4032
|
+
function isLibnode(srcFile) {
|
|
4033
|
+
const name = basename(srcFile.replaceAll("\\", "/"));
|
|
4034
|
+
return /^libnode\.so(\.\d+)*$/.test(name);
|
|
4035
|
+
}
|
|
4036
|
+
|
|
4037
|
+
/**
|
|
4038
|
+
* Probe unpackaged executables, shared libraries and native extension modules
|
|
4039
|
+
* for a compiled-in Brotli decoder. Executables and libraries already have file
|
|
4040
|
+
* components and are tagged in place; an extension module becomes a file
|
|
4041
|
+
* component only when it carries a decoder, so the BOM does not grow by every
|
|
4042
|
+
* extension in the image. Executables and libraries, and extension modules,
|
|
4043
|
+
* each get MAX_BROTLI_PROBES files, and the scan reads at most
|
|
4044
|
+
* MAX_BROTLI_PROBE_BYTES in all; what a cap leaves unread is logged and
|
|
4045
|
+
* recorded as the binary.brotli-probe.capped degradation.
|
|
4046
|
+
*
|
|
4047
|
+
* @param {Object[]} executables file components from `fileComponents(.., "executable")`
|
|
4048
|
+
* @param {Object[]} sharedLibs file components from `fileComponents(.., "shared_library")`
|
|
4049
|
+
* @param {string} basePath extracted rootfs the paths are relative to
|
|
4050
|
+
* @param {string[]} ownedFilePaths root-relative paths owned by OS packages
|
|
4051
|
+
* @returns {Promise<{components: Object[], dependencies: Object[], extensionFiles: Object[]}>}
|
|
4052
|
+
*/
|
|
4053
|
+
async function collectBrotliDecoders(
|
|
4054
|
+
executables,
|
|
4055
|
+
sharedLibs,
|
|
4056
|
+
basePath,
|
|
4057
|
+
ownedFilePaths,
|
|
4058
|
+
) {
|
|
4059
|
+
const inventory = createBrotliInventory();
|
|
4060
|
+
let bytesRead = 0;
|
|
4061
|
+
const unread = { files: 0, extensions: 0 };
|
|
4062
|
+
const probe = (relativePath) => {
|
|
4063
|
+
if (bytesRead >= MAX_BROTLI_PROBE_BYTES) {
|
|
4064
|
+
return undefined;
|
|
4065
|
+
}
|
|
4066
|
+
const decoder = readBrotliDecoder(join(basePath, relativePath), basePath);
|
|
4067
|
+
if (decoder && !decoder.skipped) {
|
|
4068
|
+
bytesRead += decoder.size || 0;
|
|
4069
|
+
}
|
|
4070
|
+
return decoder;
|
|
4071
|
+
};
|
|
4072
|
+
let probes = 0;
|
|
4073
|
+
const seenPaths = new Set();
|
|
4074
|
+
for (const component of [...(executables || []), ...(sharedLibs || [])]) {
|
|
4075
|
+
const srcFile = component?.properties?.find(
|
|
4076
|
+
(property) => property.name === "internal:SrcFile",
|
|
4077
|
+
)?.value;
|
|
4078
|
+
if (!srcFile) {
|
|
4079
|
+
continue;
|
|
4080
|
+
}
|
|
4081
|
+
const relativePath = srcFile.replaceAll("\\", "/").replace(/^\/+/, "");
|
|
4082
|
+
seenPaths.add(relativePath);
|
|
4083
|
+
if (packageStoreOf(relativePath)) {
|
|
4084
|
+
continue;
|
|
4085
|
+
}
|
|
4086
|
+
if (probes >= MAX_BROTLI_PROBES || bytesRead >= MAX_BROTLI_PROBE_BYTES) {
|
|
4087
|
+
unread.files += 1;
|
|
4088
|
+
continue;
|
|
4089
|
+
}
|
|
4090
|
+
probes += 1;
|
|
4091
|
+
const decoder = probe(relativePath);
|
|
4092
|
+
if (decoder) {
|
|
4093
|
+
attachBrotliDecoder(inventory, component, decoder);
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4096
|
+
const extensionHits = [];
|
|
4097
|
+
let extensionProbes = 0;
|
|
4098
|
+
for (const relativePath of collectNativeExtensions(basePath, [
|
|
4099
|
+
...(ownedFilePaths || []),
|
|
4100
|
+
...seenPaths,
|
|
4101
|
+
])) {
|
|
4102
|
+
if (packageStoreOf(relativePath)) {
|
|
4103
|
+
continue;
|
|
4104
|
+
}
|
|
4105
|
+
if (
|
|
4106
|
+
extensionProbes >= MAX_BROTLI_PROBES ||
|
|
4107
|
+
bytesRead >= MAX_BROTLI_PROBE_BYTES
|
|
4108
|
+
) {
|
|
4109
|
+
unread.extensions += 1;
|
|
4110
|
+
continue;
|
|
4111
|
+
}
|
|
4112
|
+
extensionProbes += 1;
|
|
4113
|
+
const decoder = probe(relativePath);
|
|
4114
|
+
if (decoder?.c || decoder?.go || decoder?.skipped) {
|
|
4115
|
+
extensionHits.push({ relativePath, decoder });
|
|
4116
|
+
}
|
|
4117
|
+
}
|
|
4118
|
+
if (unread.files || unread.extensions) {
|
|
4119
|
+
const detail = `The Brotli decoder probe left ${unread.files} executable(s) or librar(ies) and ${unread.extensions} extension module(s) unread after reaching its file or byte cap, so an embedded decoder in them is not reported.`;
|
|
4120
|
+
thoughtLog(detail);
|
|
4121
|
+
recordDegradation("binary.brotli-probe.capped", {
|
|
4122
|
+
ecosystem: "generic",
|
|
4123
|
+
tool: "brotli-probe",
|
|
4124
|
+
impact: "components",
|
|
4125
|
+
path: basePath,
|
|
4126
|
+
detail,
|
|
4127
|
+
});
|
|
4128
|
+
}
|
|
4129
|
+
const extensionFiles = [];
|
|
4130
|
+
if (extensionHits.length) {
|
|
4131
|
+
const components = await fileComponents(
|
|
4132
|
+
basePath,
|
|
4133
|
+
extensionHits.map((hit) => hit.relativePath),
|
|
4134
|
+
"shared_library",
|
|
4135
|
+
);
|
|
4136
|
+
for (let index = 0; index < components.length; index++) {
|
|
4137
|
+
const component = components[index];
|
|
4138
|
+
// Extension modules often share a basename (brotli.so is both a PHP and
|
|
4139
|
+
// a Ruby extension), so the path keeps each one a component of its own.
|
|
4140
|
+
const purl = tryBuildPurl({
|
|
4141
|
+
type: "generic",
|
|
4142
|
+
name: component.name,
|
|
4143
|
+
subpath: extensionHits[index].relativePath,
|
|
4144
|
+
});
|
|
4145
|
+
if (purl) {
|
|
4146
|
+
component.purl = purl;
|
|
4147
|
+
component["bom-ref"] = purl;
|
|
4148
|
+
}
|
|
4149
|
+
attachBrotliDecoder(inventory, component, extensionHits[index].decoder);
|
|
4150
|
+
extensionFiles.push(component);
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
return { ...finishBrotliInventory(inventory), extensionFiles };
|
|
4154
|
+
}
|
|
4155
|
+
|
|
3476
4156
|
async function fileComponents(basePath, fileList, fileType) {
|
|
3477
4157
|
const hashResults = await mapWithConcurrency(fileList, (f) =>
|
|
3478
4158
|
multiChecksumFile(["md5", "sha1"], join(basePath, f))
|