@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,4222 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import path, {
|
|
4
|
+
sep as _sep,
|
|
5
|
+
basename,
|
|
6
|
+
dirname,
|
|
7
|
+
join,
|
|
8
|
+
relative,
|
|
9
|
+
resolve,
|
|
10
|
+
} from "node:path";
|
|
11
|
+
|
|
12
|
+
import { build, Purl } from "@cdxgen/cdx-purl";
|
|
13
|
+
import ssri from "ssri";
|
|
14
|
+
import { parse as _load, parseAllDocuments } from "yaml";
|
|
15
|
+
|
|
16
|
+
import { DEBUG_MODE, readEnvironmentVariable } from "../core/activity.js";
|
|
17
|
+
import { shouldFetchPackageMetadata } from "../core/env.js";
|
|
18
|
+
import { safeExistsSync } from "../core/fs.js";
|
|
19
|
+
import { analyzeSuspiciousJsFile } from "../inventory/analyzer.js";
|
|
20
|
+
import { encodeForPurl, npmPurl } from "../inventory/purl.js";
|
|
21
|
+
import Arborist from "../third-party/arborist/lib/index.js";
|
|
22
|
+
import { getNpmMetadata } from "./ecosystems.js";
|
|
23
|
+
import {
|
|
24
|
+
buildNpmGitDistributionIntakeRefs,
|
|
25
|
+
buildNpmGitPurlQualifiers,
|
|
26
|
+
buildPnpmGitPkgRefs,
|
|
27
|
+
collectNpmManifestSources,
|
|
28
|
+
findMatchingNpmWorkspace,
|
|
29
|
+
hydrateNpmNodePackage,
|
|
30
|
+
loadNpmrcConfig,
|
|
31
|
+
normalizePnpmLockKey,
|
|
32
|
+
parsePnpmGitLockKey,
|
|
33
|
+
setNpmDevelopmentProperty,
|
|
34
|
+
setNpmOptionalProperty,
|
|
35
|
+
setNpmPeerProperty,
|
|
36
|
+
} from "./npmutils.js";
|
|
37
|
+
import { addComponentProperty } from "./parsers-python.js";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Parse nodejs package json file
|
|
41
|
+
*
|
|
42
|
+
* @param {string} pkgJsonFile package.json file
|
|
43
|
+
* @param {boolean} simple Return a simpler representation of the component by skipping extended attributes and license fetch.
|
|
44
|
+
* @param {boolean} securityProps Collect security-related properties
|
|
45
|
+
*/
|
|
46
|
+
const NPM_INSTALL_HOOK_NAMES = [
|
|
47
|
+
"preinstall",
|
|
48
|
+
"install",
|
|
49
|
+
"postinstall",
|
|
50
|
+
"prepublish",
|
|
51
|
+
"prepare",
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const NPM_BUILD_SCRIPT_NAME_PATTERN =
|
|
55
|
+
/\b(build|compile|bundle|pack|dist|watch|transpile|minif(?:y|ication)|uglif(?:y|ication))\b/i;
|
|
56
|
+
|
|
57
|
+
const NPM_LIFECYCLE_OBFUSCATION_PATTERNS = [
|
|
58
|
+
[
|
|
59
|
+
"base64-decode",
|
|
60
|
+
/\b(?:base64(?:\s+--decode|\s+-d)?|openssl\s+enc\s+-base64\s+-d)\b/i,
|
|
61
|
+
],
|
|
62
|
+
["buffer-base64", /Buffer\.from\s*\([^)]*,\s*["']base64["']\s*\)/i],
|
|
63
|
+
["atob", /\batob\s*\(/i],
|
|
64
|
+
["string-from-char-code", /\bString\.fromCharCode\s*\(/i],
|
|
65
|
+
["long-base64-literal", /\b[A-Za-z0-9+/]{80,}={0,2}\b/],
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const NPM_LIFECYCLE_EXECUTION_PATTERNS = [
|
|
69
|
+
["node-eval", /\bnode\b[^\n]*\s-[ep]\b/i],
|
|
70
|
+
["eval", /\beval\s*\(/i],
|
|
71
|
+
["function-constructor", /\b(?:new\s+Function|Function\s*\()/i],
|
|
72
|
+
[
|
|
73
|
+
"child-process",
|
|
74
|
+
/\b(?:child_process|node:child_process|execSync|execFileSync|spawnSync|execFile|spawn|exec)\b/i,
|
|
75
|
+
],
|
|
76
|
+
[
|
|
77
|
+
"shell-inline",
|
|
78
|
+
/\b(?:sh|bash|cmd|powershell|pwsh)\b[^\n]*\s-(?:c|Command|EncodedCommand)\b/i,
|
|
79
|
+
],
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
const NPM_LIFECYCLE_NETWORK_PATTERNS = [
|
|
83
|
+
["curl", /\bcurl\b/i],
|
|
84
|
+
["wget", /\bwget\b/i],
|
|
85
|
+
["invoke-webrequest", /\b(?:invoke-webrequest|iwr)\b/i],
|
|
86
|
+
["http-url", /https?:\/\//i],
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
const NPM_LIFECYCLE_JS_RUNNERS = new Set([
|
|
90
|
+
"babel-node",
|
|
91
|
+
"node",
|
|
92
|
+
"ts-node",
|
|
93
|
+
"tsx",
|
|
94
|
+
"bun",
|
|
95
|
+
"deno",
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
const NPM_LIFECYCLE_JS_RUNNER_VALUE_OPTIONS = new Set([
|
|
99
|
+
"-c",
|
|
100
|
+
"-e",
|
|
101
|
+
"-p",
|
|
102
|
+
"-r",
|
|
103
|
+
"--config",
|
|
104
|
+
"--conditions",
|
|
105
|
+
"--cwd-file",
|
|
106
|
+
"--compilerOptions",
|
|
107
|
+
"--cwd",
|
|
108
|
+
"--env-file",
|
|
109
|
+
"--env-file-if-exists",
|
|
110
|
+
"--eval",
|
|
111
|
+
"--experimental-loader",
|
|
112
|
+
"--ignore",
|
|
113
|
+
"--import",
|
|
114
|
+
"--import-map",
|
|
115
|
+
"--input-type",
|
|
116
|
+
"--inspect",
|
|
117
|
+
"--inspect-brk",
|
|
118
|
+
"--inspect-port",
|
|
119
|
+
"--loader",
|
|
120
|
+
"--print",
|
|
121
|
+
"--preload",
|
|
122
|
+
"--project",
|
|
123
|
+
"--require",
|
|
124
|
+
"--test-name-pattern",
|
|
125
|
+
"--test-reporter",
|
|
126
|
+
"--test-reporter-destination",
|
|
127
|
+
"--test-shard",
|
|
128
|
+
"--title",
|
|
129
|
+
"--tsconfig",
|
|
130
|
+
"--watch-path",
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
const NPM_LIFECYCLE_JS_SOURCE_FILE_PATTERN = /\.[cm]?[jt]sx?$/i;
|
|
134
|
+
|
|
135
|
+
const SHELL_ENV_ASSIGNMENT_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*=.*/;
|
|
136
|
+
|
|
137
|
+
function splitLifecycleScriptCommands(scriptValue) {
|
|
138
|
+
const commands = [];
|
|
139
|
+
let current = "";
|
|
140
|
+
let escaped = false;
|
|
141
|
+
let quoteChar = "";
|
|
142
|
+
|
|
143
|
+
for (let index = 0; index < scriptValue.length; index++) {
|
|
144
|
+
const character = scriptValue[index];
|
|
145
|
+
if (escaped) {
|
|
146
|
+
current += character;
|
|
147
|
+
escaped = false;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (character === "\\") {
|
|
151
|
+
current += character;
|
|
152
|
+
escaped = true;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (quoteChar) {
|
|
156
|
+
current += character;
|
|
157
|
+
if (character === quoteChar) {
|
|
158
|
+
quoteChar = "";
|
|
159
|
+
}
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (character === '"' || character === "'") {
|
|
163
|
+
current += character;
|
|
164
|
+
quoteChar = character;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (character === ";" || character === "|") {
|
|
168
|
+
if (current.trim()) {
|
|
169
|
+
commands.push(current.trim());
|
|
170
|
+
}
|
|
171
|
+
current = "";
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (character === "&") {
|
|
175
|
+
if (current.trim()) {
|
|
176
|
+
commands.push(current.trim());
|
|
177
|
+
}
|
|
178
|
+
current = "";
|
|
179
|
+
if (scriptValue[index + 1] === "&") {
|
|
180
|
+
index += 1;
|
|
181
|
+
}
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
current += character;
|
|
185
|
+
}
|
|
186
|
+
if (current.trim()) {
|
|
187
|
+
commands.push(current.trim());
|
|
188
|
+
}
|
|
189
|
+
return commands;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function lifecycleRunnerOptionConsumesValue(token) {
|
|
193
|
+
if (!token?.startsWith("-") || token === "--") {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
if (token.includes("=")) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
if (token.startsWith("--")) {
|
|
200
|
+
return NPM_LIFECYCLE_JS_RUNNER_VALUE_OPTIONS.has(token);
|
|
201
|
+
}
|
|
202
|
+
if (token.length > 2) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
return NPM_LIFECYCLE_JS_RUNNER_VALUE_OPTIONS.has(token);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function isLifecycleScriptSourceFile(token) {
|
|
209
|
+
if (!token || token.startsWith("-")) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
const fileToken = token.split(/[?#]/u, 1)[0];
|
|
213
|
+
return NPM_LIFECYCLE_JS_SOURCE_FILE_PATTERN.test(fileToken);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function findLifecycleScriptSourceArg(tokens, startIndex) {
|
|
217
|
+
for (let index = startIndex; index < tokens.length; index++) {
|
|
218
|
+
const token = tokens[index]?.trim();
|
|
219
|
+
if (!token) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (token === "--" || SHELL_ENV_ASSIGNMENT_PATTERN.test(token)) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (token.startsWith("-")) {
|
|
226
|
+
if (
|
|
227
|
+
lifecycleRunnerOptionConsumesValue(token) &&
|
|
228
|
+
index + 1 < tokens.length
|
|
229
|
+
) {
|
|
230
|
+
index += 1;
|
|
231
|
+
}
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (isLifecycleScriptSourceFile(token)) {
|
|
235
|
+
return token;
|
|
236
|
+
}
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function findLifecycleScriptSourceStartIndex(tokens, runnerIndex) {
|
|
243
|
+
const runnerToken = tokens[runnerIndex];
|
|
244
|
+
for (let index = runnerIndex + 1; index < tokens.length; index++) {
|
|
245
|
+
const token = tokens[index]?.trim();
|
|
246
|
+
if (!token || token === "--" || SHELL_ENV_ASSIGNMENT_PATTERN.test(token)) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (token.startsWith("-")) {
|
|
250
|
+
if (
|
|
251
|
+
lifecycleRunnerOptionConsumesValue(token) &&
|
|
252
|
+
index + 1 < tokens.length
|
|
253
|
+
) {
|
|
254
|
+
index += 1;
|
|
255
|
+
}
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (runnerToken === "deno") {
|
|
259
|
+
return token === "run" ? index + 1 : undefined;
|
|
260
|
+
}
|
|
261
|
+
if (runnerToken === "bun" && token === "run") {
|
|
262
|
+
return index + 1;
|
|
263
|
+
}
|
|
264
|
+
return index;
|
|
265
|
+
}
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function isResolvedPathWithinDirectory(baseDir, resolvedFile) {
|
|
270
|
+
const relativePath = relative(baseDir, resolvedFile);
|
|
271
|
+
if (!relativePath) {
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
return (
|
|
275
|
+
!relativePath.startsWith(`..${path.sep}`) &&
|
|
276
|
+
relativePath !== ".." &&
|
|
277
|
+
!path.isAbsolute(relativePath)
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function collectLifecyclePatternIndicators(scriptValue, patterns) {
|
|
282
|
+
const indicators = [];
|
|
283
|
+
patterns.forEach(([name, pattern]) => {
|
|
284
|
+
if (pattern.test(scriptValue)) {
|
|
285
|
+
indicators.push(name);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
return indicators;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function extractLifecycleScriptSourceFiles(pkgJsonFile, scriptValue) {
|
|
292
|
+
const sourceFiles = [];
|
|
293
|
+
const seen = new Set();
|
|
294
|
+
if (!scriptValue || typeof scriptValue !== "string") {
|
|
295
|
+
return sourceFiles;
|
|
296
|
+
}
|
|
297
|
+
const pkgJsonDir = resolve(dirname(pkgJsonFile));
|
|
298
|
+
const commandSegments = splitLifecycleScriptCommands(scriptValue);
|
|
299
|
+
for (const commandSegment of commandSegments) {
|
|
300
|
+
const tokens = splitCommandArgs(commandSegment);
|
|
301
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
302
|
+
if (!NPM_LIFECYCLE_JS_RUNNERS.has(tokens[index])) {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
const scriptStartIndex = findLifecycleScriptSourceStartIndex(
|
|
306
|
+
tokens,
|
|
307
|
+
index,
|
|
308
|
+
);
|
|
309
|
+
if (scriptStartIndex === undefined) {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
const relativeFile = findLifecycleScriptSourceArg(
|
|
313
|
+
tokens,
|
|
314
|
+
scriptStartIndex,
|
|
315
|
+
);
|
|
316
|
+
if (!relativeFile) {
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const resolvedFile = resolve(pkgJsonDir, relativeFile);
|
|
320
|
+
if (
|
|
321
|
+
!isResolvedPathWithinDirectory(pkgJsonDir, resolvedFile) ||
|
|
322
|
+
!safeExistsSync(resolvedFile) ||
|
|
323
|
+
seen.has(resolvedFile)
|
|
324
|
+
) {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
seen.add(resolvedFile);
|
|
328
|
+
sourceFiles.push(resolvedFile);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return sourceFiles;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function analyzeNpmLifecycleScripts(pkgJsonFile, scripts) {
|
|
335
|
+
const executionIndicators = new Set();
|
|
336
|
+
const networkIndicators = new Set();
|
|
337
|
+
const obfuscationIndicators = new Set();
|
|
338
|
+
const obfuscatedScripts = [];
|
|
339
|
+
const suspiciousScripts = [];
|
|
340
|
+
const scriptIndicatorMap = [];
|
|
341
|
+
const riskyScripts = NPM_INSTALL_HOOK_NAMES.filter(
|
|
342
|
+
(scriptName) => scripts?.[scriptName],
|
|
343
|
+
);
|
|
344
|
+
riskyScripts.forEach((scriptName) => {
|
|
345
|
+
const scriptValue = String(scripts[scriptName] || "");
|
|
346
|
+
const scriptExecutionIndicators = new Set(
|
|
347
|
+
collectLifecyclePatternIndicators(
|
|
348
|
+
scriptValue,
|
|
349
|
+
NPM_LIFECYCLE_EXECUTION_PATTERNS,
|
|
350
|
+
),
|
|
351
|
+
);
|
|
352
|
+
const scriptNetworkIndicators = new Set(
|
|
353
|
+
collectLifecyclePatternIndicators(
|
|
354
|
+
scriptValue,
|
|
355
|
+
NPM_LIFECYCLE_NETWORK_PATTERNS,
|
|
356
|
+
),
|
|
357
|
+
);
|
|
358
|
+
const scriptObfuscationIndicators = new Set(
|
|
359
|
+
collectLifecyclePatternIndicators(
|
|
360
|
+
scriptValue,
|
|
361
|
+
NPM_LIFECYCLE_OBFUSCATION_PATTERNS,
|
|
362
|
+
),
|
|
363
|
+
);
|
|
364
|
+
extractLifecycleScriptSourceFiles(pkgJsonFile, scriptValue).forEach(
|
|
365
|
+
(sourceFile) => {
|
|
366
|
+
const astIndicators = analyzeSuspiciousJsFile(sourceFile);
|
|
367
|
+
astIndicators.executionIndicators.forEach((indicator) => {
|
|
368
|
+
scriptExecutionIndicators.add(`ast:${indicator}`);
|
|
369
|
+
});
|
|
370
|
+
astIndicators.networkIndicators.forEach((indicator) => {
|
|
371
|
+
scriptNetworkIndicators.add(`ast:${indicator}`);
|
|
372
|
+
});
|
|
373
|
+
astIndicators.obfuscationIndicators.forEach((indicator) => {
|
|
374
|
+
scriptObfuscationIndicators.add(`ast:${indicator}`);
|
|
375
|
+
});
|
|
376
|
+
},
|
|
377
|
+
);
|
|
378
|
+
if (scriptObfuscationIndicators.size) {
|
|
379
|
+
scriptExecutionIndicators.forEach((indicator) => {
|
|
380
|
+
executionIndicators.add(indicator);
|
|
381
|
+
});
|
|
382
|
+
scriptObfuscationIndicators.forEach((indicator) => {
|
|
383
|
+
obfuscationIndicators.add(indicator);
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
if (
|
|
387
|
+
scriptObfuscationIndicators.size &&
|
|
388
|
+
(scriptExecutionIndicators.size || scriptNetworkIndicators.size)
|
|
389
|
+
) {
|
|
390
|
+
obfuscatedScripts.push(scriptName);
|
|
391
|
+
}
|
|
392
|
+
if (
|
|
393
|
+
scriptObfuscationIndicators.size ||
|
|
394
|
+
(scriptExecutionIndicators.size && scriptNetworkIndicators.size)
|
|
395
|
+
) {
|
|
396
|
+
suspiciousScripts.push(scriptName);
|
|
397
|
+
}
|
|
398
|
+
scriptNetworkIndicators.forEach((indicator) => {
|
|
399
|
+
networkIndicators.add(indicator);
|
|
400
|
+
});
|
|
401
|
+
if (
|
|
402
|
+
scriptExecutionIndicators.size ||
|
|
403
|
+
scriptNetworkIndicators.size ||
|
|
404
|
+
scriptObfuscationIndicators.size
|
|
405
|
+
) {
|
|
406
|
+
scriptIndicatorMap.push(
|
|
407
|
+
`${scriptName}:${[
|
|
408
|
+
...scriptObfuscationIndicators,
|
|
409
|
+
...scriptExecutionIndicators,
|
|
410
|
+
...scriptNetworkIndicators,
|
|
411
|
+
].join("+")}`,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
return {
|
|
416
|
+
executionIndicators: Array.from(executionIndicators).sort(),
|
|
417
|
+
networkIndicators: Array.from(networkIndicators).sort(),
|
|
418
|
+
obfuscatedScripts: [...new Set(obfuscatedScripts)].sort(),
|
|
419
|
+
obfuscationIndicators: Array.from(obfuscationIndicators).sort(),
|
|
420
|
+
riskyScripts,
|
|
421
|
+
scriptIndicatorMap: scriptIndicatorMap.sort(),
|
|
422
|
+
suspiciousScripts: [...new Set(suspiciousScripts)].sort(),
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Parse a package.json file into CycloneDX component objects.
|
|
428
|
+
*
|
|
429
|
+
* @param {string} pkgJsonFile Path to the package.json file.
|
|
430
|
+
* @param {boolean} [simple=false] When true, omit component properties and
|
|
431
|
+
* identity evidence and skip npm registry metadata fetching.
|
|
432
|
+
* @param {boolean} [securityProps=false] When true, append npm security-relevant
|
|
433
|
+
* properties such as lifecycle scripts, binaries, native addons, and deprecation notices.
|
|
434
|
+
* @returns {Promise<Array<object>>} Parsed component list, enriched with npm
|
|
435
|
+
* registry metadata when available and not in simple mode.
|
|
436
|
+
*/
|
|
437
|
+
export async function parsePkgJson(
|
|
438
|
+
pkgJsonFile,
|
|
439
|
+
simple = false,
|
|
440
|
+
securityProps = false,
|
|
441
|
+
) {
|
|
442
|
+
const pkgList = [];
|
|
443
|
+
if (safeExistsSync(pkgJsonFile)) {
|
|
444
|
+
try {
|
|
445
|
+
const pkgData = JSON.parse(readFileSync(pkgJsonFile, "utf8"));
|
|
446
|
+
const pkgIdentifier = parsePackageJsonName(pkgData.name);
|
|
447
|
+
let name = pkgIdentifier.fullName || pkgData.name;
|
|
448
|
+
if (!name && !pkgJsonFile.includes("node_modules")) {
|
|
449
|
+
name = basename(dirname(pkgJsonFile));
|
|
450
|
+
}
|
|
451
|
+
const group = pkgIdentifier.scope || "";
|
|
452
|
+
const purl = build({
|
|
453
|
+
type: "npm",
|
|
454
|
+
namespace: group || null,
|
|
455
|
+
name: name,
|
|
456
|
+
version: pkgData.version || null,
|
|
457
|
+
});
|
|
458
|
+
const author = pkgData.author;
|
|
459
|
+
const authorString =
|
|
460
|
+
author instanceof Object
|
|
461
|
+
? `${author.name}${author.email ? ` <${author.email}>` : ""}${
|
|
462
|
+
author.url ? ` (${author.url})` : ""
|
|
463
|
+
}`
|
|
464
|
+
: author;
|
|
465
|
+
const apkg = {
|
|
466
|
+
name,
|
|
467
|
+
group,
|
|
468
|
+
version: pkgData.version,
|
|
469
|
+
description: pkgData.description,
|
|
470
|
+
purl: purl,
|
|
471
|
+
"bom-ref": decodeURIComponent(purl),
|
|
472
|
+
author: authorString,
|
|
473
|
+
license: pkgData.license,
|
|
474
|
+
};
|
|
475
|
+
if (pkgData.homepage) {
|
|
476
|
+
apkg.homepage = { url: pkgData.homepage };
|
|
477
|
+
}
|
|
478
|
+
if (pkgData.repository?.url) {
|
|
479
|
+
apkg.repository = { url: pkgData.repository.url };
|
|
480
|
+
}
|
|
481
|
+
if (!simple) {
|
|
482
|
+
apkg.properties = [
|
|
483
|
+
{
|
|
484
|
+
name: "internal:SrcFile",
|
|
485
|
+
value: pkgJsonFile,
|
|
486
|
+
},
|
|
487
|
+
];
|
|
488
|
+
apkg.evidence = {
|
|
489
|
+
identity: {
|
|
490
|
+
field: "purl",
|
|
491
|
+
confidence: 0.7,
|
|
492
|
+
methods: [
|
|
493
|
+
{
|
|
494
|
+
technique: "manifest-analysis",
|
|
495
|
+
confidence: 0.7,
|
|
496
|
+
value: pkgJsonFile,
|
|
497
|
+
},
|
|
498
|
+
],
|
|
499
|
+
},
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
if (securityProps) {
|
|
503
|
+
if (!apkg.properties) {
|
|
504
|
+
apkg.properties = [];
|
|
505
|
+
}
|
|
506
|
+
// Track executable binaries (potential code execution vectors)
|
|
507
|
+
if (pkgData.bin) {
|
|
508
|
+
const binValue =
|
|
509
|
+
typeof pkgData.bin === "object"
|
|
510
|
+
? Object.keys(pkgData.bin).join(", ")
|
|
511
|
+
: pkgData.bin;
|
|
512
|
+
apkg.properties.push({
|
|
513
|
+
name: "cdx:npm:bin",
|
|
514
|
+
value: binValue,
|
|
515
|
+
});
|
|
516
|
+
apkg.properties.push({
|
|
517
|
+
name: "cdx:npm:has_binary",
|
|
518
|
+
value: "true",
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
// Track lifecycle scripts (preinstall, postinstall, etc. - code execution risk)
|
|
522
|
+
if (pkgData.scripts && Object.keys(pkgData.scripts).length) {
|
|
523
|
+
const scriptNames = Object.keys(pkgData.scripts).join(", ");
|
|
524
|
+
const lifecycleAnalysis = analyzeNpmLifecycleScripts(
|
|
525
|
+
pkgJsonFile,
|
|
526
|
+
pkgData.scripts,
|
|
527
|
+
);
|
|
528
|
+
apkg.properties.push({
|
|
529
|
+
name: "cdx:npm:scripts",
|
|
530
|
+
value: scriptNames,
|
|
531
|
+
});
|
|
532
|
+
// Highlight build-related scripts for downstream tooling consumers
|
|
533
|
+
const buildScriptNames = Object.keys(pkgData.scripts).filter(
|
|
534
|
+
(scriptName) => NPM_BUILD_SCRIPT_NAME_PATTERN.test(scriptName),
|
|
535
|
+
);
|
|
536
|
+
if (buildScriptNames.length) {
|
|
537
|
+
apkg.properties.push({
|
|
538
|
+
name: "cdx:npm:buildScripts",
|
|
539
|
+
value: buildScriptNames.join(", "),
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
// Flag high-risk scripts specifically
|
|
543
|
+
const riskyScripts = lifecycleAnalysis.riskyScripts;
|
|
544
|
+
if (riskyScripts.length) {
|
|
545
|
+
apkg.properties.push({
|
|
546
|
+
name: "cdx:npm:hasInstallScript",
|
|
547
|
+
value: "true",
|
|
548
|
+
});
|
|
549
|
+
apkg.properties.push({
|
|
550
|
+
name: "cdx:npm:risky_scripts",
|
|
551
|
+
value: riskyScripts.join(", "),
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
if (lifecycleAnalysis.suspiciousScripts.length) {
|
|
555
|
+
apkg.properties.push({
|
|
556
|
+
name: "cdx:npm:hasSuspiciousLifecycleScript",
|
|
557
|
+
value: "true",
|
|
558
|
+
});
|
|
559
|
+
apkg.properties.push({
|
|
560
|
+
name: "cdx:npm:suspiciousLifecycleScripts",
|
|
561
|
+
value: lifecycleAnalysis.suspiciousScripts.join(", "),
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
if (lifecycleAnalysis.obfuscatedScripts.length) {
|
|
565
|
+
apkg.properties.push({
|
|
566
|
+
name: "cdx:npm:hasObfuscatedLifecycleScript",
|
|
567
|
+
value: "true",
|
|
568
|
+
});
|
|
569
|
+
apkg.properties.push({
|
|
570
|
+
name: "cdx:npm:obfuscatedLifecycleScripts",
|
|
571
|
+
value: lifecycleAnalysis.obfuscatedScripts.join(", "),
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
if (lifecycleAnalysis.obfuscationIndicators.length) {
|
|
575
|
+
apkg.properties.push({
|
|
576
|
+
name: "cdx:npm:lifecycleObfuscationIndicators",
|
|
577
|
+
value: lifecycleAnalysis.obfuscationIndicators.join(", "),
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
if (lifecycleAnalysis.executionIndicators.length) {
|
|
581
|
+
apkg.properties.push({
|
|
582
|
+
name: "cdx:npm:lifecycleExecutionIndicators",
|
|
583
|
+
value: lifecycleAnalysis.executionIndicators.join(", "),
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
if (lifecycleAnalysis.networkIndicators.length) {
|
|
587
|
+
apkg.properties.push({
|
|
588
|
+
name: "cdx:npm:lifecycleNetworkIndicators",
|
|
589
|
+
value: lifecycleAnalysis.networkIndicators.join(", "),
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
if (lifecycleAnalysis.scriptIndicatorMap.length) {
|
|
593
|
+
apkg.properties.push({
|
|
594
|
+
name: "cdx:npm:lifecycleIndicatorMap",
|
|
595
|
+
value: lifecycleAnalysis.scriptIndicatorMap.join(" | "),
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
// Track platform/architecture constraints
|
|
600
|
+
if (pkgData.cpu && Array.isArray(pkgData.cpu) && pkgData.cpu.length) {
|
|
601
|
+
apkg.properties.push({
|
|
602
|
+
name: "cdx:npm:cpu",
|
|
603
|
+
value: pkgData.cpu.join(", "),
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
if (pkgData.os && Array.isArray(pkgData.os) && pkgData.os.length) {
|
|
607
|
+
apkg.properties.push({
|
|
608
|
+
name: "cdx:npm:os",
|
|
609
|
+
value: pkgData.os.join(", "),
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
if (
|
|
613
|
+
pkgData.libc &&
|
|
614
|
+
Array.isArray(pkgData.libc) &&
|
|
615
|
+
pkgData.libc.length
|
|
616
|
+
) {
|
|
617
|
+
apkg.properties.push({
|
|
618
|
+
name: "cdx:npm:libc",
|
|
619
|
+
value: pkgData.libc.join(", "),
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
// Track deprecation notices
|
|
623
|
+
if (pkgData.deprecated) {
|
|
624
|
+
apkg.properties.push({
|
|
625
|
+
name: "cdx:npm:deprecation_notice",
|
|
626
|
+
value: pkgData.deprecated,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
// Track if package uses node-gyp (native C/C++ addons = higher risk)
|
|
630
|
+
if (
|
|
631
|
+
pkgData.gypfile === true ||
|
|
632
|
+
pkgData.files?.some((f) => f.endsWith(".gyp") || f.endsWith(".gypi"))
|
|
633
|
+
) {
|
|
634
|
+
apkg.properties.push({
|
|
635
|
+
name: "cdx:npm:gypfile",
|
|
636
|
+
value: "true",
|
|
637
|
+
});
|
|
638
|
+
apkg.properties.push({
|
|
639
|
+
name: "cdx:npm:native_addon",
|
|
640
|
+
value: "true",
|
|
641
|
+
});
|
|
642
|
+
const nativeDeps = [
|
|
643
|
+
"nan",
|
|
644
|
+
"node-addon-api",
|
|
645
|
+
"bindings",
|
|
646
|
+
"node-gyp-build",
|
|
647
|
+
];
|
|
648
|
+
const foundNativeDeps = Object.keys(
|
|
649
|
+
pkgData.dependencies || {},
|
|
650
|
+
).filter((dep) => nativeDeps.includes(dep));
|
|
651
|
+
if (foundNativeDeps.length) {
|
|
652
|
+
apkg.properties.push({
|
|
653
|
+
name: "cdx:npm:native_deps",
|
|
654
|
+
value: foundNativeDeps.join(", "),
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
pkgList.push(apkg);
|
|
660
|
+
} catch (_err) {
|
|
661
|
+
// continue regardless of error
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (!simple && shouldFetchPackageMetadata() && pkgList?.length) {
|
|
665
|
+
if (DEBUG_MODE) {
|
|
666
|
+
console.log(
|
|
667
|
+
`About to fetch npm registry metadata for ${pkgList.length} packages in parsePkgJson`,
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
return await getNpmMetadata(pkgList);
|
|
671
|
+
}
|
|
672
|
+
return pkgList;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// The four manifest fields that npm packageExtensions and .npm-extension
|
|
676
|
+
// transformManifest may add or correct. Mirrors EXTENSION_FIELDS in the
|
|
677
|
+
// vendored arborist's package-extensions.js and the upstream npm-extension.js
|
|
678
|
+
// at the pinned commit, so overlays and provenance stay in lockstep with npm.
|
|
679
|
+
const EXTENSION_FIELDS = [
|
|
680
|
+
"dependencies",
|
|
681
|
+
"optionalDependencies",
|
|
682
|
+
"peerDependencies",
|
|
683
|
+
"peerDependenciesMeta",
|
|
684
|
+
];
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Collect the dependency names that an extension provenance object says changed,
|
|
688
|
+
* sorted and comma-separated per the component-level list convention.
|
|
689
|
+
*
|
|
690
|
+
* @param {object|null|undefined} applied Provenance from arborist
|
|
691
|
+
* ({ selector|extensionPoint, dependencies?, optionalDependencies?, peerDependencies?, peerDependenciesMeta? })
|
|
692
|
+
* @returns {string|null} Sorted comma-separated names, or null when nothing changed.
|
|
693
|
+
*/
|
|
694
|
+
function formatExtensionAppliedList(applied) {
|
|
695
|
+
if (!applied || typeof applied !== "object") {
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
const names = new Set();
|
|
699
|
+
for (const field of EXTENSION_FIELDS) {
|
|
700
|
+
const changed = applied[field];
|
|
701
|
+
if (Array.isArray(changed)) {
|
|
702
|
+
for (const name of changed) {
|
|
703
|
+
if (typeof name === "string" && name) {
|
|
704
|
+
names.add(name);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
if (!names.size) {
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
return [...names].sort().join(", ");
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Hash a root `.npm-extension` file exactly as npm does: ssri sha512 over
|
|
717
|
+
* `npm-extension:v1:<format>\n` followed by the raw file bytes. Mirrors
|
|
718
|
+
* `hashFile` in the vendored arborist's upstream npm-extension.js so the value
|
|
719
|
+
* is comparable to what npm writes into the lockfile as `npmExtensionHash`.
|
|
720
|
+
*
|
|
721
|
+
* @param {string} format Either "mjs" or "cjs"
|
|
722
|
+
* @param {Buffer} bytes Raw file contents
|
|
723
|
+
* @returns {string} ssri integrity string
|
|
724
|
+
*/
|
|
725
|
+
export function hashNpmExtensionFile(format, bytes) {
|
|
726
|
+
return ssri
|
|
727
|
+
.fromData(
|
|
728
|
+
Buffer.concat([Buffer.from(`npm-extension:v1:${format}\n`), bytes]),
|
|
729
|
+
{ algorithms: ["sha512"] },
|
|
730
|
+
)
|
|
731
|
+
.toString();
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Detect a root-owned `.npm-extension.mjs` or `.npm-extension.cjs` file and
|
|
736
|
+
* return its format, path, and npm-comparable hash. Returns null when no file
|
|
737
|
+
* is present. A non-root workspace file is never consulted, matching upstream.
|
|
738
|
+
*
|
|
739
|
+
* @param {string} rootPath Real path of the project root (tree.realpath)
|
|
740
|
+
* @returns {{format: string, path: string, hash: string, duplicate: boolean}|null}
|
|
741
|
+
*/
|
|
742
|
+
export function detectRootNpmExtension(rootPath) {
|
|
743
|
+
const candidates = [
|
|
744
|
+
{ ext: "mjs", file: ".npm-extension.mjs" },
|
|
745
|
+
{ ext: "cjs", file: ".npm-extension.cjs" },
|
|
746
|
+
];
|
|
747
|
+
let found = null;
|
|
748
|
+
let duplicate = false;
|
|
749
|
+
for (const candidate of candidates) {
|
|
750
|
+
const filePath = join(rootPath, candidate.file);
|
|
751
|
+
if (safeExistsSync(filePath)) {
|
|
752
|
+
if (found) {
|
|
753
|
+
duplicate = true;
|
|
754
|
+
break;
|
|
755
|
+
}
|
|
756
|
+
found = { ext: candidate.ext, file: filePath };
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (!found) {
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
762
|
+
const bytes = readFileSync(found.file);
|
|
763
|
+
return {
|
|
764
|
+
format: found.ext,
|
|
765
|
+
path: found.file,
|
|
766
|
+
hash: hashNpmExtensionFile(found.ext, bytes),
|
|
767
|
+
duplicate,
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Overlay the four post-extension dependency fields from the lockfile onto each
|
|
773
|
+
* node whose lockfile entry records an `.npm-extension` repair. This reproduces
|
|
774
|
+
* npm's repaired edges exactly without executing any project-supplied code.
|
|
775
|
+
* The lockfile entries hold the post-extension manifest because npm writes
|
|
776
|
+
* `node.package` — already repaired at that point — into the lockfile.
|
|
777
|
+
*
|
|
778
|
+
* @param {object} tree The arborist root node from `loadActual`
|
|
779
|
+
*/
|
|
780
|
+
function applyNpmExtensionOverlay(tree) {
|
|
781
|
+
const sw = tree.meta;
|
|
782
|
+
const packages = sw?.data?.packages;
|
|
783
|
+
if (!packages) {
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
for (const node of tree.inventory.values()) {
|
|
787
|
+
if (node.isProjectRoot) {
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
const loc = node.location;
|
|
791
|
+
if (!loc) {
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
const swEntry = packages[loc];
|
|
795
|
+
if (!swEntry?.npmExtensionApplied) {
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
const merged = { ...node.package };
|
|
799
|
+
for (const field of EXTENSION_FIELDS) {
|
|
800
|
+
if (swEntry[field] !== undefined) {
|
|
801
|
+
merged[field] = swEntry[field];
|
|
802
|
+
} else {
|
|
803
|
+
delete merged[field];
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
node.package = merged;
|
|
807
|
+
node.npmExtensionApplied = swEntry.npmExtensionApplied;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Undo packageExtensions repairs applied by the vendored arborist during
|
|
813
|
+
* loadActual, so the BOM reflects manifests as published. Only the four fields
|
|
814
|
+
* an extension can touch are restored from the node's on-disk package.json;
|
|
815
|
+
* every other field arborist derived while building the tree is left alone.
|
|
816
|
+
* Reassigning `package` rebuilds the node's edges from the restored manifest.
|
|
817
|
+
*
|
|
818
|
+
* @param {object} tree The arborist root node from `loadActual`
|
|
819
|
+
*/
|
|
820
|
+
function undoPackageExtensions(tree) {
|
|
821
|
+
for (const node of tree.inventory.values()) {
|
|
822
|
+
if (!node.packageExtensionsApplied) {
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
let published;
|
|
826
|
+
try {
|
|
827
|
+
published = JSON.parse(
|
|
828
|
+
readFileSync(join(node.path, "package.json"), "utf8"),
|
|
829
|
+
);
|
|
830
|
+
} catch {
|
|
831
|
+
// Without a readable manifest there is nothing to restore from, so the
|
|
832
|
+
// repaired one stands and the provenance property keeps saying so.
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
const restored = { ...node.package };
|
|
836
|
+
for (const field of EXTENSION_FIELDS) {
|
|
837
|
+
if (published[field] === undefined) {
|
|
838
|
+
delete restored[field];
|
|
839
|
+
} else {
|
|
840
|
+
restored[field] = published[field];
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
node.package = restored;
|
|
844
|
+
node.packageExtensionsApplied = null;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Parse nodejs package lock file
|
|
850
|
+
*
|
|
851
|
+
* @param {string} pkgLockFile package-lock.json file
|
|
852
|
+
* @param {object} options Command line options
|
|
853
|
+
*/
|
|
854
|
+
export async function parsePkgLock(pkgLockFile, options = {}) {
|
|
855
|
+
let pkgList = [];
|
|
856
|
+
let dependenciesList = [];
|
|
857
|
+
if (!options) {
|
|
858
|
+
options = {};
|
|
859
|
+
}
|
|
860
|
+
const npmrcConfig = loadNpmrcConfig(
|
|
861
|
+
options.projectRoot || dirname(pkgLockFile),
|
|
862
|
+
);
|
|
863
|
+
const pkgSpecVersionCache = {};
|
|
864
|
+
if (!safeExistsSync(pkgLockFile)) {
|
|
865
|
+
return {
|
|
866
|
+
pkgList,
|
|
867
|
+
dependenciesList,
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
const parseArboristNode = (
|
|
872
|
+
node,
|
|
873
|
+
rootNode,
|
|
874
|
+
parentRef = null,
|
|
875
|
+
visited = new Set(),
|
|
876
|
+
pkgSpecVersionCache = {},
|
|
877
|
+
options = {},
|
|
878
|
+
) => {
|
|
879
|
+
if (visited.has(node)) {
|
|
880
|
+
return { pkgList: [], dependenciesList: [] };
|
|
881
|
+
}
|
|
882
|
+
visited.add(node);
|
|
883
|
+
let pkgList = [];
|
|
884
|
+
let dependenciesList = [];
|
|
885
|
+
|
|
886
|
+
// Create the package entry
|
|
887
|
+
const srcFilePath = node.path.includes(`${_sep}node_modules`)
|
|
888
|
+
? node.path.split(`${_sep}node_modules`)[0]
|
|
889
|
+
: node.path;
|
|
890
|
+
const isDevelopmentNode = node.dev === true || node.devOptional === true;
|
|
891
|
+
const scope =
|
|
892
|
+
isDevelopmentNode || node.optional === true ? "optional" : undefined;
|
|
893
|
+
const integrity = node.integrity ? node.integrity : undefined;
|
|
894
|
+
const { nodePackage, diskPkg, packageJsonPath } = hydrateNpmNodePackage(
|
|
895
|
+
node,
|
|
896
|
+
options,
|
|
897
|
+
);
|
|
898
|
+
|
|
899
|
+
let pkg;
|
|
900
|
+
let purlString;
|
|
901
|
+
const author = nodePackage.author;
|
|
902
|
+
const authorString =
|
|
903
|
+
author instanceof Object
|
|
904
|
+
? `${author.name}${author.email ? ` <${author.email}>` : ""}${
|
|
905
|
+
author.url ? ` (${author.url})` : ""
|
|
906
|
+
}`
|
|
907
|
+
: author;
|
|
908
|
+
if (node === rootNode) {
|
|
909
|
+
const projectGroup = options.projectGroup;
|
|
910
|
+
const projectName =
|
|
911
|
+
"project-name" in options ? options.projectName : node.packageName;
|
|
912
|
+
const projectVersion = options.projectVersion || node.version;
|
|
913
|
+
// A package.json need not declare a version, and npm installs without
|
|
914
|
+
// one, so the root of a lockfile can reach here with none. A purl carries
|
|
915
|
+
// no version rather than an empty one.
|
|
916
|
+
purlString = Purl.parse(
|
|
917
|
+
`pkg:npm/${projectGroup ? `${encodeURIComponent(projectGroup).replace(/%2F/g, "/")}/` : ""}${encodeURIComponent(projectName).replace(/%2F/g, "/")}${projectVersion ? `@${projectVersion}` : ""}`,
|
|
918
|
+
)
|
|
919
|
+
.toString()
|
|
920
|
+
.replace(/%2F/g, "/");
|
|
921
|
+
pkg = {
|
|
922
|
+
author: authorString,
|
|
923
|
+
group: options.projectGroup || "",
|
|
924
|
+
name:
|
|
925
|
+
"project-name" in options ? options.projectName : node.packageName,
|
|
926
|
+
version: options.projectVersion || node.version,
|
|
927
|
+
type: "application",
|
|
928
|
+
purl: purlString,
|
|
929
|
+
"bom-ref": decodeURIComponent(purlString),
|
|
930
|
+
properties: [],
|
|
931
|
+
};
|
|
932
|
+
if (lockfilePackageExtensionsHash) {
|
|
933
|
+
pkg.properties.push({
|
|
934
|
+
name: "cdx:npm:packageExtensionsHash",
|
|
935
|
+
value: lockfilePackageExtensionsHash,
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
if (extensionFile) {
|
|
939
|
+
pkg.properties.push({
|
|
940
|
+
name: "cdx:npm:extensionHash",
|
|
941
|
+
value: extensionFile.hash,
|
|
942
|
+
});
|
|
943
|
+
pkg.properties.push({
|
|
944
|
+
name: "cdx:npm:extensionFormat",
|
|
945
|
+
value: extensionFile.format,
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
if (extensionAppliedStatus !== null) {
|
|
949
|
+
pkg.properties.push({
|
|
950
|
+
name: "cdx:npm:extensionApplied",
|
|
951
|
+
value: extensionAppliedStatus,
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
if (noPackageExtensions && isDeep) {
|
|
955
|
+
pkg.properties.push({
|
|
956
|
+
name: "cdx:npm:packageExtensionsDisabled",
|
|
957
|
+
value: "true",
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
} else {
|
|
961
|
+
let namespace = "";
|
|
962
|
+
let name = node.packageName;
|
|
963
|
+
if (node.packageName.startsWith("@")) {
|
|
964
|
+
const slashIndex = node.packageName.indexOf("/");
|
|
965
|
+
if (slashIndex > 0) {
|
|
966
|
+
namespace = node.packageName.substring(0, slashIndex);
|
|
967
|
+
name = node.packageName.substring(slashIndex + 1);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
let qualifiers = null;
|
|
971
|
+
let extRefs = [];
|
|
972
|
+
const isGitDep =
|
|
973
|
+
node.resolved &&
|
|
974
|
+
(node.resolved.startsWith("git+") ||
|
|
975
|
+
node.resolved.startsWith("git://") ||
|
|
976
|
+
node.resolved.startsWith("ssh://") ||
|
|
977
|
+
node.resolved.startsWith("git@"));
|
|
978
|
+
if (isGitDep) {
|
|
979
|
+
qualifiers = buildNpmGitPurlQualifiers(
|
|
980
|
+
node.resolved,
|
|
981
|
+
namespace,
|
|
982
|
+
npmrcConfig,
|
|
983
|
+
);
|
|
984
|
+
const gitIntakeRefs = buildNpmGitDistributionIntakeRefs(
|
|
985
|
+
namespace,
|
|
986
|
+
name,
|
|
987
|
+
node.version,
|
|
988
|
+
npmrcConfig,
|
|
989
|
+
);
|
|
990
|
+
if (gitIntakeRefs) {
|
|
991
|
+
extRefs = extRefs.concat(gitIntakeRefs);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
purlString = build({
|
|
995
|
+
type: "npm",
|
|
996
|
+
namespace: namespace || null,
|
|
997
|
+
name: name,
|
|
998
|
+
version: node.version || null,
|
|
999
|
+
qualifiers: qualifiers || null,
|
|
1000
|
+
});
|
|
1001
|
+
const pkgLockFile = join(
|
|
1002
|
+
srcFilePath.replace("/", _sep),
|
|
1003
|
+
"package-lock.json",
|
|
1004
|
+
);
|
|
1005
|
+
pkg = {
|
|
1006
|
+
group: namespace,
|
|
1007
|
+
name: name,
|
|
1008
|
+
version: node.version,
|
|
1009
|
+
author: authorString,
|
|
1010
|
+
scope: scope,
|
|
1011
|
+
_integrity: integrity,
|
|
1012
|
+
externalReferences: extRefs,
|
|
1013
|
+
properties: [
|
|
1014
|
+
{
|
|
1015
|
+
name: "internal:SrcFile",
|
|
1016
|
+
value: pkgLockFile,
|
|
1017
|
+
},
|
|
1018
|
+
],
|
|
1019
|
+
evidence: {
|
|
1020
|
+
identity: {
|
|
1021
|
+
field: "purl",
|
|
1022
|
+
confidence: 1,
|
|
1023
|
+
methods: [
|
|
1024
|
+
{
|
|
1025
|
+
technique: "manifest-analysis",
|
|
1026
|
+
confidence: 1,
|
|
1027
|
+
value: pkgLockFile,
|
|
1028
|
+
},
|
|
1029
|
+
],
|
|
1030
|
+
},
|
|
1031
|
+
},
|
|
1032
|
+
type: parentRef ? "npm" : "application",
|
|
1033
|
+
purl: purlString,
|
|
1034
|
+
"bom-ref": decodeURIComponent(purlString),
|
|
1035
|
+
};
|
|
1036
|
+
if (isDevelopmentNode) {
|
|
1037
|
+
setNpmDevelopmentProperty(pkg);
|
|
1038
|
+
}
|
|
1039
|
+
if (node.optional === true) {
|
|
1040
|
+
setNpmOptionalProperty(pkg);
|
|
1041
|
+
}
|
|
1042
|
+
if (node.peer === true) {
|
|
1043
|
+
setNpmPeerProperty(pkg);
|
|
1044
|
+
}
|
|
1045
|
+
if (node.resolved) {
|
|
1046
|
+
if (node.resolved.startsWith("file:")) {
|
|
1047
|
+
pkg.properties.push({
|
|
1048
|
+
name: "cdx:npm:resolvedPath",
|
|
1049
|
+
value: node.realpath
|
|
1050
|
+
? relative(dirname(pkgLockFile), node.realpath)
|
|
1051
|
+
: relative(
|
|
1052
|
+
dirname(pkgLockFile),
|
|
1053
|
+
resolve(node.resolved.replace("file:", "")),
|
|
1054
|
+
),
|
|
1055
|
+
});
|
|
1056
|
+
} else {
|
|
1057
|
+
pkg.properties.push({
|
|
1058
|
+
name: "internal:ResolvedUrl",
|
|
1059
|
+
value: node.resolved,
|
|
1060
|
+
});
|
|
1061
|
+
pkg.externalReferences.push({
|
|
1062
|
+
type: "distribution",
|
|
1063
|
+
url: node.resolved,
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
if (node.location) {
|
|
1068
|
+
pkg.properties.push({
|
|
1069
|
+
name: "internal:LocalNodeModulesPath",
|
|
1070
|
+
value: node.location,
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
if (node?.installLinks) {
|
|
1074
|
+
pkg.properties.push({
|
|
1075
|
+
name: "cdx:npm:installLinks",
|
|
1076
|
+
value: "true",
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
if (node?.binPaths?.length) {
|
|
1080
|
+
pkg.properties.push({
|
|
1081
|
+
name: "cdx:npm:binPaths",
|
|
1082
|
+
value: node.binPaths.join(", "),
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
if (nodePackage?.bin) {
|
|
1086
|
+
const binValue =
|
|
1087
|
+
typeof nodePackage.bin === "object"
|
|
1088
|
+
? Object.keys(nodePackage.bin).join(", ")
|
|
1089
|
+
: nodePackage.bin;
|
|
1090
|
+
pkg.properties.push({
|
|
1091
|
+
name: "cdx:npm:bin",
|
|
1092
|
+
value: binValue,
|
|
1093
|
+
});
|
|
1094
|
+
pkg.properties.push({
|
|
1095
|
+
name: "cdx:npm:has_binary",
|
|
1096
|
+
value: "true",
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
if (node?.hasInstallScript) {
|
|
1100
|
+
pkg.properties.push({
|
|
1101
|
+
name: "cdx:npm:hasInstallScript",
|
|
1102
|
+
value: "true",
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
if (node?.isLink) {
|
|
1106
|
+
pkg.properties.push({
|
|
1107
|
+
name: "cdx:npm:isLink",
|
|
1108
|
+
value: "true",
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
const npmManifestSources = collectNpmManifestSources(node);
|
|
1112
|
+
if (npmManifestSources.length) {
|
|
1113
|
+
addComponentProperty(
|
|
1114
|
+
pkg,
|
|
1115
|
+
"cdx:npm:manifestSourceType",
|
|
1116
|
+
npmManifestSources
|
|
1117
|
+
.map((manifestSource) => manifestSource.type)
|
|
1118
|
+
.join(","),
|
|
1119
|
+
);
|
|
1120
|
+
addComponentProperty(
|
|
1121
|
+
pkg,
|
|
1122
|
+
"cdx:npm:manifestSource",
|
|
1123
|
+
npmManifestSources
|
|
1124
|
+
.map((manifestSource) => manifestSource.value)
|
|
1125
|
+
.join(","),
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
// This getter method could fail with errors at times.
|
|
1129
|
+
// Example Error: Invalid tag name "^>=6.0.0" of package "^>=6.0.0": Tags may not have any characters that encodeURIComponent encodes.
|
|
1130
|
+
try {
|
|
1131
|
+
if (!node?.isRegistryDependency) {
|
|
1132
|
+
pkg.properties.push({
|
|
1133
|
+
name: "cdx:npm:isRegistryDependency",
|
|
1134
|
+
value: "false",
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
} catch (_err) {
|
|
1138
|
+
// ignore
|
|
1139
|
+
}
|
|
1140
|
+
if (node?.isWorkspace) {
|
|
1141
|
+
pkg.properties.push({
|
|
1142
|
+
name: "cdx:npm:isWorkspace",
|
|
1143
|
+
value: "true",
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
// Detect version spoofing by comparing the version in the lockfile with the version in package.json
|
|
1147
|
+
if (packageJsonPath && safeExistsSync(packageJsonPath)) {
|
|
1148
|
+
try {
|
|
1149
|
+
const onDiskPkg =
|
|
1150
|
+
diskPkg || JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
1151
|
+
if (!onDiskPkg.name || onDiskPkg.name !== node.packageName) {
|
|
1152
|
+
console.warn(
|
|
1153
|
+
`\x1b[1;35mWARNING: Package name spoofing detected for ${node.packageName}! Lockfile says ${node.packageName}, but disk says ${onDiskPkg.name}.\x1b[0m`,
|
|
1154
|
+
);
|
|
1155
|
+
if (onDiskPkg.name) {
|
|
1156
|
+
pkg.properties.push({
|
|
1157
|
+
name: "cdx:npm:nameMismatchError",
|
|
1158
|
+
value: `${onDiskPkg.name} used instead of ${node.packageName}`,
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
if (!onDiskPkg.version || onDiskPkg.version !== node.version) {
|
|
1163
|
+
console.warn(
|
|
1164
|
+
`\x1b[1;35mWARNING: Package version spoofing detected for ${node.packageName}! Lockfile says ${node.version}, but disk says ${onDiskPkg.version}.\x1b[0m`,
|
|
1165
|
+
);
|
|
1166
|
+
if (onDiskPkg.version) {
|
|
1167
|
+
pkg.properties.push({
|
|
1168
|
+
name: "cdx:npm:versionMismatchError",
|
|
1169
|
+
value: `${onDiskPkg.version} used instead of ${node.version}`,
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
} catch (_err) {
|
|
1174
|
+
// ignore
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
if (node?.inBundle) {
|
|
1178
|
+
pkg.properties.push({
|
|
1179
|
+
name: "cdx:npm:inBundle",
|
|
1180
|
+
value: "true",
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
if (node?.inDepBundle) {
|
|
1184
|
+
pkg.properties.push({
|
|
1185
|
+
name: "cdx:npm:inDepBundle",
|
|
1186
|
+
value: "true",
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
const packageExtensionsAppliedList = formatExtensionAppliedList(
|
|
1190
|
+
node.packageExtensionsApplied,
|
|
1191
|
+
);
|
|
1192
|
+
if (packageExtensionsAppliedList) {
|
|
1193
|
+
pkg.properties.push({
|
|
1194
|
+
name: "cdx:npm:packageExtensionsApplied",
|
|
1195
|
+
value: packageExtensionsAppliedList,
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
const npmExtensionAppliedList = formatExtensionAppliedList(
|
|
1199
|
+
node.npmExtensionApplied,
|
|
1200
|
+
);
|
|
1201
|
+
if (npmExtensionAppliedList) {
|
|
1202
|
+
pkg.properties.push({
|
|
1203
|
+
name: "cdx:npm:extensionFieldsApplied",
|
|
1204
|
+
value: npmExtensionAppliedList,
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
if (nodePackage?.repository?.url) {
|
|
1208
|
+
pkg.externalReferences.push({
|
|
1209
|
+
type: "vcs",
|
|
1210
|
+
url: nodePackage.repository.url,
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
if (nodePackage?.bugs?.url) {
|
|
1214
|
+
pkg.externalReferences.push({
|
|
1215
|
+
type: "issue-tracker",
|
|
1216
|
+
url: nodePackage.bugs.url,
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
if (nodePackage?.keywords?.length) {
|
|
1220
|
+
pkg.tags = Array.isArray(nodePackage.keywords)
|
|
1221
|
+
? nodePackage.keywords.sort()
|
|
1222
|
+
: nodePackage.keywords.split(",");
|
|
1223
|
+
}
|
|
1224
|
+
if (nodePackage?.description) {
|
|
1225
|
+
pkg.description = nodePackage.description;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
if (nodePackage?.license) {
|
|
1229
|
+
// License will be overridden if shouldFetchLicense() is enabled
|
|
1230
|
+
pkg.license = nodePackage.license;
|
|
1231
|
+
}
|
|
1232
|
+
const deprecatedMessage = nodePackage?.deprecated;
|
|
1233
|
+
if (deprecatedMessage) {
|
|
1234
|
+
pkg.properties.push({
|
|
1235
|
+
name: "cdx:npm:deprecated",
|
|
1236
|
+
value: deprecatedMessage,
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
if (!pkg.properties.length) {
|
|
1240
|
+
delete pkg.properties;
|
|
1241
|
+
}
|
|
1242
|
+
pkgList.push(pkg);
|
|
1243
|
+
// retrieve workspace node pkglists
|
|
1244
|
+
const workspaceDependsOn = [];
|
|
1245
|
+
if (node.fsChildren && node.fsChildren.size > 0) {
|
|
1246
|
+
for (const workspaceNode of node.fsChildren) {
|
|
1247
|
+
const {
|
|
1248
|
+
pkgList: childPkgList,
|
|
1249
|
+
dependenciesList: childDependenciesList,
|
|
1250
|
+
} = parseArboristNode(
|
|
1251
|
+
workspaceNode,
|
|
1252
|
+
rootNode,
|
|
1253
|
+
purlString,
|
|
1254
|
+
visited,
|
|
1255
|
+
pkgSpecVersionCache,
|
|
1256
|
+
options,
|
|
1257
|
+
);
|
|
1258
|
+
pkgList = pkgList.concat(childPkgList);
|
|
1259
|
+
dependenciesList = dependenciesList.concat(childDependenciesList);
|
|
1260
|
+
let depWorkspacePurlString = decodeURIComponent(
|
|
1261
|
+
new Purl({
|
|
1262
|
+
type: "npm",
|
|
1263
|
+
namespace: "" || null,
|
|
1264
|
+
name: workspaceNode.name,
|
|
1265
|
+
version: workspaceNode.version || null,
|
|
1266
|
+
})
|
|
1267
|
+
.toString()
|
|
1268
|
+
.replace(/%2F/g, "/"),
|
|
1269
|
+
);
|
|
1270
|
+
let purlStringFromPkgid;
|
|
1271
|
+
if (workspaceNode.pkgid) {
|
|
1272
|
+
purlStringFromPkgid = `pkg:npm/${workspaceNode.pkgid.replace(`${workspaceNode.name}@npm:`, "")}`;
|
|
1273
|
+
}
|
|
1274
|
+
if (
|
|
1275
|
+
purlStringFromPkgid &&
|
|
1276
|
+
purlStringFromPkgid !== depWorkspacePurlString
|
|
1277
|
+
) {
|
|
1278
|
+
if (DEBUG_MODE) {
|
|
1279
|
+
console.log(
|
|
1280
|
+
`Internal warning: Got two different refs for this workspace node: ${depWorkspacePurlString} and ${purlStringFromPkgid}. Assuming the bom-ref as ${purlStringFromPkgid} based on pkgid.`,
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
depWorkspacePurlString = purlStringFromPkgid;
|
|
1284
|
+
}
|
|
1285
|
+
if (decodeURIComponent(purlString) !== depWorkspacePurlString) {
|
|
1286
|
+
workspaceDependsOn.push(depWorkspacePurlString);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
// this handles the case when a node has ["dependencies"] key in a package-lock.json
|
|
1292
|
+
// for a node. We exclude the root node because it's already been handled
|
|
1293
|
+
// If the node has "requires", we don't have to track the "dependencies"
|
|
1294
|
+
const childrenDependsOn = [];
|
|
1295
|
+
if (node !== rootNode && !node.edgesOut.size) {
|
|
1296
|
+
for (const child of node.children) {
|
|
1297
|
+
const childNode = child[1];
|
|
1298
|
+
const {
|
|
1299
|
+
pkgList: childPkgList,
|
|
1300
|
+
dependenciesList: childDependenciesList,
|
|
1301
|
+
} = parseArboristNode(
|
|
1302
|
+
childNode,
|
|
1303
|
+
rootNode,
|
|
1304
|
+
decodeURIComponent(purlString),
|
|
1305
|
+
visited,
|
|
1306
|
+
pkgSpecVersionCache,
|
|
1307
|
+
options,
|
|
1308
|
+
);
|
|
1309
|
+
pkgList = pkgList.concat(childPkgList);
|
|
1310
|
+
dependenciesList = dependenciesList.concat(childDependenciesList);
|
|
1311
|
+
const depChildString = decodeURIComponent(
|
|
1312
|
+
new Purl({
|
|
1313
|
+
type: "npm",
|
|
1314
|
+
namespace: "" || null,
|
|
1315
|
+
name: childNode.name,
|
|
1316
|
+
version: childNode.version || null,
|
|
1317
|
+
})
|
|
1318
|
+
.toString()
|
|
1319
|
+
.replace(/%2F/g, "/"),
|
|
1320
|
+
);
|
|
1321
|
+
if (decodeURIComponent(purlString) !== depChildString) {
|
|
1322
|
+
childrenDependsOn.push(depChildString);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
// this handles the case when a node has a ["requires"] key
|
|
1328
|
+
const pkgDependsOn = [];
|
|
1329
|
+
for (const edge of node.edgesOut.values()) {
|
|
1330
|
+
let targetVersion;
|
|
1331
|
+
let targetName;
|
|
1332
|
+
let foundMatch = false;
|
|
1333
|
+
// This cache is required to help us down the line.
|
|
1334
|
+
if (edge?.to?.version && edge?.spec) {
|
|
1335
|
+
pkgSpecVersionCache[`${edge.name}-${edge.spec}`] = edge.to.version;
|
|
1336
|
+
}
|
|
1337
|
+
// if the edge doesn't have an integrity, it's likely a peer dependency
|
|
1338
|
+
// which isn't installed
|
|
1339
|
+
// Bug #795. At times, npm loses the integrity node completely and such packages are getting missed out
|
|
1340
|
+
// To keep things safe, we include these packages.
|
|
1341
|
+
let edgeToIntegrityOrLocation = edge.to ? edge.to.integrity : undefined;
|
|
1342
|
+
// Fallback to location based lookups when integrity is missing
|
|
1343
|
+
if (!edgeToIntegrityOrLocation && edge.to && edge.to.location) {
|
|
1344
|
+
edgeToIntegrityOrLocation = edge.to.location;
|
|
1345
|
+
}
|
|
1346
|
+
if (!edgeToIntegrityOrLocation) {
|
|
1347
|
+
// This hack is required to fix the package name
|
|
1348
|
+
targetName = edge.name.replace(/-cjs$/, "");
|
|
1349
|
+
foundMatch = false;
|
|
1350
|
+
} else {
|
|
1351
|
+
// the edges don't actually contain a version, so we need to search the root node
|
|
1352
|
+
// children to find the correct version. we check the node children first, then
|
|
1353
|
+
// we check the root node children
|
|
1354
|
+
for (const child of node.children) {
|
|
1355
|
+
if (edgeToIntegrityOrLocation) {
|
|
1356
|
+
if (
|
|
1357
|
+
child[1].integrity === edgeToIntegrityOrLocation ||
|
|
1358
|
+
child[1].location === edgeToIntegrityOrLocation
|
|
1359
|
+
) {
|
|
1360
|
+
targetName = child[0].replace(/node_modules\//g, "");
|
|
1361
|
+
// The package name could be different from the targetName retrieved
|
|
1362
|
+
// Eg: "string-width-cjs": "npm:string-width@^4.2.0",
|
|
1363
|
+
if (child[1].packageName && child[1].packageName !== targetName) {
|
|
1364
|
+
targetName = child[1].packageName;
|
|
1365
|
+
}
|
|
1366
|
+
targetVersion = child[1].version;
|
|
1367
|
+
foundMatch = true;
|
|
1368
|
+
break;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
if (!foundMatch) {
|
|
1374
|
+
for (const child of rootNode.children) {
|
|
1375
|
+
if (
|
|
1376
|
+
edgeToIntegrityOrLocation &&
|
|
1377
|
+
(child[1].integrity === edgeToIntegrityOrLocation ||
|
|
1378
|
+
child[1].location === edgeToIntegrityOrLocation)
|
|
1379
|
+
) {
|
|
1380
|
+
targetName = child[0].replace(/node_modules\//g, "");
|
|
1381
|
+
targetVersion = child[1].version;
|
|
1382
|
+
// The package name could be different from the targetName retrieved
|
|
1383
|
+
// "string-width-cjs": "npm:string-width@^4.2.0",
|
|
1384
|
+
if (child[1].packageName && child[1].packageName !== targetName) {
|
|
1385
|
+
targetName = child[1].packageName;
|
|
1386
|
+
}
|
|
1387
|
+
break;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
if (!targetVersion || !targetName) {
|
|
1391
|
+
if (pkgSpecVersionCache[`${edge.name}-${edge.spec}`]) {
|
|
1392
|
+
targetVersion = pkgSpecVersionCache[`${edge.name}-${edge.spec}`];
|
|
1393
|
+
targetName = edge.name.replace(/-cjs$/, "");
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// if we can't find the version of the edge, continue
|
|
1399
|
+
// it may be an optional peer dependency
|
|
1400
|
+
if (!targetVersion || !targetName) {
|
|
1401
|
+
if (
|
|
1402
|
+
DEBUG_MODE &&
|
|
1403
|
+
!options.deep &&
|
|
1404
|
+
!["optional", "peer", "peerOptional"].includes(edge?.type)
|
|
1405
|
+
) {
|
|
1406
|
+
if (!targetVersion) {
|
|
1407
|
+
console.log(
|
|
1408
|
+
`Unable to determine the version for the dependency ${edge.name} from the path ${edge?.from?.path}. This is likely an edge case that is not handled.`,
|
|
1409
|
+
edge,
|
|
1410
|
+
);
|
|
1411
|
+
} else if (!targetName) {
|
|
1412
|
+
console.log(
|
|
1413
|
+
`Unable to determine the name for the dependency from the edge from the path ${edge?.from?.path}. This is likely an edge case that is not handled.`,
|
|
1414
|
+
edge,
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
// juice-shop
|
|
1419
|
+
// Lock files created with --legacy-peer-deps will have certain peer dependencies missing
|
|
1420
|
+
// This flags any non-missing peers
|
|
1421
|
+
if (DEBUG_MODE && edge?.type === "peer" && edge?.error !== "MISSING") {
|
|
1422
|
+
console.log(
|
|
1423
|
+
`Unable to determine the version for the dependency ${edge.name} from the path ${edge?.from?.path}. This is likely an edge case that is not handled.`,
|
|
1424
|
+
edge,
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1427
|
+
continue;
|
|
1428
|
+
}
|
|
1429
|
+
const depPurlString = decodeURIComponent(
|
|
1430
|
+
npmPurl(targetName, targetVersion),
|
|
1431
|
+
);
|
|
1432
|
+
if (decodeURIComponent(purlString) !== depPurlString) {
|
|
1433
|
+
pkgDependsOn.push(depPurlString);
|
|
1434
|
+
}
|
|
1435
|
+
if (edge.to == null) {
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
const { pkgList: childPkgList, dependenciesList: childDependenciesList } =
|
|
1439
|
+
parseArboristNode(
|
|
1440
|
+
edge.to,
|
|
1441
|
+
rootNode,
|
|
1442
|
+
decodeURIComponent(purlString),
|
|
1443
|
+
visited,
|
|
1444
|
+
pkgSpecVersionCache,
|
|
1445
|
+
options,
|
|
1446
|
+
);
|
|
1447
|
+
pkgList = pkgList.concat(childPkgList);
|
|
1448
|
+
dependenciesList = dependenciesList.concat(childDependenciesList);
|
|
1449
|
+
}
|
|
1450
|
+
dependenciesList.push({
|
|
1451
|
+
ref: decodeURIComponent(purlString),
|
|
1452
|
+
dependsOn: [
|
|
1453
|
+
...new Set(
|
|
1454
|
+
workspaceDependsOn.concat(childrenDependsOn).concat(pkgDependsOn),
|
|
1455
|
+
),
|
|
1456
|
+
].sort(),
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
return { pkgList, dependenciesList };
|
|
1460
|
+
};
|
|
1461
|
+
|
|
1462
|
+
let arb = new Arborist({
|
|
1463
|
+
path: path.dirname(pkgLockFile),
|
|
1464
|
+
// legacyPeerDeps=false enables npm >v3 package dependency resolution
|
|
1465
|
+
legacyPeerDeps: false,
|
|
1466
|
+
});
|
|
1467
|
+
let tree;
|
|
1468
|
+
try {
|
|
1469
|
+
const rootNodeModulesDir = join(path.dirname(pkgLockFile), "node_modules");
|
|
1470
|
+
if (safeExistsSync(rootNodeModulesDir)) {
|
|
1471
|
+
if (options.deep) {
|
|
1472
|
+
console.log(
|
|
1473
|
+
`Constructing the actual dependency hierarchy from ${rootNodeModulesDir}.`,
|
|
1474
|
+
);
|
|
1475
|
+
tree = await arb.loadActual();
|
|
1476
|
+
} else {
|
|
1477
|
+
if (DEBUG_MODE) {
|
|
1478
|
+
console.log(
|
|
1479
|
+
"Constructing virtual dependency tree based on the lock file. Pass --deep argument to construct the actual dependency tree from disk.",
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1482
|
+
tree = await arb.loadVirtual();
|
|
1483
|
+
}
|
|
1484
|
+
} else {
|
|
1485
|
+
tree = await arb.loadVirtual();
|
|
1486
|
+
}
|
|
1487
|
+
} catch (e) {
|
|
1488
|
+
console.log(e);
|
|
1489
|
+
console.log(
|
|
1490
|
+
`Unable to parse ${pkgLockFile} without legacy peer dependencies. Retrying ...`,
|
|
1491
|
+
);
|
|
1492
|
+
if (DEBUG_MODE) {
|
|
1493
|
+
console.log(e);
|
|
1494
|
+
}
|
|
1495
|
+
try {
|
|
1496
|
+
arb = new Arborist({
|
|
1497
|
+
path: path.dirname(pkgLockFile),
|
|
1498
|
+
legacyPeerDeps: true,
|
|
1499
|
+
});
|
|
1500
|
+
tree = await arb.loadVirtual();
|
|
1501
|
+
} catch (e) {
|
|
1502
|
+
console.log(e);
|
|
1503
|
+
console.log(
|
|
1504
|
+
`Unable to parse ${pkgLockFile} in legacy and non-legacy mode. The resulting SBOM would be incomplete.`,
|
|
1505
|
+
);
|
|
1506
|
+
if (DEBUG_MODE) {
|
|
1507
|
+
console.log(e);
|
|
1508
|
+
}
|
|
1509
|
+
return { pkgList, dependenciesList };
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
if (!tree) {
|
|
1513
|
+
return { pkgList, dependenciesList };
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
const isDeep = Boolean(options.deep);
|
|
1517
|
+
const noPackageExtensions = options.packageExtensions === false;
|
|
1518
|
+
const lockfilePackageExtensionsHash =
|
|
1519
|
+
tree.meta?.packageExtensionsHash || null;
|
|
1520
|
+
const lockfileNpmExtensionHash = tree.meta?.npmExtensionHash || null;
|
|
1521
|
+
|
|
1522
|
+
let extensionFile = null;
|
|
1523
|
+
try {
|
|
1524
|
+
extensionFile = detectRootNpmExtension(tree.realpath);
|
|
1525
|
+
} catch (e) {
|
|
1526
|
+
if (DEBUG_MODE) {
|
|
1527
|
+
console.log(`Failed to inspect .npm-extension: ${e.message}`);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// Whether the repairs the root .npm-extension defines are reflected in this
|
|
1532
|
+
// BOM. null when no extension file is present, so an absent property always
|
|
1533
|
+
// means "no extension", never "we did not look".
|
|
1534
|
+
//
|
|
1535
|
+
// The lockfile is the only source for those repairs, since cdxgen never
|
|
1536
|
+
// executes the file. A lockfile written against the same file bytes carries
|
|
1537
|
+
// the repaired dependency fields, whichever load path built the tree:
|
|
1538
|
+
// loadVirtual reads them directly, loadActual overlays them onto the
|
|
1539
|
+
// manifests found on disk. So the hash comparison, not the load path, decides
|
|
1540
|
+
// the status.
|
|
1541
|
+
let extensionAppliedStatus = null;
|
|
1542
|
+
if (extensionFile) {
|
|
1543
|
+
if (extensionFile.duplicate) {
|
|
1544
|
+
console.warn(
|
|
1545
|
+
"\x1b[1;33mWARNING: Found both .npm-extension.mjs and .npm-extension.cjs at the project root. Using the .mjs file.\x1b[0m",
|
|
1546
|
+
);
|
|
1547
|
+
}
|
|
1548
|
+
if (!lockfileNpmExtensionHash) {
|
|
1549
|
+
extensionAppliedStatus = "false";
|
|
1550
|
+
console.warn(
|
|
1551
|
+
"\x1b[1;33mWARNING: A root .npm-extension file is present but the lockfile does not record one, and cdxgen does not execute project-supplied code. " +
|
|
1552
|
+
"The dependency graph may be incomplete because the manifest repairs the extension defines are not reflected.\x1b[0m",
|
|
1553
|
+
);
|
|
1554
|
+
} else if (extensionFile.hash !== lockfileNpmExtensionHash) {
|
|
1555
|
+
extensionAppliedStatus = "unverified";
|
|
1556
|
+
console.warn(
|
|
1557
|
+
"\x1b[1;33mWARNING: The root .npm-extension file has changed since the lockfile was written. " +
|
|
1558
|
+
"The dependency graph is reported as unverified because the on-disk hash does not match the lockfile.\x1b[0m",
|
|
1559
|
+
);
|
|
1560
|
+
} else {
|
|
1561
|
+
if (isDeep) {
|
|
1562
|
+
applyNpmExtensionOverlay(tree);
|
|
1563
|
+
}
|
|
1564
|
+
extensionAppliedStatus = "true";
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
if (noPackageExtensions && isDeep) {
|
|
1569
|
+
undoPackageExtensions(tree);
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
({ pkgList, dependenciesList } = parseArboristNode(
|
|
1573
|
+
tree,
|
|
1574
|
+
tree,
|
|
1575
|
+
null,
|
|
1576
|
+
new Set(),
|
|
1577
|
+
pkgSpecVersionCache,
|
|
1578
|
+
options,
|
|
1579
|
+
));
|
|
1580
|
+
|
|
1581
|
+
if (shouldFetchPackageMetadata() && pkgList?.length) {
|
|
1582
|
+
if (DEBUG_MODE) {
|
|
1583
|
+
console.log(
|
|
1584
|
+
`About to fetch npm registry metadata for ${pkgList.length} packages in parsePkgLock`,
|
|
1585
|
+
);
|
|
1586
|
+
}
|
|
1587
|
+
pkgList = await getNpmMetadata(pkgList);
|
|
1588
|
+
return { pkgList, dependenciesList };
|
|
1589
|
+
}
|
|
1590
|
+
return {
|
|
1591
|
+
pkgList,
|
|
1592
|
+
dependenciesList,
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
/**
|
|
1597
|
+
* Given a lock file this method would return an Object with the identity as the key and parsed name and value
|
|
1598
|
+
* eg: "@actions/core@^1.2.6", "@actions/core@^1.6.0":
|
|
1599
|
+
* version "1.6.0"
|
|
1600
|
+
* would result in two entries
|
|
1601
|
+
*
|
|
1602
|
+
* @param {string} lockData Yarn Lockfile data
|
|
1603
|
+
*/
|
|
1604
|
+
export function yarnLockToIdentMap(lockData) {
|
|
1605
|
+
const identMap = {};
|
|
1606
|
+
let currentIdents = [];
|
|
1607
|
+
lockData.split("\n").forEach((l) => {
|
|
1608
|
+
l = l.replaceAll("\r", "");
|
|
1609
|
+
if (l === "\n" || !l.length || l.startsWith("#")) {
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
// "@actions/core@^1.2.6", "@actions/core@^1.6.0":
|
|
1613
|
+
if (!l.startsWith(" ") && l.trim().length > 0) {
|
|
1614
|
+
const tmpA = l.replace(/["']/g, "").split(", ");
|
|
1615
|
+
if (tmpA?.length) {
|
|
1616
|
+
for (let s of tmpA) {
|
|
1617
|
+
if (!s.startsWith("__")) {
|
|
1618
|
+
if (s.endsWith(":")) {
|
|
1619
|
+
s = s.substring(0, s.length - 1);
|
|
1620
|
+
}
|
|
1621
|
+
// Non-strict mode parsing
|
|
1622
|
+
const match = s.match(/^(?:(@[^/]+?)\/)?([^/]+?)(?:@(.+))?$/);
|
|
1623
|
+
if (!match) {
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
let [, group, name, range] = match;
|
|
1627
|
+
if (group) {
|
|
1628
|
+
group = `${group}/`;
|
|
1629
|
+
}
|
|
1630
|
+
// "lru-cache@npm:^6.0.0":
|
|
1631
|
+
// "string-width-cjs@npm:string-width@^4.2.0":
|
|
1632
|
+
// Here range can be
|
|
1633
|
+
// - npm:^6.0.0
|
|
1634
|
+
// - npm:@types/ioredis@^4.28.10
|
|
1635
|
+
// - npm:strip-ansi@^6.0.1
|
|
1636
|
+
// See test cases with yarn3.lock and yarn6.lock
|
|
1637
|
+
if (range?.startsWith("npm:")) {
|
|
1638
|
+
if (range.includes("@")) {
|
|
1639
|
+
range = range.split("@").slice(-1)[0];
|
|
1640
|
+
} else {
|
|
1641
|
+
range = range.replace("npm:", "");
|
|
1642
|
+
}
|
|
1643
|
+
if (
|
|
1644
|
+
!l.includes(" ") &&
|
|
1645
|
+
l.includes("@npm:") &&
|
|
1646
|
+
l.match(/@/g).length === 2
|
|
1647
|
+
) {
|
|
1648
|
+
const newName = l
|
|
1649
|
+
.split("@npm:")
|
|
1650
|
+
.pop()
|
|
1651
|
+
.replace(`@${range}`, "")
|
|
1652
|
+
.replace(/[:"']/g, "");
|
|
1653
|
+
if (
|
|
1654
|
+
!newName.startsWith("*") &&
|
|
1655
|
+
!newName.includes(range) &&
|
|
1656
|
+
newName.length > name.length
|
|
1657
|
+
) {
|
|
1658
|
+
name = newName;
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
currentIdents.push(`${group || ""}${name}|${range}`);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
} else if (
|
|
1667
|
+
(l.startsWith(" version") || l.startsWith(' "version')) &&
|
|
1668
|
+
currentIdents.length
|
|
1669
|
+
) {
|
|
1670
|
+
const tmpA = l.replace(/"/g, "").split(" ");
|
|
1671
|
+
const version = tmpA[tmpA.length - 1].trim();
|
|
1672
|
+
for (const id of currentIdents) {
|
|
1673
|
+
identMap[id] = version;
|
|
1674
|
+
}
|
|
1675
|
+
currentIdents = [];
|
|
1676
|
+
}
|
|
1677
|
+
});
|
|
1678
|
+
return identMap;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
function _parseYarnLine(l) {
|
|
1682
|
+
let name = "";
|
|
1683
|
+
let group = "";
|
|
1684
|
+
let fullName;
|
|
1685
|
+
// A yarn lock key is `<name>@<descriptor>`. The first "@" that is not the
|
|
1686
|
+
// leading scope marker separates the package name from its descriptor:
|
|
1687
|
+
// asap@~2.0.3 -> name "asap"
|
|
1688
|
+
// @babel/cli@7.10.1 -> name "@babel/cli"
|
|
1689
|
+
// lru-cache@npm:^6.0.0 -> name "lru-cache"
|
|
1690
|
+
// react-is-18@npm:react-is@^18.3.1 -> npm alias, real name "react-is"
|
|
1691
|
+
// lru-cache@patch:lru-cache@npm:... -> patch protocol, name "lru-cache"
|
|
1692
|
+
const sepIdx = l.startsWith("@") ? l.indexOf("@", 1) : l.indexOf("@");
|
|
1693
|
+
if (sepIdx > -1) {
|
|
1694
|
+
fullName = l.slice(0, sepIdx);
|
|
1695
|
+
const descriptor = l.slice(sepIdx + 1);
|
|
1696
|
+
// Only an npm: descriptor placed immediately after the name can alias to a
|
|
1697
|
+
// different real package. Other protocols (patch:, workspace:, file:, ...)
|
|
1698
|
+
// keep the original name; the "@npm:" nested inside a patch locator is not
|
|
1699
|
+
// an alias marker, so we must not treat it as one.
|
|
1700
|
+
if (descriptor.startsWith("npm:")) {
|
|
1701
|
+
const target = descriptor.slice("npm:".length);
|
|
1702
|
+
// An alias target is `name@range`; a plain version is just a `range`.
|
|
1703
|
+
// A leading "@" only marks a scoped package, so look for the separator
|
|
1704
|
+
// beyond it.
|
|
1705
|
+
const aliasSepIdx = target.startsWith("@")
|
|
1706
|
+
? target.indexOf("@", 1)
|
|
1707
|
+
: target.indexOf("@");
|
|
1708
|
+
if (aliasSepIdx > -1) {
|
|
1709
|
+
// Aliased dependency: use the real package name from the target.
|
|
1710
|
+
fullName = target.slice(0, aliasSepIdx);
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
if (fullName) {
|
|
1715
|
+
const slashIdx = fullName.indexOf("/");
|
|
1716
|
+
if (slashIdx > -1) {
|
|
1717
|
+
group = fullName.slice(0, slashIdx);
|
|
1718
|
+
name = fullName.slice(slashIdx + 1);
|
|
1719
|
+
} else {
|
|
1720
|
+
name = fullName;
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
return { group, name };
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
function _resolveYarnDependencyBomRef(
|
|
1727
|
+
packageName,
|
|
1728
|
+
versionRange,
|
|
1729
|
+
identMap,
|
|
1730
|
+
workspacePackages = [],
|
|
1731
|
+
) {
|
|
1732
|
+
if (!packageName || !versionRange) {
|
|
1733
|
+
return undefined;
|
|
1734
|
+
}
|
|
1735
|
+
let packageNameToUse = packageName;
|
|
1736
|
+
let versionRangeToUse = versionRange;
|
|
1737
|
+
if (versionRange.startsWith("npm:")) {
|
|
1738
|
+
if (versionRange.includes("@")) {
|
|
1739
|
+
versionRangeToUse = versionRange.split("@").splice(-1)[0];
|
|
1740
|
+
packageNameToUse = versionRange
|
|
1741
|
+
.replace("npm:", "")
|
|
1742
|
+
.replace(`@${versionRangeToUse}`, "");
|
|
1743
|
+
} else {
|
|
1744
|
+
versionRangeToUse = versionRange.replace("npm:", "");
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
let resolvedVersion =
|
|
1748
|
+
identMap[`${packageName}|${versionRangeToUse}`] ||
|
|
1749
|
+
identMap[`${packageNameToUse}|${versionRangeToUse}`];
|
|
1750
|
+
if (!resolvedVersion) {
|
|
1751
|
+
const packageKeys = Object.keys(identMap).filter((key) => {
|
|
1752
|
+
const [pkg] = key.split("|");
|
|
1753
|
+
return pkg === packageName || pkg === packageNameToUse;
|
|
1754
|
+
});
|
|
1755
|
+
resolvedVersion = identMap[packageKeys[0]];
|
|
1756
|
+
}
|
|
1757
|
+
if (!resolvedVersion && workspacePackages?.length) {
|
|
1758
|
+
const matchingWorkspace = findMatchingNpmWorkspace(
|
|
1759
|
+
workspacePackages,
|
|
1760
|
+
packageNameToUse,
|
|
1761
|
+
);
|
|
1762
|
+
if (matchingWorkspace) {
|
|
1763
|
+
const versionMatch = matchingWorkspace.match(/@([^@]+)$/);
|
|
1764
|
+
if (versionMatch) {
|
|
1765
|
+
resolvedVersion = versionMatch[1];
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
let purlPart = `pkg:npm/${encodeURIComponent(packageNameToUse).replace(/%2F/g, "/")}`;
|
|
1770
|
+
if (resolvedVersion?.length) {
|
|
1771
|
+
purlPart = `${purlPart}@${resolvedVersion}`;
|
|
1772
|
+
}
|
|
1773
|
+
return decodeURIComponent(Purl.parse(purlPart).toString());
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
function _collectYarnRootDependencyRefs(
|
|
1777
|
+
yarnLockFile,
|
|
1778
|
+
identMap,
|
|
1779
|
+
workspacePackages = [],
|
|
1780
|
+
) {
|
|
1781
|
+
const rootRefs = _createYarnRootDependencyRefs();
|
|
1782
|
+
const packageJsonFile = join(dirname(yarnLockFile), "package.json");
|
|
1783
|
+
if (!safeExistsSync(packageJsonFile)) {
|
|
1784
|
+
return rootRefs;
|
|
1785
|
+
}
|
|
1786
|
+
let packageJson;
|
|
1787
|
+
try {
|
|
1788
|
+
packageJson = JSON.parse(readFileSync(packageJsonFile, "utf-8"));
|
|
1789
|
+
} catch {
|
|
1790
|
+
return rootRefs;
|
|
1791
|
+
}
|
|
1792
|
+
for (const dependencyType of Object.keys(rootRefs)) {
|
|
1793
|
+
for (const [packageName, versionRange] of Object.entries(
|
|
1794
|
+
packageJson[dependencyType] || {},
|
|
1795
|
+
)) {
|
|
1796
|
+
const bomRef = _resolveYarnDependencyBomRef(
|
|
1797
|
+
packageName,
|
|
1798
|
+
String(versionRange),
|
|
1799
|
+
identMap,
|
|
1800
|
+
workspacePackages,
|
|
1801
|
+
);
|
|
1802
|
+
if (bomRef) {
|
|
1803
|
+
rootRefs[dependencyType].add(bomRef);
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
return rootRefs;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
function _createYarnRootDependencyRefs() {
|
|
1811
|
+
return {
|
|
1812
|
+
dependencies: new Set(),
|
|
1813
|
+
devDependencies: new Set(),
|
|
1814
|
+
optionalDependencies: new Set(),
|
|
1815
|
+
peerDependencies: new Set(),
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
function _buildDependencyClosure(rootRefs, dependenciesMap) {
|
|
1820
|
+
const closure = new Set();
|
|
1821
|
+
const stack = [...rootRefs];
|
|
1822
|
+
while (stack.length) {
|
|
1823
|
+
const ref = stack.pop();
|
|
1824
|
+
if (!ref || closure.has(ref)) {
|
|
1825
|
+
continue;
|
|
1826
|
+
}
|
|
1827
|
+
closure.add(ref);
|
|
1828
|
+
for (const childRef of dependenciesMap[ref] || []) {
|
|
1829
|
+
stack.push(childRef);
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
return closure;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
function _markYarnDependencyScopeClosures(
|
|
1836
|
+
pkgList,
|
|
1837
|
+
dependenciesList,
|
|
1838
|
+
yarnRootRefs,
|
|
1839
|
+
yarnOptionalDependencyRefs,
|
|
1840
|
+
) {
|
|
1841
|
+
const dependenciesMap = {};
|
|
1842
|
+
for (const dependency of dependenciesList || []) {
|
|
1843
|
+
if (!dependenciesMap[dependency.ref]) {
|
|
1844
|
+
dependenciesMap[dependency.ref] = [];
|
|
1845
|
+
}
|
|
1846
|
+
dependenciesMap[dependency.ref] = Array.from(
|
|
1847
|
+
new Set([
|
|
1848
|
+
...dependenciesMap[dependency.ref],
|
|
1849
|
+
...(dependency.dependsOn || []),
|
|
1850
|
+
]),
|
|
1851
|
+
);
|
|
1852
|
+
}
|
|
1853
|
+
const runtimeClosure = _buildDependencyClosure(
|
|
1854
|
+
yarnRootRefs.dependencies,
|
|
1855
|
+
dependenciesMap,
|
|
1856
|
+
);
|
|
1857
|
+
const devClosure = _buildDependencyClosure(
|
|
1858
|
+
yarnRootRefs.devDependencies,
|
|
1859
|
+
dependenciesMap,
|
|
1860
|
+
);
|
|
1861
|
+
const optionalClosure = _buildDependencyClosure(
|
|
1862
|
+
new Set([
|
|
1863
|
+
...yarnRootRefs.optionalDependencies,
|
|
1864
|
+
...yarnOptionalDependencyRefs,
|
|
1865
|
+
]),
|
|
1866
|
+
dependenciesMap,
|
|
1867
|
+
);
|
|
1868
|
+
const peerClosure = _buildDependencyClosure(
|
|
1869
|
+
yarnRootRefs.peerDependencies,
|
|
1870
|
+
dependenciesMap,
|
|
1871
|
+
);
|
|
1872
|
+
for (const pkg of pkgList) {
|
|
1873
|
+
const ref = pkg["bom-ref"];
|
|
1874
|
+
if (!ref) {
|
|
1875
|
+
continue;
|
|
1876
|
+
}
|
|
1877
|
+
if (
|
|
1878
|
+
optionalClosure.has(ref) &&
|
|
1879
|
+
(!runtimeClosure.has(ref) ||
|
|
1880
|
+
yarnRootRefs.optionalDependencies.has(ref) ||
|
|
1881
|
+
yarnOptionalDependencyRefs.has(ref))
|
|
1882
|
+
) {
|
|
1883
|
+
pkg.scope = "optional";
|
|
1884
|
+
setNpmOptionalProperty(pkg);
|
|
1885
|
+
}
|
|
1886
|
+
if (peerClosure.has(ref) && !runtimeClosure.has(ref)) {
|
|
1887
|
+
pkg.scope = "optional";
|
|
1888
|
+
setNpmPeerProperty(pkg);
|
|
1889
|
+
}
|
|
1890
|
+
if (devClosure.has(ref) && !runtimeClosure.has(ref)) {
|
|
1891
|
+
pkg.scope = "optional";
|
|
1892
|
+
setNpmDevelopmentProperty(pkg);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
/**
|
|
1898
|
+
* Parse nodejs yarn lock file
|
|
1899
|
+
*
|
|
1900
|
+
* @param {string} yarnLockFile yarn.lock file
|
|
1901
|
+
* @param {Object} parentComponent parent component
|
|
1902
|
+
* @param {Array[String]} workspacePackages Workspace packages
|
|
1903
|
+
* @param {Object} workspaceSrcFiles Workspace package.json files
|
|
1904
|
+
* @param {Object} _workspaceDirectDeps Direct dependencies of each workspace
|
|
1905
|
+
* @param {Object} depsWorkspaceRefs Workspace references for each dependency
|
|
1906
|
+
*/
|
|
1907
|
+
export async function parseYarnLock(
|
|
1908
|
+
yarnLockFile,
|
|
1909
|
+
parentComponent = null,
|
|
1910
|
+
workspacePackages = [],
|
|
1911
|
+
workspaceSrcFiles = {},
|
|
1912
|
+
_workspaceDirectDeps = {},
|
|
1913
|
+
depsWorkspaceRefs = {},
|
|
1914
|
+
) {
|
|
1915
|
+
let pkgList = [];
|
|
1916
|
+
const dependenciesList = [];
|
|
1917
|
+
const depKeys = {};
|
|
1918
|
+
let yarnRootRefs = _createYarnRootDependencyRefs();
|
|
1919
|
+
const yarnOptionalDependencyRefs = new Set();
|
|
1920
|
+
const npmrcConfig = loadNpmrcConfig(dirname(yarnLockFile));
|
|
1921
|
+
if (safeExistsSync(yarnLockFile)) {
|
|
1922
|
+
const lockData = readFileSync(yarnLockFile, "utf8");
|
|
1923
|
+
let name = "";
|
|
1924
|
+
let name_aliases = [];
|
|
1925
|
+
let group = "";
|
|
1926
|
+
let version = "";
|
|
1927
|
+
let integrity = "";
|
|
1928
|
+
let resolvedUrl = "";
|
|
1929
|
+
let depsMode = false;
|
|
1930
|
+
let optionalDepsMode = false;
|
|
1931
|
+
let purlString = "";
|
|
1932
|
+
let deplist = new Set();
|
|
1933
|
+
const pkgAddedMap = {};
|
|
1934
|
+
// Map to track workspace package PURL replacements from 0.0.0-use.local to actual versions
|
|
1935
|
+
const workspacePurlMap = {};
|
|
1936
|
+
// This would have the keys and the resolved version required to solve the dependency tree
|
|
1937
|
+
const identMap = yarnLockToIdentMap(lockData);
|
|
1938
|
+
yarnRootRefs = _collectYarnRootDependencyRefs(
|
|
1939
|
+
yarnLockFile,
|
|
1940
|
+
identMap,
|
|
1941
|
+
workspacePackages,
|
|
1942
|
+
);
|
|
1943
|
+
lockData.split("\n").forEach((l) => {
|
|
1944
|
+
l = l.replaceAll("\r", "");
|
|
1945
|
+
if (l.startsWith("#")) {
|
|
1946
|
+
return;
|
|
1947
|
+
}
|
|
1948
|
+
if (!l.startsWith(" ") || l.trim() === "") {
|
|
1949
|
+
// Create an entry for the package and reset variables
|
|
1950
|
+
if (
|
|
1951
|
+
name !== "" &&
|
|
1952
|
+
version !== "" &&
|
|
1953
|
+
(integrity !== "" ||
|
|
1954
|
+
version.includes("local") ||
|
|
1955
|
+
(integrity === "" && (depsMode || l.trim() === "")))
|
|
1956
|
+
) {
|
|
1957
|
+
name_aliases.push({ group, name });
|
|
1958
|
+
// FIXME: What should we do about the dependencies for such aliases
|
|
1959
|
+
for (const ang of name_aliases) {
|
|
1960
|
+
group = ang.group;
|
|
1961
|
+
name = ang.name;
|
|
1962
|
+
let qualifiers = null;
|
|
1963
|
+
let extRefs = [];
|
|
1964
|
+
const isGitDep =
|
|
1965
|
+
resolvedUrl &&
|
|
1966
|
+
(resolvedUrl.startsWith("git+") ||
|
|
1967
|
+
resolvedUrl.startsWith("git://") ||
|
|
1968
|
+
resolvedUrl.startsWith("ssh://") ||
|
|
1969
|
+
resolvedUrl.startsWith("git@") ||
|
|
1970
|
+
resolvedUrl.includes(".git"));
|
|
1971
|
+
if (isGitDep) {
|
|
1972
|
+
qualifiers = buildNpmGitPurlQualifiers(
|
|
1973
|
+
resolvedUrl,
|
|
1974
|
+
group,
|
|
1975
|
+
npmrcConfig,
|
|
1976
|
+
);
|
|
1977
|
+
const gitIntakeRefs = buildNpmGitDistributionIntakeRefs(
|
|
1978
|
+
group,
|
|
1979
|
+
name,
|
|
1980
|
+
version,
|
|
1981
|
+
npmrcConfig,
|
|
1982
|
+
);
|
|
1983
|
+
if (gitIntakeRefs) {
|
|
1984
|
+
extRefs = extRefs.concat(gitIntakeRefs);
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
// Create a purl ref for the current package
|
|
1988
|
+
purlString = build({
|
|
1989
|
+
type: "npm",
|
|
1990
|
+
namespace: group || null,
|
|
1991
|
+
name: name,
|
|
1992
|
+
version: version || null,
|
|
1993
|
+
qualifiers: qualifiers || null,
|
|
1994
|
+
});
|
|
1995
|
+
// Skip workspace packages with placeholder version "0.0.0-use.local"
|
|
1996
|
+
// These are yarn's internal placeholders for local workspace packages
|
|
1997
|
+
// The actual workspace packages are already added with their real versions
|
|
1998
|
+
const isPlaceholderVersion = version === "0.0.0-use.local";
|
|
1999
|
+
if (isPlaceholderVersion) {
|
|
2000
|
+
// Try to find matching workspace package by creating the expected PURL format
|
|
2001
|
+
const expectedPurlStart = `pkg:npm/${group ? `${group}/${name}` : name}@`;
|
|
2002
|
+
const actualWorkspacePurl = workspacePackages.find(
|
|
2003
|
+
(wp) =>
|
|
2004
|
+
wp.startsWith(expectedPurlStart) ||
|
|
2005
|
+
wp.includes(
|
|
2006
|
+
`pkg:npm/${encodeForPurl(group ? `${group}/${name}` : name)}@`,
|
|
2007
|
+
),
|
|
2008
|
+
);
|
|
2009
|
+
// Also check if this package already exists in pkgList with a real version
|
|
2010
|
+
const existingPkg = pkgList.find(
|
|
2011
|
+
(pkg) =>
|
|
2012
|
+
pkg.group === (group || "") &&
|
|
2013
|
+
pkg.name === name &&
|
|
2014
|
+
pkg.version !== "0.0.0-use.local",
|
|
2015
|
+
);
|
|
2016
|
+
// Check if this is a root workspace package by looking at parentComponent
|
|
2017
|
+
const isRootWorkspacePackage =
|
|
2018
|
+
parentComponent &&
|
|
2019
|
+
parentComponent.name === name &&
|
|
2020
|
+
parentComponent.group === (group || "");
|
|
2021
|
+
if (
|
|
2022
|
+
actualWorkspacePurl ||
|
|
2023
|
+
existingPkg ||
|
|
2024
|
+
isRootWorkspacePackage
|
|
2025
|
+
) {
|
|
2026
|
+
if (actualWorkspacePurl) {
|
|
2027
|
+
// Store mapping from placeholder PURL to actual workspace PURL (both URL-decoded for bom-ref)
|
|
2028
|
+
workspacePurlMap[decodeURIComponent(purlString)] =
|
|
2029
|
+
decodeURIComponent(actualWorkspacePurl);
|
|
2030
|
+
} else if (existingPkg) {
|
|
2031
|
+
// Store mapping to existing package's PURL
|
|
2032
|
+
workspacePurlMap[decodeURIComponent(purlString)] =
|
|
2033
|
+
existingPkg["bom-ref"];
|
|
2034
|
+
} else if (isRootWorkspacePackage) {
|
|
2035
|
+
// Store mapping to parent component's PURL
|
|
2036
|
+
workspacePurlMap[decodeURIComponent(purlString)] =
|
|
2037
|
+
parentComponent["bom-ref"];
|
|
2038
|
+
}
|
|
2039
|
+
continue;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
// Trim duplicates
|
|
2043
|
+
if (!pkgAddedMap[purlString]) {
|
|
2044
|
+
pkgAddedMap[purlString] = true;
|
|
2045
|
+
const properties = [
|
|
2046
|
+
{
|
|
2047
|
+
name: "internal:SrcFile",
|
|
2048
|
+
value: yarnLockFile,
|
|
2049
|
+
},
|
|
2050
|
+
];
|
|
2051
|
+
const pkgObj = {
|
|
2052
|
+
group: group || "",
|
|
2053
|
+
name: name,
|
|
2054
|
+
version: version,
|
|
2055
|
+
_integrity: integrity,
|
|
2056
|
+
purl: purlString,
|
|
2057
|
+
"bom-ref": decodeURIComponent(purlString),
|
|
2058
|
+
properties,
|
|
2059
|
+
evidence: {
|
|
2060
|
+
identity: {
|
|
2061
|
+
field: "purl",
|
|
2062
|
+
confidence: 1,
|
|
2063
|
+
methods: [
|
|
2064
|
+
{
|
|
2065
|
+
technique: "manifest-analysis",
|
|
2066
|
+
confidence: 1,
|
|
2067
|
+
value: yarnLockFile,
|
|
2068
|
+
},
|
|
2069
|
+
],
|
|
2070
|
+
},
|
|
2071
|
+
},
|
|
2072
|
+
};
|
|
2073
|
+
if (extRefs && extRefs.length > 0) {
|
|
2074
|
+
pkgObj.externalReferences = extRefs;
|
|
2075
|
+
}
|
|
2076
|
+
pkgList.push(pkgObj);
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
// Reset all the variables
|
|
2080
|
+
group = "";
|
|
2081
|
+
name = "";
|
|
2082
|
+
name_aliases = [];
|
|
2083
|
+
version = "";
|
|
2084
|
+
integrity = "";
|
|
2085
|
+
}
|
|
2086
|
+
if (purlString && purlString !== "" && !depKeys[purlString]) {
|
|
2087
|
+
// Map workspace placeholder PURLs to actual workspace PURLs
|
|
2088
|
+
const resolvedRef =
|
|
2089
|
+
workspacePurlMap[decodeURIComponent(purlString)] ||
|
|
2090
|
+
decodeURIComponent(purlString);
|
|
2091
|
+
const resolvedDependsOn = [...deplist]
|
|
2092
|
+
.map((dep) => workspacePurlMap[dep] || dep)
|
|
2093
|
+
.sort();
|
|
2094
|
+
// Create an entry for dependencies
|
|
2095
|
+
dependenciesList.push({
|
|
2096
|
+
ref: resolvedRef,
|
|
2097
|
+
dependsOn: resolvedDependsOn,
|
|
2098
|
+
});
|
|
2099
|
+
depKeys[purlString] = true;
|
|
2100
|
+
deplist = new Set();
|
|
2101
|
+
purlString = "";
|
|
2102
|
+
resolvedUrl = "";
|
|
2103
|
+
depsMode = false;
|
|
2104
|
+
optionalDepsMode = false;
|
|
2105
|
+
}
|
|
2106
|
+
// Collect the group and the name
|
|
2107
|
+
l = l.replace(/["']/g, "");
|
|
2108
|
+
// Deals with lines including aliases
|
|
2109
|
+
// Eg: string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3
|
|
2110
|
+
const fragments = l.split(", ");
|
|
2111
|
+
for (let i = 0; i < fragments.length; i++) {
|
|
2112
|
+
const parsedline = _parseYarnLine(fragments[i]);
|
|
2113
|
+
if (i === 0) {
|
|
2114
|
+
group = parsedline.group;
|
|
2115
|
+
name = parsedline.name;
|
|
2116
|
+
} else {
|
|
2117
|
+
let fullName = parsedline.name;
|
|
2118
|
+
if (parsedline.group?.length) {
|
|
2119
|
+
fullName = `${parsedline.group}/${parsedline.name}`;
|
|
2120
|
+
}
|
|
2121
|
+
if (
|
|
2122
|
+
fullName !== name &&
|
|
2123
|
+
fullName !== `${group}/${name}` &&
|
|
2124
|
+
!name_aliases.includes(fullName)
|
|
2125
|
+
) {
|
|
2126
|
+
name_aliases.push({
|
|
2127
|
+
group: parsedline.group,
|
|
2128
|
+
name: parsedline.name,
|
|
2129
|
+
});
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
} else if (
|
|
2134
|
+
name !== "" &&
|
|
2135
|
+
(l.startsWith(" dependencies:") ||
|
|
2136
|
+
l.startsWith(' "dependencies:') ||
|
|
2137
|
+
l.startsWith(" optionalDependencies:") ||
|
|
2138
|
+
l.startsWith(' "optionalDependencies:'))
|
|
2139
|
+
) {
|
|
2140
|
+
if (
|
|
2141
|
+
l.startsWith(" dependencies:") ||
|
|
2142
|
+
l.startsWith(' "dependencies:')
|
|
2143
|
+
) {
|
|
2144
|
+
depsMode = true;
|
|
2145
|
+
optionalDepsMode = false;
|
|
2146
|
+
} else {
|
|
2147
|
+
depsMode = false;
|
|
2148
|
+
optionalDepsMode = true;
|
|
2149
|
+
}
|
|
2150
|
+
} else if ((depsMode || optionalDepsMode) && l.startsWith(" ")) {
|
|
2151
|
+
// Given "@actions/http-client" "^1.0.11"
|
|
2152
|
+
// We need the resolved version from identMap
|
|
2153
|
+
// Deal with values with space within the quotes. Eg: minimatch "2 || 3"
|
|
2154
|
+
// vinyl-sourcemaps-apply ">=0.1.1 <0.2.0-0"
|
|
2155
|
+
l = l.trim();
|
|
2156
|
+
let splitPattern = ' "';
|
|
2157
|
+
// yarn v7 has a different split pattern
|
|
2158
|
+
if (l.includes('": ')) {
|
|
2159
|
+
splitPattern = '": ';
|
|
2160
|
+
} else if (l.includes(": ")) {
|
|
2161
|
+
splitPattern = ": ";
|
|
2162
|
+
}
|
|
2163
|
+
const tmpA = l.trim().split(splitPattern);
|
|
2164
|
+
if (tmpA && tmpA.length === 2) {
|
|
2165
|
+
let dgroupname = tmpA[0].replace(/"/g, "");
|
|
2166
|
+
if (dgroupname.endsWith(":")) {
|
|
2167
|
+
dgroupname = dgroupname.substring(0, dgroupname.length - 1);
|
|
2168
|
+
}
|
|
2169
|
+
const range = tmpA[1].replace(/["']/g, "");
|
|
2170
|
+
const depBomRef = _resolveYarnDependencyBomRef(
|
|
2171
|
+
dgroupname,
|
|
2172
|
+
range,
|
|
2173
|
+
identMap,
|
|
2174
|
+
workspacePackages,
|
|
2175
|
+
);
|
|
2176
|
+
if (depBomRef) {
|
|
2177
|
+
deplist.add(depBomRef);
|
|
2178
|
+
}
|
|
2179
|
+
if (optionalDepsMode && depBomRef) {
|
|
2180
|
+
yarnOptionalDependencyRefs.add(depBomRef);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
} else if (name !== "") {
|
|
2184
|
+
if (!l.startsWith(" ")) {
|
|
2185
|
+
depsMode = false;
|
|
2186
|
+
optionalDepsMode = false;
|
|
2187
|
+
}
|
|
2188
|
+
l = l.replace(/"/g, "").trim();
|
|
2189
|
+
const parts = l.split(" ");
|
|
2190
|
+
if (l.startsWith("version")) {
|
|
2191
|
+
version = parts[1].replace(/"/g, "");
|
|
2192
|
+
}
|
|
2193
|
+
if (l.startsWith("integrity")) {
|
|
2194
|
+
integrity = parts[1];
|
|
2195
|
+
}
|
|
2196
|
+
// checksum used by yarn 2/3 is hex encoded
|
|
2197
|
+
if (l.startsWith("checksum")) {
|
|
2198
|
+
// in some cases yarn 4 will add a prefix to the checksum, containing the cachekey and compression level
|
|
2199
|
+
// example: 10c0/53c2b231a61a46792b39a0d43bc4f4f77...
|
|
2200
|
+
const checksum = parts[1].split("/").pop();
|
|
2201
|
+
integrity = `sha512-${Buffer.from(checksum, "hex").toString(
|
|
2202
|
+
"base64",
|
|
2203
|
+
)}`;
|
|
2204
|
+
}
|
|
2205
|
+
if (l.startsWith("resolved")) {
|
|
2206
|
+
resolvedUrl = parts[1].replace(/"/g, "");
|
|
2207
|
+
const tmpB = resolvedUrl.split("#");
|
|
2208
|
+
if (tmpB.length > 1) {
|
|
2209
|
+
const digest = tmpB[1].replace(/"/g, "");
|
|
2210
|
+
integrity = `sha256-${digest}`;
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
});
|
|
2215
|
+
}
|
|
2216
|
+
// Add workspace references for yarn workspaces
|
|
2217
|
+
if (depsWorkspaceRefs && Object.keys(depsWorkspaceRefs).length) {
|
|
2218
|
+
for (const apkg of pkgList) {
|
|
2219
|
+
if (!apkg.properties) {
|
|
2220
|
+
apkg.properties = [];
|
|
2221
|
+
}
|
|
2222
|
+
const purlObj = Purl.parse(apkg.purl);
|
|
2223
|
+
purlObj.version = undefined;
|
|
2224
|
+
const purlNoVersion = decodeURIComponent(purlObj.toString());
|
|
2225
|
+
const wsRefs =
|
|
2226
|
+
depsWorkspaceRefs[apkg["bom-ref"]] || depsWorkspaceRefs[purlNoVersion];
|
|
2227
|
+
// There is a workspace reference
|
|
2228
|
+
if (wsRefs?.length) {
|
|
2229
|
+
const wsprops = apkg.properties.filter(
|
|
2230
|
+
(p) => p.name === "internal:workspaceRef",
|
|
2231
|
+
);
|
|
2232
|
+
// workspace properties are already set.
|
|
2233
|
+
if (wsprops.length) {
|
|
2234
|
+
continue;
|
|
2235
|
+
}
|
|
2236
|
+
for (const wref of wsRefs) {
|
|
2237
|
+
// Such a cycle should never happen, but we can't sure
|
|
2238
|
+
if (wref === apkg["bom-ref"]) {
|
|
2239
|
+
continue;
|
|
2240
|
+
}
|
|
2241
|
+
apkg.properties.push({
|
|
2242
|
+
name: "internal:workspaceRef",
|
|
2243
|
+
value: wref,
|
|
2244
|
+
});
|
|
2245
|
+
const purlObj = Purl.parse(apkg.purl);
|
|
2246
|
+
purlObj.version = undefined;
|
|
2247
|
+
const wrefNoVersion = decodeURIComponent(purlObj.toString());
|
|
2248
|
+
const wsrcFile =
|
|
2249
|
+
workspaceSrcFiles[wref] || workspaceSrcFiles[wrefNoVersion];
|
|
2250
|
+
if (wsrcFile) {
|
|
2251
|
+
apkg.properties.push({
|
|
2252
|
+
name: "internal:workspaceSrcFile",
|
|
2253
|
+
value: wsrcFile,
|
|
2254
|
+
});
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
// Create components for workspace packages that are referenced but not yet created
|
|
2261
|
+
if (workspacePackages?.length) {
|
|
2262
|
+
for (const workspacePkg of workspacePackages) {
|
|
2263
|
+
const existingComponent = pkgList.find(
|
|
2264
|
+
(pkg) => pkg["bom-ref"] === decodeURIComponent(workspacePkg),
|
|
2265
|
+
);
|
|
2266
|
+
if (!existingComponent) {
|
|
2267
|
+
try {
|
|
2268
|
+
if (DEBUG_MODE) {
|
|
2269
|
+
console.log(`Processing workspace package: "${workspacePkg}"`);
|
|
2270
|
+
}
|
|
2271
|
+
let purlObj;
|
|
2272
|
+
// For workspace packages that already have versions in the PURL format
|
|
2273
|
+
if (
|
|
2274
|
+
workspacePkg.includes("@") &&
|
|
2275
|
+
workspacePkg.lastIndexOf("@") > workspacePkg.indexOf("/")
|
|
2276
|
+
) {
|
|
2277
|
+
// This is a scoped package with version like "npm:@swc/helpers@0.4.14"
|
|
2278
|
+
const lastAtIndex = workspacePkg.lastIndexOf("@");
|
|
2279
|
+
const purlWithoutVersion = workspacePkg.substring(0, lastAtIndex);
|
|
2280
|
+
const version = workspacePkg.substring(lastAtIndex + 1);
|
|
2281
|
+
// Parse the PURL without version first, then set the version.
|
|
2282
|
+
purlObj = Purl.parse(purlWithoutVersion);
|
|
2283
|
+
purlObj.version = version;
|
|
2284
|
+
} else {
|
|
2285
|
+
// Parse the PURL as-is (no version or simple package)
|
|
2286
|
+
purlObj = Purl.parse(workspacePkg);
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
// Create the workspace component using the properly parsed PURL
|
|
2290
|
+
const workspaceComponent = {
|
|
2291
|
+
group: purlObj.namespace || "",
|
|
2292
|
+
name: purlObj.name,
|
|
2293
|
+
version: purlObj.version,
|
|
2294
|
+
purl: purlObj.toString(),
|
|
2295
|
+
"bom-ref": decodeURIComponent(purlObj.toString()),
|
|
2296
|
+
type: "library",
|
|
2297
|
+
scope: "required",
|
|
2298
|
+
properties: [
|
|
2299
|
+
{
|
|
2300
|
+
name: "internal:SrcFile",
|
|
2301
|
+
value: workspaceSrcFiles[workspacePkg] || "package.json",
|
|
2302
|
+
},
|
|
2303
|
+
{
|
|
2304
|
+
name: "internal:workspaceRef",
|
|
2305
|
+
value: purlObj.toString(),
|
|
2306
|
+
},
|
|
2307
|
+
{
|
|
2308
|
+
name: "cdx:npm:is_workspace",
|
|
2309
|
+
value: workspaceSrcFiles[workspacePkg] ? "true" : "false",
|
|
2310
|
+
},
|
|
2311
|
+
],
|
|
2312
|
+
evidence: {
|
|
2313
|
+
identity: {
|
|
2314
|
+
field: "purl",
|
|
2315
|
+
confidence: 0.7,
|
|
2316
|
+
methods: [
|
|
2317
|
+
{
|
|
2318
|
+
technique: "manifest-analysis",
|
|
2319
|
+
confidence: 0.7,
|
|
2320
|
+
value: workspaceSrcFiles[workspacePkg] || "package.json",
|
|
2321
|
+
},
|
|
2322
|
+
],
|
|
2323
|
+
},
|
|
2324
|
+
},
|
|
2325
|
+
};
|
|
2326
|
+
|
|
2327
|
+
if (workspaceSrcFiles[workspacePkg]) {
|
|
2328
|
+
workspaceComponent.properties.push({
|
|
2329
|
+
name: "internal:workspaceSrcFile",
|
|
2330
|
+
value: workspaceSrcFiles[workspacePkg],
|
|
2331
|
+
});
|
|
2332
|
+
}
|
|
2333
|
+
pkgList.push(workspaceComponent);
|
|
2334
|
+
} catch (err) {
|
|
2335
|
+
if (DEBUG_MODE) {
|
|
2336
|
+
console.log(
|
|
2337
|
+
`Error parsing workspace package PURL: ${workspacePkg}`,
|
|
2338
|
+
err.message,
|
|
2339
|
+
);
|
|
2340
|
+
}
|
|
2341
|
+
// Create a fallback component with minimal information that will trigger warnings
|
|
2342
|
+
// Removed property `cdx:invalid-purl` in favour of a lower confidence value.
|
|
2343
|
+
const fallbackComponent = {
|
|
2344
|
+
group: "",
|
|
2345
|
+
name: workspacePkg.includes("/")
|
|
2346
|
+
? workspacePkg.split("/").pop()
|
|
2347
|
+
: workspacePkg,
|
|
2348
|
+
version: "",
|
|
2349
|
+
purl: workspacePkg,
|
|
2350
|
+
"bom-ref": decodeURIComponent(workspacePkg),
|
|
2351
|
+
type: "library",
|
|
2352
|
+
scope: "required",
|
|
2353
|
+
properties: [
|
|
2354
|
+
{
|
|
2355
|
+
name: "internal:SrcFile",
|
|
2356
|
+
value: workspaceSrcFiles[workspacePkg] || "package.json",
|
|
2357
|
+
},
|
|
2358
|
+
{
|
|
2359
|
+
name: "internal:workspaceRef",
|
|
2360
|
+
value: workspacePkg,
|
|
2361
|
+
},
|
|
2362
|
+
{
|
|
2363
|
+
name: "cdx:npm:is_workspace",
|
|
2364
|
+
value: workspaceSrcFiles[workspacePkg] ? "true" : "false",
|
|
2365
|
+
},
|
|
2366
|
+
],
|
|
2367
|
+
evidence: {
|
|
2368
|
+
identity: {
|
|
2369
|
+
field: "purl",
|
|
2370
|
+
confidence: 0.1,
|
|
2371
|
+
methods: [
|
|
2372
|
+
{
|
|
2373
|
+
technique: "manifest-analysis",
|
|
2374
|
+
confidence: 0.1,
|
|
2375
|
+
value: workspaceSrcFiles[workspacePkg] || "package.json",
|
|
2376
|
+
},
|
|
2377
|
+
],
|
|
2378
|
+
},
|
|
2379
|
+
},
|
|
2380
|
+
};
|
|
2381
|
+
pkgList.push(fallbackComponent);
|
|
2382
|
+
if (DEBUG_MODE) {
|
|
2383
|
+
console.log(
|
|
2384
|
+
`Created fallback workspace component with invalid PURL: ${workspacePkg}`,
|
|
2385
|
+
);
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
_markYarnDependencyScopeClosures(
|
|
2393
|
+
pkgList,
|
|
2394
|
+
dependenciesList,
|
|
2395
|
+
yarnRootRefs,
|
|
2396
|
+
yarnOptionalDependencyRefs,
|
|
2397
|
+
);
|
|
2398
|
+
|
|
2399
|
+
if (shouldFetchPackageMetadata() && pkgList?.length) {
|
|
2400
|
+
if (DEBUG_MODE) {
|
|
2401
|
+
console.log(
|
|
2402
|
+
`About to fetch npm registry metadata for ${pkgList.length} packages in parseYarnLock`,
|
|
2403
|
+
);
|
|
2404
|
+
}
|
|
2405
|
+
pkgList = await getNpmMetadata(pkgList);
|
|
2406
|
+
return {
|
|
2407
|
+
pkgList,
|
|
2408
|
+
dependenciesList,
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
return {
|
|
2412
|
+
pkgList,
|
|
2413
|
+
dependenciesList,
|
|
2414
|
+
};
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
/**
|
|
2418
|
+
* Parse nodejs shrinkwrap deps file
|
|
2419
|
+
*
|
|
2420
|
+
* @param {string} swFile shrinkwrap-deps.json file
|
|
2421
|
+
*/
|
|
2422
|
+
export async function parseNodeShrinkwrap(swFile) {
|
|
2423
|
+
const pkgList = [];
|
|
2424
|
+
if (safeExistsSync(swFile)) {
|
|
2425
|
+
const lockData = JSON.parse(readFileSync(swFile, "utf8"));
|
|
2426
|
+
const pkgKeys = Object.keys(lockData);
|
|
2427
|
+
for (const k in pkgKeys) {
|
|
2428
|
+
const fullName = pkgKeys[k];
|
|
2429
|
+
const integrity = lockData[fullName];
|
|
2430
|
+
const parts = fullName.split("@");
|
|
2431
|
+
if (parts?.length) {
|
|
2432
|
+
let name = "";
|
|
2433
|
+
let version = "";
|
|
2434
|
+
let group = "";
|
|
2435
|
+
if (parts.length === 2) {
|
|
2436
|
+
name = parts[0];
|
|
2437
|
+
version = parts[1];
|
|
2438
|
+
} else if (parts.length === 3) {
|
|
2439
|
+
if (parts[0] === "") {
|
|
2440
|
+
const gnameparts = parts[1].split("/");
|
|
2441
|
+
group = gnameparts[0];
|
|
2442
|
+
name = gnameparts[1];
|
|
2443
|
+
} else {
|
|
2444
|
+
name = parts[0];
|
|
2445
|
+
}
|
|
2446
|
+
version = parts[2];
|
|
2447
|
+
}
|
|
2448
|
+
pkgList.push({
|
|
2449
|
+
group: group,
|
|
2450
|
+
name: name,
|
|
2451
|
+
version: version,
|
|
2452
|
+
_integrity: integrity,
|
|
2453
|
+
properties: [
|
|
2454
|
+
{
|
|
2455
|
+
name: "internal:SrcFile",
|
|
2456
|
+
value: swFile,
|
|
2457
|
+
},
|
|
2458
|
+
],
|
|
2459
|
+
evidence: {
|
|
2460
|
+
identity: {
|
|
2461
|
+
field: "purl",
|
|
2462
|
+
confidence: 1,
|
|
2463
|
+
methods: [
|
|
2464
|
+
{
|
|
2465
|
+
technique: "manifest-analysis",
|
|
2466
|
+
confidence: 1,
|
|
2467
|
+
value: swFile,
|
|
2468
|
+
},
|
|
2469
|
+
],
|
|
2470
|
+
},
|
|
2471
|
+
},
|
|
2472
|
+
});
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
if (shouldFetchPackageMetadata() && pkgList?.length) {
|
|
2477
|
+
if (DEBUG_MODE) {
|
|
2478
|
+
console.log(
|
|
2479
|
+
`About to fetch npm registry metadata for ${pkgList.length} packages in parseNodeShrinkwrap`,
|
|
2480
|
+
);
|
|
2481
|
+
}
|
|
2482
|
+
return await getNpmMetadata(pkgList);
|
|
2483
|
+
}
|
|
2484
|
+
return pkgList;
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
function _markTreeOptional(
|
|
2488
|
+
dbomRef,
|
|
2489
|
+
dependenciesMap,
|
|
2490
|
+
possibleOptionalDeps,
|
|
2491
|
+
visited,
|
|
2492
|
+
) {
|
|
2493
|
+
if (possibleOptionalDeps[dbomRef] === undefined) {
|
|
2494
|
+
possibleOptionalDeps[dbomRef] = true;
|
|
2495
|
+
}
|
|
2496
|
+
if (dependenciesMap[dbomRef] && !visited[dbomRef]) {
|
|
2497
|
+
visited[dbomRef] = true;
|
|
2498
|
+
for (const eachDep of dependenciesMap[dbomRef]) {
|
|
2499
|
+
if (possibleOptionalDeps[eachDep] !== false) {
|
|
2500
|
+
_markTreeOptional(
|
|
2501
|
+
eachDep,
|
|
2502
|
+
dependenciesMap,
|
|
2503
|
+
possibleOptionalDeps,
|
|
2504
|
+
visited,
|
|
2505
|
+
);
|
|
2506
|
+
}
|
|
2507
|
+
visited[eachDep] = true;
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
function _markTreeDevelopment(
|
|
2513
|
+
dbomRef,
|
|
2514
|
+
dependenciesMap,
|
|
2515
|
+
possibleDevelopmentDeps,
|
|
2516
|
+
visited,
|
|
2517
|
+
) {
|
|
2518
|
+
// Production-required packages set this map entry to false, and that wins
|
|
2519
|
+
// over any later attempt to propagate a development-only marking.
|
|
2520
|
+
if (possibleDevelopmentDeps[dbomRef] === undefined) {
|
|
2521
|
+
possibleDevelopmentDeps[dbomRef] = true;
|
|
2522
|
+
}
|
|
2523
|
+
if (dependenciesMap[dbomRef] && !visited[dbomRef]) {
|
|
2524
|
+
visited[dbomRef] = true;
|
|
2525
|
+
for (const eachDep of dependenciesMap[dbomRef]) {
|
|
2526
|
+
// Undefined means we have not classified this dependency yet, so we
|
|
2527
|
+
// continue propagating the dev-only marking unless it was already proven
|
|
2528
|
+
// to be non-development via a false entry.
|
|
2529
|
+
if (possibleDevelopmentDeps[eachDep] !== false) {
|
|
2530
|
+
_markTreeDevelopment(
|
|
2531
|
+
eachDep,
|
|
2532
|
+
dependenciesMap,
|
|
2533
|
+
possibleDevelopmentDeps,
|
|
2534
|
+
visited,
|
|
2535
|
+
);
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
function _setTreeWorkspaceRef(
|
|
2542
|
+
dependenciesMap,
|
|
2543
|
+
depref,
|
|
2544
|
+
pkgRefMap,
|
|
2545
|
+
wref,
|
|
2546
|
+
wsrcFile,
|
|
2547
|
+
depsWorkspaceRefs,
|
|
2548
|
+
) {
|
|
2549
|
+
for (const dref of dependenciesMap[depref] || []) {
|
|
2550
|
+
const addedMap = {};
|
|
2551
|
+
const depPkg = pkgRefMap[dref];
|
|
2552
|
+
if (!depPkg) {
|
|
2553
|
+
continue;
|
|
2554
|
+
}
|
|
2555
|
+
const wsprops = depPkg.properties.filter(
|
|
2556
|
+
(p) => p.name === "internal:workspaceRef",
|
|
2557
|
+
);
|
|
2558
|
+
if (wsprops.length) {
|
|
2559
|
+
continue;
|
|
2560
|
+
}
|
|
2561
|
+
depPkg.properties = depPkg.properties || [];
|
|
2562
|
+
for (const prop of depPkg.properties) {
|
|
2563
|
+
addedMap[prop.value] = true;
|
|
2564
|
+
}
|
|
2565
|
+
if (!addedMap[wref]) {
|
|
2566
|
+
depPkg.properties.push({
|
|
2567
|
+
name: "internal:workspaceRef",
|
|
2568
|
+
value: wref,
|
|
2569
|
+
});
|
|
2570
|
+
addedMap[wref] = true;
|
|
2571
|
+
}
|
|
2572
|
+
if (wsrcFile && !addedMap[wsrcFile]) {
|
|
2573
|
+
depPkg.properties.push({
|
|
2574
|
+
name: "internal:workspaceSrcFile",
|
|
2575
|
+
value: wsrcFile,
|
|
2576
|
+
});
|
|
2577
|
+
addedMap[wsrcFile] = true;
|
|
2578
|
+
}
|
|
2579
|
+
depsWorkspaceRefs[dref] = depsWorkspaceRefs[dref] || [];
|
|
2580
|
+
depsWorkspaceRefs[dref] = depsWorkspaceRefs[dref].concat(
|
|
2581
|
+
dependenciesMap[depref] || [],
|
|
2582
|
+
);
|
|
2583
|
+
_setTreeWorkspaceRef(
|
|
2584
|
+
dependenciesMap,
|
|
2585
|
+
dref,
|
|
2586
|
+
pkgRefMap,
|
|
2587
|
+
wref,
|
|
2588
|
+
wsrcFile,
|
|
2589
|
+
depsWorkspaceRefs,
|
|
2590
|
+
);
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
/**
|
|
2595
|
+
* Strip the peer-dependency resolution that pnpm encodes into a version string.
|
|
2596
|
+
*
|
|
2597
|
+
* pnpm 5 and below append it after an underscore, either as a peer spec or as a hash
|
|
2598
|
+
* of one, while 6 and above wrap it in parentheses:
|
|
2599
|
+
*
|
|
2600
|
+
* 7.26.0_typescript@6.0.2
|
|
2601
|
+
* 2.3.3_5b3b7d3a75edb27abc53579646941536
|
|
2602
|
+
* 3.0.1(ajv@8.14.0)
|
|
2603
|
+
*
|
|
2604
|
+
* An npm version can contain neither an underscore nor a parenthesis, so both are
|
|
2605
|
+
* unambiguous separators. The suffix must not survive into a version or a purl: no
|
|
2606
|
+
* SCA tool can match `2.3.3_5b3b7d...` against an advisory, so a component carrying
|
|
2607
|
+
* one is effectively invisible to vulnerability lookups.
|
|
2608
|
+
*
|
|
2609
|
+
* @param {String} version Version string from a pnpm lock file
|
|
2610
|
+
*
|
|
2611
|
+
* @returns {String} The published version, without the peer-resolution suffix
|
|
2612
|
+
*/
|
|
2613
|
+
export function stripPnpmPeerSuffix(version) {
|
|
2614
|
+
// Only a registry version carries this suffix. Git refs, file:/link: paths and
|
|
2615
|
+
// package aliases may legitimately contain an underscore, and they never begin
|
|
2616
|
+
// with a digit.
|
|
2617
|
+
if (typeof version !== "string" || !/^\d/.test(version)) {
|
|
2618
|
+
return version;
|
|
2619
|
+
}
|
|
2620
|
+
return version.split("(")[0].split("_")[0];
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
/**
|
|
2624
|
+
* Resolve a concrete version for a pnpm dependency.
|
|
2625
|
+
*
|
|
2626
|
+
* Link/file references are resolved by reading the target package.json, while
|
|
2627
|
+
* registry versions have their pnpm peer-dependency suffix stripped via
|
|
2628
|
+
* {@link stripPnpmPeerSuffix}.
|
|
2629
|
+
*
|
|
2630
|
+
* @param {string|object} depPkg Dependency version string or package object.
|
|
2631
|
+
* @param {string} relativePath Base path for resolving link/file references.
|
|
2632
|
+
* @returns {Promise<string|undefined>} The resolved published version.
|
|
2633
|
+
*/
|
|
2634
|
+
export async function getVersionNumPnpm(depPkg, relativePath) {
|
|
2635
|
+
let version = depPkg;
|
|
2636
|
+
if (typeof version === "object" && depPkg.version) {
|
|
2637
|
+
version = depPkg.version;
|
|
2638
|
+
}
|
|
2639
|
+
// link:../packages/plugin-highlight-ssr
|
|
2640
|
+
if (version.startsWith("link:") || version.startsWith("file:")) {
|
|
2641
|
+
version = version.replace("link:", "").replace("file:", "");
|
|
2642
|
+
const relativePkgJson = relativePath
|
|
2643
|
+
? join(relativePath, version, "package.json")
|
|
2644
|
+
: join(version.replaceAll("../", "packages/"), "package.json");
|
|
2645
|
+
if (safeExistsSync(relativePkgJson)) {
|
|
2646
|
+
const importedComponentObj = await parsePkgJson(relativePkgJson, true);
|
|
2647
|
+
version = importedComponentObj[0].version;
|
|
2648
|
+
} else if (safeExistsSync(join(version, "package.json"))) {
|
|
2649
|
+
const importedComponentObj = await parsePkgJson(
|
|
2650
|
+
join(version, "package.json"),
|
|
2651
|
+
true,
|
|
2652
|
+
);
|
|
2653
|
+
version = importedComponentObj[0].version;
|
|
2654
|
+
} else if (
|
|
2655
|
+
safeExistsSync(join(version.replaceAll("../", ""), "package.json"))
|
|
2656
|
+
) {
|
|
2657
|
+
const importedComponentObj = await parsePkgJson(
|
|
2658
|
+
join(version.replaceAll("../", ""), "package.json"),
|
|
2659
|
+
true,
|
|
2660
|
+
);
|
|
2661
|
+
version = importedComponentObj[0].version;
|
|
2662
|
+
}
|
|
2663
|
+
} else {
|
|
2664
|
+
// version: 3.0.1(ajv@8.14.0) or, under lockfile 5, 3.0.1_ajv@8.14.0
|
|
2665
|
+
version = stripPnpmPeerSuffix(version);
|
|
2666
|
+
}
|
|
2667
|
+
return version;
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
/**
|
|
2671
|
+
* Resolve a pnpm dependency to its PURL string.
|
|
2672
|
+
*
|
|
2673
|
+
* Moved here from npmutils.js to break the npmutils ↔ parsers-js cycle.
|
|
2674
|
+
* Only callers are in this file.
|
|
2675
|
+
*
|
|
2676
|
+
* @param {string|object} depPkg Dependency package version or object
|
|
2677
|
+
* @param {string} packageName Package name
|
|
2678
|
+
* @param {object} gitPkgRefs Git package refs map
|
|
2679
|
+
* @param {string} relativePath Relative path for resolution
|
|
2680
|
+
* @param {string} githubServerHost GitHub server host
|
|
2681
|
+
* @param {object} [npmrcConfig={}] npmrc configuration
|
|
2682
|
+
* @returns {Promise<string>} Decoded PURL string
|
|
2683
|
+
*/
|
|
2684
|
+
export async function getPnpmDepPurl(
|
|
2685
|
+
depPkg,
|
|
2686
|
+
packageName,
|
|
2687
|
+
gitPkgRefs,
|
|
2688
|
+
relativePath,
|
|
2689
|
+
githubServerHost,
|
|
2690
|
+
npmrcConfig = {},
|
|
2691
|
+
) {
|
|
2692
|
+
let name = packageName;
|
|
2693
|
+
let group = "";
|
|
2694
|
+
let version;
|
|
2695
|
+
const versionObj = typeof depPkg === "object" ? depPkg : { version: depPkg };
|
|
2696
|
+
if (versionObj?.version?.startsWith(githubServerHost)) {
|
|
2697
|
+
const parts = versionObj.version.split("/");
|
|
2698
|
+
version = parts.pop();
|
|
2699
|
+
name = parts.pop();
|
|
2700
|
+
group = parts.pop();
|
|
2701
|
+
if (group === githubServerHost) {
|
|
2702
|
+
group = "";
|
|
2703
|
+
} else {
|
|
2704
|
+
group = `@${group}`;
|
|
2705
|
+
}
|
|
2706
|
+
gitPkgRefs[versionObj.version] = { group, name, version };
|
|
2707
|
+
} else {
|
|
2708
|
+
version = await getVersionNumPnpm(depPkg, relativePath);
|
|
2709
|
+
const gitEntry =
|
|
2710
|
+
gitPkgRefs[packageName] ||
|
|
2711
|
+
gitPkgRefs[version] ||
|
|
2712
|
+
gitPkgRefs[normalizePnpmLockKey(`${packageName}@${version}`)];
|
|
2713
|
+
if (gitEntry) {
|
|
2714
|
+
group = gitEntry.group;
|
|
2715
|
+
name = gitEntry.name;
|
|
2716
|
+
version = gitEntry.version;
|
|
2717
|
+
const qualifiers =
|
|
2718
|
+
gitEntry.qualifiers ||
|
|
2719
|
+
buildNpmGitPurlQualifiers(gitEntry.vcsUrl, group, npmrcConfig);
|
|
2720
|
+
return decodeURIComponent(
|
|
2721
|
+
build({
|
|
2722
|
+
type: "npm",
|
|
2723
|
+
namespace: group || null,
|
|
2724
|
+
name: name,
|
|
2725
|
+
version: version || null,
|
|
2726
|
+
qualifiers: qualifiers || null,
|
|
2727
|
+
}),
|
|
2728
|
+
);
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
return decodeURIComponent(
|
|
2732
|
+
build({
|
|
2733
|
+
type: "npm",
|
|
2734
|
+
namespace: group || null,
|
|
2735
|
+
name: name,
|
|
2736
|
+
version: version || null,
|
|
2737
|
+
}),
|
|
2738
|
+
);
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
/**
|
|
2742
|
+
* Parse pnpm workspace file
|
|
2743
|
+
*
|
|
2744
|
+
* @param {string} workspaceFile pnpm-workspace.yaml
|
|
2745
|
+
* @returns {object} Object containing packages and catalogs
|
|
2746
|
+
*/
|
|
2747
|
+
export function parsePnpmWorkspace(workspaceFile) {
|
|
2748
|
+
const workspaceData = readFileSync(workspaceFile, "utf-8");
|
|
2749
|
+
const yamlObj = _load(workspaceData);
|
|
2750
|
+
if (!yamlObj) {
|
|
2751
|
+
return {};
|
|
2752
|
+
}
|
|
2753
|
+
const workspacePackages = Array.isArray(yamlObj.packages)
|
|
2754
|
+
? yamlObj.packages.filter((n) => typeof n === "string").map((n) => n.trim())
|
|
2755
|
+
: typeof yamlObj.packages === "string"
|
|
2756
|
+
? [yamlObj.packages.trim()]
|
|
2757
|
+
: [];
|
|
2758
|
+
// Normalize a leading "./" so relative patterns such as "./packages/*" are
|
|
2759
|
+
// treated identically to "packages/*".
|
|
2760
|
+
const stripDotSlash = (n) => n.replace(/^\.\//, "");
|
|
2761
|
+
const excludePackages = workspacePackages
|
|
2762
|
+
.filter((n) => n.startsWith("!"))
|
|
2763
|
+
.map((n) => stripDotSlash(n.replace(/^!/, "")));
|
|
2764
|
+
const packagePatterns = workspacePackages
|
|
2765
|
+
// Drop exclusions ("!"), internal dirs ("__"), and the workspace root
|
|
2766
|
+
// self-reference ("." / "./"). Relative subpaths such as "./packages/*"
|
|
2767
|
+
// and dotdir members such as ".meta-updater" are kept - only the bare
|
|
2768
|
+
// root reference is dropped.
|
|
2769
|
+
.filter((n) => !/^(!|__)/.test(n) && n !== "." && n !== "./")
|
|
2770
|
+
.map(stripDotSlash);
|
|
2771
|
+
const packages = packagePatterns.map((n) =>
|
|
2772
|
+
n.replaceAll("/**", "").replaceAll("/*", ""),
|
|
2773
|
+
);
|
|
2774
|
+
const catalogs = yamlObj.catalog || {};
|
|
2775
|
+
return {
|
|
2776
|
+
excludePackages,
|
|
2777
|
+
packagePatterns,
|
|
2778
|
+
packages,
|
|
2779
|
+
catalogs,
|
|
2780
|
+
};
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2783
|
+
/**
|
|
2784
|
+
* Parses the workspaces field from a package.json file and returns the list of
|
|
2785
|
+
* workspace glob patterns. Handles both array and object (with packages key) formats.
|
|
2786
|
+
*
|
|
2787
|
+
* @param {string} packageJsonFile Path to the package.json file to parse
|
|
2788
|
+
* @returns {Object} Object with a packages array of workspace glob patterns, or an empty object on error
|
|
2789
|
+
*/
|
|
2790
|
+
export function parseYarnWorkspace(packageJsonFile) {
|
|
2791
|
+
try {
|
|
2792
|
+
const packageData = JSON.parse(readFileSync(packageJsonFile, "utf-8"));
|
|
2793
|
+
if (!packageData?.workspaces) {
|
|
2794
|
+
return {};
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
let packages = [];
|
|
2798
|
+
// Handle both array and object formats
|
|
2799
|
+
if (Array.isArray(packageData.workspaces)) {
|
|
2800
|
+
packages = packageData.workspaces;
|
|
2801
|
+
} else if (
|
|
2802
|
+
packageData.workspaces.packages &&
|
|
2803
|
+
Array.isArray(packageData.workspaces.packages)
|
|
2804
|
+
) {
|
|
2805
|
+
packages = packageData.workspaces.packages;
|
|
2806
|
+
}
|
|
2807
|
+
// Filter and normalize package patterns - don't strip the glob patterns as they're needed
|
|
2808
|
+
packages = packages.filter((n) => !/^(!|\.|__)/.test(n));
|
|
2809
|
+
return {
|
|
2810
|
+
packages,
|
|
2811
|
+
};
|
|
2812
|
+
} catch (_err) {
|
|
2813
|
+
return {};
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
/**
|
|
2818
|
+
* Helper function to find a package path in pnpm node_modules structure
|
|
2819
|
+
*
|
|
2820
|
+
* @param {string} baseDir Base directory containing node_modules
|
|
2821
|
+
* @param {string} packageName Package name (with or without scope)
|
|
2822
|
+
* @param {string} version Package version
|
|
2823
|
+
* @returns {string|null} Path to the package directory or null if not found
|
|
2824
|
+
*/
|
|
2825
|
+
export function findPnpmPackagePath(baseDir, packageName, version) {
|
|
2826
|
+
if (!baseDir || !packageName) {
|
|
2827
|
+
return null;
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
const nodeModulesDir = join(baseDir, "node_modules");
|
|
2831
|
+
if (!safeExistsSync(nodeModulesDir)) {
|
|
2832
|
+
return null;
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
// Try direct node_modules lookup first (for symlinked packages)
|
|
2836
|
+
const directPath = join(nodeModulesDir, packageName);
|
|
2837
|
+
if (
|
|
2838
|
+
safeExistsSync(directPath) &&
|
|
2839
|
+
safeExistsSync(join(directPath, "package.json"))
|
|
2840
|
+
) {
|
|
2841
|
+
return directPath;
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2844
|
+
// Try pnpm's .pnpm directory structure
|
|
2845
|
+
const pnpmDir = join(nodeModulesDir, ".pnpm");
|
|
2846
|
+
if (safeExistsSync(pnpmDir)) {
|
|
2847
|
+
// pnpm stores packages as {name}@{version} in .pnpm directory
|
|
2848
|
+
const encodedName = packageName.replaceAll("/", "%2f");
|
|
2849
|
+
const virtualStoreName = packageName.replaceAll("/", "+");
|
|
2850
|
+
let pnpmPackagePath;
|
|
2851
|
+
|
|
2852
|
+
// Try different formats that pnpm might use
|
|
2853
|
+
const possiblePaths = [
|
|
2854
|
+
join(
|
|
2855
|
+
pnpmDir,
|
|
2856
|
+
`${virtualStoreName}@${version}`,
|
|
2857
|
+
"node_modules",
|
|
2858
|
+
packageName,
|
|
2859
|
+
),
|
|
2860
|
+
join(pnpmDir, `${encodedName}@${version}`, "node_modules", packageName),
|
|
2861
|
+
join(pnpmDir, `${packageName}@${version}`, "node_modules", packageName),
|
|
2862
|
+
join(pnpmDir, `${virtualStoreName}@${version}`),
|
|
2863
|
+
join(pnpmDir, `${encodedName}@${version}`),
|
|
2864
|
+
join(pnpmDir, `${packageName}@${version}`),
|
|
2865
|
+
];
|
|
2866
|
+
|
|
2867
|
+
for (const possiblePath of possiblePaths) {
|
|
2868
|
+
if (
|
|
2869
|
+
safeExistsSync(possiblePath) &&
|
|
2870
|
+
safeExistsSync(join(possiblePath, "package.json"))
|
|
2871
|
+
) {
|
|
2872
|
+
pnpmPackagePath = possiblePath;
|
|
2873
|
+
break;
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
if (pnpmPackagePath) {
|
|
2878
|
+
return pnpmPackagePath;
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
return null;
|
|
2883
|
+
}
|
|
2884
|
+
|
|
2885
|
+
/**
|
|
2886
|
+
* pnpm packages with metadata from local node_modules
|
|
2887
|
+
*
|
|
2888
|
+
* @param {Array} pkgList Package list to enhance
|
|
2889
|
+
* @param {string} lockFilePath Path to the pnpm-lock.yaml file
|
|
2890
|
+
* @returns {Array} Enhanced package list
|
|
2891
|
+
*/
|
|
2892
|
+
export async function pnpmMetadata(pkgList, lockFilePath) {
|
|
2893
|
+
if (!pkgList?.length || !lockFilePath) {
|
|
2894
|
+
return pkgList;
|
|
2895
|
+
}
|
|
2896
|
+
const baseDir = dirname(lockFilePath);
|
|
2897
|
+
const nodeModulesDir = join(baseDir, "node_modules");
|
|
2898
|
+
if (!safeExistsSync(nodeModulesDir)) {
|
|
2899
|
+
return pkgList;
|
|
2900
|
+
}
|
|
2901
|
+
if (DEBUG_MODE) {
|
|
2902
|
+
console.log(
|
|
2903
|
+
`Metadata for ${pkgList.length} pnpm packages using local node_modules at ${nodeModulesDir}`,
|
|
2904
|
+
);
|
|
2905
|
+
}
|
|
2906
|
+
let enhancedCount = 0;
|
|
2907
|
+
for (const pkg of pkgList) {
|
|
2908
|
+
const packageName = pkg.group ? `${pkg.group}/${pkg.name}` : pkg.name;
|
|
2909
|
+
const packagePath = findPnpmPackagePath(baseDir, packageName, pkg.version);
|
|
2910
|
+
if (!packagePath) {
|
|
2911
|
+
continue;
|
|
2912
|
+
}
|
|
2913
|
+
const packageJsonPath = join(packagePath, "package.json");
|
|
2914
|
+
if (!safeExistsSync(packageJsonPath)) {
|
|
2915
|
+
continue;
|
|
2916
|
+
}
|
|
2917
|
+
try {
|
|
2918
|
+
const localPkgList = await parsePkgJson(packageJsonPath, true, true);
|
|
2919
|
+
if (localPkgList && localPkgList.length === 1) {
|
|
2920
|
+
const localMetadata = localPkgList[0];
|
|
2921
|
+
if (localMetadata && Object.keys(localMetadata).length) {
|
|
2922
|
+
if (!pkg.description && localMetadata.description) {
|
|
2923
|
+
pkg.description = localMetadata.description;
|
|
2924
|
+
}
|
|
2925
|
+
if (!pkg.author && localMetadata.author) {
|
|
2926
|
+
pkg.author = localMetadata.author;
|
|
2927
|
+
}
|
|
2928
|
+
if (!pkg.license && localMetadata.license) {
|
|
2929
|
+
pkg.license = localMetadata.license;
|
|
2930
|
+
}
|
|
2931
|
+
if (!pkg.homepage && localMetadata.homepage) {
|
|
2932
|
+
pkg.homepage = localMetadata.homepage;
|
|
2933
|
+
}
|
|
2934
|
+
if (!pkg.repository && localMetadata.repository) {
|
|
2935
|
+
pkg.repository = localMetadata.repository;
|
|
2936
|
+
}
|
|
2937
|
+
if (!pkg.properties) {
|
|
2938
|
+
pkg.properties = [];
|
|
2939
|
+
}
|
|
2940
|
+
if (localMetadata?.properties?.length) {
|
|
2941
|
+
const seenProperties = new Set(
|
|
2942
|
+
pkg.properties.map(
|
|
2943
|
+
(prop) => `${String(prop?.name)}\u0000${String(prop?.value)}`,
|
|
2944
|
+
),
|
|
2945
|
+
);
|
|
2946
|
+
for (const prop of localMetadata.properties) {
|
|
2947
|
+
const propertyKey = `${String(prop?.name)}\u0000${String(prop?.value)}`;
|
|
2948
|
+
if (!seenProperties.has(propertyKey)) {
|
|
2949
|
+
pkg.properties.push(prop);
|
|
2950
|
+
seenProperties.add(propertyKey);
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
pkg.properties.push({
|
|
2955
|
+
name: "internal:LocalNodeModulesPath",
|
|
2956
|
+
value: packagePath,
|
|
2957
|
+
});
|
|
2958
|
+
enhancedCount++;
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
} catch (error) {
|
|
2962
|
+
// Silently ignore parsing errors for individual packages
|
|
2963
|
+
if (DEBUG_MODE) {
|
|
2964
|
+
console.log(
|
|
2965
|
+
`Failed to parse package.json at ${packageJsonPath}:`,
|
|
2966
|
+
error.message,
|
|
2967
|
+
);
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
if (DEBUG_MODE && enhancedCount > 0) {
|
|
2972
|
+
console.log(
|
|
2973
|
+
`Enhanced metadata for ${enhancedCount} packages from local node_modules`,
|
|
2974
|
+
);
|
|
2975
|
+
}
|
|
2976
|
+
return pkgList;
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
/**
|
|
2980
|
+
* Resolve a pnpm dependency value that is an alias pointing at another lock entry
|
|
2981
|
+
* rather than a plain version.
|
|
2982
|
+
*
|
|
2983
|
+
* pnpm writes such a value in the same shape as its package keys, and that shape
|
|
2984
|
+
* changed between lockfile versions:
|
|
2985
|
+
*
|
|
2986
|
+
* v5 and below: /@wdio/utils/7.26.0_typescript@6.0.2, /string-width/4.2.3
|
|
2987
|
+
* v6 and above: /@wdio/utils@7.26.0, string-width@4.2.3
|
|
2988
|
+
*
|
|
2989
|
+
* The version is returned verbatim, peer-dependency suffix included, because that
|
|
2990
|
+
* is how the aliased package's own lock key is turned into a version elsewhere in
|
|
2991
|
+
* parsePnpmLock - stripping it here would leave the dependency ref pointing at a
|
|
2992
|
+
* component that does not exist.
|
|
2993
|
+
*
|
|
2994
|
+
* @param {String} value Raw dependency value from the lock file
|
|
2995
|
+
*
|
|
2996
|
+
* @returns {Object|undefined} `{name, version}`, or undefined when the value is a
|
|
2997
|
+
* plain version and not an alias
|
|
2998
|
+
*/
|
|
2999
|
+
export function parsePnpmAliasRef(value) {
|
|
3000
|
+
if (!value) {
|
|
3001
|
+
return undefined;
|
|
3002
|
+
}
|
|
3003
|
+
const spec = value.startsWith("/") ? value.slice(1) : value;
|
|
3004
|
+
// A leading @ introduces a scope, so the name spans two path segments there
|
|
3005
|
+
const nameSegCount = spec.startsWith("@") ? 2 : 1;
|
|
3006
|
+
const segments = spec.split("/");
|
|
3007
|
+
if (segments.length > nameSegCount) {
|
|
3008
|
+
// v5 style: the version is a path segment of its own
|
|
3009
|
+
return {
|
|
3010
|
+
name: segments.slice(0, nameSegCount).join("/"),
|
|
3011
|
+
version: segments.slice(nameSegCount).join("/"),
|
|
3012
|
+
};
|
|
3013
|
+
}
|
|
3014
|
+
if (segments.length < nameSegCount) {
|
|
3015
|
+
return undefined;
|
|
3016
|
+
}
|
|
3017
|
+
// v6 style: the version is glued to the last name segment with an @. Index 0 is
|
|
3018
|
+
// excluded so that the scope leader of an unversioned /@scope/pkg is not mistaken
|
|
3019
|
+
// for the separator.
|
|
3020
|
+
const lastSegment = segments[nameSegCount - 1];
|
|
3021
|
+
const sepIdx = lastSegment.indexOf("@");
|
|
3022
|
+
if (sepIdx < 1) {
|
|
3023
|
+
return undefined;
|
|
3024
|
+
}
|
|
3025
|
+
segments[nameSegCount - 1] = lastSegment.slice(0, sepIdx);
|
|
3026
|
+
return {
|
|
3027
|
+
name: segments.join("/"),
|
|
3028
|
+
version: lastSegment.slice(sepIdx + 1),
|
|
3029
|
+
};
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
/**
|
|
3033
|
+
* Parse nodejs pnpm lock file
|
|
3034
|
+
*
|
|
3035
|
+
* @param {string} pnpmLock pnpm-lock.yaml file
|
|
3036
|
+
* @param {Object} parentComponent parent component
|
|
3037
|
+
* @param {Array[String]} workspacePackages Workspace packages
|
|
3038
|
+
* @param {Object} workspaceSrcFiles Workspace package.json files
|
|
3039
|
+
* @param {Object} _workspaceCatalogs Workspace catalogs
|
|
3040
|
+
* @param {Object} _workspaceDirectDeps Direct dependencies of each workspace
|
|
3041
|
+
* @param {Object} depsWorkspaceRefs Workspace references for each dependency
|
|
3042
|
+
* @param {string} projectRoot Root path used to relativize pnpm-lock evidence paths
|
|
3043
|
+
*/
|
|
3044
|
+
export async function parsePnpmLock(
|
|
3045
|
+
pnpmLock,
|
|
3046
|
+
parentComponent = null,
|
|
3047
|
+
workspacePackages = [],
|
|
3048
|
+
workspaceSrcFiles = {},
|
|
3049
|
+
_workspaceCatalogs = {},
|
|
3050
|
+
_workspaceDirectDeps = {},
|
|
3051
|
+
depsWorkspaceRefs = {},
|
|
3052
|
+
projectRoot = undefined,
|
|
3053
|
+
) {
|
|
3054
|
+
let pkgList = [];
|
|
3055
|
+
const dependenciesList = [];
|
|
3056
|
+
const pnpmLockDir = dirname(pnpmLock);
|
|
3057
|
+
const pnpmEvidenceRoot = projectRoot ? resolve(projectRoot) : pnpmLockDir;
|
|
3058
|
+
const npmrcConfig = loadNpmrcConfig(projectRoot || pnpmLockDir);
|
|
3059
|
+
const pnpmLockSrcFile = path.isAbsolute(pnpmLock)
|
|
3060
|
+
? relative(pnpmEvidenceRoot, pnpmLock)
|
|
3061
|
+
: pnpmLock;
|
|
3062
|
+
// For lockfile >= 9, we need to track dev and optional packages manually
|
|
3063
|
+
// See: #1163
|
|
3064
|
+
// Moreover, we have changed >= 9 for >= 6
|
|
3065
|
+
// See: discussion #1359
|
|
3066
|
+
const possibleDevelopmentDeps = {};
|
|
3067
|
+
const possibleOptionalDeps = {};
|
|
3068
|
+
const dependenciesMap = {};
|
|
3069
|
+
let ppurl = "";
|
|
3070
|
+
let lockfileVersion = 0;
|
|
3071
|
+
const parentSubComponents = [];
|
|
3072
|
+
const srcFilesMap = {};
|
|
3073
|
+
const workspacePackageNames = {};
|
|
3074
|
+
const pkgRefMap = {};
|
|
3075
|
+
// Track references to packages that are directly installed from github.com
|
|
3076
|
+
const gitPkgRefs = {};
|
|
3077
|
+
// Track aliases
|
|
3078
|
+
const possibleAliases = {};
|
|
3079
|
+
const possibleAliasesRefs = {};
|
|
3080
|
+
// pnpm could refer to packages from git sources
|
|
3081
|
+
const githubServerHost =
|
|
3082
|
+
readEnvironmentVariable("CDXGEN_GIT_HOST") || "github.com";
|
|
3083
|
+
// Convert workspace package names to an object to help with the lookup
|
|
3084
|
+
for (const w of workspacePackages || []) {
|
|
3085
|
+
workspacePackageNames[w] = true;
|
|
3086
|
+
}
|
|
3087
|
+
if (parentComponent?.name) {
|
|
3088
|
+
ppurl =
|
|
3089
|
+
parentComponent.purl ||
|
|
3090
|
+
build({
|
|
3091
|
+
type: "npm",
|
|
3092
|
+
namespace: parentComponent.group || null,
|
|
3093
|
+
name: parentComponent.name,
|
|
3094
|
+
version: parentComponent.version || null,
|
|
3095
|
+
});
|
|
3096
|
+
}
|
|
3097
|
+
if (safeExistsSync(pnpmLock)) {
|
|
3098
|
+
const lockData = readFileSync(pnpmLock, "utf8");
|
|
3099
|
+
let yamlObj = parseAllDocuments(lockData);
|
|
3100
|
+
if (!yamlObj) {
|
|
3101
|
+
return {};
|
|
3102
|
+
}
|
|
3103
|
+
if (Array.isArray(yamlObj)) {
|
|
3104
|
+
try {
|
|
3105
|
+
yamlObj = yamlObj[yamlObj.length - 1].toJS();
|
|
3106
|
+
} catch (_e) {
|
|
3107
|
+
console.log(`Unable to parse the pnpm lock file ${pnpmLock}.`);
|
|
3108
|
+
return {};
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
lockfileVersion = yamlObj.lockfileVersion;
|
|
3112
|
+
try {
|
|
3113
|
+
lockfileVersion = Number.parseFloat(lockfileVersion, 10);
|
|
3114
|
+
} catch (_e) {
|
|
3115
|
+
// ignore parse errors
|
|
3116
|
+
}
|
|
3117
|
+
Object.assign(
|
|
3118
|
+
gitPkgRefs,
|
|
3119
|
+
buildPnpmGitPkgRefs(
|
|
3120
|
+
yamlObj.packages || {},
|
|
3121
|
+
yamlObj.snapshots || {},
|
|
3122
|
+
npmrcConfig,
|
|
3123
|
+
),
|
|
3124
|
+
);
|
|
3125
|
+
// This logic matches the pnpm list command to include only direct dependencies
|
|
3126
|
+
if (ppurl !== "" && yamlObj?.importers) {
|
|
3127
|
+
// In lock file version 6, direct dependencies is under importers
|
|
3128
|
+
const rootDirectDeps =
|
|
3129
|
+
lockfileVersion >= 6
|
|
3130
|
+
? yamlObj.importers["."]?.dependencies || {}
|
|
3131
|
+
: yamlObj.dependencies || {};
|
|
3132
|
+
const rootDevDeps =
|
|
3133
|
+
lockfileVersion >= 6
|
|
3134
|
+
? yamlObj.importers["."]?.devDependencies || {}
|
|
3135
|
+
: {};
|
|
3136
|
+
const rootOptionalDeps =
|
|
3137
|
+
lockfileVersion >= 6
|
|
3138
|
+
? yamlObj.importers["."]?.optionalDependencies || {}
|
|
3139
|
+
: {};
|
|
3140
|
+
const rootPeerDeps =
|
|
3141
|
+
lockfileVersion >= 6
|
|
3142
|
+
? yamlObj.importers["."]?.peerDependencies || {}
|
|
3143
|
+
: {};
|
|
3144
|
+
const ddeplist = new Set();
|
|
3145
|
+
// Find the root dev dependencies
|
|
3146
|
+
for (const rdk of Object.keys(rootDevDeps)) {
|
|
3147
|
+
let specifier;
|
|
3148
|
+
if (
|
|
3149
|
+
typeof rootDevDeps[rdk] === "object" &&
|
|
3150
|
+
rootDevDeps[rdk].specifier
|
|
3151
|
+
) {
|
|
3152
|
+
specifier = rootDevDeps[rdk].specifier;
|
|
3153
|
+
}
|
|
3154
|
+
if (specifier?.includes("npm:")) {
|
|
3155
|
+
possibleAliases[rdk] = specifier;
|
|
3156
|
+
}
|
|
3157
|
+
const dpurl = await getPnpmDepPurl(
|
|
3158
|
+
rootDevDeps[rdk],
|
|
3159
|
+
rdk,
|
|
3160
|
+
gitPkgRefs,
|
|
3161
|
+
undefined,
|
|
3162
|
+
githubServerHost,
|
|
3163
|
+
npmrcConfig,
|
|
3164
|
+
);
|
|
3165
|
+
possibleDevelopmentDeps[dpurl] = true;
|
|
3166
|
+
}
|
|
3167
|
+
// Find the root optional and peer dependencies
|
|
3168
|
+
for (const rdk of Object.keys({ ...rootOptionalDeps, ...rootPeerDeps })) {
|
|
3169
|
+
let specifier;
|
|
3170
|
+
if (
|
|
3171
|
+
typeof rootOptionalDeps[rdk] === "object" &&
|
|
3172
|
+
rootOptionalDeps[rdk].specifier
|
|
3173
|
+
) {
|
|
3174
|
+
specifier = rootOptionalDeps[rdk].specifier;
|
|
3175
|
+
}
|
|
3176
|
+
if (specifier?.includes("npm:")) {
|
|
3177
|
+
possibleAliases[rdk] = specifier;
|
|
3178
|
+
}
|
|
3179
|
+
const dpurl = await getPnpmDepPurl(
|
|
3180
|
+
rootOptionalDeps[rdk] || rootPeerDeps[rdk],
|
|
3181
|
+
rdk,
|
|
3182
|
+
gitPkgRefs,
|
|
3183
|
+
undefined,
|
|
3184
|
+
githubServerHost,
|
|
3185
|
+
npmrcConfig,
|
|
3186
|
+
);
|
|
3187
|
+
possibleOptionalDeps[dpurl] = true;
|
|
3188
|
+
possibleDevelopmentDeps[dpurl] = false;
|
|
3189
|
+
}
|
|
3190
|
+
// Find the root direct dependencies
|
|
3191
|
+
for (const dk of Object.keys(rootDirectDeps)) {
|
|
3192
|
+
let specifier;
|
|
3193
|
+
if (
|
|
3194
|
+
typeof rootDirectDeps[dk] === "object" &&
|
|
3195
|
+
rootDirectDeps[dk].specifier
|
|
3196
|
+
) {
|
|
3197
|
+
specifier = rootDirectDeps[dk].specifier;
|
|
3198
|
+
}
|
|
3199
|
+
if (specifier?.includes("npm:")) {
|
|
3200
|
+
possibleAliases[dk] = specifier;
|
|
3201
|
+
}
|
|
3202
|
+
const dpurl = await getPnpmDepPurl(
|
|
3203
|
+
rootDirectDeps[dk],
|
|
3204
|
+
dk,
|
|
3205
|
+
gitPkgRefs,
|
|
3206
|
+
undefined,
|
|
3207
|
+
githubServerHost,
|
|
3208
|
+
npmrcConfig,
|
|
3209
|
+
);
|
|
3210
|
+
ddeplist.add(dpurl);
|
|
3211
|
+
if (lockfileVersion >= 6) {
|
|
3212
|
+
// These are direct dependencies so cannot be optional
|
|
3213
|
+
possibleOptionalDeps[dpurl] = false;
|
|
3214
|
+
}
|
|
3215
|
+
possibleDevelopmentDeps[dpurl] = false;
|
|
3216
|
+
}
|
|
3217
|
+
// pnpm-lock.yaml contains more than root dependencies in importers
|
|
3218
|
+
// we do what we did above but for all the other components
|
|
3219
|
+
for (const importedComponentName of Object.keys(yamlObj?.importers)) {
|
|
3220
|
+
// if component name is '.' continue loop
|
|
3221
|
+
if (importedComponentName === ".") {
|
|
3222
|
+
continue;
|
|
3223
|
+
}
|
|
3224
|
+
const componentDeps =
|
|
3225
|
+
yamlObj?.importers[importedComponentName]["dependencies"] || {};
|
|
3226
|
+
const componentDevDeps =
|
|
3227
|
+
yamlObj?.importers[importedComponentName]["devDependencies"] || {};
|
|
3228
|
+
const componentOptionalDeps =
|
|
3229
|
+
yamlObj?.importers[importedComponentName]["optionalDependencies"] ||
|
|
3230
|
+
{};
|
|
3231
|
+
const componentPeerDeps =
|
|
3232
|
+
yamlObj?.importers[importedComponentName]["peerDependencies"] || {};
|
|
3233
|
+
let compPurl;
|
|
3234
|
+
let pkgSrcFile;
|
|
3235
|
+
let fallbackMode = true;
|
|
3236
|
+
if (
|
|
3237
|
+
safeExistsSync(
|
|
3238
|
+
join(pnpmLockDir, importedComponentName, "package.json"),
|
|
3239
|
+
)
|
|
3240
|
+
) {
|
|
3241
|
+
pkgSrcFile = join(pnpmLockDir, importedComponentName, "package.json");
|
|
3242
|
+
const importedComponentObj = await parsePkgJson(pkgSrcFile, true);
|
|
3243
|
+
if (importedComponentObj.length) {
|
|
3244
|
+
const version = importedComponentObj[0].version;
|
|
3245
|
+
compPurl = build({
|
|
3246
|
+
type: "npm",
|
|
3247
|
+
namespace: importedComponentObj[0]?.group || null,
|
|
3248
|
+
name: importedComponentObj[0]?.name,
|
|
3249
|
+
version: version || null,
|
|
3250
|
+
});
|
|
3251
|
+
const compRef = decodeURIComponent(compPurl);
|
|
3252
|
+
// Add this package to the root dependency list and parent component
|
|
3253
|
+
ddeplist.add(compRef);
|
|
3254
|
+
const psObj = {
|
|
3255
|
+
group: importedComponentObj[0]?.group,
|
|
3256
|
+
name: importedComponentObj[0]?.name,
|
|
3257
|
+
version,
|
|
3258
|
+
type: "application",
|
|
3259
|
+
purl: compPurl,
|
|
3260
|
+
"bom-ref": compRef,
|
|
3261
|
+
};
|
|
3262
|
+
const purlNoVersion = build({
|
|
3263
|
+
type: "npm",
|
|
3264
|
+
namespace: importedComponentObj[0]?.group || null,
|
|
3265
|
+
name: importedComponentObj[0]?.name,
|
|
3266
|
+
version: version || null,
|
|
3267
|
+
});
|
|
3268
|
+
const matchRef =
|
|
3269
|
+
workspacePackageNames[decodeURIComponent(purlNoVersion)] ||
|
|
3270
|
+
workspacePackageNames[compRef];
|
|
3271
|
+
const matchSrcFile =
|
|
3272
|
+
workspaceSrcFiles[decodeURIComponent(purlNoVersion)] ||
|
|
3273
|
+
workspaceSrcFiles[compRef];
|
|
3274
|
+
if (matchRef || matchSrcFile) {
|
|
3275
|
+
psObj.properties = [
|
|
3276
|
+
{ name: "internal:is_workspace", value: "true" },
|
|
3277
|
+
];
|
|
3278
|
+
}
|
|
3279
|
+
if (matchSrcFile) {
|
|
3280
|
+
psObj.properties.push({
|
|
3281
|
+
name: "internal:SrcFile",
|
|
3282
|
+
value: matchSrcFile,
|
|
3283
|
+
});
|
|
3284
|
+
psObj.properties.push({
|
|
3285
|
+
name: "internal:virtual_path",
|
|
3286
|
+
value: relative(dirname(pnpmLock), dirname(matchSrcFile)),
|
|
3287
|
+
});
|
|
3288
|
+
}
|
|
3289
|
+
parentSubComponents.push(psObj);
|
|
3290
|
+
fallbackMode = false;
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
if (fallbackMode) {
|
|
3294
|
+
const name = importedComponentName.split("/");
|
|
3295
|
+
const lastname = name[name.length - 1];
|
|
3296
|
+
// let subpath = name.filter(part => part !== '.' && part !== '..').join('/');
|
|
3297
|
+
const subpath = name
|
|
3298
|
+
.join("/")
|
|
3299
|
+
.replaceAll("../", "")
|
|
3300
|
+
.replaceAll("./", "");
|
|
3301
|
+
compPurl = build({
|
|
3302
|
+
type: "npm",
|
|
3303
|
+
namespace: parentComponent.group || null,
|
|
3304
|
+
name: `${parentComponent.name}/${lastname}`,
|
|
3305
|
+
version: parentComponent.version || "latest" || null,
|
|
3306
|
+
subpath: subpath || null,
|
|
3307
|
+
});
|
|
3308
|
+
}
|
|
3309
|
+
// Find the component optional dependencies
|
|
3310
|
+
const comDepList = new Set();
|
|
3311
|
+
for (const cdk of Object.keys(componentDeps)) {
|
|
3312
|
+
const versionObj = componentDeps[cdk];
|
|
3313
|
+
const depRef = await getPnpmDepPurl(
|
|
3314
|
+
versionObj,
|
|
3315
|
+
cdk,
|
|
3316
|
+
gitPkgRefs,
|
|
3317
|
+
importedComponentName,
|
|
3318
|
+
githubServerHost,
|
|
3319
|
+
npmrcConfig,
|
|
3320
|
+
);
|
|
3321
|
+
// This is a definite dependency of this component
|
|
3322
|
+
comDepList.add(depRef);
|
|
3323
|
+
possibleOptionalDeps[depRef] = false;
|
|
3324
|
+
possibleDevelopmentDeps[depRef] = false;
|
|
3325
|
+
// Track the package.json files
|
|
3326
|
+
if (pkgSrcFile) {
|
|
3327
|
+
if (!srcFilesMap[depRef]) {
|
|
3328
|
+
srcFilesMap[depRef] = [];
|
|
3329
|
+
}
|
|
3330
|
+
srcFilesMap[depRef].push(pkgSrcFile);
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
for (const cdk of Object.keys(componentDevDeps)) {
|
|
3334
|
+
const version = await getVersionNumPnpm(componentDevDeps[cdk]);
|
|
3335
|
+
const dpurl = build({
|
|
3336
|
+
type: "npm",
|
|
3337
|
+
namespace: "" || null,
|
|
3338
|
+
name: cdk,
|
|
3339
|
+
version: version || null,
|
|
3340
|
+
});
|
|
3341
|
+
const devDpRef = decodeURIComponent(dpurl);
|
|
3342
|
+
possibleDevelopmentDeps[devDpRef] = true;
|
|
3343
|
+
// This is also a dependency of this component
|
|
3344
|
+
comDepList.add(devDpRef);
|
|
3345
|
+
}
|
|
3346
|
+
for (const cdk of Object.keys({
|
|
3347
|
+
...componentOptionalDeps,
|
|
3348
|
+
...componentPeerDeps,
|
|
3349
|
+
})) {
|
|
3350
|
+
const version = await getVersionNumPnpm(
|
|
3351
|
+
componentOptionalDeps[cdk] || componentPeerDeps[cdk],
|
|
3352
|
+
);
|
|
3353
|
+
const dpurl = build({
|
|
3354
|
+
type: "npm",
|
|
3355
|
+
namespace: "" || null,
|
|
3356
|
+
name: cdk,
|
|
3357
|
+
version: version || null,
|
|
3358
|
+
});
|
|
3359
|
+
possibleOptionalDeps[decodeURIComponent(dpurl)] = true;
|
|
3360
|
+
possibleDevelopmentDeps[decodeURIComponent(dpurl)] = false;
|
|
3361
|
+
}
|
|
3362
|
+
dependenciesList.push({
|
|
3363
|
+
ref: decodeURIComponent(compPurl),
|
|
3364
|
+
dependsOn: [...comDepList].sort(),
|
|
3365
|
+
});
|
|
3366
|
+
}
|
|
3367
|
+
dependenciesList.push({
|
|
3368
|
+
ref: decodeURIComponent(ppurl),
|
|
3369
|
+
dependsOn: [...ddeplist].sort(),
|
|
3370
|
+
});
|
|
3371
|
+
}
|
|
3372
|
+
const packages = yamlObj.packages || {};
|
|
3373
|
+
// snapshots is a new key under lockfile version 9
|
|
3374
|
+
const snapshots = yamlObj.snapshots || {};
|
|
3375
|
+
const pkgKeys = { ...Object.keys(packages), ...Object.keys(snapshots) };
|
|
3376
|
+
for (const k in pkgKeys) {
|
|
3377
|
+
// Eg: @babel/code-frame/7.10.1
|
|
3378
|
+
// In lockfileVersion 6, /@babel/code-frame@7.18.6
|
|
3379
|
+
let fullName = pkgKeys[k].replace("/@", "@");
|
|
3380
|
+
// Handle /vite@4.2.1(@types/node@18.15.11) in lockfileVersion 6
|
|
3381
|
+
if (lockfileVersion >= 6 && fullName.includes("(")) {
|
|
3382
|
+
fullName = fullName.split("(")[0];
|
|
3383
|
+
}
|
|
3384
|
+
const parts = fullName.split("/");
|
|
3385
|
+
const packageNode =
|
|
3386
|
+
packages[pkgKeys[k]] ||
|
|
3387
|
+
snapshots[pkgKeys[k]] ||
|
|
3388
|
+
packages[fullName] ||
|
|
3389
|
+
snapshots[fullName];
|
|
3390
|
+
if (!packageNode) {
|
|
3391
|
+
continue;
|
|
3392
|
+
}
|
|
3393
|
+
const resolution =
|
|
3394
|
+
packages[pkgKeys[k]]?.resolution ||
|
|
3395
|
+
snapshots[pkgKeys[k]]?.resolution ||
|
|
3396
|
+
packages[fullName]?.resolution ||
|
|
3397
|
+
snapshots[fullName]?.resolution;
|
|
3398
|
+
const integrity = resolution?.integrity;
|
|
3399
|
+
const tarball = resolution?.tarball;
|
|
3400
|
+
const cpu =
|
|
3401
|
+
packages[pkgKeys[k]]?.cpu ||
|
|
3402
|
+
snapshots[pkgKeys[k]]?.cpu ||
|
|
3403
|
+
packages[fullName]?.cpu ||
|
|
3404
|
+
snapshots[fullName]?.cpu;
|
|
3405
|
+
const os =
|
|
3406
|
+
packages[pkgKeys[k]]?.os ||
|
|
3407
|
+
snapshots[pkgKeys[k]]?.os ||
|
|
3408
|
+
packages[fullName]?.os ||
|
|
3409
|
+
snapshots[fullName]?.os;
|
|
3410
|
+
const libc =
|
|
3411
|
+
packages[pkgKeys[k]]?.libc ||
|
|
3412
|
+
snapshots[pkgKeys[k]]?.libc ||
|
|
3413
|
+
packages[fullName]?.libc ||
|
|
3414
|
+
snapshots[fullName]?.libc;
|
|
3415
|
+
// In lock file version 9, dependencies is under snapshots
|
|
3416
|
+
const deps =
|
|
3417
|
+
packages[pkgKeys[k]]?.dependencies ||
|
|
3418
|
+
snapshots[pkgKeys[k]]?.dependencies ||
|
|
3419
|
+
packages[fullName]?.dependencies ||
|
|
3420
|
+
snapshots[fullName]?.dependencies ||
|
|
3421
|
+
{};
|
|
3422
|
+
const optionalDeps =
|
|
3423
|
+
packages[pkgKeys[k]]?.optionalDependencies ||
|
|
3424
|
+
snapshots[pkgKeys[k]]?.optionalDependencies ||
|
|
3425
|
+
packages[fullName]?.optionalDependencies ||
|
|
3426
|
+
snapshots[fullName]?.optionalDependencies ||
|
|
3427
|
+
{};
|
|
3428
|
+
const _peerDeps =
|
|
3429
|
+
packages[pkgKeys[k]]?.peerDependencies ||
|
|
3430
|
+
snapshots[pkgKeys[k]]?.peerDependencies ||
|
|
3431
|
+
packages[fullName]?.peerDependencies ||
|
|
3432
|
+
snapshots[fullName]?.peerDependencies ||
|
|
3433
|
+
{};
|
|
3434
|
+
// Track the explicit optional dependencies of this package
|
|
3435
|
+
for (const opkgName of Object.keys(optionalDeps)) {
|
|
3436
|
+
let vers = optionalDeps[opkgName];
|
|
3437
|
+
if (vers?.includes("(")) {
|
|
3438
|
+
vers = vers.split("(")[0];
|
|
3439
|
+
}
|
|
3440
|
+
let opurlString;
|
|
3441
|
+
if (vers.includes("@")) {
|
|
3442
|
+
opurlString = Purl.parse(
|
|
3443
|
+
`pkg:npm/${vers.replace(/^@/g, "%40")}`,
|
|
3444
|
+
).toString();
|
|
3445
|
+
} else {
|
|
3446
|
+
opurlString = npmPurl(opkgName, vers);
|
|
3447
|
+
}
|
|
3448
|
+
const obomRef = decodeURIComponent(opurlString);
|
|
3449
|
+
if (possibleOptionalDeps[obomRef] === undefined) {
|
|
3450
|
+
possibleOptionalDeps[obomRef] = true;
|
|
3451
|
+
}
|
|
3452
|
+
if (possibleAliases[opkgName]) {
|
|
3453
|
+
possibleAliasesRefs[obomRef] = opkgName;
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
let scope =
|
|
3457
|
+
packageNode.dev === true || packageNode.optional === true
|
|
3458
|
+
? "optional"
|
|
3459
|
+
: undefined;
|
|
3460
|
+
// In v9, a package can be declared optional in more places :(
|
|
3461
|
+
if (
|
|
3462
|
+
lockfileVersion >= 9 &&
|
|
3463
|
+
(packages[pkgKeys[k]]?.optional === true ||
|
|
3464
|
+
snapshots[pkgKeys[k]]?.optional === true ||
|
|
3465
|
+
packages[fullName]?.optional === true ||
|
|
3466
|
+
snapshots[fullName]?.optional === true)
|
|
3467
|
+
) {
|
|
3468
|
+
scope = "optional";
|
|
3469
|
+
}
|
|
3470
|
+
if (parts?.length) {
|
|
3471
|
+
let name = "";
|
|
3472
|
+
let version = "";
|
|
3473
|
+
let group = "";
|
|
3474
|
+
let srcUrl;
|
|
3475
|
+
const hasBin = packageNode?.hasBin;
|
|
3476
|
+
const deprecatedMessage = packageNode?.deprecated;
|
|
3477
|
+
const parsedGitKey =
|
|
3478
|
+
parsePnpmGitLockKey(fullName) || parsePnpmGitLockKey(pkgKeys[k]);
|
|
3479
|
+
if (parsedGitKey) {
|
|
3480
|
+
group = parsedGitKey.group;
|
|
3481
|
+
name = parsedGitKey.name;
|
|
3482
|
+
version =
|
|
3483
|
+
gitPkgRefs[parsedGitKey.fullName]?.version ||
|
|
3484
|
+
gitPkgRefs[parsedGitKey.packageName]?.version ||
|
|
3485
|
+
packageNode.version ||
|
|
3486
|
+
resolution?.commit ||
|
|
3487
|
+
"";
|
|
3488
|
+
const repo =
|
|
3489
|
+
resolution?.repo || gitPkgRefs[parsedGitKey.fullName]?.repo || "";
|
|
3490
|
+
const commit =
|
|
3491
|
+
resolution?.commit ||
|
|
3492
|
+
gitPkgRefs[parsedGitKey.fullName]?.commit ||
|
|
3493
|
+
"";
|
|
3494
|
+
if (repo && commit) {
|
|
3495
|
+
srcUrl = `${repo}#${commit}`;
|
|
3496
|
+
} else {
|
|
3497
|
+
srcUrl =
|
|
3498
|
+
gitPkgRefs[parsedGitKey.fullName]?.vcsUrl || parsedGitKey.gitSpec;
|
|
3499
|
+
}
|
|
3500
|
+
} else if (lockfileVersion >= 9 && fullName.includes("@")) {
|
|
3501
|
+
// ci-info@https://codeload.github.com/watson/ci-info/tar.gz/f43f6a1cefff47fb361c88cf4b943fdbcaafe540
|
|
3502
|
+
const possibleHttpParts = fullName.split("@");
|
|
3503
|
+
if (
|
|
3504
|
+
possibleHttpParts[possibleHttpParts.length - 1].startsWith("http")
|
|
3505
|
+
) {
|
|
3506
|
+
srcUrl = possibleHttpParts[possibleHttpParts.length - 1];
|
|
3507
|
+
name = fullName.replace(`@${srcUrl}`, "");
|
|
3508
|
+
version = srcUrl.split("/").pop();
|
|
3509
|
+
} else if (
|
|
3510
|
+
possibleHttpParts[possibleHttpParts.length - 1].startsWith("file")
|
|
3511
|
+
) {
|
|
3512
|
+
srcUrl = possibleHttpParts[possibleHttpParts.length - 1];
|
|
3513
|
+
name = fullName.replace(`@${srcUrl}`, "");
|
|
3514
|
+
version = "";
|
|
3515
|
+
} else {
|
|
3516
|
+
group = parts.length > 1 ? parts[0] : "";
|
|
3517
|
+
const tmpA = parts[parts.length - 1].split("@");
|
|
3518
|
+
if (tmpA.length > 1) {
|
|
3519
|
+
name = tmpA[0];
|
|
3520
|
+
version = tmpA[1];
|
|
3521
|
+
}
|
|
3522
|
+
if (version?.includes("(")) {
|
|
3523
|
+
version = version.split("(")[0];
|
|
3524
|
+
}
|
|
3525
|
+
}
|
|
3526
|
+
} else if (
|
|
3527
|
+
lockfileVersion >= 6 &&
|
|
3528
|
+
lockfileVersion < 9 &&
|
|
3529
|
+
fullName.includes("@")
|
|
3530
|
+
) {
|
|
3531
|
+
const tmpA = parts[parts.length - 1].split("@");
|
|
3532
|
+
group = parts[0];
|
|
3533
|
+
if (parts.length === 2 && tmpA.length > 1) {
|
|
3534
|
+
name = tmpA[0];
|
|
3535
|
+
version = tmpA[1];
|
|
3536
|
+
} else {
|
|
3537
|
+
console.log("Missed", parts, fullName);
|
|
3538
|
+
}
|
|
3539
|
+
} else {
|
|
3540
|
+
if (parts.length === 2) {
|
|
3541
|
+
name = parts[0];
|
|
3542
|
+
version = parts[1];
|
|
3543
|
+
} else if (parts.length === 3) {
|
|
3544
|
+
group = parts[0];
|
|
3545
|
+
name = parts[1];
|
|
3546
|
+
version = parts[2];
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
// Let's have some warnings till we fully support pnpm 8
|
|
3550
|
+
if (!name) {
|
|
3551
|
+
if (gitPkgRefs[fullName]) {
|
|
3552
|
+
name = gitPkgRefs[fullName].name;
|
|
3553
|
+
group = gitPkgRefs[fullName].group;
|
|
3554
|
+
version = gitPkgRefs[fullName].version;
|
|
3555
|
+
} else if (parts?.length >= 3 && parts[0] === githubServerHost) {
|
|
3556
|
+
version = parts[parts.length - 1];
|
|
3557
|
+
name = parts[parts.length - 2];
|
|
3558
|
+
group = parts.length === 4 ? `@${parts[parts.length - 3]}` : "";
|
|
3559
|
+
gitPkgRefs[fullName] = {
|
|
3560
|
+
group,
|
|
3561
|
+
name,
|
|
3562
|
+
version,
|
|
3563
|
+
purl: build({
|
|
3564
|
+
type: "npm",
|
|
3565
|
+
namespace: group || null,
|
|
3566
|
+
name: name,
|
|
3567
|
+
version: version || null,
|
|
3568
|
+
}),
|
|
3569
|
+
};
|
|
3570
|
+
} else {
|
|
3571
|
+
console.warn(
|
|
3572
|
+
`Unable to extract name and version for string ${pkgKeys[k]}`,
|
|
3573
|
+
parts,
|
|
3574
|
+
fullName,
|
|
3575
|
+
);
|
|
3576
|
+
continue;
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
if (name.indexOf("file:") !== 0) {
|
|
3580
|
+
// The version segment of a lockfile 5 key carries the peer-dependency
|
|
3581
|
+
// resolution, eg /@wdio/utils/7.26.0_typescript@6.0.2. Normalising here,
|
|
3582
|
+
// before the purl exists, keeps `version`, `purl` and `bom-ref` in
|
|
3583
|
+
// agreement and keeps the bom-ref equal to the purl, which trimComponents
|
|
3584
|
+
// and dedupeBom both rely on.
|
|
3585
|
+
version = stripPnpmPeerSuffix(version);
|
|
3586
|
+
const gitEntry =
|
|
3587
|
+
gitPkgRefs[parsedGitKey?.fullName] ||
|
|
3588
|
+
gitPkgRefs[parsedGitKey?.packageName];
|
|
3589
|
+
const purlQualifiers = srcUrl
|
|
3590
|
+
? buildNpmGitPurlQualifiers(srcUrl, group, npmrcConfig)
|
|
3591
|
+
: null;
|
|
3592
|
+
const purlString = build({
|
|
3593
|
+
type: "npm",
|
|
3594
|
+
namespace: group || null,
|
|
3595
|
+
name: name,
|
|
3596
|
+
version: version || null,
|
|
3597
|
+
qualifiers: purlQualifiers || null,
|
|
3598
|
+
});
|
|
3599
|
+
const bomRef = decodeURIComponent(purlString);
|
|
3600
|
+
if (
|
|
3601
|
+
packageNode.dev === true &&
|
|
3602
|
+
possibleDevelopmentDeps[bomRef] === undefined
|
|
3603
|
+
) {
|
|
3604
|
+
possibleDevelopmentDeps[bomRef] = true;
|
|
3605
|
+
}
|
|
3606
|
+
const isBaseOptional = possibleOptionalDeps[bomRef];
|
|
3607
|
+
// optionalDependencies are tracked separately because they may still
|
|
3608
|
+
// be runtime-relevant and should keep the CycloneDX optional scope.
|
|
3609
|
+
// packageNode.dev captures explicit dev-only packages from the lock
|
|
3610
|
+
// entry, while possibleDevelopmentDeps lets that marking propagate to
|
|
3611
|
+
// transitive dependencies discovered through the dependency graph.
|
|
3612
|
+
const isBaseDevelopment =
|
|
3613
|
+
packageNode.dev === true ||
|
|
3614
|
+
possibleDevelopmentDeps[bomRef] === true;
|
|
3615
|
+
const deplist = [];
|
|
3616
|
+
for (let dpkgName of Object.keys(deps)) {
|
|
3617
|
+
let vers = deps[dpkgName];
|
|
3618
|
+
if (vers?.includes("(")) {
|
|
3619
|
+
vers = vers.split("(")[0];
|
|
3620
|
+
}
|
|
3621
|
+
// A value beginning with a "/" is never a version - it is an alias
|
|
3622
|
+
// pointing at another lock entry, which pnpm writes in its package-key
|
|
3623
|
+
// shape. The dual-published *-cjs shims are the common source of these,
|
|
3624
|
+
// and from lockfile v6 they lose the leading slash, so those are trusted
|
|
3625
|
+
// as aliases on the strength of the name alone.
|
|
3626
|
+
// Eg: '@wdio/utils-cjs': /@wdio/utils/7.26.0_typescript@6.0.2
|
|
3627
|
+
// string-width-cjs: string-width@4.2.3
|
|
3628
|
+
const aliasRef =
|
|
3629
|
+
vers?.startsWith("/") || dpkgName.endsWith("-cjs")
|
|
3630
|
+
? parsePnpmAliasRef(vers)
|
|
3631
|
+
: undefined;
|
|
3632
|
+
if (aliasRef) {
|
|
3633
|
+
dpkgName = aliasRef.name;
|
|
3634
|
+
vers = aliasRef.version;
|
|
3635
|
+
}
|
|
3636
|
+
// A dependency value carries the same peer-resolution suffix as the lock
|
|
3637
|
+
// key it points at, eg jest-cli: 25.5.1_jest-resolve@25.5.1. Stripping it
|
|
3638
|
+
// here is what keeps this ref equal to that package's bom-ref.
|
|
3639
|
+
vers = stripPnpmPeerSuffix(vers);
|
|
3640
|
+
if (vers.includes("file:") || vers.includes("link:")) {
|
|
3641
|
+
vers = await getVersionNumPnpm(vers.split("@").pop());
|
|
3642
|
+
}
|
|
3643
|
+
// With overrides version could be like this: @nolyfill/side-channel@1.0.29
|
|
3644
|
+
// An alias has already been split into its name and version, and its
|
|
3645
|
+
// version legitimately contains an @ when it carries a peer suffix, so
|
|
3646
|
+
// it must not be re-split here.
|
|
3647
|
+
if (!aliasRef && vers.includes("@")) {
|
|
3648
|
+
const overrideVersion = vers.split("@").pop();
|
|
3649
|
+
dpkgName = vers
|
|
3650
|
+
.replace(`@${overrideVersion}`, "")
|
|
3651
|
+
.replace(/^\//, "");
|
|
3652
|
+
vers = overrideVersion;
|
|
3653
|
+
}
|
|
3654
|
+
const dpurlString = npmPurl(dpkgName, vers);
|
|
3655
|
+
const dbomRef = decodeURIComponent(dpurlString);
|
|
3656
|
+
deplist.push(dbomRef);
|
|
3657
|
+
// If the base package is optional, make the dependencies optional too
|
|
3658
|
+
// We need to repeat the optional detection down the line to find these new packages
|
|
3659
|
+
if (isBaseOptional && possibleOptionalDeps[dbomRef] === undefined) {
|
|
3660
|
+
possibleOptionalDeps[dbomRef] = true;
|
|
3661
|
+
scope = "optional";
|
|
3662
|
+
_markTreeOptional(
|
|
3663
|
+
dbomRef,
|
|
3664
|
+
dependenciesMap,
|
|
3665
|
+
possibleOptionalDeps,
|
|
3666
|
+
{},
|
|
3667
|
+
);
|
|
3668
|
+
}
|
|
3669
|
+
if (
|
|
3670
|
+
isBaseDevelopment &&
|
|
3671
|
+
possibleDevelopmentDeps[dbomRef] === undefined
|
|
3672
|
+
) {
|
|
3673
|
+
possibleDevelopmentDeps[dbomRef] = true;
|
|
3674
|
+
_markTreeDevelopment(
|
|
3675
|
+
dbomRef,
|
|
3676
|
+
dependenciesMap,
|
|
3677
|
+
possibleDevelopmentDeps,
|
|
3678
|
+
{},
|
|
3679
|
+
);
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
if (!dependenciesMap[bomRef]) {
|
|
3683
|
+
dependenciesMap[bomRef] = [];
|
|
3684
|
+
}
|
|
3685
|
+
|
|
3686
|
+
dependenciesMap[bomRef] = dependenciesMap[bomRef].concat(deplist);
|
|
3687
|
+
const properties = [
|
|
3688
|
+
{
|
|
3689
|
+
name: "internal:SrcFile",
|
|
3690
|
+
value: pnpmLockSrcFile,
|
|
3691
|
+
},
|
|
3692
|
+
];
|
|
3693
|
+
if (hasBin) {
|
|
3694
|
+
properties.push({
|
|
3695
|
+
name: "cdx:npm:has_binary",
|
|
3696
|
+
value: "true",
|
|
3697
|
+
});
|
|
3698
|
+
}
|
|
3699
|
+
if (deprecatedMessage) {
|
|
3700
|
+
properties.push({
|
|
3701
|
+
name: "cdx:npm:deprecation_notice",
|
|
3702
|
+
value: deprecatedMessage,
|
|
3703
|
+
});
|
|
3704
|
+
}
|
|
3705
|
+
const binary_metadata = { os, cpu, libc };
|
|
3706
|
+
Object.entries(binary_metadata).forEach(([key, value]) => {
|
|
3707
|
+
if (!value) return;
|
|
3708
|
+
properties.push({
|
|
3709
|
+
name: `cdx:npm:${key}`,
|
|
3710
|
+
value: Array.isArray(value) ? value.join(", ") : value,
|
|
3711
|
+
});
|
|
3712
|
+
});
|
|
3713
|
+
if (srcFilesMap[decodeURIComponent(purlString)]) {
|
|
3714
|
+
for (const sf of srcFilesMap[decodeURIComponent(purlString)]) {
|
|
3715
|
+
properties.push({
|
|
3716
|
+
name: "cdx:npm:package_json",
|
|
3717
|
+
value: sf,
|
|
3718
|
+
});
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
const purlNoVersion = build({
|
|
3722
|
+
type: "npm",
|
|
3723
|
+
namespace: group || null,
|
|
3724
|
+
name: name,
|
|
3725
|
+
});
|
|
3726
|
+
let packageType = "library";
|
|
3727
|
+
const theBomRef = decodeURIComponent(purlString);
|
|
3728
|
+
if (
|
|
3729
|
+
workspacePackageNames[decodeURIComponent(purlNoVersion)] ||
|
|
3730
|
+
workspacePackageNames[theBomRef]
|
|
3731
|
+
) {
|
|
3732
|
+
properties.push({
|
|
3733
|
+
name: "internal:is_workspace",
|
|
3734
|
+
value: "true",
|
|
3735
|
+
});
|
|
3736
|
+
packageType = "application";
|
|
3737
|
+
const wsSrcFile =
|
|
3738
|
+
workspaceSrcFiles[decodeURIComponent(purlNoVersion)] ||
|
|
3739
|
+
workspaceSrcFiles[theBomRef];
|
|
3740
|
+
if (wsSrcFile) {
|
|
3741
|
+
properties.push({
|
|
3742
|
+
name: "internal:virtual_path",
|
|
3743
|
+
value: relative(dirname(pnpmLock), dirname(wsSrcFile)),
|
|
3744
|
+
});
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
// Capture all the workspaces that directly depend on this package and their source file
|
|
3748
|
+
for (const wref of Array.from(
|
|
3749
|
+
depsWorkspaceRefs[purlNoVersion] ||
|
|
3750
|
+
depsWorkspaceRefs[purlString] ||
|
|
3751
|
+
[],
|
|
3752
|
+
)) {
|
|
3753
|
+
// This cycle shouldn't happen, but we can't be sure
|
|
3754
|
+
if (wref === purlString) {
|
|
3755
|
+
continue;
|
|
3756
|
+
}
|
|
3757
|
+
properties.push({
|
|
3758
|
+
name: "internal:workspaceRef",
|
|
3759
|
+
value: wref,
|
|
3760
|
+
});
|
|
3761
|
+
if (workspaceSrcFiles[wref]) {
|
|
3762
|
+
properties.push({
|
|
3763
|
+
name: "internal:workspaceSrcFile",
|
|
3764
|
+
value: workspaceSrcFiles[wref],
|
|
3765
|
+
});
|
|
3766
|
+
}
|
|
3767
|
+
// Add workspaceRef to the dependent components as well
|
|
3768
|
+
for (const dref of dependenciesMap[theBomRef]) {
|
|
3769
|
+
if (!depsWorkspaceRefs[dref]) {
|
|
3770
|
+
depsWorkspaceRefs[dref] = [];
|
|
3771
|
+
}
|
|
3772
|
+
if (!depsWorkspaceRefs[dref].includes(wref)) {
|
|
3773
|
+
depsWorkspaceRefs[dref].push(wref);
|
|
3774
|
+
}
|
|
3775
|
+
if (dependenciesMap[dref]) {
|
|
3776
|
+
for (const l2ref of dependenciesMap[dref]) {
|
|
3777
|
+
if (!depsWorkspaceRefs[l2ref]) {
|
|
3778
|
+
depsWorkspaceRefs[l2ref] = [];
|
|
3779
|
+
}
|
|
3780
|
+
if (!depsWorkspaceRefs[l2ref].includes(wref)) {
|
|
3781
|
+
depsWorkspaceRefs[l2ref].push(wref);
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
3787
|
+
const thePkg = {
|
|
3788
|
+
group: group,
|
|
3789
|
+
name: name,
|
|
3790
|
+
version: version,
|
|
3791
|
+
purl: purlString,
|
|
3792
|
+
"bom-ref": theBomRef,
|
|
3793
|
+
type: packageType,
|
|
3794
|
+
scope,
|
|
3795
|
+
_integrity: integrity,
|
|
3796
|
+
properties,
|
|
3797
|
+
evidence: {
|
|
3798
|
+
identity: {
|
|
3799
|
+
field: "purl",
|
|
3800
|
+
confidence: 1,
|
|
3801
|
+
methods: [
|
|
3802
|
+
{
|
|
3803
|
+
technique: "manifest-analysis",
|
|
3804
|
+
confidence: 1,
|
|
3805
|
+
value: pnpmLockSrcFile,
|
|
3806
|
+
},
|
|
3807
|
+
],
|
|
3808
|
+
},
|
|
3809
|
+
},
|
|
3810
|
+
};
|
|
3811
|
+
if (tarball) {
|
|
3812
|
+
thePkg.externalReferences = [
|
|
3813
|
+
{
|
|
3814
|
+
type: "distribution",
|
|
3815
|
+
url: tarball,
|
|
3816
|
+
},
|
|
3817
|
+
];
|
|
3818
|
+
} else if (gitEntry?.externalReferences?.length) {
|
|
3819
|
+
thePkg.externalReferences = gitEntry.externalReferences;
|
|
3820
|
+
}
|
|
3821
|
+
if (possibleOptionalDeps[thePkg["bom-ref"]]) {
|
|
3822
|
+
setNpmOptionalProperty(thePkg);
|
|
3823
|
+
}
|
|
3824
|
+
// Don't add internal workspace packages to the components list
|
|
3825
|
+
if (thePkg.type !== "application") {
|
|
3826
|
+
if (pkgRefMap[theBomRef]) {
|
|
3827
|
+
continue;
|
|
3828
|
+
}
|
|
3829
|
+
pkgList.push(thePkg);
|
|
3830
|
+
}
|
|
3831
|
+
pkgRefMap[thePkg["bom-ref"]] = thePkg;
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
}
|
|
3836
|
+
// We need to repeat optional packages detection
|
|
3837
|
+
if (Object.keys(possibleOptionalDeps).length) {
|
|
3838
|
+
for (const apkg of pkgList) {
|
|
3839
|
+
if (!apkg.scope) {
|
|
3840
|
+
if (possibleOptionalDeps[apkg["bom-ref"]]) {
|
|
3841
|
+
apkg.scope = "optional";
|
|
3842
|
+
_markTreeOptional(
|
|
3843
|
+
apkg["bom-ref"],
|
|
3844
|
+
dependenciesMap,
|
|
3845
|
+
possibleOptionalDeps,
|
|
3846
|
+
{},
|
|
3847
|
+
);
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
// Repeat development dependency detection after the dependency graph is fully
|
|
3853
|
+
// built, since a single package iteration can encounter a dev-only component
|
|
3854
|
+
// before its own dependency list has been captured in dependenciesMap.
|
|
3855
|
+
for (const dependencyRef of Object.keys(possibleDevelopmentDeps)) {
|
|
3856
|
+
if (possibleDevelopmentDeps[dependencyRef] === true) {
|
|
3857
|
+
_markTreeDevelopment(
|
|
3858
|
+
dependencyRef,
|
|
3859
|
+
dependenciesMap,
|
|
3860
|
+
possibleDevelopmentDeps,
|
|
3861
|
+
{},
|
|
3862
|
+
);
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
|
|
3866
|
+
// Problem: We might have over aggressively marked a package as optional even it is both required and optional
|
|
3867
|
+
// The below loops ensure required packages continue to stay required
|
|
3868
|
+
// See #1184
|
|
3869
|
+
const requiredDependencies = {};
|
|
3870
|
+
const requiredDependencyStack = [];
|
|
3871
|
+
// Initialize the required dependency stack
|
|
3872
|
+
for (const dependency in possibleOptionalDeps) {
|
|
3873
|
+
if (possibleOptionalDeps[dependency] === false) {
|
|
3874
|
+
requiredDependencyStack.push(dependency);
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
|
|
3878
|
+
// Walk the required dependency stack iteratively and mark it as required
|
|
3879
|
+
while (requiredDependencyStack.length > 0) {
|
|
3880
|
+
const requiredDependencyRef = requiredDependencyStack.pop();
|
|
3881
|
+
if (!requiredDependencies[requiredDependencyRef]) {
|
|
3882
|
+
requiredDependencies[requiredDependencyRef] = true;
|
|
3883
|
+
if (dependenciesMap[requiredDependencyRef]) {
|
|
3884
|
+
for (const subDependencyRef of dependenciesMap[requiredDependencyRef]) {
|
|
3885
|
+
requiredDependencyStack.push(subDependencyRef);
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
}
|
|
3890
|
+
// Ensure any required dependency is not scoped optionally
|
|
3891
|
+
for (const apkg of pkgList) {
|
|
3892
|
+
if (requiredDependencies[apkg["bom-ref"]]) {
|
|
3893
|
+
apkg.scope = undefined;
|
|
3894
|
+
}
|
|
3895
|
+
if (
|
|
3896
|
+
!requiredDependencies[apkg["bom-ref"]] &&
|
|
3897
|
+
possibleDevelopmentDeps[apkg["bom-ref"]]
|
|
3898
|
+
) {
|
|
3899
|
+
if (!apkg.scope) {
|
|
3900
|
+
apkg.scope = "optional";
|
|
3901
|
+
}
|
|
3902
|
+
setNpmDevelopmentProperty(apkg);
|
|
3903
|
+
}
|
|
3904
|
+
if (possibleAliasesRefs[apkg["bom-ref"]]) {
|
|
3905
|
+
apkg.properties.push({
|
|
3906
|
+
name: "cdx:pnpm:alias",
|
|
3907
|
+
value: possibleAliasesRefs[apkg["bom-ref"]],
|
|
3908
|
+
});
|
|
3909
|
+
}
|
|
3910
|
+
// There are no workspaces so exit early
|
|
3911
|
+
if (!Object.keys(workspacePackageNames).length) {
|
|
3912
|
+
continue;
|
|
3913
|
+
}
|
|
3914
|
+
const purlNoVersion = decodeURIComponent(
|
|
3915
|
+
build({ type: "npm", namespace: apkg.group || null, name: apkg.name }),
|
|
3916
|
+
);
|
|
3917
|
+
const wsRefs =
|
|
3918
|
+
depsWorkspaceRefs[apkg["bom-ref"]] || depsWorkspaceRefs[purlNoVersion];
|
|
3919
|
+
// There is a workspace reference
|
|
3920
|
+
if (wsRefs?.length) {
|
|
3921
|
+
const wsprops = apkg.properties.filter(
|
|
3922
|
+
(p) => p.name === "internal:workspaceRef",
|
|
3923
|
+
);
|
|
3924
|
+
// workspace properties are already set.
|
|
3925
|
+
if (wsprops.length) {
|
|
3926
|
+
continue;
|
|
3927
|
+
}
|
|
3928
|
+
for (const wref of wsRefs) {
|
|
3929
|
+
// Such a cycle should never happen, but we can't sure
|
|
3930
|
+
if (wref === apkg["bom-ref"]) {
|
|
3931
|
+
continue;
|
|
3932
|
+
}
|
|
3933
|
+
apkg.properties.push({
|
|
3934
|
+
name: "internal:workspaceRef",
|
|
3935
|
+
value: wref,
|
|
3936
|
+
});
|
|
3937
|
+
const purlObj = Purl.parse(apkg.purl);
|
|
3938
|
+
purlObj.version = undefined;
|
|
3939
|
+
const wrefNoVersion = decodeURIComponent(purlObj.toString());
|
|
3940
|
+
const wsrcFile =
|
|
3941
|
+
workspaceSrcFiles[wref] || workspaceSrcFiles[wrefNoVersion];
|
|
3942
|
+
if (wsrcFile) {
|
|
3943
|
+
apkg.properties.push({
|
|
3944
|
+
name: "internal:workspaceSrcFile",
|
|
3945
|
+
value: wsrcFile,
|
|
3946
|
+
});
|
|
3947
|
+
}
|
|
3948
|
+
// Repeat for the children
|
|
3949
|
+
_setTreeWorkspaceRef(
|
|
3950
|
+
dependenciesMap,
|
|
3951
|
+
apkg["bom-ref"],
|
|
3952
|
+
pkgRefMap,
|
|
3953
|
+
wref,
|
|
3954
|
+
wsrcFile,
|
|
3955
|
+
depsWorkspaceRefs,
|
|
3956
|
+
);
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
|
|
3961
|
+
if (Object.keys(dependenciesMap).length) {
|
|
3962
|
+
for (const aref of Object.keys(dependenciesMap)) {
|
|
3963
|
+
dependenciesList.push({
|
|
3964
|
+
ref: aref,
|
|
3965
|
+
dependsOn: [...new Set(dependenciesMap[aref])].sort(),
|
|
3966
|
+
});
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
3969
|
+
|
|
3970
|
+
// Enhance metadata from local node_modules if available
|
|
3971
|
+
if (pkgList?.length) {
|
|
3972
|
+
pkgList = await pnpmMetadata(pkgList, pnpmLock);
|
|
3973
|
+
}
|
|
3974
|
+
|
|
3975
|
+
if (shouldFetchPackageMetadata() && pkgList?.length) {
|
|
3976
|
+
if (DEBUG_MODE) {
|
|
3977
|
+
console.log(
|
|
3978
|
+
`About to fetch npm registry metadata for ${pkgList.length} packages in parsePnpmLock`,
|
|
3979
|
+
);
|
|
3980
|
+
}
|
|
3981
|
+
pkgList = await getNpmMetadata(pkgList);
|
|
3982
|
+
return {
|
|
3983
|
+
pkgList,
|
|
3984
|
+
dependenciesList,
|
|
3985
|
+
parentSubComponents,
|
|
3986
|
+
};
|
|
3987
|
+
}
|
|
3988
|
+
return {
|
|
3989
|
+
pkgList,
|
|
3990
|
+
dependenciesList,
|
|
3991
|
+
parentSubComponents,
|
|
3992
|
+
};
|
|
3993
|
+
}
|
|
3994
|
+
|
|
3995
|
+
/**
|
|
3996
|
+
* Parse bower json file
|
|
3997
|
+
*
|
|
3998
|
+
* @param {string} bowerJsonFile bower.json file
|
|
3999
|
+
*/
|
|
4000
|
+
export async function parseBowerJson(bowerJsonFile) {
|
|
4001
|
+
const pkgList = [];
|
|
4002
|
+
if (safeExistsSync(bowerJsonFile)) {
|
|
4003
|
+
try {
|
|
4004
|
+
const pkgData = JSON.parse(readFileSync(bowerJsonFile, "utf8"));
|
|
4005
|
+
const pkgIdentifier = parsePackageJsonName(pkgData.name);
|
|
4006
|
+
pkgList.push({
|
|
4007
|
+
name: pkgIdentifier.fullName || pkgData.name,
|
|
4008
|
+
group: pkgIdentifier.scope || "",
|
|
4009
|
+
version: pkgData.version || "",
|
|
4010
|
+
description: pkgData.description || "",
|
|
4011
|
+
license: pkgData.license || "",
|
|
4012
|
+
properties: [
|
|
4013
|
+
{
|
|
4014
|
+
name: "internal:SrcFile",
|
|
4015
|
+
value: bowerJsonFile,
|
|
4016
|
+
},
|
|
4017
|
+
],
|
|
4018
|
+
evidence: {
|
|
4019
|
+
identity: {
|
|
4020
|
+
field: "purl",
|
|
4021
|
+
confidence: 1,
|
|
4022
|
+
methods: [
|
|
4023
|
+
{
|
|
4024
|
+
technique: "manifest-analysis",
|
|
4025
|
+
confidence: 1,
|
|
4026
|
+
value: bowerJsonFile,
|
|
4027
|
+
},
|
|
4028
|
+
],
|
|
4029
|
+
},
|
|
4030
|
+
},
|
|
4031
|
+
});
|
|
4032
|
+
} catch (_err) {
|
|
4033
|
+
// continue regardless of error
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
if (shouldFetchPackageMetadata() && pkgList?.length) {
|
|
4037
|
+
if (DEBUG_MODE) {
|
|
4038
|
+
console.log(
|
|
4039
|
+
`About to fetch npm registry metadata for ${pkgList.length} packages in parseBowerJson`,
|
|
4040
|
+
);
|
|
4041
|
+
}
|
|
4042
|
+
return await getNpmMetadata(pkgList);
|
|
4043
|
+
}
|
|
4044
|
+
return pkgList;
|
|
4045
|
+
}
|
|
4046
|
+
|
|
4047
|
+
/**
|
|
4048
|
+
* Parse minified js file
|
|
4049
|
+
*
|
|
4050
|
+
* @param {string} minJsFile min.js file
|
|
4051
|
+
*/
|
|
4052
|
+
export async function parseMinJs(minJsFile) {
|
|
4053
|
+
const pkgList = [];
|
|
4054
|
+
if (safeExistsSync(minJsFile)) {
|
|
4055
|
+
try {
|
|
4056
|
+
const rawData = readFileSync(minJsFile, { encoding: "utf-8" });
|
|
4057
|
+
const tmpA = rawData.split("\n");
|
|
4058
|
+
tmpA.forEach((l) => {
|
|
4059
|
+
if ((l.startsWith("/*!") || l.startsWith(" * ")) && l.length < 500) {
|
|
4060
|
+
let delimiter = " * ";
|
|
4061
|
+
if (!l.includes(delimiter) && l.includes("/*!")) {
|
|
4062
|
+
delimiter = "/*!";
|
|
4063
|
+
}
|
|
4064
|
+
if (!l.includes(delimiter) && l.includes(" - ")) {
|
|
4065
|
+
delimiter = " - ";
|
|
4066
|
+
}
|
|
4067
|
+
const tmpPV = l.split(delimiter);
|
|
4068
|
+
if (!tmpPV || tmpPV.length < 2) {
|
|
4069
|
+
return;
|
|
4070
|
+
}
|
|
4071
|
+
// Eg: jQuery v3.6.0
|
|
4072
|
+
const pkgNameVer = tmpPV[1]
|
|
4073
|
+
.replace("/*!", "")
|
|
4074
|
+
.replace(" * ", "")
|
|
4075
|
+
.trim();
|
|
4076
|
+
const tmpB = pkgNameVer.includes(" - ")
|
|
4077
|
+
? pkgNameVer.split(" - ")
|
|
4078
|
+
: pkgNameVer.split(" ");
|
|
4079
|
+
if (tmpB && tmpB.length > 1) {
|
|
4080
|
+
// Fix #223 - lowercase parsed package name
|
|
4081
|
+
const name = tmpB[0].replace(/ /g, "-").trim().toLowerCase();
|
|
4082
|
+
if (name === "@license" || name === "license") {
|
|
4083
|
+
return;
|
|
4084
|
+
}
|
|
4085
|
+
if (name.startsWith("@") && !name.includes("/")) {
|
|
4086
|
+
return;
|
|
4087
|
+
}
|
|
4088
|
+
if (
|
|
4089
|
+
[
|
|
4090
|
+
"copyright",
|
|
4091
|
+
"author",
|
|
4092
|
+
"licensed",
|
|
4093
|
+
"minified",
|
|
4094
|
+
"vendor",
|
|
4095
|
+
"build",
|
|
4096
|
+
].includes(name.toLowerCase())
|
|
4097
|
+
) {
|
|
4098
|
+
return;
|
|
4099
|
+
}
|
|
4100
|
+
const pkgIdentifier = parsePackageJsonName(name);
|
|
4101
|
+
if (pkgIdentifier.fullName !== "") {
|
|
4102
|
+
pkgList.push({
|
|
4103
|
+
name: pkgIdentifier.fullName,
|
|
4104
|
+
group: pkgIdentifier.scope || "",
|
|
4105
|
+
version: tmpB[1].replace(/^v/, "") || "",
|
|
4106
|
+
properties: [
|
|
4107
|
+
{
|
|
4108
|
+
name: "internal:SrcFile",
|
|
4109
|
+
value: minJsFile,
|
|
4110
|
+
},
|
|
4111
|
+
],
|
|
4112
|
+
evidence: {
|
|
4113
|
+
identity: {
|
|
4114
|
+
field: "purl",
|
|
4115
|
+
confidence: 0.25,
|
|
4116
|
+
methods: [
|
|
4117
|
+
{
|
|
4118
|
+
technique: "filename",
|
|
4119
|
+
confidence: 0.25,
|
|
4120
|
+
value: minJsFile,
|
|
4121
|
+
},
|
|
4122
|
+
],
|
|
4123
|
+
},
|
|
4124
|
+
},
|
|
4125
|
+
});
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
});
|
|
4130
|
+
} catch (_err) {
|
|
4131
|
+
// continue regardless of error
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
if (shouldFetchPackageMetadata() && pkgList?.length) {
|
|
4135
|
+
if (DEBUG_MODE) {
|
|
4136
|
+
console.log(
|
|
4137
|
+
`About to fetch npm registry metadata for ${pkgList.length} packages in parseMinJs`,
|
|
4138
|
+
);
|
|
4139
|
+
}
|
|
4140
|
+
return await getNpmMetadata(pkgList);
|
|
4141
|
+
}
|
|
4142
|
+
return pkgList;
|
|
4143
|
+
}
|
|
4144
|
+
|
|
4145
|
+
// taken from a very old package https://github.com/keithamus/parse-packagejson-name/blob/master/index.js
|
|
4146
|
+
/**
|
|
4147
|
+
* Parse a package.json `name` field (or a plain string) and extract its scope,
|
|
4148
|
+
* full name, project name, and module name components.
|
|
4149
|
+
*
|
|
4150
|
+
* @param {string|Object} name The package name string or an object with a `name` property
|
|
4151
|
+
* @returns {{ scope: string|null, fullName: string, projectName: string|null, moduleName: string|null }}
|
|
4152
|
+
*/
|
|
4153
|
+
export function parsePackageJsonName(name) {
|
|
4154
|
+
const nameRegExp = /^(?:@([^/]+)\/)?(([^.]+)(?:\.(.*))?)$/;
|
|
4155
|
+
const returnObject = {
|
|
4156
|
+
scope: null,
|
|
4157
|
+
fullName: "",
|
|
4158
|
+
projectName: "",
|
|
4159
|
+
moduleName: "",
|
|
4160
|
+
};
|
|
4161
|
+
const safeName = name?.name ?? name ?? "";
|
|
4162
|
+
const match = safeName.match(nameRegExp);
|
|
4163
|
+
if (match) {
|
|
4164
|
+
returnObject.scope =
|
|
4165
|
+
(match[1] && name.includes("@") ? `@${match[1]}` : match[1]) || null;
|
|
4166
|
+
returnObject.fullName = match[2] || match[0];
|
|
4167
|
+
returnObject.projectName = match[3] === match[2] ? null : match[3];
|
|
4168
|
+
returnObject.moduleName = match[4] || match[2] || null;
|
|
4169
|
+
}
|
|
4170
|
+
return returnObject;
|
|
4171
|
+
}
|
|
4172
|
+
|
|
4173
|
+
/**
|
|
4174
|
+
* Helper to split a command line string into an array of arguments,
|
|
4175
|
+
* respecting single and double quotes.
|
|
4176
|
+
*
|
|
4177
|
+
* @param {String} commandString The full command line string
|
|
4178
|
+
* @returns {Array<String>} Array of tokens
|
|
4179
|
+
*/
|
|
4180
|
+
export function splitCommandArgs(commandString) {
|
|
4181
|
+
const args = [];
|
|
4182
|
+
let current = "";
|
|
4183
|
+
let inQuote = false;
|
|
4184
|
+
let quoteChar = "";
|
|
4185
|
+
let escaped = false;
|
|
4186
|
+
|
|
4187
|
+
for (let i = 0; i < commandString.length; i++) {
|
|
4188
|
+
const char = commandString[i];
|
|
4189
|
+
if (escaped) {
|
|
4190
|
+
current += char;
|
|
4191
|
+
escaped = false;
|
|
4192
|
+
continue;
|
|
4193
|
+
}
|
|
4194
|
+
if (char === "\\") {
|
|
4195
|
+
escaped = true;
|
|
4196
|
+
continue;
|
|
4197
|
+
}
|
|
4198
|
+
if (inQuote) {
|
|
4199
|
+
if (char === quoteChar) {
|
|
4200
|
+
inQuote = false;
|
|
4201
|
+
} else {
|
|
4202
|
+
current += char;
|
|
4203
|
+
}
|
|
4204
|
+
} else {
|
|
4205
|
+
if (char === '"' || char === "'") {
|
|
4206
|
+
inQuote = true;
|
|
4207
|
+
quoteChar = char;
|
|
4208
|
+
} else if (char === " " || char === "\t") {
|
|
4209
|
+
if (current.length > 0) {
|
|
4210
|
+
args.push(current);
|
|
4211
|
+
current = "";
|
|
4212
|
+
}
|
|
4213
|
+
} else {
|
|
4214
|
+
current += char;
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
if (current.length > 0) {
|
|
4219
|
+
args.push(current);
|
|
4220
|
+
}
|
|
4221
|
+
return args;
|
|
4222
|
+
}
|