@cdxgen/cdxgen 13.4.0 → 13.5.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 +7 -7
- package/bin/cdxgen-secure.js +15 -0
- package/bin/cdxgen.js +25 -5
- package/bin/evinse.js +5 -0
- package/bin/hbom.js +15 -7
- package/bin/repl.js +29 -0
- package/data/cbomosdb-queries.json +13 -1
- package/data/component-tags.json +8 -0
- package/data/queries-darwin.json +25 -1
- package/data/queries-win.json +19 -1
- package/data/queries.json +41 -0
- package/data/remediations.json +283 -1
- 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/lib/audit/index.js +14 -0
- package/lib/cli/bomAssembly.js +28 -3
- package/lib/cli/cliOptions.js +110 -3
- package/lib/cli/index.js +73 -4
- package/lib/cli/jvmBom.js +126 -20
- package/lib/cli/managedBom.js +711 -71
- package/lib/cli/nativeBom.js +225 -28
- package/lib/core/env.js +30 -8
- package/lib/core/fs.js +28 -19
- package/lib/core/httpClient.js +74 -45
- package/lib/core/projectConfig.js +1 -0
- package/lib/core/projectTooling.js +97 -0
- package/lib/core/propertySanitizer.js +72 -0
- package/lib/ecosystems/golem.js +550 -6
- package/lib/ecosystems/gradleutils.js +30 -20
- package/lib/ecosystems/parsers-dotnet.js +117 -18
- package/lib/ecosystems/parsers-go.js +1090 -367
- package/lib/ecosystems/parsers-jvm.js +259 -17
- package/lib/ecosystems/parsers-misc.js +229 -44
- package/lib/ecosystems/parsers-python.js +11 -10
- package/lib/ecosystems/rubyutils.js +6 -3
- package/lib/ecosystems/rusi.js +10 -0
- package/lib/ecosystems/scalacliutils.js +2 -1
- package/lib/ecosystems/scalasem.js +3 -1
- package/lib/evinser/evinser.js +126 -18
- package/lib/evinser/swiftsem.js +68 -5
- package/lib/inventory/agentSkillSpec.js +17 -0
- package/lib/inventory/apkInstalledDb.js +243 -0
- package/lib/inventory/atomUtils.js +33 -0
- package/lib/inventory/auditCategories.js +3 -3
- package/lib/inventory/cacheDir.js +52 -9
- package/lib/inventory/certificates.js +2371 -0
- package/lib/inventory/communityAiConfigParser.js +4 -9
- package/lib/inventory/deps.js +6 -4
- package/lib/inventory/display.js +200 -3
- package/lib/inventory/dosai.js +500 -58
- package/lib/inventory/dynamic.js +45 -3
- package/lib/inventory/envcontext.js +168 -5
- package/lib/inventory/evidenceUtils.js +436 -23
- package/lib/inventory/formulationParsers.js +3 -2
- package/lib/inventory/goBuildInfo.js +140 -0
- package/lib/inventory/hbom.js +67 -7
- package/lib/inventory/hbomAnalysis.js +16 -0
- package/lib/inventory/hostTopology.js +21 -0
- package/lib/inventory/osPackagePurl.js +244 -0
- package/lib/inventory/osPackageResolver.js +89 -87
- package/lib/inventory/osinfo.js +79 -31
- package/lib/inventory/osqueryTransform.js +114 -5
- package/lib/inventory/phpBuildInfo.js +487 -0
- package/lib/inventory/phpHostProbe.js +641 -0
- package/lib/inventory/rootfsCerts.js +426 -0
- package/lib/inventory/source.js +207 -3
- 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 +529 -183
- package/lib/managers/docker.js +166 -12
- package/lib/parsers/email.js +277 -0
- package/lib/parsers/largeJson.js +34 -2
- package/lib/server/server.js +186 -1
- 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 +58 -1
- package/lib/stages/postgen/specVersionCompat.js +30 -0
- package/lib/stages/pregen/pregen.js +117 -5
- package/lib/validator/bomValidator.js +26 -5
- package/lib/validator/generated/runtime.mjs +13 -0
- package/lib/validator/generated/validate-1.6.mjs +26 -7
- package/lib/validator/generated/validate-1.7.mjs +31 -12
- package/lib/validator/generated/validate-2.0.mjs +30 -11
- package/package.json +17 -17
- 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 +9 -2
- 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/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/env.d.ts +18 -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.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/ecosystems/cppEvidence.d.ts +1 -1
- 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 +10 -8
- package/types/lib/ecosystems/gradleutils.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-jvm.d.ts +28 -2
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +18 -58
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +4 -3
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +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/ecosystems/scalacliutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +25 -0
- 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 +14 -0
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/cacheDir.d.ts +12 -4
- package/types/lib/inventory/cacheDir.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 +145 -6
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- 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 +40 -2
- 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/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/hostTopology.d.ts.map +1 -1
- 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 +6 -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/source.d.ts +28 -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 +12 -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 +13 -0
- 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/pregen.d.ts +14 -1
- 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";
|
|
@@ -33,23 +41,41 @@ import {
|
|
|
33
41
|
import { mapWithConcurrency } from "../core/parallel.js";
|
|
34
42
|
import { isValidDriveRoot } from "../core/paths.js";
|
|
35
43
|
import { isCycloneDxComponentTypeEnabled } from "../inventory/bomUtils.js";
|
|
44
|
+
import {
|
|
45
|
+
CertificateCollector,
|
|
46
|
+
classifyCryptoFile,
|
|
47
|
+
} from "../inventory/certificates.js";
|
|
36
48
|
import { createContainerRiskProperties } from "../inventory/containerRisk.js";
|
|
37
49
|
import {
|
|
38
50
|
attachIdentityTools,
|
|
39
51
|
extractToolRefs,
|
|
40
52
|
} from "../inventory/evidenceUtils.js";
|
|
53
|
+
import {
|
|
54
|
+
PRE_1_18_VERSION,
|
|
55
|
+
readGoBuildVersion,
|
|
56
|
+
} from "../inventory/goBuildInfo.js";
|
|
41
57
|
import { createGtfoBinsProperties } from "../inventory/gtfobins.js";
|
|
42
58
|
import {
|
|
43
59
|
canonicalDistroQualifier,
|
|
60
|
+
getDistroInfo,
|
|
44
61
|
isCleanDistroQualifier,
|
|
45
62
|
OS_DISTRO_ALIAS,
|
|
46
63
|
OS_NAMESPACE_ALIAS,
|
|
47
64
|
readOsRelease,
|
|
48
65
|
} from "../inventory/osinfo.js";
|
|
66
|
+
import { isRealArchitecture } from "../inventory/osPackagePurl.js";
|
|
49
67
|
import {
|
|
50
68
|
collectExecutables,
|
|
51
69
|
collectSharedLibs,
|
|
52
70
|
} from "../inventory/osPackageResolver.js";
|
|
71
|
+
import {
|
|
72
|
+
attachPhpBuild,
|
|
73
|
+
classifyPhpSapi,
|
|
74
|
+
createPhpRuntimeInventory,
|
|
75
|
+
MAX_PHP_PROBES,
|
|
76
|
+
packageStoreOf,
|
|
77
|
+
readPhpBuildVersion,
|
|
78
|
+
} from "../inventory/phpBuildInfo.js";
|
|
53
79
|
import {
|
|
54
80
|
resolveCdxgenPlugins,
|
|
55
81
|
resolvePluginBinary,
|
|
@@ -60,11 +86,13 @@ import {
|
|
|
60
86
|
parsePurlLoose,
|
|
61
87
|
tryBuildPurl,
|
|
62
88
|
} from "../inventory/purl.js";
|
|
89
|
+
import { collectRootfsCertificates } from "../inventory/rootfsCerts.js";
|
|
63
90
|
import {
|
|
64
91
|
adjustLicenseInformation,
|
|
65
92
|
findLicenseId,
|
|
66
93
|
isSpdxLicenseExpression,
|
|
67
94
|
} from "../inventory/spdx.js";
|
|
95
|
+
import { isValidIdnEmail } from "../parsers/email.js";
|
|
68
96
|
import { getDirs } from "./containerutils.js";
|
|
69
97
|
|
|
70
98
|
const isWin = _platform() === "win32";
|
|
@@ -654,7 +682,35 @@ export function canonicaliseOsPackageNamespace(
|
|
|
654
682
|
group,
|
|
655
683
|
distroId,
|
|
656
684
|
) {
|
|
657
|
-
if (!purlObj
|
|
685
|
+
if (!purlObj) {
|
|
686
|
+
return group;
|
|
687
|
+
}
|
|
688
|
+
// trivy invents a `bottlerocket` purl type for Bottlerocket host packages
|
|
689
|
+
// (trivy pkg/purl/purl.go, packageurlTypeBottlerocket); the purl spec has
|
|
690
|
+
// no such type — @cdxgen/cdx-purl has no generated type class for it, and
|
|
691
|
+
// no vulnerability feed stores under it. Rewrite to the form cdxgen's own
|
|
692
|
+
// os-release path produces for a Bottlerocket host: type rpm, namespace
|
|
693
|
+
// bottlerocket (the os-release ID; OS_NAMESPACE_ALIAS needs no entry).
|
|
694
|
+
// The distro qualifier trivy stamps (bottlerocket-<os version>) survives
|
|
695
|
+
// unchanged. The caller's group is returned as for any other rpm distro
|
|
696
|
+
// without an alias, so getOSPackages still records the rpm type and the
|
|
697
|
+
// distro in allTypes (oci:image:componentTypes).
|
|
698
|
+
if (purlObj.type === "bottlerocket") {
|
|
699
|
+
purlObj.type = "rpm";
|
|
700
|
+
purlObj.namespace = "bottlerocket";
|
|
701
|
+
comp.group = "bottlerocket";
|
|
702
|
+
comp.purl = build({
|
|
703
|
+
type: purlObj.type,
|
|
704
|
+
namespace: purlObj.namespace,
|
|
705
|
+
name: name,
|
|
706
|
+
version: purlObj.version || null,
|
|
707
|
+
qualifiers: purlObj.qualifiers || null,
|
|
708
|
+
subpath: purlObj.subpath || null,
|
|
709
|
+
});
|
|
710
|
+
comp["bom-ref"] = decodeURIComponent(comp.purl);
|
|
711
|
+
return group;
|
|
712
|
+
}
|
|
713
|
+
if (!OS_PURL_TYPES.includes(purlObj.type)) {
|
|
658
714
|
return group;
|
|
659
715
|
}
|
|
660
716
|
const os_ns = purlObj.namespace || group || "";
|
|
@@ -923,80 +979,20 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
923
979
|
if (DEBUG_MODE) {
|
|
924
980
|
console.log(osReleaseData);
|
|
925
981
|
}
|
|
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);
|
|
982
|
+
// The distro of the rootfs is decided in getDistroInfo, which an OBOM run
|
|
983
|
+
// on the same system reads too, so a package gets the same qualifiers
|
|
984
|
+
// from either scan. distro_name is a real release codename/token
|
|
985
|
+
// (bookworm, jammy, rhel, alpine-3.24), never a distro *display name*:
|
|
986
|
+
// "Rocky Linux" or "AlmaLinux" is dropped, since RPM distros track
|
|
987
|
+
// packages flat and a display name there breaks downstream vuln-db
|
|
988
|
+
// matching. distro is ID-VERSION_ID with the vendor canonicalised (e.g.
|
|
989
|
+
// "mariner-2.0" -> "azure-linux-2.0"), so every spelling of one release
|
|
990
|
+
// yields a single qualifier value; it overwrites trivy's own qualifier
|
|
991
|
+
// below.
|
|
992
|
+
const distroInfo = getDistroInfo(src);
|
|
993
|
+
let distro_codename = distroInfo.distroName;
|
|
994
|
+
const distro_id = distroInfo.distroId;
|
|
995
|
+
const purl_type = distroInfo.purlType;
|
|
1000
996
|
const tmpDependencies = {};
|
|
1001
997
|
tools = enrichToolComponents(
|
|
1002
998
|
(Array.isArray(tmpBom?.metadata?.tools)
|
|
@@ -1030,6 +1026,27 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
1030
1026
|
try {
|
|
1031
1027
|
purlObj = Purl.parse(comp.purl);
|
|
1032
1028
|
purlObj.qualifiers = purlObj.qualifiers || {};
|
|
1029
|
+
if (
|
|
1030
|
+
OS_PURL_TYPES.includes(purlObj.type) &&
|
|
1031
|
+
"arch" in purlObj.qualifiers &&
|
|
1032
|
+
!isRealArchitecture(purlObj.qualifiers.arch)
|
|
1033
|
+
) {
|
|
1034
|
+
// trivy writes arch=None for a package with no architecture,
|
|
1035
|
+
// such as an rpm's gpg-pubkey; a package has the architecture
|
|
1036
|
+
// it has, or none, as in an OBOM.
|
|
1037
|
+
delete purlObj.qualifiers.arch;
|
|
1038
|
+
comp.purl = build({
|
|
1039
|
+
type: purlObj.type,
|
|
1040
|
+
namespace: purlObj.namespace || null,
|
|
1041
|
+
name: name,
|
|
1042
|
+
version: purlObj.version || null,
|
|
1043
|
+
qualifiers: Object.keys(purlObj.qualifiers).length
|
|
1044
|
+
? purlObj.qualifiers
|
|
1045
|
+
: null,
|
|
1046
|
+
subpath: purlObj.subpath || null,
|
|
1047
|
+
});
|
|
1048
|
+
comp["bom-ref"] = decodeURIComponent(comp.purl);
|
|
1049
|
+
}
|
|
1033
1050
|
} catch (_err) {
|
|
1034
1051
|
// continue regardless of error
|
|
1035
1052
|
}
|
|
@@ -1115,16 +1132,20 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
1115
1132
|
if (distro_codename !== "") {
|
|
1116
1133
|
allTypes.add(distro_codename);
|
|
1117
1134
|
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
1135
|
}
|
|
1136
|
+
// Rebuilt whether or not the distro has a codename: the distro
|
|
1137
|
+
// stamped above is ID-VERSION_ID for every distro, and a purl
|
|
1138
|
+
// left as trivy wrote it keeps trivy's own spelling (a bare
|
|
1139
|
+
// 20230201 for a Wolfi image).
|
|
1140
|
+
comp.purl = build({
|
|
1141
|
+
type: purlObj.type,
|
|
1142
|
+
namespace: purlObj.namespace || null,
|
|
1143
|
+
name: name,
|
|
1144
|
+
version: purlObj.version || null,
|
|
1145
|
+
qualifiers: purlObj.qualifiers || null,
|
|
1146
|
+
subpath: purlObj.subpath || null,
|
|
1147
|
+
});
|
|
1148
|
+
comp["bom-ref"] = decodeURIComponent(comp.purl);
|
|
1128
1149
|
} catch (_err) {
|
|
1129
1150
|
// continue regardless of error
|
|
1130
1151
|
}
|
|
@@ -1404,9 +1425,36 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
1404
1425
|
"/etc/ld.so.conf",
|
|
1405
1426
|
"/etc/ld.so.conf.d/*.conf",
|
|
1406
1427
|
ownedFilePaths,
|
|
1428
|
+
// Apache loads libphp*.so from a modules directory that is two levels
|
|
1429
|
+
// below /usr, which the default one-level globs miss.
|
|
1430
|
+
[
|
|
1431
|
+
"**/usr/lib/apache2/modules/libphp*.so",
|
|
1432
|
+
"**/usr/lib/httpd/modules/libphp*.so",
|
|
1433
|
+
"**/usr/lib64/httpd/modules/libphp*.so",
|
|
1434
|
+
],
|
|
1407
1435
|
),
|
|
1408
1436
|
"shared_library",
|
|
1409
1437
|
);
|
|
1438
|
+
// Go executables carry the toolchain they were built with in their
|
|
1439
|
+
// buildinfo header. The stdlib is statically linked, so that version
|
|
1440
|
+
// decides whether the binary applies Go's CVE-2026-111943 distrust list.
|
|
1441
|
+
const goBuildInventory = collectGoBuildInfo(executables, src);
|
|
1442
|
+
if (goBuildInventory.stdlibComponents.length) {
|
|
1443
|
+
pkgList.push(...goBuildInventory.stdlibComponents);
|
|
1444
|
+
}
|
|
1445
|
+
if (goBuildInventory.dependencies.length) {
|
|
1446
|
+
dependenciesList.push(...goBuildInventory.dependencies);
|
|
1447
|
+
}
|
|
1448
|
+
// Source-built PHP (the Docker Official image, FrankenPHP and custom
|
|
1449
|
+
// /usr/local builds) is unpackaged, so its version is only visible in the
|
|
1450
|
+
// binaries and the installed php_version.h.
|
|
1451
|
+
const phpBuildInventory = collectPhpBuildInfo(executables, sharedLibs, src);
|
|
1452
|
+
if (phpBuildInventory.runtimeComponents.length) {
|
|
1453
|
+
pkgList.push(...phpBuildInventory.runtimeComponents);
|
|
1454
|
+
}
|
|
1455
|
+
if (phpBuildInventory.dependencies.length) {
|
|
1456
|
+
dependenciesList.push(...phpBuildInventory.dependencies);
|
|
1457
|
+
}
|
|
1410
1458
|
return {
|
|
1411
1459
|
osPackages: pkgList,
|
|
1412
1460
|
osPackageFiles,
|
|
@@ -1545,17 +1593,24 @@ function mergeOrganizationalEntityField(component, fieldName, entityName) {
|
|
|
1545
1593
|
return { applied: false, represented: false };
|
|
1546
1594
|
}
|
|
1547
1595
|
|
|
1548
|
-
|
|
1596
|
+
/**
|
|
1597
|
+
* Split a `Name <address>` maintainer string into an `organizationalContact`.
|
|
1598
|
+
*
|
|
1599
|
+
* @param {string} value Maintainer or supplier string
|
|
1600
|
+
* @returns {{name: string, email?: string}|undefined} The contact, if any
|
|
1601
|
+
*/
|
|
1602
|
+
export function parseOrganizationalContact(value) {
|
|
1549
1603
|
const normalizedValue = `${value || ""}`.trim();
|
|
1550
1604
|
if (!normalizedValue) {
|
|
1551
1605
|
return undefined;
|
|
1552
1606
|
}
|
|
1553
1607
|
const match = normalizedValue.match(/^([^<>]+?)\s*<([^<>\s]+@[^<>\s]+)>$/);
|
|
1554
1608
|
if (match) {
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1609
|
+
const name = match[1].trim();
|
|
1610
|
+
const email = match[2].trim();
|
|
1611
|
+
// `organizationalContact.email` is an `idn-email`; an address that is not
|
|
1612
|
+
// one (say `a@b@c`) would fail schema validation, so keep only the name.
|
|
1613
|
+
return isValidIdnEmail(email) ? { name, email } : { name };
|
|
1559
1614
|
}
|
|
1560
1615
|
return { name: normalizedValue };
|
|
1561
1616
|
}
|
|
@@ -1644,13 +1699,34 @@ function promoteTrivyOsPackageIdentity(component, trivyMetadata) {
|
|
|
1644
1699
|
async function collectRootfsRepositoryInventory(basePath, options = {}) {
|
|
1645
1700
|
let trustedKeyComponents = [];
|
|
1646
1701
|
let refsByPath = new Map();
|
|
1702
|
+
let trustMaterialDependencies = [];
|
|
1647
1703
|
if (isCycloneDxComponentTypeEnabled("cryptographic-asset", options)) {
|
|
1648
1704
|
({ components: trustedKeyComponents, refsByPath } =
|
|
1649
1705
|
await collectTrustedKeyComponents(basePath));
|
|
1650
|
-
|
|
1706
|
+
({
|
|
1707
|
+
components: trustedKeyComponents,
|
|
1708
|
+
dependencies: trustMaterialDependencies,
|
|
1709
|
+
} = applyTrustMaterialEnhancements(
|
|
1651
1710
|
trustedKeyComponents,
|
|
1652
1711
|
collectTrustInspectorRootfsInventory(basePath),
|
|
1653
|
-
|
|
1712
|
+
basePath,
|
|
1713
|
+
));
|
|
1714
|
+
if (!TRUSTINSPECTOR_BIN) {
|
|
1715
|
+
// No plugin: walk the same locations in JavaScript. With the plugin
|
|
1716
|
+
// present its rootfs report is the only producer, so nothing is
|
|
1717
|
+
// counted twice.
|
|
1718
|
+
const fallback = collectRootfsCertificates(basePath);
|
|
1719
|
+
const knownRefs = new Set(trustedKeyComponents.map((c) => c["bom-ref"]));
|
|
1720
|
+
for (const component of fallback.components) {
|
|
1721
|
+
if (!knownRefs.has(component["bom-ref"])) {
|
|
1722
|
+
knownRefs.add(component["bom-ref"]);
|
|
1723
|
+
trustedKeyComponents.push(component);
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
trustMaterialDependencies = trustMaterialDependencies.concat(
|
|
1727
|
+
fallback.dependencies,
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
1654
1730
|
}
|
|
1655
1731
|
refsByPath = new Map(
|
|
1656
1732
|
trustedKeyComponents
|
|
@@ -1692,6 +1768,7 @@ async function collectRootfsRepositoryInventory(basePath, options = {}) {
|
|
|
1692
1768
|
});
|
|
1693
1769
|
}
|
|
1694
1770
|
}
|
|
1771
|
+
dependenciesList.push(...trustMaterialDependencies);
|
|
1695
1772
|
return { components, dependenciesList };
|
|
1696
1773
|
}
|
|
1697
1774
|
|
|
@@ -1877,7 +1954,9 @@ function executeTrustInspector(args, activity) {
|
|
|
1877
1954
|
if (DEBUG_MODE) {
|
|
1878
1955
|
console.log("Executing", TRUSTINSPECTOR_BIN, args.join(" "));
|
|
1879
1956
|
}
|
|
1880
|
-
const result = safeSpawnSync(TRUSTINSPECTOR_BIN, args
|
|
1957
|
+
const result = safeSpawnSync(TRUSTINSPECTOR_BIN, args, {
|
|
1958
|
+
timeout: 10 * 60 * 1000,
|
|
1959
|
+
});
|
|
1881
1960
|
if (result?.status !== 0 || result?.error) {
|
|
1882
1961
|
if (DEBUG_MODE && (result?.stdout || result?.stderr)) {
|
|
1883
1962
|
console.error(result.stdout, result.stderr);
|
|
@@ -1991,91 +2070,54 @@ function createTrustMaterialComponent(material) {
|
|
|
1991
2070
|
? [{ name: "cdx:crypto:expiresAt", value: material.expiresAt }]
|
|
1992
2071
|
: []),
|
|
1993
2072
|
]);
|
|
1994
|
-
let component;
|
|
1995
2073
|
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
|
-
},
|
|
2074
|
+
// Certificates are built by the shared collector in
|
|
2075
|
+
// applyTrustMaterialEnhancements; this legacy builder no longer produces
|
|
2076
|
+
// them, so a certificate material reaching here is skipped.
|
|
2077
|
+
return undefined;
|
|
2078
|
+
}
|
|
2079
|
+
const component = {
|
|
2080
|
+
"bom-ref": `crypto/related-crypto-material/public-key/${encodeURIComponent(normalizedPath)}@${material.sha256 ? `sha256:${material.sha256}` : material.keyId || "unknown"}`,
|
|
2081
|
+
name: material.name || basename(normalizedPath),
|
|
2082
|
+
type: "cryptographic-asset",
|
|
2083
|
+
version: material.sha256 || material.keyId || normalizedPath,
|
|
2084
|
+
hashes,
|
|
2085
|
+
cryptoProperties: {
|
|
2086
|
+
assetType: "related-crypto-material",
|
|
2087
|
+
relatedCryptoMaterialProperties: {
|
|
2088
|
+
type: "public-key",
|
|
2089
|
+
id:
|
|
2090
|
+
material.keyId ||
|
|
2091
|
+
material.fingerprint ||
|
|
2092
|
+
material.sha256 ||
|
|
2093
|
+
normalizedPath,
|
|
2094
|
+
state: trustMaterialState(material),
|
|
2025
2095
|
},
|
|
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),
|
|
2096
|
+
},
|
|
2097
|
+
properties: uniqueProperties(
|
|
2098
|
+
sharedProperties.concat([
|
|
2099
|
+
{
|
|
2100
|
+
name: "cdx:crypto:trustDomain",
|
|
2101
|
+
value: material.trustDomain || deriveTrustedKeyDomain(normalizedPath),
|
|
2051
2102
|
},
|
|
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
|
-
}
|
|
2103
|
+
{ name: "cdx:crypto:keyPath", value: normalizedPath },
|
|
2104
|
+
{
|
|
2105
|
+
name: "cdx:crypto:fileExtension",
|
|
2106
|
+
value:
|
|
2107
|
+
material.fileExtension ||
|
|
2108
|
+
extname(normalizedPath).replace(/^\./, "") ||
|
|
2109
|
+
"gpg",
|
|
2110
|
+
},
|
|
2111
|
+
...(material?.keyId
|
|
2112
|
+
? [{ name: "cdx:crypto:keyId", value: material.keyId }]
|
|
2113
|
+
: []),
|
|
2114
|
+
...(material?.userIds || []).map((value) => ({
|
|
2115
|
+
name: "cdx:crypto:userId",
|
|
2116
|
+
value,
|
|
2117
|
+
})),
|
|
2118
|
+
]),
|
|
2119
|
+
),
|
|
2120
|
+
};
|
|
2079
2121
|
attachIdentityTools(component, trustInspectorToolRefs());
|
|
2080
2122
|
return component;
|
|
2081
2123
|
}
|
|
@@ -2100,9 +2142,73 @@ function enhanceComponentFromTrustMaterial(component, material) {
|
|
|
2100
2142
|
return component;
|
|
2101
2143
|
}
|
|
2102
2144
|
|
|
2103
|
-
function
|
|
2145
|
+
function createKeystoreComponent(material) {
|
|
2146
|
+
const normalizedPath = normalizeContainerPath(material?.path);
|
|
2147
|
+
if (!normalizedPath) {
|
|
2148
|
+
return undefined;
|
|
2149
|
+
}
|
|
2150
|
+
const readable = (material?.properties || []).find(
|
|
2151
|
+
(property) => property.name === "cdx:crypto:keystoreReadable",
|
|
2152
|
+
)?.value;
|
|
2153
|
+
const component = {
|
|
2154
|
+
"bom-ref": `crypto/related-crypto-material/keystore/${encodeURIComponent(normalizedPath)}`,
|
|
2155
|
+
name: basename(normalizedPath),
|
|
2156
|
+
type: "cryptographic-asset",
|
|
2157
|
+
cryptoProperties: {
|
|
2158
|
+
assetType: "related-crypto-material",
|
|
2159
|
+
relatedCryptoMaterialProperties: {
|
|
2160
|
+
type: "other",
|
|
2161
|
+
...(material?.format ? { format: material.format } : {}),
|
|
2162
|
+
},
|
|
2163
|
+
},
|
|
2164
|
+
evidence: { occurrences: [{ location: normalizedPath }] },
|
|
2165
|
+
properties: [
|
|
2166
|
+
{ name: "internal:SrcFile", value: normalizedPath },
|
|
2167
|
+
...(material?.sourceType
|
|
2168
|
+
? [{ name: "cdx:crypto:sourceType", value: material.sourceType }]
|
|
2169
|
+
: []),
|
|
2170
|
+
...(readable
|
|
2171
|
+
? [{ name: "cdx:crypto:keystoreReadable", value: readable }]
|
|
2172
|
+
: []),
|
|
2173
|
+
],
|
|
2174
|
+
};
|
|
2175
|
+
attachIdentityTools(component, trustInspectorToolRefs());
|
|
2176
|
+
return component;
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
// Private keys are classified from the PEM header line only and never
|
|
2180
|
+
// decoded (PLAN section 1.7): the format travels, no key material does.
|
|
2181
|
+
function createPrivateKeyComponent(material) {
|
|
2182
|
+
const normalizedPath = normalizeContainerPath(material?.path);
|
|
2183
|
+
if (!normalizedPath) {
|
|
2184
|
+
return undefined;
|
|
2185
|
+
}
|
|
2186
|
+
const component = {
|
|
2187
|
+
"bom-ref": `crypto/related-crypto-material/private-key/${encodeURIComponent(normalizedPath)}`,
|
|
2188
|
+
name: basename(normalizedPath),
|
|
2189
|
+
type: "cryptographic-asset",
|
|
2190
|
+
cryptoProperties: {
|
|
2191
|
+
assetType: "related-crypto-material",
|
|
2192
|
+
relatedCryptoMaterialProperties: {
|
|
2193
|
+
type: "private-key",
|
|
2194
|
+
...(material?.format ? { format: material.format } : {}),
|
|
2195
|
+
},
|
|
2196
|
+
},
|
|
2197
|
+
evidence: { occurrences: [{ location: normalizedPath }] },
|
|
2198
|
+
properties: [
|
|
2199
|
+
{ name: "internal:SrcFile", value: normalizedPath },
|
|
2200
|
+
...(material?.sourceType
|
|
2201
|
+
? [{ name: "cdx:crypto:sourceType", value: material.sourceType }]
|
|
2202
|
+
: []),
|
|
2203
|
+
],
|
|
2204
|
+
};
|
|
2205
|
+
attachIdentityTools(component, trustInspectorToolRefs());
|
|
2206
|
+
return component;
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
function applyTrustMaterialEnhancements(components, materials, basePath) {
|
|
2104
2210
|
if (!materials?.length) {
|
|
2105
|
-
return components || [];
|
|
2211
|
+
return { components: components || [], dependencies: [] };
|
|
2106
2212
|
}
|
|
2107
2213
|
const componentList = [...(components || [])];
|
|
2108
2214
|
const bySrcFile = new Map();
|
|
@@ -2119,6 +2225,8 @@ function applyTrustMaterialEnhancements(components, materials) {
|
|
|
2119
2225
|
}
|
|
2120
2226
|
}
|
|
2121
2227
|
for (const material of materials) {
|
|
2228
|
+
// GPG-style keyring materials (report-1 `kind: public-key`) enhance the
|
|
2229
|
+
// components the native readers already produced.
|
|
2122
2230
|
const srcFile = normalizeContainerPath(material?.path);
|
|
2123
2231
|
const existing = bySrcFile.get(srcFile);
|
|
2124
2232
|
if (
|
|
@@ -2129,17 +2237,95 @@ function applyTrustMaterialEnhancements(components, materials) {
|
|
|
2129
2237
|
enhanceComponentFromTrustMaterial(existing, material);
|
|
2130
2238
|
continue;
|
|
2131
2239
|
}
|
|
2132
|
-
|
|
2240
|
+
if (material?.kind === "public-key") {
|
|
2241
|
+
const component = createTrustMaterialComponent(material);
|
|
2242
|
+
if (!component?.["bom-ref"] || seenRefs.has(component["bom-ref"])) {
|
|
2243
|
+
continue;
|
|
2244
|
+
}
|
|
2245
|
+
seenRefs.add(component["bom-ref"]);
|
|
2246
|
+
componentList.push(component);
|
|
2247
|
+
if (srcFile) {
|
|
2248
|
+
bySrcFile.set(srcFile, component);
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
// Certificates, their SPKI public keys, algorithm components and verified
|
|
2253
|
+
// issuer edges come from the shared collector; keystores and private keys
|
|
2254
|
+
// are their own components.
|
|
2255
|
+
const collector = new CertificateCollector();
|
|
2256
|
+
collector.addTrustInspectorMaterials(materials);
|
|
2257
|
+
const { components: cryptoComponents, dependencies } = collector.build();
|
|
2258
|
+
for (const material of materials) {
|
|
2259
|
+
if (material?.kind === "keystore") {
|
|
2260
|
+
cryptoComponents.push(createKeystoreComponent(material));
|
|
2261
|
+
} else if (material?.kind === "private-key") {
|
|
2262
|
+
cryptoComponents.push(createPrivateKeyComponent(material));
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
// A keystore the plugin could read is reported as the certificates it
|
|
2266
|
+
// holds; give it the keystore component the unreadable ones get and link
|
|
2267
|
+
// it to those certificates.
|
|
2268
|
+
if (basePath) {
|
|
2269
|
+
const certMaterialByPath = new Map();
|
|
2270
|
+
for (const material of materials) {
|
|
2271
|
+
if (material?.kind === "certificate" && material.path) {
|
|
2272
|
+
certMaterialByPath.set(normalizeContainerPath(material.path), material);
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
for (const [path, material] of certMaterialByPath) {
|
|
2276
|
+
let classification;
|
|
2277
|
+
try {
|
|
2278
|
+
const absolute = join(basePath, path.replace(/^\/+/, ""));
|
|
2279
|
+
// The path comes from the plugin report: it must stay inside the
|
|
2280
|
+
// scanned root, symlinks included.
|
|
2281
|
+
const realRoot = realpathSync(basePath);
|
|
2282
|
+
const realFile = realpathSync(absolute);
|
|
2283
|
+
const inside = relative(realRoot, realFile);
|
|
2284
|
+
if (inside.startsWith("..") || isAbsolute(inside)) {
|
|
2285
|
+
continue;
|
|
2286
|
+
}
|
|
2287
|
+
const stats = lstatSync(absolute);
|
|
2288
|
+
if (!stats.isFile() || stats.size > 16 * 1024 * 1024) {
|
|
2289
|
+
continue;
|
|
2290
|
+
}
|
|
2291
|
+
classification = classifyCryptoFile(readFileSync(absolute));
|
|
2292
|
+
} catch (_err) {
|
|
2293
|
+
continue;
|
|
2294
|
+
}
|
|
2295
|
+
if (classification.kind !== "keystore") {
|
|
2296
|
+
continue;
|
|
2297
|
+
}
|
|
2298
|
+
const keystore = createKeystoreComponent({
|
|
2299
|
+
path,
|
|
2300
|
+
format: classification.format,
|
|
2301
|
+
sourceType: material.sourceType,
|
|
2302
|
+
});
|
|
2303
|
+
cryptoComponents.push(keystore);
|
|
2304
|
+
const held = cryptoComponents
|
|
2305
|
+
.filter(
|
|
2306
|
+
(component) =>
|
|
2307
|
+
component.cryptoProperties?.assetType === "certificate" &&
|
|
2308
|
+
component.evidence?.occurrences?.some(
|
|
2309
|
+
(occurrence) =>
|
|
2310
|
+
normalizeContainerPath(occurrence.location) === path,
|
|
2311
|
+
),
|
|
2312
|
+
)
|
|
2313
|
+
.map((component) => component["bom-ref"])
|
|
2314
|
+
.sort();
|
|
2315
|
+
if (held.length) {
|
|
2316
|
+
dependencies.push({ ref: keystore["bom-ref"], dependsOn: held });
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
for (const component of cryptoComponents) {
|
|
2133
2321
|
if (!component?.["bom-ref"] || seenRefs.has(component["bom-ref"])) {
|
|
2134
2322
|
continue;
|
|
2135
2323
|
}
|
|
2136
2324
|
seenRefs.add(component["bom-ref"]);
|
|
2325
|
+
attachIdentityTools(component, trustInspectorToolRefs());
|
|
2137
2326
|
componentList.push(component);
|
|
2138
|
-
if (srcFile) {
|
|
2139
|
-
bySrcFile.set(srcFile, component);
|
|
2140
|
-
}
|
|
2141
2327
|
}
|
|
2142
|
-
return componentList;
|
|
2328
|
+
return { components: componentList, dependencies };
|
|
2143
2329
|
}
|
|
2144
2330
|
|
|
2145
2331
|
function collectTrustInspectorRootfsInventory(basePath) {
|
|
@@ -3299,10 +3485,37 @@ export function enrichOSComponentsWithTrustData(components = []) {
|
|
|
3299
3485
|
if (hostComponents.length) {
|
|
3300
3486
|
mergedComponents.push(...hostComponents);
|
|
3301
3487
|
}
|
|
3488
|
+
// report-2 host mode enumerates the platform trust stores as materials;
|
|
3489
|
+
// they flow through the shared certificate model like any other producer.
|
|
3490
|
+
const hostCollector = new CertificateCollector();
|
|
3491
|
+
hostCollector.addTrustInspectorMaterials(hostData?.materials);
|
|
3492
|
+
const { components: hostMaterialComponents } = hostCollector.build();
|
|
3493
|
+
for (const material of hostData?.materials || []) {
|
|
3494
|
+
if (material?.kind === "keystore") {
|
|
3495
|
+
const component = createKeystoreComponent(material);
|
|
3496
|
+
if (component) {
|
|
3497
|
+
hostMaterialComponents.push(component);
|
|
3498
|
+
}
|
|
3499
|
+
} else if (material?.kind === "private-key") {
|
|
3500
|
+
const component = createPrivateKeyComponent(material);
|
|
3501
|
+
if (component) {
|
|
3502
|
+
hostMaterialComponents.push(component);
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
for (const component of hostMaterialComponents) {
|
|
3507
|
+
if (!component?.["bom-ref"]) {
|
|
3508
|
+
continue;
|
|
3509
|
+
}
|
|
3510
|
+
attachIdentityTools(component, trustInspectorToolRefs());
|
|
3511
|
+
mergedComponents.push(component);
|
|
3512
|
+
}
|
|
3302
3513
|
return {
|
|
3303
3514
|
components: mergedComponents,
|
|
3304
3515
|
tools:
|
|
3305
|
-
pathInspectionCandidates.length ||
|
|
3516
|
+
pathInspectionCandidates.length ||
|
|
3517
|
+
hostComponents.length ||
|
|
3518
|
+
hostMaterialComponents.length
|
|
3306
3519
|
? getPluginToolComponents(["trustinspector"])
|
|
3307
3520
|
: [],
|
|
3308
3521
|
};
|
|
@@ -3473,6 +3686,139 @@ export function getBinaryBom(src, binaryBomFile, deepMode) {
|
|
|
3473
3686
|
return true;
|
|
3474
3687
|
}
|
|
3475
3688
|
|
|
3689
|
+
// The Go standard library has no module path, so `pkg:golang/stdlib@v<x>` is
|
|
3690
|
+
// not a purl cdx-purl will build (the same reason trivy's stdlib purl is
|
|
3691
|
+
// dropped further up). The bom-ref below is the decoded form of that string,
|
|
3692
|
+
// exactly the ref trivy-produced stdlib components already carry, so both
|
|
3693
|
+
// paths land on one component. Trivy reports the stdlib version without the
|
|
3694
|
+
// `go` prefix (`v1.27.2`) while buildinfo keeps it (`go1.27.2`).
|
|
3695
|
+
function goStdlibRef(goVersion) {
|
|
3696
|
+
// "go1.26.10 X:boringcrypto" names the toolchain go1.26.10.
|
|
3697
|
+
return `pkg:golang/stdlib@v${goVersion.split(/\s/)[0].replace(/^go/, "")}`;
|
|
3698
|
+
}
|
|
3699
|
+
|
|
3700
|
+
/**
|
|
3701
|
+
* Read the Go buildinfo version of each executable file component and link it
|
|
3702
|
+
* to (or create) the matching `pkg:golang/stdlib` component. Mutates the
|
|
3703
|
+
* components in place by adding `cdx:go:buildVersion`.
|
|
3704
|
+
*
|
|
3705
|
+
* @param {Object[]} executables file components from `fileComponents(.., "executable")`
|
|
3706
|
+
* @param {string} basePath extracted rootfs the paths are relative to
|
|
3707
|
+
* @returns {{stdlibComponents: Object[], dependencies: Object[]}}
|
|
3708
|
+
*/
|
|
3709
|
+
function collectGoBuildInfo(executables, basePath) {
|
|
3710
|
+
const stdlibComponents = [];
|
|
3711
|
+
const dependencies = [];
|
|
3712
|
+
if (!executables?.length) {
|
|
3713
|
+
return { stdlibComponents, dependencies };
|
|
3714
|
+
}
|
|
3715
|
+
const stdlibRefs = new Set();
|
|
3716
|
+
for (const component of executables) {
|
|
3717
|
+
if (!component?.properties) {
|
|
3718
|
+
continue;
|
|
3719
|
+
}
|
|
3720
|
+
const srcFile = component.properties.find(
|
|
3721
|
+
(property) => property.name === "internal:SrcFile",
|
|
3722
|
+
)?.value;
|
|
3723
|
+
if (!srcFile) {
|
|
3724
|
+
continue;
|
|
3725
|
+
}
|
|
3726
|
+
const goVersion = readGoBuildVersion(
|
|
3727
|
+
join(basePath, srcFile.replace(/^\/+/, "")),
|
|
3728
|
+
);
|
|
3729
|
+
if (!goVersion) {
|
|
3730
|
+
continue;
|
|
3731
|
+
}
|
|
3732
|
+
component.properties.push({
|
|
3733
|
+
name: "cdx:go:buildVersion",
|
|
3734
|
+
value: goVersion,
|
|
3735
|
+
});
|
|
3736
|
+
// A pre-1.18 toolchain has no version string to build a stdlib ref
|
|
3737
|
+
// from; the buildVersion property alone records the fact.
|
|
3738
|
+
if (goVersion === PRE_1_18_VERSION) {
|
|
3739
|
+
continue;
|
|
3740
|
+
}
|
|
3741
|
+
const stdlibRef = goStdlibRef(goVersion);
|
|
3742
|
+
if (!stdlibRefs.has(stdlibRef)) {
|
|
3743
|
+
stdlibRefs.add(stdlibRef);
|
|
3744
|
+
stdlibComponents.push({
|
|
3745
|
+
"bom-ref": stdlibRef,
|
|
3746
|
+
name: "stdlib",
|
|
3747
|
+
type: "library",
|
|
3748
|
+
version: stdlibRef.slice("pkg:golang/stdlib@".length),
|
|
3749
|
+
group: "",
|
|
3750
|
+
});
|
|
3751
|
+
dependencies.push({ ref: stdlibRef, dependsOn: [] });
|
|
3752
|
+
}
|
|
3753
|
+
if (component["bom-ref"]) {
|
|
3754
|
+
dependencies.push({
|
|
3755
|
+
ref: component["bom-ref"],
|
|
3756
|
+
dependsOn: [stdlibRef],
|
|
3757
|
+
});
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3760
|
+
return { stdlibComponents, dependencies };
|
|
3761
|
+
}
|
|
3762
|
+
|
|
3763
|
+
/**
|
|
3764
|
+
* Read the PHP build version of each PHP-named executable and shared library,
|
|
3765
|
+
* add the cdx:php:* properties, and link the components to one platform
|
|
3766
|
+
* component per distinct version. Mutates the components in place, as the Go
|
|
3767
|
+
* collector does.
|
|
3768
|
+
*
|
|
3769
|
+
* @param {Object[]} executables file components from `fileComponents(.., "executable")`
|
|
3770
|
+
* @param {Object[]} sharedLibs file components from `fileComponents(.., "shared_library")`
|
|
3771
|
+
* @param {string} basePath extracted rootfs the paths are relative to
|
|
3772
|
+
* @returns {{runtimeComponents: Object[], dependencies: Object[]}}
|
|
3773
|
+
*/
|
|
3774
|
+
function collectPhpBuildInfo(executables, sharedLibs, basePath) {
|
|
3775
|
+
const inventory = createPhpRuntimeInventory();
|
|
3776
|
+
let probes = 0;
|
|
3777
|
+
for (const component of [...(executables || []), ...(sharedLibs || [])]) {
|
|
3778
|
+
if (probes >= MAX_PHP_PROBES) {
|
|
3779
|
+
break;
|
|
3780
|
+
}
|
|
3781
|
+
if (!component?.properties) {
|
|
3782
|
+
continue;
|
|
3783
|
+
}
|
|
3784
|
+
const srcFile = component.properties.find(
|
|
3785
|
+
(property) => property.name === "internal:SrcFile",
|
|
3786
|
+
)?.value;
|
|
3787
|
+
if (!srcFile) {
|
|
3788
|
+
continue;
|
|
3789
|
+
}
|
|
3790
|
+
const sapi = classifyPhpSapi(srcFile);
|
|
3791
|
+
if (!sapi) {
|
|
3792
|
+
continue;
|
|
3793
|
+
}
|
|
3794
|
+
const absolutePath = join(basePath, srcFile.replace(/^\/+/, ""));
|
|
3795
|
+
let resolvedPath = srcFile;
|
|
3796
|
+
try {
|
|
3797
|
+
resolvedPath = relative(
|
|
3798
|
+
realpathSync(basePath),
|
|
3799
|
+
realpathSync(absolutePath),
|
|
3800
|
+
);
|
|
3801
|
+
} catch {
|
|
3802
|
+
// readPhpBuildVersion decides what an unresolvable path yields.
|
|
3803
|
+
}
|
|
3804
|
+
// Trivy does not list Nix, Guix or Homebrew packages, so a PHP in their
|
|
3805
|
+
// stores looks unpackaged here, but it is still theirs.
|
|
3806
|
+
if (packageStoreOf(srcFile) || packageStoreOf(resolvedPath)) {
|
|
3807
|
+
continue;
|
|
3808
|
+
}
|
|
3809
|
+
probes += 1;
|
|
3810
|
+
const build = readPhpBuildVersion(absolutePath, basePath);
|
|
3811
|
+
if (!build) {
|
|
3812
|
+
continue;
|
|
3813
|
+
}
|
|
3814
|
+
attachPhpBuild(inventory, component, build, sapi);
|
|
3815
|
+
}
|
|
3816
|
+
return {
|
|
3817
|
+
runtimeComponents: inventory.runtimeComponents,
|
|
3818
|
+
dependencies: inventory.dependencies,
|
|
3819
|
+
};
|
|
3820
|
+
}
|
|
3821
|
+
|
|
3476
3822
|
async function fileComponents(basePath, fileList, fileType) {
|
|
3477
3823
|
const hashResults = await mapWithConcurrency(fileList, (f) =>
|
|
3478
3824
|
multiChecksumFile(["md5", "sha1"], join(basePath, f))
|