@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,3191 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import {
|
|
4
|
+
sep as _sep,
|
|
5
|
+
basename,
|
|
6
|
+
dirname,
|
|
7
|
+
join,
|
|
8
|
+
relative,
|
|
9
|
+
resolve,
|
|
10
|
+
} from "node:path";
|
|
11
|
+
import process from "node:process";
|
|
12
|
+
|
|
13
|
+
import { build, Purl } from "@cdxgen/cdx-purl";
|
|
14
|
+
import { parse as _load } from "yaml";
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
cdxgenAgent,
|
|
18
|
+
DEBUG_MODE,
|
|
19
|
+
readEnvironmentVariable,
|
|
20
|
+
} from "../core/activity.js";
|
|
21
|
+
import { shouldFetchLicense } from "../core/env.js";
|
|
22
|
+
import {
|
|
23
|
+
safeExistsSync,
|
|
24
|
+
safeSpawnSync,
|
|
25
|
+
safeWriteSync,
|
|
26
|
+
temporaryFiles,
|
|
27
|
+
} from "../core/fs.js";
|
|
28
|
+
import { thoughtLog } from "../core/logger.js";
|
|
29
|
+
import { isWin } from "../core/paths.js";
|
|
30
|
+
import { mesonWrapDB } from "../core/state.js";
|
|
31
|
+
import { isGitHubUrl } from "../core/urls.js";
|
|
32
|
+
import { parseWorkflowFile } from "../inventory/ciParsers/githubActions.js";
|
|
33
|
+
import {
|
|
34
|
+
prefetchEnabled,
|
|
35
|
+
prefetchedResponse,
|
|
36
|
+
prefetchJson,
|
|
37
|
+
} from "../inventory/fetchBatch.js";
|
|
38
|
+
import {
|
|
39
|
+
applyPurl,
|
|
40
|
+
mapConanPkgRefToPurlStringAndNameAndVersion,
|
|
41
|
+
nixGenericPurl,
|
|
42
|
+
purlFromUrlString,
|
|
43
|
+
} from "../inventory/purl.js";
|
|
44
|
+
import { getLicenses, spdxLicenses } from "../inventory/spdx.js";
|
|
45
|
+
import { getDartMetadata } from "./ecosystems.js";
|
|
46
|
+
import { parsePkgJson } from "./parsers-js.js";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Method to parse pubspec.lock files.
|
|
50
|
+
*
|
|
51
|
+
* @param pubLockData Contents of lock data
|
|
52
|
+
* @param lockFile Filename for setting evidence
|
|
53
|
+
*
|
|
54
|
+
* @returns {Object}
|
|
55
|
+
*/
|
|
56
|
+
export async function parsePubLockData(pubLockData, lockFile) {
|
|
57
|
+
if (!pubLockData) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
let pkgList = [];
|
|
61
|
+
const rootList = [];
|
|
62
|
+
const data = _load(pubLockData);
|
|
63
|
+
const packages = data.packages;
|
|
64
|
+
for (const [packageName, packageData] of Object.entries(packages)) {
|
|
65
|
+
const pkg = {
|
|
66
|
+
name: packageName,
|
|
67
|
+
version: packageData.version,
|
|
68
|
+
properties: [],
|
|
69
|
+
};
|
|
70
|
+
// older dart versions don't have sha256
|
|
71
|
+
if (packageData.description?.sha256) {
|
|
72
|
+
pkg._integrity = `sha256-${packageData.description?.sha256}`;
|
|
73
|
+
}
|
|
74
|
+
if (
|
|
75
|
+
packageData.description?.url &&
|
|
76
|
+
packageData.description?.url !== "https://pub.dev"
|
|
77
|
+
) {
|
|
78
|
+
pkg.properties.push({
|
|
79
|
+
name: "cdx:pub:registry",
|
|
80
|
+
value: packageData.description.url,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
const purlString = new Purl({
|
|
84
|
+
type: "pub",
|
|
85
|
+
namespace: "" || null,
|
|
86
|
+
name: pkg.name,
|
|
87
|
+
version: pkg.version || null,
|
|
88
|
+
})
|
|
89
|
+
.toString()
|
|
90
|
+
.replace(/%2F/g, "/");
|
|
91
|
+
pkg["bom-ref"] = decodeURIComponent(purlString);
|
|
92
|
+
if (packageData.dependency === "direct main") {
|
|
93
|
+
pkg.scope = "required";
|
|
94
|
+
rootList.push(pkg);
|
|
95
|
+
} else if (packageData.dependency === "transitive") {
|
|
96
|
+
pkg.scope = "required";
|
|
97
|
+
} else if (packageData.dependency === "direct dev") {
|
|
98
|
+
pkg.scope = "optional";
|
|
99
|
+
}
|
|
100
|
+
if (lockFile) {
|
|
101
|
+
pkg.properties.push({
|
|
102
|
+
name: "internal:SrcFile",
|
|
103
|
+
value: lockFile,
|
|
104
|
+
});
|
|
105
|
+
pkg.evidence = {
|
|
106
|
+
identity: {
|
|
107
|
+
field: "purl",
|
|
108
|
+
confidence: 1,
|
|
109
|
+
methods: [
|
|
110
|
+
{
|
|
111
|
+
technique: "manifest-analysis",
|
|
112
|
+
confidence: 1,
|
|
113
|
+
value: lockFile,
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
pkgList.push(pkg);
|
|
120
|
+
}
|
|
121
|
+
if (shouldFetchLicense()) {
|
|
122
|
+
pkgList = await getDartMetadata(pkgList);
|
|
123
|
+
}
|
|
124
|
+
return { rootList, pkgList };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Parses a Dart pub package's pubspec.yaml content and returns a list containing
|
|
129
|
+
* a single component object with name, description, version, homepage, and purl.
|
|
130
|
+
*
|
|
131
|
+
* @param {string} pubYamlData Raw YAML string contents of a pubspec.yaml file
|
|
132
|
+
* @returns {Object[]} List containing a single Dart package component object
|
|
133
|
+
*/
|
|
134
|
+
export function parsePubYamlData(pubYamlData) {
|
|
135
|
+
const pkgList = [];
|
|
136
|
+
let yamlObj;
|
|
137
|
+
try {
|
|
138
|
+
yamlObj = _load(pubYamlData);
|
|
139
|
+
} catch (_err) {
|
|
140
|
+
// continue regardless of error
|
|
141
|
+
}
|
|
142
|
+
if (!yamlObj) {
|
|
143
|
+
return pkgList;
|
|
144
|
+
}
|
|
145
|
+
const pkg = {
|
|
146
|
+
name: yamlObj.name,
|
|
147
|
+
description: yamlObj.description,
|
|
148
|
+
version: yamlObj.version,
|
|
149
|
+
homepage: { url: yamlObj.homepage },
|
|
150
|
+
};
|
|
151
|
+
const purlString = new Purl({
|
|
152
|
+
type: "pub",
|
|
153
|
+
namespace: "" || null,
|
|
154
|
+
name: pkg.name,
|
|
155
|
+
version: pkg.version || null,
|
|
156
|
+
})
|
|
157
|
+
.toString()
|
|
158
|
+
.replace(/%2F/g, "/");
|
|
159
|
+
pkg.purl = purlString;
|
|
160
|
+
pkg["bom-ref"] = decodeURIComponent(purlString);
|
|
161
|
+
pkgList.push(pkg);
|
|
162
|
+
return pkgList;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Parses Helm chart YAML data (Chart.yaml or repository index.yaml) and returns
|
|
167
|
+
* a list of Helm chart component objects including the chart itself and any
|
|
168
|
+
* declared dependencies or index entries.
|
|
169
|
+
*
|
|
170
|
+
* @param {string} helmData Raw YAML string contents of a Helm Chart.yaml or index.yaml file
|
|
171
|
+
* @returns {Object[]} List of Helm chart component objects with name, version, and optional homepage/repository
|
|
172
|
+
*/
|
|
173
|
+
export function parseHelmYamlData(helmData) {
|
|
174
|
+
const pkgList = [];
|
|
175
|
+
let yamlObj;
|
|
176
|
+
try {
|
|
177
|
+
yamlObj = _load(helmData);
|
|
178
|
+
} catch (_err) {
|
|
179
|
+
// continue regardless of error
|
|
180
|
+
}
|
|
181
|
+
if (!yamlObj) {
|
|
182
|
+
return pkgList;
|
|
183
|
+
}
|
|
184
|
+
if (yamlObj.name && yamlObj.version) {
|
|
185
|
+
const pkg = {
|
|
186
|
+
name: yamlObj.name,
|
|
187
|
+
description: yamlObj.description || "",
|
|
188
|
+
version: yamlObj.version,
|
|
189
|
+
};
|
|
190
|
+
if (yamlObj.home) {
|
|
191
|
+
pkg["homepage"] = { url: yamlObj.home };
|
|
192
|
+
}
|
|
193
|
+
pkgList.push(pkg);
|
|
194
|
+
}
|
|
195
|
+
if (yamlObj.dependencies) {
|
|
196
|
+
for (const hd of yamlObj.dependencies) {
|
|
197
|
+
const pkg = {
|
|
198
|
+
name: hd.name,
|
|
199
|
+
version: hd.version, // This could have * so not precise
|
|
200
|
+
};
|
|
201
|
+
if (hd.repository) {
|
|
202
|
+
pkg["repository"] = { url: hd.repository };
|
|
203
|
+
}
|
|
204
|
+
pkgList.push(pkg);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (yamlObj.entries) {
|
|
208
|
+
for (const he of Object.keys(yamlObj.entries)) {
|
|
209
|
+
for (const key of Object.keys(yamlObj.entries[he])) {
|
|
210
|
+
const hd = yamlObj.entries[he][key];
|
|
211
|
+
if (hd.name && hd.version) {
|
|
212
|
+
const pkg = {
|
|
213
|
+
name: hd.name,
|
|
214
|
+
version: hd.version,
|
|
215
|
+
description: hd.description || "",
|
|
216
|
+
};
|
|
217
|
+
if (hd.sources && Array.isArray(hd.sources) && hd.sources.length) {
|
|
218
|
+
pkg["repository"] = { url: hd.sources[0] };
|
|
219
|
+
if (hd.home && hd.home !== hd.sources[0]) {
|
|
220
|
+
pkg["homepage"] = { url: hd.home };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (hd.home && !pkg["homepage"]) {
|
|
224
|
+
pkg["homepage"] = { url: hd.home };
|
|
225
|
+
}
|
|
226
|
+
if (hd.digest) {
|
|
227
|
+
pkg._integrity = `sha256-${hd.digest}`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
pkgList.push(pkg);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return pkgList;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Recursively walks a parsed YAML/JSON object structure to find container image
|
|
240
|
+
* references stored under common keys (image, repository, dockerImage, etc.) and
|
|
241
|
+
* appends discovered image and service entries to pkgList while tracking seen
|
|
242
|
+
* images in imgList to avoid duplicates.
|
|
243
|
+
*
|
|
244
|
+
* @param {Object|Array|string} keyValueObj The object, array, or string node to inspect
|
|
245
|
+
* @param {Object[]} pkgList Accumulator array that receives {image} and {service} entries
|
|
246
|
+
* @param {string[]} imgList Accumulator array of image name strings already seen
|
|
247
|
+
* @returns {string[]} The updated imgList
|
|
248
|
+
*/
|
|
249
|
+
export function recurseImageNameLookup(keyValueObj, pkgList, imgList) {
|
|
250
|
+
if (typeof keyValueObj === "string" || keyValueObj instanceof String) {
|
|
251
|
+
return imgList;
|
|
252
|
+
}
|
|
253
|
+
if (Array.isArray(keyValueObj)) {
|
|
254
|
+
for (const ele of keyValueObj) {
|
|
255
|
+
if (typeof ele !== "string") {
|
|
256
|
+
recurseImageNameLookup(ele, pkgList, imgList);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
} else if (Object.keys(keyValueObj).length) {
|
|
260
|
+
let imageLike =
|
|
261
|
+
keyValueObj.image ||
|
|
262
|
+
keyValueObj.repository ||
|
|
263
|
+
keyValueObj.dockerImage ||
|
|
264
|
+
keyValueObj.mavenImage ||
|
|
265
|
+
keyValueObj.gradleImage ||
|
|
266
|
+
keyValueObj.packImage ||
|
|
267
|
+
keyValueObj.koImage ||
|
|
268
|
+
keyValueObj.kanikoImage;
|
|
269
|
+
if (
|
|
270
|
+
!imageLike &&
|
|
271
|
+
keyValueObj.name &&
|
|
272
|
+
typeof keyValueObj.name === "string" &&
|
|
273
|
+
keyValueObj.name.includes("/")
|
|
274
|
+
) {
|
|
275
|
+
imageLike = keyValueObj.name;
|
|
276
|
+
}
|
|
277
|
+
if (
|
|
278
|
+
imageLike &&
|
|
279
|
+
typeof imageLike === "string" &&
|
|
280
|
+
!imgList.includes(imageLike)
|
|
281
|
+
) {
|
|
282
|
+
if (imageLike.includes("VERSION")) {
|
|
283
|
+
imageLike = imageLike
|
|
284
|
+
.replaceAll(":${VERSION:-", ":")
|
|
285
|
+
.replaceAll(":${VERSION:", ":")
|
|
286
|
+
.replaceAll(":%VERSION%", ":latest")
|
|
287
|
+
.replaceAll("}", "");
|
|
288
|
+
}
|
|
289
|
+
pkgList.push({ image: imageLike });
|
|
290
|
+
pkgList.push({ service: keyValueObj.name || imageLike });
|
|
291
|
+
imgList.push(imageLike);
|
|
292
|
+
}
|
|
293
|
+
for (const key of Object.keys(keyValueObj)) {
|
|
294
|
+
// Skip unwanted blocks to improve performance
|
|
295
|
+
if (["schema", "openAPIV3Schema", "names", "status"].includes(key)) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const valueObj = keyValueObj[key];
|
|
299
|
+
if (!valueObj) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (Object.keys(valueObj).length && typeof valueObj !== "string") {
|
|
303
|
+
recurseImageNameLookup(valueObj, pkgList, imgList);
|
|
304
|
+
}
|
|
305
|
+
if (Array.isArray(valueObj)) {
|
|
306
|
+
for (const ele of valueObj) {
|
|
307
|
+
if (typeof ele !== "string") {
|
|
308
|
+
recurseImageNameLookup(ele, pkgList, imgList);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return imgList;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function substituteBuildArgs(statement, buildArgs) {
|
|
318
|
+
for (const argMatch of [
|
|
319
|
+
...statement.matchAll(/\${?([^:\/\\}]+)}?/g),
|
|
320
|
+
].reverse()) {
|
|
321
|
+
const fullArgName = argMatch[0];
|
|
322
|
+
const argName = argMatch[1];
|
|
323
|
+
const argIndex = argMatch.index;
|
|
324
|
+
if (buildArgs.has(argName)) {
|
|
325
|
+
statement =
|
|
326
|
+
statement.slice(0, argIndex) +
|
|
327
|
+
buildArgs.get(argName) +
|
|
328
|
+
statement.slice(argIndex + fullArgName.length);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return statement;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Parses the contents of a Dockerfile or Containerfile and returns a list of
|
|
336
|
+
* base image objects referenced by FROM instructions, substituting ARG default
|
|
337
|
+
* values where possible and skipping multi-stage build alias references.
|
|
338
|
+
*
|
|
339
|
+
* @param {string} fileContents Raw string contents of the Dockerfile/Containerfile
|
|
340
|
+
* @returns {Object[]} Array of objects with an image property for each unique base image
|
|
341
|
+
*/
|
|
342
|
+
export function parseContainerFile(fileContents) {
|
|
343
|
+
const buildArgs = new Map();
|
|
344
|
+
const imagesSet = new Set();
|
|
345
|
+
const buildStageNames = [];
|
|
346
|
+
for (let line of fileContents.split("\n")) {
|
|
347
|
+
line = line.trim();
|
|
348
|
+
|
|
349
|
+
if (line.startsWith("#")) {
|
|
350
|
+
continue; // skip commented out lines
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (line.startsWith("ARG")) {
|
|
354
|
+
const argStatement = line.split("ARG ")[1].split("=");
|
|
355
|
+
|
|
356
|
+
if (argStatement.length < 2) {
|
|
357
|
+
continue; // skip ARG statements without default value
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const argName = argStatement[0].trim();
|
|
361
|
+
let argValue = argStatement[1].trim().replace(/['"]+/g, "");
|
|
362
|
+
if (argValue.includes("$")) {
|
|
363
|
+
argValue = substituteBuildArgs(argValue, buildArgs);
|
|
364
|
+
}
|
|
365
|
+
buildArgs.set(argName, argValue);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (line.startsWith("FROM")) {
|
|
369
|
+
// The alias could be called AS or as
|
|
370
|
+
const fromStatement = line.split("FROM ")[1].split(/\s(as|AS)\s/);
|
|
371
|
+
|
|
372
|
+
let imageStatement = fromStatement[0].trim();
|
|
373
|
+
const buildStageName =
|
|
374
|
+
fromStatement.length > 1
|
|
375
|
+
? fromStatement[fromStatement.length - 1].trim()
|
|
376
|
+
: undefined;
|
|
377
|
+
if (buildStageNames.includes(imageStatement)) {
|
|
378
|
+
if (DEBUG_MODE) {
|
|
379
|
+
console.log(
|
|
380
|
+
`Skipping image ${imageStatement} which uses previously seen build stage name.`,
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (imageStatement.includes("$")) {
|
|
386
|
+
imageStatement = substituteBuildArgs(imageStatement, buildArgs);
|
|
387
|
+
if (imageStatement.includes("$")) {
|
|
388
|
+
if (DEBUG_MODE) {
|
|
389
|
+
console.log(
|
|
390
|
+
`Unable to substitute build arguments in '${line}' statement.`,
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
imagesSet.add(imageStatement);
|
|
397
|
+
|
|
398
|
+
if (buildStageName) {
|
|
399
|
+
buildStageNames.push(buildStageName);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return Array.from(imagesSet).map((i) => {
|
|
405
|
+
return { image: i };
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Parses a Bitbucket Pipelines YAML file and extracts all Docker image references
|
|
411
|
+
* used as build environments and pipe references (docker:// pipes are normalized).
|
|
412
|
+
*
|
|
413
|
+
* @param {string} fileContents Raw string contents of the bitbucket-pipelines.yml file
|
|
414
|
+
* @returns {Object[]} Array of objects with an image property for each referenced image or pipe
|
|
415
|
+
*/
|
|
416
|
+
export function parseBitbucketPipelinesFile(fileContents) {
|
|
417
|
+
const imgList = [];
|
|
418
|
+
|
|
419
|
+
let privateImageBlockFound = false;
|
|
420
|
+
|
|
421
|
+
for (let line of fileContents.split("\n")) {
|
|
422
|
+
line = line.trim();
|
|
423
|
+
if (line.startsWith("#")) {
|
|
424
|
+
continue; // skip commented out lines
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Assume this is a private build image object
|
|
428
|
+
if (line.startsWith("name:") && privateImageBlockFound) {
|
|
429
|
+
const imageName = line.split("name:").pop().trim();
|
|
430
|
+
|
|
431
|
+
imgList.push({
|
|
432
|
+
image: imageName,
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
privateImageBlockFound = false;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Docker image usage
|
|
439
|
+
if (line.startsWith("image:")) {
|
|
440
|
+
const imageName = line.split("image:").pop().trim();
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Assume this is a private build image object
|
|
444
|
+
* See: https://support.atlassian.com/bitbucket-cloud/docs/use-docker-images-as-build-environments/#Using-private-build-images
|
|
445
|
+
*/
|
|
446
|
+
if (imageName === "") {
|
|
447
|
+
privateImageBlockFound = true;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Assume this is a public build image
|
|
452
|
+
* See: https://support.atlassian.com/bitbucket-cloud/docs/use-docker-images-as-build-environments/#Using-public-build-images
|
|
453
|
+
*/
|
|
454
|
+
|
|
455
|
+
imgList.push({
|
|
456
|
+
image: imageName,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Pipe usage
|
|
461
|
+
if (line.startsWith("- pipe:")) {
|
|
462
|
+
let pipeName = line.split("- pipe:").pop().trim();
|
|
463
|
+
|
|
464
|
+
if (pipeName.startsWith("docker://")) {
|
|
465
|
+
pipeName = pipeName.replace("docker://", "");
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
imgList.push({
|
|
469
|
+
image: pipeName,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return imgList;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Parses container specification data such as Docker Compose files, Kubernetes
|
|
479
|
+
* manifests, Tekton tasks, Skaffold configs, or Kustomize overlays (YAML, possibly
|
|
480
|
+
* multi-document) and returns a list of image, service, and OCI spec entries.
|
|
481
|
+
*
|
|
482
|
+
* @param {string} dcData Raw YAML string contents of the container spec file
|
|
483
|
+
* @returns {Object[]} Array of objects with image, service, or ociSpec properties
|
|
484
|
+
*/
|
|
485
|
+
export function parseContainerSpecData(dcData) {
|
|
486
|
+
const pkgList = [];
|
|
487
|
+
const imgList = [];
|
|
488
|
+
if (!dcData.includes("image") && !dcData.includes("kind")) {
|
|
489
|
+
return pkgList;
|
|
490
|
+
}
|
|
491
|
+
let dcDataList = [dcData];
|
|
492
|
+
if (dcData.includes("---")) {
|
|
493
|
+
dcDataList = dcData.split("---");
|
|
494
|
+
}
|
|
495
|
+
for (const dcData of dcDataList) {
|
|
496
|
+
let yamlObj;
|
|
497
|
+
try {
|
|
498
|
+
yamlObj = _load(dcData);
|
|
499
|
+
} catch (_err) {
|
|
500
|
+
// ignore errors
|
|
501
|
+
}
|
|
502
|
+
if (!yamlObj) {
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
if (yamlObj.services) {
|
|
506
|
+
for (const serv of Object.keys(yamlObj.services)) {
|
|
507
|
+
pkgList.push({
|
|
508
|
+
service: serv,
|
|
509
|
+
});
|
|
510
|
+
const aservice = yamlObj.services[serv];
|
|
511
|
+
// Track locally built images
|
|
512
|
+
if (aservice.build) {
|
|
513
|
+
if (Object.keys(aservice.build).length && aservice.build.dockerfile) {
|
|
514
|
+
pkgList.push({
|
|
515
|
+
ociSpec: aservice.build.dockerfile,
|
|
516
|
+
});
|
|
517
|
+
} else {
|
|
518
|
+
if (aservice.build === "." || aservice.build === "./") {
|
|
519
|
+
pkgList.push({
|
|
520
|
+
ociSpec: "Dockerfile",
|
|
521
|
+
});
|
|
522
|
+
} else {
|
|
523
|
+
pkgList.push({
|
|
524
|
+
ociSpec: aservice.build,
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
} else if (aservice.image && !imgList.includes(aservice.image)) {
|
|
529
|
+
let imgFullName = aservice.image;
|
|
530
|
+
if (imgFullName.includes(":${VERSION:")) {
|
|
531
|
+
imgFullName = imgFullName
|
|
532
|
+
.replaceAll(":${VERSION:-", ":")
|
|
533
|
+
.replaceAll(":${VERSION:", ":")
|
|
534
|
+
.replaceAll("}", "");
|
|
535
|
+
}
|
|
536
|
+
pkgList.push({
|
|
537
|
+
image: imgFullName,
|
|
538
|
+
});
|
|
539
|
+
imgList.push(imgFullName);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
// Tekton tasks and kustomize have spec. Skaffold has build
|
|
544
|
+
const recurseBlock = yamlObj.spec || yamlObj.build || yamlObj.images;
|
|
545
|
+
if (recurseBlock) {
|
|
546
|
+
recurseImageNameLookup(recurseBlock, pkgList, imgList);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return pkgList;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Identifies the data flow direction of a Privado processing object based on its
|
|
554
|
+
* sinkId value: "write" sinks map to "inbound", "read" sinks to "outbound", and
|
|
555
|
+
* HTTP/gRPC sinks to "bi-directional".
|
|
556
|
+
*
|
|
557
|
+
* @param {Object} processingObj Privado processing object, expected to have a sinkId property
|
|
558
|
+
* @returns {string} Flow direction string: "inbound", "outbound", "bi-directional", or "unknown"
|
|
559
|
+
*/
|
|
560
|
+
export function identifyFlow(processingObj) {
|
|
561
|
+
let flow = "unknown";
|
|
562
|
+
if (processingObj.sinkId) {
|
|
563
|
+
const sinkId = processingObj.sinkId.toLowerCase();
|
|
564
|
+
if (sinkId.endsWith("write")) {
|
|
565
|
+
flow = "inbound";
|
|
566
|
+
} else if (sinkId.endsWith("read")) {
|
|
567
|
+
flow = "outbound";
|
|
568
|
+
} else if (sinkId.includes("http") || sinkId.includes("grpc")) {
|
|
569
|
+
flow = "bi-directional";
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return flow;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function convertProcessing(processing_list) {
|
|
576
|
+
const data_list = [];
|
|
577
|
+
for (const p of processing_list) {
|
|
578
|
+
data_list.push({
|
|
579
|
+
classification: p.sourceId || p.sinkId,
|
|
580
|
+
flow: identifyFlow(p),
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
return data_list;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Parses a Privado data flow JSON file and returns a list of service objects
|
|
588
|
+
* enriched with data classifications, endpoints, trust-boundary flag, violations,
|
|
589
|
+
* and git metadata properties extracted from the scan result.
|
|
590
|
+
*
|
|
591
|
+
* @param {string} f Path to the Privado scan result JSON file
|
|
592
|
+
* @returns {Object[]} List of service component objects suitable for a SaaSBOM
|
|
593
|
+
*/
|
|
594
|
+
export function parsePrivadoFile(f) {
|
|
595
|
+
const pData = readFileSync(f, { encoding: "utf-8" });
|
|
596
|
+
const servlist = [];
|
|
597
|
+
if (!pData) {
|
|
598
|
+
return servlist;
|
|
599
|
+
}
|
|
600
|
+
const jsonData = JSON.parse(pData);
|
|
601
|
+
const aservice = {
|
|
602
|
+
"x-trust-boundary": false,
|
|
603
|
+
properties: [],
|
|
604
|
+
data: [],
|
|
605
|
+
endpoints: [],
|
|
606
|
+
};
|
|
607
|
+
if (jsonData.repoName) {
|
|
608
|
+
aservice.name = jsonData.repoName;
|
|
609
|
+
aservice.properties = [
|
|
610
|
+
{
|
|
611
|
+
name: "internal:SrcFile",
|
|
612
|
+
value: f,
|
|
613
|
+
},
|
|
614
|
+
];
|
|
615
|
+
// Capture git metadata info
|
|
616
|
+
if (jsonData.gitMetadata) {
|
|
617
|
+
aservice.version = jsonData.gitMetadata.commitId || "";
|
|
618
|
+
aservice.properties.push({
|
|
619
|
+
name: "internal:privadoCoreVersion",
|
|
620
|
+
value: jsonData.privadoCoreVersion || "",
|
|
621
|
+
});
|
|
622
|
+
aservice.properties.push({
|
|
623
|
+
name: "internal:privadoCLIVersion",
|
|
624
|
+
value: jsonData.privadoCLIVersion || "",
|
|
625
|
+
});
|
|
626
|
+
aservice.properties.push({
|
|
627
|
+
name: "internal:localScanPath",
|
|
628
|
+
value: jsonData.localScanPath || "",
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
// Capture processing
|
|
632
|
+
if (jsonData.processing?.length) {
|
|
633
|
+
aservice.data = aservice.data.concat(
|
|
634
|
+
convertProcessing(jsonData.processing),
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
// Capture sink processing
|
|
638
|
+
if (jsonData.sinkProcessing?.length) {
|
|
639
|
+
aservice.data = aservice.data.concat(
|
|
640
|
+
convertProcessing(jsonData.sinkProcessing),
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
// Find endpoints
|
|
644
|
+
if (jsonData.collections) {
|
|
645
|
+
const endpoints = [];
|
|
646
|
+
for (const c of jsonData.collections) {
|
|
647
|
+
for (const occ of c.collections) {
|
|
648
|
+
for (const e of occ.occurrences) {
|
|
649
|
+
if (e.endPoint) {
|
|
650
|
+
endpoints.push(e.endPoint);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
aservice.endpoints = endpoints;
|
|
656
|
+
}
|
|
657
|
+
// Capture violations
|
|
658
|
+
if (jsonData.violations) {
|
|
659
|
+
for (const v of jsonData.violations) {
|
|
660
|
+
aservice.properties.push({
|
|
661
|
+
name: "internal:privado_violations",
|
|
662
|
+
value: v.policyId,
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
// If there are third party libraries detected, then there are cross boundary calls happening
|
|
667
|
+
if (jsonData.dataFlow?.third_parties?.length) {
|
|
668
|
+
aservice["x-trust-boundary"] = true;
|
|
669
|
+
}
|
|
670
|
+
servlist.push(aservice);
|
|
671
|
+
}
|
|
672
|
+
return servlist;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Parses an OpenAPI specification (JSON or YAML string) and returns a list
|
|
677
|
+
* containing a single service object with name, version, endpoints, and
|
|
678
|
+
* authentication flag derived from the spec's info, servers, paths, and
|
|
679
|
+
* securitySchemes sections.
|
|
680
|
+
*
|
|
681
|
+
* @param {string} oaData Raw JSON or YAML string contents of an OpenAPI specification
|
|
682
|
+
* @returns {Object[]} List containing a single service component object
|
|
683
|
+
*/
|
|
684
|
+
export function parseOpenapiSpecData(oaData) {
|
|
685
|
+
const servlist = [];
|
|
686
|
+
if (!oaData) {
|
|
687
|
+
return servlist;
|
|
688
|
+
}
|
|
689
|
+
try {
|
|
690
|
+
if (oaData.startsWith("openapi:")) {
|
|
691
|
+
oaData = _load(oaData);
|
|
692
|
+
} else {
|
|
693
|
+
oaData = JSON.parse(oaData);
|
|
694
|
+
}
|
|
695
|
+
} catch (_e) {
|
|
696
|
+
return servlist;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const name = oaData.info?.title
|
|
700
|
+
? oaData.info.title.replace(/ /g, "-")
|
|
701
|
+
: "default-name";
|
|
702
|
+
const version = oaData.info?.version ? oaData.info.version : "latest";
|
|
703
|
+
const aservice = {
|
|
704
|
+
"bom-ref": `urn:service:${name}:${version}`,
|
|
705
|
+
name,
|
|
706
|
+
description: oaData.description || "",
|
|
707
|
+
version,
|
|
708
|
+
};
|
|
709
|
+
let serverName = [];
|
|
710
|
+
if (oaData.servers?.length && oaData.servers[0].url) {
|
|
711
|
+
serverName = oaData.servers[0].url;
|
|
712
|
+
if (!serverName.startsWith("http") || !serverName.includes("//")) {
|
|
713
|
+
serverName = `http://${serverName}`;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if (oaData.paths) {
|
|
717
|
+
const endpoints = [];
|
|
718
|
+
for (const route of Object.keys(oaData.paths)) {
|
|
719
|
+
let sep = "";
|
|
720
|
+
if (!route.startsWith("/")) {
|
|
721
|
+
sep = "/";
|
|
722
|
+
}
|
|
723
|
+
endpoints.push(`${serverName}${sep}${route}`);
|
|
724
|
+
}
|
|
725
|
+
aservice.endpoints = endpoints;
|
|
726
|
+
}
|
|
727
|
+
let authenticated = false;
|
|
728
|
+
if (oaData.components?.securitySchemes) {
|
|
729
|
+
authenticated = true;
|
|
730
|
+
}
|
|
731
|
+
aservice.authenticated = authenticated;
|
|
732
|
+
servlist.push(aservice);
|
|
733
|
+
return servlist;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Parses Haskell Cabal freeze file content and extracts package name and version
|
|
738
|
+
* pairs from constraint lines (lines containing " ==").
|
|
739
|
+
*
|
|
740
|
+
* @param {string} cabalData Raw string contents of a Cabal freeze file
|
|
741
|
+
* @returns {Object[]} List of package objects with name and version fields
|
|
742
|
+
*/
|
|
743
|
+
export function parseCabalData(cabalData) {
|
|
744
|
+
const pkgList = [];
|
|
745
|
+
if (!cabalData) {
|
|
746
|
+
return pkgList;
|
|
747
|
+
}
|
|
748
|
+
cabalData.split("\n").forEach((l) => {
|
|
749
|
+
if (!l.includes(" ==")) {
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
l = l.replaceAll("\r", "");
|
|
753
|
+
if (l.includes(" ==")) {
|
|
754
|
+
const tmpA = l.split(" ==");
|
|
755
|
+
const name = tmpA[0]
|
|
756
|
+
.replace(/^constraints: /, "")
|
|
757
|
+
.replace(/^any\./, "")
|
|
758
|
+
.trim();
|
|
759
|
+
const version = tmpA[1].replace(",", "").trim();
|
|
760
|
+
if (name && version) {
|
|
761
|
+
pkgList.push({
|
|
762
|
+
name,
|
|
763
|
+
version,
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
return pkgList;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Parses an Elixir mix.lock file and extracts Hex package name and version pairs
|
|
773
|
+
* from lines containing ":hex".
|
|
774
|
+
*
|
|
775
|
+
* @param {string} mixData Raw string contents of a mix.lock file
|
|
776
|
+
* @returns {Object[]} List of package objects with name and version fields
|
|
777
|
+
*/
|
|
778
|
+
export function parseMixLockData(mixData) {
|
|
779
|
+
const pkgList = [];
|
|
780
|
+
if (!mixData) {
|
|
781
|
+
return pkgList;
|
|
782
|
+
}
|
|
783
|
+
mixData.split("\n").forEach((l) => {
|
|
784
|
+
if (!l.includes(":hex")) {
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
l = l.replaceAll("\r", "");
|
|
788
|
+
if (l.includes(":hex")) {
|
|
789
|
+
const tmpA = l.split(",");
|
|
790
|
+
if (tmpA.length > 3) {
|
|
791
|
+
const name = tmpA[1].replace(/^\s*:/, "").trim();
|
|
792
|
+
const version = tmpA[2].trim().replace(/"/g, "");
|
|
793
|
+
if (name && version) {
|
|
794
|
+
pkgList.push({
|
|
795
|
+
name,
|
|
796
|
+
version,
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
return pkgList;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Parses a GitHub Actions workflow YAML file and returns a list of action
|
|
807
|
+
* components for each step that uses an external action (steps with a "uses"
|
|
808
|
+
* field). Each component captures the action name, group, version/commit SHA,
|
|
809
|
+
* version pinning type, job context (runner, permissions, environment), and
|
|
810
|
+
* workflow-level metadata (triggers, concurrency, write permissions).
|
|
811
|
+
*
|
|
812
|
+
* @param {string} f Path to the GitHub Actions workflow YAML file
|
|
813
|
+
* @returns {Object[]} List of action component objects with purl, properties, and evidence
|
|
814
|
+
*/
|
|
815
|
+
export function parseGitHubWorkflowData(f) {
|
|
816
|
+
const { components } = parseWorkflowFile(f);
|
|
817
|
+
return components.filter(
|
|
818
|
+
(component) =>
|
|
819
|
+
component.scope === "required" ||
|
|
820
|
+
component.properties?.some(
|
|
821
|
+
(property) =>
|
|
822
|
+
property?.name === "cdx:github:step:usesCargo" &&
|
|
823
|
+
property?.value === "true",
|
|
824
|
+
),
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Parse Google Cloud Build YAML data and extract container image steps as packages.
|
|
830
|
+
*
|
|
831
|
+
* @param {string} cbwData Raw YAML string of a Cloud Build configuration file
|
|
832
|
+
* @returns {Object[]} Array of package objects parsed from the build steps
|
|
833
|
+
*/
|
|
834
|
+
export function parseCloudBuildData(cbwData) {
|
|
835
|
+
const pkgList = [];
|
|
836
|
+
const keys_cache = {};
|
|
837
|
+
if (!cbwData) {
|
|
838
|
+
return pkgList;
|
|
839
|
+
}
|
|
840
|
+
const yamlObj = _load(cbwData);
|
|
841
|
+
if (!yamlObj) {
|
|
842
|
+
return pkgList;
|
|
843
|
+
}
|
|
844
|
+
if (yamlObj.steps) {
|
|
845
|
+
for (const step of yamlObj.steps) {
|
|
846
|
+
if (step.name) {
|
|
847
|
+
const tmpA = step.name.split(":");
|
|
848
|
+
if (tmpA.length === 2) {
|
|
849
|
+
let group = dirname(tmpA[0]);
|
|
850
|
+
const name = basename(tmpA[0]);
|
|
851
|
+
if (group === ".") {
|
|
852
|
+
group = "";
|
|
853
|
+
}
|
|
854
|
+
const version = tmpA[1];
|
|
855
|
+
const key = `${group}-${name}-${version}`;
|
|
856
|
+
if (!keys_cache[key] && name && version) {
|
|
857
|
+
keys_cache[key] = key;
|
|
858
|
+
pkgList.push({
|
|
859
|
+
group,
|
|
860
|
+
name,
|
|
861
|
+
version,
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
return pkgList;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Parse Conan lock file data (conan.lock) and return the package list, dependency map,
|
|
873
|
+
* and parent component dependencies.
|
|
874
|
+
*
|
|
875
|
+
* Supports both the legacy `graph_lock.nodes` format (Conan 1.x) and the newer
|
|
876
|
+
* `requires` format (Conan 2.x).
|
|
877
|
+
*
|
|
878
|
+
* @param {string} conanLockData Raw JSON string of the Conan lock file
|
|
879
|
+
* @returns {{ pkgList: Object[], dependencies: Object, parentComponentDependencies: string[] }}
|
|
880
|
+
*/
|
|
881
|
+
export function parseConanLockData(conanLockData) {
|
|
882
|
+
const pkgList = [];
|
|
883
|
+
const dependencies = {};
|
|
884
|
+
const parentComponentDependencies = [];
|
|
885
|
+
|
|
886
|
+
if (!conanLockData) {
|
|
887
|
+
return { pkgList, dependencies, parentComponentDependencies };
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
const lockFile = JSON.parse(conanLockData);
|
|
891
|
+
if (!lockFile?.graph_lock?.nodes && !lockFile.requires) {
|
|
892
|
+
return { pkgList, dependencies, parentComponentDependencies };
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if (lockFile.graph_lock?.nodes) {
|
|
896
|
+
const depends = lockFile.graph_lock.nodes;
|
|
897
|
+
const nodeKeyToBomRefMap = {};
|
|
898
|
+
|
|
899
|
+
for (const nk of Object.keys(depends)) {
|
|
900
|
+
if (!depends[nk].ref) continue;
|
|
901
|
+
|
|
902
|
+
const [purl, name, version] = mapConanPkgRefToPurlStringAndNameAndVersion(
|
|
903
|
+
depends[nk].ref,
|
|
904
|
+
);
|
|
905
|
+
if (purl === null) continue;
|
|
906
|
+
|
|
907
|
+
const bomRef = decodeURIComponent(purl);
|
|
908
|
+
pkgList.push({
|
|
909
|
+
name,
|
|
910
|
+
version,
|
|
911
|
+
purl,
|
|
912
|
+
"bom-ref": bomRef,
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
nodeKeyToBomRefMap[nk] = bomRef;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
for (const nk of Object.keys(depends)) {
|
|
919
|
+
let requirementNodeKeys = [];
|
|
920
|
+
if (Array.isArray(depends[nk].requires))
|
|
921
|
+
requirementNodeKeys = requirementNodeKeys.concat(depends[nk].requires);
|
|
922
|
+
if (Array.isArray(depends[nk].build_requires))
|
|
923
|
+
requirementNodeKeys = requirementNodeKeys.concat(
|
|
924
|
+
depends[nk].build_requires,
|
|
925
|
+
);
|
|
926
|
+
|
|
927
|
+
for (const dependencyNodeKey of requirementNodeKeys) {
|
|
928
|
+
const dependencyBomRef = nodeKeyToBomRefMap[dependencyNodeKey];
|
|
929
|
+
if (!dependencyBomRef) continue;
|
|
930
|
+
|
|
931
|
+
const dependentBomRef = nodeKeyToBomRefMap[nk];
|
|
932
|
+
if (dependentBomRef) {
|
|
933
|
+
if (!(dependentBomRef in dependencies))
|
|
934
|
+
dependencies[dependentBomRef] = [];
|
|
935
|
+
if (!dependencies[dependentBomRef].includes(dependencyBomRef))
|
|
936
|
+
dependencies[dependentBomRef].push(dependencyBomRef);
|
|
937
|
+
} else if (nk === "0") {
|
|
938
|
+
// parent component for which the conan.lock was generated
|
|
939
|
+
if (!parentComponentDependencies.includes(dependencyBomRef))
|
|
940
|
+
parentComponentDependencies.push(dependencyBomRef);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
} else if (lockFile.requires) {
|
|
945
|
+
const depends = lockFile.requires;
|
|
946
|
+
for (const nk of Object.keys(depends)) {
|
|
947
|
+
depends[nk] = depends[nk].split("%").shift();
|
|
948
|
+
const [purl, name, version] = mapConanPkgRefToPurlStringAndNameAndVersion(
|
|
949
|
+
depends[nk],
|
|
950
|
+
);
|
|
951
|
+
if (purl !== null) {
|
|
952
|
+
pkgList.push({
|
|
953
|
+
name,
|
|
954
|
+
version,
|
|
955
|
+
purl,
|
|
956
|
+
"bom-ref": decodeURIComponent(purl),
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
return { pkgList, dependencies, parentComponentDependencies };
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Parse a Conan conanfile.txt and extract required and optional packages.
|
|
966
|
+
*
|
|
967
|
+
* @param {string} conanData Raw text contents of a conanfile.txt
|
|
968
|
+
* @returns {Object[]} Array of package objects with purl, name, version, and scope
|
|
969
|
+
*/
|
|
970
|
+
export function parseConanData(conanData) {
|
|
971
|
+
const pkgList = [];
|
|
972
|
+
if (!conanData) {
|
|
973
|
+
return pkgList;
|
|
974
|
+
}
|
|
975
|
+
let scope = "required";
|
|
976
|
+
conanData.split("\n").forEach((l) => {
|
|
977
|
+
l = l.replaceAll("\r", "");
|
|
978
|
+
if (l.includes("[build_requires]")) {
|
|
979
|
+
scope = "optional";
|
|
980
|
+
}
|
|
981
|
+
if (l.includes("[requires]")) {
|
|
982
|
+
scope = "required";
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// The line must start with sequence non-whitespace characters, followed by a slash,
|
|
986
|
+
// followed by at least one more non-whitespace character.
|
|
987
|
+
// Provides a heuristic for locating Conan package references inside conanfile.txt files.
|
|
988
|
+
if (l.match(/^[^\s\/]+\/\S+/)) {
|
|
989
|
+
const [purl, name, version] =
|
|
990
|
+
mapConanPkgRefToPurlStringAndNameAndVersion(l);
|
|
991
|
+
if (purl !== null) {
|
|
992
|
+
pkgList.push({
|
|
993
|
+
name,
|
|
994
|
+
version,
|
|
995
|
+
purl,
|
|
996
|
+
"bom-ref": decodeURIComponent(purl),
|
|
997
|
+
scope,
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
return pkgList;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* Construct a generic package component object for collider-managed packages.
|
|
1007
|
+
*
|
|
1008
|
+
* @param {string} name Package name
|
|
1009
|
+
* @param {Object} pkgData Locked package entry from collider.lock
|
|
1010
|
+
* @param {string} lockFile Source lock file path
|
|
1011
|
+
* @param {string} dependencyKind Whether the package is direct or transitive
|
|
1012
|
+
* @returns {Object|undefined} Package component
|
|
1013
|
+
*/
|
|
1014
|
+
function buildColliderComponent(name, pkgData, lockFile, dependencyKind) {
|
|
1015
|
+
if (!name) {
|
|
1016
|
+
return undefined;
|
|
1017
|
+
}
|
|
1018
|
+
pkgData = pkgData || {};
|
|
1019
|
+
dependencyKind = dependencyKind || "transitive";
|
|
1020
|
+
const version = pkgData?.version || "";
|
|
1021
|
+
const purl = build({
|
|
1022
|
+
type: "generic",
|
|
1023
|
+
namespace: "" || null,
|
|
1024
|
+
name: name,
|
|
1025
|
+
version: version || undefined || null,
|
|
1026
|
+
});
|
|
1027
|
+
const properties = [
|
|
1028
|
+
{
|
|
1029
|
+
name: "cdx:collider:dependencyKind",
|
|
1030
|
+
value: dependencyKind,
|
|
1031
|
+
},
|
|
1032
|
+
];
|
|
1033
|
+
if (lockFile) {
|
|
1034
|
+
properties.unshift({
|
|
1035
|
+
name: "internal:SrcFile",
|
|
1036
|
+
value: lockFile,
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
const wrapHash =
|
|
1040
|
+
typeof pkgData?.wrap_hash === "string" ? pkgData.wrap_hash.trim() : "";
|
|
1041
|
+
const wrapHashMatch = wrapHash.match(/^sha256:([0-9A-Fa-f]{64})$/);
|
|
1042
|
+
if (wrapHash) {
|
|
1043
|
+
properties.push({
|
|
1044
|
+
name: "cdx:collider:wrapHash",
|
|
1045
|
+
value: wrapHash,
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
properties.push({
|
|
1049
|
+
name: "cdx:collider:hasWrapHash",
|
|
1050
|
+
value: wrapHashMatch ? "true" : "false",
|
|
1051
|
+
});
|
|
1052
|
+
if (wrapHash && !wrapHashMatch) {
|
|
1053
|
+
properties.push({
|
|
1054
|
+
name: "cdx:collider:wrapHashInvalid",
|
|
1055
|
+
value: "true",
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
let originReference;
|
|
1059
|
+
if (typeof pkgData?.origin === "string" && pkgData.origin.trim()) {
|
|
1060
|
+
try {
|
|
1061
|
+
const originUrl = new URL(pkgData.origin.trim());
|
|
1062
|
+
const originHadSensitiveParts = Boolean(
|
|
1063
|
+
originUrl.username ||
|
|
1064
|
+
originUrl.password ||
|
|
1065
|
+
originUrl.search ||
|
|
1066
|
+
originUrl.hash,
|
|
1067
|
+
);
|
|
1068
|
+
originUrl.username = "";
|
|
1069
|
+
originUrl.password = "";
|
|
1070
|
+
originUrl.search = "";
|
|
1071
|
+
originUrl.hash = "";
|
|
1072
|
+
originReference = originUrl.toString();
|
|
1073
|
+
properties.push({
|
|
1074
|
+
name: "cdx:collider:origin",
|
|
1075
|
+
value: originReference,
|
|
1076
|
+
});
|
|
1077
|
+
properties.push({
|
|
1078
|
+
name: "cdx:collider:originScheme",
|
|
1079
|
+
value: originUrl.protocol.replace(":", ""),
|
|
1080
|
+
});
|
|
1081
|
+
if (originUrl.host) {
|
|
1082
|
+
properties.push({
|
|
1083
|
+
name: "cdx:collider:originHost",
|
|
1084
|
+
value: originUrl.host,
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
if (originHadSensitiveParts) {
|
|
1088
|
+
properties.push({
|
|
1089
|
+
name: "cdx:collider:originSanitized",
|
|
1090
|
+
value: "true",
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
} catch {
|
|
1094
|
+
thoughtLog("Ignoring invalid Collider origin URL");
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
const component = {
|
|
1098
|
+
name,
|
|
1099
|
+
version,
|
|
1100
|
+
purl,
|
|
1101
|
+
"bom-ref": decodeURIComponent(purl),
|
|
1102
|
+
properties,
|
|
1103
|
+
};
|
|
1104
|
+
if (dependencyKind === "direct") {
|
|
1105
|
+
component.scope = "required";
|
|
1106
|
+
}
|
|
1107
|
+
if (wrapHashMatch) {
|
|
1108
|
+
component.hashes = [
|
|
1109
|
+
{
|
|
1110
|
+
alg: "SHA-256",
|
|
1111
|
+
content: wrapHashMatch[1].toLowerCase(),
|
|
1112
|
+
},
|
|
1113
|
+
];
|
|
1114
|
+
}
|
|
1115
|
+
if (originReference) {
|
|
1116
|
+
component.externalReferences = [
|
|
1117
|
+
{
|
|
1118
|
+
type: "distribution",
|
|
1119
|
+
url: originReference,
|
|
1120
|
+
},
|
|
1121
|
+
];
|
|
1122
|
+
}
|
|
1123
|
+
return component;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/**
|
|
1127
|
+
* Parse Collider lock file data (collider.lock) and return the package list and
|
|
1128
|
+
* parent component dependencies.
|
|
1129
|
+
*
|
|
1130
|
+
* @param {string} colliderLockData Raw JSON string of the Collider lock file
|
|
1131
|
+
* @param {string} lockFile Source lock file path
|
|
1132
|
+
* @returns {{ pkgList: Object[], dependencies: Object, parentComponentDependencies: string[] }}
|
|
1133
|
+
*/
|
|
1134
|
+
export function parseColliderLockData(colliderLockData, lockFile) {
|
|
1135
|
+
const pkgList = [];
|
|
1136
|
+
const dependencies = {};
|
|
1137
|
+
const parentComponentDependencies = [];
|
|
1138
|
+
if (!colliderLockData) {
|
|
1139
|
+
return { pkgList, dependencies, parentComponentDependencies };
|
|
1140
|
+
}
|
|
1141
|
+
let parsedLockFile;
|
|
1142
|
+
try {
|
|
1143
|
+
parsedLockFile = JSON.parse(colliderLockData);
|
|
1144
|
+
} catch {
|
|
1145
|
+
return { pkgList, dependencies, parentComponentDependencies };
|
|
1146
|
+
}
|
|
1147
|
+
const addedBomRefs = new Set();
|
|
1148
|
+
const directDependencies = parsedLockFile.dependencies || {};
|
|
1149
|
+
const packages = parsedLockFile.packages || {};
|
|
1150
|
+
for (const [name, pkgData] of Object.entries(directDependencies)) {
|
|
1151
|
+
const component = buildColliderComponent(name, pkgData, lockFile, "direct");
|
|
1152
|
+
if (!component) {
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
if (!addedBomRefs.has(component["bom-ref"])) {
|
|
1156
|
+
pkgList.push(component);
|
|
1157
|
+
addedBomRefs.add(component["bom-ref"]);
|
|
1158
|
+
}
|
|
1159
|
+
if (!parentComponentDependencies.includes(component["bom-ref"])) {
|
|
1160
|
+
parentComponentDependencies.push(component["bom-ref"]);
|
|
1161
|
+
}
|
|
1162
|
+
if (!(component["bom-ref"] in dependencies)) {
|
|
1163
|
+
dependencies[component["bom-ref"]] = [];
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
for (const [name, pkgData] of Object.entries(packages)) {
|
|
1167
|
+
const component = buildColliderComponent(
|
|
1168
|
+
name,
|
|
1169
|
+
pkgData,
|
|
1170
|
+
lockFile,
|
|
1171
|
+
"transitive",
|
|
1172
|
+
);
|
|
1173
|
+
if (!component || addedBomRefs.has(component["bom-ref"])) {
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
pkgList.push(component);
|
|
1177
|
+
addedBomRefs.add(component["bom-ref"]);
|
|
1178
|
+
if (!(component["bom-ref"] in dependencies)) {
|
|
1179
|
+
dependencies[component["bom-ref"]] = [];
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
return { pkgList, dependencies, parentComponentDependencies };
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/**
|
|
1186
|
+
* Method to parse flake.nix files
|
|
1187
|
+
*
|
|
1188
|
+
* @param {String} flakeNixFile flake.nix file to parse
|
|
1189
|
+
* @returns {Object} Object containing package information
|
|
1190
|
+
*/
|
|
1191
|
+
export function parseFlakeNix(flakeNixFile) {
|
|
1192
|
+
const pkgList = [];
|
|
1193
|
+
const dependencies = [];
|
|
1194
|
+
|
|
1195
|
+
if (!safeExistsSync(flakeNixFile)) {
|
|
1196
|
+
return { pkgList, dependencies };
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
try {
|
|
1200
|
+
const flakeContent = readFileSync(flakeNixFile, "utf-8");
|
|
1201
|
+
|
|
1202
|
+
// Extract inputs from flake.nix using regex
|
|
1203
|
+
const inputsRegex = /inputs\s*=\s*\{[^}]*}/g;
|
|
1204
|
+
let match;
|
|
1205
|
+
while ((match = inputsRegex.exec(flakeContent)) !== null) {
|
|
1206
|
+
const inputBlock = match[0];
|
|
1207
|
+
|
|
1208
|
+
// Match different input patterns including nested inputs
|
|
1209
|
+
const inputPatterns = [
|
|
1210
|
+
/([a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*)\.url\s*=\s*"([^"]+)"/g,
|
|
1211
|
+
/([a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*)\s*=\s*\{\s*url\s*=\s*"([^"]+)"[^}]*}/gs,
|
|
1212
|
+
];
|
|
1213
|
+
|
|
1214
|
+
const addedPackages = new Set();
|
|
1215
|
+
|
|
1216
|
+
for (const pattern of inputPatterns) {
|
|
1217
|
+
let subMatch;
|
|
1218
|
+
pattern.lastIndex = 0;
|
|
1219
|
+
while ((subMatch = pattern.exec(inputBlock)) !== null) {
|
|
1220
|
+
const name = subMatch[1];
|
|
1221
|
+
const url = subMatch[2] || subMatch[3];
|
|
1222
|
+
|
|
1223
|
+
if (name && url && !addedPackages.has(name)) {
|
|
1224
|
+
addedPackages.add(name);
|
|
1225
|
+
const pkg = {
|
|
1226
|
+
name: name,
|
|
1227
|
+
version: "latest",
|
|
1228
|
+
description: `Nix flake input: ${name}`,
|
|
1229
|
+
scope: "required",
|
|
1230
|
+
properties: [
|
|
1231
|
+
{
|
|
1232
|
+
name: "internal:SrcFile",
|
|
1233
|
+
value: flakeNixFile,
|
|
1234
|
+
},
|
|
1235
|
+
{
|
|
1236
|
+
name: "cdx:nix:input_url",
|
|
1237
|
+
value: url,
|
|
1238
|
+
},
|
|
1239
|
+
],
|
|
1240
|
+
evidence: {
|
|
1241
|
+
identity: {
|
|
1242
|
+
field: "purl",
|
|
1243
|
+
confidence: 0.8,
|
|
1244
|
+
methods: [
|
|
1245
|
+
{
|
|
1246
|
+
technique: "manifest-analysis",
|
|
1247
|
+
confidence: 0.8,
|
|
1248
|
+
value: flakeNixFile,
|
|
1249
|
+
},
|
|
1250
|
+
],
|
|
1251
|
+
},
|
|
1252
|
+
},
|
|
1253
|
+
};
|
|
1254
|
+
|
|
1255
|
+
pkg.purl = nixGenericPurl(name, "latest");
|
|
1256
|
+
pkg["bom-ref"] = pkg.purl
|
|
1257
|
+
? decodeURIComponent(pkg.purl)
|
|
1258
|
+
: `library:${name}:latest`;
|
|
1259
|
+
pkg.properties.push({
|
|
1260
|
+
name: "cdx:purl:proposedType",
|
|
1261
|
+
value: "nix",
|
|
1262
|
+
});
|
|
1263
|
+
|
|
1264
|
+
pkgList.push(pkg);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
} catch (error) {
|
|
1270
|
+
console.warn(`Failed to parse ${flakeNixFile}: ${error.message}`);
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
return { pkgList, dependencies };
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
/**
|
|
1277
|
+
* Method to parse flake.lock files
|
|
1278
|
+
*
|
|
1279
|
+
* @param {String} flakeLockFile flake.lock file to parse
|
|
1280
|
+
* @returns {Object} Object containing locked dependency information
|
|
1281
|
+
*/
|
|
1282
|
+
export function parseFlakeLock(flakeLockFile) {
|
|
1283
|
+
const pkgList = [];
|
|
1284
|
+
const dependencies = [];
|
|
1285
|
+
const rootInputs = [];
|
|
1286
|
+
|
|
1287
|
+
if (!safeExistsSync(flakeLockFile)) {
|
|
1288
|
+
return { pkgList, dependencies, rootInputs };
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
try {
|
|
1292
|
+
const lockContent = readFileSync(flakeLockFile, "utf-8");
|
|
1293
|
+
const lockData = JSON.parse(lockContent);
|
|
1294
|
+
|
|
1295
|
+
if (lockData.nodes) {
|
|
1296
|
+
for (const [nodeName, nodeData] of Object.entries(lockData.nodes)) {
|
|
1297
|
+
if (nodeName === "root" || !nodeData.locked) continue;
|
|
1298
|
+
|
|
1299
|
+
const locked = nodeData.locked;
|
|
1300
|
+
|
|
1301
|
+
let version = "latest";
|
|
1302
|
+
if (locked.rev) {
|
|
1303
|
+
version = locked.rev.substring(0, 7);
|
|
1304
|
+
} else if (locked.ref) {
|
|
1305
|
+
version = locked.ref;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
const pkg = {
|
|
1309
|
+
name: nodeName,
|
|
1310
|
+
version: version,
|
|
1311
|
+
description: `Nix flake dependency: ${nodeName}`,
|
|
1312
|
+
scope: "required",
|
|
1313
|
+
properties: [
|
|
1314
|
+
{
|
|
1315
|
+
name: "internal:SrcFile",
|
|
1316
|
+
value: flakeLockFile,
|
|
1317
|
+
},
|
|
1318
|
+
],
|
|
1319
|
+
evidence: {
|
|
1320
|
+
identity: {
|
|
1321
|
+
field: "purl",
|
|
1322
|
+
confidence: 1.0,
|
|
1323
|
+
methods: [
|
|
1324
|
+
{
|
|
1325
|
+
technique: "manifest-analysis",
|
|
1326
|
+
confidence: 1.0,
|
|
1327
|
+
value: flakeLockFile,
|
|
1328
|
+
},
|
|
1329
|
+
],
|
|
1330
|
+
},
|
|
1331
|
+
},
|
|
1332
|
+
};
|
|
1333
|
+
|
|
1334
|
+
if (locked.narHash) {
|
|
1335
|
+
pkg.properties.push({
|
|
1336
|
+
name: "cdx:nix:nar_hash",
|
|
1337
|
+
value: locked.narHash,
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
if (locked.lastModified) {
|
|
1342
|
+
pkg.properties.push({
|
|
1343
|
+
name: "cdx:nix:last_modified",
|
|
1344
|
+
value: locked.lastModified.toString(),
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
if (locked.rev) {
|
|
1349
|
+
pkg.properties.push({
|
|
1350
|
+
name: "cdx:nix:revision",
|
|
1351
|
+
value: locked.rev,
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
if (locked.ref) {
|
|
1356
|
+
pkg.properties.push({
|
|
1357
|
+
name: "cdx:nix:ref",
|
|
1358
|
+
value: locked.ref,
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
const urls = nixLockedUrls(locked);
|
|
1363
|
+
if (urls.vcsUrl) {
|
|
1364
|
+
pkg.properties.push({
|
|
1365
|
+
name: "cdx:nix:vcs_url",
|
|
1366
|
+
value: urls.vcsUrl,
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
if (urls.downloadUrl) {
|
|
1370
|
+
pkg.properties.push({
|
|
1371
|
+
name: "cdx:nix:download_url",
|
|
1372
|
+
value: urls.downloadUrl,
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
pkg.properties.push({
|
|
1377
|
+
name: "cdx:purl:proposedType",
|
|
1378
|
+
value: "nix",
|
|
1379
|
+
});
|
|
1380
|
+
|
|
1381
|
+
const purl = nixGenericPurl(nodeName, version, {
|
|
1382
|
+
...(urls.vcsUrl ? { vcs_url: urls.vcsUrl } : {}),
|
|
1383
|
+
});
|
|
1384
|
+
if (purl) {
|
|
1385
|
+
pkg.purl = purl;
|
|
1386
|
+
pkg["bom-ref"] = decodeURIComponent(purl);
|
|
1387
|
+
} else {
|
|
1388
|
+
pkg["bom-ref"] = `library:${nodeName}:${version}`;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
pkgList.push(pkg);
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
// Collect the root's direct inputs as bom-refs. The root dependency edge
|
|
1395
|
+
// itself is built by createNixBom, which is the only place that knows the
|
|
1396
|
+
// parent component's real bom-ref. Emitting a synthetic root here with a
|
|
1397
|
+
// guessed ref produced a dangling edge (no component carried that ref).
|
|
1398
|
+
if (lockData.nodes?.root?.inputs) {
|
|
1399
|
+
const inputNames = Object.keys(lockData.nodes.root.inputs);
|
|
1400
|
+
for (const input of inputNames) {
|
|
1401
|
+
const ref = pkgList.find((pkg) => pkg.name === input)?.["bom-ref"];
|
|
1402
|
+
if (ref) {
|
|
1403
|
+
rootInputs.push(ref);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
} catch (error) {
|
|
1409
|
+
console.warn(`Failed to parse ${flakeLockFile}: ${error.message}`);
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
return { pkgList, dependencies, rootInputs };
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
/**
|
|
1416
|
+
* Derive version-control and download URLs for a flake lock `locked` node.
|
|
1417
|
+
*
|
|
1418
|
+
* flake.lock records a `type` discriminator (github, gitlab, sourcehut,
|
|
1419
|
+
* bitbucket, path, tarball, …). Only the git-hosted forge types carry enough
|
|
1420
|
+
* information to reconstruct a resolvable archive URL; local `path` inputs have
|
|
1421
|
+
* none and are left without URLs so downstream tooling does not chase a
|
|
1422
|
+
* non-existent artefact.
|
|
1423
|
+
*
|
|
1424
|
+
* @param {object} locked The `locked` object from a flake.lock node
|
|
1425
|
+
* @returns {{vcsUrl: string|null, downloadUrl: string|null}} derived URLs
|
|
1426
|
+
*/
|
|
1427
|
+
function nixLockedUrls(locked) {
|
|
1428
|
+
const { type, owner, repo, rev } = locked;
|
|
1429
|
+
if (!type || !owner || !repo) {
|
|
1430
|
+
return { vcsUrl: null, downloadUrl: null };
|
|
1431
|
+
}
|
|
1432
|
+
const lower = `${type}`.toLowerCase();
|
|
1433
|
+
let vcsUrl = null;
|
|
1434
|
+
let downloadUrl = null;
|
|
1435
|
+
if (lower === "github") {
|
|
1436
|
+
vcsUrl = `https://github.com/${owner}/${repo}`;
|
|
1437
|
+
if (rev) {
|
|
1438
|
+
downloadUrl = `https://github.com/${owner}/${repo}/archive/${rev}.tar.gz`;
|
|
1439
|
+
}
|
|
1440
|
+
} else if (lower === "gitlab") {
|
|
1441
|
+
vcsUrl = `https://gitlab.com/${owner}/${repo}`;
|
|
1442
|
+
if (rev) {
|
|
1443
|
+
downloadUrl = `https://gitlab.com/${owner}/${repo}/-/archive/${rev}/${repo}-${rev}.tar.gz`;
|
|
1444
|
+
}
|
|
1445
|
+
} else if (lower === "bitbucket") {
|
|
1446
|
+
vcsUrl = `https://bitbucket.org/${owner}/${repo}`;
|
|
1447
|
+
if (rev) {
|
|
1448
|
+
downloadUrl = `https://bitbucket.org/${owner}/${repo}/get/${rev}.tar.gz`;
|
|
1449
|
+
}
|
|
1450
|
+
} else if (lower === "sourcehut" || lower === "sr.ht") {
|
|
1451
|
+
vcsUrl = `https://sr.ht/${owner}/${repo}`;
|
|
1452
|
+
}
|
|
1453
|
+
return { vcsUrl, downloadUrl };
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
/**
|
|
1457
|
+
* Parse composer.json file
|
|
1458
|
+
*
|
|
1459
|
+
* @param {string} composerJsonFile composer.json file
|
|
1460
|
+
*
|
|
1461
|
+
* @returns {Object} Object with rootRequires and parent component
|
|
1462
|
+
*/
|
|
1463
|
+
export function parseComposerJson(composerJsonFile) {
|
|
1464
|
+
const moduleParent = {};
|
|
1465
|
+
const composerData = JSON.parse(
|
|
1466
|
+
readFileSync(composerJsonFile, { encoding: "utf-8" }),
|
|
1467
|
+
);
|
|
1468
|
+
const rootRequires = {
|
|
1469
|
+
...composerData.require,
|
|
1470
|
+
...composerData["require-dev"],
|
|
1471
|
+
};
|
|
1472
|
+
const pkgName = composerData.name;
|
|
1473
|
+
if (pkgName) {
|
|
1474
|
+
moduleParent.group = dirname(pkgName);
|
|
1475
|
+
if (moduleParent.group === ".") {
|
|
1476
|
+
moduleParent.group = "";
|
|
1477
|
+
}
|
|
1478
|
+
moduleParent.name = basename(pkgName);
|
|
1479
|
+
moduleParent.type = "application";
|
|
1480
|
+
moduleParent.version = composerData.version;
|
|
1481
|
+
if (composerData.description) {
|
|
1482
|
+
moduleParent.description = composerData.description;
|
|
1483
|
+
}
|
|
1484
|
+
if (composerData.license) {
|
|
1485
|
+
moduleParent.licenses = getLicenses({
|
|
1486
|
+
expression: composerData.license,
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
moduleParent.purl = build({
|
|
1490
|
+
type: "composer",
|
|
1491
|
+
namespace: moduleParent.group || null,
|
|
1492
|
+
name: moduleParent.name,
|
|
1493
|
+
version: moduleParent.version || null,
|
|
1494
|
+
});
|
|
1495
|
+
moduleParent["bom-ref"] = decodeURIComponent(moduleParent.purl);
|
|
1496
|
+
}
|
|
1497
|
+
return { rootRequires, moduleParent };
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
/**
|
|
1501
|
+
* Parse composer lock file
|
|
1502
|
+
*
|
|
1503
|
+
* @param {string} pkgLockFile composer.lock file
|
|
1504
|
+
* @param {array} rootRequires require section from composer.json
|
|
1505
|
+
*/
|
|
1506
|
+
export function parseComposerLock(pkgLockFile, rootRequires) {
|
|
1507
|
+
const pkgList = [];
|
|
1508
|
+
const dependenciesList = [];
|
|
1509
|
+
const dependenciesMap = {};
|
|
1510
|
+
const pkgNamePurlMap = {};
|
|
1511
|
+
const rootList = [];
|
|
1512
|
+
const rootRequiresMap = {};
|
|
1513
|
+
if (rootRequires) {
|
|
1514
|
+
for (const rr of Object.keys(rootRequires)) {
|
|
1515
|
+
// Skip platform requirements (php, hhvm, ext-*, lib-*) — they are never
|
|
1516
|
+
// Composer package names and must not be used to identify root packages.
|
|
1517
|
+
if (
|
|
1518
|
+
rr === "php" ||
|
|
1519
|
+
rr === "hhvm" ||
|
|
1520
|
+
rr.startsWith("ext-") ||
|
|
1521
|
+
rr.startsWith("lib-")
|
|
1522
|
+
) {
|
|
1523
|
+
continue;
|
|
1524
|
+
}
|
|
1525
|
+
rootRequiresMap[rr] = true;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
if (safeExistsSync(pkgLockFile)) {
|
|
1529
|
+
let lockData = {};
|
|
1530
|
+
try {
|
|
1531
|
+
lockData = JSON.parse(readFileSync(pkgLockFile, { encoding: "utf-8" }));
|
|
1532
|
+
} catch (_e) {
|
|
1533
|
+
console.error("Invalid composer.lock file:", pkgLockFile);
|
|
1534
|
+
return [];
|
|
1535
|
+
}
|
|
1536
|
+
if (lockData) {
|
|
1537
|
+
const packages = {};
|
|
1538
|
+
if (lockData["packages"]) {
|
|
1539
|
+
packages["required"] = lockData["packages"];
|
|
1540
|
+
}
|
|
1541
|
+
if (lockData["packages-dev"]) {
|
|
1542
|
+
packages["optional"] = lockData["packages-dev"];
|
|
1543
|
+
}
|
|
1544
|
+
// Pass 1: Collect all packages
|
|
1545
|
+
for (const compScope in packages) {
|
|
1546
|
+
for (const i in packages[compScope]) {
|
|
1547
|
+
const pkg = packages[compScope][i];
|
|
1548
|
+
// Be extra cautious. Potential fix for #236
|
|
1549
|
+
if (!pkg?.name || !pkg.version) {
|
|
1550
|
+
continue;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
let group = dirname(pkg.name);
|
|
1554
|
+
if (group === ".") {
|
|
1555
|
+
group = "";
|
|
1556
|
+
}
|
|
1557
|
+
const name = basename(pkg.name);
|
|
1558
|
+
const purl = build({
|
|
1559
|
+
type: "composer",
|
|
1560
|
+
namespace: group || null,
|
|
1561
|
+
name: name,
|
|
1562
|
+
version: pkg.version?.toString() || null,
|
|
1563
|
+
});
|
|
1564
|
+
const apkg = {
|
|
1565
|
+
group: group,
|
|
1566
|
+
name: name,
|
|
1567
|
+
purl,
|
|
1568
|
+
"bom-ref": decodeURIComponent(purl),
|
|
1569
|
+
version: pkg.version?.toString(),
|
|
1570
|
+
repository: pkg.source,
|
|
1571
|
+
license: pkg.license,
|
|
1572
|
+
description: pkg.description,
|
|
1573
|
+
scope: compScope,
|
|
1574
|
+
properties: [
|
|
1575
|
+
{
|
|
1576
|
+
name: "internal:SrcFile",
|
|
1577
|
+
value: pkgLockFile,
|
|
1578
|
+
},
|
|
1579
|
+
],
|
|
1580
|
+
evidence: {
|
|
1581
|
+
identity: {
|
|
1582
|
+
field: "purl",
|
|
1583
|
+
confidence: 1,
|
|
1584
|
+
methods: [
|
|
1585
|
+
{
|
|
1586
|
+
technique: "manifest-analysis",
|
|
1587
|
+
confidence: 1,
|
|
1588
|
+
value: pkgLockFile,
|
|
1589
|
+
},
|
|
1590
|
+
],
|
|
1591
|
+
},
|
|
1592
|
+
},
|
|
1593
|
+
};
|
|
1594
|
+
if (pkg?.dist?.url) {
|
|
1595
|
+
// Replace placeholders like %prettyVersion% with actual version
|
|
1596
|
+
let distUrl = pkg.dist.url;
|
|
1597
|
+
if (
|
|
1598
|
+
distUrl.includes("%prettyVersion%") ||
|
|
1599
|
+
distUrl.includes("%version%")
|
|
1600
|
+
) {
|
|
1601
|
+
const version = pkg.version || "unknown";
|
|
1602
|
+
distUrl = distUrl
|
|
1603
|
+
.replace(/%prettyVersion%/g, version)
|
|
1604
|
+
.replace(/%version%/g, version);
|
|
1605
|
+
}
|
|
1606
|
+
apkg.distribution = { url: distUrl };
|
|
1607
|
+
}
|
|
1608
|
+
if (pkg?.authors?.length) {
|
|
1609
|
+
apkg.authors = pkg.authors.map((a) => {
|
|
1610
|
+
return { name: a.name, email: a.email };
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
if (pkg?.keywords?.length) {
|
|
1614
|
+
apkg.tags = pkg.keywords;
|
|
1615
|
+
}
|
|
1616
|
+
if (pkg.autoload && Object.keys(pkg.autoload).length) {
|
|
1617
|
+
const namespaces = [];
|
|
1618
|
+
for (const aaload of Object.keys(pkg.autoload)) {
|
|
1619
|
+
if (aaload.startsWith("psr")) {
|
|
1620
|
+
for (const ans of Object.keys(pkg.autoload[aaload])) {
|
|
1621
|
+
namespaces.push(ans.trim());
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
if (namespaces.length) {
|
|
1626
|
+
apkg.properties.push({
|
|
1627
|
+
name: "internal:Namespaces",
|
|
1628
|
+
value: namespaces.join(", "),
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
pkgList.push(apkg);
|
|
1633
|
+
dependenciesMap[purl] = new Set();
|
|
1634
|
+
pkgNamePurlMap[pkg.name] = purl;
|
|
1635
|
+
// Add this package to the root list if needed
|
|
1636
|
+
if (rootRequiresMap[pkg.name]) {
|
|
1637
|
+
rootList.push(apkg);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
// Pass 2: Construct dependency tree
|
|
1642
|
+
for (const compScope in packages) {
|
|
1643
|
+
for (const i in packages[compScope]) {
|
|
1644
|
+
const pkg = packages[compScope][i];
|
|
1645
|
+
if (!pkg?.name || !pkg.version) {
|
|
1646
|
+
continue;
|
|
1647
|
+
}
|
|
1648
|
+
if (!pkg.require || !Object.keys(pkg.require).length) {
|
|
1649
|
+
continue;
|
|
1650
|
+
}
|
|
1651
|
+
const purl = pkgNamePurlMap[pkg.name];
|
|
1652
|
+
for (const adepName of Object.keys(pkg.require)) {
|
|
1653
|
+
if (pkgNamePurlMap[adepName]) {
|
|
1654
|
+
dependenciesMap[purl].add(pkgNamePurlMap[adepName]);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
for (const ref in dependenciesMap) {
|
|
1662
|
+
dependenciesList.push({
|
|
1663
|
+
ref: ref,
|
|
1664
|
+
dependsOn: Array.from(dependenciesMap[ref]).sort(),
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
return {
|
|
1668
|
+
pkgList,
|
|
1669
|
+
dependenciesList,
|
|
1670
|
+
rootList,
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
function convertStdoutToList(result) {
|
|
1675
|
+
if (result.status !== 0 || result.error) {
|
|
1676
|
+
return undefined;
|
|
1677
|
+
}
|
|
1678
|
+
const stdout = result.stdout;
|
|
1679
|
+
if (stdout) {
|
|
1680
|
+
const cmdOutput = Buffer.from(stdout).toString();
|
|
1681
|
+
return cmdOutput
|
|
1682
|
+
.trim()
|
|
1683
|
+
.toLowerCase()
|
|
1684
|
+
.split("\n")
|
|
1685
|
+
.filter((p) => p.length > 2 && p.includes("."))
|
|
1686
|
+
.sort();
|
|
1687
|
+
}
|
|
1688
|
+
return undefined;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
/**
|
|
1692
|
+
* Method to execute dpkg --listfiles to determine the files provided by a given package
|
|
1693
|
+
*
|
|
1694
|
+
* @param {string} pkgName deb package name
|
|
1695
|
+
* @returns
|
|
1696
|
+
*/
|
|
1697
|
+
export function executeDpkgList(pkgName) {
|
|
1698
|
+
const result = safeSpawnSync("dpkg", ["--listfiles", "--no-pager", pkgName]);
|
|
1699
|
+
return convertStdoutToList(result);
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
/**
|
|
1703
|
+
* Method to execute dnf repoquery to determine the files provided by a given package
|
|
1704
|
+
*
|
|
1705
|
+
* @param {string} pkgName deb package name
|
|
1706
|
+
* @returns
|
|
1707
|
+
*/
|
|
1708
|
+
export function executeRpmList(pkgName) {
|
|
1709
|
+
let result = safeSpawnSync("dnf", ["repoquery", "-l", pkgName]);
|
|
1710
|
+
// Fallback to rpm
|
|
1711
|
+
if (result.status !== 0 || result.error) {
|
|
1712
|
+
result = safeSpawnSync("rpm", ["-ql", pkgName]);
|
|
1713
|
+
}
|
|
1714
|
+
return convertStdoutToList(result);
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
/**
|
|
1718
|
+
* Method to execute apk -L info to determine the files provided by a given package
|
|
1719
|
+
*
|
|
1720
|
+
* @param {string} pkgName deb package name
|
|
1721
|
+
* @returns
|
|
1722
|
+
*/
|
|
1723
|
+
export function executeApkList(pkgName) {
|
|
1724
|
+
const result = safeSpawnSync("apk", ["-L", "info", pkgName]);
|
|
1725
|
+
return convertStdoutToList(result);
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
/**
|
|
1729
|
+
* Method to execute alpm -Ql to determine the files provided by a given package
|
|
1730
|
+
*
|
|
1731
|
+
* @param {string} pkgName deb package name
|
|
1732
|
+
* @returns
|
|
1733
|
+
*/
|
|
1734
|
+
export function executeAlpmList(pkgName) {
|
|
1735
|
+
const result = safeSpawnSync("pacman", ["-Ql", pkgName]);
|
|
1736
|
+
return convertStdoutToList(result);
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
/**
|
|
1740
|
+
* Method to execute equery files to determine the files provided by a given package
|
|
1741
|
+
*
|
|
1742
|
+
* @param {string} pkgName deb package name
|
|
1743
|
+
* @returns
|
|
1744
|
+
*/
|
|
1745
|
+
export function executeEqueryList(pkgName) {
|
|
1746
|
+
const result = safeSpawnSync("equery", ["files", pkgName]);
|
|
1747
|
+
return convertStdoutToList(result);
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
/**
|
|
1751
|
+
* Parse swift dependency tree output json object
|
|
1752
|
+
*
|
|
1753
|
+
* @param {Array} pkgList Package list
|
|
1754
|
+
* @param {Array} dependenciesList Dependencies
|
|
1755
|
+
* @param {string} jsonObject Swift dependencies json object
|
|
1756
|
+
* @param {string} pkgFile Package.swift file
|
|
1757
|
+
*/
|
|
1758
|
+
export function parseSwiftJsonTreeObject(
|
|
1759
|
+
pkgList,
|
|
1760
|
+
dependenciesList,
|
|
1761
|
+
jsonObject,
|
|
1762
|
+
pkgFile,
|
|
1763
|
+
) {
|
|
1764
|
+
let purl;
|
|
1765
|
+
const urlOrPath = jsonObject.url || jsonObject.path;
|
|
1766
|
+
if (jsonObject?.url?.startsWith("http")) {
|
|
1767
|
+
const version = jsonObject.version;
|
|
1768
|
+
purl = purlFromUrlString("swift", urlOrPath, version);
|
|
1769
|
+
} else {
|
|
1770
|
+
// Local swift packages have no remote URL and therefore no valid swift
|
|
1771
|
+
// namespace (cdx-purl requires host/owner). Construct the component
|
|
1772
|
+
// directly without a purl.
|
|
1773
|
+
purl = null;
|
|
1774
|
+
}
|
|
1775
|
+
const rootPkg = applyPurl(
|
|
1776
|
+
{
|
|
1777
|
+
name: purl ? purl.name : jsonObject.name,
|
|
1778
|
+
group: purl ? purl.namespace : "",
|
|
1779
|
+
version: purl ? purl.version : jsonObject.version,
|
|
1780
|
+
},
|
|
1781
|
+
purl ? decodeURIComponent(purl.toString()) : null,
|
|
1782
|
+
jsonObject.name,
|
|
1783
|
+
);
|
|
1784
|
+
const properties = [];
|
|
1785
|
+
if (jsonObject.name && jsonObject.name !== rootPkg.name) {
|
|
1786
|
+
properties.push({
|
|
1787
|
+
name: "cdx:swift:packageName",
|
|
1788
|
+
value: jsonObject.name,
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
if (jsonObject?.path?.includes("checkouts")) {
|
|
1792
|
+
const relativeCheckoutPath = relative(dirname(pkgFile), jsonObject.path);
|
|
1793
|
+
if (!relativeCheckoutPath.startsWith("..")) {
|
|
1794
|
+
properties.push({
|
|
1795
|
+
name: "cdx:swift:localCheckoutPath",
|
|
1796
|
+
value: relativeCheckoutPath,
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
if (urlOrPath) {
|
|
1801
|
+
if (urlOrPath.startsWith("http")) {
|
|
1802
|
+
rootPkg.repository = { url: urlOrPath };
|
|
1803
|
+
} else {
|
|
1804
|
+
rootPkg.type = "application";
|
|
1805
|
+
properties.push({
|
|
1806
|
+
name: "internal:SrcPath",
|
|
1807
|
+
value: urlOrPath,
|
|
1808
|
+
});
|
|
1809
|
+
if (pkgFile) {
|
|
1810
|
+
properties.push({
|
|
1811
|
+
name: "internal:SrcFile",
|
|
1812
|
+
value: pkgFile,
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
if (properties.length) {
|
|
1818
|
+
rootPkg.properties = properties;
|
|
1819
|
+
}
|
|
1820
|
+
pkgList.push(rootPkg);
|
|
1821
|
+
const depList = new Set();
|
|
1822
|
+
if (jsonObject.dependencies) {
|
|
1823
|
+
for (const dependency of jsonObject.dependencies) {
|
|
1824
|
+
const res = parseSwiftJsonTreeObject(
|
|
1825
|
+
pkgList,
|
|
1826
|
+
dependenciesList,
|
|
1827
|
+
dependency,
|
|
1828
|
+
pkgFile,
|
|
1829
|
+
);
|
|
1830
|
+
depList.add(res);
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
dependenciesList.push({
|
|
1834
|
+
ref: rootPkg["bom-ref"],
|
|
1835
|
+
dependsOn: [...depList].sort(),
|
|
1836
|
+
});
|
|
1837
|
+
return rootPkg["bom-ref"];
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
/**
|
|
1841
|
+
* Parse swift dependency tree output
|
|
1842
|
+
* @param {string} rawOutput Swift dependencies json output
|
|
1843
|
+
* @param {string} pkgFile Package.swift file
|
|
1844
|
+
*/
|
|
1845
|
+
export function parseSwiftJsonTree(rawOutput, pkgFile) {
|
|
1846
|
+
if (!rawOutput) {
|
|
1847
|
+
return {};
|
|
1848
|
+
}
|
|
1849
|
+
const pkgList = [];
|
|
1850
|
+
const dependenciesList = [];
|
|
1851
|
+
try {
|
|
1852
|
+
const jsonData = JSON.parse(rawOutput);
|
|
1853
|
+
parseSwiftJsonTreeObject(pkgList, dependenciesList, jsonData, pkgFile);
|
|
1854
|
+
} catch (e) {
|
|
1855
|
+
if (DEBUG_MODE) {
|
|
1856
|
+
console.log(e);
|
|
1857
|
+
}
|
|
1858
|
+
return {};
|
|
1859
|
+
}
|
|
1860
|
+
const rootList = pkgList.filter((p) => p.type === "application");
|
|
1861
|
+
return {
|
|
1862
|
+
rootList,
|
|
1863
|
+
pkgList,
|
|
1864
|
+
dependenciesList,
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
/**
|
|
1869
|
+
* Parse swift package resolved file
|
|
1870
|
+
* @param {string} resolvedFile Package.resolved file
|
|
1871
|
+
*/
|
|
1872
|
+
export function parseSwiftResolved(resolvedFile) {
|
|
1873
|
+
const pkgList = [];
|
|
1874
|
+
if (safeExistsSync(resolvedFile)) {
|
|
1875
|
+
try {
|
|
1876
|
+
const pkgData = JSON.parse(
|
|
1877
|
+
readFileSync(resolvedFile, { encoding: "utf-8" }),
|
|
1878
|
+
);
|
|
1879
|
+
let resolvedList = [];
|
|
1880
|
+
if (pkgData.pins) {
|
|
1881
|
+
resolvedList = pkgData.pins;
|
|
1882
|
+
} else if (pkgData.object?.pins) {
|
|
1883
|
+
resolvedList = pkgData.object.pins;
|
|
1884
|
+
}
|
|
1885
|
+
for (const adep of resolvedList) {
|
|
1886
|
+
const locationOrUrl = adep.location || adep.repositoryURL;
|
|
1887
|
+
const version = adep.state.version || adep.state.revision;
|
|
1888
|
+
const purl = purlFromUrlString("swift", locationOrUrl, version);
|
|
1889
|
+
const purlString = decodeURIComponent(purl.toString());
|
|
1890
|
+
const rootPkg = {
|
|
1891
|
+
name: purl.name,
|
|
1892
|
+
group: purl.namespace,
|
|
1893
|
+
version: purl.version,
|
|
1894
|
+
purl: purlString,
|
|
1895
|
+
"bom-ref": purlString,
|
|
1896
|
+
properties: [
|
|
1897
|
+
{
|
|
1898
|
+
name: "internal:SrcFile",
|
|
1899
|
+
value: resolvedFile,
|
|
1900
|
+
},
|
|
1901
|
+
],
|
|
1902
|
+
evidence: {
|
|
1903
|
+
identity: {
|
|
1904
|
+
field: "purl",
|
|
1905
|
+
confidence: 1,
|
|
1906
|
+
methods: [
|
|
1907
|
+
{
|
|
1908
|
+
technique: "manifest-analysis",
|
|
1909
|
+
confidence: 1,
|
|
1910
|
+
value: resolvedFile,
|
|
1911
|
+
},
|
|
1912
|
+
],
|
|
1913
|
+
},
|
|
1914
|
+
},
|
|
1915
|
+
};
|
|
1916
|
+
if (locationOrUrl) {
|
|
1917
|
+
rootPkg.repository = { url: locationOrUrl };
|
|
1918
|
+
}
|
|
1919
|
+
pkgList.push(rootPkg);
|
|
1920
|
+
}
|
|
1921
|
+
} catch (_err) {
|
|
1922
|
+
// continue regardless of error
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
return pkgList;
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
/**
|
|
1929
|
+
* Parse a CMake-generated dot/graphviz file and extract components and their dependency
|
|
1930
|
+
* relationships.
|
|
1931
|
+
*
|
|
1932
|
+
* The first `digraph` entry becomes the parent component. Subsequent `node` entries
|
|
1933
|
+
* with a `label` attribute are treated as direct dependencies, while commented
|
|
1934
|
+
* `node -> node` relationships are used to construct the dependency graph.
|
|
1935
|
+
*
|
|
1936
|
+
* @param {string} dotFile Path to the CMake-generated dot file
|
|
1937
|
+
* @param {string} pkgType PackageURL type to assign to extracted packages (e.g. `"generic"`)
|
|
1938
|
+
* @param {Object} options CLI options; may contain `projectGroup`, `projectName`, and `projectVersion`
|
|
1939
|
+
* @returns {{ parentComponent: Object, pkgList: Object[], dependenciesList: Object[] }}
|
|
1940
|
+
*/
|
|
1941
|
+
export function parseCmakeDotFile(dotFile, pkgType, options = {}) {
|
|
1942
|
+
const dotGraphData = readFileSync(dotFile, { encoding: "utf-8" });
|
|
1943
|
+
const pkgList = [];
|
|
1944
|
+
const dependenciesMap = {};
|
|
1945
|
+
const pkgBomRefMap = {};
|
|
1946
|
+
let parentComponent = {};
|
|
1947
|
+
dotGraphData.split("\n").forEach((l) => {
|
|
1948
|
+
l = l.replaceAll("\r", "").trim();
|
|
1949
|
+
if (l === "\n" || l.startsWith("#")) {
|
|
1950
|
+
return;
|
|
1951
|
+
}
|
|
1952
|
+
let name = "";
|
|
1953
|
+
const group = "";
|
|
1954
|
+
const version = "";
|
|
1955
|
+
let path;
|
|
1956
|
+
if (l.startsWith("digraph")) {
|
|
1957
|
+
const tmpA = l.split(" ");
|
|
1958
|
+
if (tmpA && tmpA.length > 1) {
|
|
1959
|
+
name = tmpA[1].replace(/"/g, "");
|
|
1960
|
+
}
|
|
1961
|
+
} else if (l.startsWith('"node')) {
|
|
1962
|
+
// Direct dependencies are represented as nodes
|
|
1963
|
+
if (l.includes("label =")) {
|
|
1964
|
+
const tmpA = l.split('label = "');
|
|
1965
|
+
if (tmpA && tmpA.length > 1) {
|
|
1966
|
+
name = tmpA[1].split('"')[0];
|
|
1967
|
+
}
|
|
1968
|
+
if (name.includes("\\n")) {
|
|
1969
|
+
name = name.split("\\n")[0];
|
|
1970
|
+
} else if (name.includes(_sep)) {
|
|
1971
|
+
path = name;
|
|
1972
|
+
name = basename(name);
|
|
1973
|
+
}
|
|
1974
|
+
} else if (l.includes("// ")) {
|
|
1975
|
+
// Indirect dependencies are represented with comments
|
|
1976
|
+
const tmpA = l.split("// ");
|
|
1977
|
+
if (tmpA?.length) {
|
|
1978
|
+
const relationship = tmpA[1];
|
|
1979
|
+
if (relationship.includes("->")) {
|
|
1980
|
+
const tmpB = relationship.split(" -> ");
|
|
1981
|
+
if (tmpB && tmpB.length === 2) {
|
|
1982
|
+
if (tmpB[0].includes(_sep)) {
|
|
1983
|
+
tmpB[0] = basename(tmpB[0]);
|
|
1984
|
+
}
|
|
1985
|
+
if (tmpB[1].includes(_sep)) {
|
|
1986
|
+
tmpB[1] = basename(tmpB[1]);
|
|
1987
|
+
}
|
|
1988
|
+
const ref = pkgBomRefMap[tmpB[0]];
|
|
1989
|
+
const depends = pkgBomRefMap[tmpB[1]];
|
|
1990
|
+
if (ref && depends) {
|
|
1991
|
+
if (!dependenciesMap[ref]) {
|
|
1992
|
+
dependenciesMap[ref] = new Set();
|
|
1993
|
+
}
|
|
1994
|
+
dependenciesMap[ref].add(depends);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
if (!Object.keys(parentComponent).length) {
|
|
2002
|
+
parentComponent = {
|
|
2003
|
+
group: options.projectGroup || "",
|
|
2004
|
+
name: "project-name" in options ? options.projectName : name,
|
|
2005
|
+
version: options.projectVersion || "",
|
|
2006
|
+
type: "application",
|
|
2007
|
+
};
|
|
2008
|
+
parentComponent["purl"] = build({
|
|
2009
|
+
type: pkgType,
|
|
2010
|
+
namespace: parentComponent.group || null,
|
|
2011
|
+
name: parentComponent.name,
|
|
2012
|
+
version: parentComponent.version || null,
|
|
2013
|
+
subpath: path || null,
|
|
2014
|
+
});
|
|
2015
|
+
parentComponent["bom-ref"] = decodeURIComponent(parentComponent["purl"]);
|
|
2016
|
+
} else if (name) {
|
|
2017
|
+
const apkg = {
|
|
2018
|
+
name: name,
|
|
2019
|
+
type: pkgType,
|
|
2020
|
+
purl: build({
|
|
2021
|
+
type: pkgType,
|
|
2022
|
+
namespace: group || null,
|
|
2023
|
+
name: name,
|
|
2024
|
+
version: version || null,
|
|
2025
|
+
subpath: path ? path.replace(/^\/+/, "") : null,
|
|
2026
|
+
}),
|
|
2027
|
+
};
|
|
2028
|
+
apkg["bom-ref"] = decodeURIComponent(apkg["purl"]);
|
|
2029
|
+
pkgList.push(apkg);
|
|
2030
|
+
pkgBomRefMap[name] = apkg["bom-ref"];
|
|
2031
|
+
}
|
|
2032
|
+
});
|
|
2033
|
+
const dependenciesList = [];
|
|
2034
|
+
for (const pk of Object.keys(dependenciesMap)) {
|
|
2035
|
+
const dependsOn = Array.from(dependenciesMap[pk] || []).sort();
|
|
2036
|
+
dependenciesList.push({
|
|
2037
|
+
ref: pk,
|
|
2038
|
+
dependsOn,
|
|
2039
|
+
});
|
|
2040
|
+
}
|
|
2041
|
+
return {
|
|
2042
|
+
parentComponent,
|
|
2043
|
+
pkgList,
|
|
2044
|
+
dependenciesList,
|
|
2045
|
+
};
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
/**
|
|
2049
|
+
* Parse a CMake-like build file (CMakeLists.txt, meson.build, etc.) and extract the
|
|
2050
|
+
* parent component and list of dependency packages.
|
|
2051
|
+
*
|
|
2052
|
+
* Handles `set`, `project`, `find_package`, `find_library`, `find_dependency`,
|
|
2053
|
+
* `find_file`, `FetchContent_MakeAvailable`, and `dependency()` directives.
|
|
2054
|
+
* Uses the MesonWrapDB to improve name resolution confidence.
|
|
2055
|
+
*
|
|
2056
|
+
* @param {string} cmakeListFile Path to the CMake-like build file
|
|
2057
|
+
* @param {string} pkgType PackageURL type to assign to extracted packages (e.g. `"generic"`)
|
|
2058
|
+
* @param {Object} options CLI options; may contain `projectGroup`, `projectName`, and `projectVersion`
|
|
2059
|
+
* @returns {{ parentComponent: Object, pkgList: Object[] }}
|
|
2060
|
+
*/
|
|
2061
|
+
export function parseCmakeLikeFile(cmakeListFile, pkgType, options = {}) {
|
|
2062
|
+
let cmakeListData = readFileSync(cmakeListFile, { encoding: "utf-8" });
|
|
2063
|
+
const pkgList = [];
|
|
2064
|
+
const pkgAddedMap = {};
|
|
2065
|
+
const versionSpecifiersMap = {};
|
|
2066
|
+
const versionsMap = {};
|
|
2067
|
+
let parentComponent = {};
|
|
2068
|
+
const templateValues = {};
|
|
2069
|
+
cmakeListData = cmakeListData
|
|
2070
|
+
.replace(/^ {2}/g, "")
|
|
2071
|
+
.replace(/\(\r\n/g, "(")
|
|
2072
|
+
.replace(/\(\n/g, "(")
|
|
2073
|
+
.replace(/,\r\n/g, ",")
|
|
2074
|
+
.replace(/,\n/g, ",");
|
|
2075
|
+
cmakeListData.split("\n").forEach((l) => {
|
|
2076
|
+
l = l.replaceAll("\r", "").trim();
|
|
2077
|
+
if (l === "\n" || l.startsWith("#")) {
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
const group = "";
|
|
2081
|
+
const path = undefined;
|
|
2082
|
+
const name_list = [];
|
|
2083
|
+
if (l.startsWith("set")) {
|
|
2084
|
+
const tmpA = l.replace("set(", "").replace(")", "").trim().split(" ");
|
|
2085
|
+
if (tmpA && tmpA.length === 2) {
|
|
2086
|
+
templateValues[tmpA[0]] = tmpA[1];
|
|
2087
|
+
}
|
|
2088
|
+
} else if (
|
|
2089
|
+
l.startsWith("project") &&
|
|
2090
|
+
!Object.keys(parentComponent).length
|
|
2091
|
+
) {
|
|
2092
|
+
if (l.includes("${")) {
|
|
2093
|
+
for (const tmplKey of Object.keys(templateValues)) {
|
|
2094
|
+
l = l.replace(`\${${tmplKey}}`, templateValues[tmplKey] || "");
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
const tmpA = l.replace("project (", "project(").split("project(");
|
|
2098
|
+
if (tmpA && tmpA.length > 1) {
|
|
2099
|
+
const tmpB = (tmpA[1] || "")
|
|
2100
|
+
.trim()
|
|
2101
|
+
.replace(/["']/g, "")
|
|
2102
|
+
.replace(/ /g, ",")
|
|
2103
|
+
.split(")")[0]
|
|
2104
|
+
.split(",")
|
|
2105
|
+
.filter((v) => v.length > 1);
|
|
2106
|
+
const parentName =
|
|
2107
|
+
tmpB.length > 0 ? tmpB[0].replace(":", "").trim() : "";
|
|
2108
|
+
let parentVersion;
|
|
2109
|
+
// In case of meson.build we can find the version number after the word version
|
|
2110
|
+
// thanks to our replaces and splits
|
|
2111
|
+
const versionIndex = tmpB.findIndex(
|
|
2112
|
+
(v) => v?.toLowerCase() === "version",
|
|
2113
|
+
);
|
|
2114
|
+
if (versionIndex > -1 && tmpB.length > versionIndex) {
|
|
2115
|
+
parentVersion = tmpB[versionIndex + 1];
|
|
2116
|
+
}
|
|
2117
|
+
// A `${VAR}` that no `set()` in this file defines stays literal. It is
|
|
2118
|
+
// not a version, and percent-encoding it into a purl publishes the
|
|
2119
|
+
// variable name as the version, so the field is left empty instead.
|
|
2120
|
+
// CMakeCache.txt carries the evaluated value when a build tree exists.
|
|
2121
|
+
if (parentVersion?.includes("$")) {
|
|
2122
|
+
parentVersion = undefined;
|
|
2123
|
+
}
|
|
2124
|
+
if (parentName?.length && !parentName.includes("$")) {
|
|
2125
|
+
parentComponent = {
|
|
2126
|
+
group: options.projectGroup || "",
|
|
2127
|
+
name: parentName,
|
|
2128
|
+
version: parentVersion || options.projectVersion || "",
|
|
2129
|
+
type: "application",
|
|
2130
|
+
};
|
|
2131
|
+
const safePath = path ? path.replace(/^\/+/, "") : null;
|
|
2132
|
+
parentComponent["purl"] = build({
|
|
2133
|
+
type: pkgType,
|
|
2134
|
+
namespace: parentComponent.group || null,
|
|
2135
|
+
name: parentComponent.name,
|
|
2136
|
+
version: parentComponent.version || null,
|
|
2137
|
+
subpath: safePath,
|
|
2138
|
+
});
|
|
2139
|
+
parentComponent["bom-ref"] = decodeURIComponent(
|
|
2140
|
+
parentComponent["purl"],
|
|
2141
|
+
);
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
} else if (l.startsWith("find_")) {
|
|
2145
|
+
let tmpA = [];
|
|
2146
|
+
for (const fm of [
|
|
2147
|
+
"find_package(",
|
|
2148
|
+
"find_library(",
|
|
2149
|
+
"find_dependency(",
|
|
2150
|
+
"find_file(",
|
|
2151
|
+
"FetchContent_MakeAvailable(",
|
|
2152
|
+
]) {
|
|
2153
|
+
if (l.startsWith(fm)) {
|
|
2154
|
+
tmpA = l.split(fm);
|
|
2155
|
+
break;
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
if (tmpA && tmpA.length > 1) {
|
|
2159
|
+
let tmpB = tmpA[1].split(")")[0].split(" ");
|
|
2160
|
+
tmpB = tmpB.filter(
|
|
2161
|
+
(v) =>
|
|
2162
|
+
![
|
|
2163
|
+
"REQUIRED",
|
|
2164
|
+
"COMPONENTS",
|
|
2165
|
+
"QUIET",
|
|
2166
|
+
"NAMES",
|
|
2167
|
+
"PATHS",
|
|
2168
|
+
"ENV",
|
|
2169
|
+
"NO_MODULE",
|
|
2170
|
+
"NO_DEFAULT_PATH",
|
|
2171
|
+
].includes(v) &&
|
|
2172
|
+
!v.includes("$") &&
|
|
2173
|
+
!v.startsWith("CMAKE_") &&
|
|
2174
|
+
v.length,
|
|
2175
|
+
);
|
|
2176
|
+
// find_package(Catch2)
|
|
2177
|
+
// find_package(GTest REQUIRED)
|
|
2178
|
+
// find_package(Boost 1.79 COMPONENTS date_time)
|
|
2179
|
+
// find_library(PTHREADPOOL_LIB pthreadpool REQUIRED)
|
|
2180
|
+
// find_library and find_file name the cache variable that receives the
|
|
2181
|
+
// result in their first argument, so that argument is a destination
|
|
2182
|
+
// rather than a package. Every other command in this list opens with a
|
|
2183
|
+
// package name, including names such as `ZLIB` that read like a
|
|
2184
|
+
// variable.
|
|
2185
|
+
if (l.startsWith("find_library") || l.startsWith("find_file")) {
|
|
2186
|
+
tmpB = tmpB.slice(1);
|
|
2187
|
+
}
|
|
2188
|
+
if (tmpB.length) {
|
|
2189
|
+
const working_name = tmpB[0];
|
|
2190
|
+
name_list.push(working_name);
|
|
2191
|
+
if (l.startsWith("find_package") && tmpB.length > 1) {
|
|
2192
|
+
if (
|
|
2193
|
+
/^\d+/.test(tmpB[1]) &&
|
|
2194
|
+
!tmpB[1].includes("${") &&
|
|
2195
|
+
!tmpB[1].startsWith("@")
|
|
2196
|
+
) {
|
|
2197
|
+
versionsMap[working_name] = tmpB[1];
|
|
2198
|
+
}
|
|
2199
|
+
} else {
|
|
2200
|
+
for (const n of tmpB) {
|
|
2201
|
+
if (n.match(/^\d/)) {
|
|
2202
|
+
continue;
|
|
2203
|
+
}
|
|
2204
|
+
if (n.includes(_sep)) {
|
|
2205
|
+
if (
|
|
2206
|
+
n.includes(".so") ||
|
|
2207
|
+
n.includes(".a") ||
|
|
2208
|
+
n.includes(".dll")
|
|
2209
|
+
) {
|
|
2210
|
+
name_list.push(basename(n));
|
|
2211
|
+
}
|
|
2212
|
+
} else {
|
|
2213
|
+
name_list.push(n);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
} else if (l.includes("dependency(")) {
|
|
2220
|
+
if (!l.includes("_dependency") && !l.includes(".dependency")) {
|
|
2221
|
+
const depMatch = l.match(/dependency\(\s*['"]?([^'",)\s]+)['"]?/);
|
|
2222
|
+
const depName = depMatch?.[1]?.trim();
|
|
2223
|
+
if (depName) {
|
|
2224
|
+
name_list.push(depName);
|
|
2225
|
+
const versionMatch = l.match(/version\s*:\s*['"]?([^'",)\s]+)['"]?/);
|
|
2226
|
+
const depVersion = versionMatch?.[1]?.trim();
|
|
2227
|
+
if (depVersion) {
|
|
2228
|
+
if (depVersion.includes(">") || depVersion.includes("<")) {
|
|
2229
|
+
// We have a version specifier
|
|
2230
|
+
versionSpecifiersMap[depName] = depVersion;
|
|
2231
|
+
} else if (
|
|
2232
|
+
/^\d+/.test(depVersion) &&
|
|
2233
|
+
!depVersion.includes("${") &&
|
|
2234
|
+
!depVersion.startsWith("@")
|
|
2235
|
+
) {
|
|
2236
|
+
// We have a valid version
|
|
2237
|
+
versionsMap[depName] = depVersion;
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
for (let n of name_list) {
|
|
2244
|
+
const props = [];
|
|
2245
|
+
let confidence = 0;
|
|
2246
|
+
if (
|
|
2247
|
+
n &&
|
|
2248
|
+
n.length > 1 &&
|
|
2249
|
+
!pkgAddedMap[n] &&
|
|
2250
|
+
!n.startsWith(_sep) &&
|
|
2251
|
+
!n.startsWith("@")
|
|
2252
|
+
) {
|
|
2253
|
+
n = n.replace(/"/g, "");
|
|
2254
|
+
const scrapedName = n;
|
|
2255
|
+
// Can this be replaced with a db lookup?
|
|
2256
|
+
for (const wrapkey of Object.keys(mesonWrapDB)) {
|
|
2257
|
+
const awrap = mesonWrapDB[wrapkey];
|
|
2258
|
+
if (
|
|
2259
|
+
awrap.PkgProvides.includes(n) ||
|
|
2260
|
+
awrap.PkgProvides.includes(n.toLowerCase())
|
|
2261
|
+
) {
|
|
2262
|
+
// Use the new name
|
|
2263
|
+
n = wrapkey;
|
|
2264
|
+
for (const eprop of Object.keys(awrap)) {
|
|
2265
|
+
props.push({
|
|
2266
|
+
name: eprop,
|
|
2267
|
+
value: Array.isArray(awrap[eprop])
|
|
2268
|
+
? awrap[eprop].join("|")
|
|
2269
|
+
: awrap[eprop],
|
|
2270
|
+
});
|
|
2271
|
+
}
|
|
2272
|
+
// Our confidence has improved from 0 since there is a matching wrap so we know the correct name
|
|
2273
|
+
// and url. We lack the version details.
|
|
2274
|
+
confidence = 0.5;
|
|
2275
|
+
break;
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
// A wrap match renames the package, so the same directive reached under
|
|
2279
|
+
// its scraped name and under its wrap name describe one component. Both
|
|
2280
|
+
// spellings are remembered to keep the second arrival out of the list.
|
|
2281
|
+
if (pkgAddedMap[n]) {
|
|
2282
|
+
pkgAddedMap[scrapedName] = true;
|
|
2283
|
+
continue;
|
|
2284
|
+
}
|
|
2285
|
+
// The version maps are keyed by the name the directive spelled, which a
|
|
2286
|
+
// wrap match has since replaced, so the scraped name is the fallback.
|
|
2287
|
+
const versionSpecifier =
|
|
2288
|
+
versionSpecifiersMap[n] || versionSpecifiersMap[scrapedName];
|
|
2289
|
+
const version = versionsMap[n] || versionsMap[scrapedName] || "";
|
|
2290
|
+
if (versionSpecifier) {
|
|
2291
|
+
props.push({
|
|
2292
|
+
name: "cdx:build:versionSpecifiers",
|
|
2293
|
+
value: versionSpecifier,
|
|
2294
|
+
});
|
|
2295
|
+
}
|
|
2296
|
+
const apkg = {
|
|
2297
|
+
name: n,
|
|
2298
|
+
version,
|
|
2299
|
+
type: pkgType,
|
|
2300
|
+
purl: build({
|
|
2301
|
+
type: pkgType,
|
|
2302
|
+
namespace: group || null,
|
|
2303
|
+
name: n,
|
|
2304
|
+
version: version || null,
|
|
2305
|
+
subpath: path || null,
|
|
2306
|
+
}),
|
|
2307
|
+
evidence: {
|
|
2308
|
+
identity: {
|
|
2309
|
+
field: "purl",
|
|
2310
|
+
confidence,
|
|
2311
|
+
methods: [
|
|
2312
|
+
{
|
|
2313
|
+
technique: "source-code-analysis",
|
|
2314
|
+
confidence: 0.5,
|
|
2315
|
+
value: `Filename ${cmakeListFile}`,
|
|
2316
|
+
},
|
|
2317
|
+
],
|
|
2318
|
+
},
|
|
2319
|
+
},
|
|
2320
|
+
properties: props,
|
|
2321
|
+
};
|
|
2322
|
+
apkg["bom-ref"] = decodeURIComponent(apkg["purl"]);
|
|
2323
|
+
pkgList.push(apkg);
|
|
2324
|
+
pkgAddedMap[n] = true;
|
|
2325
|
+
pkgAddedMap[scrapedName] = true;
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
});
|
|
2329
|
+
return {
|
|
2330
|
+
parentComponent,
|
|
2331
|
+
pkgList,
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
/**
|
|
2336
|
+
* Parse an atom C/C++ usage-slice report into per-file package usage data.
|
|
2337
|
+
*
|
|
2338
|
+
* Walks the `objectSlices` of an atom report, collecting resolved method names
|
|
2339
|
+
* and full names per source file while skipping include globals and compiler
|
|
2340
|
+
* internals.
|
|
2341
|
+
*
|
|
2342
|
+
* @param {object} sliceData Parsed atom usage-slice report.
|
|
2343
|
+
* @returns {Object<string, Set<string>>|undefined} Map of file name to the set
|
|
2344
|
+
* of used symbols, or undefined when sliceData is empty.
|
|
2345
|
+
*/
|
|
2346
|
+
export function parseCUsageSlice(sliceData) {
|
|
2347
|
+
if (!sliceData) {
|
|
2348
|
+
return undefined;
|
|
2349
|
+
}
|
|
2350
|
+
const usageData = {};
|
|
2351
|
+
try {
|
|
2352
|
+
const objectSlices = sliceData.objectSlices || [];
|
|
2353
|
+
for (const slice of objectSlices) {
|
|
2354
|
+
if (
|
|
2355
|
+
(!slice.fileName && !slice.code.startsWith("#include")) ||
|
|
2356
|
+
slice.fileName.startsWith("<includes") ||
|
|
2357
|
+
slice.fileName.startsWith("<global") ||
|
|
2358
|
+
slice.fileName.includes("__")
|
|
2359
|
+
) {
|
|
2360
|
+
continue;
|
|
2361
|
+
}
|
|
2362
|
+
const slFileName = slice.fileName;
|
|
2363
|
+
const allLines = usageData[slFileName] || new Set();
|
|
2364
|
+
if (slice.fullName && slice.fullName.length > 3) {
|
|
2365
|
+
if (slice.code?.startsWith("#include")) {
|
|
2366
|
+
usageData[slice.fullName] = new Set();
|
|
2367
|
+
} else {
|
|
2368
|
+
allLines.add(slice.fullName);
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
for (const ausage of slice.usages) {
|
|
2372
|
+
let calls = ausage?.invokedCalls || [];
|
|
2373
|
+
calls = calls.concat(ausage?.argToCalls || []);
|
|
2374
|
+
for (const acall of calls) {
|
|
2375
|
+
if (!acall.resolvedMethod.includes("->")) {
|
|
2376
|
+
allLines.add(acall.resolvedMethod);
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
if (Array.from(allLines).length) {
|
|
2381
|
+
usageData[slFileName] = allLines;
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
} catch (_err) {
|
|
2385
|
+
// ignore
|
|
2386
|
+
}
|
|
2387
|
+
return usageData;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
/**
|
|
2391
|
+
* Function to parse the .d make files
|
|
2392
|
+
*
|
|
2393
|
+
* @param {String} dfile .d file path
|
|
2394
|
+
*
|
|
2395
|
+
* @returns {Object} pkgFilesMap Object with package name and list of files
|
|
2396
|
+
*/
|
|
2397
|
+
export function parseMakeDFile(dfile) {
|
|
2398
|
+
const pkgFilesMap = {};
|
|
2399
|
+
const dData = readFileSync(dfile, { encoding: "utf-8" });
|
|
2400
|
+
const pkgName = basename(dfile).split("-").shift();
|
|
2401
|
+
const filesList = new Set();
|
|
2402
|
+
dData.split("\n").forEach((l) => {
|
|
2403
|
+
l = l.replaceAll("\r", "");
|
|
2404
|
+
if (!l.endsWith(".rs:")) {
|
|
2405
|
+
return;
|
|
2406
|
+
}
|
|
2407
|
+
const fileName = `.cargo/${l.split(".cargo/").pop()}`.replace(
|
|
2408
|
+
".rs:",
|
|
2409
|
+
".rs",
|
|
2410
|
+
);
|
|
2411
|
+
filesList.add(fileName);
|
|
2412
|
+
});
|
|
2413
|
+
pkgFilesMap[pkgName] = Array.from(filesList);
|
|
2414
|
+
return pkgFilesMap;
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
/**
|
|
2418
|
+
* Parse the contents of a 'Podfile.lock'
|
|
2419
|
+
*
|
|
2420
|
+
* @param {Object} podfileLock The content of the podfile.lock as an Object
|
|
2421
|
+
* @param {String} projectPath The path to the project root
|
|
2422
|
+
* @returns {Map} Map of all dependencies with their direct dependencies
|
|
2423
|
+
*/
|
|
2424
|
+
export async function parsePodfileLock(podfileLock, projectPath) {
|
|
2425
|
+
const dependencies = new Map();
|
|
2426
|
+
for (const pod of podfileLock["PODS"]) {
|
|
2427
|
+
const dependency = {};
|
|
2428
|
+
if (pod.constructor === Object) {
|
|
2429
|
+
for (const key in pod) {
|
|
2430
|
+
dependency.metadata = parseCocoaDependency(key);
|
|
2431
|
+
const subDependencies = new Set();
|
|
2432
|
+
for (const subPod of pod[key]) {
|
|
2433
|
+
subDependencies.add(parseCocoaDependency(subPod, false));
|
|
2434
|
+
}
|
|
2435
|
+
dependency.dependencies = Array.from(subDependencies);
|
|
2436
|
+
}
|
|
2437
|
+
} else {
|
|
2438
|
+
dependency.metadata = parseCocoaDependency(pod);
|
|
2439
|
+
}
|
|
2440
|
+
const podName = dependency.metadata.name.includes("/")
|
|
2441
|
+
? dependency.metadata.name.substring(
|
|
2442
|
+
0,
|
|
2443
|
+
dependency.metadata.name.indexOf("/"),
|
|
2444
|
+
)
|
|
2445
|
+
: dependency.metadata.name;
|
|
2446
|
+
if (podfileLock["EXTERNAL SOURCES"]?.[podName]) {
|
|
2447
|
+
const externalPod = podfileLock["EXTERNAL SOURCES"][podName];
|
|
2448
|
+
if (externalPod[":git"]) {
|
|
2449
|
+
let projectRepo = externalPod[":git"];
|
|
2450
|
+
if (isGitHubUrl(projectRepo)) {
|
|
2451
|
+
projectRepo = projectRepo.replace(
|
|
2452
|
+
"github.com",
|
|
2453
|
+
"raw.githubusercontent.com",
|
|
2454
|
+
);
|
|
2455
|
+
}
|
|
2456
|
+
if (projectRepo.endsWith(".git")) {
|
|
2457
|
+
projectRepo = projectRepo.substring(0, projectRepo.length - 4);
|
|
2458
|
+
}
|
|
2459
|
+
const projectRepoBranchOrTag = externalPod[":tag"]
|
|
2460
|
+
? `tags/${externalPod[":tag"]}`
|
|
2461
|
+
: `heads/${externalPod[":branch"] ? externalPod[":branch"] : "<DEFAULT>"}`;
|
|
2462
|
+
dependency.metadata.properties = [
|
|
2463
|
+
{
|
|
2464
|
+
name: "cdx:pods:podspecLocation",
|
|
2465
|
+
value: `${projectRepo}/refs/${projectRepoBranchOrTag}/${podName}.podspec`,
|
|
2466
|
+
},
|
|
2467
|
+
];
|
|
2468
|
+
} else if (externalPod[":path"]) {
|
|
2469
|
+
const projectLocation = resolve(projectPath, externalPod[":path"]);
|
|
2470
|
+
dependency.metadata.properties = [
|
|
2471
|
+
{
|
|
2472
|
+
name: "cdx:pods:projectDir",
|
|
2473
|
+
value: projectLocation,
|
|
2474
|
+
},
|
|
2475
|
+
];
|
|
2476
|
+
let podspec = join(projectLocation, `${podName}.podspec`);
|
|
2477
|
+
if (!safeExistsSync(podspec)) {
|
|
2478
|
+
podspec = `${podspec}.json`;
|
|
2479
|
+
}
|
|
2480
|
+
if (safeExistsSync(podspec)) {
|
|
2481
|
+
dependency.metadata.properties.push({
|
|
2482
|
+
name: "cdx:pods:podspecLocation",
|
|
2483
|
+
value: podspec,
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2486
|
+
} else if (externalPod[":podspec"]) {
|
|
2487
|
+
const podspecLocation = resolve(projectPath, externalPod[":podspec"]);
|
|
2488
|
+
dependency.metadata.properties = [
|
|
2489
|
+
{
|
|
2490
|
+
name: "cdx:pods:projectDir",
|
|
2491
|
+
value: dirname(podspecLocation),
|
|
2492
|
+
},
|
|
2493
|
+
{
|
|
2494
|
+
name: "cdx:pods:podspecLocation",
|
|
2495
|
+
value: podspecLocation,
|
|
2496
|
+
},
|
|
2497
|
+
];
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
dependencies.set(dependency.metadata.name, dependency);
|
|
2501
|
+
}
|
|
2502
|
+
if (
|
|
2503
|
+
!["false", "0"].includes(readEnvironmentVariable("COCOA_MERGE_SUBSPECS"))
|
|
2504
|
+
) {
|
|
2505
|
+
for (const subspecComponentName of [...dependencies.keys()].filter((name) =>
|
|
2506
|
+
name.includes("/"),
|
|
2507
|
+
)) {
|
|
2508
|
+
const subspecComponent = dependencies.get(subspecComponentName);
|
|
2509
|
+
const mainComponentName = subspecComponentName.split("/")[0];
|
|
2510
|
+
let mainComponent = dependencies.get(mainComponentName);
|
|
2511
|
+
if (!mainComponent) {
|
|
2512
|
+
mainComponent = {
|
|
2513
|
+
metadata: {
|
|
2514
|
+
name: mainComponentName,
|
|
2515
|
+
version: subspecComponent.metadata.version,
|
|
2516
|
+
},
|
|
2517
|
+
};
|
|
2518
|
+
dependencies.set(mainComponentName, mainComponent);
|
|
2519
|
+
}
|
|
2520
|
+
if (subspecComponent.dependencies) {
|
|
2521
|
+
if (mainComponent.dependencies) {
|
|
2522
|
+
mainComponent.dependencies = [
|
|
2523
|
+
...mainComponent.dependencies,
|
|
2524
|
+
...subspecComponent.dependencies,
|
|
2525
|
+
];
|
|
2526
|
+
} else {
|
|
2527
|
+
mainComponent.dependencies = subspecComponent.dependencies;
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
mainComponent.metadata.properties = [
|
|
2531
|
+
...(mainComponent.metadata.properties
|
|
2532
|
+
? mainComponent.metadata.properties
|
|
2533
|
+
: []),
|
|
2534
|
+
{
|
|
2535
|
+
name: "cdx:pods:Subspec",
|
|
2536
|
+
value: subspecComponentName.substring(
|
|
2537
|
+
subspecComponentName.indexOf("/") + 1,
|
|
2538
|
+
),
|
|
2539
|
+
},
|
|
2540
|
+
...(subspecComponent.metadata.propertie
|
|
2541
|
+
? subspecComponent.metadata.properties
|
|
2542
|
+
: []),
|
|
2543
|
+
];
|
|
2544
|
+
dependencies.delete(subspecComponentName);
|
|
2545
|
+
}
|
|
2546
|
+
for (const [dependencyName, dependency] of dependencies) {
|
|
2547
|
+
if (dependency.dependencies) {
|
|
2548
|
+
dependency.dependencies.forEach((dep) => {
|
|
2549
|
+
dep.name = dep.name.split("/")[0];
|
|
2550
|
+
});
|
|
2551
|
+
dependency.dependencies = [
|
|
2552
|
+
...new Map(
|
|
2553
|
+
dependency.dependencies
|
|
2554
|
+
.filter((dep) => dep.name !== dependencyName)
|
|
2555
|
+
.map((dep) => [dep.name, dep]),
|
|
2556
|
+
).values(),
|
|
2557
|
+
];
|
|
2558
|
+
if (dependency.dependencies.length === 0) {
|
|
2559
|
+
delete dependency.dependencies;
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
return dependencies;
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
/**
|
|
2568
|
+
* Parse all targets and their direct dependencies from the 'Podfile'
|
|
2569
|
+
*
|
|
2570
|
+
* @param {Object} target A JSON-object representing a target
|
|
2571
|
+
* @param {Map} allDependencies The map containing all parsed direct dependencies for a target
|
|
2572
|
+
* @param {String} [prefix=undefined] Prefix to add to the targets name
|
|
2573
|
+
*/
|
|
2574
|
+
export function parsePodfileTargets(
|
|
2575
|
+
target,
|
|
2576
|
+
allDependencies,
|
|
2577
|
+
prefix = undefined,
|
|
2578
|
+
) {
|
|
2579
|
+
const targetName = (prefix ? `${prefix}/` : "") + target.name;
|
|
2580
|
+
const targetDependencies = new Set(
|
|
2581
|
+
prefix && allDependencies.has(prefix)
|
|
2582
|
+
? allDependencies.get(prefix)
|
|
2583
|
+
: targetName !== "Pods"
|
|
2584
|
+
? allDependencies.get("Pods")
|
|
2585
|
+
: [],
|
|
2586
|
+
);
|
|
2587
|
+
if (target["dependencies"]) {
|
|
2588
|
+
for (const targetDependency of target["dependencies"]) {
|
|
2589
|
+
if (targetDependency.constructor === Object) {
|
|
2590
|
+
targetDependencies.add(Object.keys(targetDependency)[0]);
|
|
2591
|
+
} else {
|
|
2592
|
+
targetDependencies.add(targetDependency);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
allDependencies.set(targetName, Array.from(targetDependencies));
|
|
2597
|
+
if (target.children) {
|
|
2598
|
+
const childPrefix = targetName === "Pods" ? undefined : targetName;
|
|
2599
|
+
for (const childTarget of target.children) {
|
|
2600
|
+
parsePodfileTargets(childTarget, allDependencies, childPrefix);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
/**
|
|
2606
|
+
* Parse a single line representing a dependency
|
|
2607
|
+
*
|
|
2608
|
+
* @param {String} dependencyLine The line that should be parsed as a dependency
|
|
2609
|
+
* @param {boolean} [parseVersion=true] Include parsing the version of the dependency
|
|
2610
|
+
* @returns {Object} Object representing a dependency
|
|
2611
|
+
*/
|
|
2612
|
+
export function parseCocoaDependency(dependencyLine, parseVersion = true) {
|
|
2613
|
+
const dependencyData = dependencyLine.split(" (");
|
|
2614
|
+
const dependency = { name: dependencyData[0] };
|
|
2615
|
+
if (parseVersion) {
|
|
2616
|
+
dependency.version = dependencyData[1].substring(
|
|
2617
|
+
0,
|
|
2618
|
+
dependencyData[1].length - 1,
|
|
2619
|
+
);
|
|
2620
|
+
}
|
|
2621
|
+
return dependency;
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
/**
|
|
2625
|
+
* Execute the 'pod'-command with parameters
|
|
2626
|
+
*
|
|
2627
|
+
* @param {String[]} parameters The parameters for the command
|
|
2628
|
+
* @param {String} path The path where the command should be executed
|
|
2629
|
+
* @param {Object} options CLI options
|
|
2630
|
+
* @returns {Object} The result of running the command
|
|
2631
|
+
*/
|
|
2632
|
+
export function executePodCommand(parameters, path, options) {
|
|
2633
|
+
if (DEBUG_MODE) {
|
|
2634
|
+
if (path) {
|
|
2635
|
+
console.log("Executing pod", parameters.join(" "), "in", path);
|
|
2636
|
+
} else {
|
|
2637
|
+
console.log("Executing pod", parameters.join(" "));
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
const result = safeSpawnSync(
|
|
2641
|
+
readEnvironmentVariable("POD_CMD") || "pod",
|
|
2642
|
+
parameters,
|
|
2643
|
+
{
|
|
2644
|
+
cwd: path,
|
|
2645
|
+
shell: isWin,
|
|
2646
|
+
},
|
|
2647
|
+
);
|
|
2648
|
+
if (result.status !== 0 || result.error) {
|
|
2649
|
+
if (result?.stderr?.includes("Unable to find a pod")) {
|
|
2650
|
+
console.log(
|
|
2651
|
+
"Try again by running 'pod install' before invoking 'cdxgen'.",
|
|
2652
|
+
);
|
|
2653
|
+
}
|
|
2654
|
+
if (readEnvironmentVariable("CDXGEN_IN_CONTAINER") !== "true") {
|
|
2655
|
+
console.log(
|
|
2656
|
+
"Consider using the cdxgen container image (`ghcr.io/cdxgen/cdxgen`), which includes cocoapods and additional build tools.",
|
|
2657
|
+
);
|
|
2658
|
+
} else if (!DEBUG_MODE) {
|
|
2659
|
+
console.log(
|
|
2660
|
+
"Something went wrong when trying to execute cocoapods -- Set the environment variable 'CDXGEN_DEBUG_MODE=debug' to troubleshoot cocoapods related errors",
|
|
2661
|
+
);
|
|
2662
|
+
}
|
|
2663
|
+
if (options.failOnError || DEBUG_MODE) {
|
|
2664
|
+
if (result.stdout) {
|
|
2665
|
+
console.log(result.stdout);
|
|
2666
|
+
}
|
|
2667
|
+
if (result.stderr) {
|
|
2668
|
+
console.log(result.stderr);
|
|
2669
|
+
}
|
|
2670
|
+
options.failOnError && process.exit(1);
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
return result;
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
/**
|
|
2677
|
+
* Method that handles object creation for cocoa pods.
|
|
2678
|
+
*
|
|
2679
|
+
* @param {Object} dependency The dependency that is to be transformed into an SBOM object
|
|
2680
|
+
* @param {Object} options CLI options
|
|
2681
|
+
* @param {String} [type="library"] The type of Object to create
|
|
2682
|
+
* @returns {Object} An object representing the pod in SBOM-format
|
|
2683
|
+
*/
|
|
2684
|
+
export async function buildObjectForCocoaPod(
|
|
2685
|
+
dependency,
|
|
2686
|
+
options,
|
|
2687
|
+
type = "library",
|
|
2688
|
+
) {
|
|
2689
|
+
let component;
|
|
2690
|
+
if (
|
|
2691
|
+
!["false", "0"].includes(
|
|
2692
|
+
readEnvironmentVariable("COCOA_RESOLVE_FROM_NODE"),
|
|
2693
|
+
) &&
|
|
2694
|
+
dependency.properties?.find(({ name }) => name === "cdx:pods:projectDir")
|
|
2695
|
+
) {
|
|
2696
|
+
let tmpDir = dependency.properties.find(
|
|
2697
|
+
({ name }) => name === "cdx:pods:projectDir",
|
|
2698
|
+
).value;
|
|
2699
|
+
const exclusionDirs = readEnvironmentVariable(
|
|
2700
|
+
"COCOA_RESOLVE_FROM_NODE_EXCLUSION_DIRS",
|
|
2701
|
+
)
|
|
2702
|
+
? readEnvironmentVariable("COCOA_RESOLVE_FROM_NODE_EXCLUSION_DIRS").split(
|
|
2703
|
+
",",
|
|
2704
|
+
)
|
|
2705
|
+
: [];
|
|
2706
|
+
if (
|
|
2707
|
+
tmpDir &&
|
|
2708
|
+
!exclusionDirs.some((dir) =>
|
|
2709
|
+
`${tmpDir.replaceAll("\\", "/")}/`.includes(
|
|
2710
|
+
`/${dir.replaceAll("\\", "/")}/`.replaceAll("//", "/"),
|
|
2711
|
+
),
|
|
2712
|
+
) &&
|
|
2713
|
+
tmpDir.indexOf("node_modules") !== -1
|
|
2714
|
+
) {
|
|
2715
|
+
do {
|
|
2716
|
+
const npmPackages = await parsePkgJson(join(tmpDir, "package.json"));
|
|
2717
|
+
if (npmPackages.length === 1) {
|
|
2718
|
+
component = npmPackages[0];
|
|
2719
|
+
component.type = "library";
|
|
2720
|
+
component.properties = component.properties.concat(
|
|
2721
|
+
{
|
|
2722
|
+
name: "cdx:pods:PodName",
|
|
2723
|
+
value: dependency.name,
|
|
2724
|
+
},
|
|
2725
|
+
dependency.properties,
|
|
2726
|
+
);
|
|
2727
|
+
tmpDir = undefined;
|
|
2728
|
+
} else {
|
|
2729
|
+
tmpDir = dirname(tmpDir);
|
|
2730
|
+
}
|
|
2731
|
+
} while (tmpDir && tmpDir.indexOf("node_modules") !== -1);
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
if (!component) {
|
|
2735
|
+
let name = dependency.name;
|
|
2736
|
+
let subspec = null;
|
|
2737
|
+
const locationOfSubspec = dependency.name.indexOf("/");
|
|
2738
|
+
if (locationOfSubspec !== -1) {
|
|
2739
|
+
name = dependency.name.substring(0, locationOfSubspec);
|
|
2740
|
+
subspec = dependency.name.substring(locationOfSubspec + 1);
|
|
2741
|
+
}
|
|
2742
|
+
component = {
|
|
2743
|
+
...dependency,
|
|
2744
|
+
type,
|
|
2745
|
+
};
|
|
2746
|
+
if (subspec) {
|
|
2747
|
+
if (!component.properties) {
|
|
2748
|
+
component.properties = [];
|
|
2749
|
+
}
|
|
2750
|
+
component.properties.push({
|
|
2751
|
+
name: "cdx:pods:Subspec",
|
|
2752
|
+
value: subspec,
|
|
2753
|
+
});
|
|
2754
|
+
}
|
|
2755
|
+
const purl = build({
|
|
2756
|
+
type: "cocoapods",
|
|
2757
|
+
namespace: "" || null,
|
|
2758
|
+
name: name,
|
|
2759
|
+
version: component.version || null,
|
|
2760
|
+
subpath: subspec || null,
|
|
2761
|
+
});
|
|
2762
|
+
component["purl"] = purl;
|
|
2763
|
+
component["bom-ref"] = decodeURIComponent(purl);
|
|
2764
|
+
if (
|
|
2765
|
+
options &&
|
|
2766
|
+
!["false", "0"].includes(readEnvironmentVariable("COCOA_FULL_SCAN"))
|
|
2767
|
+
) {
|
|
2768
|
+
await fullScanCocoaPod(dependency, component, options);
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
return component;
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
/**
|
|
2775
|
+
* The podspec URLs to try, in the order `fullScanCocoaPod` tries them.
|
|
2776
|
+
*
|
|
2777
|
+
* A location carrying `<DEFAULT>` stands for "the repository's default branch",
|
|
2778
|
+
* which the lockfile does not name, so both conventional names are probed and
|
|
2779
|
+
* each is probed twice: CocoaPods publishes some podspecs as `.podspec` and
|
|
2780
|
+
* some as `.podspec.json` at the same path.
|
|
2781
|
+
*
|
|
2782
|
+
* @param {String} podspecLocation Location with an unresolved `<DEFAULT>`.
|
|
2783
|
+
* @returns {Array<String>} Candidate URLs, most likely first.
|
|
2784
|
+
*/
|
|
2785
|
+
function podspecCandidates(podspecLocation) {
|
|
2786
|
+
const candidates = [];
|
|
2787
|
+
for (const branchName of ["main", "master"]) {
|
|
2788
|
+
const resolved = podspecLocation.replace("<DEFAULT>", branchName);
|
|
2789
|
+
candidates.push(resolved, `${resolved}.json`);
|
|
2790
|
+
}
|
|
2791
|
+
return candidates;
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
/**
|
|
2795
|
+
* Podspec documents, prefetched by {@link prefetchCocoaPodspecs}.
|
|
2796
|
+
*
|
|
2797
|
+
* Module state for the same reason as the other prefetch maps: the reader is
|
|
2798
|
+
* several call frames below the point where the whole pod list is in hand. A
|
|
2799
|
+
* miss means the scan issues its own request.
|
|
2800
|
+
*/
|
|
2801
|
+
let cocoaPodspecPrefetch = new Map();
|
|
2802
|
+
|
|
2803
|
+
/**
|
|
2804
|
+
* Discard prefetched podspecs. Tests only.
|
|
2805
|
+
*/
|
|
2806
|
+
export function resetCocoaPodspecPrefetch() {
|
|
2807
|
+
cocoaPodspecPrefetch = new Map();
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
/**
|
|
2811
|
+
* Prefetch the remote podspecs a full CocoaPods scan is about to read.
|
|
2812
|
+
*
|
|
2813
|
+
* The probe is speculative — up to four URLs per pod — so batching all of them
|
|
2814
|
+
* at once would issue requests the serial path never makes: a pod whose podspec
|
|
2815
|
+
* sits on `main` is one request there and would be four here. Instead each
|
|
2816
|
+
* candidate position is its own round, and a round carries only the pods that
|
|
2817
|
+
* every earlier round missed. That is the same set of requests the serial path
|
|
2818
|
+
* makes, in the same order of preference, with the pods within a round
|
|
2819
|
+
* overlapped rather than queued. Rounds are bounded at four however many pods
|
|
2820
|
+
* there are.
|
|
2821
|
+
*
|
|
2822
|
+
* @param {Array<Object>} dependencies Pod metadata objects, each optionally
|
|
2823
|
+
* carrying a `cdx:pods:podspecLocation` property.
|
|
2824
|
+
* @returns {Promise<void>}
|
|
2825
|
+
*/
|
|
2826
|
+
export async function prefetchCocoaPodspecs(dependencies) {
|
|
2827
|
+
if (!prefetchEnabled() || !Array.isArray(dependencies)) {
|
|
2828
|
+
return;
|
|
2829
|
+
}
|
|
2830
|
+
if (["false", "0"].includes(readEnvironmentVariable("COCOA_FULL_SCAN"))) {
|
|
2831
|
+
return;
|
|
2832
|
+
}
|
|
2833
|
+
let pending = [];
|
|
2834
|
+
for (const dependency of dependencies || []) {
|
|
2835
|
+
const location = dependency?.properties?.find(
|
|
2836
|
+
({ name }) => name === "cdx:pods:podspecLocation",
|
|
2837
|
+
)?.value;
|
|
2838
|
+
if (location?.startsWith("http")) {
|
|
2839
|
+
pending.push(podspecCandidates(location));
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
if (!pending.length) {
|
|
2843
|
+
return;
|
|
2844
|
+
}
|
|
2845
|
+
const rounds = Math.max(...pending.map((candidates) => candidates.length));
|
|
2846
|
+
for (let position = 0; position < rounds && pending.length; position++) {
|
|
2847
|
+
const requests = [];
|
|
2848
|
+
const seen = new Set();
|
|
2849
|
+
for (const candidates of pending) {
|
|
2850
|
+
const url = candidates[position];
|
|
2851
|
+
if (!url || seen.has(url) || cocoaPodspecPrefetch.has(url)) {
|
|
2852
|
+
continue;
|
|
2853
|
+
}
|
|
2854
|
+
seen.add(url);
|
|
2855
|
+
requests.push({ url, responseType: "text" });
|
|
2856
|
+
}
|
|
2857
|
+
if (requests.length) {
|
|
2858
|
+
for (const [key, value] of await prefetchJson(requests)) {
|
|
2859
|
+
cocoaPodspecPrefetch.set(key, value);
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
// A pod whose candidate resolved is done; the rest fall through to the next
|
|
2863
|
+
// position, exactly as the serial probe would.
|
|
2864
|
+
pending = pending.filter(
|
|
2865
|
+
(candidates) => !cocoaPodspecPrefetch.get(candidates[position])?.ok,
|
|
2866
|
+
);
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
async function fullScanCocoaPod(dependency, component, options) {
|
|
2871
|
+
let result;
|
|
2872
|
+
if (
|
|
2873
|
+
component.properties?.find(
|
|
2874
|
+
({ name }) => name === "cdx:pods:podspecLocation",
|
|
2875
|
+
)
|
|
2876
|
+
) {
|
|
2877
|
+
let podspecLocation = component.properties.find(
|
|
2878
|
+
({ name }) => name === "cdx:pods:podspecLocation",
|
|
2879
|
+
).value;
|
|
2880
|
+
if (
|
|
2881
|
+
component.properties.find(({ name }) => name === "cdx:pods:projectDir")
|
|
2882
|
+
) {
|
|
2883
|
+
component.properties.push({
|
|
2884
|
+
name: "internal:SrcFile",
|
|
2885
|
+
value: podspecLocation,
|
|
2886
|
+
});
|
|
2887
|
+
}
|
|
2888
|
+
let replacements = [];
|
|
2889
|
+
if (
|
|
2890
|
+
podspecLocation.endsWith(".podspec") &&
|
|
2891
|
+
readEnvironmentVariable("COCOA_PODSPEC_REPLACEMENTS")
|
|
2892
|
+
) {
|
|
2893
|
+
replacements = readEnvironmentVariable(
|
|
2894
|
+
"COCOA_PODSPEC_REPLACEMENTS",
|
|
2895
|
+
).split(";");
|
|
2896
|
+
} else if (
|
|
2897
|
+
podspecLocation.endsWith(".json") &&
|
|
2898
|
+
readEnvironmentVariable("COCOA_PODSPEC_JSON_REPLACEMENTS")
|
|
2899
|
+
) {
|
|
2900
|
+
replacements = readEnvironmentVariable(
|
|
2901
|
+
"COCOA_PODSPEC_JSON_REPLACEMENTS",
|
|
2902
|
+
).split(";");
|
|
2903
|
+
}
|
|
2904
|
+
if (replacements || podspecLocation.startsWith("http")) {
|
|
2905
|
+
let podspecContent;
|
|
2906
|
+
if (podspecLocation.startsWith("http")) {
|
|
2907
|
+
let httpResult;
|
|
2908
|
+
for (const candidate of podspecCandidates(podspecLocation)) {
|
|
2909
|
+
try {
|
|
2910
|
+
httpResult =
|
|
2911
|
+
prefetchedResponse(cocoaPodspecPrefetch, candidate) ||
|
|
2912
|
+
(await cdxgenAgent.get(candidate));
|
|
2913
|
+
podspecLocation = candidate;
|
|
2914
|
+
} catch (_err) {
|
|
2915
|
+
continue;
|
|
2916
|
+
}
|
|
2917
|
+
component.properties.find(
|
|
2918
|
+
({ name }) => name === "cdx:pods:podspecLocation",
|
|
2919
|
+
).value = podspecLocation;
|
|
2920
|
+
podspecLocation = `${randomUUID()}.${podspecLocation.substring(podspecLocation.lastIndexOf(".") + 1)}`;
|
|
2921
|
+
podspecContent = httpResult.body;
|
|
2922
|
+
break;
|
|
2923
|
+
}
|
|
2924
|
+
} else {
|
|
2925
|
+
podspecContent = readFileSync(podspecLocation, "utf-8");
|
|
2926
|
+
}
|
|
2927
|
+
for (const replacement of replacements) {
|
|
2928
|
+
const replacementPair = replacement.split("=");
|
|
2929
|
+
let match = replacementPair[0].replaceAll("<NEWLINE>", "\n");
|
|
2930
|
+
if (match.startsWith("/") && match.endsWith("/")) {
|
|
2931
|
+
match = new RegExp(match.substring(1, match.length - 1), "g");
|
|
2932
|
+
}
|
|
2933
|
+
const repl = replacementPair[1].replaceAll("<NEWLINE>", "\n");
|
|
2934
|
+
podspecContent = podspecContent.replaceAll(match, repl);
|
|
2935
|
+
}
|
|
2936
|
+
podspecLocation = join(
|
|
2937
|
+
dirname(podspecLocation),
|
|
2938
|
+
`${randomUUID()}.${podspecLocation.substring(podspecLocation.lastIndexOf(".") + 1)}`,
|
|
2939
|
+
);
|
|
2940
|
+
safeWriteSync(podspecLocation, podspecContent);
|
|
2941
|
+
temporaryFiles.add(podspecLocation);
|
|
2942
|
+
}
|
|
2943
|
+
result = executePodCommand(
|
|
2944
|
+
["ipc", "spec", "--silent", podspecLocation],
|
|
2945
|
+
undefined,
|
|
2946
|
+
options,
|
|
2947
|
+
);
|
|
2948
|
+
} else {
|
|
2949
|
+
let dependencyName = dependency.name;
|
|
2950
|
+
if (dependencyName.includes("/")) {
|
|
2951
|
+
dependencyName = dependencyName.substring(0, dependencyName.indexOf("/"));
|
|
2952
|
+
}
|
|
2953
|
+
// `pod` may not be installed at all, in which case spawn returns a result
|
|
2954
|
+
// with no stdout. Calling .trim() on that threw and aborted the whole
|
|
2955
|
+
// CocoaPods scan, so any machine without CocoaPods could not generate a
|
|
2956
|
+
// BOM from a Podfile.lock it had already parsed successfully.
|
|
2957
|
+
const srcFileResult = executePodCommand(
|
|
2958
|
+
[
|
|
2959
|
+
"spec",
|
|
2960
|
+
"which",
|
|
2961
|
+
`^${dependencyName}$`,
|
|
2962
|
+
"--regex",
|
|
2963
|
+
`--version=${dependency.version}`,
|
|
2964
|
+
],
|
|
2965
|
+
undefined,
|
|
2966
|
+
options,
|
|
2967
|
+
);
|
|
2968
|
+
if (srcFileResult?.stdout) {
|
|
2969
|
+
const srcFileProperty = {
|
|
2970
|
+
name: "internal:SrcFile",
|
|
2971
|
+
value: srcFileResult.stdout.trim(),
|
|
2972
|
+
};
|
|
2973
|
+
if (component.properties) {
|
|
2974
|
+
component.properties.push(srcFileProperty);
|
|
2975
|
+
} else {
|
|
2976
|
+
component.properties = [srcFileProperty];
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
result = executePodCommand(
|
|
2980
|
+
[
|
|
2981
|
+
"spec",
|
|
2982
|
+
"cat",
|
|
2983
|
+
`^${dependencyName}$`,
|
|
2984
|
+
"--regex",
|
|
2985
|
+
`--version=${dependency.version}`,
|
|
2986
|
+
],
|
|
2987
|
+
undefined,
|
|
2988
|
+
options,
|
|
2989
|
+
);
|
|
2990
|
+
}
|
|
2991
|
+
// Same reason as above: no `pod` binary means no result to parse.
|
|
2992
|
+
const podspecText = result?.stdout;
|
|
2993
|
+
if (!podspecText) {
|
|
2994
|
+
return;
|
|
2995
|
+
}
|
|
2996
|
+
let podspec;
|
|
2997
|
+
try {
|
|
2998
|
+
podspec = JSON.parse(
|
|
2999
|
+
podspecText.substring(
|
|
3000
|
+
podspecText.indexOf("{"),
|
|
3001
|
+
podspecText.lastIndexOf("}") + 1,
|
|
3002
|
+
),
|
|
3003
|
+
);
|
|
3004
|
+
} catch (_e) {
|
|
3005
|
+
return;
|
|
3006
|
+
}
|
|
3007
|
+
const externalRefs = [];
|
|
3008
|
+
if (podspec.authors) {
|
|
3009
|
+
component.authors = [];
|
|
3010
|
+
if (podspec.authors.constructor === Object) {
|
|
3011
|
+
Object.entries(podspec.authors).forEach(([name, email]) => {
|
|
3012
|
+
email.includes("@")
|
|
3013
|
+
? component.authors.push({ name, email })
|
|
3014
|
+
: component.authors.push({ name });
|
|
3015
|
+
});
|
|
3016
|
+
} else if (podspec.authors.constructor === Array) {
|
|
3017
|
+
podspec.authors.forEach((name) => {
|
|
3018
|
+
component.authors.push({ name });
|
|
3019
|
+
});
|
|
3020
|
+
} else {
|
|
3021
|
+
component.authors.push({ name: podspec.authors });
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
if (podspec.description) {
|
|
3025
|
+
component.description = podspec.description;
|
|
3026
|
+
} else if (podspec.summary) {
|
|
3027
|
+
component.description = podspec.summary;
|
|
3028
|
+
}
|
|
3029
|
+
if (podspec.documentation_url) {
|
|
3030
|
+
externalRefs.push({
|
|
3031
|
+
type: "documentation",
|
|
3032
|
+
url: podspec.documentation_url,
|
|
3033
|
+
});
|
|
3034
|
+
} else if (podspec.readme) {
|
|
3035
|
+
externalRefs.push({
|
|
3036
|
+
type: "documentation",
|
|
3037
|
+
url: podspec.readme,
|
|
3038
|
+
});
|
|
3039
|
+
}
|
|
3040
|
+
if (podspec.homepage) {
|
|
3041
|
+
externalRefs.push({
|
|
3042
|
+
type: "website",
|
|
3043
|
+
url: podspec.homepage,
|
|
3044
|
+
});
|
|
3045
|
+
}
|
|
3046
|
+
if (podspec.license) {
|
|
3047
|
+
if (podspec.license.constructor === Object) {
|
|
3048
|
+
if (podspec.license.type === "Copyright") {
|
|
3049
|
+
component.copyright = podspec.license.text;
|
|
3050
|
+
} else {
|
|
3051
|
+
component.licenses = [{ license: {} }];
|
|
3052
|
+
if (spdxLicenses.includes(podspec.license.type)) {
|
|
3053
|
+
component.licenses[0].license.id = podspec.license.type;
|
|
3054
|
+
} else {
|
|
3055
|
+
component.licenses[0].license.name = podspec.license.type;
|
|
3056
|
+
}
|
|
3057
|
+
const licenseText = [];
|
|
3058
|
+
if (podspec.license.text) {
|
|
3059
|
+
if (podspec.license.text.startsWith("http")) {
|
|
3060
|
+
component.licenses[0].license.url = podspec.license.text;
|
|
3061
|
+
} else {
|
|
3062
|
+
licenseText.push(podspec.license.text);
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
if (podspec.license.file) {
|
|
3066
|
+
if (podspec.license.file.startsWith("http")) {
|
|
3067
|
+
if (component.licenses[0].license.url) {
|
|
3068
|
+
if (licenseText.length !== 0) {
|
|
3069
|
+
licenseText.push("");
|
|
3070
|
+
}
|
|
3071
|
+
licenseText.push(
|
|
3072
|
+
`See also: ${component.licenses[0].license.url}`,
|
|
3073
|
+
);
|
|
3074
|
+
}
|
|
3075
|
+
component.licenses[0].license.url = podspec.license.file;
|
|
3076
|
+
} else {
|
|
3077
|
+
if (licenseText.length !== 0) {
|
|
3078
|
+
licenseText.push("");
|
|
3079
|
+
}
|
|
3080
|
+
licenseText.push(`See license in file '${podspec.license.file}'`);
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
if (licenseText.length !== 0) {
|
|
3084
|
+
component.licenses[0].license.text = {
|
|
3085
|
+
content: licenseText.join("\n"),
|
|
3086
|
+
};
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
} else {
|
|
3090
|
+
if (spdxLicenses.includes(podspec.license)) {
|
|
3091
|
+
component.licenses = [{ license: { id: podspec.license } }];
|
|
3092
|
+
} else {
|
|
3093
|
+
component.licenses = [{ license: { name: podspec.license } }];
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
if (podspec.social_media_url) {
|
|
3098
|
+
externalRefs.push({
|
|
3099
|
+
type: "social",
|
|
3100
|
+
url: podspec.social_media_url,
|
|
3101
|
+
});
|
|
3102
|
+
}
|
|
3103
|
+
if (podspec.source) {
|
|
3104
|
+
const comment = [];
|
|
3105
|
+
if (podspec.source.http) {
|
|
3106
|
+
const sourceDistro = {
|
|
3107
|
+
type: "source-distribution",
|
|
3108
|
+
url: podspec.source.http,
|
|
3109
|
+
};
|
|
3110
|
+
const hashes = [];
|
|
3111
|
+
if (podspec.source.http.sha1) {
|
|
3112
|
+
hashes.push({
|
|
3113
|
+
alg: "SHA-1",
|
|
3114
|
+
content: podspec.source.http.sha1,
|
|
3115
|
+
});
|
|
3116
|
+
}
|
|
3117
|
+
if (podspec.source.http.sha256) {
|
|
3118
|
+
hashes.push({
|
|
3119
|
+
alg: "SHA-256",
|
|
3120
|
+
content: podspec.source.http.sha256,
|
|
3121
|
+
});
|
|
3122
|
+
}
|
|
3123
|
+
if (hashes.length !== 0) {
|
|
3124
|
+
sourceDistro.hashes = hashes;
|
|
3125
|
+
}
|
|
3126
|
+
if (podspec.source.flatten) {
|
|
3127
|
+
comment.push(`Flatten: ${podspec.source.flatten}`);
|
|
3128
|
+
}
|
|
3129
|
+
if (podspec.source.type) {
|
|
3130
|
+
comment.push(`Type: ${podspec.source.type}`);
|
|
3131
|
+
}
|
|
3132
|
+
if (podspec.source.headers) {
|
|
3133
|
+
comment.push(`Headers: ${podspec.source.headers}`);
|
|
3134
|
+
}
|
|
3135
|
+
if (comment.length !== 0) {
|
|
3136
|
+
sourceDistro.comment = comment.join("\n");
|
|
3137
|
+
}
|
|
3138
|
+
externalRefs.push(sourceDistro);
|
|
3139
|
+
} else {
|
|
3140
|
+
let url;
|
|
3141
|
+
if (podspec.source.git) {
|
|
3142
|
+
url = podspec.source.git;
|
|
3143
|
+
comment.push("Type: git");
|
|
3144
|
+
if (podspec.source.branch) {
|
|
3145
|
+
comment.push(`Branch: ${podspec.source.branch}`);
|
|
3146
|
+
}
|
|
3147
|
+
if (podspec.source.commit) {
|
|
3148
|
+
comment.push(`Commit: ${podspec.source.commit}`);
|
|
3149
|
+
}
|
|
3150
|
+
if (podspec.source.tag) {
|
|
3151
|
+
comment.push(`Tag: ${podspec.source.tag}`);
|
|
3152
|
+
}
|
|
3153
|
+
if (podspec.source.submodules) {
|
|
3154
|
+
comment.push(`Submodules: ${podspec.source.submodules}`);
|
|
3155
|
+
}
|
|
3156
|
+
} else if (podspec.source.hg) {
|
|
3157
|
+
url = podspec.source.hg;
|
|
3158
|
+
comment.push("Type: hg");
|
|
3159
|
+
if (podspec.source.revision) {
|
|
3160
|
+
comment.push(`Revision: ${podspec.source.revision}`);
|
|
3161
|
+
}
|
|
3162
|
+
} else if (podspec.source.svn) {
|
|
3163
|
+
url = podspec.source.svn;
|
|
3164
|
+
comment.push("Type: svn");
|
|
3165
|
+
if (podspec.source.folder) {
|
|
3166
|
+
comment.push(`Folder: ${podspec.source.folder}`);
|
|
3167
|
+
}
|
|
3168
|
+
if (podspec.source.revision) {
|
|
3169
|
+
comment.push(`Revision: ${podspec.source.revision}`);
|
|
3170
|
+
}
|
|
3171
|
+
if (podspec.source.tag) {
|
|
3172
|
+
comment.push(`Tag: ${podspec.source.tag}`);
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
if (url) {
|
|
3176
|
+
externalRefs.push({
|
|
3177
|
+
type: "vcs",
|
|
3178
|
+
url: url,
|
|
3179
|
+
comment: comment.join("\n"),
|
|
3180
|
+
});
|
|
3181
|
+
} else {
|
|
3182
|
+
console.warn(
|
|
3183
|
+
`${dependency.name} has property 'source' defined, but it does not contain a URL -- ignoring...`,
|
|
3184
|
+
);
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
if (externalRefs.length !== 0) {
|
|
3189
|
+
component.externalReferences = externalRefs;
|
|
3190
|
+
}
|
|
3191
|
+
}
|