@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,2362 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path, { basename, join, resolve } from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
|
|
5
|
+
import { Purl } from "@cdxgen/cdx-purl";
|
|
6
|
+
|
|
7
|
+
import { DEBUG_MODE, readEnvironmentVariable } from "../core/activity.js";
|
|
8
|
+
import { PROJECT_TYPE_ALIASES } from "../core/env.js";
|
|
9
|
+
import {
|
|
10
|
+
getAllFiles,
|
|
11
|
+
getTimestamp,
|
|
12
|
+
getTmpDir,
|
|
13
|
+
safeExistsSync,
|
|
14
|
+
safeMkdtempSync,
|
|
15
|
+
safeRmSync,
|
|
16
|
+
safeWriteSync,
|
|
17
|
+
} from "../core/fs.js";
|
|
18
|
+
import {
|
|
19
|
+
analyzeGolemProject,
|
|
20
|
+
collectGolemEvidence,
|
|
21
|
+
isGolemGoLanguage,
|
|
22
|
+
} from "../ecosystems/golem.js";
|
|
23
|
+
import {
|
|
24
|
+
collectGradleDependencies,
|
|
25
|
+
getGradleCommand,
|
|
26
|
+
getMavenCommand,
|
|
27
|
+
} from "../ecosystems/gradleutils.js";
|
|
28
|
+
import {
|
|
29
|
+
analyzeRusiProject,
|
|
30
|
+
collectRusiEvidence,
|
|
31
|
+
isRusiRustLanguage,
|
|
32
|
+
} from "../ecosystems/rusi.js";
|
|
33
|
+
import {
|
|
34
|
+
buildAtomCommandEnv,
|
|
35
|
+
executeAtom,
|
|
36
|
+
filterAtomSlicesByExcludePatterns,
|
|
37
|
+
} from "../inventory/atomUtils.js";
|
|
38
|
+
import {
|
|
39
|
+
collectDosaiCryptoComponents,
|
|
40
|
+
findCryptoAlgos,
|
|
41
|
+
} from "../inventory/cbomutils.js";
|
|
42
|
+
import { collectMvnDependencies } from "../inventory/deps.js";
|
|
43
|
+
import { mergeServices } from "../inventory/depsUtils.js";
|
|
44
|
+
import {
|
|
45
|
+
collectDosaiDataFlowFrames,
|
|
46
|
+
collectDosaiPurlEvidence,
|
|
47
|
+
collectDosaiServicesFromMethods,
|
|
48
|
+
createDosaiDataFlowSlice,
|
|
49
|
+
createDosaiMethodsSlice,
|
|
50
|
+
isDosaiDotnetLanguage,
|
|
51
|
+
persistDosaiSemanticsReport,
|
|
52
|
+
} from "../inventory/dosai.js";
|
|
53
|
+
import { parseOccurrenceEvidenceLocation } from "../inventory/evidenceUtils.js";
|
|
54
|
+
import { executeOsQuery } from "../managers/binary.js";
|
|
55
|
+
import { postProcess } from "../stages/postgen/postgen.js";
|
|
56
|
+
import { createOrLoad } from "./db.js";
|
|
57
|
+
import { findPurlLocations } from "./scalasem.js";
|
|
58
|
+
import { createSemanticsSlices } from "./swiftsem.js";
|
|
59
|
+
|
|
60
|
+
const typePurlsCache = {};
|
|
61
|
+
|
|
62
|
+
function appendUniqueProperty(properties, name, value) {
|
|
63
|
+
if (value === undefined || value === null || value === "") {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const propertyValue = String(value);
|
|
67
|
+
if (
|
|
68
|
+
!properties.some(
|
|
69
|
+
(property) => property.name === name && property.value === propertyValue,
|
|
70
|
+
)
|
|
71
|
+
) {
|
|
72
|
+
properties.push({ name, value: propertyValue });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function appendComponentProperties(component, properties = []) {
|
|
77
|
+
if (!component || !properties.length) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
component.properties ??= [];
|
|
81
|
+
for (const property of properties) {
|
|
82
|
+
appendUniqueProperty(component.properties, property.name, property.value);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function filterAtomSliceData(sliceData, options = {}) {
|
|
87
|
+
return filterAtomSlicesByExcludePatterns(sliceData, options.exclude);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Function to create the db for the libraries referred in the sbom.
|
|
92
|
+
*
|
|
93
|
+
* @param {Object} options Command line options
|
|
94
|
+
*/
|
|
95
|
+
export async function prepareDB(options) {
|
|
96
|
+
const dirPath = options._[0] || ".";
|
|
97
|
+
const bomJsonFile = options.input;
|
|
98
|
+
if (!safeExistsSync(bomJsonFile)) {
|
|
99
|
+
console.log(
|
|
100
|
+
"Bom file doesn't exist. Check if cdxgen was invoked with the correct type argument.",
|
|
101
|
+
);
|
|
102
|
+
if (!readEnvironmentVariable("CDXGEN_DEBUG_MODE")) {
|
|
103
|
+
console.log(
|
|
104
|
+
"Set the environment variable CDXGEN_DEBUG_MODE to debug to troubleshoot the issue further.",
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const bomJson = JSON.parse(fs.readFileSync(bomJsonFile, "utf8"));
|
|
110
|
+
if (bomJson.specVersion < 1.5) {
|
|
111
|
+
console.log(
|
|
112
|
+
"Evinse requires the input SBOM in CycloneDX 1.5 format or above. You can generate one by invoking cdxgen without any --spec-version argument.",
|
|
113
|
+
);
|
|
114
|
+
process.exit(0);
|
|
115
|
+
}
|
|
116
|
+
const components = bomJson.components || [];
|
|
117
|
+
const { sequelize, Namespaces, Usages, DataFlows } = await createOrLoad();
|
|
118
|
+
let hasMavenPkgs = false;
|
|
119
|
+
// We need to slice only non-maven packages
|
|
120
|
+
const purlsToSlice = {};
|
|
121
|
+
const purlsJars = {};
|
|
122
|
+
let usagesSlice;
|
|
123
|
+
for (const comp of components) {
|
|
124
|
+
if (!comp.purl) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
usagesSlice = await Usages.findByPk(comp.purl);
|
|
128
|
+
const namespaceSlice = await Namespaces.findByPk(comp.purl);
|
|
129
|
+
if ((!usagesSlice && !namespaceSlice) || options.force) {
|
|
130
|
+
if (comp.purl.startsWith("pkg:maven")) {
|
|
131
|
+
hasMavenPkgs = true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// If there are maven packages we collect and store the namespaces
|
|
136
|
+
if (!options.skipMavenCollector && hasMavenPkgs) {
|
|
137
|
+
const pomXmlFiles = getAllFiles(dirPath, "**/" + "pom.xml");
|
|
138
|
+
const gradleFiles = getAllFiles(dirPath, "**/" + "build.gradle*");
|
|
139
|
+
if (pomXmlFiles?.length) {
|
|
140
|
+
await catalogMavenDeps(dirPath, purlsJars, Namespaces, options);
|
|
141
|
+
}
|
|
142
|
+
if (gradleFiles?.length) {
|
|
143
|
+
await catalogGradleDeps(dirPath, purlsJars, Namespaces);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (const purl of Object.keys(purlsToSlice)) {
|
|
147
|
+
await createAndStoreSlice(purl, purlsJars, Usages, options);
|
|
148
|
+
}
|
|
149
|
+
return { sequelize, Namespaces, Usages, DataFlows };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Collect Maven jar namespace mappings for the purls found in the SBOM.
|
|
154
|
+
*
|
|
155
|
+
* If a previously generated `bom.json.map` is present in `dirPath` it is reused;
|
|
156
|
+
* otherwise the maven command is invoked to collect the jar dependencies. Each
|
|
157
|
+
* resolved purl is recorded in the `purlsJars` map and persisted in the
|
|
158
|
+
* `Namespaces` model.
|
|
159
|
+
*
|
|
160
|
+
* @param {string} dirPath Project directory scanned by evinse
|
|
161
|
+
* @param {Object<string, string>} purlsJars Map populated with purl -> jar file path
|
|
162
|
+
* @param {Object} Namespaces Sequelize-like Namespaces model used to persist pom and namespaces
|
|
163
|
+
* @param {object} options CLI options
|
|
164
|
+
* @returns {Promise<void>}
|
|
165
|
+
*/
|
|
166
|
+
export async function catalogMavenDeps(
|
|
167
|
+
dirPath,
|
|
168
|
+
purlsJars,
|
|
169
|
+
Namespaces,
|
|
170
|
+
options = {},
|
|
171
|
+
) {
|
|
172
|
+
let jarNSMapping;
|
|
173
|
+
if (safeExistsSync(join(dirPath, "bom.json.map"))) {
|
|
174
|
+
try {
|
|
175
|
+
const mapData = JSON.parse(
|
|
176
|
+
fs.readFileSync(join(dirPath, "bom.json.map"), "utf-8"),
|
|
177
|
+
);
|
|
178
|
+
if (mapData && Object.keys(mapData).length) {
|
|
179
|
+
jarNSMapping = mapData;
|
|
180
|
+
}
|
|
181
|
+
} catch (_err) {
|
|
182
|
+
// ignore
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (!jarNSMapping) {
|
|
186
|
+
console.log("About to collect jar dependencies for the path", dirPath);
|
|
187
|
+
const mavenCmd = getMavenCommand(dirPath, dirPath);
|
|
188
|
+
// collect all jars including from the cache if data-flow mode is enabled
|
|
189
|
+
jarNSMapping = await collectMvnDependencies(
|
|
190
|
+
mavenCmd,
|
|
191
|
+
dirPath,
|
|
192
|
+
false,
|
|
193
|
+
options.withDeepJarCollector,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
if (jarNSMapping) {
|
|
197
|
+
for (const purl of Object.keys(jarNSMapping)) {
|
|
198
|
+
purlsJars[purl] = jarNSMapping[purl].jarFile;
|
|
199
|
+
await Namespaces.findOrCreate({
|
|
200
|
+
where: { purl },
|
|
201
|
+
defaults: {
|
|
202
|
+
purl,
|
|
203
|
+
data: JSON.stringify(
|
|
204
|
+
{
|
|
205
|
+
pom: jarNSMapping[purl].pom,
|
|
206
|
+
namespaces: jarNSMapping[purl].namespaces,
|
|
207
|
+
},
|
|
208
|
+
null,
|
|
209
|
+
null,
|
|
210
|
+
),
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Collect Gradle cache jar namespace mappings for the purls found in the SBOM.
|
|
219
|
+
*
|
|
220
|
+
* Invokes the gradle command to collect all jars (including from the cache) and
|
|
221
|
+
* records each resolved purl in the `purlsJars` map and the `Namespaces` model.
|
|
222
|
+
*
|
|
223
|
+
* @param {string} dirPath Project directory scanned by evinse
|
|
224
|
+
* @param {Object<string, string>} purlsJars Map populated with purl -> jar file path
|
|
225
|
+
* @param {Object} Namespaces Sequelize-like Namespaces model used to persist pom and namespaces
|
|
226
|
+
* @returns {Promise<void>}
|
|
227
|
+
*/
|
|
228
|
+
export async function catalogGradleDeps(dirPath, purlsJars, Namespaces) {
|
|
229
|
+
console.log(
|
|
230
|
+
"About to collect jar dependencies from the gradle cache. This would take a while ...",
|
|
231
|
+
);
|
|
232
|
+
const gradleCmd = getGradleCommand(dirPath, dirPath);
|
|
233
|
+
// collect all jars including from the cache if data-flow mode is enabled
|
|
234
|
+
const jarNSMapping = await collectGradleDependencies(
|
|
235
|
+
gradleCmd,
|
|
236
|
+
dirPath,
|
|
237
|
+
false,
|
|
238
|
+
true,
|
|
239
|
+
);
|
|
240
|
+
if (jarNSMapping) {
|
|
241
|
+
for (const purl of Object.keys(jarNSMapping)) {
|
|
242
|
+
purlsJars[purl] = jarNSMapping[purl].jarFile;
|
|
243
|
+
await Namespaces.findOrCreate({
|
|
244
|
+
where: { purl },
|
|
245
|
+
defaults: {
|
|
246
|
+
purl,
|
|
247
|
+
data: JSON.stringify(
|
|
248
|
+
{
|
|
249
|
+
pom: jarNSMapping[purl].pom,
|
|
250
|
+
namespaces: jarNSMapping[purl].namespaces,
|
|
251
|
+
},
|
|
252
|
+
null,
|
|
253
|
+
null,
|
|
254
|
+
),
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
console.log(
|
|
260
|
+
"To speed up successive re-runs, pass the argument --skip-maven-collector to evinse command.",
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Generate a usage slice for the given purl and persist it in the Usages model.
|
|
266
|
+
*
|
|
267
|
+
* Delegates to {@link createSlice} to produce the slice file, then stores the
|
|
268
|
+
* file contents in the `Usages` model keyed by purl. Any temporary directory
|
|
269
|
+
* created under the cdxgen temp root is cleaned up afterwards.
|
|
270
|
+
*
|
|
271
|
+
* @param {string} purl Package URL to slice
|
|
272
|
+
* @param {Object<string, string>} purlsJars Map of purl -> jar/file path
|
|
273
|
+
* @param {Object} Usages Sequelize-like Usages model used to persist slice data
|
|
274
|
+
* @param {object} options CLI options
|
|
275
|
+
* @returns {Promise<Object|undefined>} The created or found Usages record
|
|
276
|
+
*/
|
|
277
|
+
export async function createAndStoreSlice(
|
|
278
|
+
purl,
|
|
279
|
+
purlsJars,
|
|
280
|
+
Usages,
|
|
281
|
+
options = {},
|
|
282
|
+
) {
|
|
283
|
+
const retMap = createSlice(purl, purlsJars[purl], "usages", options);
|
|
284
|
+
let sliceData;
|
|
285
|
+
if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
|
|
286
|
+
sliceData = await Usages.findOrCreate({
|
|
287
|
+
where: { purl },
|
|
288
|
+
defaults: {
|
|
289
|
+
purl,
|
|
290
|
+
data: fs.readFileSync(retMap.slicesFile, "utf-8"),
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
if (retMap?.tempDir?.startsWith(getTmpDir())) {
|
|
295
|
+
safeRmSync(retMap.tempDir, { recursive: true, force: true });
|
|
296
|
+
}
|
|
297
|
+
return sliceData;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Name the option a slice type reads its output path from.
|
|
302
|
+
*
|
|
303
|
+
* The CLI flags are hyphenated (`--data-flow-slices-file`) and yargs hands them
|
|
304
|
+
* over camel-cased, so a slice type containing a hyphen cannot be turned into
|
|
305
|
+
* its option name by concatenation alone.
|
|
306
|
+
*
|
|
307
|
+
* @param {string} sliceType Slice type such as `usages` or `data-flow`
|
|
308
|
+
* @returns {string} Matching option name
|
|
309
|
+
*/
|
|
310
|
+
export function sliceFileOption(sliceType) {
|
|
311
|
+
const camelCased = sliceType.replace(/-([a-z])/g, (_match, letter) =>
|
|
312
|
+
letter.toUpperCase(),
|
|
313
|
+
);
|
|
314
|
+
return `${camelCased}SlicesFile`;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Run atom/sourcekitten/dosai to produce a usage or data-flow slice file for a purl or language.
|
|
319
|
+
*
|
|
320
|
+
* Accepts either a package url (resolved to a language via {@link purlToLanguage})
|
|
321
|
+
* or an explicit language string. The chosen language is normalised to the
|
|
322
|
+
* canonical atom language name and the requested slice type (`usages`,
|
|
323
|
+
* `data-flow`, `reachables`, or `semantics`) is generated into a temporary
|
|
324
|
+
* directory.
|
|
325
|
+
*
|
|
326
|
+
* @param {string|string[]} purlOrLanguages Package URL or language name (or array whose first entry is used)
|
|
327
|
+
* @param {string} filePath Path to the source file, jar, or project directory to slice
|
|
328
|
+
* @param {string} [sliceType="usages"] Slice type: `usages`, `data-flow`, `reachables`, or `semantics`
|
|
329
|
+
* @param {object} [options={}] CLI options controlling slice generation
|
|
330
|
+
* @returns {Promise<Object>} Result map with `slicesFile`, `atomFile`, `openapiSpecFile`, `semanticsSlicesFile`, `tempDir`, and `tempDirOwned`
|
|
331
|
+
*/
|
|
332
|
+
export async function createSlice(
|
|
333
|
+
purlOrLanguages,
|
|
334
|
+
filePath,
|
|
335
|
+
sliceType = "usages",
|
|
336
|
+
options = {},
|
|
337
|
+
) {
|
|
338
|
+
if (!filePath) {
|
|
339
|
+
return {};
|
|
340
|
+
}
|
|
341
|
+
const firstLanguage = Array.isArray(purlOrLanguages)
|
|
342
|
+
? purlOrLanguages[0]
|
|
343
|
+
: purlOrLanguages;
|
|
344
|
+
let language = firstLanguage.startsWith("pkg:")
|
|
345
|
+
? purlToLanguage(firstLanguage, filePath)
|
|
346
|
+
: firstLanguage;
|
|
347
|
+
if (!language) {
|
|
348
|
+
return {};
|
|
349
|
+
}
|
|
350
|
+
// Handle language with version types
|
|
351
|
+
if (language.startsWith("ruby")) {
|
|
352
|
+
language = "ruby";
|
|
353
|
+
} else if (language.startsWith("java") && language !== "javascript") {
|
|
354
|
+
language = "java";
|
|
355
|
+
} else if (language.startsWith("node")) {
|
|
356
|
+
language = "js";
|
|
357
|
+
} else if (language.startsWith("python")) {
|
|
358
|
+
language = "python";
|
|
359
|
+
} else if (PROJECT_TYPE_ALIASES.scala.includes(language)) {
|
|
360
|
+
language = "scala";
|
|
361
|
+
} else if (isDosaiDotnetLanguage(language)) {
|
|
362
|
+
language = "csharp";
|
|
363
|
+
}
|
|
364
|
+
if (
|
|
365
|
+
PROJECT_TYPE_ALIASES.swift.includes(language) &&
|
|
366
|
+
sliceType !== "semantics"
|
|
367
|
+
) {
|
|
368
|
+
return {};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
let sliceOutputDir = safeMkdtempSync(join(getTmpDir(), `atom-${sliceType}-`));
|
|
372
|
+
let tempDirOwned = true;
|
|
373
|
+
if (options?.output) {
|
|
374
|
+
const resolvedOutputPath = resolve(options.output);
|
|
375
|
+
sliceOutputDir =
|
|
376
|
+
safeExistsSync(resolvedOutputPath) &&
|
|
377
|
+
fs.lstatSync(resolvedOutputPath).isDirectory()
|
|
378
|
+
? resolvedOutputPath
|
|
379
|
+
: path.dirname(resolvedOutputPath);
|
|
380
|
+
tempDirOwned = false;
|
|
381
|
+
}
|
|
382
|
+
const slicesFile =
|
|
383
|
+
options[sliceFileOption(sliceType)] ||
|
|
384
|
+
join(sliceOutputDir, `${language}-${sliceType}.slices.json`);
|
|
385
|
+
const openapiSpecFile = basename(
|
|
386
|
+
options.openapiSpecFile ||
|
|
387
|
+
readEnvironmentVariable("ATOM_TOOLS_OPENAPI_FILENAME") ||
|
|
388
|
+
join(sliceOutputDir, `${language}-openapi.json`),
|
|
389
|
+
);
|
|
390
|
+
// For some languages such as scala, semantics slices file would get created during usages slicing.
|
|
391
|
+
let semanticsSlicesFile;
|
|
392
|
+
if (sliceType === "semantics") {
|
|
393
|
+
const slicesData = createSemanticsSlices(resolve(filePath), options);
|
|
394
|
+
// Write the semantics slices data
|
|
395
|
+
if (slicesData) {
|
|
396
|
+
safeWriteSync(
|
|
397
|
+
slicesFile,
|
|
398
|
+
JSON.stringify(slicesData, null, options.jsonPretty ? 2 : null),
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
return { tempDir: sliceOutputDir, tempDirOwned, slicesFile };
|
|
402
|
+
}
|
|
403
|
+
if (isDosaiDotnetLanguage(language)) {
|
|
404
|
+
console.log(
|
|
405
|
+
`Creating ${sliceType} slice for ${resolve(filePath)} using dosai. Please wait ...`,
|
|
406
|
+
);
|
|
407
|
+
const sliceResult =
|
|
408
|
+
sliceType === "data-flow"
|
|
409
|
+
? createDosaiDataFlowSlice(
|
|
410
|
+
resolve(filePath),
|
|
411
|
+
resolve(slicesFile),
|
|
412
|
+
options,
|
|
413
|
+
)
|
|
414
|
+
: createDosaiMethodsSlice(
|
|
415
|
+
resolve(filePath),
|
|
416
|
+
resolve(slicesFile),
|
|
417
|
+
options,
|
|
418
|
+
);
|
|
419
|
+
if (!sliceResult) {
|
|
420
|
+
console.warn(
|
|
421
|
+
`Unable to generate ${sliceType} slice using dosai. Check if this is a supported .NET project.`,
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
tempDir: sliceOutputDir,
|
|
426
|
+
tempDirOwned,
|
|
427
|
+
slicesFile,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
console.log(
|
|
431
|
+
`Creating ${sliceType} slice for ${resolve(filePath)}. Please wait ...`,
|
|
432
|
+
);
|
|
433
|
+
const atomFile = join(sliceOutputDir, `${language}-app.atom`);
|
|
434
|
+
let args = [sliceType];
|
|
435
|
+
// Support for crypto slices aka CBOM
|
|
436
|
+
if (sliceType === "reachables" && options.includeCrypto) {
|
|
437
|
+
args.push("--include-crypto");
|
|
438
|
+
}
|
|
439
|
+
if (sliceType === "usages") {
|
|
440
|
+
// Generate OpenAPI specification for endpoints. Needs atom-tools pypi package to be installed.
|
|
441
|
+
args.push("--extract-endpoints");
|
|
442
|
+
if (
|
|
443
|
+
readEnvironmentVariable("CDXGEN_IN_CONTAINER") !== "true" &&
|
|
444
|
+
!readEnvironmentVariable("DEVENV_NIX") &&
|
|
445
|
+
!readEnvironmentVariable("NIX_STORE")
|
|
446
|
+
) {
|
|
447
|
+
console.log(
|
|
448
|
+
"Use an official cdxgen container image to improve the precision of endpoints detection (for SaaSBOM).",
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
if (["ruby", "scala"].includes(language)) {
|
|
452
|
+
args.push("--remove-atom");
|
|
453
|
+
}
|
|
454
|
+
if (["scala"].includes(language)) {
|
|
455
|
+
semanticsSlicesFile = join(
|
|
456
|
+
sliceOutputDir,
|
|
457
|
+
basename(options.semanticsSlicesFile || "semantics.slices.json"),
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
args = args.concat([
|
|
462
|
+
"-l",
|
|
463
|
+
language,
|
|
464
|
+
"-o",
|
|
465
|
+
resolve(atomFile),
|
|
466
|
+
"--slice-outfile",
|
|
467
|
+
resolve(slicesFile),
|
|
468
|
+
]);
|
|
469
|
+
// For projects with several layers, slice depth needs to be increased from the default 7 to 15 or 20
|
|
470
|
+
// This would increase the time but would yield more deeper paths
|
|
471
|
+
if (
|
|
472
|
+
sliceType === "data-flow" &&
|
|
473
|
+
readEnvironmentVariable("ATOM_SLICE_DEPTH")
|
|
474
|
+
) {
|
|
475
|
+
args.push("--slice-depth");
|
|
476
|
+
args.push(readEnvironmentVariable("ATOM_SLICE_DEPTH"));
|
|
477
|
+
}
|
|
478
|
+
args.push(resolve(filePath));
|
|
479
|
+
const atomExcludeEnv = buildAtomCommandEnv(options, language);
|
|
480
|
+
// Execute atom
|
|
481
|
+
const result = executeAtom(filePath, args, {
|
|
482
|
+
...atomExcludeEnv,
|
|
483
|
+
ATOM_TOOLS_OPENAPI_FILENAME: openapiSpecFile, // The file would get over-written
|
|
484
|
+
ATOM_TOOLS_OPENAPI_FORMAT:
|
|
485
|
+
readEnvironmentVariable("ATOM_TOOLS_OPENAPI_FORMAT") || "openapi3.1.0", // editor.swagger.io doesn't support 3.1.0 yet
|
|
486
|
+
ATOM_TOOLS_WORK_DIR:
|
|
487
|
+
readEnvironmentVariable("ATOM_TOOLS_WORK_DIR") || resolve(filePath), // This must be the directory containing semantics.slices.json
|
|
488
|
+
OPENAPI_SERVER_URL: readEnvironmentVariable("OPENAPI_SERVER_URL"),
|
|
489
|
+
});
|
|
490
|
+
// A non-zero atom exit no longer discards a slices file that was written:
|
|
491
|
+
// atom can fail a late phase (endpoint extraction, cleanup) after the slices
|
|
492
|
+
// are on disk, and throwing that work away would be a regression against
|
|
493
|
+
// atom 2.x, where the exit status was not observable at all.
|
|
494
|
+
if (!safeExistsSync(slicesFile)) {
|
|
495
|
+
if (!result) {
|
|
496
|
+
console.warn(
|
|
497
|
+
`atom reported a failure and produced no ${sliceType} slice for ${language}.`,
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
console.warn(
|
|
501
|
+
`Unable to generate ${sliceType} slice using atom. Check if this is a supported language.`,
|
|
502
|
+
);
|
|
503
|
+
if (!readEnvironmentVariable("CDXGEN_DEBUG_MODE")) {
|
|
504
|
+
console.log(
|
|
505
|
+
"Set the environment variable CDXGEN_DEBUG_MODE=debug to troubleshoot.",
|
|
506
|
+
);
|
|
507
|
+
} else {
|
|
508
|
+
if (readEnvironmentVariable("CDXGEN_IN_CONTAINER") === "true") {
|
|
509
|
+
console.log(
|
|
510
|
+
"TIP: Try creating the slices using the official atom container image `ghcr.io/appthreat/atom:main` directly. Refer to the documentation: https://atom-docs.appthreat.dev/",
|
|
511
|
+
);
|
|
512
|
+
console.log(
|
|
513
|
+
`evinse will automatically reuse any slices matching the name ${slicesFile}`,
|
|
514
|
+
);
|
|
515
|
+
} else {
|
|
516
|
+
console.log(
|
|
517
|
+
`TIP: Try using a cdxgen container image optimized for ${language}. Refer to the documentation or ask cdxgenGPT for the image details.`,
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
} else if (
|
|
522
|
+
DEBUG_MODE &&
|
|
523
|
+
sliceType === "usages" &&
|
|
524
|
+
!safeExistsSync(join(filePath, openapiSpecFile))
|
|
525
|
+
) {
|
|
526
|
+
console.log(
|
|
527
|
+
`openapi spec file "${join(filePath, openapiSpecFile)}" was not generated successfully. Check if atom-tools pypi package is installed and available in PATH.`,
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
if (
|
|
531
|
+
["scala"].includes(language) &&
|
|
532
|
+
sliceType === "usages" &&
|
|
533
|
+
!safeExistsSync(semanticsSlicesFile)
|
|
534
|
+
) {
|
|
535
|
+
console.log(
|
|
536
|
+
`Semantics slices file "${semanticsSlicesFile}" was not generated successfully. Try running atom cli in Java mode.`,
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
return {
|
|
540
|
+
tempDir: sliceOutputDir,
|
|
541
|
+
tempDirOwned,
|
|
542
|
+
slicesFile,
|
|
543
|
+
atomFile,
|
|
544
|
+
openapiSpecFile: resolve(join(filePath, openapiSpecFile)),
|
|
545
|
+
semanticsSlicesFile,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Map a package URL type to an analysis language (java, python, js, …).
|
|
551
|
+
*
|
|
552
|
+
* @param {string} purl Package URL to inspect
|
|
553
|
+
* @param {string} [filePath] Optional file path used to distinguish jar vs java for maven purls
|
|
554
|
+
* @returns {string|undefined} Resolved language name or `undefined` when the purl type is unsupported
|
|
555
|
+
*/
|
|
556
|
+
export function purlToLanguage(purl, filePath) {
|
|
557
|
+
let language;
|
|
558
|
+
const purlObj = Purl.parse(purl);
|
|
559
|
+
switch (purlObj.type) {
|
|
560
|
+
case "maven":
|
|
561
|
+
language = filePath?.endsWith(".jar") ? "jar" : "java";
|
|
562
|
+
break;
|
|
563
|
+
case "npm":
|
|
564
|
+
language = "javascript";
|
|
565
|
+
break;
|
|
566
|
+
case "pypi":
|
|
567
|
+
language = "python";
|
|
568
|
+
break;
|
|
569
|
+
case "composer":
|
|
570
|
+
language = "php";
|
|
571
|
+
break;
|
|
572
|
+
case "gem":
|
|
573
|
+
language = "ruby";
|
|
574
|
+
break;
|
|
575
|
+
case "nuget":
|
|
576
|
+
language = "csharp";
|
|
577
|
+
break;
|
|
578
|
+
case "generic":
|
|
579
|
+
language = "c";
|
|
580
|
+
}
|
|
581
|
+
return language;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Seed purl-location and import maps from SBOM component evidence.
|
|
586
|
+
*
|
|
587
|
+
* Walks the supplied components reading `internal:ImportedModules` (or
|
|
588
|
+
* `internal:Namespaces` for php/ruby) and `evidence.occurrences` properties
|
|
589
|
+
* to construct lookup maps used during slice analysis.
|
|
590
|
+
*
|
|
591
|
+
* @param {Object[]} components CycloneDX components from the input SBOM
|
|
592
|
+
* @param {string} language Application language used to select the import property name
|
|
593
|
+
* @returns {{ purlLocationMap: Object<string, Set>, purlImportsMap: Object<string, string[]> }} Seeded purl location and import maps
|
|
594
|
+
*/
|
|
595
|
+
export function initFromSbom(components, language) {
|
|
596
|
+
const purlLocationMap = {};
|
|
597
|
+
const purlImportsMap = {};
|
|
598
|
+
for (const comp of components) {
|
|
599
|
+
if (!comp?.evidence) {
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
if (["php", "ruby"].includes(language)) {
|
|
603
|
+
(comp.properties || [])
|
|
604
|
+
.filter((v) => v.name === "internal:Namespaces")
|
|
605
|
+
.forEach((v) => {
|
|
606
|
+
purlImportsMap[comp.purl] = (v.value || "").split(", ");
|
|
607
|
+
});
|
|
608
|
+
} else {
|
|
609
|
+
(comp.properties || [])
|
|
610
|
+
.filter((v) => v.name === "internal:ImportedModules")
|
|
611
|
+
.forEach((v) => {
|
|
612
|
+
purlImportsMap[comp.purl] = (v.value || "").split(",");
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
if (comp.evidence.occurrences) {
|
|
616
|
+
purlLocationMap[comp.purl] = new Set(
|
|
617
|
+
comp.evidence.occurrences.map((v) => v.location),
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return {
|
|
622
|
+
purlLocationMap,
|
|
623
|
+
purlImportsMap,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function usableSlicesFile(slicesFile) {
|
|
628
|
+
if (!slicesFile || !safeExistsSync(slicesFile)) {
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
const stats = fs.statSync(slicesFile);
|
|
632
|
+
if (!stats.isFile()) {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
const fileSizeInBytes = stats.size;
|
|
636
|
+
return fileSizeInBytes > 1024;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Function to analyze the project
|
|
641
|
+
*
|
|
642
|
+
* @param {Object} dbObjMap DB and model instances
|
|
643
|
+
* @param {Object} options Command line options
|
|
644
|
+
*/
|
|
645
|
+
export async function analyzeProject(dbObjMap, options) {
|
|
646
|
+
const dirPath = options._[0] || ".";
|
|
647
|
+
const languages = options.language;
|
|
648
|
+
const language = Array.isArray(languages) ? languages[0] : languages;
|
|
649
|
+
let usageSlice;
|
|
650
|
+
let dataFlowSlice;
|
|
651
|
+
let reachablesSlice;
|
|
652
|
+
let semanticsSlice;
|
|
653
|
+
let usagesSlicesFile;
|
|
654
|
+
let dataFlowSlicesFile;
|
|
655
|
+
let reachablesSlicesFile;
|
|
656
|
+
let semanticsSlicesFile;
|
|
657
|
+
let dataFlowFrames = {};
|
|
658
|
+
let servicesMap = {};
|
|
659
|
+
let retMap = {};
|
|
660
|
+
let userDefinedTypesMap = {};
|
|
661
|
+
const bomFile = options.input;
|
|
662
|
+
const bomJson = JSON.parse(fs.readFileSync(bomFile, "utf8"));
|
|
663
|
+
const components = bomJson.components || [];
|
|
664
|
+
let cryptoComponents = [];
|
|
665
|
+
let cryptoGeneratePurls = {};
|
|
666
|
+
let openapiSpecFile;
|
|
667
|
+
// Load any existing purl-location information from the sbom.
|
|
668
|
+
// For eg: cdxgen populates this information for javascript projects
|
|
669
|
+
let { purlLocationMap, purlImportsMap } = initFromSbom(components, language);
|
|
670
|
+
|
|
671
|
+
if (isGolemGoLanguage(language)) {
|
|
672
|
+
const golemReport = analyzeGolemProject(dirPath, options);
|
|
673
|
+
if (golemReport && Object.keys(golemReport).length) {
|
|
674
|
+
const golemEvidence = collectGolemEvidence(golemReport, [
|
|
675
|
+
bomJson.metadata?.component,
|
|
676
|
+
...components,
|
|
677
|
+
]);
|
|
678
|
+
return {
|
|
679
|
+
purlLocationMap: golemEvidence.purlLocationMap,
|
|
680
|
+
dataFlowFrames: golemEvidence.dataFlowFrames,
|
|
681
|
+
componentPropertiesMap: golemEvidence.componentPropertiesMap,
|
|
682
|
+
metadataProperties: golemEvidence.metadataProperties,
|
|
683
|
+
cryptoComponents: golemEvidence.cryptoComponents,
|
|
684
|
+
cryptoGeneratePurls: golemEvidence.cryptoGeneratePurls,
|
|
685
|
+
servicesMap,
|
|
686
|
+
userDefinedTypesMap,
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
return {
|
|
690
|
+
purlLocationMap,
|
|
691
|
+
servicesMap,
|
|
692
|
+
dataFlowFrames,
|
|
693
|
+
userDefinedTypesMap,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
if (isRusiRustLanguage(language)) {
|
|
698
|
+
const rusiReport = analyzeRusiProject(dirPath, options);
|
|
699
|
+
if (rusiReport && Object.keys(rusiReport).length) {
|
|
700
|
+
const rusiEvidence = collectRusiEvidence(rusiReport, [
|
|
701
|
+
bomJson.metadata?.component,
|
|
702
|
+
...components,
|
|
703
|
+
]);
|
|
704
|
+
return {
|
|
705
|
+
purlLocationMap: rusiEvidence.purlLocationMap,
|
|
706
|
+
dataFlowFrames: rusiEvidence.dataFlowFrames,
|
|
707
|
+
componentPropertiesMap: rusiEvidence.componentPropertiesMap,
|
|
708
|
+
metadataProperties: rusiEvidence.metadataProperties,
|
|
709
|
+
cryptoComponents: rusiEvidence.cryptoComponents,
|
|
710
|
+
cryptoGeneratePurls: rusiEvidence.cryptoGeneratePurls,
|
|
711
|
+
servicesMap,
|
|
712
|
+
userDefinedTypesMap,
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
return {
|
|
716
|
+
purlLocationMap,
|
|
717
|
+
servicesMap,
|
|
718
|
+
dataFlowFrames,
|
|
719
|
+
userDefinedTypesMap,
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (isDosaiDotnetLanguage(language)) {
|
|
724
|
+
if (options.profile === "research") {
|
|
725
|
+
options.withDataFlow = true;
|
|
726
|
+
options.includeCrypto = true;
|
|
727
|
+
}
|
|
728
|
+
if (
|
|
729
|
+
options.usagesSlicesFile &&
|
|
730
|
+
usableSlicesFile(options.usagesSlicesFile)
|
|
731
|
+
) {
|
|
732
|
+
usageSlice = JSON.parse(
|
|
733
|
+
fs.readFileSync(options.usagesSlicesFile, "utf-8"),
|
|
734
|
+
);
|
|
735
|
+
usagesSlicesFile = options.usagesSlicesFile;
|
|
736
|
+
} else {
|
|
737
|
+
retMap = await createSlice(language, dirPath, "usages", options);
|
|
738
|
+
if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
|
|
739
|
+
usageSlice = JSON.parse(fs.readFileSync(retMap.slicesFile, "utf-8"));
|
|
740
|
+
usagesSlicesFile = retMap.slicesFile;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
if (usageSlice && Object.keys(usageSlice).length) {
|
|
744
|
+
const dosaiEvidence = collectDosaiPurlEvidence(usageSlice, components);
|
|
745
|
+
for (const [purl, locations] of Object.entries(
|
|
746
|
+
dosaiEvidence.purlLocationMap,
|
|
747
|
+
)) {
|
|
748
|
+
purlLocationMap[purl] ??= new Set();
|
|
749
|
+
for (const location of locations) {
|
|
750
|
+
purlLocationMap[purl].add(location);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
servicesMap = collectDosaiServicesFromMethods(usageSlice, servicesMap);
|
|
754
|
+
userDefinedTypesMap = {};
|
|
755
|
+
}
|
|
756
|
+
if (options.withDataFlow) {
|
|
757
|
+
if (
|
|
758
|
+
options.dataFlowSlicesFile &&
|
|
759
|
+
safeExistsSync(options.dataFlowSlicesFile)
|
|
760
|
+
) {
|
|
761
|
+
dataFlowSlicesFile = options.dataFlowSlicesFile;
|
|
762
|
+
dataFlowSlice = JSON.parse(
|
|
763
|
+
fs.readFileSync(options.dataFlowSlicesFile, "utf-8"),
|
|
764
|
+
);
|
|
765
|
+
} else {
|
|
766
|
+
retMap = await createSlice(language, dirPath, "data-flow", options);
|
|
767
|
+
if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
|
|
768
|
+
dataFlowSlicesFile = retMap.slicesFile;
|
|
769
|
+
dataFlowSlice = JSON.parse(
|
|
770
|
+
fs.readFileSync(retMap.slicesFile, "utf-8"),
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
if (dataFlowSlice && Object.keys(dataFlowSlice).length) {
|
|
775
|
+
dataFlowFrames = collectDosaiDataFlowFrames(dataFlowSlice, components);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
if (options.includeCrypto) {
|
|
779
|
+
cryptoComponents = await collectDosaiCryptoComponents(dirPath, options);
|
|
780
|
+
}
|
|
781
|
+
// Persist the combined native dosai report (methods + dataflows) to the
|
|
782
|
+
// semantics-slices path when one was provided (--semantics-slices-file).
|
|
783
|
+
// Mirrors analyzeRusiProject/analyzeGolemProject on feat/rusi-persist-report:
|
|
784
|
+
// dotnet does not otherwise use the semantics slice (atom is never run for
|
|
785
|
+
// dotnet), so the path is free to carry the full native dosai report that
|
|
786
|
+
// downstream tools (depscan) consume as the source of truth. Only the full
|
|
787
|
+
// native report is persisted; the existing SBOM evidence projection below
|
|
788
|
+
// is unchanged.
|
|
789
|
+
semanticsSlicesFile = persistDosaiSemanticsReport(
|
|
790
|
+
options,
|
|
791
|
+
usageSlice,
|
|
792
|
+
dataFlowSlice,
|
|
793
|
+
);
|
|
794
|
+
return {
|
|
795
|
+
usagesSlicesFile,
|
|
796
|
+
dataFlowSlicesFile,
|
|
797
|
+
semanticsSlicesFile,
|
|
798
|
+
purlLocationMap,
|
|
799
|
+
servicesMap,
|
|
800
|
+
dataFlowFrames,
|
|
801
|
+
tempDir: retMap?.tempDir,
|
|
802
|
+
tempDirOwned: retMap?.tempDirOwned,
|
|
803
|
+
userDefinedTypesMap,
|
|
804
|
+
cryptoComponents,
|
|
805
|
+
cryptoGeneratePurls,
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
// Do reachables first so that usages slicing can reuse the atom file
|
|
809
|
+
// We need reachables slicing even when trying to infer crypto packages
|
|
810
|
+
if (options.withReachables || options.includeCrypto) {
|
|
811
|
+
if (
|
|
812
|
+
options.reachablesSlicesFile &&
|
|
813
|
+
usableSlicesFile(options.reachablesSlicesFile)
|
|
814
|
+
) {
|
|
815
|
+
reachablesSlicesFile = options.reachablesSlicesFile;
|
|
816
|
+
reachablesSlice = filterAtomSliceData(
|
|
817
|
+
JSON.parse(fs.readFileSync(options.reachablesSlicesFile, "utf-8")),
|
|
818
|
+
options,
|
|
819
|
+
);
|
|
820
|
+
} else {
|
|
821
|
+
retMap = await createSlice(language, dirPath, "reachables", options);
|
|
822
|
+
if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
|
|
823
|
+
reachablesSlicesFile = retMap.slicesFile;
|
|
824
|
+
reachablesSlice = filterAtomSliceData(
|
|
825
|
+
JSON.parse(fs.readFileSync(retMap.slicesFile, "utf-8")),
|
|
826
|
+
options,
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
if (reachablesSlice && Object.keys(reachablesSlice).length) {
|
|
832
|
+
const retMap = collectReachableFrames(language, reachablesSlice);
|
|
833
|
+
dataFlowFrames = retMap.dataFlowFrames;
|
|
834
|
+
cryptoComponents = retMap.cryptoComponents;
|
|
835
|
+
cryptoGeneratePurls = retMap.cryptoGeneratePurls;
|
|
836
|
+
}
|
|
837
|
+
// Reuse existing usages slices
|
|
838
|
+
if (options.usagesSlicesFile && usableSlicesFile(options.usagesSlicesFile)) {
|
|
839
|
+
usageSlice = filterAtomSliceData(
|
|
840
|
+
JSON.parse(fs.readFileSync(options.usagesSlicesFile, "utf-8")),
|
|
841
|
+
options,
|
|
842
|
+
);
|
|
843
|
+
usagesSlicesFile = options.usagesSlicesFile;
|
|
844
|
+
} else {
|
|
845
|
+
// Generate our own slices
|
|
846
|
+
retMap = await createSlice(language, dirPath, "usages", options);
|
|
847
|
+
if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
|
|
848
|
+
usageSlice = filterAtomSliceData(
|
|
849
|
+
JSON.parse(fs.readFileSync(retMap.slicesFile, "utf-8")),
|
|
850
|
+
options,
|
|
851
|
+
);
|
|
852
|
+
usagesSlicesFile = retMap.slicesFile;
|
|
853
|
+
}
|
|
854
|
+
if (retMap?.semanticsSlicesFile) {
|
|
855
|
+
options.semanticsSlicesFile = retMap.semanticsSlicesFile;
|
|
856
|
+
semanticsSlicesFile = retMap.semanticsSlicesFile;
|
|
857
|
+
if (DEBUG_MODE) {
|
|
858
|
+
console.log(
|
|
859
|
+
`Reusing the generated semantics slices file "${semanticsSlicesFile}".`,
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
if (retMap.openapiSpecFile) {
|
|
864
|
+
// Retain any generated openapi spec file
|
|
865
|
+
openapiSpecFile = retMap.openapiSpecFile;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
// Support for semantics slicing
|
|
869
|
+
if (
|
|
870
|
+
(PROJECT_TYPE_ALIASES.swift.includes(language) ||
|
|
871
|
+
PROJECT_TYPE_ALIASES.scala.includes(language)) &&
|
|
872
|
+
components.length
|
|
873
|
+
) {
|
|
874
|
+
// Reuse existing semantics slices for swift and scala
|
|
875
|
+
if (
|
|
876
|
+
options.semanticsSlicesFile &&
|
|
877
|
+
safeExistsSync(options.semanticsSlicesFile)
|
|
878
|
+
) {
|
|
879
|
+
semanticsSlice = JSON.parse(
|
|
880
|
+
fs.readFileSync(options.semanticsSlicesFile, "utf-8"),
|
|
881
|
+
);
|
|
882
|
+
semanticsSlicesFile = options.semanticsSlicesFile;
|
|
883
|
+
} else if (PROJECT_TYPE_ALIASES.swift.includes(language)) {
|
|
884
|
+
// Generate our own slices for swift
|
|
885
|
+
retMap = await createSlice(language, dirPath, "semantics", options);
|
|
886
|
+
if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
|
|
887
|
+
semanticsSlice = JSON.parse(
|
|
888
|
+
fs.readFileSync(retMap.slicesFile, "utf-8"),
|
|
889
|
+
);
|
|
890
|
+
semanticsSlicesFile = retMap.slicesFile;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
// Parse usage slices
|
|
895
|
+
if (usageSlice && Object.keys(usageSlice).length) {
|
|
896
|
+
const retMap = await parseObjectSlices(
|
|
897
|
+
language,
|
|
898
|
+
usageSlice,
|
|
899
|
+
dbObjMap,
|
|
900
|
+
servicesMap,
|
|
901
|
+
purlLocationMap,
|
|
902
|
+
purlImportsMap,
|
|
903
|
+
openapiSpecFile,
|
|
904
|
+
);
|
|
905
|
+
purlLocationMap = retMap.purlLocationMap;
|
|
906
|
+
servicesMap = retMap.servicesMap;
|
|
907
|
+
userDefinedTypesMap = retMap.userDefinedTypesMap;
|
|
908
|
+
}
|
|
909
|
+
// Parse the semantics slices for swift and scala
|
|
910
|
+
if (
|
|
911
|
+
semanticsSlice &&
|
|
912
|
+
Object.keys(semanticsSlice).length &&
|
|
913
|
+
components.length
|
|
914
|
+
) {
|
|
915
|
+
// Identify the purl locations
|
|
916
|
+
const retMap = parseSemanticSlices(language, components, semanticsSlice);
|
|
917
|
+
purlLocationMap = retMap.purlLocationMap;
|
|
918
|
+
}
|
|
919
|
+
if (options.withDataFlow) {
|
|
920
|
+
if (
|
|
921
|
+
options.dataFlowSlicesFile &&
|
|
922
|
+
safeExistsSync(options.dataFlowSlicesFile)
|
|
923
|
+
) {
|
|
924
|
+
dataFlowSlicesFile = options.dataFlowSlicesFile;
|
|
925
|
+
dataFlowSlice = filterAtomSliceData(
|
|
926
|
+
JSON.parse(fs.readFileSync(options.dataFlowSlicesFile, "utf-8")),
|
|
927
|
+
options,
|
|
928
|
+
);
|
|
929
|
+
} else if (!PROJECT_TYPE_ALIASES.scala.includes(language)) {
|
|
930
|
+
retMap = await createSlice(language, dirPath, "data-flow", options);
|
|
931
|
+
if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
|
|
932
|
+
dataFlowSlicesFile = retMap.slicesFile;
|
|
933
|
+
dataFlowSlice = filterAtomSliceData(
|
|
934
|
+
JSON.parse(fs.readFileSync(retMap.slicesFile, "utf-8")),
|
|
935
|
+
options,
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
if (dataFlowSlice && Object.keys(dataFlowSlice).length) {
|
|
941
|
+
dataFlowFrames = await collectDataFlowFrames(
|
|
942
|
+
language,
|
|
943
|
+
userDefinedTypesMap,
|
|
944
|
+
dataFlowSlice,
|
|
945
|
+
dbObjMap,
|
|
946
|
+
purlLocationMap,
|
|
947
|
+
purlImportsMap,
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
return {
|
|
951
|
+
atomFile: retMap?.atomFile,
|
|
952
|
+
usagesSlicesFile,
|
|
953
|
+
dataFlowSlicesFile,
|
|
954
|
+
reachablesSlicesFile,
|
|
955
|
+
semanticsSlicesFile,
|
|
956
|
+
purlLocationMap,
|
|
957
|
+
servicesMap,
|
|
958
|
+
dataFlowFrames,
|
|
959
|
+
tempDir: retMap?.tempDir,
|
|
960
|
+
tempDirOwned: retMap?.tempDirOwned,
|
|
961
|
+
userDefinedTypesMap,
|
|
962
|
+
cryptoComponents,
|
|
963
|
+
cryptoGeneratePurls,
|
|
964
|
+
openapiSpecFile,
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Parse atom object slices into usages, user-defined types, services, and purl-location evidence.
|
|
970
|
+
*
|
|
971
|
+
* Iterates over the `objectSlices` and `userDefinedTypes` of an atom usage slice,
|
|
972
|
+
* delegating to {@link parseSliceUsages} to record purl locations and imports.
|
|
973
|
+
* Services are detected first from an OpenAPI spec (when present) and then from
|
|
974
|
+
* usage slices or user-defined types as fallbacks.
|
|
975
|
+
*
|
|
976
|
+
* @param {string} language Application language
|
|
977
|
+
* @param {Object} usageSlice Parsed atom usage slice object
|
|
978
|
+
* @param {Object} dbObjMap DB models handle containing Namespaces and Usages models
|
|
979
|
+
* @param {Object} [servicesMap={}] Map populated with detected service definitions
|
|
980
|
+
* @param {Object<string, Set>} [purlLocationMap={}] Map populated with purl -> source locations
|
|
981
|
+
* @param {Object<string, string[]>} [purlImportsMap={}] Map populated with purl -> imported modules
|
|
982
|
+
* @param {string} [openapiSpecFile=undefined] Optional path to an OpenAPI spec used for service detection
|
|
983
|
+
* @returns {Promise<Object>} Map with `purlLocationMap`, `servicesMap`, and `userDefinedTypesMap`
|
|
984
|
+
*/
|
|
985
|
+
export async function parseObjectSlices(
|
|
986
|
+
language,
|
|
987
|
+
usageSlice,
|
|
988
|
+
dbObjMap,
|
|
989
|
+
servicesMap = {},
|
|
990
|
+
purlLocationMap = {},
|
|
991
|
+
purlImportsMap = {},
|
|
992
|
+
openapiSpecFile = undefined,
|
|
993
|
+
) {
|
|
994
|
+
let openapiServicesMode = false;
|
|
995
|
+
if (!usageSlice || !Object.keys(usageSlice).length) {
|
|
996
|
+
return purlLocationMap;
|
|
997
|
+
}
|
|
998
|
+
const userDefinedTypesMap = {};
|
|
999
|
+
(usageSlice.userDefinedTypes || []).forEach((ut) => {
|
|
1000
|
+
userDefinedTypesMap[ut.name] = true;
|
|
1001
|
+
});
|
|
1002
|
+
for (const slice of [
|
|
1003
|
+
...(usageSlice.objectSlices || []),
|
|
1004
|
+
...(usageSlice.userDefinedTypes || []),
|
|
1005
|
+
]) {
|
|
1006
|
+
// Skip the library code typically without filename
|
|
1007
|
+
if (
|
|
1008
|
+
!slice.fileName?.trim().length ||
|
|
1009
|
+
slice.fileName === "<empty>" ||
|
|
1010
|
+
slice.fileName === "<unknown>"
|
|
1011
|
+
) {
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
await parseSliceUsages(
|
|
1015
|
+
language,
|
|
1016
|
+
userDefinedTypesMap,
|
|
1017
|
+
slice,
|
|
1018
|
+
dbObjMap,
|
|
1019
|
+
purlLocationMap,
|
|
1020
|
+
purlImportsMap,
|
|
1021
|
+
);
|
|
1022
|
+
// Prefer openapi for identifying services
|
|
1023
|
+
if (
|
|
1024
|
+
!openapiServicesMode &&
|
|
1025
|
+
openapiSpecFile &&
|
|
1026
|
+
safeExistsSync(openapiSpecFile)
|
|
1027
|
+
) {
|
|
1028
|
+
detectServicesFromOpenAPI(language, openapiSpecFile, servicesMap);
|
|
1029
|
+
if (servicesMap && Object.keys(servicesMap).length) {
|
|
1030
|
+
openapiServicesMode = true;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
// Only identify services from usage slices as a fallback
|
|
1034
|
+
if (!openapiServicesMode) {
|
|
1035
|
+
detectServicesFromUsages(language, slice, servicesMap);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
// Only identify services from user defined types as a second fallback
|
|
1039
|
+
if (!openapiServicesMode) {
|
|
1040
|
+
detectServicesFromUDT(language, usageSlice.userDefinedTypes, servicesMap);
|
|
1041
|
+
}
|
|
1042
|
+
return {
|
|
1043
|
+
purlLocationMap,
|
|
1044
|
+
servicesMap,
|
|
1045
|
+
userDefinedTypesMap,
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* The implementation of this function is based on the logic proposed in the atom slices specification
|
|
1051
|
+
* https://github.com/AppThreat/atom/blob/main/specification/docs/slices.md#use
|
|
1052
|
+
*
|
|
1053
|
+
* @param {string} language Application language
|
|
1054
|
+
* @param {Object} userDefinedTypesMap User Defined types in the application
|
|
1055
|
+
* @param {Array} slice Usages array for each objectSlice
|
|
1056
|
+
* @param {Object} dbObjMap DB Models
|
|
1057
|
+
* @param {Object} purlLocationMap Object to track locations where purls are used
|
|
1058
|
+
* @param {Object} purlImportsMap Object to track package urls and their import aliases
|
|
1059
|
+
* @returns
|
|
1060
|
+
*/
|
|
1061
|
+
export async function parseSliceUsages(
|
|
1062
|
+
language,
|
|
1063
|
+
userDefinedTypesMap,
|
|
1064
|
+
slice,
|
|
1065
|
+
dbObjMap,
|
|
1066
|
+
purlLocationMap,
|
|
1067
|
+
purlImportsMap,
|
|
1068
|
+
) {
|
|
1069
|
+
const fileName = slice.fileName;
|
|
1070
|
+
const typesToLookup = new Set();
|
|
1071
|
+
const lKeyOverrides = {};
|
|
1072
|
+
const usages = slice.usages || [];
|
|
1073
|
+
// What should be the line number to use. slice.lineNumber would be quite coarse and could lead to reports such as
|
|
1074
|
+
// #1670. Line numbers under targetObj and definedBy is a safe bet for dynamic languages, but occassionally leads to
|
|
1075
|
+
// confusion when inter-procedural tracking works better than expected.
|
|
1076
|
+
let sliceLineNumber;
|
|
1077
|
+
if (["java", "jar"].includes(language)) {
|
|
1078
|
+
sliceLineNumber = slice.lineNumber;
|
|
1079
|
+
}
|
|
1080
|
+
// Annotations from usages
|
|
1081
|
+
if (slice.signature?.startsWith("@") && !usages.length) {
|
|
1082
|
+
typesToLookup.add(slice.fullName);
|
|
1083
|
+
addToOverrides(lKeyOverrides, slice.fullName, fileName, slice.lineNumber);
|
|
1084
|
+
}
|
|
1085
|
+
// PHP imports from usages
|
|
1086
|
+
if (slice.code?.startsWith("use") && !usages.length) {
|
|
1087
|
+
typesToLookup.add(slice.fullName);
|
|
1088
|
+
addToOverrides(lKeyOverrides, slice.fullName, fileName, slice.lineNumber);
|
|
1089
|
+
}
|
|
1090
|
+
for (const ausage of usages) {
|
|
1091
|
+
const ausageLine =
|
|
1092
|
+
sliceLineNumber ||
|
|
1093
|
+
ausage?.targetObj?.lineNumber ||
|
|
1094
|
+
ausage?.definedBy?.lineNumber;
|
|
1095
|
+
// First capture the types in the targetObj and definedBy
|
|
1096
|
+
for (const atype of [
|
|
1097
|
+
[ausage?.targetObj?.isExternal, ausage?.targetObj?.typeFullName],
|
|
1098
|
+
[ausage?.targetObj?.isExternal, ausage?.targetObj?.resolvedMethod],
|
|
1099
|
+
[ausage?.definedBy?.name?.includes("::"), ausage?.definedBy?.name],
|
|
1100
|
+
[ausage?.definedBy?.isExternal, ausage?.definedBy?.typeFullName],
|
|
1101
|
+
[ausage?.definedBy?.isExternal, ausage?.definedBy?.resolvedMethod],
|
|
1102
|
+
...(ausage?.fields || []).map((f) => [f?.isExternal, f?.typeFullName]),
|
|
1103
|
+
]) {
|
|
1104
|
+
if (
|
|
1105
|
+
!atype[0] &&
|
|
1106
|
+
(!atype[1] || ["ANY", "(...)", "<empty>"].includes(atype[1]))
|
|
1107
|
+
) {
|
|
1108
|
+
continue;
|
|
1109
|
+
}
|
|
1110
|
+
if (
|
|
1111
|
+
atype[0] !== false &&
|
|
1112
|
+
!isFilterableType(language, userDefinedTypesMap, atype[1])
|
|
1113
|
+
) {
|
|
1114
|
+
if (!atype[1].includes("(") && !atype[1].includes(".py")) {
|
|
1115
|
+
typesToLookup.add(simplifyType(atype[1]));
|
|
1116
|
+
// Javascript and Ruby calls can be resolved to a precise line number only from the call nodes
|
|
1117
|
+
if (
|
|
1118
|
+
["javascript", "js", "ts", "typescript", "ruby"].includes(
|
|
1119
|
+
language,
|
|
1120
|
+
) &&
|
|
1121
|
+
ausageLine
|
|
1122
|
+
) {
|
|
1123
|
+
if (atype[1].includes(":")) {
|
|
1124
|
+
typesToLookup.add(
|
|
1125
|
+
simplifyType(atype[1].split("::")[0].replace(/:/g, "/")),
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
addToOverrides(lKeyOverrides, atype[1], fileName, ausageLine);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
const maybeClassType = getClassTypeFromSignature(language, atype[1]);
|
|
1132
|
+
typesToLookup.add(maybeClassType);
|
|
1133
|
+
if (ausageLine) {
|
|
1134
|
+
addToOverrides(lKeyOverrides, maybeClassType, fileName, ausageLine);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
// Now capture full method signatures from invokedCalls, argToCalls including the paramtypes
|
|
1139
|
+
for (const acall of []
|
|
1140
|
+
.concat(ausage?.invokedCalls || [])
|
|
1141
|
+
.concat(ausage?.argToCalls || [])
|
|
1142
|
+
.concat(ausage?.procedures || [])) {
|
|
1143
|
+
if (
|
|
1144
|
+
acall.resolvedMethod?.startsWith("@") ||
|
|
1145
|
+
acall?.callName?.includes("::")
|
|
1146
|
+
) {
|
|
1147
|
+
typesToLookup.add(acall.callName);
|
|
1148
|
+
if (acall.lineNumber) {
|
|
1149
|
+
addToOverrides(
|
|
1150
|
+
lKeyOverrides,
|
|
1151
|
+
acall.callName,
|
|
1152
|
+
fileName,
|
|
1153
|
+
acall.lineNumber,
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
} else if (acall.isExternal === false) {
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
if (
|
|
1160
|
+
!isFilterableType(language, userDefinedTypesMap, acall?.resolvedMethod)
|
|
1161
|
+
) {
|
|
1162
|
+
if (
|
|
1163
|
+
!acall?.resolvedMethod.includes("(") &&
|
|
1164
|
+
!acall?.resolvedMethod.includes(".py")
|
|
1165
|
+
) {
|
|
1166
|
+
typesToLookup.add(simplifyType(acall?.resolvedMethod));
|
|
1167
|
+
// Javascript calls can be resolved to a precise line number only from the call nodes
|
|
1168
|
+
if (acall.lineNumber) {
|
|
1169
|
+
addToOverrides(
|
|
1170
|
+
lKeyOverrides,
|
|
1171
|
+
acall?.resolvedMethod,
|
|
1172
|
+
fileName,
|
|
1173
|
+
acall.lineNumber,
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
const maybeClassType = getClassTypeFromSignature(
|
|
1178
|
+
language,
|
|
1179
|
+
acall?.resolvedMethod,
|
|
1180
|
+
);
|
|
1181
|
+
typesToLookup.add(maybeClassType);
|
|
1182
|
+
if (acall.lineNumber) {
|
|
1183
|
+
addToOverrides(
|
|
1184
|
+
lKeyOverrides,
|
|
1185
|
+
maybeClassType,
|
|
1186
|
+
fileName,
|
|
1187
|
+
acall.lineNumber,
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
for (const aparamType of acall?.paramTypes || []) {
|
|
1192
|
+
if (!isFilterableType(language, userDefinedTypesMap, aparamType)) {
|
|
1193
|
+
if (!aparamType.includes("(") && !aparamType.includes(".py")) {
|
|
1194
|
+
typesToLookup.add(simplifyType(aparamType));
|
|
1195
|
+
if (acall.lineNumber) {
|
|
1196
|
+
if (aparamType.includes(":")) {
|
|
1197
|
+
typesToLookup.add(
|
|
1198
|
+
simplifyType(aparamType.split("::")[0].replace(/:/g, "/")),
|
|
1199
|
+
);
|
|
1200
|
+
}
|
|
1201
|
+
addToOverrides(
|
|
1202
|
+
lKeyOverrides,
|
|
1203
|
+
aparamType,
|
|
1204
|
+
fileName,
|
|
1205
|
+
acall.lineNumber,
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
const maybeClassType = getClassTypeFromSignature(
|
|
1210
|
+
language,
|
|
1211
|
+
aparamType,
|
|
1212
|
+
);
|
|
1213
|
+
typesToLookup.add(maybeClassType);
|
|
1214
|
+
if (acall.lineNumber) {
|
|
1215
|
+
addToOverrides(
|
|
1216
|
+
lKeyOverrides,
|
|
1217
|
+
maybeClassType,
|
|
1218
|
+
fileName,
|
|
1219
|
+
acall.lineNumber,
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
for (const atype of typesToLookup) {
|
|
1227
|
+
if (isFilterableType(language, userDefinedTypesMap, atype)) {
|
|
1228
|
+
continue;
|
|
1229
|
+
}
|
|
1230
|
+
if (purlImportsMap && Object.keys(purlImportsMap).length) {
|
|
1231
|
+
for (const apurl of Object.keys(purlImportsMap)) {
|
|
1232
|
+
const apurlImports = purlImportsMap[apurl];
|
|
1233
|
+
if (["php", "python", "ruby"].includes(language)) {
|
|
1234
|
+
for (const aimp of apurlImports) {
|
|
1235
|
+
if (
|
|
1236
|
+
atype.startsWith(aimp) ||
|
|
1237
|
+
(language === "ruby" && aimp.startsWith(atype))
|
|
1238
|
+
) {
|
|
1239
|
+
if (!purlLocationMap[apurl]) {
|
|
1240
|
+
purlLocationMap[apurl] = new Set();
|
|
1241
|
+
}
|
|
1242
|
+
if (lKeyOverrides[atype]) {
|
|
1243
|
+
purlLocationMap[apurl].add(...lKeyOverrides[atype]);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
} else {
|
|
1248
|
+
if (apurlImports?.includes(atype)) {
|
|
1249
|
+
if (!purlLocationMap[apurl]) {
|
|
1250
|
+
purlLocationMap[apurl] = new Set();
|
|
1251
|
+
}
|
|
1252
|
+
if (lKeyOverrides[atype]) {
|
|
1253
|
+
purlLocationMap[apurl].add(...lKeyOverrides[atype]);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
} else {
|
|
1259
|
+
// Check the namespaces db
|
|
1260
|
+
let nsHits = typePurlsCache[atype];
|
|
1261
|
+
if (!nsHits && ["java", "jar"].includes(language)) {
|
|
1262
|
+
nsHits = await dbObjMap.Namespaces.findAll({
|
|
1263
|
+
attributes: ["purl"],
|
|
1264
|
+
where: {
|
|
1265
|
+
data: {
|
|
1266
|
+
like: `%${atype}%`,
|
|
1267
|
+
},
|
|
1268
|
+
},
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
if (nsHits?.length) {
|
|
1272
|
+
for (const ns of nsHits) {
|
|
1273
|
+
if (!purlLocationMap[ns.purl]) {
|
|
1274
|
+
purlLocationMap[ns.purl] = new Set();
|
|
1275
|
+
}
|
|
1276
|
+
if (lKeyOverrides[atype]) {
|
|
1277
|
+
purlLocationMap[ns.purl].add(...lKeyOverrides[atype]);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
typePurlsCache[atype] = nsHits;
|
|
1281
|
+
} else {
|
|
1282
|
+
// Avoid persistent lookups
|
|
1283
|
+
typePurlsCache[atype] = [];
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
/**
|
|
1290
|
+
* Method to parse semantic slice data. Currently supported for swift and scala languages.
|
|
1291
|
+
*
|
|
1292
|
+
* @param {String} language Project language.
|
|
1293
|
+
* @param {Array} components Components from the input SBOM
|
|
1294
|
+
* @param {Object} semanticsSlice Semantic slice data
|
|
1295
|
+
* @returns {Object} Parsed metadata
|
|
1296
|
+
*/
|
|
1297
|
+
export function parseSemanticSlices(language, components, semanticsSlice) {
|
|
1298
|
+
// For scala, use the dedicated scalasem module.
|
|
1299
|
+
if (language === "scala") {
|
|
1300
|
+
return findPurlLocations(components, semanticsSlice);
|
|
1301
|
+
}
|
|
1302
|
+
if (!components) {
|
|
1303
|
+
return undefined;
|
|
1304
|
+
}
|
|
1305
|
+
const componentNamePurlMap = {};
|
|
1306
|
+
const componentSymbolsMap = {};
|
|
1307
|
+
const allObfuscationsMap = {};
|
|
1308
|
+
// We have two attributes in the semantics slice to expand a given module to its constituent symbols
|
|
1309
|
+
// - A less precise buildSymbols, which is obtained by parsing the various output-file-map.json files
|
|
1310
|
+
// - A granular and precise moduleInfos, which has the exact classes, protocols, enums etc belonging to each module
|
|
1311
|
+
// The objective then is to build the purlLocationMap, which is the list of locations where a given purl is used
|
|
1312
|
+
// For this, we have two attributes:
|
|
1313
|
+
// - A simpler fileStructures attribute that has the mapping between a swift file and the list of referenced types
|
|
1314
|
+
// - A granular fileIndexes attribute that contains information about the clang modules and line number information
|
|
1315
|
+
|
|
1316
|
+
// We first need to map out the component names to their purls
|
|
1317
|
+
// This is because the semantics slice use the module names everywhere
|
|
1318
|
+
for (const comp of components) {
|
|
1319
|
+
componentNamePurlMap[comp.name] = comp.purl;
|
|
1320
|
+
if (!componentSymbolsMap[comp.name]) {
|
|
1321
|
+
componentSymbolsMap[comp.name] = new Set();
|
|
1322
|
+
}
|
|
1323
|
+
if (semanticsSlice?.buildSymbols[comp.name]) {
|
|
1324
|
+
for (const asym of semanticsSlice.buildSymbols[comp.name]) {
|
|
1325
|
+
componentSymbolsMap[comp.name].add(asym);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
const moduleInfo = semanticsSlice?.moduleInfos[comp.name] || {};
|
|
1329
|
+
if (moduleInfo.classes) {
|
|
1330
|
+
for (const asym of moduleInfo.classes) {
|
|
1331
|
+
componentSymbolsMap[comp.name].add(asym);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
if (moduleInfo.protocols) {
|
|
1335
|
+
for (const asym of moduleInfo.protocols) {
|
|
1336
|
+
componentSymbolsMap[comp.name].add(asym);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
if (moduleInfo.enums) {
|
|
1340
|
+
for (const asym of moduleInfo.enums) {
|
|
1341
|
+
componentSymbolsMap[comp.name].add(asym);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
// Now collect the method signatures from class and protocol methods
|
|
1345
|
+
if (moduleInfo.classMethods) {
|
|
1346
|
+
for (const aclassName of Object.keys(moduleInfo.classMethods)) {
|
|
1347
|
+
for (const asym of moduleInfo.classMethods[aclassName]) {
|
|
1348
|
+
componentSymbolsMap[comp.name].add(asym);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
if (moduleInfo.protocolMethods) {
|
|
1353
|
+
for (const aclassName of Object.keys(moduleInfo.protocolMethods)) {
|
|
1354
|
+
for (const asym of moduleInfo.protocolMethods[aclassName]) {
|
|
1355
|
+
componentSymbolsMap[comp.name].add(asym);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
// Build a large obfuscation map
|
|
1360
|
+
if (moduleInfo.obfuscationMap) {
|
|
1361
|
+
for (const asym of Object.keys(moduleInfo.obfuscationMap)) {
|
|
1362
|
+
allObfuscationsMap[asym] = moduleInfo.obfuscationMap[asym];
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
const purlLocationsSet = {};
|
|
1367
|
+
// We now have a good set of data in componentSymbolsMap and allObfuscationsMap
|
|
1368
|
+
// We can iterate these symbols and check if they exist under fileIndexes.symbolLocations
|
|
1369
|
+
for (const compName of Object.keys(componentSymbolsMap)) {
|
|
1370
|
+
const compSymbols = Array.from(componentSymbolsMap[compName]);
|
|
1371
|
+
const searchHits = searchSymbolLocations(
|
|
1372
|
+
compSymbols,
|
|
1373
|
+
semanticsSlice?.fileIndexes,
|
|
1374
|
+
);
|
|
1375
|
+
// We have an occurrence hit. Let's populate the purlLocationMap
|
|
1376
|
+
if (searchHits?.length) {
|
|
1377
|
+
const locations =
|
|
1378
|
+
purlLocationsSet[componentNamePurlMap[compName]] || new Set();
|
|
1379
|
+
for (const ahit of searchHits) {
|
|
1380
|
+
for (const aline of ahit.lineNumbers) {
|
|
1381
|
+
locations.add(`${ahit.file}#${aline}`);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
purlLocationsSet[componentNamePurlMap[compName]] = locations;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
const purlLocationMap = {};
|
|
1388
|
+
for (const apurl of Object.keys(purlLocationsSet)) {
|
|
1389
|
+
purlLocationMap[apurl] = Array.from(purlLocationsSet[apurl]).sort();
|
|
1390
|
+
}
|
|
1391
|
+
return { purlLocationMap };
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
function searchSymbolLocations(compSymbols, fileIndexes) {
|
|
1395
|
+
const searchHits = [];
|
|
1396
|
+
if (!fileIndexes) {
|
|
1397
|
+
return undefined;
|
|
1398
|
+
}
|
|
1399
|
+
for (const aswiftFile of Object.keys(fileIndexes)) {
|
|
1400
|
+
if (!fileIndexes[aswiftFile].symbolLocations) {
|
|
1401
|
+
continue;
|
|
1402
|
+
}
|
|
1403
|
+
for (const asym of Object.keys(fileIndexes[aswiftFile].symbolLocations)) {
|
|
1404
|
+
const lineNumbers = fileIndexes[aswiftFile].symbolLocations[asym];
|
|
1405
|
+
if (compSymbols.includes(asym)) {
|
|
1406
|
+
searchHits.push({
|
|
1407
|
+
file: aswiftFile,
|
|
1408
|
+
symbol: asym,
|
|
1409
|
+
lineNumbers,
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
return searchHits;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
/**
|
|
1418
|
+
* Decide whether a type name should be ignored during slice analysis.
|
|
1419
|
+
*
|
|
1420
|
+
* Returns `true` for placeholder/builtin/unresolved names and for language
|
|
1421
|
+
* specific primitives and JDK/runtime packages. User-defined types recorded in
|
|
1422
|
+
* `userDefinedTypesMap` are also considered filterable.
|
|
1423
|
+
*
|
|
1424
|
+
* @param {string} language Application language
|
|
1425
|
+
* @param {Object<string, boolean>} userDefinedTypesMap Known user-defined type names
|
|
1426
|
+
* @param {string} typeFullName Full type name to test
|
|
1427
|
+
* @returns {boolean} `true` when the type should be filtered out
|
|
1428
|
+
*/
|
|
1429
|
+
export function isFilterableType(language, userDefinedTypesMap, typeFullName) {
|
|
1430
|
+
if (
|
|
1431
|
+
!typeFullName ||
|
|
1432
|
+
["ANY", "UNKNOWN", "VOID", "IMPORT"].includes(typeFullName.toUpperCase())
|
|
1433
|
+
) {
|
|
1434
|
+
return true;
|
|
1435
|
+
}
|
|
1436
|
+
for (const ab of [
|
|
1437
|
+
"<operator",
|
|
1438
|
+
"<unresolved",
|
|
1439
|
+
"<unknownFullName",
|
|
1440
|
+
"__builtin",
|
|
1441
|
+
"LAMBDA",
|
|
1442
|
+
"../",
|
|
1443
|
+
]) {
|
|
1444
|
+
if (typeFullName.startsWith(ab)) {
|
|
1445
|
+
return true;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
if (language && ["java", "jar"].includes(language)) {
|
|
1449
|
+
if (
|
|
1450
|
+
!typeFullName.includes(".") ||
|
|
1451
|
+
typeFullName.startsWith("@") ||
|
|
1452
|
+
typeFullName.startsWith("java.") ||
|
|
1453
|
+
typeFullName.startsWith("sun.") ||
|
|
1454
|
+
typeFullName.startsWith("jdk.") ||
|
|
1455
|
+
typeFullName.startsWith("org.w3c.") ||
|
|
1456
|
+
typeFullName.startsWith("org.xml.") ||
|
|
1457
|
+
typeFullName.startsWith("javax.xml.")
|
|
1458
|
+
) {
|
|
1459
|
+
return true;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
if (["javascript", "js", "ts", "typescript"].includes(language)) {
|
|
1463
|
+
if (
|
|
1464
|
+
typeFullName.includes(".js") ||
|
|
1465
|
+
typeFullName.includes("=>") ||
|
|
1466
|
+
typeFullName.startsWith("__") ||
|
|
1467
|
+
typeFullName.startsWith("{ ") ||
|
|
1468
|
+
typeFullName.startsWith("JSON") ||
|
|
1469
|
+
typeFullName.startsWith("void:") ||
|
|
1470
|
+
typeFullName.startsWith("node:")
|
|
1471
|
+
) {
|
|
1472
|
+
return true;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
if (["python", "py"].includes(language)) {
|
|
1476
|
+
if (
|
|
1477
|
+
typeFullName.startsWith("tmp") ||
|
|
1478
|
+
typeFullName.startsWith("self.") ||
|
|
1479
|
+
typeFullName.startsWith("_") ||
|
|
1480
|
+
typeFullName.startsWith("def ")
|
|
1481
|
+
) {
|
|
1482
|
+
return true;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
if (["php"].includes(language)) {
|
|
1486
|
+
if (!typeFullName.includes("\\") && !typeFullName.startsWith("use")) {
|
|
1487
|
+
return true;
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
if (["ruby"].includes(language)) {
|
|
1491
|
+
if (
|
|
1492
|
+
!typeFullName ||
|
|
1493
|
+
["<empty>"].includes(typeFullName) ||
|
|
1494
|
+
typeFullName.startsWith("__core.") ||
|
|
1495
|
+
typeFullName.startsWith("@") ||
|
|
1496
|
+
typeFullName.toLowerCase() === typeFullName
|
|
1497
|
+
) {
|
|
1498
|
+
return true;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
return !!userDefinedTypesMap[typeFullName];
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
/**
|
|
1505
|
+
* Extract service and endpoint definitions from an OpenAPI spec file.
|
|
1506
|
+
*
|
|
1507
|
+
* Parses the JSON spec and, for every operation under `paths`, records a
|
|
1508
|
+
* service entry keyed by the URL pattern and HTTP method into `servicesMap`.
|
|
1509
|
+
*
|
|
1510
|
+
* @param {string} _language Application language (unused; reserved for future use)
|
|
1511
|
+
* @param {string} openapiSpecFile Path to the OpenAPI JSON spec file
|
|
1512
|
+
* @param {Object} servicesMap Map populated with detected services
|
|
1513
|
+
* @returns {void}
|
|
1514
|
+
*/
|
|
1515
|
+
export function detectServicesFromOpenAPI(
|
|
1516
|
+
_language,
|
|
1517
|
+
openapiSpecFile,
|
|
1518
|
+
servicesMap,
|
|
1519
|
+
) {
|
|
1520
|
+
try {
|
|
1521
|
+
const specData = JSON.parse(
|
|
1522
|
+
fs.readFileSync(openapiSpecFile, { encoding: "utf-8" }),
|
|
1523
|
+
);
|
|
1524
|
+
if (!specData?.paths || !Object.keys(specData.paths).length) {
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
for (const aurlPattern of Object.keys(specData.paths)) {
|
|
1528
|
+
const httpMethodObj = specData.paths[aurlPattern];
|
|
1529
|
+
for (const httpMethod of Object.keys(httpMethodObj)) {
|
|
1530
|
+
const hobj = httpMethodObj[httpMethod];
|
|
1531
|
+
const serviceName = `service-${aurlPattern.replaceAll("/", "")}-${httpMethod}`;
|
|
1532
|
+
const operationId = hobj["operationId"];
|
|
1533
|
+
const properties = [
|
|
1534
|
+
{ name: "cdx:service:httpMethod", value: httpMethod },
|
|
1535
|
+
];
|
|
1536
|
+
if (operationId) {
|
|
1537
|
+
properties.push({ name: "internal:operationId", value: operationId });
|
|
1538
|
+
}
|
|
1539
|
+
servicesMap[serviceName] = {
|
|
1540
|
+
endpoints: new Set([aurlPattern]),
|
|
1541
|
+
authenticated: undefined,
|
|
1542
|
+
xTrustBoundary: undefined,
|
|
1543
|
+
properties,
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
} catch (_e) {
|
|
1548
|
+
// Ignore malformed or unsupported OpenAPI files and fall back to usage slices.
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
/**
|
|
1553
|
+
* Method to detect services from annotation objects in the usage slice
|
|
1554
|
+
*
|
|
1555
|
+
* @param {string} language Application language
|
|
1556
|
+
* @param {Array} slice Usages array for each objectSlice
|
|
1557
|
+
* @param {Object} servicesMap Existing service map
|
|
1558
|
+
*/
|
|
1559
|
+
export function detectServicesFromUsages(language, slice, servicesMap = {}) {
|
|
1560
|
+
const usages = slice.usages;
|
|
1561
|
+
if (!usages) {
|
|
1562
|
+
return [];
|
|
1563
|
+
}
|
|
1564
|
+
for (const usage of usages) {
|
|
1565
|
+
const targetObj = usage?.targetObj;
|
|
1566
|
+
const definedBy = usage?.definedBy;
|
|
1567
|
+
let endpoints = [];
|
|
1568
|
+
let authenticated;
|
|
1569
|
+
if (language === "ruby" && definedBy?.name?.includes("/")) {
|
|
1570
|
+
endpoints = extractEndpoints(language, definedBy.name);
|
|
1571
|
+
} else if (targetObj?.resolvedMethod) {
|
|
1572
|
+
if (language !== "php") {
|
|
1573
|
+
endpoints = extractEndpoints(language, targetObj?.resolvedMethod);
|
|
1574
|
+
}
|
|
1575
|
+
if (targetObj?.resolvedMethod.toLowerCase().includes("auth")) {
|
|
1576
|
+
authenticated = true;
|
|
1577
|
+
}
|
|
1578
|
+
} else if (definedBy?.resolvedMethod) {
|
|
1579
|
+
if (language !== "php") {
|
|
1580
|
+
endpoints = extractEndpoints(language, definedBy?.resolvedMethod);
|
|
1581
|
+
}
|
|
1582
|
+
if (definedBy?.resolvedMethod.toLowerCase().includes("auth")) {
|
|
1583
|
+
authenticated = true;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
if (usage.invokedCalls) {
|
|
1587
|
+
for (const acall of usage.invokedCalls) {
|
|
1588
|
+
if (acall.resolvedMethod) {
|
|
1589
|
+
if (language !== "php") {
|
|
1590
|
+
const tmpEndpoints = extractEndpoints(
|
|
1591
|
+
language,
|
|
1592
|
+
acall.resolvedMethod,
|
|
1593
|
+
);
|
|
1594
|
+
if (acall.resolvedMethod.toLowerCase().includes("auth")) {
|
|
1595
|
+
authenticated = true;
|
|
1596
|
+
}
|
|
1597
|
+
if (tmpEndpoints?.length) {
|
|
1598
|
+
endpoints = (endpoints || []).concat(tmpEndpoints);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
if (endpoints?.length) {
|
|
1605
|
+
const serviceName = constructServiceName(language, slice);
|
|
1606
|
+
if (!servicesMap[serviceName]) {
|
|
1607
|
+
servicesMap[serviceName] = {
|
|
1608
|
+
endpoints: new Set(),
|
|
1609
|
+
authenticated,
|
|
1610
|
+
xTrustBoundary: authenticated === true ? true : undefined,
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
for (const endpoint of endpoints) {
|
|
1614
|
+
servicesMap[serviceName].endpoints.add(endpoint);
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
/**
|
|
1621
|
+
* Method to detect services from user defined types in the usage slice
|
|
1622
|
+
*
|
|
1623
|
+
* @param {string} language Application language
|
|
1624
|
+
* @param {Array} userDefinedTypes User defined types
|
|
1625
|
+
* @param {Object} servicesMap Existing service map
|
|
1626
|
+
*/
|
|
1627
|
+
export function detectServicesFromUDT(language, userDefinedTypes, servicesMap) {
|
|
1628
|
+
if (
|
|
1629
|
+
["python", "py", "c", "cpp", "c++", "php", "ruby"].includes(language) &&
|
|
1630
|
+
userDefinedTypes?.length
|
|
1631
|
+
) {
|
|
1632
|
+
for (const audt of userDefinedTypes) {
|
|
1633
|
+
if (
|
|
1634
|
+
audt.name.toLowerCase().includes("route") ||
|
|
1635
|
+
audt.name.toLowerCase().includes("path") ||
|
|
1636
|
+
audt.name.toLowerCase().includes("url") ||
|
|
1637
|
+
audt.name.toLowerCase().includes("registerhandler") ||
|
|
1638
|
+
audt.name.toLowerCase().includes("endpoint") ||
|
|
1639
|
+
audt.name.toLowerCase().includes("api") ||
|
|
1640
|
+
audt.name.toLowerCase().includes("add_method") ||
|
|
1641
|
+
audt.name.toLowerCase().includes("get") ||
|
|
1642
|
+
audt.name.toLowerCase().includes("post") ||
|
|
1643
|
+
audt.name.toLowerCase().includes("delete") ||
|
|
1644
|
+
audt.name.toLowerCase().includes("put") ||
|
|
1645
|
+
audt.name.toLowerCase().includes("head") ||
|
|
1646
|
+
audt.name.toLowerCase().includes("options") ||
|
|
1647
|
+
audt.name.toLowerCase().includes("addRoute") ||
|
|
1648
|
+
audt.name.toLowerCase().includes("connect")
|
|
1649
|
+
) {
|
|
1650
|
+
const fields = audt.fields || [];
|
|
1651
|
+
if (fields.length && fields[0]?.name && fields[0].name.length > 1) {
|
|
1652
|
+
const endpoints = extractEndpoints(language, fields[0].name);
|
|
1653
|
+
let serviceName = "service";
|
|
1654
|
+
if (audt.fileName) {
|
|
1655
|
+
serviceName = `${path.basename(
|
|
1656
|
+
audt.fileName.replace(".py", ""),
|
|
1657
|
+
)}-service`;
|
|
1658
|
+
}
|
|
1659
|
+
if (endpoints?.length) {
|
|
1660
|
+
if (!servicesMap[serviceName]) {
|
|
1661
|
+
servicesMap[serviceName] = {
|
|
1662
|
+
endpoints: new Set(),
|
|
1663
|
+
authenticated: false,
|
|
1664
|
+
xTrustBoundary: undefined,
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
for (const endpoint of endpoints) {
|
|
1668
|
+
servicesMap[serviceName].endpoints.add(endpoint);
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
/**
|
|
1678
|
+
* Derive a service name from a slice's fullName or file name.
|
|
1679
|
+
*
|
|
1680
|
+
* Uses the portion of `slice.fullName` before the first `:` (dots replaced with
|
|
1681
|
+
* hyphens), falling back to the file basename. A `-service` suffix is appended
|
|
1682
|
+
* when not already present.
|
|
1683
|
+
*
|
|
1684
|
+
* @param {string} _language Application language (unused; reserved for future use)
|
|
1685
|
+
* @param {Object} slice Object slice containing `fullName` and/or `fileName`
|
|
1686
|
+
* @returns {string} Constructed service name
|
|
1687
|
+
*/
|
|
1688
|
+
export function constructServiceName(_language, slice) {
|
|
1689
|
+
let serviceName = "service";
|
|
1690
|
+
if (slice?.fullName) {
|
|
1691
|
+
serviceName = slice.fullName.split(":")[0].replace(/\./g, "-");
|
|
1692
|
+
} else if (slice?.fileName) {
|
|
1693
|
+
serviceName = path.basename(slice.fileName).split(".")[0];
|
|
1694
|
+
}
|
|
1695
|
+
if (!serviceName.endsWith("service")) {
|
|
1696
|
+
serviceName = `${serviceName}-service`;
|
|
1697
|
+
}
|
|
1698
|
+
return serviceName;
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
/**
|
|
1702
|
+
* Extract HTTP endpoint paths from source code annotations for a given language.
|
|
1703
|
+
*
|
|
1704
|
+
* Examines the supplied code snippet for framework-specific routing annotations
|
|
1705
|
+
* (e.g. Spring `@*Mapping`, Express `app.`/`route`, Rails route verbs) and
|
|
1706
|
+
* returns the matched endpoint path strings.
|
|
1707
|
+
*
|
|
1708
|
+
* @param {string} language Application language such as `java`, `js`, or `ruby`
|
|
1709
|
+
* @param {string} code Source code snippet to inspect
|
|
1710
|
+
* @returns {string[]|undefined} Array of endpoint paths or `undefined` when none are found
|
|
1711
|
+
*/
|
|
1712
|
+
export function extractEndpoints(language, code) {
|
|
1713
|
+
if (!code) {
|
|
1714
|
+
return undefined;
|
|
1715
|
+
}
|
|
1716
|
+
let endpoints;
|
|
1717
|
+
switch (language) {
|
|
1718
|
+
case "java":
|
|
1719
|
+
case "jar":
|
|
1720
|
+
if (
|
|
1721
|
+
code.startsWith("@") &&
|
|
1722
|
+
(code.includes("Mapping") || code.includes("Path")) &&
|
|
1723
|
+
code.includes("(")
|
|
1724
|
+
) {
|
|
1725
|
+
const matches = code.match(/['"](.*?)['"]/gi) || [];
|
|
1726
|
+
endpoints = matches
|
|
1727
|
+
.map((v) => v.replace(/["']/g, ""))
|
|
1728
|
+
.filter(
|
|
1729
|
+
(v) =>
|
|
1730
|
+
v.length &&
|
|
1731
|
+
!v.startsWith(".") &&
|
|
1732
|
+
v.includes("/") &&
|
|
1733
|
+
!v.startsWith("@"),
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1736
|
+
break;
|
|
1737
|
+
case "js":
|
|
1738
|
+
case "ts":
|
|
1739
|
+
case "javascript":
|
|
1740
|
+
case "typescript":
|
|
1741
|
+
if (code.includes("app.") || code.includes("route")) {
|
|
1742
|
+
const matches = code.match(/['"](.*?)['"]/gi) || [];
|
|
1743
|
+
endpoints = matches
|
|
1744
|
+
.map((v) => v.replace(/["']/g, ""))
|
|
1745
|
+
.filter(
|
|
1746
|
+
(v) =>
|
|
1747
|
+
v.length &&
|
|
1748
|
+
!v.startsWith(".") &&
|
|
1749
|
+
v.includes("/") &&
|
|
1750
|
+
!v.startsWith("@") &&
|
|
1751
|
+
!v.startsWith("application/") &&
|
|
1752
|
+
!v.startsWith("text/"),
|
|
1753
|
+
);
|
|
1754
|
+
}
|
|
1755
|
+
break;
|
|
1756
|
+
case "ruby":
|
|
1757
|
+
case "rb": {
|
|
1758
|
+
let urlPrefix = "";
|
|
1759
|
+
let urlSuffix = "";
|
|
1760
|
+
// Remove the ellipsis added by the frontend
|
|
1761
|
+
code = code.replaceAll("...", "");
|
|
1762
|
+
if (code.includes("namespace ")) {
|
|
1763
|
+
urlPrefix = code.split("namespace ").pop().split(" ")[0];
|
|
1764
|
+
} else if (code.includes("collection do get ")) {
|
|
1765
|
+
urlPrefix = code.split("collection do get ").pop().split(" ")[0];
|
|
1766
|
+
}
|
|
1767
|
+
for (const m of ["get", "post", "delete", "options", "put", "head"]) {
|
|
1768
|
+
if (code.includes(`${m} `)) {
|
|
1769
|
+
urlSuffix = code.split(`${m} `).pop().split(" ")[0];
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
if (code.includes("http") && code.includes('"')) {
|
|
1773
|
+
endpoints = code.split('"').filter((s) => s.startsWith("http"));
|
|
1774
|
+
}
|
|
1775
|
+
if (urlPrefix !== "" || urlSuffix !== "") {
|
|
1776
|
+
if (!endpoints) {
|
|
1777
|
+
endpoints = [];
|
|
1778
|
+
}
|
|
1779
|
+
endpoints.push(
|
|
1780
|
+
`${urlPrefix.replace(/['"]/g, "")}${urlSuffix.replace(/['"]/g, "")}`,
|
|
1781
|
+
);
|
|
1782
|
+
}
|
|
1783
|
+
endpoints =
|
|
1784
|
+
endpoints && Array.isArray(endpoints)
|
|
1785
|
+
? endpoints.filter(
|
|
1786
|
+
(u) => u.length > 1 && !u.startsWith(".") && u !== "https:/",
|
|
1787
|
+
)
|
|
1788
|
+
: endpoints;
|
|
1789
|
+
break;
|
|
1790
|
+
}
|
|
1791
|
+
default:
|
|
1792
|
+
endpoints = (code.match(/['"](.*?)['"]/gi) || [])
|
|
1793
|
+
.map((v) => v.replace(/["']/g, "").replace("\n", ""))
|
|
1794
|
+
.filter((v) => v.length > 2 && v.includes("/"));
|
|
1795
|
+
break;
|
|
1796
|
+
}
|
|
1797
|
+
return endpoints;
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
/**
|
|
1801
|
+
* Method to create the SBOM with evidence file called evinse file.
|
|
1802
|
+
*
|
|
1803
|
+
* @param {Object} sliceArtefacts Various artefacts from the slice operation
|
|
1804
|
+
* @param {Object} options Command line options
|
|
1805
|
+
* @returns
|
|
1806
|
+
*/
|
|
1807
|
+
export function createEvinseFile(sliceArtefacts, options) {
|
|
1808
|
+
const {
|
|
1809
|
+
tempDir,
|
|
1810
|
+
tempDirOwned,
|
|
1811
|
+
usagesSlicesFile,
|
|
1812
|
+
dataFlowSlicesFile,
|
|
1813
|
+
reachablesSlicesFile,
|
|
1814
|
+
purlLocationMap,
|
|
1815
|
+
servicesMap,
|
|
1816
|
+
dataFlowFrames,
|
|
1817
|
+
componentPropertiesMap = {},
|
|
1818
|
+
metadataProperties = [],
|
|
1819
|
+
cryptoComponents,
|
|
1820
|
+
cryptoGeneratePurls,
|
|
1821
|
+
} = sliceArtefacts;
|
|
1822
|
+
const bomFile = options.input;
|
|
1823
|
+
const evinseOutFile = options.output;
|
|
1824
|
+
const bomJson = JSON.parse(fs.readFileSync(bomFile, "utf8"));
|
|
1825
|
+
const components = bomJson.components || [];
|
|
1826
|
+
// Clear existing annotations
|
|
1827
|
+
bomJson.annotations = [];
|
|
1828
|
+
appendComponentProperties(bomJson.metadata?.component, metadataProperties);
|
|
1829
|
+
let occEvidencePresent = false;
|
|
1830
|
+
let csEvidencePresent = false;
|
|
1831
|
+
let servicesPresent = false;
|
|
1832
|
+
const propertyEvidencePresent =
|
|
1833
|
+
metadataProperties.length || Object.keys(componentPropertiesMap).length;
|
|
1834
|
+
for (const comp of [bomJson.metadata?.component, ...components]) {
|
|
1835
|
+
if (!comp?.purl) {
|
|
1836
|
+
continue;
|
|
1837
|
+
}
|
|
1838
|
+
delete comp.signature;
|
|
1839
|
+
appendComponentProperties(comp, componentPropertiesMap[comp.purl]);
|
|
1840
|
+
const locationOccurrences = Array.from(
|
|
1841
|
+
purlLocationMap[comp.purl] || [],
|
|
1842
|
+
).sort();
|
|
1843
|
+
if (locationOccurrences.length) {
|
|
1844
|
+
if (!comp.evidence) {
|
|
1845
|
+
comp.evidence = {};
|
|
1846
|
+
}
|
|
1847
|
+
// This step would replace any existing occurrences
|
|
1848
|
+
// This is fine as long as the input sbom was also generated by cdxgen
|
|
1849
|
+
comp.evidence.occurrences = locationOccurrences
|
|
1850
|
+
.filter((l) => !!l)
|
|
1851
|
+
.map((l) => parseOccurrenceEvidenceLocation(l));
|
|
1852
|
+
occEvidencePresent = true;
|
|
1853
|
+
}
|
|
1854
|
+
const dfFrames = dataFlowFrames[comp.purl];
|
|
1855
|
+
if (dfFrames?.length) {
|
|
1856
|
+
if (!comp.evidence) {
|
|
1857
|
+
comp.evidence = {};
|
|
1858
|
+
}
|
|
1859
|
+
if (!comp.evidence.callstack) {
|
|
1860
|
+
comp.evidence.callstack = {};
|
|
1861
|
+
}
|
|
1862
|
+
if (!comp.evidence.callstack.frames) {
|
|
1863
|
+
comp.evidence.callstack.frames = framePicker(dfFrames);
|
|
1864
|
+
csEvidencePresent = true;
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
// Add crypto tags if this purl offers any generation algorithm
|
|
1868
|
+
if (
|
|
1869
|
+
cryptoGeneratePurls?.[comp.purl] &&
|
|
1870
|
+
Array.from(cryptoGeneratePurls[comp.purl]).length
|
|
1871
|
+
) {
|
|
1872
|
+
comp.tags = ["crypto", "crypto-generate"];
|
|
1873
|
+
}
|
|
1874
|
+
} // for
|
|
1875
|
+
if (servicesMap && Object.keys(servicesMap).length) {
|
|
1876
|
+
const services = [];
|
|
1877
|
+
for (const serviceName of Object.keys(servicesMap)) {
|
|
1878
|
+
services.push({
|
|
1879
|
+
name: serviceName,
|
|
1880
|
+
endpoints: Array.from(servicesMap[serviceName].endpoints),
|
|
1881
|
+
authenticated: servicesMap[serviceName].authenticated,
|
|
1882
|
+
"x-trust-boundary": servicesMap[serviceName].xTrustBoundary,
|
|
1883
|
+
properties: servicesMap[serviceName].properties,
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
// Add to existing services while preserving CycloneDX uniqueItems validity
|
|
1887
|
+
bomJson.services = mergeServices(bomJson.services || [], services);
|
|
1888
|
+
servicesPresent = true;
|
|
1889
|
+
}
|
|
1890
|
+
// Add the crypto components to the components list
|
|
1891
|
+
if (cryptoComponents?.length) {
|
|
1892
|
+
bomJson.components = bomJson.components.concat(cryptoComponents);
|
|
1893
|
+
}
|
|
1894
|
+
// Fix the dependencies section with provides information
|
|
1895
|
+
if (
|
|
1896
|
+
cryptoGeneratePurls &&
|
|
1897
|
+
Object.keys(cryptoGeneratePurls).length &&
|
|
1898
|
+
bomJson.dependencies
|
|
1899
|
+
) {
|
|
1900
|
+
const newDependencies = [];
|
|
1901
|
+
for (const depObj of bomJson.dependencies) {
|
|
1902
|
+
if (depObj.ref && cryptoGeneratePurls[depObj.ref]) {
|
|
1903
|
+
const providedAlgos = Array.from(cryptoGeneratePurls[depObj.ref]);
|
|
1904
|
+
if (providedAlgos.length) {
|
|
1905
|
+
depObj.provides = providedAlgos;
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
newDependencies.push(depObj);
|
|
1909
|
+
}
|
|
1910
|
+
bomJson.dependencies = newDependencies;
|
|
1911
|
+
}
|
|
1912
|
+
if (options.annotate) {
|
|
1913
|
+
if (usagesSlicesFile && safeExistsSync(usagesSlicesFile)) {
|
|
1914
|
+
bomJson.annotations.push({
|
|
1915
|
+
subjects: [bomJson.serialNumber],
|
|
1916
|
+
annotator: { component: bomJson.metadata.tools.components[0] },
|
|
1917
|
+
timestamp: getTimestamp(),
|
|
1918
|
+
text: fs.readFileSync(usagesSlicesFile, "utf8"),
|
|
1919
|
+
});
|
|
1920
|
+
}
|
|
1921
|
+
if (dataFlowSlicesFile && safeExistsSync(dataFlowSlicesFile)) {
|
|
1922
|
+
bomJson.annotations.push({
|
|
1923
|
+
subjects: [bomJson.serialNumber],
|
|
1924
|
+
annotator: { component: bomJson.metadata.tools.components[0] },
|
|
1925
|
+
timestamp: getTimestamp(),
|
|
1926
|
+
text: fs.readFileSync(dataFlowSlicesFile, "utf8"),
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1929
|
+
if (reachablesSlicesFile && safeExistsSync(reachablesSlicesFile)) {
|
|
1930
|
+
bomJson.annotations.push({
|
|
1931
|
+
subjects: [bomJson.serialNumber],
|
|
1932
|
+
annotator: { component: bomJson.metadata.tools.components[0] },
|
|
1933
|
+
timestamp: getTimestamp(),
|
|
1934
|
+
text: fs.readFileSync(reachablesSlicesFile, "utf8"),
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
// Increment the version
|
|
1939
|
+
bomJson.version = (bomJson.version || 1) + 1;
|
|
1940
|
+
// Set the current timestamp to indicate this is newer
|
|
1941
|
+
bomJson.metadata.timestamp = getTimestamp();
|
|
1942
|
+
delete bomJson.signature;
|
|
1943
|
+
// Redo post-processing with evinse data. `executeOsQuery` has to be injected
|
|
1944
|
+
// because postgen no longer imports it directly (stages is below managers);
|
|
1945
|
+
// without it the crypto formulation path receives `undefined` and throws.
|
|
1946
|
+
const bomNSData = postProcess({ bomJson }, { ...options, executeOsQuery });
|
|
1947
|
+
safeWriteSync(
|
|
1948
|
+
evinseOutFile,
|
|
1949
|
+
JSON.stringify(bomNSData.bomJson, null, options.jsonPretty ? 2 : null),
|
|
1950
|
+
);
|
|
1951
|
+
if (
|
|
1952
|
+
occEvidencePresent ||
|
|
1953
|
+
csEvidencePresent ||
|
|
1954
|
+
servicesPresent ||
|
|
1955
|
+
propertyEvidencePresent
|
|
1956
|
+
) {
|
|
1957
|
+
console.log(evinseOutFile, "created successfully.");
|
|
1958
|
+
} else {
|
|
1959
|
+
console.log(
|
|
1960
|
+
"Unable to identify component evidence for the input SBOM based on the slices from atom. The slices are either empty or lack appropriate tags.",
|
|
1961
|
+
);
|
|
1962
|
+
if (DEBUG_MODE) {
|
|
1963
|
+
console.log(
|
|
1964
|
+
"1. Ensure cdxgen was installed without omitting any optional dependencies.",
|
|
1965
|
+
);
|
|
1966
|
+
console.log(
|
|
1967
|
+
"2. Retry after removing the following files from the root directory: app.atom, usages.slices.json, reachables.slices.json.",
|
|
1968
|
+
);
|
|
1969
|
+
if (readEnvironmentVariable("CDXGEN_IN_CONTAINER") !== "true") {
|
|
1970
|
+
console.log(
|
|
1971
|
+
"3. Additional environment variables may have to be set for local invocations. Check the documentation for atom and evinse.",
|
|
1972
|
+
);
|
|
1973
|
+
} else {
|
|
1974
|
+
console.log(
|
|
1975
|
+
"TIP: Try creating the slices using the official atom container image `ghcr.io/appthreat/atom:main` directly. Refer to the documentation: https://atom-docs.appthreat.dev/",
|
|
1976
|
+
);
|
|
1977
|
+
}
|
|
1978
|
+
console.log(
|
|
1979
|
+
"Large projects may require more memory. Consider increasing the memory to 16GB or higher.",
|
|
1980
|
+
);
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
if (tempDirOwned && tempDir?.startsWith(getTmpDir())) {
|
|
1984
|
+
safeRmSync(tempDir, { recursive: true, force: true });
|
|
1985
|
+
}
|
|
1986
|
+
return bomNSData?.bomJson;
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
/**
|
|
1990
|
+
* Method to convert dataflow slice into usable callstack frames
|
|
1991
|
+
* Implemented based on the logic proposed here - https://github.com/AppThreat/atom/blob/main/specification/docs/slices.md#data-flow-slice
|
|
1992
|
+
*
|
|
1993
|
+
* @param {string} language Application language
|
|
1994
|
+
* @param {Object} userDefinedTypesMap User Defined types in the application
|
|
1995
|
+
* @param {Object} dataFlowSlice Data flow slice object from atom
|
|
1996
|
+
* @param {Object} dbObjMap DB models
|
|
1997
|
+
* @param {Object} _purlLocationMap Object to track locations where purls are used
|
|
1998
|
+
* @param {Object} purlImportsMap Object to track package urls and their import aliases
|
|
1999
|
+
*/
|
|
2000
|
+
export async function collectDataFlowFrames(
|
|
2001
|
+
language,
|
|
2002
|
+
userDefinedTypesMap,
|
|
2003
|
+
dataFlowSlice,
|
|
2004
|
+
dbObjMap,
|
|
2005
|
+
_purlLocationMap,
|
|
2006
|
+
purlImportsMap,
|
|
2007
|
+
) {
|
|
2008
|
+
const nodes = dataFlowSlice?.graph?.nodes || [];
|
|
2009
|
+
// Cache the nodes based on the id to improve lookup
|
|
2010
|
+
const nodeCache = {};
|
|
2011
|
+
// purl key and an array of frames array
|
|
2012
|
+
// CycloneDX 1.5 currently accepts only 1 frame as evidence
|
|
2013
|
+
// so this method is more future-proof
|
|
2014
|
+
const dfFrames = {};
|
|
2015
|
+
for (const n of nodes) {
|
|
2016
|
+
nodeCache[n.id] = n;
|
|
2017
|
+
}
|
|
2018
|
+
const paths = dataFlowSlice?.paths || [];
|
|
2019
|
+
for (const apath of paths) {
|
|
2020
|
+
const aframe = [];
|
|
2021
|
+
let referredPurls = new Set();
|
|
2022
|
+
for (const nid of apath) {
|
|
2023
|
+
const theNode = nodeCache[nid];
|
|
2024
|
+
if (!theNode) {
|
|
2025
|
+
continue;
|
|
2026
|
+
}
|
|
2027
|
+
let typeFullName = theNode.typeFullName;
|
|
2028
|
+
if (
|
|
2029
|
+
["javascript", "js", "ts", "typescript"].includes(language) &&
|
|
2030
|
+
typeFullName === "ANY"
|
|
2031
|
+
) {
|
|
2032
|
+
if (
|
|
2033
|
+
theNode.code &&
|
|
2034
|
+
(theNode.code.startsWith("new ") ||
|
|
2035
|
+
["METHOD_PARAMETER_IN", "IDENTIFIER"].includes(theNode.label))
|
|
2036
|
+
) {
|
|
2037
|
+
typeFullName = theNode.code.split("(")[0].replace("new ", "");
|
|
2038
|
+
} else {
|
|
2039
|
+
typeFullName = theNode.fullName || theNode.name;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
const maybeClassType = getClassTypeFromSignature(language, typeFullName);
|
|
2043
|
+
if (!isFilterableType(language, userDefinedTypesMap, typeFullName)) {
|
|
2044
|
+
if (purlImportsMap && Object.keys(purlImportsMap).length) {
|
|
2045
|
+
for (const apurl of Object.keys(purlImportsMap)) {
|
|
2046
|
+
const apurlImports = purlImportsMap[apurl];
|
|
2047
|
+
if (
|
|
2048
|
+
apurlImports &&
|
|
2049
|
+
(apurlImports.includes(typeFullName) ||
|
|
2050
|
+
apurlImports.includes(maybeClassType))
|
|
2051
|
+
) {
|
|
2052
|
+
referredPurls.add(apurl);
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
} else {
|
|
2056
|
+
// Check the namespaces db
|
|
2057
|
+
let nsHits = typePurlsCache[typeFullName];
|
|
2058
|
+
if (["java", "jar"].includes(language)) {
|
|
2059
|
+
nsHits = await dbObjMap.Namespaces.findAll({
|
|
2060
|
+
attributes: ["purl"],
|
|
2061
|
+
where: {
|
|
2062
|
+
data: {
|
|
2063
|
+
like: `%${typeFullName}%`,
|
|
2064
|
+
},
|
|
2065
|
+
},
|
|
2066
|
+
});
|
|
2067
|
+
}
|
|
2068
|
+
if (nsHits?.length) {
|
|
2069
|
+
for (const ns of nsHits) {
|
|
2070
|
+
referredPurls.add(ns.purl);
|
|
2071
|
+
}
|
|
2072
|
+
typePurlsCache[typeFullName] = nsHits;
|
|
2073
|
+
} else if (DEBUG_MODE) {
|
|
2074
|
+
console.log("Unable to identify purl for", typeFullName);
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
let parentPackageName = theNode.parentPackageName || "";
|
|
2079
|
+
if (
|
|
2080
|
+
parentPackageName === "<global>" &&
|
|
2081
|
+
theNode.parentClassName &&
|
|
2082
|
+
theNode.parentClassName.includes("::")
|
|
2083
|
+
) {
|
|
2084
|
+
parentPackageName = theNode.parentClassName.split("::")[0];
|
|
2085
|
+
if (parentPackageName.includes(".js")) {
|
|
2086
|
+
const tmpA = parentPackageName.split("/");
|
|
2087
|
+
if (tmpA.length > 1) {
|
|
2088
|
+
tmpA.pop();
|
|
2089
|
+
}
|
|
2090
|
+
parentPackageName = tmpA.join("/");
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
aframe.push({
|
|
2094
|
+
package: parentPackageName,
|
|
2095
|
+
module: theNode.parentClassName || "",
|
|
2096
|
+
function: theNode.parentMethodName || "",
|
|
2097
|
+
line: theNode.lineNumber || undefined,
|
|
2098
|
+
column: theNode.columnNumber || undefined,
|
|
2099
|
+
fullFilename: theNode.parentFileName || "",
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
referredPurls = Array.from(referredPurls);
|
|
2103
|
+
if (referredPurls.length) {
|
|
2104
|
+
for (const apurl of referredPurls) {
|
|
2105
|
+
if (!dfFrames[apurl]) {
|
|
2106
|
+
dfFrames[apurl] = [];
|
|
2107
|
+
}
|
|
2108
|
+
// Store this frame as an evidence for this purl
|
|
2109
|
+
dfFrames[apurl].push(aframe);
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
return dfFrames;
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
/**
|
|
2117
|
+
* Method to convert reachable slice into usable callstack frames and crypto components
|
|
2118
|
+
*
|
|
2119
|
+
* Implemented based on the logic proposed here - https://github.com/AppThreat/atom/blob/main/specification/docs/slices.md#data-flow-slice
|
|
2120
|
+
*
|
|
2121
|
+
* @param {string} _language Application language
|
|
2122
|
+
* @param {Object} reachablesSlice Reachables slice object from atom
|
|
2123
|
+
*/
|
|
2124
|
+
export function collectReachableFrames(_language, reachablesSlice) {
|
|
2125
|
+
const reachableNodes = Array.isArray(reachablesSlice)
|
|
2126
|
+
? reachablesSlice
|
|
2127
|
+
: reachablesSlice?.reachables || [];
|
|
2128
|
+
// purl key and an array of frames array
|
|
2129
|
+
// CycloneDX 1.5 currently accepts only 1 frame as evidence
|
|
2130
|
+
// so this method is more future-proof
|
|
2131
|
+
const dfFrames = {};
|
|
2132
|
+
const cryptoComponentsMap = {};
|
|
2133
|
+
// Track purls which provide the specific generate algorithm
|
|
2134
|
+
const cryptoGeneratePurls = {};
|
|
2135
|
+
for (const anode of reachableNodes) {
|
|
2136
|
+
const aframe = [];
|
|
2137
|
+
let referredPurls = new Set(anode.purls || []);
|
|
2138
|
+
let isCryptoFlow = false;
|
|
2139
|
+
let codeSnippets = "";
|
|
2140
|
+
let cpurls = [];
|
|
2141
|
+
for (const fnode of anode.flows) {
|
|
2142
|
+
const tagStr = fnode.tags || "";
|
|
2143
|
+
if (tagStr.includes("crypto")) {
|
|
2144
|
+
isCryptoFlow = true;
|
|
2145
|
+
}
|
|
2146
|
+
if (isCryptoFlow && fnode.code) {
|
|
2147
|
+
codeSnippets = `${codeSnippets}\\n${fnode.code}`;
|
|
2148
|
+
}
|
|
2149
|
+
if (
|
|
2150
|
+
tagStr.includes("crypto-generate") ||
|
|
2151
|
+
fnode.code.includes("encrypt") ||
|
|
2152
|
+
fnode.code.includes("decrypt") ||
|
|
2153
|
+
fnode.code.includes("sign")
|
|
2154
|
+
) {
|
|
2155
|
+
cpurls = tagStr.split(", ").filter((t) => t.startsWith("pkg:"));
|
|
2156
|
+
for (const cpurl of cpurls) {
|
|
2157
|
+
if (!cryptoGeneratePurls[cpurl]) {
|
|
2158
|
+
cryptoGeneratePurls[cpurl] = new Set();
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
if (!fnode.parentFileName || fnode.parentFileName === "<unknown>") {
|
|
2163
|
+
continue;
|
|
2164
|
+
}
|
|
2165
|
+
aframe.push({
|
|
2166
|
+
package: fnode.parentPackageName,
|
|
2167
|
+
module: fnode.parentClassName || "",
|
|
2168
|
+
function: fnode.parentMethodName || "",
|
|
2169
|
+
line: fnode.lineNumber || undefined,
|
|
2170
|
+
column: fnode.columnNumber || undefined,
|
|
2171
|
+
fullFilename: fnode.parentFileName || "",
|
|
2172
|
+
});
|
|
2173
|
+
}
|
|
2174
|
+
referredPurls = Array.from(referredPurls);
|
|
2175
|
+
if (referredPurls.length) {
|
|
2176
|
+
for (const apurl of referredPurls) {
|
|
2177
|
+
if (!dfFrames[apurl]) {
|
|
2178
|
+
dfFrames[apurl] = [];
|
|
2179
|
+
}
|
|
2180
|
+
// Store this frame as an evidence for this purl
|
|
2181
|
+
dfFrames[apurl].push(aframe);
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
// Detect crypto algorithms used in a crypto flow
|
|
2185
|
+
if (isCryptoFlow && codeSnippets.length) {
|
|
2186
|
+
const cryptoAlgos = findCryptoAlgos(codeSnippets);
|
|
2187
|
+
for (const algo of cryptoAlgos) {
|
|
2188
|
+
cryptoComponentsMap[algo.ref] = algo;
|
|
2189
|
+
for (const cpurl of cpurls) {
|
|
2190
|
+
cryptoGeneratePurls[cpurl].add(algo.ref);
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
const cryptoComponents = [];
|
|
2196
|
+
for (const cref of Object.keys(cryptoComponentsMap)) {
|
|
2197
|
+
const algoObj = cryptoComponentsMap[cref];
|
|
2198
|
+
cryptoComponents.push({
|
|
2199
|
+
type: "cryptographic-asset",
|
|
2200
|
+
name: algoObj.name,
|
|
2201
|
+
"bom-ref": algoObj.ref,
|
|
2202
|
+
description: algoObj.description || "",
|
|
2203
|
+
cryptoProperties: {
|
|
2204
|
+
assetType: "algorithm",
|
|
2205
|
+
oid: algoObj.oid,
|
|
2206
|
+
},
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
return {
|
|
2210
|
+
dataFlowFrames: dfFrames,
|
|
2211
|
+
cryptoComponents,
|
|
2212
|
+
cryptoGeneratePurls,
|
|
2213
|
+
};
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
/**
|
|
2217
|
+
* Method to pick a callstack frame as an evidence. This method is required since CycloneDX 1.5 accepts only a single frame as evidence.
|
|
2218
|
+
*
|
|
2219
|
+
* @param {Array} dfFrames Data flow frames
|
|
2220
|
+
* @returns
|
|
2221
|
+
*/
|
|
2222
|
+
export function framePicker(dfFrames) {
|
|
2223
|
+
if (!dfFrames?.length) {
|
|
2224
|
+
return undefined;
|
|
2225
|
+
}
|
|
2226
|
+
const normalizedStacks = dfFrames
|
|
2227
|
+
.filter((stack) => Array.isArray(stack) && stack.length)
|
|
2228
|
+
.map((stack) =>
|
|
2229
|
+
stack.filter(
|
|
2230
|
+
(frame) => frame?.fullFilename && frame.fullFilename !== "<unknown>",
|
|
2231
|
+
),
|
|
2232
|
+
)
|
|
2233
|
+
.filter((stack) => stack.length);
|
|
2234
|
+
if (!normalizedStacks.length) {
|
|
2235
|
+
return undefined;
|
|
2236
|
+
}
|
|
2237
|
+
const frameKey = (frame) =>
|
|
2238
|
+
`${frame.fullFilename}#${frame.line || ""}#${frame.column || ""}`;
|
|
2239
|
+
const dedupeFrames = (frames) => {
|
|
2240
|
+
const seen = new Set();
|
|
2241
|
+
const out = [];
|
|
2242
|
+
for (const frame of frames) {
|
|
2243
|
+
const key = frameKey(frame);
|
|
2244
|
+
if (seen.has(key)) {
|
|
2245
|
+
continue;
|
|
2246
|
+
}
|
|
2247
|
+
seen.add(key);
|
|
2248
|
+
out.push(frame);
|
|
2249
|
+
}
|
|
2250
|
+
return out;
|
|
2251
|
+
};
|
|
2252
|
+
const stackScore = (stack) => {
|
|
2253
|
+
const deduped = dedupeFrames(stack);
|
|
2254
|
+
const uniqueFiles = new Set(deduped.map((frame) => frame.fullFilename))
|
|
2255
|
+
.size;
|
|
2256
|
+
return uniqueFiles * 100 + deduped.length;
|
|
2257
|
+
};
|
|
2258
|
+
const rankedStacks = normalizedStacks
|
|
2259
|
+
.map((stack) => dedupeFrames(stack))
|
|
2260
|
+
.sort((left, right) => stackScore(right) - stackScore(left));
|
|
2261
|
+
const best = rankedStacks[0];
|
|
2262
|
+
const bestUniqueFiles = new Set(best.map((frame) => frame.fullFilename)).size;
|
|
2263
|
+
if (bestUniqueFiles >= 2 || best.length >= 2) {
|
|
2264
|
+
return best;
|
|
2265
|
+
}
|
|
2266
|
+
const synthesized = [];
|
|
2267
|
+
const seen = new Set();
|
|
2268
|
+
for (const stack of rankedStacks) {
|
|
2269
|
+
for (const frame of stack) {
|
|
2270
|
+
const key = frameKey(frame);
|
|
2271
|
+
if (seen.has(key)) {
|
|
2272
|
+
continue;
|
|
2273
|
+
}
|
|
2274
|
+
seen.add(key);
|
|
2275
|
+
synthesized.push(frame);
|
|
2276
|
+
if (synthesized.length >= 8) {
|
|
2277
|
+
return synthesized;
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
return synthesized.length ? synthesized : best;
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
/**
|
|
2285
|
+
* Method to simplify types. For example, arrays ending with [] could be simplified.
|
|
2286
|
+
*
|
|
2287
|
+
* @param {string} typeFullName Full name of the type to simplify
|
|
2288
|
+
* @returns Simplified type string
|
|
2289
|
+
*/
|
|
2290
|
+
export function simplifyType(typeFullName) {
|
|
2291
|
+
return typeFullName.replace("[]", "");
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
/**
|
|
2295
|
+
* Reduce a full type signature to its enclosing class type for a given language.
|
|
2296
|
+
*
|
|
2297
|
+
* Strips method, call, and member qualifiers from the signature using language
|
|
2298
|
+
* specific rules (e.g. java method selectors, javascript `::`/`new`/`await`,
|
|
2299
|
+
* python module/body suffixes) and returns the simplified class type via
|
|
2300
|
+
* {@link simplifyType}.
|
|
2301
|
+
*
|
|
2302
|
+
* @param {string} language Application language
|
|
2303
|
+
* @param {string} typeFullName Full type signature to reduce
|
|
2304
|
+
* @returns {string|undefined} Enclosing class type or `undefined` when it cannot be resolved
|
|
2305
|
+
*/
|
|
2306
|
+
export function getClassTypeFromSignature(language, typeFullName) {
|
|
2307
|
+
if (["java", "jar"].includes(language) && typeFullName.includes(":")) {
|
|
2308
|
+
typeFullName = typeFullName.split(":")[0];
|
|
2309
|
+
const tmpA = typeFullName.split(".");
|
|
2310
|
+
tmpA.pop();
|
|
2311
|
+
typeFullName = tmpA.join(".");
|
|
2312
|
+
} else if (["javascript", "js", "ts", "typescript"].includes(language)) {
|
|
2313
|
+
typeFullName = typeFullName.replace("new: ", "").replace("await ", "");
|
|
2314
|
+
if (typeFullName.includes(":")) {
|
|
2315
|
+
const tmpA = typeFullName.split("::")[0].replace(/:/g, "/").split("/");
|
|
2316
|
+
if (tmpA.length > 1) {
|
|
2317
|
+
tmpA.pop();
|
|
2318
|
+
}
|
|
2319
|
+
typeFullName = tmpA.join("/");
|
|
2320
|
+
}
|
|
2321
|
+
} else if (["python", "py"].includes(language)) {
|
|
2322
|
+
if (typeFullName.includes("/")) {
|
|
2323
|
+
typeFullName = typeFullName.split("/").pop();
|
|
2324
|
+
}
|
|
2325
|
+
typeFullName = typeFullName
|
|
2326
|
+
.replace(".py:<module>", "")
|
|
2327
|
+
.replace(/\//g, ".")
|
|
2328
|
+
.replace(".<metaClassCallHandler>", "")
|
|
2329
|
+
.replace(".<fakeNew>", "")
|
|
2330
|
+
.replace(".<body>", "")
|
|
2331
|
+
.replace(".__iter__", "")
|
|
2332
|
+
.replace(".__init__", "");
|
|
2333
|
+
} else if (["php"].includes(language)) {
|
|
2334
|
+
typeFullName = typeFullName.split("->")[0].split("::")[0];
|
|
2335
|
+
} else if (["ruby"].includes(language)) {
|
|
2336
|
+
if (
|
|
2337
|
+
["<empty>"].includes(typeFullName) ||
|
|
2338
|
+
typeFullName.startsWith("__core.")
|
|
2339
|
+
) {
|
|
2340
|
+
return undefined;
|
|
2341
|
+
}
|
|
2342
|
+
typeFullName = typeFullName.split("::")[0].split(" ").pop();
|
|
2343
|
+
}
|
|
2344
|
+
if (
|
|
2345
|
+
typeFullName.startsWith("<unresolved") ||
|
|
2346
|
+
typeFullName.startsWith("<operator") ||
|
|
2347
|
+
typeFullName.startsWith("<unknownFullName")
|
|
2348
|
+
) {
|
|
2349
|
+
return undefined;
|
|
2350
|
+
}
|
|
2351
|
+
if (typeFullName.includes("$")) {
|
|
2352
|
+
typeFullName = typeFullName.split("$")[0];
|
|
2353
|
+
}
|
|
2354
|
+
return simplifyType(typeFullName);
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
function addToOverrides(lKeyOverrides, atype, fileName, ausageLineNumber) {
|
|
2358
|
+
if (!lKeyOverrides[atype]) {
|
|
2359
|
+
lKeyOverrides[atype] = new Set();
|
|
2360
|
+
}
|
|
2361
|
+
lKeyOverrides[atype].add(`${fileName}#${ausageLineNumber}`);
|
|
2362
|
+
}
|