@cdxgen/cdxgen 0.0.1 → 13.0.1
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 +921 -87
- package/bin/audit.js +239 -0
- package/bin/cdxgen.js +2335 -0
- package/bin/convert.js +220 -0
- package/bin/evinse.js +353 -0
- package/bin/hbom.js +504 -0
- package/bin/repl.js +2224 -0
- package/bin/sign.js +195 -0
- package/bin/tracebom.js +303 -0
- package/bin/validate.js +268 -0
- package/bin/verify.js +189 -0
- package/data/README.md +307 -0
- package/data/bom-1.4.schema.json +1605 -0
- package/data/bom-1.5.schema.json +3660 -0
- package/data/bom-1.6.schema.json +5511 -0
- package/data/bom-1.7.schema.json +6700 -0
- package/data/cbomosdb-queries.json +68 -0
- package/data/component-tags.json +339 -0
- package/data/container-knowledge-index.json +125 -0
- package/data/cosdb-queries.json +17 -0
- package/data/crypto-oid.json +1998 -0
- package/data/cryptography-defs.json +3793 -0
- package/data/cryptography-defs.schema.json +576 -0
- package/data/cyclonedx-2.0-bundled.schema.json +7182 -0
- package/data/frameworks-list.json +219 -0
- package/data/glibc-stdlib.json +117 -0
- package/data/gtfobins-index.json +6296 -0
- package/data/helpers/init.gradle +71 -0
- package/data/jsf-0.82.schema.json +211 -0
- package/data/known-licenses.json +82 -0
- package/data/lic-mapping.json +338 -0
- package/data/license-aliases.json +6624 -0
- package/data/license-db.json +39059 -0
- package/data/license-deprecations.json +302 -0
- package/data/lolbas-index.json +150 -0
- package/data/predictive-audit-allowlist.json +11 -0
- package/data/pypi-pkg-aliases.json +1199 -0
- package/data/python-stdlib.json +308 -0
- package/data/queries-darwin.json +193 -0
- package/data/queries-win.json +257 -0
- package/data/queries.json +268 -0
- package/data/ruby-known-modules.json +281 -0
- package/data/rules/ai-agent-governance.yaml +323 -0
- package/data/rules/ai-bom.yaml +268 -0
- package/data/rules/ai-oversight.yaml +186 -0
- package/data/rules/ai-provenance.yaml +92 -0
- package/data/rules/asar-archives.yaml +150 -0
- package/data/rules/cbom-security.yaml +165 -0
- package/data/rules/chrome-extensions.yaml +248 -0
- package/data/rules/ci-permissions.yaml +802 -0
- package/data/rules/container-risk.yaml +353 -0
- package/data/rules/dependency-sources.yaml +236 -0
- package/data/rules/golem-go.yaml +229 -0
- package/data/rules/hbom-compliance.yaml +325 -0
- package/data/rules/hbom-performance.yaml +307 -0
- package/data/rules/hbom-security.yaml +248 -0
- package/data/rules/host-topology.yaml +165 -0
- package/data/rules/mcp-servers.yaml +388 -0
- package/data/rules/obom-runtime.yaml +1776 -0
- package/data/rules/package-integrity.yaml +364 -0
- package/data/rules/rootfs-hardening.yaml +179 -0
- package/data/rules/vscode-extensions.yaml +237 -0
- package/data/spdx-export.schema.json +6794 -0
- package/data/spdx-license-list.json +7449 -0
- package/data/spdx-licenses.json +759 -0
- package/data/spdx.schema.json +819 -0
- package/data/templates/README.md +3 -0
- package/data/templates/asvs-4.0.3.cdx.json +2943 -0
- package/data/templates/asvs-5.0.cdx.json +3046 -0
- package/data/templates/bsimm-v13.cdx.json +1008 -0
- package/data/templates/masvs-2.0.0.cdx.json +225 -0
- package/data/templates/nist-ssdf-1.1.cdx.json +828 -0
- package/data/templates/pcissc-secure-slc-1.1.cdx.json +1027 -0
- package/data/templates/scvs-1.0.0.cdx.json +813 -0
- package/data/templates/ssaf-DRAFT-2023-11.cdx.json +127 -0
- package/data/vendor-alias.json +41967 -0
- package/data/wrapdb-releases.json +4227 -0
- package/index.cjs +15 -0
- package/lib/audit/index.js +2800 -0
- package/lib/audit/progress.js +114 -0
- package/lib/audit/reporters.js +937 -0
- package/lib/audit/scoring.js +560 -0
- package/lib/audit/targets.js +798 -0
- package/lib/cli/bomAssembly.js +1882 -0
- package/lib/cli/cliOptions.js +543 -0
- package/lib/cli/index.js +2759 -0
- package/lib/cli/jsBom.js +2087 -0
- package/lib/cli/jvmBom.js +1858 -0
- package/lib/cli/managedBom.js +2083 -0
- package/lib/cli/nativeBom.js +2125 -0
- package/lib/core/activity.js +1087 -0
- package/lib/core/env.js +719 -0
- package/lib/core/fs.js +1058 -0
- package/lib/core/httpClient.js +730 -0
- package/lib/core/logger.js +280 -0
- package/lib/core/parallel.js +323 -0
- package/lib/core/paths.js +70 -0
- package/lib/core/projectConfig.js +122 -0
- package/lib/core/propertySanitizer.js +174 -0
- package/lib/core/spdx.js +5 -0
- package/lib/core/state.js +59 -0
- package/lib/core/text.js +207 -0
- package/lib/core/ui.js +920 -0
- package/lib/core/urls.js +84 -0
- package/lib/ecosystems/asarutils.js +1636 -0
- package/lib/ecosystems/bunutils.js +402 -0
- package/lib/ecosystems/caxa.js +77 -0
- package/lib/ecosystems/chromextutils.js +1174 -0
- package/lib/ecosystems/cmakeResolver.js +587 -0
- package/lib/ecosystems/cppEvidence.js +305 -0
- package/lib/ecosystems/denoutils.js +1094 -0
- package/lib/ecosystems/dotnetutils.js +132 -0
- package/lib/ecosystems/ecosystems.js +2772 -0
- package/lib/ecosystems/golem.js +1807 -0
- package/lib/ecosystems/gradleutils.js +1048 -0
- package/lib/ecosystems/jsEvidence.js +317 -0
- package/lib/ecosystems/npmutils.js +586 -0
- package/lib/ecosystems/parsers-bazel.js +875 -0
- package/lib/ecosystems/parsers-dotnet.js +1430 -0
- package/lib/ecosystems/parsers-gleam.js +302 -0
- package/lib/ecosystems/parsers-go.js +818 -0
- package/lib/ecosystems/parsers-js.js +4222 -0
- package/lib/ecosystems/parsers-jvm.js +948 -0
- package/lib/ecosystems/parsers-misc.js +3191 -0
- package/lib/ecosystems/parsers-python.js +2070 -0
- package/lib/ecosystems/parsers-rust.js +1294 -0
- package/lib/ecosystems/parsers-zig.js +861 -0
- package/lib/ecosystems/pep770.js +341 -0
- package/lib/ecosystems/pylockutils.js +427 -0
- package/lib/ecosystems/pythonutils.js +1281 -0
- package/lib/ecosystems/registryProvenance.js +1003 -0
- package/lib/ecosystems/remote/huggingface.js +1343 -0
- package/lib/ecosystems/rubyutils.js +1629 -0
- package/lib/ecosystems/rusi.js +820 -0
- package/lib/ecosystems/sbtutils.js +972 -0
- package/lib/ecosystems/tea.js +607 -0
- package/lib/ecosystems/utils.js +583 -0
- package/lib/ecosystems/zigResolver.js +410 -0
- package/lib/evinser/db.js +145 -0
- package/lib/evinser/evinser.js +2362 -0
- package/lib/evinser/scalasem.js +65 -0
- package/lib/evinser/swiftsem.js +767 -0
- package/lib/helpers/annotationFormatter.js +84 -0
- package/lib/helpers/bomSigner.js +312 -0
- package/lib/helpers/exportUtils.js +160 -0
- package/lib/helpers/remote/dependency-track.js +117 -0
- package/lib/helpers/versutils.js +202 -0
- package/lib/helpers/vsixutils.js +1069 -0
- package/lib/inventory/agentFormulationParser.js +322 -0
- package/lib/inventory/aiCollector.js +2140 -0
- package/lib/inventory/aiInventory.js +414 -0
- package/lib/inventory/aiModelVariants.js +121 -0
- package/lib/inventory/aiOversightCollector.js +661 -0
- package/lib/inventory/aiProvenanceCollector.js +822 -0
- package/lib/inventory/analyzer.js +6089 -0
- package/lib/inventory/analyzerScope.js +807 -0
- package/lib/inventory/astWorker.js +20 -0
- package/lib/inventory/atomUtils.js +1092 -0
- package/lib/inventory/auditCategories.js +192 -0
- package/lib/inventory/bomUtils.js +369 -0
- package/lib/inventory/cacheCommands.js +182 -0
- package/lib/inventory/cacheDir.js +104 -0
- package/lib/inventory/cbomutils.js +590 -0
- package/lib/inventory/cdxrs.js +434 -0
- package/lib/inventory/ciParsers/azurePipelines.js +295 -0
- package/lib/inventory/ciParsers/circleCi.js +286 -0
- package/lib/inventory/ciParsers/common.js +24 -0
- package/lib/inventory/ciParsers/githubActions.js +2434 -0
- package/lib/inventory/ciParsers/gitlabCi.js +213 -0
- package/lib/inventory/ciParsers/jenkins.js +180 -0
- package/lib/inventory/citations.js +234 -0
- package/lib/inventory/communityAiConfigParser.js +756 -0
- package/lib/inventory/containerRisk.js +204 -0
- package/lib/inventory/cryptoAlgorithmFamily.js +258 -0
- package/lib/inventory/deps.js +807 -0
- package/lib/inventory/depsUtils.js +588 -0
- package/lib/inventory/display.js +2128 -0
- package/lib/inventory/dosai.js +601 -0
- package/lib/inventory/dosaiParsers.js +154 -0
- package/lib/inventory/dynamic.js +145 -0
- package/lib/inventory/envcontext.js +1793 -0
- package/lib/inventory/evidenceUtils.js +774 -0
- package/lib/inventory/fetchBatch.js +779 -0
- package/lib/inventory/fetchRate.js +255 -0
- package/lib/inventory/forgeEnricher.js +300 -0
- package/lib/inventory/formulationParsers.js +422 -0
- package/lib/inventory/gtfobins.js +339 -0
- package/lib/inventory/hbom.js +719 -0
- package/lib/inventory/hbomAnalysis.js +310 -0
- package/lib/inventory/hbomLoader.js +35 -0
- package/lib/inventory/hostTopology.js +835 -0
- package/lib/inventory/inventoryStats.js +100 -0
- package/lib/inventory/jsonLike.js +121 -0
- package/lib/inventory/licenseEnhancer.js +918 -0
- package/lib/inventory/lolbas.js +285 -0
- package/lib/inventory/mcp.js +253 -0
- package/lib/inventory/mcpConfigParser.js +683 -0
- package/lib/inventory/mcpDiscovery.js +112 -0
- package/lib/inventory/mcpPinning.js +209 -0
- package/lib/inventory/osPackageResolver.js +493 -0
- package/lib/inventory/osinfo.js +383 -0
- package/lib/inventory/osqueryTransform.js +274 -0
- package/lib/inventory/plugins.js +466 -0
- package/lib/inventory/protobom.js +231 -0
- package/lib/inventory/protobomLoader.js +43 -0
- package/lib/inventory/provenanceUtils.js +224 -0
- package/lib/inventory/purl.js +749 -0
- package/lib/inventory/rustFormulationParser.js +339 -0
- package/lib/inventory/sbomDocument.js +340 -0
- package/lib/inventory/source.js +1488 -0
- package/lib/inventory/spdx.js +361 -0
- package/lib/inventory/spdxExpression.js +512 -0
- package/lib/inventory/spdxUtils.js +107 -0
- package/lib/inventory/table.js +267 -0
- package/lib/inventory/traceRunner.js +439 -0
- package/lib/inventory/unicodeScan.js +147 -0
- package/lib/managers/binary.js +3459 -0
- package/lib/managers/containerutils.js +125 -0
- package/lib/managers/docker.js +2145 -0
- package/lib/managers/dockerConnection.js +162 -0
- package/lib/managers/oci.js +704 -0
- package/lib/managers/piptree.js +267 -0
- package/lib/parsers/cmakeCache.js +199 -0
- package/lib/parsers/gguf.js +492 -0
- package/lib/parsers/gitAiNotes.js +233 -0
- package/lib/parsers/gitmodules.js +239 -0
- package/lib/parsers/htmlExtract.js +610 -0
- package/lib/parsers/huggingfaceManifest.js +551 -0
- package/lib/parsers/huggingfaceUtils.js +506 -0
- package/lib/parsers/iri.js +618 -0
- package/lib/parsers/npmrc.js +99 -0
- package/lib/parsers/ollama.js +101 -0
- package/lib/parsers/xml.js +841 -0
- package/lib/server/openapi.yaml +433 -0
- package/lib/server/server.js +616 -0
- package/lib/stages/postgen/annotator.js +977 -0
- package/lib/stages/postgen/auditBom.js +468 -0
- package/lib/stages/postgen/postgen.js +1239 -0
- package/lib/stages/postgen/ruleEngine.js +731 -0
- package/lib/stages/postgen/sortBom.js +195 -0
- package/lib/stages/postgen/spdxConverter.js +796 -0
- package/lib/stages/postgen/specVersionCompat.js +703 -0
- package/lib/stages/pregen/envAudit.js +388 -0
- package/lib/stages/pregen/pregen.js +637 -0
- package/lib/third-party/README.md +9 -0
- package/lib/third-party/arborist/CHANGELOG.md +1109 -0
- package/lib/third-party/arborist/LICENSE.md +20 -0
- package/lib/third-party/arborist/README.md +315 -0
- package/lib/third-party/arborist/lib/arborist/index.js +258 -0
- package/lib/third-party/arborist/lib/arborist/load-actual.js +605 -0
- package/lib/third-party/arborist/lib/arborist/load-virtual.js +300 -0
- package/lib/third-party/arborist/lib/bin-links.js +120 -0
- package/lib/third-party/arborist/lib/calc-dep-flags.js +130 -0
- package/lib/third-party/arborist/lib/case-insensitive-map.js +51 -0
- package/lib/third-party/arborist/lib/consistent-resolve.js +42 -0
- package/lib/third-party/arborist/lib/debug.js +50 -0
- package/lib/third-party/arborist/lib/dep-valid.js +190 -0
- package/lib/third-party/arborist/lib/edge.js +417 -0
- package/lib/third-party/arborist/lib/from-path.js +36 -0
- package/lib/third-party/arborist/lib/gather-dep-set.js +43 -0
- package/lib/third-party/arborist/lib/index.js +22 -0
- package/lib/third-party/arborist/lib/inventory.js +138 -0
- package/lib/third-party/arborist/lib/link.js +169 -0
- package/lib/third-party/arborist/lib/node.js +1673 -0
- package/lib/third-party/arborist/lib/npm-extension.js +45 -0
- package/lib/third-party/arborist/lib/override-resolves.js +11 -0
- package/lib/third-party/arborist/lib/override-set.js +319 -0
- package/lib/third-party/arborist/lib/package-extensions.js +289 -0
- package/lib/third-party/arborist/lib/printable.js +210 -0
- package/lib/third-party/arborist/lib/proc-log.js +30 -0
- package/lib/third-party/arborist/lib/realpath.js +100 -0
- package/lib/third-party/arborist/lib/relpath.js +4 -0
- package/lib/third-party/arborist/lib/shrinkwrap.js +1291 -0
- package/lib/third-party/arborist/lib/spec-from-lock.js +34 -0
- package/lib/third-party/arborist/lib/tree-check.js +165 -0
- package/lib/third-party/arborist/lib/validate-npm-package-name.js +107 -0
- package/lib/third-party/arborist/lib/version-from-tgz.js +51 -0
- package/lib/third-party/arborist/lib/yarn-lock.js +397 -0
- package/lib/validator/bomValidator.js +985 -0
- package/lib/validator/complianceEngine.js +241 -0
- package/lib/validator/complianceRules.js +1680 -0
- package/lib/validator/generated/runtime.mjs +17 -0
- package/lib/validator/generated/validate-1.6.mjs +21258 -0
- package/lib/validator/generated/validate-1.7.mjs +24803 -0
- package/lib/validator/generated/validate-2.0.mjs +22931 -0
- package/lib/validator/index.js +246 -0
- package/lib/validator/reporters/annotations.js +142 -0
- package/lib/validator/reporters/console.js +160 -0
- package/lib/validator/reporters/index.js +41 -0
- package/lib/validator/reporters/json.js +37 -0
- package/lib/validator/reporters/sarif.js +184 -0
- package/package.json +209 -26
- package/types/bin/audit.d.ts +3 -0
- package/types/bin/audit.d.ts.map +1 -0
- package/types/bin/cdxgen.d.ts +3 -0
- package/types/bin/cdxgen.d.ts.map +1 -0
- package/types/bin/convert.d.ts +3 -0
- package/types/bin/convert.d.ts.map +1 -0
- package/types/bin/evinse.d.ts +3 -0
- package/types/bin/evinse.d.ts.map +1 -0
- package/types/bin/hbom.d.ts +3 -0
- package/types/bin/hbom.d.ts.map +1 -0
- package/types/bin/repl.d.ts +3 -0
- package/types/bin/repl.d.ts.map +1 -0
- package/types/bin/sign.d.ts +3 -0
- package/types/bin/sign.d.ts.map +1 -0
- package/types/bin/tracebom.d.ts +3 -0
- package/types/bin/tracebom.d.ts.map +1 -0
- package/types/bin/validate.d.ts +14 -0
- package/types/bin/validate.d.ts.map +1 -0
- package/types/bin/verify.d.ts +3 -0
- package/types/bin/verify.d.ts.map +1 -0
- package/types/lib/audit/index.d.ts +153 -0
- package/types/lib/audit/index.d.ts.map +1 -0
- package/types/lib/audit/progress.d.ts +31 -0
- package/types/lib/audit/progress.d.ts.map +1 -0
- package/types/lib/audit/reporters.d.ts +62 -0
- package/types/lib/audit/reporters.d.ts.map +1 -0
- package/types/lib/audit/scoring.d.ts +36 -0
- package/types/lib/audit/scoring.d.ts.map +1 -0
- package/types/lib/audit/targets.d.ts +77 -0
- package/types/lib/audit/targets.d.ts.map +1 -0
- package/types/lib/cli/bomAssembly.d.ts +329 -0
- package/types/lib/cli/bomAssembly.d.ts.map +1 -0
- package/types/lib/cli/cliOptions.d.ts +156 -0
- package/types/lib/cli/cliOptions.d.ts.map +1 -0
- package/types/lib/cli/index.d.ts +107 -0
- package/types/lib/cli/index.d.ts.map +1 -0
- package/types/lib/cli/jsBom.d.ts +94 -0
- package/types/lib/cli/jsBom.d.ts.map +1 -0
- package/types/lib/cli/jvmBom.d.ts +54 -0
- package/types/lib/cli/jvmBom.d.ts.map +1 -0
- package/types/lib/cli/managedBom.d.ts +105 -0
- package/types/lib/cli/managedBom.d.ts.map +1 -0
- package/types/lib/cli/nativeBom.d.ts +143 -0
- package/types/lib/cli/nativeBom.d.ts.map +1 -0
- package/types/lib/core/activity.d.ts +178 -0
- package/types/lib/core/activity.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +192 -0
- package/types/lib/core/env.d.ts.map +1 -0
- package/types/lib/core/fs.d.ts +158 -0
- package/types/lib/core/fs.d.ts.map +1 -0
- package/types/lib/core/httpClient.d.ts +149 -0
- package/types/lib/core/httpClient.d.ts.map +1 -0
- package/types/lib/core/logger.d.ts +50 -0
- package/types/lib/core/logger.d.ts.map +1 -0
- package/types/lib/core/parallel.d.ts +95 -0
- package/types/lib/core/parallel.d.ts.map +1 -0
- package/types/lib/core/paths.d.ts +36 -0
- package/types/lib/core/paths.d.ts.map +1 -0
- package/types/lib/core/projectConfig.d.ts +23 -0
- package/types/lib/core/projectConfig.d.ts.map +1 -0
- package/types/lib/core/propertySanitizer.d.ts +23 -0
- package/types/lib/core/propertySanitizer.d.ts.map +1 -0
- package/types/lib/core/spdx.d.ts +5 -0
- package/types/lib/core/spdx.d.ts.map +1 -0
- package/types/lib/core/state.d.ts +20 -0
- package/types/lib/core/state.d.ts.map +1 -0
- package/types/lib/core/text.d.ts +78 -0
- package/types/lib/core/text.d.ts.map +1 -0
- package/types/lib/core/ui.d.ts +87 -0
- package/types/lib/core/ui.d.ts.map +1 -0
- package/types/lib/core/urls.d.ts +43 -0
- package/types/lib/core/urls.d.ts.map +1 -0
- package/types/lib/ecosystems/asarutils.d.ts +71 -0
- package/types/lib/ecosystems/asarutils.d.ts.map +1 -0
- package/types/lib/ecosystems/bunutils.d.ts +38 -0
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -0
- package/types/lib/ecosystems/caxa.d.ts +8 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -0
- package/types/lib/ecosystems/chromextutils.d.ts +97 -0
- package/types/lib/ecosystems/chromextutils.d.ts.map +1 -0
- package/types/lib/ecosystems/cmakeResolver.d.ts +176 -0
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +35 -0
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -0
- package/types/lib/ecosystems/denoutils.d.ts +76 -0
- package/types/lib/ecosystems/denoutils.d.ts.map +1 -0
- package/types/lib/ecosystems/dotnetutils.d.ts +45 -0
- package/types/lib/ecosystems/dotnetutils.d.ts.map +1 -0
- package/types/lib/ecosystems/ecosystems.d.ts +279 -0
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -0
- package/types/lib/ecosystems/golem.d.ts +60 -0
- package/types/lib/ecosystems/golem.d.ts.map +1 -0
- package/types/lib/ecosystems/gradleutils.d.ts +139 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -0
- package/types/lib/ecosystems/jsEvidence.d.ts +17 -0
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -0
- package/types/lib/ecosystems/npmutils.d.ts +183 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-bazel.d.ts +36 -0
- package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts +116 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-gleam.d.ts +33 -0
- package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-go.d.ts +92 -0
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-js.d.ts +242 -0
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-jvm.d.ts +100 -0
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-misc.d.ts +450 -0
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-python.d.ts +145 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rust.d.ts +56 -0
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-zig.d.ts +83 -0
- package/types/lib/ecosystems/parsers-zig.d.ts.map +1 -0
- package/types/lib/ecosystems/pep770.d.ts +38 -0
- package/types/lib/ecosystems/pep770.d.ts.map +1 -0
- package/types/lib/ecosystems/pylockutils.d.ts +68 -0
- package/types/lib/ecosystems/pylockutils.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +70 -0
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -0
- package/types/lib/ecosystems/registryProvenance.d.ts +26 -0
- package/types/lib/ecosystems/registryProvenance.d.ts.map +1 -0
- package/types/lib/ecosystems/remote/huggingface.d.ts +31 -0
- package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -0
- package/types/lib/ecosystems/rubyutils.d.ts +182 -0
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -0
- package/types/lib/ecosystems/rusi.d.ts +40 -0
- package/types/lib/ecosystems/rusi.d.ts.map +1 -0
- package/types/lib/ecosystems/sbtutils.d.ts +179 -0
- package/types/lib/ecosystems/sbtutils.d.ts.map +1 -0
- package/types/lib/ecosystems/tea.d.ts +180 -0
- package/types/lib/ecosystems/tea.d.ts.map +1 -0
- package/types/lib/ecosystems/utils.d.ts +41 -0
- package/types/lib/ecosystems/utils.d.ts.map +1 -0
- package/types/lib/ecosystems/zigResolver.d.ts +70 -0
- package/types/lib/ecosystems/zigResolver.d.ts.map +1 -0
- package/types/lib/evinser/db.d.ts +17 -0
- package/types/lib/evinser/db.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +354 -0
- package/types/lib/evinser/evinser.d.ts.map +1 -0
- package/types/lib/evinser/scalasem.d.ts +15 -0
- package/types/lib/evinser/scalasem.d.ts.map +1 -0
- package/types/lib/evinser/swiftsem.d.ts +103 -0
- package/types/lib/evinser/swiftsem.d.ts.map +1 -0
- package/types/lib/helpers/annotationFormatter.d.ts +23 -0
- package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
- package/types/lib/helpers/bomSigner.d.ts +28 -0
- package/types/lib/helpers/bomSigner.d.ts.map +1 -0
- package/types/lib/helpers/exportUtils.d.ts +52 -0
- package/types/lib/helpers/exportUtils.d.ts.map +1 -0
- package/types/lib/helpers/remote/dependency-track.d.ts +23 -0
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
- package/types/lib/helpers/versutils.d.ts +8 -0
- package/types/lib/helpers/versutils.d.ts.map +1 -0
- package/types/lib/helpers/vsixutils.d.ts +130 -0
- package/types/lib/helpers/vsixutils.d.ts.map +1 -0
- package/types/lib/inventory/agentFormulationParser.d.ts +21 -0
- package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -0
- package/types/lib/inventory/aiCollector.d.ts +61 -0
- package/types/lib/inventory/aiCollector.d.ts.map +1 -0
- package/types/lib/inventory/aiInventory.d.ts +103 -0
- package/types/lib/inventory/aiInventory.d.ts.map +1 -0
- package/types/lib/inventory/aiModelVariants.d.ts +31 -0
- package/types/lib/inventory/aiModelVariants.d.ts.map +1 -0
- package/types/lib/inventory/aiOversightCollector.d.ts +28 -0
- package/types/lib/inventory/aiOversightCollector.d.ts.map +1 -0
- package/types/lib/inventory/aiProvenanceCollector.d.ts +13 -0
- package/types/lib/inventory/aiProvenanceCollector.d.ts.map +1 -0
- package/types/lib/inventory/analyzer.d.ts +157 -0
- package/types/lib/inventory/analyzer.d.ts.map +1 -0
- package/types/lib/inventory/analyzerScope.d.ts +106 -0
- package/types/lib/inventory/analyzerScope.d.ts.map +1 -0
- package/types/lib/inventory/astWorker.d.ts +9 -0
- package/types/lib/inventory/astWorker.d.ts.map +1 -0
- package/types/lib/inventory/atomUtils.d.ts +159 -0
- package/types/lib/inventory/atomUtils.d.ts.map +1 -0
- package/types/lib/inventory/auditCategories.d.ts +80 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -0
- package/types/lib/inventory/bomUtils.d.ts +145 -0
- package/types/lib/inventory/bomUtils.d.ts.map +1 -0
- package/types/lib/inventory/cacheCommands.d.ts +28 -0
- package/types/lib/inventory/cacheCommands.d.ts.map +1 -0
- package/types/lib/inventory/cacheDir.d.ts +55 -0
- package/types/lib/inventory/cacheDir.d.ts.map +1 -0
- package/types/lib/inventory/cbomutils.d.ts +37 -0
- package/types/lib/inventory/cbomutils.d.ts.map +1 -0
- package/types/lib/inventory/cdxrs.d.ts +90 -0
- package/types/lib/inventory/cdxrs.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/azurePipelines.d.ts +27 -0
- package/types/lib/inventory/ciParsers/azurePipelines.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/circleCi.d.ts +27 -0
- package/types/lib/inventory/ciParsers/circleCi.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/common.d.ts +11 -0
- package/types/lib/inventory/ciParsers/common.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/githubActions.d.ts +38 -0
- package/types/lib/inventory/ciParsers/githubActions.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/gitlabCi.d.ts +26 -0
- package/types/lib/inventory/ciParsers/gitlabCi.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/jenkins.d.ts +26 -0
- package/types/lib/inventory/ciParsers/jenkins.d.ts.map +1 -0
- package/types/lib/inventory/citations.d.ts +90 -0
- package/types/lib/inventory/citations.d.ts.map +1 -0
- package/types/lib/inventory/communityAiConfigParser.d.ts +14 -0
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -0
- package/types/lib/inventory/containerRisk.d.ts +20 -0
- package/types/lib/inventory/containerRisk.d.ts.map +1 -0
- package/types/lib/inventory/cryptoAlgorithmFamily.d.ts +37 -0
- package/types/lib/inventory/cryptoAlgorithmFamily.d.ts.map +1 -0
- package/types/lib/inventory/deps.d.ts +140 -0
- package/types/lib/inventory/deps.d.ts.map +1 -0
- package/types/lib/inventory/depsUtils.d.ts +74 -0
- package/types/lib/inventory/depsUtils.d.ts.map +1 -0
- package/types/lib/inventory/display.d.ts +227 -0
- package/types/lib/inventory/display.d.ts.map +1 -0
- package/types/lib/inventory/dosai.d.ts +130 -0
- package/types/lib/inventory/dosai.d.ts.map +1 -0
- package/types/lib/inventory/dosaiParsers.d.ts +59 -0
- package/types/lib/inventory/dosaiParsers.d.ts.map +1 -0
- package/types/lib/inventory/dynamic.d.ts +25 -0
- package/types/lib/inventory/dynamic.d.ts.map +1 -0
- package/types/lib/inventory/envcontext.d.ts +377 -0
- package/types/lib/inventory/envcontext.d.ts.map +1 -0
- package/types/lib/inventory/evidenceUtils.d.ts +74 -0
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -0
- package/types/lib/inventory/fetchBatch.d.ts +127 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -0
- package/types/lib/inventory/fetchRate.d.ts +118 -0
- package/types/lib/inventory/fetchRate.d.ts.map +1 -0
- package/types/lib/inventory/forgeEnricher.d.ts +31 -0
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -0
- package/types/lib/inventory/formulationParsers.d.ts +50 -0
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -0
- package/types/lib/inventory/gtfobins.d.ts +27 -0
- package/types/lib/inventory/gtfobins.d.ts.map +1 -0
- package/types/lib/inventory/hbom.d.ts +58 -0
- package/types/lib/inventory/hbom.d.ts.map +1 -0
- package/types/lib/inventory/hbomAnalysis.d.ts +56 -0
- package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -0
- package/types/lib/inventory/hbomLoader.d.ts +7 -0
- package/types/lib/inventory/hbomLoader.d.ts.map +1 -0
- package/types/lib/inventory/hostTopology.d.ts +47 -0
- package/types/lib/inventory/hostTopology.d.ts.map +1 -0
- package/types/lib/inventory/inventoryStats.d.ts +42 -0
- package/types/lib/inventory/inventoryStats.d.ts.map +1 -0
- package/types/lib/inventory/jsonLike.d.ts +23 -0
- package/types/lib/inventory/jsonLike.d.ts.map +1 -0
- package/types/lib/inventory/licenseEnhancer.d.ts +70 -0
- package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -0
- package/types/lib/inventory/lolbas.d.ts +16 -0
- package/types/lib/inventory/lolbas.d.ts.map +1 -0
- package/types/lib/inventory/mcp.d.ts +29 -0
- package/types/lib/inventory/mcp.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts +20 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -0
- package/types/lib/inventory/mcpDiscovery.d.ts +33 -0
- package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -0
- package/types/lib/inventory/mcpPinning.d.ts +11 -0
- package/types/lib/inventory/mcpPinning.d.ts.map +1 -0
- package/types/lib/inventory/osPackageResolver.d.ts +49 -0
- package/types/lib/inventory/osPackageResolver.d.ts.map +1 -0
- package/types/lib/inventory/osinfo.d.ts +201 -0
- package/types/lib/inventory/osinfo.d.ts.map +1 -0
- package/types/lib/inventory/osqueryTransform.d.ts +84 -0
- package/types/lib/inventory/osqueryTransform.d.ts.map +1 -0
- package/types/lib/inventory/plugins.d.ts +58 -0
- package/types/lib/inventory/plugins.d.ts.map +1 -0
- package/types/lib/inventory/protobom.d.ts +71 -0
- package/types/lib/inventory/protobom.d.ts.map +1 -0
- package/types/lib/inventory/protobomLoader.d.ts +17 -0
- package/types/lib/inventory/protobomLoader.d.ts.map +1 -0
- package/types/lib/inventory/provenanceUtils.d.ts +98 -0
- package/types/lib/inventory/provenanceUtils.d.ts.map +1 -0
- package/types/lib/inventory/purl.d.ts +313 -0
- package/types/lib/inventory/purl.d.ts.map +1 -0
- package/types/lib/inventory/rustFormulationParser.d.ts +17 -0
- package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -0
- package/types/lib/inventory/sbomDocument.d.ts +59 -0
- package/types/lib/inventory/sbomDocument.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +163 -0
- package/types/lib/inventory/source.d.ts.map +1 -0
- package/types/lib/inventory/spdx.d.ts +66 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -0
- package/types/lib/inventory/spdxExpression.d.ts +58 -0
- package/types/lib/inventory/spdxExpression.d.ts.map +1 -0
- package/types/lib/inventory/spdxUtils.d.ts +12 -0
- package/types/lib/inventory/spdxUtils.d.ts.map +1 -0
- package/types/lib/inventory/table.d.ts +22 -0
- package/types/lib/inventory/table.d.ts.map +1 -0
- package/types/lib/inventory/traceRunner.d.ts +80 -0
- package/types/lib/inventory/traceRunner.d.ts.map +1 -0
- package/types/lib/inventory/unicodeScan.d.ts +46 -0
- package/types/lib/inventory/unicodeScan.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts +147 -0
- package/types/lib/managers/binary.d.ts.map +1 -0
- package/types/lib/managers/containerutils.d.ts +30 -0
- package/types/lib/managers/containerutils.d.ts.map +1 -0
- package/types/lib/managers/docker.d.ts +157 -0
- package/types/lib/managers/docker.d.ts.map +1 -0
- package/types/lib/managers/dockerConnection.d.ts +42 -0
- package/types/lib/managers/dockerConnection.d.ts.map +1 -0
- package/types/lib/managers/oci.d.ts +21 -0
- package/types/lib/managers/oci.d.ts.map +1 -0
- package/types/lib/managers/piptree.d.ts +13 -0
- package/types/lib/managers/piptree.d.ts.map +1 -0
- package/types/lib/parsers/cmakeCache.d.ts +70 -0
- package/types/lib/parsers/cmakeCache.d.ts.map +1 -0
- package/types/lib/parsers/gguf.d.ts +39 -0
- package/types/lib/parsers/gguf.d.ts.map +1 -0
- package/types/lib/parsers/gitAiNotes.d.ts +15 -0
- package/types/lib/parsers/gitAiNotes.d.ts.map +1 -0
- package/types/lib/parsers/gitmodules.d.ts +88 -0
- package/types/lib/parsers/gitmodules.d.ts.map +1 -0
- package/types/lib/parsers/htmlExtract.d.ts +34 -0
- package/types/lib/parsers/htmlExtract.d.ts.map +1 -0
- package/types/lib/parsers/huggingfaceManifest.d.ts +153 -0
- package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts +155 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -0
- package/types/lib/parsers/iri.d.ts +68 -0
- package/types/lib/parsers/iri.d.ts.map +1 -0
- package/types/lib/parsers/npmrc.d.ts +33 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -0
- package/types/lib/parsers/ollama.d.ts +22 -0
- package/types/lib/parsers/ollama.d.ts.map +1 -0
- package/types/lib/parsers/xml.d.ts +47 -0
- package/types/lib/parsers/xml.d.ts.map +1 -0
- package/types/lib/server/server.d.ts +51 -0
- package/types/lib/server/server.d.ts.map +1 -0
- package/types/lib/stages/postgen/annotator.d.ts +36 -0
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -0
- package/types/lib/stages/postgen/auditBom.d.ts +92 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +59 -0
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -0
- package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
- package/types/lib/stages/postgen/sortBom.d.ts +34 -0
- package/types/lib/stages/postgen/sortBom.d.ts.map +1 -0
- package/types/lib/stages/postgen/spdxConverter.d.ts +11 -0
- package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
- package/types/lib/stages/postgen/specVersionCompat.d.ts +71 -0
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -0
- package/types/lib/stages/pregen/envAudit.d.ts +14 -0
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
- package/types/lib/stages/pregen/pregen.d.ts +59 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/arborist/index.d.ts +47 -0
- package/types/lib/third-party/arborist/lib/arborist/index.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/arborist/load-actual.d.ts +4 -0
- package/types/lib/third-party/arborist/lib/arborist/load-actual.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/arborist/load-virtual.d.ts +4 -0
- package/types/lib/third-party/arborist/lib/arborist/load-virtual.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/bin-links.d.ts +18 -0
- package/types/lib/third-party/arborist/lib/bin-links.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/calc-dep-flags.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/calc-dep-flags.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/case-insensitive-map.d.ts +9 -0
- package/types/lib/third-party/arborist/lib/case-insensitive-map.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/consistent-resolve.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/consistent-resolve.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/debug.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/debug.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/dep-valid.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/dep-valid.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/edge.d.ts +46 -0
- package/types/lib/third-party/arborist/lib/edge.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/from-path.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/from-path.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/gather-dep-set.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/gather-dep-set.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/index.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/index.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/inventory.d.ts +14 -0
- package/types/lib/third-party/arborist/lib/inventory.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/link.d.ts +18 -0
- package/types/lib/third-party/arborist/lib/link.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/node.d.ts +112 -0
- package/types/lib/third-party/arborist/lib/node.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/npm-extension.d.ts +18 -0
- package/types/lib/third-party/arborist/lib/npm-extension.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/override-resolves.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/override-resolves.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/override-set.d.ts +27 -0
- package/types/lib/third-party/arborist/lib/override-set.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/package-extensions.d.ts +36 -0
- package/types/lib/third-party/arborist/lib/package-extensions.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/printable.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/printable.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/proc-log.d.ts +20 -0
- package/types/lib/third-party/arborist/lib/proc-log.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/realpath.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/realpath.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/relpath.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/relpath.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/shrinkwrap.d.ts +54 -0
- package/types/lib/third-party/arborist/lib/shrinkwrap.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/spec-from-lock.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/spec-from-lock.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/tree-check.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/tree-check.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/validate-npm-package-name.d.ts +13 -0
- package/types/lib/third-party/arborist/lib/validate-npm-package-name.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/version-from-tgz.d.ts +6 -0
- package/types/lib/third-party/arborist/lib/version-from-tgz.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/yarn-lock.d.ts +35 -0
- package/types/lib/third-party/arborist/lib/yarn-lock.d.ts.map +1 -0
- package/types/lib/validator/bomValidator.d.ts +40 -0
- package/types/lib/validator/bomValidator.d.ts.map +1 -0
- package/types/lib/validator/complianceEngine.d.ts +72 -0
- package/types/lib/validator/complianceEngine.d.ts.map +1 -0
- package/types/lib/validator/complianceRules.d.ts +61 -0
- package/types/lib/validator/complianceRules.d.ts.map +1 -0
- package/types/lib/validator/index.d.ts +101 -0
- package/types/lib/validator/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/annotations.d.ts +37 -0
- package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
- package/types/lib/validator/reporters/console.d.ts +34 -0
- package/types/lib/validator/reporters/console.d.ts.map +1 -0
- package/types/lib/validator/reporters/index.d.ts +30 -0
- package/types/lib/validator/reporters/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/json.d.ts +11 -0
- package/types/lib/validator/reporters/json.d.ts.map +1 -0
- package/types/lib/validator/reporters/sarif.d.ts +22 -0
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
- package/mod.d.ts +0 -7
- package/mod.js +0 -22
|
@@ -0,0 +1,1629 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, dirname, join } from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
|
|
6
|
+
import { build, Purl } from "@cdxgen/cdx-purl";
|
|
7
|
+
|
|
8
|
+
// utils.js does not import rubyutils.js, so importing from it here is safe and
|
|
9
|
+
// does not introduce a cyclic dependency.
|
|
10
|
+
import {
|
|
11
|
+
cdxgenAgent,
|
|
12
|
+
DEBUG_MODE,
|
|
13
|
+
readEnvironmentVariable,
|
|
14
|
+
} from "../core/activity.js";
|
|
15
|
+
import { shouldFetchLicense } from "../core/env.js";
|
|
16
|
+
import { getAllFiles, safeExistsSync, safeSpawnSync } from "../core/fs.js";
|
|
17
|
+
import { dirNameStr, isWin } from "../core/paths.js";
|
|
18
|
+
import {
|
|
19
|
+
prefetchEnabled,
|
|
20
|
+
prefetchedResponse,
|
|
21
|
+
prefetchJson,
|
|
22
|
+
} from "../inventory/fetchBatch.js";
|
|
23
|
+
|
|
24
|
+
// FIXME. This has to get removed, once we improve the module detection one-liner.
|
|
25
|
+
// If you're a Rubyist, please help us improve this code.
|
|
26
|
+
const RUBY_KNOWN_MODULES = JSON.parse(
|
|
27
|
+
readFileSync(join(dirNameStr, "data", "ruby-known-modules.json"), "utf-8"),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// `Gem::Platform#to_s` is `[cpu, os, os_version].compact.join("-")`, which
|
|
31
|
+
// Bundler writes as a `-` separated suffix to the version in the `specs:`
|
|
32
|
+
// section of Gemfile.lock. Examples of the suffix: `x86_64-linux`,
|
|
33
|
+
// `x86_64-linux-musl`, `aarch64-linux-gnu`, `arm64-darwin`, `x64-mingw-ucrt`,
|
|
34
|
+
// `x86-mswin32-60`, `universal-darwin-20`, `java`, `universal-java-11`.
|
|
35
|
+
// The cpu is an open token, so there is no exhaustive list to match against.
|
|
36
|
+
// The os component, however, comes from a fixed table in `Gem::Platform`.
|
|
37
|
+
const RUBY_PLATFORM_OSES = new Set([
|
|
38
|
+
"aix",
|
|
39
|
+
"cygwin",
|
|
40
|
+
"dalvik",
|
|
41
|
+
"darwin",
|
|
42
|
+
"dotnet",
|
|
43
|
+
"freebsd",
|
|
44
|
+
"java",
|
|
45
|
+
"jruby",
|
|
46
|
+
"linux",
|
|
47
|
+
"macruby",
|
|
48
|
+
"mingw",
|
|
49
|
+
"mingw32",
|
|
50
|
+
"mswin32",
|
|
51
|
+
"mswin64",
|
|
52
|
+
"netbsdelf",
|
|
53
|
+
"openbsd",
|
|
54
|
+
"ruby",
|
|
55
|
+
"solaris",
|
|
56
|
+
"wasi",
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Is the given string a `Gem::Platform` such as `x86_64-linux` or `java`?
|
|
61
|
+
* The cpu component is an open token in RubyGems, so we validate by looking
|
|
62
|
+
* for a known os name in any position.
|
|
63
|
+
*
|
|
64
|
+
* Note that `truffleruby` is deliberately absent from the os table.
|
|
65
|
+
* `Gem::Platform.new("truffleruby").to_s` is `"unknown"`: TruffleRuby reports a
|
|
66
|
+
* conventional local platform such as `x86_64-linux`, and reuses plain ruby
|
|
67
|
+
* gems via an allowlist rather than through a platform of its own. So
|
|
68
|
+
* `truffleruby` never appears as a platform suffix.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} value Candidate platform string
|
|
71
|
+
* @returns {boolean} true if the value looks like a gem platform
|
|
72
|
+
*/
|
|
73
|
+
export function isRubyPlatform(value) {
|
|
74
|
+
if (!value?.length) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
return value.split("-").some((segment) => RUBY_PLATFORM_OSES.has(segment));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Normalize a gem platform the way `Gem::Platform` does.
|
|
82
|
+
*
|
|
83
|
+
* The only alias that matters for a lockfile or a gemspec is `jruby`, which
|
|
84
|
+
* `Gem::Platform` maps to the os `java`, so `Gem::Platform.new("jruby").to_s`
|
|
85
|
+
* is `"java"`. Normalizing keeps a JRuby gem from being reported under two
|
|
86
|
+
* different purls depending on which spelling the source used.
|
|
87
|
+
*
|
|
88
|
+
* @param {string | undefined} platform Platform to normalize
|
|
89
|
+
* @returns {string | undefined} Normalized platform
|
|
90
|
+
*/
|
|
91
|
+
export function normalizeGemPlatform(platform) {
|
|
92
|
+
if (!platform?.length) {
|
|
93
|
+
return platform;
|
|
94
|
+
}
|
|
95
|
+
return platform === "jruby" ? "java" : platform;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Split a gem version string into its version and optional native platform.
|
|
100
|
+
*
|
|
101
|
+
* Bundler writes native gems as `name (version-platform)`, for example
|
|
102
|
+
* `google-protobuf (3.25.1-x86_64-linux)`. This mirrors how Bundler itself
|
|
103
|
+
* parses the lockfile: its `NAME_VERSION` regex captures the version as
|
|
104
|
+
* `([^-]*)` followed by an optional `-(.*)` platform, i.e. it splits at the
|
|
105
|
+
* *first* hyphen. That is unambiguous because `Gem::Version` rewrites any `-`
|
|
106
|
+
* to `.pre.` on construction, so a canonical version string, which is what
|
|
107
|
+
* Bundler writes to the lockfile, never contains a hyphen.
|
|
108
|
+
*
|
|
109
|
+
* @param {string | undefined} version Version that may include a platform suffix
|
|
110
|
+
* @returns {{version: (string | undefined), platform: (string | undefined)}} Version and platform
|
|
111
|
+
*/
|
|
112
|
+
export function splitRubyVersionPlatform(version) {
|
|
113
|
+
if (!version?.length) {
|
|
114
|
+
return { version, platform: undefined };
|
|
115
|
+
}
|
|
116
|
+
const hyphenIndex = version.indexOf("-");
|
|
117
|
+
// A leading hyphen, or nothing after it, is not a platform suffix
|
|
118
|
+
if (hyphenIndex < 1 || hyphenIndex === version.length - 1) {
|
|
119
|
+
return { version, platform: undefined };
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
version: version.slice(0, hyphenIndex),
|
|
123
|
+
platform: version.slice(hyphenIndex + 1),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Simplify the ruby version by removing platform suffixes
|
|
129
|
+
*
|
|
130
|
+
* @param {string} version Version to simplify
|
|
131
|
+
* @returns {string} Simplified version
|
|
132
|
+
*/
|
|
133
|
+
export function simplifyRubyVersion(version) {
|
|
134
|
+
return splitRubyVersionPlatform(version).version;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Construct a gem purl. Per the purl specification, the native platform is
|
|
139
|
+
* represented with the `platform` qualifier and not as part of the version.
|
|
140
|
+
* `ruby` is the implied default platform, so it is left out.
|
|
141
|
+
*
|
|
142
|
+
* @param {string} name Gem name
|
|
143
|
+
* @param {string | undefined} version Gem version without any platform suffix
|
|
144
|
+
* @param {string | undefined} platform Gem platform such as `x86_64-linux`
|
|
145
|
+
* @returns {string} purl string
|
|
146
|
+
*/
|
|
147
|
+
export function toGemPurl(name, version, platform) {
|
|
148
|
+
const gemPlatform = normalizeGemPlatform(platform);
|
|
149
|
+
return build({
|
|
150
|
+
type: "gem",
|
|
151
|
+
namespace: "" || null,
|
|
152
|
+
name: name,
|
|
153
|
+
version: version || null,
|
|
154
|
+
qualifiers:
|
|
155
|
+
gemPlatform && gemPlatform !== "ruby"
|
|
156
|
+
? { platform: gemPlatform }
|
|
157
|
+
: null || null,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Well known `Gem::Specification#metadata` keys, mapped to the CycloneDX
|
|
162
|
+
// external reference types. See
|
|
163
|
+
// https://guides.rubygems.org/specification-reference/#metadata
|
|
164
|
+
const GEM_METADATA_REFERENCE_TYPES = {
|
|
165
|
+
bug_tracker_uri: "issue-tracker",
|
|
166
|
+
changelog_uri: "release-notes",
|
|
167
|
+
documentation_uri: "documentation",
|
|
168
|
+
funding_uri: "other",
|
|
169
|
+
homepage_uri: "website",
|
|
170
|
+
mailing_list_uri: "other",
|
|
171
|
+
source_code_uri: "vcs",
|
|
172
|
+
wiki_uri: "documentation",
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Parse a ruby array literal as found in a gemspec, such as
|
|
177
|
+
* `["ext/nokogiri/extconf.rb".freeze]` or `%w[a b]`.
|
|
178
|
+
*
|
|
179
|
+
* @param {string} value Ruby array literal
|
|
180
|
+
* @returns {Array<string>} Parsed entries
|
|
181
|
+
*/
|
|
182
|
+
function parseGemspecArray(value) {
|
|
183
|
+
if (!value) {
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
return value
|
|
187
|
+
.replaceAll(".freeze", "")
|
|
188
|
+
.replace(/^%w?[[(]/, "")
|
|
189
|
+
.replace(/^\[/, "")
|
|
190
|
+
.replace(/[\])].*$/, "")
|
|
191
|
+
.split(",")
|
|
192
|
+
.map((s) => s.trim().replace(/["']/g, ""))
|
|
193
|
+
.filter((s) => s.length);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Parse a `Gem::Requirement` literal as found in a gemspec, such as
|
|
198
|
+
* `Gem::Requirement.new([">= 2.2".freeze, "< 4.0".freeze])` or
|
|
199
|
+
* `Gem::Requirement.new(">= 3.0".freeze)`.
|
|
200
|
+
*
|
|
201
|
+
* @param {string} value Requirement literal
|
|
202
|
+
* @returns {string | undefined} Comma separated requirement string
|
|
203
|
+
*/
|
|
204
|
+
function parseGemRequirement(value) {
|
|
205
|
+
if (!value) {
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
const inner = value.includes("Gem::Requirement.new")
|
|
209
|
+
? value.replace(/.*Gem::Requirement\.new\(/, "").replace(/\).*$/, "")
|
|
210
|
+
: value;
|
|
211
|
+
const parts = inner
|
|
212
|
+
.replaceAll(".freeze", "")
|
|
213
|
+
.replace(/^\[/, "")
|
|
214
|
+
.replace(/].*$/, "")
|
|
215
|
+
.split(",")
|
|
216
|
+
.map((s) => s.trim().replace(/["';]/g, ""))
|
|
217
|
+
.filter((s) => s.length && s !== "nil");
|
|
218
|
+
return parts.length ? parts.join(", ") : undefined;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Digest algorithms Bundler may record in the CHECKSUMS section, mapped to the
|
|
222
|
+
// CycloneDX hash algorithm names.
|
|
223
|
+
const GEM_CHECKSUM_ALGOS = {
|
|
224
|
+
sha1: "SHA-1",
|
|
225
|
+
sha256: "SHA-256",
|
|
226
|
+
sha512: "SHA-512",
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Parse a single line from the `CHECKSUMS` section of a Gemfile.lock. Bundler
|
|
231
|
+
* 2.5 onwards writes `name (version[-platform]) algo=digest[,algo=digest]`,
|
|
232
|
+
* where the `name (version[-platform])` token is identical to the one used in
|
|
233
|
+
* the `specs:` section. The digest is lowercase hex. An entry may carry no
|
|
234
|
+
* checksum at all when Bundler could not obtain one.
|
|
235
|
+
*
|
|
236
|
+
* @param {string} line Trimmed line from the CHECKSUMS section
|
|
237
|
+
* @returns {{lockName: string, hashes: Array<object>} | undefined} Lock name and CycloneDX hashes
|
|
238
|
+
*/
|
|
239
|
+
export function parseGemChecksumLine(line) {
|
|
240
|
+
const match = line?.match(/^(\S+) \(([^)]+)\)(?:\s+(\S+))?$/);
|
|
241
|
+
if (!match) {
|
|
242
|
+
return undefined;
|
|
243
|
+
}
|
|
244
|
+
const [, name, version, checksums] = match;
|
|
245
|
+
const hashes = [];
|
|
246
|
+
for (const achecksum of (checksums || "").split(",")) {
|
|
247
|
+
const [algo, digest] = achecksum.split("=");
|
|
248
|
+
const alg = GEM_CHECKSUM_ALGOS[algo?.toLowerCase()];
|
|
249
|
+
// Bundler always writes hex, but guard against anything else
|
|
250
|
+
if (alg && /^[0-9a-f]+$/.test(digest || "")) {
|
|
251
|
+
hashes.push({ alg, content: digest });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (!hashes.length) {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
return { lockName: `${name} (${version})`, hashes };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Default location of Bundler's compact index cache. This is the protocol
|
|
262
|
+
* Bundler itself uses to resolve, so any gem the developer has ever installed
|
|
263
|
+
* has a cached `info/<gem>` file here.
|
|
264
|
+
*
|
|
265
|
+
* @returns {string} Path to the compact index cache directory
|
|
266
|
+
*/
|
|
267
|
+
export function getCompactIndexCacheDir() {
|
|
268
|
+
return (
|
|
269
|
+
readEnvironmentVariable("CDXGEN_COMPACT_INDEX_CACHE_DIR") ||
|
|
270
|
+
join(
|
|
271
|
+
readEnvironmentVariable("BUNDLE_USER_CACHE") ||
|
|
272
|
+
join(homedir(), ".bundle", "cache"),
|
|
273
|
+
"compact_index",
|
|
274
|
+
)
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Parse the contents of a Bundler compact index `info/<gem>` file. Each line
|
|
280
|
+
* describes one release:
|
|
281
|
+
*
|
|
282
|
+
* `VERSION[-PLATFORM] <deps>|checksum:<sha256>,ruby:<req>,rubygems:<req>,...`
|
|
283
|
+
*
|
|
284
|
+
* This is the cheapest source of gem metadata available: a single local file
|
|
285
|
+
* carries the sha256, the runtime dependencies and the required ruby and
|
|
286
|
+
* rubygems versions for every release of a gem.
|
|
287
|
+
*
|
|
288
|
+
* @param {string} infoData Contents of an info file
|
|
289
|
+
* @returns {object} Map of `version[-platform]` to release metadata
|
|
290
|
+
*/
|
|
291
|
+
export function parseCompactIndexInfo(infoData) {
|
|
292
|
+
const releases = {};
|
|
293
|
+
if (!infoData) {
|
|
294
|
+
return releases;
|
|
295
|
+
}
|
|
296
|
+
for (const aline of infoData.split("\n")) {
|
|
297
|
+
const line = aline.trim();
|
|
298
|
+
if (!line.length || line === "---") {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const pipeIndex = line.lastIndexOf("|");
|
|
302
|
+
const spec = (pipeIndex > -1 ? line.slice(0, pipeIndex) : line).trim();
|
|
303
|
+
const fullVersion = spec.split(" ")[0];
|
|
304
|
+
if (!fullVersion?.length) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
const release = {};
|
|
308
|
+
if (pipeIndex > -1) {
|
|
309
|
+
for (const requirement of line.slice(pipeIndex + 1).split(",")) {
|
|
310
|
+
const separator = requirement.indexOf(":");
|
|
311
|
+
if (separator < 1) {
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
release[requirement.slice(0, separator)] = requirement
|
|
315
|
+
.slice(separator + 1)
|
|
316
|
+
.trim();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
releases[fullVersion] = release;
|
|
320
|
+
}
|
|
321
|
+
return releases;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Parse a `.bundle/config` file. Bundler writes a small YAML document of
|
|
326
|
+
* `BUNDLE_<SETTING>: "value"` pairs. Two settings matter for an SBOM:
|
|
327
|
+
* `BUNDLE_PATH`, which tells us where the gems actually live, and
|
|
328
|
+
* `BUNDLE_WITHOUT`, which tells us that some groups were never installed and
|
|
329
|
+
* that the SBOM is therefore incomplete by construction.
|
|
330
|
+
*
|
|
331
|
+
* @param {string} configFile Path to a .bundle/config file
|
|
332
|
+
* @returns {object} Map of setting name to value
|
|
333
|
+
*/
|
|
334
|
+
export function parseBundleConfig(configFile) {
|
|
335
|
+
const settings = {};
|
|
336
|
+
if (!configFile || !safeExistsSync(configFile)) {
|
|
337
|
+
return settings;
|
|
338
|
+
}
|
|
339
|
+
let configData;
|
|
340
|
+
try {
|
|
341
|
+
configData = readFileSync(configFile, { encoding: "utf-8" });
|
|
342
|
+
} catch (_err) {
|
|
343
|
+
return settings;
|
|
344
|
+
}
|
|
345
|
+
for (const aline of configData.split("\n")) {
|
|
346
|
+
const line = aline.trim();
|
|
347
|
+
if (!line.length || line.startsWith("#") || line.startsWith("---")) {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
const separator = line.indexOf(":");
|
|
351
|
+
if (separator < 1) {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
const key = line.slice(0, separator).trim();
|
|
355
|
+
if (!key.startsWith("BUNDLE_")) {
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
settings[key] = line
|
|
359
|
+
.slice(separator + 1)
|
|
360
|
+
.trim()
|
|
361
|
+
.replace(/^["']|["']$/g, "");
|
|
362
|
+
}
|
|
363
|
+
return settings;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Enrich gem components from the caches present on the machine, without making
|
|
368
|
+
* any network calls. Two sources are consulted:
|
|
369
|
+
*
|
|
370
|
+
* 1. Bundler's compact index cache, for the sha256 and the required ruby and
|
|
371
|
+
* rubygems versions of the exact release, including native variants.
|
|
372
|
+
* 2. The installed gemspecs under `GEM_HOME/specifications`, for licenses,
|
|
373
|
+
* description, project URIs and the native extension list.
|
|
374
|
+
*
|
|
375
|
+
* Because this only reads files it also works in dry-run mode, where the
|
|
376
|
+
* registry lookups performed by `getRubyGemsMetadata` are blocked.
|
|
377
|
+
*
|
|
378
|
+
* @param {Array} pkgList List of gem components to enrich in place
|
|
379
|
+
* @param {object} options Options
|
|
380
|
+
* @param {string} options.gemHome GEM_HOME to read installed gemspecs from
|
|
381
|
+
* @param {string} options.compactIndexCacheDir Bundler compact index cache directory
|
|
382
|
+
* @returns {Promise<Array>} The enriched package list
|
|
383
|
+
*/
|
|
384
|
+
export async function enrichGemsFromLocalCache(pkgList, options = {}) {
|
|
385
|
+
if (!pkgList?.length) {
|
|
386
|
+
return pkgList;
|
|
387
|
+
}
|
|
388
|
+
const compactIndexCacheDir =
|
|
389
|
+
options.compactIndexCacheDir || getCompactIndexCacheDir();
|
|
390
|
+
const gemHome = options.gemHome;
|
|
391
|
+
// A gem may be cached under several remotes. The cache lays out one
|
|
392
|
+
// `<host>.<port>.<hash>/info/<gem>` directory per remote, so a shallow listing
|
|
393
|
+
// of the remotes is enough to build the candidate paths for a given gem. That
|
|
394
|
+
// is far cheaper than walking every cached info file.
|
|
395
|
+
let infoDirs = [];
|
|
396
|
+
if (safeExistsSync(compactIndexCacheDir)) {
|
|
397
|
+
try {
|
|
398
|
+
infoDirs = readdirSync(compactIndexCacheDir, { withFileTypes: true })
|
|
399
|
+
.filter((anentry) => anentry.isDirectory())
|
|
400
|
+
.map((anentry) => join(compactIndexCacheDir, anentry.name, "info"))
|
|
401
|
+
.filter((adir) => safeExistsSync(adir));
|
|
402
|
+
} catch (_err) {
|
|
403
|
+
// pass
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
// Installed gemspecs are named `<name>-<version>[-<platform>].gemspec`
|
|
407
|
+
const gemspecsByFullName = {};
|
|
408
|
+
if (gemHome && safeExistsSync(gemHome)) {
|
|
409
|
+
for (const gemspecFile of getAllFiles(
|
|
410
|
+
gemHome,
|
|
411
|
+
"**/specifications/**/*.gemspec",
|
|
412
|
+
{ noIgnore: true },
|
|
413
|
+
)) {
|
|
414
|
+
gemspecsByFullName[basename(gemspecFile).replace(".gemspec", "")] =
|
|
415
|
+
gemspecFile;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (!infoDirs.length && !Object.keys(gemspecsByFullName).length) {
|
|
419
|
+
return pkgList;
|
|
420
|
+
}
|
|
421
|
+
const releasesCache = {};
|
|
422
|
+
for (const p of pkgList) {
|
|
423
|
+
if (!p.name || !p.version) {
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
const platform = p.purl
|
|
427
|
+
? Purl.parse(p.purl).qualifiers?.platform
|
|
428
|
+
: undefined;
|
|
429
|
+
const fullVersion = platform ? `${p.version}-${platform}` : p.version;
|
|
430
|
+
const fullName = `${p.name}-${fullVersion}`;
|
|
431
|
+
// 1. Compact index cache
|
|
432
|
+
if (!releasesCache[p.name]) {
|
|
433
|
+
releasesCache[p.name] = {};
|
|
434
|
+
for (const infoDir of infoDirs) {
|
|
435
|
+
const infoFile = join(infoDir, p.name);
|
|
436
|
+
if (!safeExistsSync(infoFile)) {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
try {
|
|
440
|
+
Object.assign(
|
|
441
|
+
releasesCache[p.name],
|
|
442
|
+
parseCompactIndexInfo(
|
|
443
|
+
readFileSync(infoFile, { encoding: "utf-8" }),
|
|
444
|
+
),
|
|
445
|
+
);
|
|
446
|
+
} catch (_err) {
|
|
447
|
+
// pass
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const release = releasesCache[p.name]?.[fullVersion];
|
|
452
|
+
if (release) {
|
|
453
|
+
if (release.checksum && !p.hashes?.length && !p._integrity) {
|
|
454
|
+
p._integrity = `sha256-${release.checksum}`;
|
|
455
|
+
}
|
|
456
|
+
p.properties = p.properties || [];
|
|
457
|
+
for (const [key, propName] of [
|
|
458
|
+
["ruby", "cdx:gem:rubyVersionSpecifiers"],
|
|
459
|
+
["rubygems", "cdx:gem:rubygemsVersionSpecifiers"],
|
|
460
|
+
]) {
|
|
461
|
+
// Compact index joins alternative requirements with `&`
|
|
462
|
+
const value = release[key]?.replaceAll("&", ", ");
|
|
463
|
+
if (
|
|
464
|
+
value?.length &&
|
|
465
|
+
value !== ">= 0" &&
|
|
466
|
+
!p.properties.some((prop) => prop.name === propName)
|
|
467
|
+
) {
|
|
468
|
+
p.properties.push({ name: propName, value });
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
// 2. Installed gemspec
|
|
473
|
+
const gemspecFile = gemspecsByFullName[fullName];
|
|
474
|
+
if (gemspecFile) {
|
|
475
|
+
let installedPkgs = [];
|
|
476
|
+
try {
|
|
477
|
+
installedPkgs = await parseGemspecData(
|
|
478
|
+
readFileSync(gemspecFile, { encoding: "utf-8" }),
|
|
479
|
+
gemspecFile,
|
|
480
|
+
);
|
|
481
|
+
} catch (_err) {
|
|
482
|
+
// pass
|
|
483
|
+
}
|
|
484
|
+
const installed = installedPkgs?.[0];
|
|
485
|
+
if (installed) {
|
|
486
|
+
if (!p.description?.length && installed.description?.length) {
|
|
487
|
+
p.description = installed.description;
|
|
488
|
+
}
|
|
489
|
+
if (!p.license && installed.licenses?.length) {
|
|
490
|
+
// parseGemspecData returns the CycloneDX license shape, but the
|
|
491
|
+
// license of an unfinished component is a plain list of names
|
|
492
|
+
const licenseNames = installed.licenses
|
|
493
|
+
.map((alicense) => alicense?.license?.name || alicense?.license?.id)
|
|
494
|
+
.filter((aname) => aname?.length);
|
|
495
|
+
if (licenseNames.length) {
|
|
496
|
+
p.license = licenseNames;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (installed.externalReferences?.length) {
|
|
500
|
+
p.externalReferences = p.externalReferences || [];
|
|
501
|
+
for (const aref of installed.externalReferences) {
|
|
502
|
+
if (
|
|
503
|
+
!p.externalReferences.some(
|
|
504
|
+
(existing) =>
|
|
505
|
+
existing.type === aref.type && existing.url === aref.url,
|
|
506
|
+
)
|
|
507
|
+
) {
|
|
508
|
+
p.externalReferences.push(aref);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
p.properties = p.properties || [];
|
|
513
|
+
for (const prop of installed.properties || []) {
|
|
514
|
+
if (
|
|
515
|
+
prop.name.startsWith("cdx:gem:") &&
|
|
516
|
+
!p.properties.some((existing) => existing.name === prop.name)
|
|
517
|
+
) {
|
|
518
|
+
p.properties.push(prop);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return pkgList;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Apply a RubyGems API metadata payload to a component. Shared by the single
|
|
529
|
+
* version lookup and the bulk versions lookup.
|
|
530
|
+
*
|
|
531
|
+
* @param {object} p Component to enrich in place
|
|
532
|
+
* @param {object} body Metadata payload for one gem version
|
|
533
|
+
*/
|
|
534
|
+
function applyGemMetadata(p, body) {
|
|
535
|
+
if (!body) {
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
p.properties = p.properties || [];
|
|
539
|
+
const hasProperty = (name) => p.properties.some((prop) => prop.name === name);
|
|
540
|
+
const addProperty = (name, value) => {
|
|
541
|
+
if (value !== undefined && value !== null && !hasProperty(name)) {
|
|
542
|
+
p.properties.push({ name, value: `${value}` });
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
p.description = body.description || body.summary || "";
|
|
546
|
+
if (body.licenses) {
|
|
547
|
+
p.license = body.licenses;
|
|
548
|
+
}
|
|
549
|
+
if (body.metadata) {
|
|
550
|
+
if (body.metadata.source_code_uri) {
|
|
551
|
+
p.repository = { url: body.metadata.source_code_uri };
|
|
552
|
+
if (
|
|
553
|
+
body.homepage_uri &&
|
|
554
|
+
body.homepage_uri !== body.metadata.source_code_uri
|
|
555
|
+
) {
|
|
556
|
+
p.homepage = { url: body.homepage_uri };
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
if (body.metadata.bug_tracker_uri) {
|
|
560
|
+
p.bugs = { url: body.metadata.bug_tracker_uri };
|
|
561
|
+
}
|
|
562
|
+
// Whether the gem requires MFA to publish is a supply chain signal
|
|
563
|
+
if (body.metadata.rubygems_mfa_required) {
|
|
564
|
+
addProperty("cdx:gem:mfaRequired", body.metadata.rubygems_mfa_required);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
// The well known project URIs are reported at the top level by the v1 gems
|
|
568
|
+
// and v2 versions endpoints, and inside `metadata` by the versions listing.
|
|
569
|
+
p.externalReferences = p.externalReferences || [];
|
|
570
|
+
for (const [key, refType] of Object.entries(GEM_METADATA_REFERENCE_TYPES)) {
|
|
571
|
+
const url = body[key] || body.metadata?.[key];
|
|
572
|
+
if (
|
|
573
|
+
url?.length &&
|
|
574
|
+
!p.externalReferences.some(
|
|
575
|
+
(aref) => aref.type === refType && aref.url === url,
|
|
576
|
+
)
|
|
577
|
+
) {
|
|
578
|
+
p.externalReferences.push({ type: refType, url, comment: key });
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
// The .gem tarball is the distribution artifact for the component
|
|
582
|
+
if (body.gem_uri?.length) {
|
|
583
|
+
if (
|
|
584
|
+
!p.externalReferences.some(
|
|
585
|
+
(aref) => aref.type === "distribution" && aref.url === body.gem_uri,
|
|
586
|
+
)
|
|
587
|
+
) {
|
|
588
|
+
p.externalReferences.push({ type: "distribution", url: body.gem_uri });
|
|
589
|
+
}
|
|
590
|
+
addProperty("cdx:gem:gemUri", body.gem_uri);
|
|
591
|
+
}
|
|
592
|
+
if (!p.externalReferences.length) {
|
|
593
|
+
p.externalReferences = undefined;
|
|
594
|
+
}
|
|
595
|
+
if (body.sha) {
|
|
596
|
+
p._integrity = `sha256-${body.sha}`;
|
|
597
|
+
}
|
|
598
|
+
if (body.authors) {
|
|
599
|
+
p.author = body.authors;
|
|
600
|
+
}
|
|
601
|
+
// `ruby_version` is the `required_ruby_version` requirement. Note that the
|
|
602
|
+
// sibling `rubygems_version` field is the version of RubyGems that packaged
|
|
603
|
+
// the gem, not a requirement, so it is deliberately not recorded as one.
|
|
604
|
+
if (body.ruby_version?.length && body.ruby_version !== ">= 0") {
|
|
605
|
+
addProperty("cdx:gem:rubyVersionSpecifiers", body.ruby_version);
|
|
606
|
+
}
|
|
607
|
+
if (body.yanked) {
|
|
608
|
+
addProperty("cdx:gem:yanked", body.yanked);
|
|
609
|
+
}
|
|
610
|
+
if (body.prerelease) {
|
|
611
|
+
addProperty("cdx:gem:prerelease", body.prerelease);
|
|
612
|
+
}
|
|
613
|
+
// Use the latest version if none specified
|
|
614
|
+
if (!p.version) {
|
|
615
|
+
p.version = body.number;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* The platform of a gem component, taken from the purl qualifier. `ruby` is the
|
|
621
|
+
* implied default that the RubyGems API reports for a pure ruby gem.
|
|
622
|
+
*
|
|
623
|
+
* @param {object} p Component
|
|
624
|
+
* @returns {string} Platform such as `x86_64-linux`, or `ruby`
|
|
625
|
+
*/
|
|
626
|
+
function gemComponentPlatform(p) {
|
|
627
|
+
if (!p?.purl) {
|
|
628
|
+
return "ruby";
|
|
629
|
+
}
|
|
630
|
+
try {
|
|
631
|
+
return Purl.parse(p.purl).qualifiers?.platform || "ruby";
|
|
632
|
+
} catch (_err) {
|
|
633
|
+
return "ruby";
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* The URL for a single gem's direct lookup, or `undefined` when the component
|
|
639
|
+
* lacks the minimum fields. Shared by the batch prefetch and the per-package
|
|
640
|
+
* fallback so the two cannot construct different URLs.
|
|
641
|
+
*
|
|
642
|
+
* @param {object} p Component with `name`, optional `version`, and purl.
|
|
643
|
+
* @param {string} v2Url Base url of the v2 rubygems endpoint.
|
|
644
|
+
* @param {string} v1Url Base url of the v1 gems endpoint.
|
|
645
|
+
* @returns {string|undefined}
|
|
646
|
+
*/
|
|
647
|
+
function gemDirectUrl(p, v2Url, v1Url) {
|
|
648
|
+
if (!p?.name) {
|
|
649
|
+
return undefined;
|
|
650
|
+
}
|
|
651
|
+
const platform = gemComponentPlatform(p);
|
|
652
|
+
const platformQuery =
|
|
653
|
+
platform && platform !== "ruby"
|
|
654
|
+
? `?platform=${encodeURIComponent(platform)}`
|
|
655
|
+
: "";
|
|
656
|
+
return p.version
|
|
657
|
+
? `${v2Url}${p.name}/versions/${simplifyRubyVersion(p.version)}.json${platformQuery}`
|
|
658
|
+
: `${v1Url}${p.name}.json`;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Method to query rubygems api for gems details
|
|
663
|
+
*
|
|
664
|
+
* A gem that ships several native builds appears in the BOM once per platform.
|
|
665
|
+
* Rather than making one request per variant, the versions listing endpoint is
|
|
666
|
+
* used to fetch every version and platform of such a gem in a single request.
|
|
667
|
+
*
|
|
668
|
+
* @param {Array} pkgList List of packages with metadata
|
|
669
|
+
*/
|
|
670
|
+
export async function getRubyGemsMetadata(pkgList) {
|
|
671
|
+
const RUBYGEMS_V2_URL =
|
|
672
|
+
readEnvironmentVariable("RUBYGEMS_V2_URL") ||
|
|
673
|
+
"https://rubygems.org/api/v2/rubygems/";
|
|
674
|
+
const RUBYGEMS_V1_URL =
|
|
675
|
+
readEnvironmentVariable("RUBYGEMS_V1_URL") ||
|
|
676
|
+
"https://rubygems.org/api/v1/gems/";
|
|
677
|
+
const RUBYGEMS_V1_VERSIONS_URL =
|
|
678
|
+
readEnvironmentVariable("RUBYGEMS_V1_VERSIONS_URL") ||
|
|
679
|
+
"https://rubygems.org/api/v1/versions/";
|
|
680
|
+
const rdepList = [];
|
|
681
|
+
const apiOptions = {
|
|
682
|
+
responseType: "json",
|
|
683
|
+
};
|
|
684
|
+
if (readEnvironmentVariable("GEM_HOST_API_KEY")) {
|
|
685
|
+
apiOptions.headers = {
|
|
686
|
+
Authorization: readEnvironmentVariable("GEM_HOST_API_KEY"),
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
// Group the components by gem name so that the multi platform gems can be
|
|
690
|
+
// resolved with a single request each.
|
|
691
|
+
const pkgsByName = {};
|
|
692
|
+
for (const p of pkgList) {
|
|
693
|
+
if (!pkgsByName[p.name]) {
|
|
694
|
+
pkgsByName[p.name] = [];
|
|
695
|
+
}
|
|
696
|
+
pkgsByName[p.name].push(p);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Collect every URL the loop below is going to need — the per-name versions
|
|
700
|
+
// listing for multi-variant gems, and the per-package direct lookup for
|
|
701
|
+
// everything else — and hand them to one batched round. The derivation in
|
|
702
|
+
// applyGemMetadata is unchanged; only where the body comes from differs.
|
|
703
|
+
const batchRequests = [];
|
|
704
|
+
if (prefetchEnabled()) {
|
|
705
|
+
const seenUrls = new Set();
|
|
706
|
+
const gemAuthRealm = readEnvironmentVariable("GEM_HOST_API_KEY")
|
|
707
|
+
? "gem-auth"
|
|
708
|
+
: undefined;
|
|
709
|
+
const addBatchUrl = (url) => {
|
|
710
|
+
if (url && !seenUrls.has(url)) {
|
|
711
|
+
seenUrls.add(url);
|
|
712
|
+
batchRequests.push({
|
|
713
|
+
url,
|
|
714
|
+
authRealm: gemAuthRealm,
|
|
715
|
+
headers: apiOptions.headers,
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
for (const [name, pkgs] of Object.entries(pkgsByName)) {
|
|
720
|
+
const versionedPkgs = pkgs.filter((p) => p.version);
|
|
721
|
+
if (versionedPkgs.length > 1) {
|
|
722
|
+
addBatchUrl(`${RUBYGEMS_V1_VERSIONS_URL}${name}.json`);
|
|
723
|
+
}
|
|
724
|
+
for (const p of pkgs) {
|
|
725
|
+
addBatchUrl(gemDirectUrl(p, RUBYGEMS_V2_URL, RUBYGEMS_V1_URL));
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
const prefetched = await prefetchJson(batchRequests);
|
|
730
|
+
|
|
731
|
+
for (const [name, pkgs] of Object.entries(pkgsByName)) {
|
|
732
|
+
// The versions listing only helps when there are several versioned
|
|
733
|
+
// components to satisfy from the one response.
|
|
734
|
+
const versionedPkgs = pkgs.filter((p) => p.version);
|
|
735
|
+
if (versionedPkgs.length > 1) {
|
|
736
|
+
try {
|
|
737
|
+
const versionsUrl = `${RUBYGEMS_V1_VERSIONS_URL}${name}.json`;
|
|
738
|
+
let versions;
|
|
739
|
+
const prefetchedEntry = prefetched.get(versionsUrl);
|
|
740
|
+
if (prefetchedEntry?.ok) {
|
|
741
|
+
versions = prefetchedEntry.body;
|
|
742
|
+
} else {
|
|
743
|
+
if (DEBUG_MODE) {
|
|
744
|
+
console.log(
|
|
745
|
+
`Querying rubygems.org for all ${versionedPkgs.length} variants of ${name}`,
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
const res =
|
|
749
|
+
prefetchedResponse(prefetched, versionsUrl) ||
|
|
750
|
+
(await cdxgenAgent.get(versionsUrl, apiOptions));
|
|
751
|
+
versions = res.body;
|
|
752
|
+
}
|
|
753
|
+
if (Array.isArray(versions) && versions.length) {
|
|
754
|
+
const remaining = [];
|
|
755
|
+
for (const p of pkgs) {
|
|
756
|
+
const platform = gemComponentPlatform(p);
|
|
757
|
+
const match = versions.find(
|
|
758
|
+
(aversion) =>
|
|
759
|
+
aversion.number === p.version &&
|
|
760
|
+
(aversion.platform || "ruby") === platform,
|
|
761
|
+
);
|
|
762
|
+
if (match) {
|
|
763
|
+
applyGemMetadata(p, match);
|
|
764
|
+
rdepList.push(p);
|
|
765
|
+
} else {
|
|
766
|
+
remaining.push(p);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
// Anything the listing did not cover falls through to a direct lookup
|
|
770
|
+
for (const p of remaining) {
|
|
771
|
+
await enrichGemFromVersionEndpoint(
|
|
772
|
+
p,
|
|
773
|
+
RUBYGEMS_V2_URL,
|
|
774
|
+
RUBYGEMS_V1_URL,
|
|
775
|
+
apiOptions,
|
|
776
|
+
prefetched,
|
|
777
|
+
);
|
|
778
|
+
rdepList.push(p);
|
|
779
|
+
}
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
} catch (err) {
|
|
783
|
+
if (DEBUG_MODE) {
|
|
784
|
+
console.error(name, err);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
for (const p of pkgs) {
|
|
789
|
+
await enrichGemFromVersionEndpoint(
|
|
790
|
+
p,
|
|
791
|
+
RUBYGEMS_V2_URL,
|
|
792
|
+
RUBYGEMS_V1_URL,
|
|
793
|
+
apiOptions,
|
|
794
|
+
prefetched,
|
|
795
|
+
);
|
|
796
|
+
rdepList.push(p);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
return rdepList;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Look up a single gem version and enrich the component in place.
|
|
804
|
+
*
|
|
805
|
+
* @param {object} p Component to enrich
|
|
806
|
+
* @param {string} v2Url Base url of the v2 rubygems endpoint
|
|
807
|
+
* @param {string} v1Url Base url of the v1 gems endpoint
|
|
808
|
+
* @param {object} apiOptions Request options
|
|
809
|
+
* @param {Map} [prefetched] Results from a prior prefetchJson round.
|
|
810
|
+
*/
|
|
811
|
+
async function enrichGemFromVersionEndpoint(
|
|
812
|
+
p,
|
|
813
|
+
v2Url,
|
|
814
|
+
v1Url,
|
|
815
|
+
apiOptions,
|
|
816
|
+
prefetched,
|
|
817
|
+
) {
|
|
818
|
+
try {
|
|
819
|
+
if (DEBUG_MODE) {
|
|
820
|
+
console.log(`Querying rubygems.org for ${p.name}`);
|
|
821
|
+
}
|
|
822
|
+
const fullUrl = gemDirectUrl(p, v2Url, v1Url);
|
|
823
|
+
if (!fullUrl) {
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
const res =
|
|
827
|
+
prefetchedResponse(prefetched, fullUrl) ||
|
|
828
|
+
(await cdxgenAgent.get(fullUrl, apiOptions));
|
|
829
|
+
let body = res.body;
|
|
830
|
+
if (body?.length) {
|
|
831
|
+
body = body[0];
|
|
832
|
+
}
|
|
833
|
+
applyGemMetadata(p, body);
|
|
834
|
+
} catch (err) {
|
|
835
|
+
if (DEBUG_MODE) {
|
|
836
|
+
console.error(p, err);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function _upperFirst(string) {
|
|
842
|
+
return string.slice(0, 1).toUpperCase() + string.slice(1, string.length);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Utility method to convert a gem package name to a CamelCased module name. Low accuracy.
|
|
847
|
+
*
|
|
848
|
+
* @param name Package name
|
|
849
|
+
*/
|
|
850
|
+
export function toGemModuleNames(name) {
|
|
851
|
+
const modList = name.split("-").map((s) => {
|
|
852
|
+
return s
|
|
853
|
+
.split("_")
|
|
854
|
+
.map((str) => {
|
|
855
|
+
return _upperFirst(str.split("/").map(_upperFirst).join("/"));
|
|
856
|
+
})
|
|
857
|
+
.join("");
|
|
858
|
+
});
|
|
859
|
+
const moduleNames = [];
|
|
860
|
+
let prefix = "";
|
|
861
|
+
for (const amod of modList) {
|
|
862
|
+
if (amod !== "Ruby") {
|
|
863
|
+
moduleNames.push(`${prefix}${amod}`);
|
|
864
|
+
}
|
|
865
|
+
prefix = prefix?.length ? `${prefix}${amod}::` : `${amod}::`;
|
|
866
|
+
// ruby-prof is RubyProf
|
|
867
|
+
if (prefix === "Ruby::") {
|
|
868
|
+
prefix = "Ruby";
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return moduleNames;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* Collect all namespaces for a given gem present at the given gemHome
|
|
876
|
+
*
|
|
877
|
+
* @param {String} rubyCommand Ruby command to use if bundle is not available
|
|
878
|
+
* @param {String} bundleCommand Bundle command to use
|
|
879
|
+
* @param {String} gemHome Value to use as GEM_HOME env variable
|
|
880
|
+
* @param {String} gemName Name of the gem
|
|
881
|
+
* @param {String} filePath File path to the directory containing the Gemfile or .bundle directory
|
|
882
|
+
*
|
|
883
|
+
* @returns {Array<string>} List of module names
|
|
884
|
+
*/
|
|
885
|
+
export function collectGemModuleNames(
|
|
886
|
+
rubyCommand,
|
|
887
|
+
bundleCommand,
|
|
888
|
+
gemHome,
|
|
889
|
+
gemName,
|
|
890
|
+
filePath,
|
|
891
|
+
) {
|
|
892
|
+
gemHome =
|
|
893
|
+
gemHome ||
|
|
894
|
+
readEnvironmentVariable("CDXGEN_GEM_HOME") ||
|
|
895
|
+
readEnvironmentVariable("GEM_HOME");
|
|
896
|
+
if (!gemHome) {
|
|
897
|
+
console.log(
|
|
898
|
+
"Set the environment variable CDXGEN_GEM_HOME or GEM_HOME to collect the gem module names.",
|
|
899
|
+
);
|
|
900
|
+
return [];
|
|
901
|
+
}
|
|
902
|
+
if (!gemName || gemName.startsWith("/") || gemName === ".") {
|
|
903
|
+
return [];
|
|
904
|
+
}
|
|
905
|
+
gemName = gemName.replace(/["']/g, "");
|
|
906
|
+
// Module names for some gems cannot be obtained with our one-liner
|
|
907
|
+
// So we keep a hard-coded list of such problematic ones.
|
|
908
|
+
if (RUBY_KNOWN_MODULES[gemName]) {
|
|
909
|
+
return RUBY_KNOWN_MODULES[gemName];
|
|
910
|
+
}
|
|
911
|
+
const moduleNames = new Set();
|
|
912
|
+
const commandToUse = bundleCommand || rubyCommand;
|
|
913
|
+
let args = bundleCommand ? ["exec", "ruby"] : [];
|
|
914
|
+
args = args.concat([
|
|
915
|
+
"-e",
|
|
916
|
+
`initial = ObjectSpace.each_object(Module).map { |m| m.respond_to?(:name) ? m.name : nil }.compact;
|
|
917
|
+
require '${gemName}';
|
|
918
|
+
begin
|
|
919
|
+
afterwards = ObjectSpace.each_object(Module).map { |m| m.respond_to?(:name) ? m.name : nil }.compact;
|
|
920
|
+
added = afterwards - initial;
|
|
921
|
+
puts added.sort
|
|
922
|
+
rescue NoMethodError => e
|
|
923
|
+
puts ""
|
|
924
|
+
end
|
|
925
|
+
`,
|
|
926
|
+
]);
|
|
927
|
+
const result = safeSpawnSync(commandToUse, args, {
|
|
928
|
+
shell: isWin,
|
|
929
|
+
timeout: 5000,
|
|
930
|
+
cwd: filePath,
|
|
931
|
+
env: {
|
|
932
|
+
...process.env,
|
|
933
|
+
GEM_HOME: gemHome,
|
|
934
|
+
},
|
|
935
|
+
});
|
|
936
|
+
if (result.error || result.status !== 0) {
|
|
937
|
+
if (result?.stderr?.includes("Could not locate Gemfile or .bundle")) {
|
|
938
|
+
console.log(
|
|
939
|
+
`${filePath} must be a directory containing the Gemfile. This appears like a bug in cdxgen.`,
|
|
940
|
+
);
|
|
941
|
+
return [];
|
|
942
|
+
}
|
|
943
|
+
// Let's retry for simple mismatches
|
|
944
|
+
if (gemName?.includes("-")) {
|
|
945
|
+
return collectGemModuleNames(
|
|
946
|
+
rubyCommand,
|
|
947
|
+
bundleCommand,
|
|
948
|
+
gemHome,
|
|
949
|
+
gemName.replaceAll("-", "/"),
|
|
950
|
+
filePath,
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
// bundle can sometimes offer suggestions for simple mismatches. Let's try that.
|
|
954
|
+
if (result?.stderr?.includes("Did you mean?")) {
|
|
955
|
+
const altGemName = result.stderr
|
|
956
|
+
.split("Did you mean? ")[1]
|
|
957
|
+
.split("\n")[0]
|
|
958
|
+
.trim();
|
|
959
|
+
if (
|
|
960
|
+
altGemName?.length &&
|
|
961
|
+
!altGemName.startsWith("/") &&
|
|
962
|
+
altGemName !== "." &&
|
|
963
|
+
gemName.replace(/[-_/]/g, "").toLowerCase() ===
|
|
964
|
+
altGemName.replace(/[-_/]/g, "").toLowerCase()
|
|
965
|
+
) {
|
|
966
|
+
if (DEBUG_MODE) {
|
|
967
|
+
console.log("Retrying", gemName, "with", altGemName);
|
|
968
|
+
}
|
|
969
|
+
return collectGemModuleNames(
|
|
970
|
+
rubyCommand,
|
|
971
|
+
bundleCommand,
|
|
972
|
+
gemHome,
|
|
973
|
+
altGemName,
|
|
974
|
+
filePath,
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
if (DEBUG_MODE) {
|
|
978
|
+
console.log(
|
|
979
|
+
`Is ${altGemName} an alternative gem name for '${gemName}' package? Please let us know if this is correct.`,
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
// Gem wasn't installed or the GEM_HOME was not set correctly.
|
|
984
|
+
if (
|
|
985
|
+
result?.stderr?.includes("Bundler::GemNotFound") ||
|
|
986
|
+
result?.stderr?.includes("(LoadError)")
|
|
987
|
+
) {
|
|
988
|
+
return [];
|
|
989
|
+
}
|
|
990
|
+
if (
|
|
991
|
+
!result?.stderr?.includes("(NameError)") &&
|
|
992
|
+
!result?.stderr?.includes("(NoMethodError)") &&
|
|
993
|
+
!result?.stderr?.includes("(ArgumentError)") &&
|
|
994
|
+
DEBUG_MODE
|
|
995
|
+
) {
|
|
996
|
+
console.log(
|
|
997
|
+
`Unable to collect the module names exported by the gem ${gemName}.`,
|
|
998
|
+
);
|
|
999
|
+
console.log(result.stderr);
|
|
1000
|
+
}
|
|
1001
|
+
// Let's guess the module name based on common naming convention.
|
|
1002
|
+
return toGemModuleNames(gemName);
|
|
1003
|
+
}
|
|
1004
|
+
const simpleModuleNames = new Set();
|
|
1005
|
+
for (const aline of result.stdout.split("\n")) {
|
|
1006
|
+
if (
|
|
1007
|
+
!aline?.length ||
|
|
1008
|
+
aline.startsWith("Ignoring ") ||
|
|
1009
|
+
aline.includes("cannot load such file") ||
|
|
1010
|
+
aline.startsWith("#<")
|
|
1011
|
+
) {
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
if (!aline.includes("::")) {
|
|
1015
|
+
simpleModuleNames.add(aline.trim());
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
moduleNames.add(aline.trim());
|
|
1019
|
+
}
|
|
1020
|
+
return moduleNames.size
|
|
1021
|
+
? Array.from(moduleNames).sort()
|
|
1022
|
+
: Array.from(simpleModuleNames).sort();
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
/**
|
|
1026
|
+
* Method to parse Gemspec file contents
|
|
1027
|
+
*
|
|
1028
|
+
* @param {string} gemspecData Gemspec data
|
|
1029
|
+
* @param {string} gemspecFile File name for evidence.
|
|
1030
|
+
*/
|
|
1031
|
+
export async function parseGemspecData(gemspecData, gemspecFile) {
|
|
1032
|
+
let pkgList = [];
|
|
1033
|
+
const pkg = { properties: [] };
|
|
1034
|
+
if (gemspecFile) {
|
|
1035
|
+
pkg.name = basename(gemspecFile).replace(".gemspec", "");
|
|
1036
|
+
}
|
|
1037
|
+
if (!gemspecData) {
|
|
1038
|
+
return pkgList;
|
|
1039
|
+
}
|
|
1040
|
+
let versionHackMatch = false;
|
|
1041
|
+
let gemPlatform;
|
|
1042
|
+
const gemMetadata = {};
|
|
1043
|
+
let metadataBlock = false;
|
|
1044
|
+
gemspecData.split("\n").forEach((l) => {
|
|
1045
|
+
versionHackMatch = false;
|
|
1046
|
+
l = l.replaceAll("\r", "");
|
|
1047
|
+
l = l.replace(/\s+/g, " ").replaceAll("%q{", "").trim().replace(/}$/, "");
|
|
1048
|
+
if (l.startsWith("#")) {
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
// Native gems declare a platform. Installed gemspecs use either a plain
|
|
1052
|
+
// string (`s.platform = "java".freeze`) or the marshalled array form
|
|
1053
|
+
// (`s.platform = Gem::Platform.new(["x86_64", "linux", nil])`).
|
|
1054
|
+
if (l.includes(".platform = ")) {
|
|
1055
|
+
const platformValue = l
|
|
1056
|
+
.split(".platform = ")[1]
|
|
1057
|
+
.replaceAll(".freeze", "");
|
|
1058
|
+
if (platformValue.includes("Gem::Platform.new")) {
|
|
1059
|
+
const platformParts = platformValue
|
|
1060
|
+
.replace(/.*\[/, "")
|
|
1061
|
+
.replace(/].*/, "")
|
|
1062
|
+
.split(",")
|
|
1063
|
+
.map((s) => s.trim().replace(/["']/g, ""))
|
|
1064
|
+
.filter((s) => s.length && s !== "nil");
|
|
1065
|
+
if (platformParts.length) {
|
|
1066
|
+
gemPlatform = platformParts.join("-");
|
|
1067
|
+
}
|
|
1068
|
+
} else if (!platformValue.includes("Gem::Platform::RUBY")) {
|
|
1069
|
+
const platformString = platformValue.replace(/["';]/g, "").trim();
|
|
1070
|
+
if (platformString.length && isRubyPlatform(platformString)) {
|
|
1071
|
+
gemPlatform = platformString;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
for (const aprop of ["name", "version", "description", "homepage"]) {
|
|
1076
|
+
if (l.includes(`.${aprop} = `)) {
|
|
1077
|
+
let value = l
|
|
1078
|
+
.split(`.${aprop} = `)[1]
|
|
1079
|
+
.replace(".freeze", "")
|
|
1080
|
+
.replaceAll("''', ", "")
|
|
1081
|
+
.replace(/"/g, "");
|
|
1082
|
+
if (["name", "version"].includes(aprop)) {
|
|
1083
|
+
value = value.replace(/["']/g, "");
|
|
1084
|
+
}
|
|
1085
|
+
// Do not set name=name or version=version
|
|
1086
|
+
if (value !== aprop) {
|
|
1087
|
+
pkg[aprop] = value;
|
|
1088
|
+
break;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
// Handle common problems
|
|
1093
|
+
if (pkg.name === "name") {
|
|
1094
|
+
console.log(
|
|
1095
|
+
"Unable to identify the package name by parsing the file",
|
|
1096
|
+
gemspecFile,
|
|
1097
|
+
);
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
if (
|
|
1101
|
+
pkg?.version === "version" ||
|
|
1102
|
+
pkg?.version?.includes("$") ||
|
|
1103
|
+
pkg?.version?.includes("gem_version") ||
|
|
1104
|
+
pkg?.version?.includes("File.") ||
|
|
1105
|
+
pkg?.version?.includes("::")
|
|
1106
|
+
) {
|
|
1107
|
+
const origVersion = pkg.version;
|
|
1108
|
+
pkg.version = undefined;
|
|
1109
|
+
// Can we find the version from the directory name?
|
|
1110
|
+
if (gemspecFile) {
|
|
1111
|
+
const parentDir = basename(dirname(gemspecFile));
|
|
1112
|
+
const prefix = `${pkg.name}-`;
|
|
1113
|
+
if (parentDir.startsWith(prefix)) {
|
|
1114
|
+
const versionFromDir = parentDir.slice(prefix.length).split("-")[0];
|
|
1115
|
+
if (/\d/.test(versionFromDir)) {
|
|
1116
|
+
pkg.version = versionFromDir;
|
|
1117
|
+
versionHackMatch = true;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
if (!versionHackMatch && !pkg.version) {
|
|
1122
|
+
if (origVersion?.toLowerCase().includes("version")) {
|
|
1123
|
+
if (DEBUG_MODE) {
|
|
1124
|
+
console.log(
|
|
1125
|
+
`Unable to identify the version for '${pkg.name}' from the string '${origVersion}'. Spec file: ${gemspecFile}`,
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
} else {
|
|
1129
|
+
console.log(
|
|
1130
|
+
`Unable to identify the version for '${pkg.name}'. Spec file: ${gemspecFile}`,
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
for (const aprop of ["authors", "licenses"]) {
|
|
1136
|
+
if (l.includes(`.${aprop} = `)) {
|
|
1137
|
+
try {
|
|
1138
|
+
const pline = l
|
|
1139
|
+
.split(`.${aprop} = `)
|
|
1140
|
+
.pop()
|
|
1141
|
+
.replaceAll(".freeze", "")
|
|
1142
|
+
.replaceAll("%w", "")
|
|
1143
|
+
.replaceAll("'", '"')
|
|
1144
|
+
.replaceAll(']"', "");
|
|
1145
|
+
const apropList = JSON.parse(pline);
|
|
1146
|
+
if (apropList) {
|
|
1147
|
+
if (Array.isArray(apropList)) {
|
|
1148
|
+
pkg[aprop] = apropList;
|
|
1149
|
+
} else if (
|
|
1150
|
+
typeof apropList === "string" ||
|
|
1151
|
+
apropList instanceof String
|
|
1152
|
+
) {
|
|
1153
|
+
pkg[aprop] = apropList.split(",");
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
} catch (_err) {
|
|
1157
|
+
const alist = l
|
|
1158
|
+
.replace(/[[\]'"]/g, "")
|
|
1159
|
+
.replaceAll("%w", "")
|
|
1160
|
+
.split(", ");
|
|
1161
|
+
if (alist?.length) {
|
|
1162
|
+
pkg[aprop] = alist;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
if (l.includes(".executables = ")) {
|
|
1168
|
+
try {
|
|
1169
|
+
const exeList = JSON.parse(
|
|
1170
|
+
l
|
|
1171
|
+
.split(".executables = ")
|
|
1172
|
+
.pop()
|
|
1173
|
+
.replaceAll(".freeze", "")
|
|
1174
|
+
.replaceAll("'", '"')
|
|
1175
|
+
.replaceAll(']"', ""),
|
|
1176
|
+
);
|
|
1177
|
+
if (exeList && Array.isArray(exeList)) {
|
|
1178
|
+
pkg.properties.push({
|
|
1179
|
+
name: "cdx:gem:executables",
|
|
1180
|
+
value: exeList.join(", "),
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
} catch (_err) {
|
|
1184
|
+
// pass
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
// A gem with extensions compiles native code at install time, which is a
|
|
1188
|
+
// meaningful execution surface for a supply chain review.
|
|
1189
|
+
if (l.includes(".extensions = ")) {
|
|
1190
|
+
const extList = parseGemspecArray(l.split(".extensions = ").pop());
|
|
1191
|
+
if (extList.length) {
|
|
1192
|
+
pkg.properties.push({
|
|
1193
|
+
name: "cdx:gem:extensions",
|
|
1194
|
+
value: extList.join(", "),
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
// `required_ruby_version` and `required_rubygems_version` are
|
|
1199
|
+
// Gem::Requirement strings such as `>= 3.0` or `>= 2.2, < 4.0`
|
|
1200
|
+
for (const [aprop, propName] of [
|
|
1201
|
+
["required_ruby_version", "cdx:gem:rubyVersionSpecifiers"],
|
|
1202
|
+
["required_rubygems_version", "cdx:gem:rubygemsVersionSpecifiers"],
|
|
1203
|
+
]) {
|
|
1204
|
+
if (l.includes(`.${aprop} = `)) {
|
|
1205
|
+
const requirement = parseGemRequirement(l.split(`.${aprop} = `).pop());
|
|
1206
|
+
// `>= 0` carries no information
|
|
1207
|
+
if (requirement && requirement !== ">= 0") {
|
|
1208
|
+
pkg.properties.push({ name: propName, value: requirement });
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
// The free form metadata hash carries the well known project URIs and the
|
|
1213
|
+
// `rubygems_mfa_required` supply chain signal. It may be written inline as
|
|
1214
|
+
// `s.metadata = { "a" => "b", "c" => "d" }`, spread over several lines, or
|
|
1215
|
+
// assigned a key at a time with `s.metadata["a"] = "b"`.
|
|
1216
|
+
if (l.includes(".metadata")) {
|
|
1217
|
+
metadataBlock = true;
|
|
1218
|
+
}
|
|
1219
|
+
if (metadataBlock) {
|
|
1220
|
+
for (const [, key, value] of l.matchAll(
|
|
1221
|
+
/["']([a-z_]+)["']\s*(?:=>|\]\s*=)\s*["']([^"']*)["']/g,
|
|
1222
|
+
)) {
|
|
1223
|
+
gemMetadata[key] = value;
|
|
1224
|
+
}
|
|
1225
|
+
if (l.includes("}") || (!l.includes(".metadata") && !l.includes("=>"))) {
|
|
1226
|
+
metadataBlock = false;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
});
|
|
1230
|
+
for (const [key, value] of Object.entries(gemMetadata)) {
|
|
1231
|
+
if (key === "rubygems_mfa_required") {
|
|
1232
|
+
pkg.properties.push({
|
|
1233
|
+
name: "cdx:gem:mfaRequired",
|
|
1234
|
+
value: `${value}`,
|
|
1235
|
+
});
|
|
1236
|
+
continue;
|
|
1237
|
+
}
|
|
1238
|
+
const refType = GEM_METADATA_REFERENCE_TYPES[key];
|
|
1239
|
+
if (refType) {
|
|
1240
|
+
pkg.externalReferences = pkg.externalReferences || [];
|
|
1241
|
+
pkg.externalReferences.push({ type: refType, url: value, comment: key });
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
if (pkg.name) {
|
|
1245
|
+
const purlString = toGemPurl(pkg.name, pkg.version, gemPlatform);
|
|
1246
|
+
pkg.purl = purlString;
|
|
1247
|
+
pkg["bom-ref"] = decodeURIComponent(purlString);
|
|
1248
|
+
}
|
|
1249
|
+
if (gemspecFile) {
|
|
1250
|
+
pkg.properties.push({ name: "internal:SrcFile", value: gemspecFile });
|
|
1251
|
+
// Did we find the version number from the directory name? Let's reduce the confidence and set the correct technique
|
|
1252
|
+
pkg.evidence = {
|
|
1253
|
+
identity: {
|
|
1254
|
+
field: "purl",
|
|
1255
|
+
confidence: !pkg.version || versionHackMatch ? 0.2 : 0.5,
|
|
1256
|
+
methods: [
|
|
1257
|
+
{
|
|
1258
|
+
technique: versionHackMatch ? "filename" : "manifest-analysis",
|
|
1259
|
+
confidence: !pkg.version || versionHackMatch ? 0.2 : 0.5,
|
|
1260
|
+
value: gemspecFile,
|
|
1261
|
+
},
|
|
1262
|
+
],
|
|
1263
|
+
},
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
if (pkg.authors) {
|
|
1267
|
+
pkg.authors = pkg.authors.map((a) => {
|
|
1268
|
+
return { name: a };
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
if (pkg.licenses) {
|
|
1272
|
+
pkg.licenses = pkg.licenses.map((l) => {
|
|
1273
|
+
return { license: { name: l } };
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
if (pkg.name) {
|
|
1277
|
+
pkgList = [pkg];
|
|
1278
|
+
} else {
|
|
1279
|
+
console.log("Unable to parse", gemspecData, gemspecFile);
|
|
1280
|
+
}
|
|
1281
|
+
if (shouldFetchLicense()) {
|
|
1282
|
+
return await getRubyGemsMetadata(pkgList);
|
|
1283
|
+
}
|
|
1284
|
+
return pkgList;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
/**
|
|
1288
|
+
* Method to parse Gemfile.lock
|
|
1289
|
+
*
|
|
1290
|
+
* @param {object} gemLockData Gemfile.lock data
|
|
1291
|
+
* @param {string} lockFile Lock file
|
|
1292
|
+
*/
|
|
1293
|
+
export async function parseGemfileLockData(gemLockData, lockFile) {
|
|
1294
|
+
let pkgList = [];
|
|
1295
|
+
const pkgnames = {};
|
|
1296
|
+
const dependenciesList = [];
|
|
1297
|
+
const dependenciesMap = {};
|
|
1298
|
+
const pkgVersionMap = {};
|
|
1299
|
+
const pkgVersionPlatformMap = {};
|
|
1300
|
+
const pkgNameRef = {};
|
|
1301
|
+
if (!gemLockData) {
|
|
1302
|
+
return pkgList;
|
|
1303
|
+
}
|
|
1304
|
+
const checksumsMap = {};
|
|
1305
|
+
let specsFound = false;
|
|
1306
|
+
let checksumsFound = false;
|
|
1307
|
+
// We need two passes to identify components and resolve dependencies
|
|
1308
|
+
// In the first pass, we capture package name and version
|
|
1309
|
+
gemLockData.split("\n").forEach((l) => {
|
|
1310
|
+
l = l.trim();
|
|
1311
|
+
l = l.replaceAll("\r", "");
|
|
1312
|
+
if (checksumsFound) {
|
|
1313
|
+
const gemHashes = parseGemChecksumLine(l);
|
|
1314
|
+
if (gemHashes) {
|
|
1315
|
+
checksumsMap[gemHashes.lockName] = gemHashes.hashes;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
if (specsFound) {
|
|
1319
|
+
const tmpA = l.split(" ");
|
|
1320
|
+
if (tmpA && tmpA.length === 2) {
|
|
1321
|
+
const name = tmpA[0];
|
|
1322
|
+
if (name === "remote:") {
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
let version = tmpA[1];
|
|
1326
|
+
// We only allow bracket characters ()
|
|
1327
|
+
if (version.search(/[,><~ ]/) < 0) {
|
|
1328
|
+
version = version.replace(/[=()]/g, "");
|
|
1329
|
+
// Sometimes, the version number could include the platform
|
|
1330
|
+
// Examples:
|
|
1331
|
+
// bcrypt_pbkdf (1.1.0)
|
|
1332
|
+
// bcrypt_pbkdf (1.1.0-x64-mingw32)
|
|
1333
|
+
// bcrypt_pbkdf (1.1.0-x86-mingw32)
|
|
1334
|
+
// In such cases, we need to track all of them to improve precision
|
|
1335
|
+
const { platform } = splitRubyVersionPlatform(version);
|
|
1336
|
+
if (platform) {
|
|
1337
|
+
pkgVersionMap[`${name}-${platform}`] = version;
|
|
1338
|
+
if (!pkgVersionPlatformMap[name]) {
|
|
1339
|
+
pkgVersionPlatformMap[name] = new Set();
|
|
1340
|
+
}
|
|
1341
|
+
pkgVersionPlatformMap[name].add(version);
|
|
1342
|
+
} else {
|
|
1343
|
+
pkgVersionMap[name] = version;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
if (l === "specs:") {
|
|
1349
|
+
specsFound = true;
|
|
1350
|
+
}
|
|
1351
|
+
if (l === l.toUpperCase()) {
|
|
1352
|
+
specsFound = false;
|
|
1353
|
+
checksumsFound = l === "CHECKSUMS";
|
|
1354
|
+
}
|
|
1355
|
+
});
|
|
1356
|
+
specsFound = false;
|
|
1357
|
+
let lastParent;
|
|
1358
|
+
let lastRemote;
|
|
1359
|
+
let lastRevision;
|
|
1360
|
+
let lastBranch;
|
|
1361
|
+
let lastTag;
|
|
1362
|
+
let lastParentPlatform;
|
|
1363
|
+
// Dependencies block would begin with DEPENDENCIES
|
|
1364
|
+
let dependenciesBlock = false;
|
|
1365
|
+
const rootList = [];
|
|
1366
|
+
// In the second pass, we use the space in the prefix to figure out the dependency tree
|
|
1367
|
+
gemLockData.split("\n").forEach((l) => {
|
|
1368
|
+
l = l.replaceAll("\r", "");
|
|
1369
|
+
if (l.trim().startsWith("remote:")) {
|
|
1370
|
+
lastRemote = l.trim().split(" ")[1];
|
|
1371
|
+
if (lastRemote.length < 3) {
|
|
1372
|
+
lastRemote = undefined;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
if (l.trim().startsWith("revision:")) {
|
|
1376
|
+
lastRevision = l.trim().split(" ")[1];
|
|
1377
|
+
}
|
|
1378
|
+
if (l.trim().startsWith("branch:")) {
|
|
1379
|
+
lastBranch = l.trim().split(" ")[1];
|
|
1380
|
+
}
|
|
1381
|
+
if (l.trim().startsWith("tag:")) {
|
|
1382
|
+
lastTag = l.trim().split(" ")[1];
|
|
1383
|
+
}
|
|
1384
|
+
if (l.trim() === l.trim().toUpperCase()) {
|
|
1385
|
+
if (l.trim() === "DEPENDENCIES") {
|
|
1386
|
+
dependenciesBlock = true;
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
dependenciesBlock = false;
|
|
1390
|
+
specsFound = false;
|
|
1391
|
+
lastRemote = undefined;
|
|
1392
|
+
lastRevision = undefined;
|
|
1393
|
+
lastBranch = undefined;
|
|
1394
|
+
lastTag = undefined;
|
|
1395
|
+
lastParentPlatform = undefined;
|
|
1396
|
+
}
|
|
1397
|
+
if (l.trim() === "specs:") {
|
|
1398
|
+
specsFound = true;
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
if (specsFound) {
|
|
1402
|
+
const tmpA = l.split(" (");
|
|
1403
|
+
const nameWithPrefix = tmpA[0];
|
|
1404
|
+
const name = tmpA[0].replace(/["']/g, "").trim();
|
|
1405
|
+
const level = nameWithPrefix.replace(name, "").split(" ").length % 2;
|
|
1406
|
+
if (
|
|
1407
|
+
!name.length ||
|
|
1408
|
+
["remote:", "bundler", name.toUpperCase()].includes(name)
|
|
1409
|
+
) {
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
let mayBeVersion = l
|
|
1413
|
+
.trim()
|
|
1414
|
+
.replace(name, "")
|
|
1415
|
+
.replace(" (", "")
|
|
1416
|
+
.replace(")", "");
|
|
1417
|
+
if (mayBeVersion.search(/[,><~ ]/) < 0) {
|
|
1418
|
+
// Reset the platform
|
|
1419
|
+
if (level === 1) {
|
|
1420
|
+
lastParentPlatform = undefined;
|
|
1421
|
+
}
|
|
1422
|
+
// Extract the platform. Child gems without an explicit version inherit
|
|
1423
|
+
// the platform of their platform-specific parent, so we must only
|
|
1424
|
+
// overwrite the tracked platform when this line declares one.
|
|
1425
|
+
const { platform } = splitRubyVersionPlatform(mayBeVersion);
|
|
1426
|
+
if (platform) {
|
|
1427
|
+
lastParentPlatform = platform;
|
|
1428
|
+
}
|
|
1429
|
+
} else {
|
|
1430
|
+
mayBeVersion = undefined;
|
|
1431
|
+
}
|
|
1432
|
+
// Resolve this line to one or more concrete gem releases.
|
|
1433
|
+
//
|
|
1434
|
+
// A line that declares its own version is a spec and resolves to exactly
|
|
1435
|
+
// one release. A dependency reference has to be looked up, and when the
|
|
1436
|
+
// gem ships only as native builds we cannot tell which one Bundler would
|
|
1437
|
+
// install, so every variant becomes a candidate. Emitting the variants
|
|
1438
|
+
// that the lockfile really contains is more accurate than inventing a
|
|
1439
|
+
// phantom versionless component, which is what used to happen.
|
|
1440
|
+
const variants = pkgVersionPlatformMap[name]
|
|
1441
|
+
? Array.from(pkgVersionPlatformMap[name])
|
|
1442
|
+
: [];
|
|
1443
|
+
let resolvedVersions = [];
|
|
1444
|
+
if (mayBeVersion) {
|
|
1445
|
+
resolvedVersions = [mayBeVersion];
|
|
1446
|
+
} else if (pkgVersionMap[name]) {
|
|
1447
|
+
// Identifying the resolved version for a given dependency requires multiple lookups
|
|
1448
|
+
resolvedVersions = [pkgVersionMap[name]];
|
|
1449
|
+
} else if (lastParentPlatform) {
|
|
1450
|
+
// Is there a platform specific alias?
|
|
1451
|
+
const alias = pkgVersionMap[`${name}-${lastParentPlatform}`];
|
|
1452
|
+
if (alias) {
|
|
1453
|
+
resolvedVersions = [alias];
|
|
1454
|
+
} else {
|
|
1455
|
+
// Is there a match based on the last parent platform?
|
|
1456
|
+
const fuzzyMatch = variants.find((aver) =>
|
|
1457
|
+
aver.includes(lastParentPlatform.replace("-gnu", "")),
|
|
1458
|
+
);
|
|
1459
|
+
if (fuzzyMatch) {
|
|
1460
|
+
resolvedVersions = [fuzzyMatch];
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
if (!resolvedVersions.length && variants.length) {
|
|
1465
|
+
resolvedVersions = variants;
|
|
1466
|
+
}
|
|
1467
|
+
if (!resolvedVersions.length) {
|
|
1468
|
+
resolvedVersions = [undefined];
|
|
1469
|
+
}
|
|
1470
|
+
for (const resolvedVersion of resolvedVersions) {
|
|
1471
|
+
// RubyGems models the version and the native platform separately, so we
|
|
1472
|
+
// must not leave the platform suffix in the version. The purl
|
|
1473
|
+
// specification represents it with the `platform` qualifier.
|
|
1474
|
+
const { version, platform } = splitRubyVersionPlatform(resolvedVersion);
|
|
1475
|
+
const purlString = toGemPurl(name, version, platform);
|
|
1476
|
+
const bomRef = decodeURIComponent(purlString);
|
|
1477
|
+
if (level === 1) {
|
|
1478
|
+
lastParent = bomRef;
|
|
1479
|
+
}
|
|
1480
|
+
const properties = [
|
|
1481
|
+
{
|
|
1482
|
+
name: "internal:SrcFile",
|
|
1483
|
+
value: lockFile,
|
|
1484
|
+
},
|
|
1485
|
+
];
|
|
1486
|
+
if (lastRemote) {
|
|
1487
|
+
properties.push({
|
|
1488
|
+
name: "cdx:gem:remote",
|
|
1489
|
+
value: lastRemote,
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
if (lastRevision) {
|
|
1493
|
+
properties.push({
|
|
1494
|
+
name: "cdx:gem:remoteRevision",
|
|
1495
|
+
value: lastRevision,
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
if (lastBranch) {
|
|
1499
|
+
properties.push({
|
|
1500
|
+
name: "cdx:gem:remoteBranch",
|
|
1501
|
+
value: lastBranch,
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
if (lastTag) {
|
|
1505
|
+
properties.push({
|
|
1506
|
+
name: "cdx:gem:remoteTag",
|
|
1507
|
+
value: lastTag,
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
const apkg = {
|
|
1511
|
+
name,
|
|
1512
|
+
version,
|
|
1513
|
+
purl: purlString,
|
|
1514
|
+
"bom-ref": bomRef,
|
|
1515
|
+
properties,
|
|
1516
|
+
evidence: {
|
|
1517
|
+
identity: {
|
|
1518
|
+
field: "purl",
|
|
1519
|
+
confidence: 0.8,
|
|
1520
|
+
methods: [
|
|
1521
|
+
{
|
|
1522
|
+
technique: "manifest-analysis",
|
|
1523
|
+
confidence: 0.8,
|
|
1524
|
+
value: lockFile,
|
|
1525
|
+
},
|
|
1526
|
+
],
|
|
1527
|
+
},
|
|
1528
|
+
},
|
|
1529
|
+
};
|
|
1530
|
+
// Bundler 2.5 onwards records the sha256 of each gem in a CHECKSUMS
|
|
1531
|
+
// section, keyed by the same `name (version[-platform])` token.
|
|
1532
|
+
const gemHashes = checksumsMap[`${name} (${resolvedVersion})`];
|
|
1533
|
+
if (gemHashes) {
|
|
1534
|
+
apkg.hashes = gemHashes;
|
|
1535
|
+
}
|
|
1536
|
+
if (lastParent && lastParent !== bomRef) {
|
|
1537
|
+
if (!dependenciesMap[lastParent]) {
|
|
1538
|
+
dependenciesMap[lastParent] = new Set();
|
|
1539
|
+
}
|
|
1540
|
+
dependenciesMap[lastParent].add(bomRef);
|
|
1541
|
+
}
|
|
1542
|
+
if (!dependenciesMap[bomRef]) {
|
|
1543
|
+
dependenciesMap[bomRef] = new Set();
|
|
1544
|
+
}
|
|
1545
|
+
// A gem with several native variants has several bom-refs. Track all of
|
|
1546
|
+
// them so that a direct dependency on such a gem does not resolve to an
|
|
1547
|
+
// arbitrary variant.
|
|
1548
|
+
if (!pkgNameRef[name]) {
|
|
1549
|
+
pkgNameRef[name] = [];
|
|
1550
|
+
}
|
|
1551
|
+
if (!pkgNameRef[name].includes(bomRef)) {
|
|
1552
|
+
pkgNameRef[name].push(bomRef);
|
|
1553
|
+
}
|
|
1554
|
+
// Allow duplicate packages if the version number includes platform
|
|
1555
|
+
if (!pkgnames[purlString]) {
|
|
1556
|
+
pkgList.push(apkg);
|
|
1557
|
+
pkgnames[purlString] = true;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
} else if (dependenciesBlock) {
|
|
1561
|
+
const rootDepName = l.trim().split(" ")[0].replace("!", "");
|
|
1562
|
+
if (pkgNameRef[rootDepName]?.length) {
|
|
1563
|
+
// Every native variant of a direct dependency is a direct dependency
|
|
1564
|
+
for (const aref of pkgNameRef[rootDepName]) {
|
|
1565
|
+
if (!rootList.includes(aref)) {
|
|
1566
|
+
rootList.push(aref);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
} else {
|
|
1570
|
+
// We are dealing with an optional platform-dependent import
|
|
1571
|
+
// create a placeholder component to track this
|
|
1572
|
+
let specifier;
|
|
1573
|
+
if (l.includes("(")) {
|
|
1574
|
+
specifier = l.trim().split(" (").pop().replace(")", "").trim();
|
|
1575
|
+
}
|
|
1576
|
+
const untrackedPurl = toGemPurl(rootDepName, null, null);
|
|
1577
|
+
const untrackedBomRef = decodeURIComponent(untrackedPurl);
|
|
1578
|
+
const untrackedProps = [
|
|
1579
|
+
{
|
|
1580
|
+
name: "internal:SrcFile",
|
|
1581
|
+
value: lockFile,
|
|
1582
|
+
},
|
|
1583
|
+
];
|
|
1584
|
+
if (specifier) {
|
|
1585
|
+
untrackedProps.push({
|
|
1586
|
+
name: "cdx:gem:versionSpecifiers",
|
|
1587
|
+
value: specifier,
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
const untrackedRootDep = {
|
|
1591
|
+
name: rootDepName,
|
|
1592
|
+
version: undefined,
|
|
1593
|
+
purl: untrackedPurl,
|
|
1594
|
+
"bom-ref": untrackedBomRef,
|
|
1595
|
+
properties: untrackedProps,
|
|
1596
|
+
evidence: {
|
|
1597
|
+
identity: {
|
|
1598
|
+
field: "purl",
|
|
1599
|
+
confidence: 0.3,
|
|
1600
|
+
methods: [
|
|
1601
|
+
{
|
|
1602
|
+
technique: "manifest-analysis",
|
|
1603
|
+
confidence: 0.3,
|
|
1604
|
+
value: lockFile,
|
|
1605
|
+
},
|
|
1606
|
+
],
|
|
1607
|
+
},
|
|
1608
|
+
},
|
|
1609
|
+
};
|
|
1610
|
+
pkgnames[untrackedPurl] = true;
|
|
1611
|
+
pkgNameRef[rootDepName] = [untrackedBomRef];
|
|
1612
|
+
pkgList.push(untrackedRootDep);
|
|
1613
|
+
rootList.push(untrackedBomRef);
|
|
1614
|
+
dependenciesMap[untrackedBomRef] = new Set();
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
});
|
|
1618
|
+
for (const k of Object.keys(dependenciesMap)) {
|
|
1619
|
+
dependenciesList.push({
|
|
1620
|
+
ref: k,
|
|
1621
|
+
dependsOn: Array.from(dependenciesMap[k]).sort(),
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
if (shouldFetchLicense()) {
|
|
1625
|
+
pkgList = await getRubyGemsMetadata(pkgList);
|
|
1626
|
+
return { pkgList, dependenciesList, rootList };
|
|
1627
|
+
}
|
|
1628
|
+
return { pkgList, dependenciesList, rootList };
|
|
1629
|
+
}
|