@cdxgen/cdxgen 13.5.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 +6 -6
- package/bin/cdxgen.js +4 -0
- package/bin/evinse.js +3 -0
- package/bin/validate.js +6 -0
- package/data/license-aliases.json +24 -2
- package/data/remediations.json +124 -0
- package/data/rules/ai-bom.yaml +1 -1
- package/data/rules/brotli-decoder.yaml +107 -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/cli/bomAssembly.js +92 -3
- package/lib/cli/index.js +108 -1
- package/lib/cli/jsBom.js +153 -64
- package/lib/cli/jvmBom.js +237 -46
- package/lib/cli/managedBom.js +142 -16
- package/lib/cli/nativeBom.js +42 -5
- package/lib/core/boundedFile.js +344 -0
- package/lib/core/brotliProbe.js +182 -0
- package/lib/core/env.js +8 -0
- package/lib/core/fs.js +12 -0
- package/lib/core/httpClient.js +98 -1
- package/lib/core/lockPaths.js +38 -0
- package/lib/core/paths.js +24 -1
- package/lib/core/processTree.js +5 -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/gradleutils.js +828 -224
- package/lib/ecosystems/parsers-bazel.js +107 -6
- package/lib/ecosystems/parsers-js.js +505 -22
- package/lib/ecosystems/parsers-misc.js +209 -20
- package/lib/ecosystems/parsers-python.js +89 -2
- package/lib/ecosystems/pythonutils.js +128 -2
- package/lib/ecosystems/rubyutils.js +538 -31
- package/lib/ecosystems/scalacliutils.js +2 -0
- package/lib/ecosystems/scalasem.js +796 -151
- package/lib/ecosystems/vendoredCode.js +237 -3
- package/lib/ecosystems/venvProbe.js +360 -0
- package/lib/evinser/evinser.js +346 -43
- package/lib/helpers/agenticToolutils.js +413 -93
- package/lib/helpers/vsixutils.js +1574 -315
- package/lib/inventory/atomUtils.js +77 -0
- package/lib/inventory/brotliDecoder.js +142 -0
- package/lib/inventory/buildToolRateLimit.js +1 -1
- package/lib/inventory/cbomutils.js +56 -7
- package/lib/inventory/deps.js +35 -0
- package/lib/inventory/dosai.js +139 -5
- package/lib/inventory/dotnetRuntime.js +217 -0
- package/lib/inventory/envcontext.js +307 -13
- package/lib/inventory/evidenceUtils.js +11 -0
- package/lib/inventory/fetchBatch.js +1 -1
- package/lib/inventory/formulationParsers.js +31 -3
- package/lib/inventory/gtfobins.js +13 -0
- package/lib/inventory/hostRuntimeProbe.js +686 -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/osPackageResolver.js +182 -0
- package/lib/inventory/phpBuildInfo.js +10 -59
- package/lib/inventory/phpHostProbe.js +32 -488
- 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 +49 -2
- package/lib/managers/binary.js +337 -3
- package/lib/managers/docker.js +79 -0
- package/lib/parsers/largeJson.js +28 -11
- package/lib/parsers/vscodeExtensionId.js +183 -0
- package/lib/server/server.js +12 -1
- package/lib/stages/postgen/ruleEngine.js +13 -5
- package/lib/stages/pregen/envAudit.js +1 -0
- package/lib/stages/pregen/pregen.js +114 -57
- package/lib/validator/bomValidator.js +6 -1
- package/lib/validator/generated/validate-1.6.mjs +1 -1
- package/lib/validator/generated/validate-1.7.mjs +1 -1
- package/lib/validator/generated/validate-2.0.mjs +1 -1
- package/lib/validator/index.js +1 -1
- package/package.json +3 -2
- package/types/lib/cli/bomAssembly.d.ts +13 -1
- package/types/lib/cli/bomAssembly.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.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 +4 -0
- 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/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/gradleutils.d.ts +47 -13
- 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-js.d.ts +17 -20
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +19 -0
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +10 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +20 -2
- 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/scalacliutils.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 +63 -8
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +19 -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/cbomutils.d.ts +17 -0
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +17 -1
- 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/envcontext.d.ts +52 -6
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
- package/types/lib/inventory/gtfobins.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/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/osPackageResolver.d.ts +38 -0
- package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
- package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -1
- package/types/lib/inventory/phpHostProbe.d.ts.map +1 -1
- 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 +10 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- 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/largeJson.d.ts +4 -3
- package/types/lib/parsers/largeJson.d.ts.map +1 -1
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +12 -8
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
package/lib/inventory/source.js
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
prefetchedResponse,
|
|
31
31
|
prefetchJson,
|
|
32
32
|
} from "./fetchBatch.js";
|
|
33
|
+
import { mavenCentralFetchUrl } from "./mavenCentralMirror.js";
|
|
33
34
|
import { canonicalPurlFromLooseString } from "./purl.js";
|
|
34
35
|
|
|
35
36
|
/** Raised when a caller omits a registry helper that the purl type requires. */
|
|
@@ -795,6 +796,48 @@ export function getGitRemoteHost(remote) {
|
|
|
795
796
|
return host.toLowerCase();
|
|
796
797
|
}
|
|
797
798
|
|
|
799
|
+
/**
|
|
800
|
+
* Remove credentials from git output before it is logged. Every URL in the
|
|
801
|
+
* text loses its user name and password, which git copies into its error
|
|
802
|
+
* messages from the remote it was given.
|
|
803
|
+
*
|
|
804
|
+
* @param {string|undefined} text Output of a git command
|
|
805
|
+
* @param {string|undefined} repoUrl Remote the command was given
|
|
806
|
+
* @returns {string|undefined} Output with credentials replaced by ***
|
|
807
|
+
*/
|
|
808
|
+
export function redactGitOutput(text, repoUrl) {
|
|
809
|
+
if (!text || typeof text !== "string") {
|
|
810
|
+
return text;
|
|
811
|
+
}
|
|
812
|
+
let redacted = text;
|
|
813
|
+
if (repoUrl && typeof repoUrl === "string") {
|
|
814
|
+
redacted = redacted.split(repoUrl).join(sanitizeRemoteUrlForLogs(repoUrl));
|
|
815
|
+
}
|
|
816
|
+
let from = 0;
|
|
817
|
+
while (from < redacted.length) {
|
|
818
|
+
const schemeEnd = redacted.indexOf("://", from);
|
|
819
|
+
if (schemeEnd === -1) {
|
|
820
|
+
break;
|
|
821
|
+
}
|
|
822
|
+
const authorityStart = schemeEnd + 3;
|
|
823
|
+
let authorityEnd = authorityStart;
|
|
824
|
+
while (
|
|
825
|
+
authorityEnd < redacted.length &&
|
|
826
|
+
!"/?#'\"<> \t\r\n".includes(redacted[authorityEnd])
|
|
827
|
+
) {
|
|
828
|
+
authorityEnd++;
|
|
829
|
+
}
|
|
830
|
+
const at = redacted.lastIndexOf("@", authorityEnd - 1);
|
|
831
|
+
if (at >= authorityStart) {
|
|
832
|
+
redacted = `${redacted.slice(0, authorityStart)}***:***${redacted.slice(at)}`;
|
|
833
|
+
from = authorityStart + "***:***".length;
|
|
834
|
+
} else {
|
|
835
|
+
from = authorityEnd;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return redacted;
|
|
839
|
+
}
|
|
840
|
+
|
|
798
841
|
/**
|
|
799
842
|
* Clone a git repository into a temporary directory.
|
|
800
843
|
*
|
|
@@ -859,8 +902,11 @@ export function gitClone(repoUrl, branch = null) {
|
|
|
859
902
|
`Cloning Repo${branch ? ` with branch ${branch}` : ""} to ${tempDir}`,
|
|
860
903
|
);
|
|
861
904
|
const result = hardenedGitCommand(gitArgs);
|
|
862
|
-
if (result.status !== 0) {
|
|
863
|
-
console.error(result.stderr);
|
|
905
|
+
if (result.status !== 0 || result.error) {
|
|
906
|
+
console.error(redactGitOutput(result.stderr, repoUrl));
|
|
907
|
+
// The directory was created by this call, so it is always safe to remove.
|
|
908
|
+
safeRmSync(tempDir, { recursive: true, force: true });
|
|
909
|
+
throw new Error("Git clone failed.");
|
|
864
910
|
}
|
|
865
911
|
|
|
866
912
|
return tempDir;
|
|
@@ -1588,6 +1634,7 @@ export async function resolveGitUrlFromPurl(purlString, helpers = {}) {
|
|
|
1588
1634
|
break;
|
|
1589
1635
|
}
|
|
1590
1636
|
const mavenCentralUrl =
|
|
1637
|
+
mavenCentralFetchUrl() ||
|
|
1591
1638
|
readEnvironmentVariable("MAVEN_CENTRAL_URL") ||
|
|
1592
1639
|
"https://repo1.maven.org/maven2/";
|
|
1593
1640
|
registry = mavenCentralUrl.endsWith("/")
|
package/lib/managers/binary.js
CHANGED
|
@@ -38,14 +38,24 @@ import {
|
|
|
38
38
|
safeRmSync,
|
|
39
39
|
safeSpawnSync,
|
|
40
40
|
} from "../core/fs.js";
|
|
41
|
+
import { thoughtLog } from "../core/logger.js";
|
|
41
42
|
import { mapWithConcurrency } from "../core/parallel.js";
|
|
42
43
|
import { isValidDriveRoot } from "../core/paths.js";
|
|
43
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";
|
|
44
53
|
import {
|
|
45
54
|
CertificateCollector,
|
|
46
55
|
classifyCryptoFile,
|
|
47
56
|
} from "../inventory/certificates.js";
|
|
48
57
|
import { createContainerRiskProperties } from "../inventory/containerRisk.js";
|
|
58
|
+
import { collectDotnetRuntimes } from "../inventory/dotnetRuntime.js";
|
|
49
59
|
import {
|
|
50
60
|
attachIdentityTools,
|
|
51
61
|
extractToolRefs,
|
|
@@ -55,6 +65,13 @@ import {
|
|
|
55
65
|
readGoBuildVersion,
|
|
56
66
|
} from "../inventory/goBuildInfo.js";
|
|
57
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";
|
|
58
75
|
import {
|
|
59
76
|
canonicalDistroQualifier,
|
|
60
77
|
getDistroInfo,
|
|
@@ -66,6 +83,7 @@ import {
|
|
|
66
83
|
import { isRealArchitecture } from "../inventory/osPackagePurl.js";
|
|
67
84
|
import {
|
|
68
85
|
collectExecutables,
|
|
86
|
+
collectNativeExtensions,
|
|
69
87
|
collectSharedLibs,
|
|
70
88
|
} from "../inventory/osPackageResolver.js";
|
|
71
89
|
import {
|
|
@@ -87,6 +105,15 @@ import {
|
|
|
87
105
|
tryBuildPurl,
|
|
88
106
|
} from "../inventory/purl.js";
|
|
89
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";
|
|
90
117
|
import {
|
|
91
118
|
adjustLicenseInformation,
|
|
92
119
|
findLicenseId,
|
|
@@ -538,6 +565,15 @@ const COMMON_RUNTIMES = [
|
|
|
538
565
|
"openjdk25-jre",
|
|
539
566
|
];
|
|
540
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
|
+
|
|
541
577
|
/**
|
|
542
578
|
* Run the `cargo-auditable` companion binary against the given source path and
|
|
543
579
|
* return its stdout.
|
|
@@ -829,7 +865,7 @@ export function dropRedundantSourceComponents(pkgList) {
|
|
|
829
865
|
const installedPurls = new Set();
|
|
830
866
|
for (const comp of pkgList) {
|
|
831
867
|
if (comp?.purl && !comp?.tags?.includes("source")) {
|
|
832
|
-
installedPurls.add(comp.purl);
|
|
868
|
+
installedPurls.add(withoutArch(comp.purl));
|
|
833
869
|
}
|
|
834
870
|
}
|
|
835
871
|
if (!installedPurls.size) {
|
|
@@ -837,13 +873,33 @@ export function dropRedundantSourceComponents(pkgList) {
|
|
|
837
873
|
}
|
|
838
874
|
for (let i = pkgList.length - 1; i >= 0; i--) {
|
|
839
875
|
const comp = pkgList[i];
|
|
840
|
-
if (
|
|
876
|
+
if (
|
|
877
|
+
comp?.tags?.includes("source") &&
|
|
878
|
+
installedPurls.has(withoutArch(comp.purl))
|
|
879
|
+
) {
|
|
841
880
|
pkgList.splice(i, 1);
|
|
842
881
|
}
|
|
843
882
|
}
|
|
844
883
|
return pkgList;
|
|
845
884
|
}
|
|
846
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
|
+
|
|
847
903
|
/**
|
|
848
904
|
* Get the packages installed in the container image filesystem.
|
|
849
905
|
*
|
|
@@ -1346,6 +1402,14 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
1346
1402
|
if (epoch) {
|
|
1347
1403
|
newCompQualifiers.epoch = epoch;
|
|
1348
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
|
+
}
|
|
1349
1413
|
newComp.purl = build({
|
|
1350
1414
|
type: purlObj.type,
|
|
1351
1415
|
namespace: purlObj.namespace || null,
|
|
@@ -1381,6 +1445,7 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
1381
1445
|
components: ownedFileComponents,
|
|
1382
1446
|
dependenciesList: ownedFileDependencies,
|
|
1383
1447
|
ownedFilePaths,
|
|
1448
|
+
ownerByPath,
|
|
1384
1449
|
services: ownedServices,
|
|
1385
1450
|
} = await createOSPackageFileComponents(src, osPackageEntries);
|
|
1386
1451
|
if (ownedFileComponents.length) {
|
|
@@ -1455,9 +1520,61 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
1455
1520
|
if (phpBuildInventory.dependencies.length) {
|
|
1456
1521
|
dependenciesList.push(...phpBuildInventory.dependencies);
|
|
1457
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
|
+
}
|
|
1458
1570
|
return {
|
|
1459
1571
|
osPackages: pkgList,
|
|
1460
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,
|
|
1461
1578
|
dependenciesList,
|
|
1462
1579
|
allTypes: Array.from(allTypes).sort(),
|
|
1463
1580
|
bundledSdks: Array.from(bundledSdks).sort(),
|
|
@@ -2696,6 +2813,7 @@ async function createOSPackageFileComponents(basePath, osPackageEntries) {
|
|
|
2696
2813
|
const components = [];
|
|
2697
2814
|
const dependenciesList = [];
|
|
2698
2815
|
const ownedFilePaths = new Set();
|
|
2816
|
+
const ownerByPath = new Map();
|
|
2699
2817
|
const services = [];
|
|
2700
2818
|
const componentByPath = new Map();
|
|
2701
2819
|
for (const packageEntry of osPackageEntries) {
|
|
@@ -2706,6 +2824,10 @@ async function createOSPackageFileComponents(basePath, osPackageEntries) {
|
|
|
2706
2824
|
continue;
|
|
2707
2825
|
}
|
|
2708
2826
|
ownedFilePaths.add(filePath);
|
|
2827
|
+
const ownerKey = filePath.replaceAll("\\", "/").replace(/^\/+/, "");
|
|
2828
|
+
if (!ownerByPath.has(ownerKey)) {
|
|
2829
|
+
ownerByPath.set(ownerKey, packageEntry.packageRef);
|
|
2830
|
+
}
|
|
2709
2831
|
let fileComponent = componentByPath.get(filePath);
|
|
2710
2832
|
if (!fileComponent) {
|
|
2711
2833
|
fileComponent = await createOSPackageFileComponent(
|
|
@@ -2744,6 +2866,7 @@ async function createOSPackageFileComponents(basePath, osPackageEntries) {
|
|
|
2744
2866
|
components,
|
|
2745
2867
|
dependenciesList,
|
|
2746
2868
|
ownedFilePaths: Array.from(ownedFilePaths).sort(),
|
|
2869
|
+
ownerByPath,
|
|
2747
2870
|
services: dedupeServices(services),
|
|
2748
2871
|
};
|
|
2749
2872
|
}
|
|
@@ -3229,7 +3352,10 @@ function detectSdksRuntimes(comp, bundledSdks, bundledRuntimes) {
|
|
|
3229
3352
|
bundledRuntimes.add(comp.name);
|
|
3230
3353
|
}
|
|
3231
3354
|
// TODO: Need to test this for a range of base images
|
|
3232
|
-
if (
|
|
3355
|
+
if (
|
|
3356
|
+
COMMON_RUNTIMES.includes(comp.name) ||
|
|
3357
|
+
VERSIONED_RUNTIME_PATTERNS.some((pattern) => pattern.test(comp.name))
|
|
3358
|
+
) {
|
|
3233
3359
|
bundledRuntimes.add(comp.name);
|
|
3234
3360
|
}
|
|
3235
3361
|
}
|
|
@@ -3819,6 +3945,214 @@ function collectPhpBuildInfo(executables, sharedLibs, basePath) {
|
|
|
3819
3945
|
};
|
|
3820
3946
|
}
|
|
3821
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
|
+
|
|
3822
4156
|
async function fileComponents(basePath, fileList, fileType) {
|
|
3823
4157
|
const hashResults = await mapWithConcurrency(fileList, (f) =>
|
|
3824
4158
|
multiChecksumFile(["md5", "sha1"], join(basePath, f))
|
package/lib/managers/docker.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
basename,
|
|
11
11
|
isAbsolute,
|
|
12
12
|
join,
|
|
13
|
+
posix,
|
|
13
14
|
relative,
|
|
14
15
|
resolve,
|
|
15
16
|
win32,
|
|
@@ -2487,6 +2488,77 @@ export const exportImage = async (fullImageName, options) => {
|
|
|
2487
2488
|
return undefined;
|
|
2488
2489
|
};
|
|
2489
2490
|
|
|
2491
|
+
// Directories that hold far more than gems. An image that names one as its gem
|
|
2492
|
+
// directory would turn the gem scan into a scan of the whole tree.
|
|
2493
|
+
const BROAD_IMAGE_DIRS = new Set([
|
|
2494
|
+
"/bin",
|
|
2495
|
+
"/boot",
|
|
2496
|
+
"/dev",
|
|
2497
|
+
"/etc",
|
|
2498
|
+
"/home",
|
|
2499
|
+
"/lib",
|
|
2500
|
+
"/lib64",
|
|
2501
|
+
"/opt",
|
|
2502
|
+
"/proc",
|
|
2503
|
+
"/root",
|
|
2504
|
+
"/run",
|
|
2505
|
+
"/sbin",
|
|
2506
|
+
"/srv",
|
|
2507
|
+
"/sys",
|
|
2508
|
+
"/tmp",
|
|
2509
|
+
"/usr",
|
|
2510
|
+
"/usr/bin",
|
|
2511
|
+
"/usr/lib",
|
|
2512
|
+
"/usr/lib64",
|
|
2513
|
+
"/usr/local",
|
|
2514
|
+
"/usr/local/lib",
|
|
2515
|
+
"/usr/share",
|
|
2516
|
+
"/var",
|
|
2517
|
+
"/var/lib",
|
|
2518
|
+
]);
|
|
2519
|
+
const MAX_IMAGE_GEM_DIRS = 16;
|
|
2520
|
+
|
|
2521
|
+
/**
|
|
2522
|
+
* The gem directories an image's environment names in GEM_HOME, GEM_PATH and
|
|
2523
|
+
* BUNDLE_PATH. Only absolute paths without `..` segments are kept, so an image
|
|
2524
|
+
* cannot steer the scan outside its own root, and each is normalized; the root
|
|
2525
|
+
* and broad system directories such as /usr or /var are refused, so an image
|
|
2526
|
+
* cannot turn the gem scan into a scan of the whole tree.
|
|
2527
|
+
*
|
|
2528
|
+
* @param {string[]|undefined} env image config Env entries, `NAME=value`
|
|
2529
|
+
* @returns {string[]} absolute in-image directories, deduplicated, at most
|
|
2530
|
+
* MAX_IMAGE_GEM_DIRS
|
|
2531
|
+
*/
|
|
2532
|
+
export function imageGemDirs(env) {
|
|
2533
|
+
const dirs = new Set();
|
|
2534
|
+
if (!Array.isArray(env)) {
|
|
2535
|
+
return [];
|
|
2536
|
+
}
|
|
2537
|
+
for (const entry of env) {
|
|
2538
|
+
if (typeof entry !== "string") {
|
|
2539
|
+
continue;
|
|
2540
|
+
}
|
|
2541
|
+
const separator = entry.indexOf("=");
|
|
2542
|
+
if (separator <= 0) {
|
|
2543
|
+
continue;
|
|
2544
|
+
}
|
|
2545
|
+
const name = entry.slice(0, separator);
|
|
2546
|
+
if (!["GEM_HOME", "GEM_PATH", "BUNDLE_PATH"].includes(name)) {
|
|
2547
|
+
continue;
|
|
2548
|
+
}
|
|
2549
|
+
for (const value of entry.slice(separator + 1).split(":")) {
|
|
2550
|
+
if (!value.startsWith("/") || value.split("/").includes("..")) {
|
|
2551
|
+
continue;
|
|
2552
|
+
}
|
|
2553
|
+
const dir = posix.normalize(value).replace(/\/+$/, "");
|
|
2554
|
+
if (dir && !BROAD_IMAGE_DIRS.has(dir)) {
|
|
2555
|
+
dirs.add(dir);
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
return Array.from(dirs).sort().slice(0, MAX_IMAGE_GEM_DIRS);
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2490
2562
|
/**
|
|
2491
2563
|
* Method to retrieve path list for system-level packages
|
|
2492
2564
|
*/
|
|
@@ -2530,6 +2602,7 @@ export const getPkgPathList = (exportData, lastWorkingDir) => {
|
|
|
2530
2602
|
if (allLayersExplodedDir && allLayersExplodedDir !== "") {
|
|
2531
2603
|
knownSysPaths = knownSysPaths.concat([
|
|
2532
2604
|
join(allLayersExplodedDir, "/usr/local/go"),
|
|
2605
|
+
join(allLayersExplodedDir, "/usr/local/bundle"),
|
|
2533
2606
|
join(allLayersExplodedDir, "/usr/local/lib"),
|
|
2534
2607
|
join(allLayersExplodedDir, "/usr/local/lib64"),
|
|
2535
2608
|
join(allLayersExplodedDir, "/opt"),
|
|
@@ -2544,6 +2617,7 @@ export const getPkgPathList = (exportData, lastWorkingDir) => {
|
|
|
2544
2617
|
} else if (allLayersExplodedDir === "") {
|
|
2545
2618
|
knownSysPaths = knownSysPaths.concat([
|
|
2546
2619
|
join(allLayersExplodedDir, "/usr/local/go"),
|
|
2620
|
+
join(allLayersExplodedDir, "/usr/local/bundle"),
|
|
2547
2621
|
join(allLayersExplodedDir, "/usr/local/lib"),
|
|
2548
2622
|
join(allLayersExplodedDir, "/usr/local/lib64"),
|
|
2549
2623
|
join(allLayersExplodedDir, "/opt"),
|
|
@@ -2554,6 +2628,11 @@ export const getPkgPathList = (exportData, lastWorkingDir) => {
|
|
|
2554
2628
|
join(allLayersExplodedDir, "/var/lib"),
|
|
2555
2629
|
]);
|
|
2556
2630
|
}
|
|
2631
|
+
// The official Ruby images install gems into GEM_HOME=/usr/local/bundle, and
|
|
2632
|
+
// an image can point GEM_HOME, GEM_PATH or BUNDLE_PATH anywhere else.
|
|
2633
|
+
for (const gemDir of imageGemDirs(exportData?.inspectData?.Config?.Env)) {
|
|
2634
|
+
knownSysPaths.push(join(allLayersExplodedDir || "", gemDir));
|
|
2635
|
+
}
|
|
2557
2636
|
if (safeExistsSync(join(allLayersDir, "Files"))) {
|
|
2558
2637
|
knownSysPaths.push(join(allLayersDir, "Files"));
|
|
2559
2638
|
}
|