@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
|
@@ -16,6 +16,7 @@ import { build, Purl } from "@cdxgen/cdx-purl";
|
|
|
16
16
|
// utils.js does not import rubyutils.js, so importing from it here is safe and
|
|
17
17
|
// does not introduce a cyclic dependency.
|
|
18
18
|
import { DEBUG_MODE, readEnvironmentVariable } from "../core/activity.js";
|
|
19
|
+
import { readRegularTextFile } from "../core/boundedFile.js";
|
|
19
20
|
import { shouldFetchLicense } from "../core/env.js";
|
|
20
21
|
import { getAllFiles, safeExistsSync, safeSpawnSync } from "../core/fs.js";
|
|
21
22
|
import { thoughtLog } from "../core/logger.js";
|
|
@@ -29,6 +30,12 @@ import {
|
|
|
29
30
|
recordPolicyDegradationFromError,
|
|
30
31
|
} from "../inventory/fetchBatch.js";
|
|
31
32
|
import { genericPurl } from "../inventory/purl.js";
|
|
33
|
+
import {
|
|
34
|
+
createRubyRuntimeComponent,
|
|
35
|
+
defaultGemInstall,
|
|
36
|
+
parseRubyVersion,
|
|
37
|
+
} from "../inventory/rubyBuildInfo.js";
|
|
38
|
+
import { isValidIdnEmail } from "../parsers/email.js";
|
|
32
39
|
|
|
33
40
|
// FIXME. This has to get removed, once we improve the module detection one-liner.
|
|
34
41
|
// If you're a Rubyist, please help us improve this code.
|
|
@@ -1562,6 +1569,40 @@ function listGemBindirFiles(gemspecFile, bindir = "bin") {
|
|
|
1562
1569
|
}
|
|
1563
1570
|
}
|
|
1564
1571
|
|
|
1572
|
+
/**
|
|
1573
|
+
* Whether gemspec text is really a Marshal dump. RubyGems' spec cache stores
|
|
1574
|
+
* Marshal-dumped specifications in files named `<gem>-<version>.gemspec`; they
|
|
1575
|
+
* start with the Marshal 4.8 header bytes 0x04 0x08 and hold no Ruby source to
|
|
1576
|
+
* parse, so reading one would leave only the file name to guess from.
|
|
1577
|
+
*
|
|
1578
|
+
* @param {string} gemspecData file contents
|
|
1579
|
+
* @returns {boolean} true for a Marshal dump
|
|
1580
|
+
*/
|
|
1581
|
+
function isMarshalData(gemspecData) {
|
|
1582
|
+
return (
|
|
1583
|
+
typeof gemspecData === "string" &&
|
|
1584
|
+
gemspecData.charCodeAt(0) === 4 &&
|
|
1585
|
+
gemspecData.charCodeAt(1) === 8
|
|
1586
|
+
);
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
/**
|
|
1590
|
+
* Whether a gemspec is a default gem, one that ships with Ruby: RubyGems
|
|
1591
|
+
* keeps them under `specifications/default/`.
|
|
1592
|
+
*
|
|
1593
|
+
* @param {string} gemspecFile path of the gemspec, either separator
|
|
1594
|
+
* @returns {boolean}
|
|
1595
|
+
*/
|
|
1596
|
+
export function isDefaultGemspecPath(gemspecFile) {
|
|
1597
|
+
if (typeof gemspecFile !== "string") {
|
|
1598
|
+
return false;
|
|
1599
|
+
}
|
|
1600
|
+
return (
|
|
1601
|
+
defaultGemInstall(gemspecFile) !== undefined ||
|
|
1602
|
+
gemspecFile.replaceAll("\\", "/").includes("/specifications/default/")
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1565
1606
|
/**
|
|
1566
1607
|
* Method to parse Gemspec file contents.
|
|
1567
1608
|
*
|
|
@@ -1593,7 +1634,7 @@ export async function parseGemspecData(gemspecData, gemspecFile, options = {}) {
|
|
|
1593
1634
|
if (gemspecFile) {
|
|
1594
1635
|
pkg.name = basename(gemspecFile).replace(".gemspec", "");
|
|
1595
1636
|
}
|
|
1596
|
-
if (!gemspecData) {
|
|
1637
|
+
if (!gemspecData || isMarshalData(gemspecData)) {
|
|
1597
1638
|
return pkgList;
|
|
1598
1639
|
}
|
|
1599
1640
|
let versionHackMatch = false;
|
|
@@ -1948,6 +1989,12 @@ export async function parseGemspecData(gemspecData, gemspecFile, options = {}) {
|
|
|
1948
1989
|
}
|
|
1949
1990
|
if (gemspecFile) {
|
|
1950
1991
|
pkg.properties.push({ name: "internal:SrcFile", value: gemspecFile });
|
|
1992
|
+
// RubyGems keeps the gems that ship with Ruby itself, such as resolv and
|
|
1993
|
+
// net-http, under specifications/default. They are updated with Ruby, so
|
|
1994
|
+
// their advisories are often fixed by a Ruby release.
|
|
1995
|
+
if (isDefaultGemspecPath(gemspecFile)) {
|
|
1996
|
+
pkg.properties.push({ name: "cdx:gem:defaultGem", value: "true" });
|
|
1997
|
+
}
|
|
1951
1998
|
// Did we find the version number from the directory name? Let's reduce the confidence and set the correct technique
|
|
1952
1999
|
pkg.evidence = {
|
|
1953
2000
|
identity: {
|
|
@@ -1967,9 +2014,11 @@ export async function parseGemspecData(gemspecData, gemspecFile, options = {}) {
|
|
|
1967
2014
|
// `spec.email` holds the contact addresses of the gem, not of an author.
|
|
1968
2015
|
// An address can only be attributed when there is one per author, in the
|
|
1969
2016
|
// same order, or when the gem has a single author.
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
)
|
|
2017
|
+
// Only a valid `idn-email` may reach `organizationalContact.email`; the
|
|
2018
|
+
// schema asserts the format, so anything else fails BOM validation.
|
|
2019
|
+
const emails = (lists.email || [])
|
|
2020
|
+
.map((email) => `${email}`.trim())
|
|
2021
|
+
.filter(isValidIdnEmail);
|
|
1973
2022
|
pkg.authors = lists.authors.map((name, i) => {
|
|
1974
2023
|
let email;
|
|
1975
2024
|
if (emails.length === lists.authors.length) {
|
|
@@ -2858,30 +2907,434 @@ export function rubyRequirementToVers(requirements) {
|
|
|
2858
2907
|
return `vers:gem/${constraints.map((c) => c.text).join("|")}`;
|
|
2859
2908
|
}
|
|
2860
2909
|
|
|
2910
|
+
// Ruby engines a version manager or Bundler can name. MRI is `ruby`.
|
|
2911
|
+
const RUBY_ENGINES = new Set(["ruby", "jruby", "truffleruby"]);
|
|
2912
|
+
const MAX_PIN_FILE_BYTES = 64 * 1024;
|
|
2913
|
+
const MAX_GEMFILE_BYTES = 1024 * 1024;
|
|
2914
|
+
const MAX_LOCKFILE_BYTES = 16 * 1024 * 1024;
|
|
2915
|
+
|
|
2916
|
+
// An exact version, or a partial one such as `3.3` that pins a series and is
|
|
2917
|
+
// read as the requirement `~> 3.3.0`.
|
|
2918
|
+
function rubyPin(engine, text, source) {
|
|
2919
|
+
const value = `${text || ""}`.trim();
|
|
2920
|
+
if (!value || value.length > 64) {
|
|
2921
|
+
return undefined;
|
|
2922
|
+
}
|
|
2923
|
+
if (engine === "ruby") {
|
|
2924
|
+
const version = parseRubyVersion(value);
|
|
2925
|
+
if (version) {
|
|
2926
|
+
return { engine, version, source };
|
|
2927
|
+
}
|
|
2928
|
+
if (/^\d+\.\d+$/.test(value)) {
|
|
2929
|
+
return { engine, requirement: `~> ${value}.0`, source };
|
|
2930
|
+
}
|
|
2931
|
+
if (/^\d+$/.test(value)) {
|
|
2932
|
+
return { engine, requirement: `~> ${value}.0`, source };
|
|
2933
|
+
}
|
|
2934
|
+
return undefined;
|
|
2935
|
+
}
|
|
2936
|
+
return /^\d+(\.\d+){1,3}$/.test(value)
|
|
2937
|
+
? { engine, version: value, source }
|
|
2938
|
+
: undefined;
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
/**
|
|
2942
|
+
* Read the Ruby a `.ruby-version` file selects, as rbenv, chruby, rvm, asdf
|
|
2943
|
+
* and mise read it: `3.3.6`, `ruby-3.3.6`, `3.3`, `jruby-9.4.8.0`,
|
|
2944
|
+
* `truffleruby-24.1.0`, `truffleruby+graalvm-24.1.0`. Only the first word
|
|
2945
|
+
* counts; comments and blank lines are skipped.
|
|
2946
|
+
*
|
|
2947
|
+
* @param {string} text file contents
|
|
2948
|
+
* @returns {{engine: string, version?: string, requirement?: string, source: string}|undefined}
|
|
2949
|
+
*/
|
|
2950
|
+
export function parseRubyVersionFile(text) {
|
|
2951
|
+
if (typeof text !== "string") {
|
|
2952
|
+
return undefined;
|
|
2953
|
+
}
|
|
2954
|
+
for (const rawLine of text.slice(0, MAX_PIN_FILE_BYTES).split(/\r?\n/)) {
|
|
2955
|
+
const line = rawLine.split("#")[0].trim().split(/\s+/)[0];
|
|
2956
|
+
if (!line) {
|
|
2957
|
+
continue;
|
|
2958
|
+
}
|
|
2959
|
+
const dash = line.indexOf("-");
|
|
2960
|
+
if (dash > 0 && !/^\d/.test(line)) {
|
|
2961
|
+
const engine = line.slice(0, dash).split("+")[0];
|
|
2962
|
+
if (!RUBY_ENGINES.has(engine)) {
|
|
2963
|
+
return undefined;
|
|
2964
|
+
}
|
|
2965
|
+
return rubyPin(engine, line.slice(dash + 1), ".ruby-version");
|
|
2966
|
+
}
|
|
2967
|
+
return rubyPin("ruby", line, ".ruby-version");
|
|
2968
|
+
}
|
|
2969
|
+
return undefined;
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
/**
|
|
2973
|
+
* Read the Ruby an asdf `.tool-versions` file selects: the first version on
|
|
2974
|
+
* the `ruby` line, e.g. `ruby 3.3.6 3.2.2`.
|
|
2975
|
+
*
|
|
2976
|
+
* @param {string} text file contents
|
|
2977
|
+
* @returns {{engine: string, version?: string, requirement?: string, source: string}|undefined}
|
|
2978
|
+
*/
|
|
2979
|
+
export function parseToolVersionsRuby(text) {
|
|
2980
|
+
if (typeof text !== "string") {
|
|
2981
|
+
return undefined;
|
|
2982
|
+
}
|
|
2983
|
+
for (const rawLine of text.slice(0, MAX_PIN_FILE_BYTES).split(/\r?\n/)) {
|
|
2984
|
+
const words = rawLine.split("#")[0].trim().split(/\s+/);
|
|
2985
|
+
if (words[0] !== "ruby" || !words[1]) {
|
|
2986
|
+
continue;
|
|
2987
|
+
}
|
|
2988
|
+
const value = words[1];
|
|
2989
|
+
const dash = value.indexOf("-");
|
|
2990
|
+
if (dash > 0 && !/^\d/.test(value)) {
|
|
2991
|
+
const engine = value.slice(0, dash).split("+")[0];
|
|
2992
|
+
return RUBY_ENGINES.has(engine)
|
|
2993
|
+
? rubyPin(engine, value.slice(dash + 1), ".tool-versions")
|
|
2994
|
+
: undefined;
|
|
2995
|
+
}
|
|
2996
|
+
return rubyPin("ruby", value, ".tool-versions");
|
|
2997
|
+
}
|
|
2998
|
+
return undefined;
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
/**
|
|
3002
|
+
* Read the Ruby a mise configuration selects from its `[tools]` table:
|
|
3003
|
+
* `ruby = "3.3.6"`, `ruby = ["3.3.6", "3.2"]` or `ruby = { version = "3.3.6" }`.
|
|
3004
|
+
*
|
|
3005
|
+
* @param {string} text mise.toml contents
|
|
3006
|
+
* @param {string} [source="mise.toml"] the file name, for the result
|
|
3007
|
+
* @returns {{engine: string, version?: string, requirement?: string, source: string}|undefined}
|
|
3008
|
+
*/
|
|
3009
|
+
export function parseMiseRuby(text, source = "mise.toml") {
|
|
3010
|
+
if (typeof text !== "string") {
|
|
3011
|
+
return undefined;
|
|
3012
|
+
}
|
|
3013
|
+
let inTools = false;
|
|
3014
|
+
for (const rawLine of text.slice(0, MAX_PIN_FILE_BYTES).split(/\r?\n/)) {
|
|
3015
|
+
const line = rawLine.trim();
|
|
3016
|
+
if (line.startsWith("[")) {
|
|
3017
|
+
// `[tools]`, `[ tools ]` and `[tools] # pinned` open the same table
|
|
3018
|
+
inTools = line.split("#")[0].replace(/\s+/g, "") === "[tools]";
|
|
3019
|
+
continue;
|
|
3020
|
+
}
|
|
3021
|
+
if (!inTools || !/^["']?ruby["']?\s*=/.test(line)) {
|
|
3022
|
+
continue;
|
|
3023
|
+
}
|
|
3024
|
+
const value = line.slice(line.indexOf("=") + 1).trim();
|
|
3025
|
+
const quoted = /["']([^"']+)["']/.exec(
|
|
3026
|
+
value.startsWith("{") ? value.slice(value.indexOf("version")) : value,
|
|
3027
|
+
);
|
|
3028
|
+
return quoted ? rubyPin("ruby", quoted[1], source) : undefined;
|
|
3029
|
+
}
|
|
3030
|
+
return undefined;
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
/**
|
|
3034
|
+
* Read the `ruby` directive of a Gemfile: `ruby "3.3.6"`, `ruby "~> 3.3"`,
|
|
3035
|
+
* `ruby "3.1.4", engine: "jruby", engine_version: "9.4.8.0"`, or
|
|
3036
|
+
* `ruby file: ".ruby-version"`. The Gemfile is never evaluated, so a
|
|
3037
|
+
* directive built from an expression, such as `ruby RUBY_VERSION`, says
|
|
3038
|
+
* nothing.
|
|
3039
|
+
*
|
|
3040
|
+
* @param {string} text Gemfile contents
|
|
3041
|
+
* @returns {{engine: string, version?: string, requirement?: string,
|
|
3042
|
+
* file?: string, source: string}|undefined}
|
|
3043
|
+
*/
|
|
3044
|
+
export function parseGemfileRubyDirective(text) {
|
|
3045
|
+
if (typeof text !== "string") {
|
|
3046
|
+
return undefined;
|
|
3047
|
+
}
|
|
3048
|
+
for (const rawLine of text.slice(0, 1024 * 1024).split(/\r?\n/)) {
|
|
3049
|
+
const line = rawLine.trim();
|
|
3050
|
+
if (!/^ruby[\s(]/.test(line)) {
|
|
3051
|
+
continue;
|
|
3052
|
+
}
|
|
3053
|
+
let rest = line.slice(4).trim();
|
|
3054
|
+
if (rest.startsWith("(")) {
|
|
3055
|
+
rest = rest.slice(1).trim();
|
|
3056
|
+
}
|
|
3057
|
+
const fileMatch = /^file:\s*/.exec(rest);
|
|
3058
|
+
if (fileMatch) {
|
|
3059
|
+
const literal = readRubyStringLiteral(rest.slice(fileMatch[0].length));
|
|
3060
|
+
return literal?.value && !literal.interpolated
|
|
3061
|
+
? { engine: "ruby", file: literal.value, source: "Gemfile" }
|
|
3062
|
+
: undefined;
|
|
3063
|
+
}
|
|
3064
|
+
const literal = readRubyStringLiteral(rest);
|
|
3065
|
+
if (!literal?.value || literal.interpolated) {
|
|
3066
|
+
return undefined;
|
|
3067
|
+
}
|
|
3068
|
+
// `ruby "3.2.2" if ENV["LEGACY"]` holds only on some machines; a later
|
|
3069
|
+
// unconditional directive, if any, is the project's.
|
|
3070
|
+
if (/(^|[\s,)])(if|unless)\s/.test(literal.rest)) {
|
|
3071
|
+
continue;
|
|
3072
|
+
}
|
|
3073
|
+
const keyword = (name) => {
|
|
3074
|
+
const index = rest.indexOf(`${name}:`);
|
|
3075
|
+
if (index === -1) {
|
|
3076
|
+
return undefined;
|
|
3077
|
+
}
|
|
3078
|
+
const value = readRubyStringLiteral(
|
|
3079
|
+
rest.slice(index + name.length + 1).trim(),
|
|
3080
|
+
);
|
|
3081
|
+
return value && !value.interpolated ? value.value : undefined;
|
|
3082
|
+
};
|
|
3083
|
+
const engine = keyword("engine");
|
|
3084
|
+
const engineVersion = keyword("engine_version");
|
|
3085
|
+
if (engine && engine !== "ruby") {
|
|
3086
|
+
if (!RUBY_ENGINES.has(engine) || !engineVersion) {
|
|
3087
|
+
return undefined;
|
|
3088
|
+
}
|
|
3089
|
+
const pin = rubyPin(engine, engineVersion, "Gemfile");
|
|
3090
|
+
return pin ? { ...pin, rubyVersion: literal.value } : undefined;
|
|
3091
|
+
}
|
|
3092
|
+
const value = literal.value.trim();
|
|
3093
|
+
const exact = parseRubyVersion(value);
|
|
3094
|
+
if (exact) {
|
|
3095
|
+
return { engine: "ruby", version: exact, source: "Gemfile" };
|
|
3096
|
+
}
|
|
3097
|
+
return rubyRequirementToVers([value]) !== undefined
|
|
3098
|
+
? { engine: "ruby", requirement: value, source: "Gemfile" }
|
|
3099
|
+
: undefined;
|
|
3100
|
+
}
|
|
3101
|
+
return undefined;
|
|
3102
|
+
}
|
|
3103
|
+
|
|
2861
3104
|
/**
|
|
2862
|
-
*
|
|
3105
|
+
* Read the `RUBY VERSION` section of a Gemfile.lock, which Bundler writes when
|
|
3106
|
+
* the Gemfile has a `ruby` directive: `ruby 3.3.6p108`, `ruby 3.4.0p-1` for a
|
|
3107
|
+
* pre-release, or `ruby 3.1.4p0 (jruby 9.4.8.0)` for another engine.
|
|
3108
|
+
*
|
|
3109
|
+
* @param {string} text Gemfile.lock contents
|
|
3110
|
+
* @returns {{engine: string, version: string, patchlevel?: string,
|
|
3111
|
+
* rubyVersion?: string, source: string}|undefined}
|
|
3112
|
+
*/
|
|
3113
|
+
export function parseGemfileLockRubyVersion(text) {
|
|
3114
|
+
if (typeof text !== "string") {
|
|
3115
|
+
return undefined;
|
|
3116
|
+
}
|
|
3117
|
+
const lines = text.split(/\r?\n/);
|
|
3118
|
+
for (let index = 0; index < lines.length; index++) {
|
|
3119
|
+
if (lines[index].trim() !== "RUBY VERSION") {
|
|
3120
|
+
continue;
|
|
3121
|
+
}
|
|
3122
|
+
const value = (lines[index + 1] || "").trim();
|
|
3123
|
+
const match =
|
|
3124
|
+
/^ruby (\d+\.\d+\.\d+)(?:p(-?\d+))?(?: \((jruby|truffleruby) (\d+(?:\.\d+){1,3})\))?$/.exec(
|
|
3125
|
+
value,
|
|
3126
|
+
);
|
|
3127
|
+
if (!match) {
|
|
3128
|
+
return undefined;
|
|
3129
|
+
}
|
|
3130
|
+
if (match[3]) {
|
|
3131
|
+
return {
|
|
3132
|
+
engine: match[3],
|
|
3133
|
+
version: match[4],
|
|
3134
|
+
rubyVersion: match[1],
|
|
3135
|
+
source: "Gemfile.lock",
|
|
3136
|
+
};
|
|
3137
|
+
}
|
|
3138
|
+
const result = {
|
|
3139
|
+
engine: "ruby",
|
|
3140
|
+
version: match[1],
|
|
3141
|
+
source: "Gemfile.lock",
|
|
3142
|
+
};
|
|
3143
|
+
if (match[2] !== undefined && match[2] !== "-1") {
|
|
3144
|
+
result.patchlevel = match[2];
|
|
3145
|
+
} else if (match[2] === "-1") {
|
|
3146
|
+
// A pre-release or development build, whose tag Bundler does not record
|
|
3147
|
+
result.prerelease = true;
|
|
3148
|
+
}
|
|
3149
|
+
return result;
|
|
3150
|
+
}
|
|
3151
|
+
return undefined;
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
// A pin file is read only when it is a regular file inside the project and
|
|
3155
|
+
// within a size cap: a repository can commit `.tool-versions -> /dev/zero` or
|
|
3156
|
+
// a FIFO, which an unbounded read would never finish.
|
|
3157
|
+
function readPinFile(file, projectRoot, maxBytes = MAX_PIN_FILE_BYTES) {
|
|
3158
|
+
return readRegularTextFile(file, maxBytes, projectRoot);
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
/**
|
|
3162
|
+
* Collect every Ruby a project directory declares, from the Gemfile.lock
|
|
3163
|
+
* `RUBY VERSION` section, `.ruby-version`, `.tool-versions`, mise.toml and the
|
|
3164
|
+
* Gemfile `ruby` directive. Each declaration names its source.
|
|
3165
|
+
*
|
|
3166
|
+
* @param {string} projectPath project directory
|
|
3167
|
+
* @param {string} [lockData] Gemfile.lock contents, when already read
|
|
3168
|
+
* @returns {Array<{engine: string, version?: string, requirement?: string,
|
|
3169
|
+
* patchlevel?: string, source: string, path: string}>}
|
|
3170
|
+
*/
|
|
3171
|
+
export function collectProjectRubyDeclarations(projectPath, lockData) {
|
|
3172
|
+
const declarations = [];
|
|
3173
|
+
const add = (declaration, path) => {
|
|
3174
|
+
if (declaration) {
|
|
3175
|
+
declarations.push({ ...declaration, path });
|
|
3176
|
+
}
|
|
3177
|
+
};
|
|
3178
|
+
const lockFile = join(projectPath, "Gemfile.lock");
|
|
3179
|
+
add(
|
|
3180
|
+
parseGemfileLockRubyVersion(
|
|
3181
|
+
lockData === undefined
|
|
3182
|
+
? readPinFile(lockFile, projectPath, MAX_LOCKFILE_BYTES)
|
|
3183
|
+
: lockData,
|
|
3184
|
+
),
|
|
3185
|
+
lockFile,
|
|
3186
|
+
);
|
|
3187
|
+
const versionFile = join(projectPath, ".ruby-version");
|
|
3188
|
+
add(parseRubyVersionFile(readPinFile(versionFile, projectPath)), versionFile);
|
|
3189
|
+
const toolVersions = join(projectPath, ".tool-versions");
|
|
3190
|
+
add(
|
|
3191
|
+
parseToolVersionsRuby(readPinFile(toolVersions, projectPath)),
|
|
3192
|
+
toolVersions,
|
|
3193
|
+
);
|
|
3194
|
+
for (const name of ["mise.toml", ".mise.toml", ".config/mise.toml"]) {
|
|
3195
|
+
const miseFile = join(projectPath, name);
|
|
3196
|
+
add(parseMiseRuby(readPinFile(miseFile, projectPath), name), miseFile);
|
|
3197
|
+
}
|
|
3198
|
+
const gemfile = join(projectPath, "Gemfile");
|
|
3199
|
+
const directive = parseGemfileRubyDirective(
|
|
3200
|
+
readPinFile(gemfile, projectPath, MAX_GEMFILE_BYTES),
|
|
3201
|
+
);
|
|
3202
|
+
if (directive?.file) {
|
|
3203
|
+
// `ruby file: ".ruby-version"` reads the version from that file, relative
|
|
3204
|
+
// to the Gemfile; a path out of the project is not followed.
|
|
3205
|
+
const target = resolve(projectPath, directive.file);
|
|
3206
|
+
const inside = relative(resolve(projectPath), target);
|
|
3207
|
+
if (inside && !inside.startsWith("..") && !isAbsolute(inside)) {
|
|
3208
|
+
// Bundler reads .tool-versions and mise.toml through `ruby file:` too
|
|
3209
|
+
const text = readPinFile(target, projectPath);
|
|
3210
|
+
const name = basename(target);
|
|
3211
|
+
const fromFile =
|
|
3212
|
+
name === ".tool-versions"
|
|
3213
|
+
? parseToolVersionsRuby(text)
|
|
3214
|
+
: name.endsWith(".toml")
|
|
3215
|
+
? parseMiseRuby(text)
|
|
3216
|
+
: parseRubyVersionFile(text);
|
|
3217
|
+
add(fromFile ? { ...fromFile, source: "Gemfile" } : undefined, gemfile);
|
|
3218
|
+
}
|
|
3219
|
+
} else {
|
|
3220
|
+
add(directive, gemfile);
|
|
3221
|
+
}
|
|
3222
|
+
return declarations;
|
|
3223
|
+
}
|
|
3224
|
+
|
|
3225
|
+
// The Ruby a lockfile was resolved with is what the project ran on; the pin
|
|
3226
|
+
// files say what developers select; the Gemfile says what Bundler accepts.
|
|
3227
|
+
const DECLARATION_PRECEDENCE = [
|
|
3228
|
+
"Gemfile.lock",
|
|
3229
|
+
".ruby-version",
|
|
3230
|
+
".tool-versions",
|
|
3231
|
+
"mise.toml",
|
|
3232
|
+
".mise.toml",
|
|
3233
|
+
".config/mise.toml",
|
|
3234
|
+
"Gemfile",
|
|
3235
|
+
];
|
|
3236
|
+
|
|
3237
|
+
/**
|
|
3238
|
+
* Settle the Ruby a project runs on from its declarations: the exact version
|
|
3239
|
+
* of the most authoritative source that has one, and the requirements of the
|
|
3240
|
+
* sources that only constrain it.
|
|
3241
|
+
*
|
|
3242
|
+
* @param {Array<Object>} declarations result of collectProjectRubyDeclarations
|
|
3243
|
+
* @returns {{engine: string, version?: string, patchlevel?: string,
|
|
3244
|
+
* prerelease?: boolean, rubyVersion?: string, source?: string,
|
|
3245
|
+
* requirements: string[], conflicts: string[]}|undefined}
|
|
3246
|
+
*/
|
|
3247
|
+
export function resolveProjectRuby(declarations) {
|
|
3248
|
+
if (!declarations?.length) {
|
|
3249
|
+
return undefined;
|
|
3250
|
+
}
|
|
3251
|
+
const ordered = [...declarations].sort(
|
|
3252
|
+
(a, b) =>
|
|
3253
|
+
DECLARATION_PRECEDENCE.indexOf(a.source) -
|
|
3254
|
+
DECLARATION_PRECEDENCE.indexOf(b.source),
|
|
3255
|
+
);
|
|
3256
|
+
let exact = ordered.find((declaration) => declaration.version);
|
|
3257
|
+
// Bundler records a pre-release as `ruby 3.4.0p-1`, without its tag. A pin
|
|
3258
|
+
// that names the tag of that same version says which pre-release it is.
|
|
3259
|
+
const lockedPrerelease = exact?.prerelease ? exact : undefined;
|
|
3260
|
+
if (lockedPrerelease) {
|
|
3261
|
+
exact =
|
|
3262
|
+
ordered.find(
|
|
3263
|
+
(declaration) =>
|
|
3264
|
+
declaration.engine === lockedPrerelease.engine &&
|
|
3265
|
+
declaration.version?.startsWith(`${lockedPrerelease.version}-`),
|
|
3266
|
+
) || lockedPrerelease;
|
|
3267
|
+
}
|
|
3268
|
+
const requirements = ordered
|
|
3269
|
+
.filter((declaration) => declaration.requirement)
|
|
3270
|
+
.map((declaration) => declaration.requirement);
|
|
3271
|
+
const result = {
|
|
3272
|
+
engine: exact?.engine || "ruby",
|
|
3273
|
+
requirements,
|
|
3274
|
+
conflicts: [],
|
|
3275
|
+
};
|
|
3276
|
+
if (exact) {
|
|
3277
|
+
for (const key of [
|
|
3278
|
+
"version",
|
|
3279
|
+
"patchlevel",
|
|
3280
|
+
"prerelease",
|
|
3281
|
+
"rubyVersion",
|
|
3282
|
+
"source",
|
|
3283
|
+
]) {
|
|
3284
|
+
if (exact[key] !== undefined) {
|
|
3285
|
+
result[key] = exact[key];
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
for (const other of ordered) {
|
|
3289
|
+
if (
|
|
3290
|
+
other !== exact &&
|
|
3291
|
+
other !== lockedPrerelease &&
|
|
3292
|
+
other.version &&
|
|
3293
|
+
(other.engine !== exact.engine || other.version !== exact.version)
|
|
3294
|
+
) {
|
|
3295
|
+
result.conflicts.push(
|
|
3296
|
+
`${other.source}: ${other.engine} ${other.version}`,
|
|
3297
|
+
);
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
return result;
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
/**
|
|
3305
|
+
* Describe the Ruby runtime of a project as one external `platform`
|
|
2863
3306
|
* component, and make the components that declare a `required_ruby_version`
|
|
2864
3307
|
* depend on it (discussion 4409).
|
|
2865
3308
|
*
|
|
2866
|
-
*
|
|
2867
|
-
*
|
|
2868
|
-
*
|
|
2869
|
-
*
|
|
2870
|
-
*
|
|
2871
|
-
*
|
|
3309
|
+
* When the project pins a Ruby (Gemfile.lock `RUBY VERSION`, `.ruby-version`,
|
|
3310
|
+
* `.tool-versions`, mise or the Gemfile `ruby` directive), the component names
|
|
3311
|
+
* that version, `pkg:generic/ruby@3.3.6`, with the CPE a scanner matches
|
|
3312
|
+
* interpreter advisories by. The requirements of the gems then stay in the
|
|
3313
|
+
* `cdx:ruby:requiredVersionRange` property, since CycloneDX allows a
|
|
3314
|
+
* `versionRange` only on a component without a version.
|
|
3315
|
+
*
|
|
3316
|
+
* Without a pin, the component stands for the Ruby the bundle as a whole runs
|
|
3317
|
+
* on, so its `versionRange` is the intersection of every requirement that
|
|
3318
|
+
* could be read, in the `vers` syntax CycloneDX mandates for the field. Each
|
|
3319
|
+
* component's own requirement stays in its `cdx:gem:rubyVersionSpecifiers`
|
|
3320
|
+
* property. One runtime component per distinct requirement would describe the
|
|
3321
|
+
* same interpreter many times over: a modest bundle spells `>= 2.x` a dozen
|
|
3322
|
+
* ways.
|
|
2872
3323
|
*
|
|
2873
3324
|
* The type is `platform`, the CycloneDX type for a runtime environment that
|
|
2874
3325
|
* interprets software, and the one cdxgen gives the Ruby it finds in the
|
|
2875
|
-
* build environment.
|
|
3326
|
+
* build environment. A range is not a version, so an unpinned purl names none.
|
|
2876
3327
|
*
|
|
2877
|
-
* `isExternal` and `versionRange` are CycloneDX 1.7 fields
|
|
2878
|
-
*
|
|
2879
|
-
*
|
|
3328
|
+
* `isExternal` and `versionRange` are CycloneDX 1.7 fields. A pinned runtime
|
|
3329
|
+
* also carries the `cdx:ruby:isExternal` property, which says the interpreter
|
|
3330
|
+
* comes with the deployment environment in a BOM of an older spec; the caller
|
|
3331
|
+
* decides whether such a BOM should carry an unpinned runtime at all.
|
|
2880
3332
|
*
|
|
2881
3333
|
* @param {Array} pkgList Components
|
|
2882
3334
|
* @param {object} parentComponent The metadata.component, when the project is
|
|
2883
3335
|
* itself a gem
|
|
2884
3336
|
* @param {Array} dependencies Dependency edges
|
|
3337
|
+
* @param {object} [projectRuby] result of resolveProjectRuby
|
|
2885
3338
|
* @returns {{pkgList: Array, dependencies: Array}} Components and edges with
|
|
2886
3339
|
* the runtime component added
|
|
2887
3340
|
*/
|
|
@@ -2889,6 +3342,7 @@ export function addRubyRuntimeComponent(
|
|
|
2889
3342
|
pkgList,
|
|
2890
3343
|
parentComponent,
|
|
2891
3344
|
dependencies,
|
|
3345
|
+
projectRuby,
|
|
2892
3346
|
) {
|
|
2893
3347
|
const requirements = [];
|
|
2894
3348
|
const declaringRefs = new Set();
|
|
@@ -2905,29 +3359,85 @@ export function addRubyRuntimeComponent(
|
|
|
2905
3359
|
declaringRefs.add(component["bom-ref"]);
|
|
2906
3360
|
}
|
|
2907
3361
|
}
|
|
2908
|
-
|
|
3362
|
+
for (const requirement of projectRuby?.requirements || []) {
|
|
3363
|
+
if (rubyRequirementToVers([requirement]) !== undefined) {
|
|
3364
|
+
requirements.push(requirement);
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
const pinned = projectRuby?.version;
|
|
3368
|
+
if (!pinned && !requirements.length) {
|
|
2909
3369
|
return { pkgList, dependencies };
|
|
2910
3370
|
}
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
runtimeComponent.
|
|
3371
|
+
if (projectRuby && parentComponent?.["bom-ref"]) {
|
|
3372
|
+
declaringRefs.add(parentComponent["bom-ref"]);
|
|
3373
|
+
}
|
|
3374
|
+
const versionRange = requirements.length
|
|
3375
|
+
? rubyRequirementToVers(requirements)
|
|
3376
|
+
: undefined;
|
|
3377
|
+
let runtimeComponent;
|
|
3378
|
+
if (pinned) {
|
|
3379
|
+
runtimeComponent = createRubyRuntimeComponent(pinned, {
|
|
3380
|
+
engine: projectRuby.engine,
|
|
3381
|
+
// Bundler records no tag for a pre-release, so no CPE can name it
|
|
3382
|
+
withCpe: !projectRuby.prerelease,
|
|
3383
|
+
});
|
|
3384
|
+
runtimeComponent.scope = "required";
|
|
3385
|
+
runtimeComponent.isExternal = true;
|
|
3386
|
+
const properties = [
|
|
3387
|
+
{ name: "cdx:ruby:isExternal", value: "true" },
|
|
3388
|
+
{ name: "cdx:ruby:versionSource", value: projectRuby.source },
|
|
3389
|
+
];
|
|
3390
|
+
if (projectRuby.prerelease) {
|
|
3391
|
+
// Not the release of that number, and no tag says which pre-release
|
|
3392
|
+
properties.push({ name: "cdx:ruby:prerelease", value: "true" });
|
|
3393
|
+
}
|
|
3394
|
+
if (projectRuby.patchlevel !== undefined) {
|
|
3395
|
+
properties.push({
|
|
3396
|
+
name: "cdx:ruby:patchlevel",
|
|
3397
|
+
value: projectRuby.patchlevel,
|
|
3398
|
+
});
|
|
3399
|
+
}
|
|
3400
|
+
if (projectRuby.rubyVersion) {
|
|
3401
|
+
properties.push({
|
|
3402
|
+
name: "cdx:ruby:languageVersion",
|
|
3403
|
+
value: projectRuby.rubyVersion,
|
|
3404
|
+
});
|
|
3405
|
+
}
|
|
3406
|
+
if (versionRange) {
|
|
3407
|
+
properties.push({
|
|
3408
|
+
name: "cdx:ruby:requiredVersionRange",
|
|
3409
|
+
value: versionRange,
|
|
3410
|
+
});
|
|
3411
|
+
}
|
|
3412
|
+
if (projectRuby.conflicts?.length) {
|
|
3413
|
+
properties.push({
|
|
3414
|
+
name: "cdx:ruby:conflictingDeclarations",
|
|
3415
|
+
value: projectRuby.conflicts.join(", "),
|
|
3416
|
+
});
|
|
3417
|
+
}
|
|
3418
|
+
runtimeComponent.properties = properties;
|
|
2925
3419
|
} else {
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
3420
|
+
const runtimeRef = genericPurl("ruby");
|
|
3421
|
+
runtimeComponent = {
|
|
3422
|
+
type: "platform",
|
|
3423
|
+
name: "ruby",
|
|
3424
|
+
scope: "required",
|
|
3425
|
+
// The interpreter comes with the deployment environment, not with the
|
|
3426
|
+
// product the BOM describes
|
|
3427
|
+
isExternal: true,
|
|
3428
|
+
purl: runtimeRef,
|
|
3429
|
+
"bom-ref": runtimeRef,
|
|
3430
|
+
};
|
|
3431
|
+
if (versionRange) {
|
|
3432
|
+
runtimeComponent.versionRange = versionRange;
|
|
3433
|
+
} else {
|
|
3434
|
+
thoughtLog(
|
|
3435
|
+
"The Ruby version requirements of the gems exclude each other, so the Ruby runtime component carries no version range.",
|
|
3436
|
+
{ requirements },
|
|
3437
|
+
);
|
|
3438
|
+
}
|
|
2930
3439
|
}
|
|
3440
|
+
const runtimeRef = runtimeComponent["bom-ref"];
|
|
2931
3441
|
const describedRefs = new Set(dependencies.map((d) => d.ref));
|
|
2932
3442
|
const outDependencies = dependencies.map((d) =>
|
|
2933
3443
|
declaringRefs.has(d.ref)
|
package/lib/ecosystems/rusi.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
reportPluginRunFailure,
|
|
15
15
|
resolvePluginSourceDir,
|
|
16
16
|
} from "../core/pluginRun.js";
|
|
17
|
+
import { isProjectToolingAllowed } from "../core/projectTooling.js";
|
|
17
18
|
import { applyAlgorithmProperties } from "../inventory/cryptoAlgorithmFamily.js";
|
|
18
19
|
import { resolvePluginBinary } from "../inventory/plugins.js";
|
|
19
20
|
|
|
@@ -821,6 +822,15 @@ export function runRusiAnalysis(src, outputFile, options = {}) {
|
|
|
821
822
|
if (!executable || !src || !outputFile) {
|
|
822
823
|
return false;
|
|
823
824
|
}
|
|
825
|
+
// rusi runs cargo and rustc on the project, which follow its
|
|
826
|
+
// `.cargo/config.toml` and `rust-toolchain.toml` and can compile its build
|
|
827
|
+
// scripts, so it waits for permission to run the project's tooling.
|
|
828
|
+
if (!isProjectToolingAllowed(options)) {
|
|
829
|
+
console.log(
|
|
830
|
+
"Skipping rusi since dependency installation is disabled. Rust evidence needs cargo to run on the project.",
|
|
831
|
+
);
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
824
834
|
|
|
825
835
|
// cdxgen may run with its working directory already inside the project
|
|
826
836
|
// while the source argument is relative to a repository root; a plain
|