@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,3459 @@
|
|
|
1
|
+
import {
|
|
2
|
+
lstatSync,
|
|
3
|
+
readdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
realpathSync,
|
|
6
|
+
statSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import { platform as _platform, homedir } from "node:os";
|
|
9
|
+
import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
|
10
|
+
|
|
11
|
+
import { build, Purl } from "@cdxgen/cdx-purl";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
DEBUG_MODE,
|
|
15
|
+
hasDangerousUnicode,
|
|
16
|
+
isDryRun,
|
|
17
|
+
readEnvironmentVariable,
|
|
18
|
+
recordActivity,
|
|
19
|
+
recordSymlinkResolution,
|
|
20
|
+
} from "../core/activity.js";
|
|
21
|
+
import { extractPathEnv } from "../core/env.js";
|
|
22
|
+
import {
|
|
23
|
+
getTmpDir,
|
|
24
|
+
multiChecksumFile,
|
|
25
|
+
safeExistsSync,
|
|
26
|
+
safeMkdirSync,
|
|
27
|
+
safeMkdtempSync,
|
|
28
|
+
safeRmSync,
|
|
29
|
+
safeSpawnSync,
|
|
30
|
+
} from "../core/fs.js";
|
|
31
|
+
import { mapWithConcurrency } from "../core/parallel.js";
|
|
32
|
+
import { isValidDriveRoot } from "../core/paths.js";
|
|
33
|
+
import { isCycloneDxComponentTypeEnabled } from "../inventory/bomUtils.js";
|
|
34
|
+
import { createContainerRiskProperties } from "../inventory/containerRisk.js";
|
|
35
|
+
import {
|
|
36
|
+
attachIdentityTools,
|
|
37
|
+
extractToolRefs,
|
|
38
|
+
} from "../inventory/evidenceUtils.js";
|
|
39
|
+
import { createGtfoBinsProperties } from "../inventory/gtfobins.js";
|
|
40
|
+
import {
|
|
41
|
+
canonicalDistroQualifier,
|
|
42
|
+
isCleanDistroQualifier,
|
|
43
|
+
OS_DISTRO_ALIAS,
|
|
44
|
+
OS_NAMESPACE_ALIAS,
|
|
45
|
+
readOsRelease,
|
|
46
|
+
} from "../inventory/osinfo.js";
|
|
47
|
+
import {
|
|
48
|
+
collectExecutables,
|
|
49
|
+
collectSharedLibs,
|
|
50
|
+
} from "../inventory/osPackageResolver.js";
|
|
51
|
+
import {
|
|
52
|
+
resolveCdxgenPlugins,
|
|
53
|
+
resolvePluginBinary,
|
|
54
|
+
setPluginsPathEnv,
|
|
55
|
+
} from "../inventory/plugins.js";
|
|
56
|
+
import { genericPurl, tryBuildPurl } from "../inventory/purl.js";
|
|
57
|
+
import {
|
|
58
|
+
adjustLicenseInformation,
|
|
59
|
+
findLicenseId,
|
|
60
|
+
isSpdxLicenseExpression,
|
|
61
|
+
} from "../inventory/spdx.js";
|
|
62
|
+
import { getDirs } from "./containerutils.js";
|
|
63
|
+
|
|
64
|
+
const isWin = _platform() === "win32";
|
|
65
|
+
const OS_PURL_TYPES = ["deb", "apk", "rpm", "alpm", "qpkg"];
|
|
66
|
+
const pluginRuntime = setPluginsPathEnv(resolveCdxgenPlugins());
|
|
67
|
+
const platform = pluginRuntime.platform;
|
|
68
|
+
const CDXGEN_PLUGINS_DIR = pluginRuntime.pluginsDir;
|
|
69
|
+
const PLUGIN_MANIFEST_FILE = pluginRuntime.pluginManifestFile;
|
|
70
|
+
let pluginManifest;
|
|
71
|
+
|
|
72
|
+
const MAX_PLUGIN_MANIFEST_BYTES = 1024 * 1024;
|
|
73
|
+
const MAX_PLUGIN_MANIFEST_PLUGINS = 32;
|
|
74
|
+
const MAX_PLUGIN_COMPONENT_HASHES = 16;
|
|
75
|
+
const MAX_PLUGIN_COMPONENT_REFERENCES = 32;
|
|
76
|
+
const MAX_PLUGIN_COMPONENT_PROPERTIES = 128;
|
|
77
|
+
const MAX_PLUGIN_COMPONENT_LICENSES = 8;
|
|
78
|
+
const MAX_PLUGIN_STRING_LENGTH = 4096;
|
|
79
|
+
const MAX_PLUGIN_LONG_STRING_LENGTH = 16384;
|
|
80
|
+
|
|
81
|
+
function sanitizeManifestString(value, maxLength = MAX_PLUGIN_STRING_LENGTH) {
|
|
82
|
+
if (typeof value !== "string") {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
const trimmedValue = value.trim();
|
|
86
|
+
if (!trimmedValue || trimmedValue.length > maxLength) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
return trimmedValue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function sanitizeManifestObjectList(values, limit, mapper) {
|
|
93
|
+
if (!Array.isArray(values) || !values.length) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
const mappedValues = values
|
|
97
|
+
.slice(0, limit)
|
|
98
|
+
.map((value) => mapper(value))
|
|
99
|
+
.filter(Boolean);
|
|
100
|
+
return mappedValues.length ? mappedValues : undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function sanitizeManifestHash(hash) {
|
|
104
|
+
const alg = sanitizeManifestString(hash?.alg, 64);
|
|
105
|
+
const content = sanitizeManifestString(hash?.content, 512);
|
|
106
|
+
if (!alg || !content) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
return { alg, content };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function sanitizeManifestProperty(property) {
|
|
113
|
+
const name = sanitizeManifestString(property?.name, 256);
|
|
114
|
+
const value = sanitizeManifestString(
|
|
115
|
+
property?.value,
|
|
116
|
+
MAX_PLUGIN_LONG_STRING_LENGTH,
|
|
117
|
+
);
|
|
118
|
+
if (!name || !value) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
return { name, value };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function sanitizeManifestExternalReference(reference) {
|
|
125
|
+
const type = sanitizeManifestString(reference?.type, 64);
|
|
126
|
+
const url = sanitizeManifestString(
|
|
127
|
+
reference?.url,
|
|
128
|
+
MAX_PLUGIN_LONG_STRING_LENGTH,
|
|
129
|
+
);
|
|
130
|
+
if (!type || !url) {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
const sanitizedReference = { type, url };
|
|
134
|
+
const comment = sanitizeManifestString(reference?.comment, 512);
|
|
135
|
+
if (comment) {
|
|
136
|
+
sanitizedReference.comment = comment;
|
|
137
|
+
}
|
|
138
|
+
return sanitizedReference;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function sanitizeManifestLicense(licenseEntry) {
|
|
142
|
+
const licenseId = sanitizeManifestString(licenseEntry?.license?.id, 128);
|
|
143
|
+
const licenseName = sanitizeManifestString(licenseEntry?.license?.name, 256);
|
|
144
|
+
const licenseUrl = sanitizeManifestString(
|
|
145
|
+
licenseEntry?.license?.url,
|
|
146
|
+
MAX_PLUGIN_LONG_STRING_LENGTH,
|
|
147
|
+
);
|
|
148
|
+
if (!licenseId && !licenseName) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
const license = {};
|
|
152
|
+
if (licenseId) {
|
|
153
|
+
license.id = licenseId;
|
|
154
|
+
}
|
|
155
|
+
if (licenseName) {
|
|
156
|
+
license.name = licenseName;
|
|
157
|
+
}
|
|
158
|
+
if (licenseUrl) {
|
|
159
|
+
license.url = licenseUrl;
|
|
160
|
+
}
|
|
161
|
+
return { license };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function sanitizeManifestComponent(component, fallbackName) {
|
|
165
|
+
if (!component || typeof component !== "object") {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
const sanitizedComponent = {};
|
|
169
|
+
const stringFields = {
|
|
170
|
+
group: 256,
|
|
171
|
+
name: 256,
|
|
172
|
+
version: 256,
|
|
173
|
+
description: MAX_PLUGIN_LONG_STRING_LENGTH,
|
|
174
|
+
publisher: 256,
|
|
175
|
+
purl: MAX_PLUGIN_LONG_STRING_LENGTH,
|
|
176
|
+
"bom-ref": MAX_PLUGIN_LONG_STRING_LENGTH,
|
|
177
|
+
type: 64,
|
|
178
|
+
};
|
|
179
|
+
for (const [field, maxLength] of Object.entries(stringFields)) {
|
|
180
|
+
const sanitizedValue = sanitizeManifestString(component[field], maxLength);
|
|
181
|
+
if (sanitizedValue) {
|
|
182
|
+
sanitizedComponent[field] = sanitizedValue;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (!sanitizedComponent.name && fallbackName) {
|
|
186
|
+
sanitizedComponent.name = fallbackName;
|
|
187
|
+
}
|
|
188
|
+
if (!sanitizedComponent.name || !sanitizedComponent["bom-ref"]) {
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
const hashes = sanitizeManifestObjectList(
|
|
192
|
+
component.hashes,
|
|
193
|
+
MAX_PLUGIN_COMPONENT_HASHES,
|
|
194
|
+
sanitizeManifestHash,
|
|
195
|
+
);
|
|
196
|
+
const externalReferences = sanitizeManifestObjectList(
|
|
197
|
+
component.externalReferences,
|
|
198
|
+
MAX_PLUGIN_COMPONENT_REFERENCES,
|
|
199
|
+
sanitizeManifestExternalReference,
|
|
200
|
+
);
|
|
201
|
+
const properties = sanitizeManifestObjectList(
|
|
202
|
+
component.properties,
|
|
203
|
+
MAX_PLUGIN_COMPONENT_PROPERTIES,
|
|
204
|
+
sanitizeManifestProperty,
|
|
205
|
+
);
|
|
206
|
+
const licenses = sanitizeManifestObjectList(
|
|
207
|
+
component.licenses,
|
|
208
|
+
MAX_PLUGIN_COMPONENT_LICENSES,
|
|
209
|
+
sanitizeManifestLicense,
|
|
210
|
+
);
|
|
211
|
+
if (hashes) {
|
|
212
|
+
sanitizedComponent.hashes = hashes;
|
|
213
|
+
}
|
|
214
|
+
if (externalReferences) {
|
|
215
|
+
sanitizedComponent.externalReferences = externalReferences;
|
|
216
|
+
}
|
|
217
|
+
if (properties) {
|
|
218
|
+
sanitizedComponent.properties = properties;
|
|
219
|
+
}
|
|
220
|
+
if (licenses) {
|
|
221
|
+
sanitizedComponent.licenses = licenses;
|
|
222
|
+
}
|
|
223
|
+
return sanitizedComponent;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function sanitizePluginManifest(manifest) {
|
|
227
|
+
if (!manifest || typeof manifest !== "object") {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
const sanitizedManifest = {
|
|
231
|
+
plugins: [],
|
|
232
|
+
};
|
|
233
|
+
const generatedAt = sanitizeManifestString(manifest.generatedAt, 128);
|
|
234
|
+
if (generatedAt) {
|
|
235
|
+
sanitizedManifest.generatedAt = generatedAt;
|
|
236
|
+
}
|
|
237
|
+
if (manifest.package && typeof manifest.package === "object") {
|
|
238
|
+
const sanitizedPackage = {};
|
|
239
|
+
for (const [field, maxLength] of Object.entries({
|
|
240
|
+
name: 256,
|
|
241
|
+
version: 256,
|
|
242
|
+
repository: MAX_PLUGIN_LONG_STRING_LENGTH,
|
|
243
|
+
homepage: MAX_PLUGIN_LONG_STRING_LENGTH,
|
|
244
|
+
})) {
|
|
245
|
+
const sanitizedValue = sanitizeManifestString(
|
|
246
|
+
manifest.package[field],
|
|
247
|
+
maxLength,
|
|
248
|
+
);
|
|
249
|
+
if (sanitizedValue) {
|
|
250
|
+
sanitizedPackage[field] = sanitizedValue;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (Object.keys(sanitizedPackage).length) {
|
|
254
|
+
sanitizedManifest.package = sanitizedPackage;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
for (const pluginEntry of Array.isArray(manifest.plugins)
|
|
258
|
+
? manifest.plugins.slice(0, MAX_PLUGIN_MANIFEST_PLUGINS)
|
|
259
|
+
: []) {
|
|
260
|
+
const name = sanitizeManifestString(pluginEntry?.name, 128);
|
|
261
|
+
const component = sanitizeManifestComponent(pluginEntry?.component, name);
|
|
262
|
+
if (!name || !component) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const sanitizedPlugin = { name, component };
|
|
266
|
+
for (const [field, maxLength] of Object.entries({
|
|
267
|
+
version: 256,
|
|
268
|
+
binaryPath: MAX_PLUGIN_LONG_STRING_LENGTH,
|
|
269
|
+
sbomFile: MAX_PLUGIN_LONG_STRING_LENGTH,
|
|
270
|
+
sha256: 256,
|
|
271
|
+
})) {
|
|
272
|
+
const sanitizedValue = sanitizeManifestString(
|
|
273
|
+
pluginEntry?.[field],
|
|
274
|
+
maxLength,
|
|
275
|
+
);
|
|
276
|
+
if (sanitizedValue) {
|
|
277
|
+
sanitizedPlugin[field] = sanitizedValue;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
sanitizedManifest.plugins.push(sanitizedPlugin);
|
|
281
|
+
}
|
|
282
|
+
return sanitizedManifest.plugins.length ? sanitizedManifest : null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function loadPluginManifest() {
|
|
286
|
+
if (pluginManifest !== undefined) {
|
|
287
|
+
return pluginManifest;
|
|
288
|
+
}
|
|
289
|
+
if (!PLUGIN_MANIFEST_FILE) {
|
|
290
|
+
pluginManifest = null;
|
|
291
|
+
return pluginManifest;
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
const manifestRealPath = realpathSync(PLUGIN_MANIFEST_FILE);
|
|
295
|
+
const manifestDirectory = dirname(manifestRealPath);
|
|
296
|
+
const expectedManifestDirectory = realpathSync(CDXGEN_PLUGINS_DIR);
|
|
297
|
+
const manifestStats = statSync(manifestRealPath);
|
|
298
|
+
if (
|
|
299
|
+
basename(manifestRealPath) !== "plugins-manifest.json" ||
|
|
300
|
+
manifestDirectory !== expectedManifestDirectory ||
|
|
301
|
+
!manifestStats.isFile() ||
|
|
302
|
+
manifestStats.size > MAX_PLUGIN_MANIFEST_BYTES
|
|
303
|
+
) {
|
|
304
|
+
pluginManifest = null;
|
|
305
|
+
return pluginManifest;
|
|
306
|
+
}
|
|
307
|
+
pluginManifest = sanitizePluginManifest(
|
|
308
|
+
JSON.parse(readFileSync(manifestRealPath, { encoding: "utf-8" })),
|
|
309
|
+
);
|
|
310
|
+
} catch (_err) {
|
|
311
|
+
pluginManifest = null;
|
|
312
|
+
}
|
|
313
|
+
return pluginManifest;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function cloneSerializable(value) {
|
|
317
|
+
if (!value || typeof value !== "object") {
|
|
318
|
+
return value;
|
|
319
|
+
}
|
|
320
|
+
return JSON.parse(JSON.stringify(value));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function getPluginManifestEntry(toolName) {
|
|
324
|
+
const manifest = loadPluginManifest();
|
|
325
|
+
if (!manifest?.plugins?.length) {
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
return manifest.plugins.find((entry) => entry?.name === toolName);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function mergeToolComponent(manifestComponent, existingComponent) {
|
|
332
|
+
if (!manifestComponent) {
|
|
333
|
+
return cloneSerializable(existingComponent);
|
|
334
|
+
}
|
|
335
|
+
if (!existingComponent) {
|
|
336
|
+
return cloneSerializable(manifestComponent);
|
|
337
|
+
}
|
|
338
|
+
const merged = {
|
|
339
|
+
...cloneSerializable(manifestComponent),
|
|
340
|
+
...cloneSerializable(existingComponent),
|
|
341
|
+
};
|
|
342
|
+
for (const key of [
|
|
343
|
+
"group",
|
|
344
|
+
"name",
|
|
345
|
+
"version",
|
|
346
|
+
"description",
|
|
347
|
+
"publisher",
|
|
348
|
+
"purl",
|
|
349
|
+
"bom-ref",
|
|
350
|
+
"type",
|
|
351
|
+
]) {
|
|
352
|
+
if (manifestComponent?.[key]) {
|
|
353
|
+
merged[key] = manifestComponent[key];
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
merged.hashes = manifestComponent?.hashes?.length
|
|
357
|
+
? manifestComponent.hashes
|
|
358
|
+
: existingComponent?.hashes;
|
|
359
|
+
merged.externalReferences = uniqueExternalReferences(
|
|
360
|
+
(manifestComponent?.externalReferences || []).concat(
|
|
361
|
+
existingComponent?.externalReferences || [],
|
|
362
|
+
),
|
|
363
|
+
);
|
|
364
|
+
merged.properties = uniqueProperties(
|
|
365
|
+
(manifestComponent?.properties || []).concat(
|
|
366
|
+
existingComponent?.properties || [],
|
|
367
|
+
),
|
|
368
|
+
);
|
|
369
|
+
merged.licenses =
|
|
370
|
+
manifestComponent?.licenses?.length || !existingComponent?.licenses?.length
|
|
371
|
+
? manifestComponent?.licenses
|
|
372
|
+
: existingComponent.licenses;
|
|
373
|
+
merged.evidence = manifestComponent?.evidence || existingComponent?.evidence;
|
|
374
|
+
return merged;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function uniqueExternalReferences(references) {
|
|
378
|
+
const seen = new Set();
|
|
379
|
+
const uniqueValues = [];
|
|
380
|
+
for (const reference of references || []) {
|
|
381
|
+
if (!reference?.url || !reference?.type) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
const key = `${reference.type}\u0000${reference.url}`;
|
|
385
|
+
if (seen.has(key)) {
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
seen.add(key);
|
|
389
|
+
uniqueValues.push(reference);
|
|
390
|
+
}
|
|
391
|
+
return uniqueValues;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Look up plugin manifest entries for the given tool names and return their
|
|
396
|
+
* CycloneDX tool component objects, de-duplicated by bom-ref.
|
|
397
|
+
*
|
|
398
|
+
* @param {string[]} [toolNames=[]] Plugin tool names to resolve.
|
|
399
|
+
* @returns {Object[]} De-duplicated CycloneDX tool component objects.
|
|
400
|
+
*/
|
|
401
|
+
export function getPluginToolComponents(toolNames = []) {
|
|
402
|
+
const components = [];
|
|
403
|
+
const seenRefs = new Set();
|
|
404
|
+
for (const toolName of uniqueSortedStrings(toolNames)) {
|
|
405
|
+
const component = cloneSerializable(
|
|
406
|
+
getPluginManifestEntry(toolName)?.component,
|
|
407
|
+
);
|
|
408
|
+
if (!component?.["bom-ref"] || seenRefs.has(component["bom-ref"])) {
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
seenRefs.add(component["bom-ref"]);
|
|
412
|
+
components.push(component);
|
|
413
|
+
}
|
|
414
|
+
return components;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function enrichToolComponents(existingTools = [], toolNames = []) {
|
|
418
|
+
const manifestTools = getPluginToolComponents(toolNames);
|
|
419
|
+
if (!existingTools?.length) {
|
|
420
|
+
return manifestTools;
|
|
421
|
+
}
|
|
422
|
+
const toolMap = new Map();
|
|
423
|
+
for (const tool of existingTools) {
|
|
424
|
+
if (!tool) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
toolMap.set(tool["bom-ref"] || tool.name || JSON.stringify(tool), tool);
|
|
428
|
+
}
|
|
429
|
+
for (const manifestTool of manifestTools) {
|
|
430
|
+
const matchKey = Array.from(toolMap.keys()).find((key) => {
|
|
431
|
+
const existing = toolMap.get(key);
|
|
432
|
+
return (
|
|
433
|
+
existing?.["bom-ref"] === manifestTool["bom-ref"] ||
|
|
434
|
+
existing?.name === manifestTool.name
|
|
435
|
+
);
|
|
436
|
+
});
|
|
437
|
+
if (matchKey) {
|
|
438
|
+
toolMap.set(
|
|
439
|
+
matchKey,
|
|
440
|
+
mergeToolComponent(manifestTool, toolMap.get(matchKey)),
|
|
441
|
+
);
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
toolMap.set(manifestTool["bom-ref"], manifestTool);
|
|
445
|
+
}
|
|
446
|
+
return Array.from(toolMap.values());
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const TRIVY_BIN = resolvePluginBinary("trivy", pluginRuntime);
|
|
450
|
+
const CARGO_AUDITABLE_BIN = resolvePluginBinary(
|
|
451
|
+
"cargo-auditable",
|
|
452
|
+
pluginRuntime,
|
|
453
|
+
);
|
|
454
|
+
const OSQUERY_BIN = resolvePluginBinary("osquery", pluginRuntime);
|
|
455
|
+
const DOSAI_BIN = resolvePluginBinary("dosai", pluginRuntime);
|
|
456
|
+
const TRUSTINSPECTOR_BIN = resolvePluginBinary("trustinspector", pluginRuntime);
|
|
457
|
+
|
|
458
|
+
// Blint bin
|
|
459
|
+
const BLINT_BIN = readEnvironmentVariable("BLINT_CMD") || "blint";
|
|
460
|
+
|
|
461
|
+
// sourcekitten
|
|
462
|
+
const SOURCEKITTEN_BIN = resolvePluginBinary("sourcekitten", pluginRuntime);
|
|
463
|
+
|
|
464
|
+
// TODO: Move the lists to a config file
|
|
465
|
+
const COMMON_RUNTIMES = [
|
|
466
|
+
"java",
|
|
467
|
+
"node",
|
|
468
|
+
"nodejs",
|
|
469
|
+
"nodejs-current",
|
|
470
|
+
"deno",
|
|
471
|
+
"bun",
|
|
472
|
+
"python",
|
|
473
|
+
"python3",
|
|
474
|
+
"ruby",
|
|
475
|
+
"ruby3",
|
|
476
|
+
"php",
|
|
477
|
+
"php7",
|
|
478
|
+
"php8",
|
|
479
|
+
"perl",
|
|
480
|
+
"openjdk",
|
|
481
|
+
"openjdk8",
|
|
482
|
+
"openjdk11",
|
|
483
|
+
"openjdk17",
|
|
484
|
+
"openjdk21",
|
|
485
|
+
"openjdk22",
|
|
486
|
+
"openjdk23",
|
|
487
|
+
"openjdk24",
|
|
488
|
+
"openjdk25",
|
|
489
|
+
"openjdk8-jdk",
|
|
490
|
+
"openjdk11-jdk",
|
|
491
|
+
"openjdk17-jdk",
|
|
492
|
+
"openjdk21-jdk",
|
|
493
|
+
"openjdk22-jdk",
|
|
494
|
+
"openjdk23-jdk",
|
|
495
|
+
"openjdk24-jdk",
|
|
496
|
+
"openjdk25-jdk",
|
|
497
|
+
"openjdk8-jre",
|
|
498
|
+
"openjdk11-jre",
|
|
499
|
+
"openjdk17-jre",
|
|
500
|
+
"openjdk21-jre",
|
|
501
|
+
"openjdk22-jre",
|
|
502
|
+
"openjdk23-jre",
|
|
503
|
+
"openjdk24-jre",
|
|
504
|
+
"openjdk25-jre",
|
|
505
|
+
];
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Run the `cargo-auditable` companion binary against the given source path and
|
|
509
|
+
* return its stdout.
|
|
510
|
+
*
|
|
511
|
+
* @param {string} src Path to the Rust binary or source to inspect.
|
|
512
|
+
* @returns {string|undefined} The tool's stdout, or undefined when the binary
|
|
513
|
+
* is unavailable or produced no output.
|
|
514
|
+
*/
|
|
515
|
+
export function getCargoAuditableInfo(src) {
|
|
516
|
+
if (CARGO_AUDITABLE_BIN) {
|
|
517
|
+
const result = safeSpawnSync(CARGO_AUDITABLE_BIN, [src]);
|
|
518
|
+
if (result.status !== 0 || result.error) {
|
|
519
|
+
if (result.stdout || result.stderr) {
|
|
520
|
+
console.error(result.stdout, result.stderr);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (result) {
|
|
524
|
+
const stdout = result.stdout;
|
|
525
|
+
if (stdout) {
|
|
526
|
+
return stdout;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return undefined;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Execute sourcekitten plugin with the given arguments
|
|
535
|
+
*
|
|
536
|
+
* @param args {Array} Arguments
|
|
537
|
+
* @returns {undefined|Object} Command output
|
|
538
|
+
*/
|
|
539
|
+
export function executeSourcekitten(args) {
|
|
540
|
+
if (SOURCEKITTEN_BIN) {
|
|
541
|
+
const result = safeSpawnSync(SOURCEKITTEN_BIN, args);
|
|
542
|
+
if (result.status !== 0 || result.error) {
|
|
543
|
+
if (result.stdout || result.stderr) {
|
|
544
|
+
console.error(result.stdout, result.stderr);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
if (result) {
|
|
548
|
+
const stdout = result.stdout;
|
|
549
|
+
if (stdout) {
|
|
550
|
+
return JSON.parse(stdout);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return undefined;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Canonicalise the distro vendor namespace of an OS package component
|
|
559
|
+
* produced by trivy, regardless of whether trivy expressed the vendor via the
|
|
560
|
+
* purl namespace or the component group (it is inconsistent between
|
|
561
|
+
* packages). e.g. pkg:rpm/alma/... -> pkg:rpm/almalinux/... and
|
|
562
|
+
* pkg:rpm/cbl-mariner/... -> pkg:rpm/azure-linux/... so the purls match the
|
|
563
|
+
* vulnerability feeds. Also aligns the ``distro`` qualifier vendor prefix
|
|
564
|
+
* (alma-9.8 -> almalinux-9.8, mariner-2.0 -> azure-linux-2.0), including
|
|
565
|
+
* spellings that disagree with the namespace, and rebuilds ``group`` and
|
|
566
|
+
* ``bom-ref`` from the rewritten purl.
|
|
567
|
+
*
|
|
568
|
+
* The namespace table is OS_NAMESPACE_ALIAS from lib/inventory/osinfo.js so
|
|
569
|
+
* the trivy path and the os-release path (getDistroInfo) cannot drift apart.
|
|
570
|
+
*
|
|
571
|
+
* A `distro` qualifier that is not purl-safe (trivy emits Amazon Linux as
|
|
572
|
+
* `amazon-2023.12.20260727+(Amazon Linux)`) is replaced with the os-release
|
|
573
|
+
* derived value, or dropped when there is none: an unusable qualifier
|
|
574
|
+
* invalidates the purl and fails validation for the whole BOM.
|
|
575
|
+
*
|
|
576
|
+
* @param {Object} comp trivy OS package component (mutated in place)
|
|
577
|
+
* @param {Object} purlObj parsed PackageURL of comp.purl (mutated in place)
|
|
578
|
+
* @param {string} name component package name (basename form)
|
|
579
|
+
* @param {string} group component group derived from the trivy component name
|
|
580
|
+
* @param {string} [distroId] canonical os-release distro id, e.g. "amazon-2023"
|
|
581
|
+
* @returns {string} the canonicalised group
|
|
582
|
+
*/
|
|
583
|
+
export function canonicaliseOsPackageNamespace(
|
|
584
|
+
comp,
|
|
585
|
+
purlObj,
|
|
586
|
+
name,
|
|
587
|
+
group,
|
|
588
|
+
distroId,
|
|
589
|
+
) {
|
|
590
|
+
if (!purlObj || !OS_PURL_TYPES.includes(purlObj.type)) {
|
|
591
|
+
return group;
|
|
592
|
+
}
|
|
593
|
+
const os_ns = purlObj.namespace || group || "";
|
|
594
|
+
const os_ns_alias = OS_NAMESPACE_ALIAS[os_ns];
|
|
595
|
+
const distroQual = purlObj.qualifiers?.distro;
|
|
596
|
+
let canonicalDistroQual = distroQual;
|
|
597
|
+
if (os_ns_alias && distroQual?.startsWith(`${os_ns}-`)) {
|
|
598
|
+
canonicalDistroQual = `${os_ns_alias}-${distroQual.slice(os_ns.length + 1)}`;
|
|
599
|
+
}
|
|
600
|
+
canonicalDistroQual = canonicalDistroQualifier(canonicalDistroQual);
|
|
601
|
+
if (canonicalDistroQual && !isCleanDistroQualifier(canonicalDistroQual)) {
|
|
602
|
+
canonicalDistroQual = isCleanDistroQualifier(distroId)
|
|
603
|
+
? distroId
|
|
604
|
+
: undefined;
|
|
605
|
+
}
|
|
606
|
+
if (!os_ns_alias && canonicalDistroQual === distroQual) {
|
|
607
|
+
return group;
|
|
608
|
+
}
|
|
609
|
+
group = os_ns_alias || purlObj.namespace || group || "";
|
|
610
|
+
comp.group = group;
|
|
611
|
+
purlObj.namespace = group;
|
|
612
|
+
if (canonicalDistroQual !== distroQual) {
|
|
613
|
+
if (canonicalDistroQual) {
|
|
614
|
+
purlObj.qualifiers.distro = canonicalDistroQual;
|
|
615
|
+
} else {
|
|
616
|
+
delete purlObj.qualifiers.distro;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
comp.purl = build({
|
|
620
|
+
type: purlObj.type,
|
|
621
|
+
namespace: purlObj.namespace || null,
|
|
622
|
+
name: name,
|
|
623
|
+
version: purlObj.version || null,
|
|
624
|
+
qualifiers: purlObj.qualifiers || null,
|
|
625
|
+
subpath: purlObj.subpath || null,
|
|
626
|
+
});
|
|
627
|
+
comp["bom-ref"] = decodeURIComponent(comp.purl);
|
|
628
|
+
return group;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Repair a `distro` qualifier that cannot survive purl parsing, in place.
|
|
633
|
+
*
|
|
634
|
+
* trivy stamps Amazon Linux with the full pretty version — the qualifier
|
|
635
|
+
* arrives as `distro=amazon-2023.12.20260727+%28Amazon+Linux%29`, whose spaces
|
|
636
|
+
* and parentheses `Purl.parse` rejects outright ("Invalid character in
|
|
637
|
+
* qualifier distro"). The component then bypasses every canonicalisation below
|
|
638
|
+
* (they all need a parsed purl) and reaches the BOM verbatim, where schema
|
|
639
|
+
* validation fails and the *entire* SBOM is discarded — an Amazon Linux scan
|
|
640
|
+
* produces no output at all today.
|
|
641
|
+
*
|
|
642
|
+
* The release is already known from os-release, so the unusable value is
|
|
643
|
+
* swapped for it; with no usable replacement the qualifier is dropped, since a
|
|
644
|
+
* missing qualifier costs one matching hint while an invalid one costs the
|
|
645
|
+
* whole document.
|
|
646
|
+
*
|
|
647
|
+
* @param {Object} comp component whose purl may carry an unusable qualifier
|
|
648
|
+
* @param {string} [distroId] canonical os-release distro id, e.g. "amazon-2023"
|
|
649
|
+
* @returns {Object} the same component
|
|
650
|
+
*/
|
|
651
|
+
export function repairOsComponentPurl(comp, distroId) {
|
|
652
|
+
const purl = comp?.purl;
|
|
653
|
+
if (!purl?.includes("distro=")) {
|
|
654
|
+
return comp;
|
|
655
|
+
}
|
|
656
|
+
const match = /([?&]distro=)([^&]*)/.exec(purl);
|
|
657
|
+
if (!match) {
|
|
658
|
+
return comp;
|
|
659
|
+
}
|
|
660
|
+
let decoded = match[2];
|
|
661
|
+
try {
|
|
662
|
+
decoded = decodeURIComponent(match[2].replace(/\+/g, " "));
|
|
663
|
+
} catch (_err) {
|
|
664
|
+
// A value that cannot even be decoded is definitely not purl-safe
|
|
665
|
+
}
|
|
666
|
+
if (isCleanDistroQualifier(decoded)) {
|
|
667
|
+
return comp;
|
|
668
|
+
}
|
|
669
|
+
const replacement = isCleanDistroQualifier(distroId)
|
|
670
|
+
? `${match[1]}${distroId}`
|
|
671
|
+
: "";
|
|
672
|
+
comp.purl = purl.replace(match[0], replacement).replace(/\?&/, "?");
|
|
673
|
+
if (comp.purl.endsWith("?")) {
|
|
674
|
+
comp.purl = comp.purl.slice(0, -1);
|
|
675
|
+
}
|
|
676
|
+
comp["bom-ref"] = decodeURIComponent(comp.purl);
|
|
677
|
+
return comp;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Drop source-package shadow components that duplicate a real installed
|
|
682
|
+
* package, in place.
|
|
683
|
+
*
|
|
684
|
+
* A shadow is emitted whenever an rpm/deb declares a source name different
|
|
685
|
+
* from its own (bzip2-libs -> bzip2) so that scanners keying on source names
|
|
686
|
+
* still match. When the source package is *itself* installed at the same
|
|
687
|
+
* version, the shadow's purl is identical to the real component's and the two
|
|
688
|
+
* collapse in trimComponents() — which keeps whichever was seen first and
|
|
689
|
+
* never merges `tags`. Seen-first is trivy's listing order, so the outcome is
|
|
690
|
+
* arbitrary: when the shadow wins, a genuinely installed package is reported
|
|
691
|
+
* with `tags: ["source"]` and identity confidence 0, the very marker the
|
|
692
|
+
* shadow carries so consumers can filter shadows out. That turns a real
|
|
693
|
+
* package into a filtered-away one.
|
|
694
|
+
*
|
|
695
|
+
* Dropping the redundant shadow is loss-free: it duplicates the real
|
|
696
|
+
* component's purl (so nothing that matches on purl changes) and its bom-ref
|
|
697
|
+
* is byte-identical, so no dependency reference can dangle.
|
|
698
|
+
*
|
|
699
|
+
* @param {Array} pkgList component list, mutated in place
|
|
700
|
+
* @returns {Array} the same list, for convenience
|
|
701
|
+
*/
|
|
702
|
+
export function dropRedundantSourceComponents(pkgList) {
|
|
703
|
+
if (!Array.isArray(pkgList) || !pkgList.length) {
|
|
704
|
+
return pkgList;
|
|
705
|
+
}
|
|
706
|
+
const installedPurls = new Set();
|
|
707
|
+
for (const comp of pkgList) {
|
|
708
|
+
if (comp?.purl && !comp?.tags?.includes("source")) {
|
|
709
|
+
installedPurls.add(comp.purl);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
if (!installedPurls.size) {
|
|
713
|
+
return pkgList;
|
|
714
|
+
}
|
|
715
|
+
for (let i = pkgList.length - 1; i >= 0; i--) {
|
|
716
|
+
const comp = pkgList[i];
|
|
717
|
+
if (comp?.tags?.includes("source") && installedPurls.has(comp.purl)) {
|
|
718
|
+
pkgList.splice(i, 1);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return pkgList;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Get the packages installed in the container image filesystem.
|
|
726
|
+
*
|
|
727
|
+
* @param src {String} Source directory containing the extracted filesystem.
|
|
728
|
+
* @param imageConfig {Object} Image configuration containing environment variables, command, entrypoints etc
|
|
729
|
+
* @param options CLI options controlling inventory generation
|
|
730
|
+
*
|
|
731
|
+
* @returns {Object} Metadata containing packages, dependencies, etc
|
|
732
|
+
*/
|
|
733
|
+
export async function getOSPackages(src, imageConfig, options = {}) {
|
|
734
|
+
if (isDryRun) {
|
|
735
|
+
recordActivity({
|
|
736
|
+
kind: "container",
|
|
737
|
+
reason:
|
|
738
|
+
"Dry run mode blocks Trivy-based OS package generation because it executes external tools and writes temporary output.",
|
|
739
|
+
status: "blocked",
|
|
740
|
+
target: src,
|
|
741
|
+
});
|
|
742
|
+
return {
|
|
743
|
+
allTypes: new Set(),
|
|
744
|
+
binPaths: [],
|
|
745
|
+
bundledRuntimes: new Set(),
|
|
746
|
+
bundledSdks: new Set(),
|
|
747
|
+
dependenciesList: [],
|
|
748
|
+
executables: [],
|
|
749
|
+
osPackages: [],
|
|
750
|
+
osPackageFiles: [],
|
|
751
|
+
sharedLibs: [],
|
|
752
|
+
services: [],
|
|
753
|
+
tools: [],
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
const pkgList = [];
|
|
757
|
+
const osPackageEntries = [];
|
|
758
|
+
const dependenciesList = [];
|
|
759
|
+
const allTypes = new Set();
|
|
760
|
+
const bundledSdks = new Set();
|
|
761
|
+
const bundledRuntimes = new Set();
|
|
762
|
+
let osPackageFiles = [];
|
|
763
|
+
let services = [];
|
|
764
|
+
let tools = [];
|
|
765
|
+
let binPaths = extractPathEnv(imageConfig?.Env);
|
|
766
|
+
if (!binPaths?.length) {
|
|
767
|
+
const rootBinPaths = getDirs(src, "{sbin,bin}", true, false);
|
|
768
|
+
const usrBinPaths = getDirs(
|
|
769
|
+
src,
|
|
770
|
+
"/{app,opt,usr,home}/**/{sbin,bin}",
|
|
771
|
+
true,
|
|
772
|
+
true,
|
|
773
|
+
);
|
|
774
|
+
binPaths = Array.from(
|
|
775
|
+
new Set(
|
|
776
|
+
rootBinPaths
|
|
777
|
+
.concat(usrBinPaths)
|
|
778
|
+
.map((f) => relative(src, f))
|
|
779
|
+
.filter(Boolean),
|
|
780
|
+
),
|
|
781
|
+
).sort();
|
|
782
|
+
if (DEBUG_MODE && binPaths.length) {
|
|
783
|
+
console.log(
|
|
784
|
+
`Falling back to inferred binary paths for ${src}: ${binPaths.join(", ")}`,
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
if (TRIVY_BIN) {
|
|
789
|
+
let imageType = "image";
|
|
790
|
+
const trivyCacheDir = join(homedir(), ".cache", "trivy");
|
|
791
|
+
try {
|
|
792
|
+
safeMkdirSync(join(trivyCacheDir, "db"), { recursive: true });
|
|
793
|
+
safeMkdirSync(join(trivyCacheDir, "java-db"), { recursive: true });
|
|
794
|
+
} catch (_err) {
|
|
795
|
+
// ignore errors
|
|
796
|
+
}
|
|
797
|
+
if (safeExistsSync(src)) {
|
|
798
|
+
imageType = "rootfs";
|
|
799
|
+
}
|
|
800
|
+
const tempDir = safeMkdtempSync(join(getTmpDir(), "trivy-cdxgen-"));
|
|
801
|
+
const bomJsonFile = join(tempDir, "trivy-bom.json");
|
|
802
|
+
const args = [
|
|
803
|
+
imageType,
|
|
804
|
+
"--cache-dir",
|
|
805
|
+
trivyCacheDir,
|
|
806
|
+
"--output",
|
|
807
|
+
bomJsonFile,
|
|
808
|
+
];
|
|
809
|
+
if (DEBUG_MODE) {
|
|
810
|
+
args.push("--debug");
|
|
811
|
+
}
|
|
812
|
+
args.push(src);
|
|
813
|
+
if (DEBUG_MODE) {
|
|
814
|
+
console.log("Executing", TRIVY_BIN, args.join(" "));
|
|
815
|
+
}
|
|
816
|
+
const result = safeSpawnSync(TRIVY_BIN, args);
|
|
817
|
+
if (result.status !== 0 || result.error) {
|
|
818
|
+
if (result.stdout || result.stderr) {
|
|
819
|
+
console.error(result.stdout, result.stderr);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
if (safeExistsSync(bomJsonFile)) {
|
|
823
|
+
let tmpBom = {};
|
|
824
|
+
try {
|
|
825
|
+
tmpBom = JSON.parse(
|
|
826
|
+
readFileSync(bomJsonFile, {
|
|
827
|
+
encoding: "utf-8",
|
|
828
|
+
}),
|
|
829
|
+
);
|
|
830
|
+
} catch (_e) {
|
|
831
|
+
// ignore errors
|
|
832
|
+
}
|
|
833
|
+
// Clean up
|
|
834
|
+
if (tempDir?.startsWith(getTmpDir())) {
|
|
835
|
+
if (DEBUG_MODE) {
|
|
836
|
+
console.log(`Cleaning up ${tempDir}`);
|
|
837
|
+
}
|
|
838
|
+
safeRmSync(tempDir, { recursive: true, force: true });
|
|
839
|
+
}
|
|
840
|
+
const osReleaseData = readOsRelease(src);
|
|
841
|
+
if (DEBUG_MODE) {
|
|
842
|
+
console.log(osReleaseData);
|
|
843
|
+
}
|
|
844
|
+
// distro_name must be a real release codename/token (bookworm, jammy,
|
|
845
|
+
// rhel-9, alpine-3.19), never a distro *display name*. VERSION_CODENAME is
|
|
846
|
+
// authoritative when present; otherwise the RHEL/CentOS product-name
|
|
847
|
+
// fallbacks are only accepted when they resolve through OS_DISTRO_ALIAS
|
|
848
|
+
// (e.g. "red hat enterprise linux 9" -> "rhel-9"). Display names such as
|
|
849
|
+
// "Rocky Linux" or "AlmaLinux" have no alias entry and are dropped so we
|
|
850
|
+
// never emit a bogus distro_name qualifier (RPM distros track packages
|
|
851
|
+
// flat, without a release segment, so folding a display name breaks
|
|
852
|
+
// downstream vuln-db matching).
|
|
853
|
+
let distro_codename = (
|
|
854
|
+
osReleaseData["VERSION_CODENAME"] || ""
|
|
855
|
+
).toLowerCase();
|
|
856
|
+
if (!distro_codename) {
|
|
857
|
+
const distro_display_name = (
|
|
858
|
+
osReleaseData["CENTOS_MANTISBT_PROJECT"] ||
|
|
859
|
+
osReleaseData["REDHAT_BUGZILLA_PRODUCT"] ||
|
|
860
|
+
osReleaseData["REDHAT_SUPPORT_PRODUCT"] ||
|
|
861
|
+
""
|
|
862
|
+
).toLowerCase();
|
|
863
|
+
if (distro_display_name && OS_DISTRO_ALIAS[distro_display_name]) {
|
|
864
|
+
distro_codename = OS_DISTRO_ALIAS[distro_display_name];
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
let distro_id = osReleaseData["ID"] || "";
|
|
868
|
+
const distro_id_like = osReleaseData["ID_LIKE"] || "";
|
|
869
|
+
let purl_type = "rpm";
|
|
870
|
+
switch (distro_id) {
|
|
871
|
+
case "debian":
|
|
872
|
+
case "ubuntu":
|
|
873
|
+
case "pop":
|
|
874
|
+
purl_type = "deb";
|
|
875
|
+
break;
|
|
876
|
+
case "sles":
|
|
877
|
+
case "suse":
|
|
878
|
+
case "opensuse":
|
|
879
|
+
purl_type = "rpm";
|
|
880
|
+
break;
|
|
881
|
+
case "alpine":
|
|
882
|
+
purl_type = "apk";
|
|
883
|
+
if (osReleaseData.VERSION_ID) {
|
|
884
|
+
const versionParts = osReleaseData["VERSION_ID"].split(".");
|
|
885
|
+
if (versionParts.length >= 2) {
|
|
886
|
+
distro_codename = `alpine-${versionParts[0]}.${versionParts[1]}`;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
break;
|
|
890
|
+
default:
|
|
891
|
+
if (distro_id_like.includes("debian")) {
|
|
892
|
+
purl_type = "deb";
|
|
893
|
+
} else if (
|
|
894
|
+
distro_id_like.includes("rhel") ||
|
|
895
|
+
distro_id_like.includes("centos") ||
|
|
896
|
+
distro_id_like.includes("fedora")
|
|
897
|
+
) {
|
|
898
|
+
purl_type = "rpm";
|
|
899
|
+
}
|
|
900
|
+
break;
|
|
901
|
+
}
|
|
902
|
+
if (osReleaseData["VERSION_ID"]) {
|
|
903
|
+
distro_id = `${distro_id}-${osReleaseData["VERSION_ID"]}`;
|
|
904
|
+
if (OS_DISTRO_ALIAS[distro_id]) {
|
|
905
|
+
distro_codename = OS_DISTRO_ALIAS[distro_id];
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
// Canonicalise the distro qualifier vendor (e.g. "mariner-2.0" ->
|
|
909
|
+
// "azure-linux-2.0") so every spelling of one release yields a single
|
|
910
|
+
// qualifier value. This runs before distro_id is used to overwrite
|
|
911
|
+
// trivy's own qualifier below.
|
|
912
|
+
distro_id = canonicalDistroQualifier(distro_id);
|
|
913
|
+
const tmpDependencies = {};
|
|
914
|
+
tools = enrichToolComponents(
|
|
915
|
+
(Array.isArray(tmpBom?.metadata?.tools)
|
|
916
|
+
? tmpBom.metadata.tools
|
|
917
|
+
: tmpBom?.metadata?.tools?.components || []
|
|
918
|
+
).filter((tool) => tool?.["bom-ref"] && tool?.name !== "cdxgen"),
|
|
919
|
+
["trivy"].concat(imageType === "rootfs" ? ["trustinspector"] : []),
|
|
920
|
+
);
|
|
921
|
+
const toolRefs = extractToolRefs(
|
|
922
|
+
{ components: tools },
|
|
923
|
+
(tool) => tool?.name !== "cdxgen",
|
|
924
|
+
);
|
|
925
|
+
(tmpBom.dependencies || []).forEach((d) => {
|
|
926
|
+
tmpDependencies[d.ref] = d.dependsOn;
|
|
927
|
+
});
|
|
928
|
+
if (tmpBom?.components) {
|
|
929
|
+
for (const comp of tmpBom.components) {
|
|
930
|
+
if (comp.purl) {
|
|
931
|
+
const origBomRef = comp["bom-ref"];
|
|
932
|
+
// Fix the group
|
|
933
|
+
let group = dirname(comp.name);
|
|
934
|
+
const name = basename(comp.name);
|
|
935
|
+
let purlObj;
|
|
936
|
+
if (group === ".") {
|
|
937
|
+
group = "";
|
|
938
|
+
}
|
|
939
|
+
comp.group = group;
|
|
940
|
+
comp.name = name;
|
|
941
|
+
const modularityLabel = extractModularityLabel(comp);
|
|
942
|
+
repairOsComponentPurl(comp, distro_id);
|
|
943
|
+
try {
|
|
944
|
+
purlObj = Purl.parse(comp.purl);
|
|
945
|
+
purlObj.qualifiers = purlObj.qualifiers || {};
|
|
946
|
+
} catch (_err) {
|
|
947
|
+
// continue regardless of error
|
|
948
|
+
}
|
|
949
|
+
// Canonicalise the distro vendor namespace (see
|
|
950
|
+
// canonicaliseOsPackageNamespace) before any later bom-ref
|
|
951
|
+
// rebuild so the rewritten purl, group and bom-ref stay in sync.
|
|
952
|
+
group = canonicaliseOsPackageNamespace(
|
|
953
|
+
comp,
|
|
954
|
+
purlObj,
|
|
955
|
+
name,
|
|
956
|
+
group,
|
|
957
|
+
distro_id,
|
|
958
|
+
);
|
|
959
|
+
if (
|
|
960
|
+
group === "" &&
|
|
961
|
+
purlObj &&
|
|
962
|
+
OS_PURL_TYPES.includes(purlObj["type"])
|
|
963
|
+
) {
|
|
964
|
+
try {
|
|
965
|
+
if (purlObj?.namespace && purlObj.namespace !== "") {
|
|
966
|
+
group =
|
|
967
|
+
OS_NAMESPACE_ALIAS[purlObj.namespace] || purlObj.namespace;
|
|
968
|
+
comp.group = group;
|
|
969
|
+
purlObj.namespace = group;
|
|
970
|
+
}
|
|
971
|
+
if (distro_id?.length) {
|
|
972
|
+
purlObj.qualifiers["distro"] = distro_id;
|
|
973
|
+
}
|
|
974
|
+
if (distro_codename?.length) {
|
|
975
|
+
purlObj.qualifiers["distro_name"] = distro_codename;
|
|
976
|
+
}
|
|
977
|
+
// Bug fix for mageia and oracle linux
|
|
978
|
+
// Type is being returned as none for ubuntu as well!
|
|
979
|
+
if (purlObj?.type === "none") {
|
|
980
|
+
purlObj["type"] = purl_type;
|
|
981
|
+
purlObj["namespace"] = "";
|
|
982
|
+
comp.group = "";
|
|
983
|
+
if (comp.purl?.includes(".mga")) {
|
|
984
|
+
purlObj["namespace"] = "mageia";
|
|
985
|
+
comp.group = "mageia";
|
|
986
|
+
purlObj.qualifiers["distro"] = "mageia";
|
|
987
|
+
distro_codename = "mga";
|
|
988
|
+
}
|
|
989
|
+
comp.purl = build({
|
|
990
|
+
type: purlObj.type,
|
|
991
|
+
namespace: purlObj.namespace || null,
|
|
992
|
+
name: name,
|
|
993
|
+
version: purlObj.version || null,
|
|
994
|
+
qualifiers: purlObj.qualifiers || null,
|
|
995
|
+
subpath: purlObj.subpath || null,
|
|
996
|
+
});
|
|
997
|
+
comp["bom-ref"] = decodeURIComponent(comp.purl);
|
|
998
|
+
}
|
|
999
|
+
if (purlObj?.type !== "none") {
|
|
1000
|
+
allTypes.add(purlObj.type);
|
|
1001
|
+
}
|
|
1002
|
+
// Prefix distro codename for ubuntu
|
|
1003
|
+
if (purlObj?.qualifiers?.distro) {
|
|
1004
|
+
allTypes.add(purlObj.qualifiers.distro);
|
|
1005
|
+
if (OS_DISTRO_ALIAS[purlObj.qualifiers.distro]) {
|
|
1006
|
+
distro_codename =
|
|
1007
|
+
OS_DISTRO_ALIAS[purlObj.qualifiers.distro];
|
|
1008
|
+
} else if (group === "alpine") {
|
|
1009
|
+
const dtmpA = purlObj.qualifiers.distro.split(".");
|
|
1010
|
+
if (dtmpA && dtmpA.length > 2) {
|
|
1011
|
+
distro_codename = `${dtmpA[0]}.${dtmpA[1]}`;
|
|
1012
|
+
}
|
|
1013
|
+
} else if (group === "photon") {
|
|
1014
|
+
const dtmpA = purlObj.qualifiers.distro.split("-");
|
|
1015
|
+
if (dtmpA && dtmpA.length > 1) {
|
|
1016
|
+
distro_codename = dtmpA[0];
|
|
1017
|
+
}
|
|
1018
|
+
} else if (group === "redhat") {
|
|
1019
|
+
const dtmpA = purlObj.qualifiers.distro.split(".");
|
|
1020
|
+
if (dtmpA && dtmpA.length > 1) {
|
|
1021
|
+
distro_codename = dtmpA[0].replace(
|
|
1022
|
+
"redhat",
|
|
1023
|
+
"enterprise_linux",
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
if (distro_codename !== "") {
|
|
1029
|
+
allTypes.add(distro_codename);
|
|
1030
|
+
allTypes.add(purlObj.namespace);
|
|
1031
|
+
comp.purl = build({
|
|
1032
|
+
type: purlObj.type,
|
|
1033
|
+
namespace: purlObj.namespace || null,
|
|
1034
|
+
name: name,
|
|
1035
|
+
version: purlObj.version || null,
|
|
1036
|
+
qualifiers: purlObj.qualifiers || null,
|
|
1037
|
+
subpath: purlObj.subpath || null,
|
|
1038
|
+
});
|
|
1039
|
+
comp["bom-ref"] = decodeURIComponent(comp.purl);
|
|
1040
|
+
}
|
|
1041
|
+
} catch (_err) {
|
|
1042
|
+
// continue regardless of error
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
if (comp.purl.includes("epoch=")) {
|
|
1046
|
+
try {
|
|
1047
|
+
const epoch = purlObj.qualifiers?.epoch;
|
|
1048
|
+
// trivy seems to be removing the epoch from the version and moving it to a qualifier
|
|
1049
|
+
// let's fix this hack to improve confidence.
|
|
1050
|
+
if (epoch) {
|
|
1051
|
+
purlObj.version = `${epoch}:${purlObj.version}`;
|
|
1052
|
+
comp.version = purlObj.version;
|
|
1053
|
+
}
|
|
1054
|
+
comp.evidence = {
|
|
1055
|
+
identity: [
|
|
1056
|
+
{
|
|
1057
|
+
field: "purl",
|
|
1058
|
+
confidence: 1,
|
|
1059
|
+
methods: [
|
|
1060
|
+
{
|
|
1061
|
+
technique: "other",
|
|
1062
|
+
confidence: 1,
|
|
1063
|
+
value: comp.purl,
|
|
1064
|
+
},
|
|
1065
|
+
],
|
|
1066
|
+
},
|
|
1067
|
+
],
|
|
1068
|
+
};
|
|
1069
|
+
if (distro_id?.length) {
|
|
1070
|
+
purlObj.qualifiers["distro"] = distro_id;
|
|
1071
|
+
}
|
|
1072
|
+
if (distro_codename?.length) {
|
|
1073
|
+
purlObj.qualifiers["distro_name"] = distro_codename;
|
|
1074
|
+
}
|
|
1075
|
+
allTypes.add(purlObj.namespace);
|
|
1076
|
+
comp.purl = build({
|
|
1077
|
+
type: purlObj.type,
|
|
1078
|
+
namespace: purlObj.namespace || null,
|
|
1079
|
+
name: name,
|
|
1080
|
+
version: purlObj.version || null,
|
|
1081
|
+
qualifiers: purlObj.qualifiers || null,
|
|
1082
|
+
subpath: purlObj.subpath || null,
|
|
1083
|
+
});
|
|
1084
|
+
comp["bom-ref"] = decodeURIComponent(comp.purl);
|
|
1085
|
+
} catch (err) {
|
|
1086
|
+
// continue regardless of error
|
|
1087
|
+
console.log(err);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
attachIdentityTools(comp, toolRefs);
|
|
1091
|
+
// Fix licenses
|
|
1092
|
+
if (
|
|
1093
|
+
comp.licenses &&
|
|
1094
|
+
Array.isArray(comp.licenses) &&
|
|
1095
|
+
comp.licenses.length
|
|
1096
|
+
) {
|
|
1097
|
+
const newLicenses = [];
|
|
1098
|
+
for (const aLic of comp.licenses) {
|
|
1099
|
+
if (aLic?.license?.name) {
|
|
1100
|
+
if (isSpdxLicenseExpression(aLic.license.name)) {
|
|
1101
|
+
newLicenses.push({ expression: aLic.license.name });
|
|
1102
|
+
} else {
|
|
1103
|
+
const possibleId = findLicenseId(aLic.license.name);
|
|
1104
|
+
if (possibleId !== aLic.license.name) {
|
|
1105
|
+
newLicenses.push({ license: { id: possibleId } });
|
|
1106
|
+
} else {
|
|
1107
|
+
newLicenses.push({
|
|
1108
|
+
license: { name: aLic.license.name },
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
} else if (
|
|
1113
|
+
aLic?.license &&
|
|
1114
|
+
Object.keys(aLic).length &&
|
|
1115
|
+
Object.keys(aLic.license).length
|
|
1116
|
+
) {
|
|
1117
|
+
newLicenses.push(aLic);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
comp.licenses = adjustLicenseInformation(newLicenses);
|
|
1121
|
+
}
|
|
1122
|
+
// Fix hashes
|
|
1123
|
+
if (
|
|
1124
|
+
comp.hashes &&
|
|
1125
|
+
Array.isArray(comp.hashes) &&
|
|
1126
|
+
comp.hashes.length
|
|
1127
|
+
) {
|
|
1128
|
+
const hashContent = comp.hashes[0].content;
|
|
1129
|
+
if (!hashContent || hashContent.length < 32) {
|
|
1130
|
+
delete comp.hashes;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
const compProperties = comp.properties;
|
|
1134
|
+
const trivyMetadata = extractTrivyOsPackageMetadata(compProperties);
|
|
1135
|
+
const fallbackIdentityProperties = promoteTrivyOsPackageIdentity(
|
|
1136
|
+
comp,
|
|
1137
|
+
trivyMetadata,
|
|
1138
|
+
);
|
|
1139
|
+
let { srcName, srcVersion, srcRelease, epoch } = trivyMetadata;
|
|
1140
|
+
// See issue #2067
|
|
1141
|
+
if (srcVersion && srcRelease) {
|
|
1142
|
+
srcVersion = `${srcVersion}-${srcRelease}`;
|
|
1143
|
+
}
|
|
1144
|
+
if (epoch) {
|
|
1145
|
+
srcVersion = `${epoch}:${srcVersion}`;
|
|
1146
|
+
}
|
|
1147
|
+
const labelProperties = modularityLabel
|
|
1148
|
+
? [{ name: "cdx:os:modularityLabel", value: modularityLabel }]
|
|
1149
|
+
: [];
|
|
1150
|
+
if (
|
|
1151
|
+
trivyMetadata.retainedProperties.length ||
|
|
1152
|
+
fallbackIdentityProperties.length ||
|
|
1153
|
+
labelProperties.length
|
|
1154
|
+
) {
|
|
1155
|
+
comp.properties = uniqueProperties(
|
|
1156
|
+
trivyMetadata.retainedProperties
|
|
1157
|
+
.concat(fallbackIdentityProperties)
|
|
1158
|
+
.concat(labelProperties),
|
|
1159
|
+
);
|
|
1160
|
+
} else {
|
|
1161
|
+
delete comp.properties;
|
|
1162
|
+
}
|
|
1163
|
+
// A golang purl carries the module path in its namespace, and the
|
|
1164
|
+
// Go standard library has no module path — trivy reports it as the
|
|
1165
|
+
// bare name `stdlib`, which is not a valid purl. The component is
|
|
1166
|
+
// worth keeping, since the toolchain version is a real signal, so
|
|
1167
|
+
// the purl is dropped rather than given an invented namespace.
|
|
1168
|
+
if (
|
|
1169
|
+
comp.purl?.startsWith("pkg:golang/") &&
|
|
1170
|
+
!tryBuildPurl({
|
|
1171
|
+
type: "golang",
|
|
1172
|
+
namespace: comp.group || null,
|
|
1173
|
+
name: comp.name,
|
|
1174
|
+
version: comp.version || null,
|
|
1175
|
+
})
|
|
1176
|
+
) {
|
|
1177
|
+
comp["bom-ref"] = decodeURIComponent(comp.purl);
|
|
1178
|
+
delete comp.purl;
|
|
1179
|
+
}
|
|
1180
|
+
// Bug fix: We can get bom-ref like this: pkg:rpm/sles/libstdc%2B%2B6@14.2.0+git10526-150000.1.6.1?arch=x86_64&distro=sles-15.5
|
|
1181
|
+
if (
|
|
1182
|
+
comp["bom-ref"] &&
|
|
1183
|
+
comp.purl &&
|
|
1184
|
+
comp["bom-ref"] !== decodeURIComponent(comp.purl)
|
|
1185
|
+
) {
|
|
1186
|
+
comp["bom-ref"] = decodeURIComponent(comp.purl);
|
|
1187
|
+
}
|
|
1188
|
+
pkgList.push(comp);
|
|
1189
|
+
if (trivyMetadata.installedFiles.length) {
|
|
1190
|
+
osPackageEntries.push({
|
|
1191
|
+
capabilities: trivyMetadata.capabilities,
|
|
1192
|
+
commandPaths: trivyMetadata.installedCommandPaths,
|
|
1193
|
+
commands: trivyMetadata.installedCommands,
|
|
1194
|
+
files: trivyMetadata.installedFiles,
|
|
1195
|
+
packageName: comp.name,
|
|
1196
|
+
packageRef: comp["bom-ref"],
|
|
1197
|
+
packageVersion: comp.version,
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
detectSdksRuntimes(comp, bundledSdks, bundledRuntimes);
|
|
1201
|
+
const compDeps = retrieveDependencies(
|
|
1202
|
+
tmpDependencies,
|
|
1203
|
+
origBomRef,
|
|
1204
|
+
comp,
|
|
1205
|
+
);
|
|
1206
|
+
if (compDeps) {
|
|
1207
|
+
dependenciesList.push(compDeps);
|
|
1208
|
+
}
|
|
1209
|
+
// HACK: Many vulnerability databases, including vdb, track vulnerabilities based on source package names :(
|
|
1210
|
+
// If there is a source package defined we include it as well to make such SCA scanners work.
|
|
1211
|
+
// As a compromise, we reduce the confidence to zero so that there is a way to filter these out.
|
|
1212
|
+
if (srcName && srcVersion && srcName !== comp.name) {
|
|
1213
|
+
const newComp = Object.assign({}, comp);
|
|
1214
|
+
newComp.name = srcName;
|
|
1215
|
+
newComp.version = srcVersion;
|
|
1216
|
+
newComp.tags = ["source"];
|
|
1217
|
+
newComp.evidence = {
|
|
1218
|
+
identity: [
|
|
1219
|
+
{
|
|
1220
|
+
field: "purl",
|
|
1221
|
+
confidence: 0,
|
|
1222
|
+
methods: [
|
|
1223
|
+
{
|
|
1224
|
+
technique: "filename",
|
|
1225
|
+
confidence: 0,
|
|
1226
|
+
value: comp.name,
|
|
1227
|
+
},
|
|
1228
|
+
],
|
|
1229
|
+
},
|
|
1230
|
+
],
|
|
1231
|
+
};
|
|
1232
|
+
// Track upstream and source versions as qualifiers
|
|
1233
|
+
if (purlObj) {
|
|
1234
|
+
const newCompQualifiers = {
|
|
1235
|
+
...purlObj.qualifiers,
|
|
1236
|
+
};
|
|
1237
|
+
delete newCompQualifiers.epoch;
|
|
1238
|
+
if (epoch) {
|
|
1239
|
+
newCompQualifiers.epoch = epoch;
|
|
1240
|
+
}
|
|
1241
|
+
newComp.purl = build({
|
|
1242
|
+
type: purlObj.type,
|
|
1243
|
+
namespace: purlObj.namespace || null,
|
|
1244
|
+
name: srcName,
|
|
1245
|
+
version: srcVersion || null,
|
|
1246
|
+
qualifiers: newCompQualifiers || null,
|
|
1247
|
+
subpath: purlObj.subpath || null,
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
newComp["bom-ref"] = decodeURIComponent(newComp.purl);
|
|
1251
|
+
delete newComp.properties;
|
|
1252
|
+
attachIdentityTools(newComp, toolRefs);
|
|
1253
|
+
pkgList.push(newComp);
|
|
1254
|
+
detectSdksRuntimes(newComp, bundledSdks, bundledRuntimes);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
dropRedundantSourceComponents(pkgList);
|
|
1262
|
+
const rootfsRepositoryInventory = await collectRootfsRepositoryInventory(
|
|
1263
|
+
src,
|
|
1264
|
+
options,
|
|
1265
|
+
);
|
|
1266
|
+
if (rootfsRepositoryInventory.components.length) {
|
|
1267
|
+
pkgList.push(...rootfsRepositoryInventory.components);
|
|
1268
|
+
}
|
|
1269
|
+
if (rootfsRepositoryInventory.dependenciesList.length) {
|
|
1270
|
+
dependenciesList.push(...rootfsRepositoryInventory.dependenciesList);
|
|
1271
|
+
}
|
|
1272
|
+
const {
|
|
1273
|
+
components: ownedFileComponents,
|
|
1274
|
+
dependenciesList: ownedFileDependencies,
|
|
1275
|
+
ownedFilePaths,
|
|
1276
|
+
services: ownedServices,
|
|
1277
|
+
} = await createOSPackageFileComponents(src, osPackageEntries);
|
|
1278
|
+
if (ownedFileComponents.length) {
|
|
1279
|
+
osPackageFiles = ownedFileComponents;
|
|
1280
|
+
}
|
|
1281
|
+
if (ownedFileDependencies.length) {
|
|
1282
|
+
dependenciesList.push(...ownedFileDependencies);
|
|
1283
|
+
}
|
|
1284
|
+
if (ownedServices.length) {
|
|
1285
|
+
services = ownedServices;
|
|
1286
|
+
}
|
|
1287
|
+
let executables = [];
|
|
1288
|
+
if (binPaths?.length) {
|
|
1289
|
+
executables = await fileComponents(
|
|
1290
|
+
src,
|
|
1291
|
+
collectExecutables(src, binPaths, ownedFilePaths),
|
|
1292
|
+
"executable",
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
1295
|
+
// Directories containing shared libraries
|
|
1296
|
+
const defaultLibPaths = [
|
|
1297
|
+
"/lib",
|
|
1298
|
+
"/lib64",
|
|
1299
|
+
"/usr/lib",
|
|
1300
|
+
"/usr/lib64",
|
|
1301
|
+
"/usr/local/lib64",
|
|
1302
|
+
"/usr/local/lib",
|
|
1303
|
+
"/lib/x86_64-linux-gnu",
|
|
1304
|
+
"/usr/lib/x86_64-linux-gnu",
|
|
1305
|
+
"/lib/i386-linux-gnu",
|
|
1306
|
+
"/usr/lib/i386-linux-gnu",
|
|
1307
|
+
"/lib/arm-linux-gnueabihf",
|
|
1308
|
+
"/usr/lib/arm-linux-gnueabihf",
|
|
1309
|
+
"/opt/**/lib",
|
|
1310
|
+
"/root/**/lib",
|
|
1311
|
+
];
|
|
1312
|
+
const sharedLibs = await fileComponents(
|
|
1313
|
+
src,
|
|
1314
|
+
collectSharedLibs(
|
|
1315
|
+
src,
|
|
1316
|
+
defaultLibPaths,
|
|
1317
|
+
"/etc/ld.so.conf",
|
|
1318
|
+
"/etc/ld.so.conf.d/*.conf",
|
|
1319
|
+
ownedFilePaths,
|
|
1320
|
+
),
|
|
1321
|
+
"shared_library",
|
|
1322
|
+
);
|
|
1323
|
+
return {
|
|
1324
|
+
osPackages: pkgList,
|
|
1325
|
+
osPackageFiles,
|
|
1326
|
+
dependenciesList,
|
|
1327
|
+
allTypes: Array.from(allTypes).sort(),
|
|
1328
|
+
bundledSdks: Array.from(bundledSdks).sort(),
|
|
1329
|
+
bundledRuntimes: Array.from(bundledRuntimes).sort(),
|
|
1330
|
+
binPaths,
|
|
1331
|
+
executables,
|
|
1332
|
+
sharedLibs,
|
|
1333
|
+
services,
|
|
1334
|
+
tools,
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
function extractTrivyOsPackageMetadata(compProperties) {
|
|
1339
|
+
const metadata = {
|
|
1340
|
+
capabilities: [],
|
|
1341
|
+
installedCommandPaths: [],
|
|
1342
|
+
installedCommands: [],
|
|
1343
|
+
installedFiles: [],
|
|
1344
|
+
packageMaintainer: undefined,
|
|
1345
|
+
packageVendor: undefined,
|
|
1346
|
+
retainedProperties: [],
|
|
1347
|
+
srcName: undefined,
|
|
1348
|
+
srcRelease: undefined,
|
|
1349
|
+
srcVersion: undefined,
|
|
1350
|
+
epoch: undefined,
|
|
1351
|
+
};
|
|
1352
|
+
if (!Array.isArray(compProperties) || !compProperties.length) {
|
|
1353
|
+
return metadata;
|
|
1354
|
+
}
|
|
1355
|
+
for (const aprop of compProperties) {
|
|
1356
|
+
if (!aprop?.name) {
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
1359
|
+
if (aprop.name.endsWith("SrcName")) {
|
|
1360
|
+
metadata.srcName = aprop.value;
|
|
1361
|
+
continue;
|
|
1362
|
+
}
|
|
1363
|
+
if (aprop.name.endsWith("SrcVersion")) {
|
|
1364
|
+
metadata.srcVersion = aprop.value;
|
|
1365
|
+
continue;
|
|
1366
|
+
}
|
|
1367
|
+
if (aprop.name.endsWith("SrcRelease")) {
|
|
1368
|
+
metadata.srcRelease = aprop.value;
|
|
1369
|
+
continue;
|
|
1370
|
+
}
|
|
1371
|
+
if (aprop.name.endsWith("SrcEpoch")) {
|
|
1372
|
+
metadata.epoch = aprop.value;
|
|
1373
|
+
continue;
|
|
1374
|
+
}
|
|
1375
|
+
if (aprop.name.endsWith("PackageMaintainer")) {
|
|
1376
|
+
metadata.packageMaintainer = aprop.value;
|
|
1377
|
+
continue;
|
|
1378
|
+
}
|
|
1379
|
+
if (aprop.name.endsWith("PackageVendor")) {
|
|
1380
|
+
metadata.packageVendor = aprop.value;
|
|
1381
|
+
continue;
|
|
1382
|
+
}
|
|
1383
|
+
if (aprop.name.endsWith("InstalledFile")) {
|
|
1384
|
+
metadata.installedFiles.push(aprop.value);
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1387
|
+
if (aprop.name.endsWith("InstalledCommandPath")) {
|
|
1388
|
+
metadata.installedCommandPaths.push(aprop.value);
|
|
1389
|
+
metadata.retainedProperties.push(aprop);
|
|
1390
|
+
continue;
|
|
1391
|
+
}
|
|
1392
|
+
if (aprop.name.endsWith("InstalledCommand")) {
|
|
1393
|
+
metadata.installedCommands.push(aprop.value);
|
|
1394
|
+
metadata.retainedProperties.push(aprop);
|
|
1395
|
+
continue;
|
|
1396
|
+
}
|
|
1397
|
+
if (aprop.name.endsWith("Capability")) {
|
|
1398
|
+
metadata.capabilities.push(aprop.value);
|
|
1399
|
+
metadata.retainedProperties.push(aprop);
|
|
1400
|
+
continue;
|
|
1401
|
+
}
|
|
1402
|
+
if (
|
|
1403
|
+
aprop.name.endsWith("CapabilityCount") ||
|
|
1404
|
+
aprop.name.endsWith("InstalledFileCount") ||
|
|
1405
|
+
aprop.name.endsWith("InstalledCommandCount")
|
|
1406
|
+
) {
|
|
1407
|
+
metadata.retainedProperties.push(aprop);
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
metadata.retainedProperties.push(aprop);
|
|
1411
|
+
}
|
|
1412
|
+
metadata.capabilities = uniqueSortedStrings(metadata.capabilities);
|
|
1413
|
+
metadata.installedCommandPaths = uniqueSortedStrings(
|
|
1414
|
+
metadata.installedCommandPaths,
|
|
1415
|
+
);
|
|
1416
|
+
metadata.installedCommands = uniqueSortedStrings(metadata.installedCommands);
|
|
1417
|
+
metadata.installedFiles = uniqueSortedStrings(metadata.installedFiles);
|
|
1418
|
+
metadata.retainedProperties = uniqueProperties(metadata.retainedProperties);
|
|
1419
|
+
return metadata;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
function getOrganizationalEntityName(entity) {
|
|
1423
|
+
if (!entity) {
|
|
1424
|
+
return undefined;
|
|
1425
|
+
}
|
|
1426
|
+
if (typeof entity === "string") {
|
|
1427
|
+
return entity.trim() || undefined;
|
|
1428
|
+
}
|
|
1429
|
+
if (typeof entity === "object" && typeof entity.name === "string") {
|
|
1430
|
+
return entity.name.trim() || undefined;
|
|
1431
|
+
}
|
|
1432
|
+
return undefined;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
function sameOrganizationalEntity(entity, expectedName) {
|
|
1436
|
+
const currentName = getOrganizationalEntityName(entity);
|
|
1437
|
+
return Boolean(
|
|
1438
|
+
currentName &&
|
|
1439
|
+
expectedName &&
|
|
1440
|
+
currentName.localeCompare(expectedName, undefined, {
|
|
1441
|
+
sensitivity: "accent",
|
|
1442
|
+
}) === 0,
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
function mergeOrganizationalEntityField(component, fieldName, entityName) {
|
|
1447
|
+
const normalizedName = `${entityName || ""}`.trim();
|
|
1448
|
+
if (!normalizedName) {
|
|
1449
|
+
return { applied: false, represented: false };
|
|
1450
|
+
}
|
|
1451
|
+
if (!component?.[fieldName]) {
|
|
1452
|
+
component[fieldName] = { name: normalizedName };
|
|
1453
|
+
return { applied: true, represented: true };
|
|
1454
|
+
}
|
|
1455
|
+
if (sameOrganizationalEntity(component[fieldName], normalizedName)) {
|
|
1456
|
+
return { applied: false, represented: true };
|
|
1457
|
+
}
|
|
1458
|
+
return { applied: false, represented: false };
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
function parseOrganizationalContact(value) {
|
|
1462
|
+
const normalizedValue = `${value || ""}`.trim();
|
|
1463
|
+
if (!normalizedValue) {
|
|
1464
|
+
return undefined;
|
|
1465
|
+
}
|
|
1466
|
+
const match = normalizedValue.match(/^([^<>]+?)\s*<([^<>\s]+@[^<>\s]+)>$/);
|
|
1467
|
+
if (match) {
|
|
1468
|
+
return {
|
|
1469
|
+
name: match[1].trim(),
|
|
1470
|
+
email: match[2].trim(),
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
return { name: normalizedValue };
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
function sameOrganizationalContact(left, right) {
|
|
1477
|
+
if (!left || !right) {
|
|
1478
|
+
return false;
|
|
1479
|
+
}
|
|
1480
|
+
const leftName = `${left.name || ""}`.trim();
|
|
1481
|
+
const rightName = `${right.name || ""}`.trim();
|
|
1482
|
+
const leftEmail = `${left.email || ""}`.trim().toLowerCase();
|
|
1483
|
+
const rightEmail = `${right.email || ""}`.trim().toLowerCase();
|
|
1484
|
+
return leftName === rightName && leftEmail === rightEmail;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
function mergeAuthorsFromMaintainer(component, maintainerValue) {
|
|
1488
|
+
const authorContact = parseOrganizationalContact(maintainerValue);
|
|
1489
|
+
if (!authorContact?.name) {
|
|
1490
|
+
return { applied: false, represented: false };
|
|
1491
|
+
}
|
|
1492
|
+
if (!Array.isArray(component?.authors) || !component.authors.length) {
|
|
1493
|
+
component.authors = [authorContact];
|
|
1494
|
+
return { applied: true, represented: true };
|
|
1495
|
+
}
|
|
1496
|
+
if (
|
|
1497
|
+
component.authors.some((author) =>
|
|
1498
|
+
sameOrganizationalContact(author, authorContact),
|
|
1499
|
+
)
|
|
1500
|
+
) {
|
|
1501
|
+
return { applied: false, represented: true };
|
|
1502
|
+
}
|
|
1503
|
+
return { applied: false, represented: false };
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
function promoteTrivyOsPackageIdentity(component, trivyMetadata) {
|
|
1507
|
+
const fallbackProperties = [];
|
|
1508
|
+
const vendorValue = `${trivyMetadata?.packageVendor || ""}`.trim();
|
|
1509
|
+
const supplierName = getOrganizationalEntityName(component?.supplier);
|
|
1510
|
+
const maintainerValue = `${
|
|
1511
|
+
trivyMetadata?.packageMaintainer || supplierName || ""
|
|
1512
|
+
}`.trim();
|
|
1513
|
+
|
|
1514
|
+
const maintainerAuthorResult = mergeAuthorsFromMaintainer(
|
|
1515
|
+
component,
|
|
1516
|
+
maintainerValue,
|
|
1517
|
+
);
|
|
1518
|
+
const maintainerSupplierResult = mergeOrganizationalEntityField(
|
|
1519
|
+
component,
|
|
1520
|
+
"supplier",
|
|
1521
|
+
maintainerValue,
|
|
1522
|
+
);
|
|
1523
|
+
if (
|
|
1524
|
+
trivyMetadata?.packageMaintainer &&
|
|
1525
|
+
!maintainerAuthorResult.represented &&
|
|
1526
|
+
!maintainerSupplierResult.represented
|
|
1527
|
+
) {
|
|
1528
|
+
fallbackProperties.push({
|
|
1529
|
+
name: "internal:PackageMaintainer",
|
|
1530
|
+
value: trivyMetadata.packageMaintainer,
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
const vendorSupplierResult = mergeOrganizationalEntityField(
|
|
1535
|
+
component,
|
|
1536
|
+
"supplier",
|
|
1537
|
+
vendorValue,
|
|
1538
|
+
);
|
|
1539
|
+
const vendorManufacturerResult = mergeOrganizationalEntityField(
|
|
1540
|
+
component,
|
|
1541
|
+
"manufacturer",
|
|
1542
|
+
vendorValue,
|
|
1543
|
+
);
|
|
1544
|
+
if (
|
|
1545
|
+
vendorValue &&
|
|
1546
|
+
!vendorSupplierResult.represented &&
|
|
1547
|
+
!vendorManufacturerResult.represented
|
|
1548
|
+
) {
|
|
1549
|
+
fallbackProperties.push({
|
|
1550
|
+
name: "internal:PackageVendor",
|
|
1551
|
+
value: vendorValue,
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
return fallbackProperties;
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
async function collectRootfsRepositoryInventory(basePath, options = {}) {
|
|
1558
|
+
let trustedKeyComponents = [];
|
|
1559
|
+
let refsByPath = new Map();
|
|
1560
|
+
if (isCycloneDxComponentTypeEnabled("cryptographic-asset", options)) {
|
|
1561
|
+
({ components: trustedKeyComponents, refsByPath } =
|
|
1562
|
+
await collectTrustedKeyComponents(basePath));
|
|
1563
|
+
trustedKeyComponents = applyTrustMaterialEnhancements(
|
|
1564
|
+
trustedKeyComponents,
|
|
1565
|
+
collectTrustInspectorRootfsInventory(basePath),
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
refsByPath = new Map(
|
|
1569
|
+
trustedKeyComponents
|
|
1570
|
+
.map((component) => {
|
|
1571
|
+
const srcFile = (component.properties || []).find(
|
|
1572
|
+
(property) => property.name === "internal:SrcFile",
|
|
1573
|
+
)?.value;
|
|
1574
|
+
return srcFile
|
|
1575
|
+
? [normalizeContainerPath(srcFile), component["bom-ref"]]
|
|
1576
|
+
: undefined;
|
|
1577
|
+
})
|
|
1578
|
+
.filter(Boolean),
|
|
1579
|
+
);
|
|
1580
|
+
const repositoryEntries = uniqueRepositoryEntries(
|
|
1581
|
+
parseAptRepositorySources(basePath).concat(
|
|
1582
|
+
parseYumRepositorySources(basePath),
|
|
1583
|
+
),
|
|
1584
|
+
);
|
|
1585
|
+
const components = [...trustedKeyComponents];
|
|
1586
|
+
const dependenciesList = [];
|
|
1587
|
+
const seenComponentRefs = new Set(components.map((comp) => comp["bom-ref"]));
|
|
1588
|
+
for (const entry of repositoryEntries) {
|
|
1589
|
+
const component = createRepositorySourceComponent(entry);
|
|
1590
|
+
if (seenComponentRefs.has(component["bom-ref"])) {
|
|
1591
|
+
continue;
|
|
1592
|
+
}
|
|
1593
|
+
seenComponentRefs.add(component["bom-ref"]);
|
|
1594
|
+
components.push(component);
|
|
1595
|
+
const dependsOn = uniqueSortedStrings(
|
|
1596
|
+
(entry.keyReferences || [])
|
|
1597
|
+
.map((keyRef) => normalizeLocalRepositoryReference(keyRef))
|
|
1598
|
+
.map((keyRef) => refsByPath.get(keyRef))
|
|
1599
|
+
.filter(Boolean),
|
|
1600
|
+
);
|
|
1601
|
+
if (dependsOn.length) {
|
|
1602
|
+
dependenciesList.push({
|
|
1603
|
+
ref: component["bom-ref"],
|
|
1604
|
+
dependsOn,
|
|
1605
|
+
});
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
return { components, dependenciesList };
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
async function collectTrustedKeyComponents(basePath) {
|
|
1612
|
+
const refsByPath = new Map();
|
|
1613
|
+
const components = [];
|
|
1614
|
+
for (const normalizedPath of collectTrustedKeyPaths(basePath)) {
|
|
1615
|
+
const component = await createTrustedKeyComponent(basePath, normalizedPath);
|
|
1616
|
+
if (!component?.["bom-ref"]) {
|
|
1617
|
+
continue;
|
|
1618
|
+
}
|
|
1619
|
+
refsByPath.set(normalizedPath, component["bom-ref"]);
|
|
1620
|
+
components.push(component);
|
|
1621
|
+
}
|
|
1622
|
+
return { components, refsByPath };
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
function collectTrustedKeyPaths(basePath) {
|
|
1626
|
+
const results = new Set();
|
|
1627
|
+
for (const candidate of [
|
|
1628
|
+
"/etc/apt/trusted.gpg",
|
|
1629
|
+
"/etc/apt/trusted.gpg.d",
|
|
1630
|
+
"/usr/share/keyrings",
|
|
1631
|
+
"/etc/pki/rpm-gpg",
|
|
1632
|
+
"/usr/share/distribution-gpg-keys",
|
|
1633
|
+
"/etc/apk/keys",
|
|
1634
|
+
]) {
|
|
1635
|
+
const normalizedCandidate = normalizeContainerPath(candidate);
|
|
1636
|
+
const absoluteCandidate = join(
|
|
1637
|
+
basePath,
|
|
1638
|
+
normalizedCandidate.replace(/^\/+/, ""),
|
|
1639
|
+
);
|
|
1640
|
+
if (!safeExistsSync(absoluteCandidate)) {
|
|
1641
|
+
continue;
|
|
1642
|
+
}
|
|
1643
|
+
const stats = statSync(absoluteCandidate, { throwIfNoEntry: false });
|
|
1644
|
+
if (!stats) {
|
|
1645
|
+
continue;
|
|
1646
|
+
}
|
|
1647
|
+
if (stats.isDirectory()) {
|
|
1648
|
+
for (const filePath of walkRootfsFiles(basePath, normalizedCandidate)) {
|
|
1649
|
+
if (isTrustedKeyPath(filePath)) {
|
|
1650
|
+
results.add(filePath);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
continue;
|
|
1654
|
+
}
|
|
1655
|
+
if (isTrustedKeyPath(normalizedCandidate)) {
|
|
1656
|
+
results.add(normalizedCandidate);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
return Array.from(results).sort();
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
function walkRootfsFiles(basePath, normalizedDir) {
|
|
1663
|
+
const results = [];
|
|
1664
|
+
const absoluteDir = join(basePath, normalizedDir.replace(/^\/+/, ""));
|
|
1665
|
+
if (!safeExistsSync(absoluteDir)) {
|
|
1666
|
+
return results;
|
|
1667
|
+
}
|
|
1668
|
+
let entries;
|
|
1669
|
+
try {
|
|
1670
|
+
entries = readdirSync(absoluteDir, { withFileTypes: true });
|
|
1671
|
+
} catch (_e) {
|
|
1672
|
+
return results;
|
|
1673
|
+
}
|
|
1674
|
+
for (const entry of entries) {
|
|
1675
|
+
const normalizedPath = normalizeContainerPath(
|
|
1676
|
+
`${normalizedDir.replace(/\/+$/, "")}/${entry.name}`,
|
|
1677
|
+
);
|
|
1678
|
+
if (entry.isDirectory()) {
|
|
1679
|
+
results.push(...walkRootfsFiles(basePath, normalizedPath));
|
|
1680
|
+
continue;
|
|
1681
|
+
}
|
|
1682
|
+
if (entry.isFile()) {
|
|
1683
|
+
results.push(normalizedPath);
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
return results;
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
function isTrustedKeyPath(normalizedPath) {
|
|
1690
|
+
const lowerPath = normalizeContainerPath(normalizedPath)?.toLowerCase();
|
|
1691
|
+
if (!lowerPath) {
|
|
1692
|
+
return false;
|
|
1693
|
+
}
|
|
1694
|
+
return (
|
|
1695
|
+
lowerPath === "/etc/apt/trusted.gpg" ||
|
|
1696
|
+
lowerPath.includes("/trusted.gpg.d/") ||
|
|
1697
|
+
lowerPath.includes("/keyrings/") ||
|
|
1698
|
+
lowerPath.includes("/rpm-gpg/") ||
|
|
1699
|
+
lowerPath.includes("/distribution-gpg-keys/") ||
|
|
1700
|
+
lowerPath.includes("/apk/keys/")
|
|
1701
|
+
);
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
async function createTrustedKeyComponent(basePath, normalizedPath) {
|
|
1705
|
+
const absolutePath = join(basePath, normalizedPath.replace(/^\/+/, ""));
|
|
1706
|
+
const stats = statSync(absolutePath, { throwIfNoEntry: false });
|
|
1707
|
+
if (!stats || stats.isDirectory()) {
|
|
1708
|
+
return undefined;
|
|
1709
|
+
}
|
|
1710
|
+
let hashValues = {};
|
|
1711
|
+
try {
|
|
1712
|
+
hashValues = await multiChecksumFile(["sha1", "sha256"], absolutePath);
|
|
1713
|
+
} catch (_err) {
|
|
1714
|
+
// ignore
|
|
1715
|
+
}
|
|
1716
|
+
const version = hashValues.sha256 || hashValues.sha1 || `${stats.mtimeMs}`;
|
|
1717
|
+
const hashes = [];
|
|
1718
|
+
if (hashValues.sha1) {
|
|
1719
|
+
hashes.push({ alg: "SHA-1", content: hashValues.sha1 });
|
|
1720
|
+
}
|
|
1721
|
+
if (hashValues.sha256) {
|
|
1722
|
+
hashes.push({ alg: "SHA-256", content: hashValues.sha256 });
|
|
1723
|
+
}
|
|
1724
|
+
return {
|
|
1725
|
+
"bom-ref": `crypto/related-crypto-material/public-key/${encodeURIComponent(normalizedPath)}@${hashValues.sha256 ? `sha256:${hashValues.sha256}` : version}`,
|
|
1726
|
+
name: basename(normalizedPath),
|
|
1727
|
+
type: "cryptographic-asset",
|
|
1728
|
+
version,
|
|
1729
|
+
hashes,
|
|
1730
|
+
cryptoProperties: {
|
|
1731
|
+
assetType: "related-crypto-material",
|
|
1732
|
+
relatedCryptoMaterialProperties: {
|
|
1733
|
+
type: "public-key",
|
|
1734
|
+
id: hashValues.sha256 || hashValues.sha1 || normalizedPath,
|
|
1735
|
+
state: "active",
|
|
1736
|
+
},
|
|
1737
|
+
},
|
|
1738
|
+
properties: uniqueProperties([
|
|
1739
|
+
{ name: "internal:SrcFile", value: normalizedPath },
|
|
1740
|
+
{
|
|
1741
|
+
name: "cdx:crypto:trustDomain",
|
|
1742
|
+
value: deriveTrustedKeyDomain(normalizedPath),
|
|
1743
|
+
},
|
|
1744
|
+
{ name: "cdx:crypto:keyPath", value: normalizedPath },
|
|
1745
|
+
{
|
|
1746
|
+
name: "cdx:crypto:fileExtension",
|
|
1747
|
+
value: extname(normalizedPath).replace(/^\./, "") || "gpg",
|
|
1748
|
+
},
|
|
1749
|
+
]),
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
function deriveTrustedKeyDomain(normalizedPath) {
|
|
1754
|
+
const lowerPath = normalizeContainerPath(normalizedPath)?.toLowerCase() || "";
|
|
1755
|
+
if (lowerPath.includes("/apt/") || lowerPath.includes("/keyrings/")) {
|
|
1756
|
+
return "apt";
|
|
1757
|
+
}
|
|
1758
|
+
if (
|
|
1759
|
+
lowerPath.includes("/rpm-gpg/") ||
|
|
1760
|
+
lowerPath.includes("/distribution-gpg-keys/")
|
|
1761
|
+
) {
|
|
1762
|
+
return "rpm";
|
|
1763
|
+
}
|
|
1764
|
+
if (lowerPath.includes("/apk/keys/")) {
|
|
1765
|
+
return "apk";
|
|
1766
|
+
}
|
|
1767
|
+
return "generic";
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
function trustInspectorToolRefs() {
|
|
1771
|
+
return extractToolRefs({
|
|
1772
|
+
components: getPluginToolComponents(["trustinspector"]),
|
|
1773
|
+
});
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
function executeTrustInspector(args, activity) {
|
|
1777
|
+
if (isDryRun) {
|
|
1778
|
+
recordActivity({
|
|
1779
|
+
kind: "trustinspector",
|
|
1780
|
+
reason:
|
|
1781
|
+
"Dry run mode blocks trustinspector execution and reports the requested inspection instead.",
|
|
1782
|
+
status: "blocked",
|
|
1783
|
+
...activity,
|
|
1784
|
+
});
|
|
1785
|
+
return undefined;
|
|
1786
|
+
}
|
|
1787
|
+
if (!TRUSTINSPECTOR_BIN) {
|
|
1788
|
+
return undefined;
|
|
1789
|
+
}
|
|
1790
|
+
if (DEBUG_MODE) {
|
|
1791
|
+
console.log("Executing", TRUSTINSPECTOR_BIN, args.join(" "));
|
|
1792
|
+
}
|
|
1793
|
+
const result = safeSpawnSync(TRUSTINSPECTOR_BIN, args);
|
|
1794
|
+
if (result?.status !== 0 || result?.error) {
|
|
1795
|
+
if (DEBUG_MODE && (result?.stdout || result?.stderr)) {
|
|
1796
|
+
console.error(result.stdout, result.stderr);
|
|
1797
|
+
}
|
|
1798
|
+
return undefined;
|
|
1799
|
+
}
|
|
1800
|
+
if (!result?.stdout) {
|
|
1801
|
+
return undefined;
|
|
1802
|
+
}
|
|
1803
|
+
try {
|
|
1804
|
+
return JSON.parse(result.stdout);
|
|
1805
|
+
} catch (_err) {
|
|
1806
|
+
return undefined;
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
function normalizeTrustInspectorTargetPath(basePath) {
|
|
1811
|
+
if (typeof basePath !== "string") {
|
|
1812
|
+
return undefined;
|
|
1813
|
+
}
|
|
1814
|
+
const trimmedPath = basePath.trim();
|
|
1815
|
+
if (
|
|
1816
|
+
!trimmedPath ||
|
|
1817
|
+
hasDangerousUnicode(trimmedPath) ||
|
|
1818
|
+
/[\r\n]/.test(trimmedPath)
|
|
1819
|
+
) {
|
|
1820
|
+
return undefined;
|
|
1821
|
+
}
|
|
1822
|
+
const resolvedPath = resolve(trimmedPath);
|
|
1823
|
+
if (
|
|
1824
|
+
!resolvedPath ||
|
|
1825
|
+
hasDangerousUnicode(resolvedPath) ||
|
|
1826
|
+
/[\r\n]/.test(resolvedPath)
|
|
1827
|
+
) {
|
|
1828
|
+
return undefined;
|
|
1829
|
+
}
|
|
1830
|
+
if (
|
|
1831
|
+
(isWin &&
|
|
1832
|
+
!(
|
|
1833
|
+
resolvedPath.startsWith("\\\\") ||
|
|
1834
|
+
isValidDriveRoot(resolvedPath.slice(0, 3))
|
|
1835
|
+
)) ||
|
|
1836
|
+
(!isWin && !resolvedPath.startsWith("/")) ||
|
|
1837
|
+
!safeExistsSync(resolvedPath)
|
|
1838
|
+
) {
|
|
1839
|
+
return undefined;
|
|
1840
|
+
}
|
|
1841
|
+
const targetStats = statSync(resolvedPath, { throwIfNoEntry: false });
|
|
1842
|
+
if (!targetStats?.isDirectory()) {
|
|
1843
|
+
return undefined;
|
|
1844
|
+
}
|
|
1845
|
+
let canonicalPath;
|
|
1846
|
+
try {
|
|
1847
|
+
canonicalPath = realpathSync(resolvedPath);
|
|
1848
|
+
} catch (_err) {
|
|
1849
|
+
return undefined;
|
|
1850
|
+
}
|
|
1851
|
+
if (
|
|
1852
|
+
!canonicalPath ||
|
|
1853
|
+
hasDangerousUnicode(canonicalPath) ||
|
|
1854
|
+
/[\r\n]/.test(canonicalPath)
|
|
1855
|
+
) {
|
|
1856
|
+
return undefined;
|
|
1857
|
+
}
|
|
1858
|
+
return resolvedPath;
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
function trustMaterialHashes(material) {
|
|
1862
|
+
const hashes = [];
|
|
1863
|
+
if (material?.sha1) {
|
|
1864
|
+
hashes.push({ alg: "SHA-1", content: material.sha1 });
|
|
1865
|
+
}
|
|
1866
|
+
if (material?.sha256) {
|
|
1867
|
+
hashes.push({ alg: "SHA-256", content: material.sha256 });
|
|
1868
|
+
}
|
|
1869
|
+
return hashes;
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
function trustMaterialState(material) {
|
|
1873
|
+
const expiresAt = material?.expiresAt
|
|
1874
|
+
? Date.parse(material.expiresAt)
|
|
1875
|
+
: Number.NaN;
|
|
1876
|
+
if (!Number.isNaN(expiresAt) && expiresAt < Date.now()) {
|
|
1877
|
+
return "expired";
|
|
1878
|
+
}
|
|
1879
|
+
return "active";
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
function createTrustMaterialComponent(material) {
|
|
1883
|
+
const normalizedPath = normalizeContainerPath(material?.path);
|
|
1884
|
+
if (!normalizedPath || !material?.kind) {
|
|
1885
|
+
return undefined;
|
|
1886
|
+
}
|
|
1887
|
+
const hashes = trustMaterialHashes(material);
|
|
1888
|
+
const sharedProperties = uniqueProperties([
|
|
1889
|
+
{ name: "internal:SrcFile", value: normalizedPath },
|
|
1890
|
+
...(material?.properties || []),
|
|
1891
|
+
...(material?.fingerprint
|
|
1892
|
+
? [{ name: "cdx:crypto:fingerprint", value: material.fingerprint }]
|
|
1893
|
+
: []),
|
|
1894
|
+
...(material?.algorithm
|
|
1895
|
+
? [{ name: "cdx:crypto:algorithm", value: material.algorithm }]
|
|
1896
|
+
: []),
|
|
1897
|
+
...(material?.keyStrength
|
|
1898
|
+
? [{ name: "cdx:crypto:keyStrength", value: `${material.keyStrength}` }]
|
|
1899
|
+
: []),
|
|
1900
|
+
...(material?.createdAt
|
|
1901
|
+
? [{ name: "cdx:crypto:createdAt", value: material.createdAt }]
|
|
1902
|
+
: []),
|
|
1903
|
+
...(material?.expiresAt
|
|
1904
|
+
? [{ name: "cdx:crypto:expiresAt", value: material.expiresAt }]
|
|
1905
|
+
: []),
|
|
1906
|
+
]);
|
|
1907
|
+
let component;
|
|
1908
|
+
if (material.kind === "certificate") {
|
|
1909
|
+
component = {
|
|
1910
|
+
"bom-ref": `crypto/certificate/${encodeURIComponent(material.name || normalizedPath)}@${material.sha256 ? `sha256:${material.sha256}` : material.serial || material.expiresAt || "unknown"}`,
|
|
1911
|
+
name: material.name || basename(normalizedPath),
|
|
1912
|
+
type: "cryptographic-asset",
|
|
1913
|
+
version:
|
|
1914
|
+
material.sha256 ||
|
|
1915
|
+
material.serial ||
|
|
1916
|
+
material.expiresAt ||
|
|
1917
|
+
"configured",
|
|
1918
|
+
hashes,
|
|
1919
|
+
description: material.subject || normalizedPath,
|
|
1920
|
+
cryptoProperties: {
|
|
1921
|
+
assetType: "certificate",
|
|
1922
|
+
algorithmProperties: {
|
|
1923
|
+
executionEnvironment: "unknown",
|
|
1924
|
+
implementationPlatform: "unknown",
|
|
1925
|
+
},
|
|
1926
|
+
certificateProperties: {
|
|
1927
|
+
serialNumber: material.serial || undefined,
|
|
1928
|
+
subjectName: material.subject || undefined,
|
|
1929
|
+
issuerName: material.issuer || undefined,
|
|
1930
|
+
notValidBefore: material.createdAt || undefined,
|
|
1931
|
+
notValidAfter: material.expiresAt || undefined,
|
|
1932
|
+
certificateFormat: material.format || "X.509",
|
|
1933
|
+
certificateFileExtension: material.fileExtension || undefined,
|
|
1934
|
+
fingerprint: material.fingerprint
|
|
1935
|
+
? { alg: "SHA-256", content: material.fingerprint }
|
|
1936
|
+
: undefined,
|
|
1937
|
+
},
|
|
1938
|
+
},
|
|
1939
|
+
properties: uniqueProperties(
|
|
1940
|
+
sharedProperties.concat(
|
|
1941
|
+
material.trustDomain
|
|
1942
|
+
? [{ name: "cdx:crypto:trustDomain", value: material.trustDomain }]
|
|
1943
|
+
: [],
|
|
1944
|
+
),
|
|
1945
|
+
),
|
|
1946
|
+
};
|
|
1947
|
+
} else {
|
|
1948
|
+
component = {
|
|
1949
|
+
"bom-ref": `crypto/related-crypto-material/public-key/${encodeURIComponent(normalizedPath)}@${material.sha256 ? `sha256:${material.sha256}` : material.keyId || "unknown"}`,
|
|
1950
|
+
name: material.name || basename(normalizedPath),
|
|
1951
|
+
type: "cryptographic-asset",
|
|
1952
|
+
version: material.sha256 || material.keyId || normalizedPath,
|
|
1953
|
+
hashes,
|
|
1954
|
+
cryptoProperties: {
|
|
1955
|
+
assetType: "related-crypto-material",
|
|
1956
|
+
relatedCryptoMaterialProperties: {
|
|
1957
|
+
type: "public-key",
|
|
1958
|
+
id:
|
|
1959
|
+
material.keyId ||
|
|
1960
|
+
material.fingerprint ||
|
|
1961
|
+
material.sha256 ||
|
|
1962
|
+
normalizedPath,
|
|
1963
|
+
state: trustMaterialState(material),
|
|
1964
|
+
},
|
|
1965
|
+
},
|
|
1966
|
+
properties: uniqueProperties(
|
|
1967
|
+
sharedProperties.concat([
|
|
1968
|
+
{
|
|
1969
|
+
name: "cdx:crypto:trustDomain",
|
|
1970
|
+
value:
|
|
1971
|
+
material.trustDomain || deriveTrustedKeyDomain(normalizedPath),
|
|
1972
|
+
},
|
|
1973
|
+
{ name: "cdx:crypto:keyPath", value: normalizedPath },
|
|
1974
|
+
{
|
|
1975
|
+
name: "cdx:crypto:fileExtension",
|
|
1976
|
+
value:
|
|
1977
|
+
material.fileExtension ||
|
|
1978
|
+
extname(normalizedPath).replace(/^\./, "") ||
|
|
1979
|
+
"gpg",
|
|
1980
|
+
},
|
|
1981
|
+
...(material?.keyId
|
|
1982
|
+
? [{ name: "cdx:crypto:keyId", value: material.keyId }]
|
|
1983
|
+
: []),
|
|
1984
|
+
...(material?.userIds || []).map((value) => ({
|
|
1985
|
+
name: "cdx:crypto:userId",
|
|
1986
|
+
value,
|
|
1987
|
+
})),
|
|
1988
|
+
]),
|
|
1989
|
+
),
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
attachIdentityTools(component, trustInspectorToolRefs());
|
|
1993
|
+
return component;
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
function enhanceComponentFromTrustMaterial(component, material) {
|
|
1997
|
+
if (!component || !material) {
|
|
1998
|
+
return component;
|
|
1999
|
+
}
|
|
2000
|
+
component.hashes = component.hashes?.length
|
|
2001
|
+
? component.hashes
|
|
2002
|
+
: trustMaterialHashes(material);
|
|
2003
|
+
component.properties = uniqueProperties(
|
|
2004
|
+
(component.properties || []).concat(
|
|
2005
|
+
createTrustMaterialComponent(material)?.properties || [],
|
|
2006
|
+
),
|
|
2007
|
+
);
|
|
2008
|
+
if (!component.cryptoProperties) {
|
|
2009
|
+
component.cryptoProperties =
|
|
2010
|
+
createTrustMaterialComponent(material)?.cryptoProperties;
|
|
2011
|
+
}
|
|
2012
|
+
attachIdentityTools(component, trustInspectorToolRefs());
|
|
2013
|
+
return component;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
function applyTrustMaterialEnhancements(components, materials) {
|
|
2017
|
+
if (!materials?.length) {
|
|
2018
|
+
return components || [];
|
|
2019
|
+
}
|
|
2020
|
+
const componentList = [...(components || [])];
|
|
2021
|
+
const bySrcFile = new Map();
|
|
2022
|
+
const seenRefs = new Set();
|
|
2023
|
+
for (const component of componentList) {
|
|
2024
|
+
if (component?.["bom-ref"]) {
|
|
2025
|
+
seenRefs.add(component["bom-ref"]);
|
|
2026
|
+
}
|
|
2027
|
+
const srcFile = (component?.properties || []).find(
|
|
2028
|
+
(property) => property.name === "internal:SrcFile",
|
|
2029
|
+
)?.value;
|
|
2030
|
+
if (srcFile) {
|
|
2031
|
+
bySrcFile.set(normalizeContainerPath(srcFile), component);
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
for (const material of materials) {
|
|
2035
|
+
const srcFile = normalizeContainerPath(material?.path);
|
|
2036
|
+
const existing = bySrcFile.get(srcFile);
|
|
2037
|
+
if (
|
|
2038
|
+
existing &&
|
|
2039
|
+
existing.type === "cryptographic-asset" &&
|
|
2040
|
+
material?.kind === "public-key"
|
|
2041
|
+
) {
|
|
2042
|
+
enhanceComponentFromTrustMaterial(existing, material);
|
|
2043
|
+
continue;
|
|
2044
|
+
}
|
|
2045
|
+
const component = createTrustMaterialComponent(material);
|
|
2046
|
+
if (!component?.["bom-ref"] || seenRefs.has(component["bom-ref"])) {
|
|
2047
|
+
continue;
|
|
2048
|
+
}
|
|
2049
|
+
seenRefs.add(component["bom-ref"]);
|
|
2050
|
+
componentList.push(component);
|
|
2051
|
+
if (srcFile) {
|
|
2052
|
+
bySrcFile.set(srcFile, component);
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
return componentList;
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
function collectTrustInspectorRootfsInventory(basePath) {
|
|
2059
|
+
const targetPath = normalizeTrustInspectorTargetPath(basePath);
|
|
2060
|
+
if (!targetPath) {
|
|
2061
|
+
return [];
|
|
2062
|
+
}
|
|
2063
|
+
const trustData = executeTrustInspector(["rootfs", targetPath], {
|
|
2064
|
+
target: targetPath,
|
|
2065
|
+
});
|
|
2066
|
+
return trustData?.materials || [];
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
function parseAptRepositorySources(basePath) {
|
|
2070
|
+
const sourceFiles = [
|
|
2071
|
+
"/etc/apt/sources.list",
|
|
2072
|
+
...walkRootfsFiles(basePath, "/etc/apt/sources.list.d").filter(
|
|
2073
|
+
(filePath) => filePath.endsWith(".list") || filePath.endsWith(".sources"),
|
|
2074
|
+
),
|
|
2075
|
+
].filter((filePath, index, values) => values.indexOf(filePath) === index);
|
|
2076
|
+
const entries = [];
|
|
2077
|
+
for (const sourceFile of sourceFiles) {
|
|
2078
|
+
const data = readRootfsTextFile(basePath, sourceFile);
|
|
2079
|
+
if (!data) {
|
|
2080
|
+
continue;
|
|
2081
|
+
}
|
|
2082
|
+
if (sourceFile.endsWith(".sources")) {
|
|
2083
|
+
entries.push(...parseDeb822AptRepositorySources(data, sourceFile));
|
|
2084
|
+
continue;
|
|
2085
|
+
}
|
|
2086
|
+
entries.push(...parseLegacyAptRepositorySources(data, sourceFile));
|
|
2087
|
+
}
|
|
2088
|
+
return entries;
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
function parseLegacyAptRepositorySources(data, sourceFile) {
|
|
2092
|
+
const entries = [];
|
|
2093
|
+
for (const rawLine of data.split(/\r?\n/)) {
|
|
2094
|
+
const line = rawLine.split("#")[0].trim();
|
|
2095
|
+
if (!line || (!line.startsWith("deb ") && !line.startsWith("deb-src "))) {
|
|
2096
|
+
continue;
|
|
2097
|
+
}
|
|
2098
|
+
const match = line.match(
|
|
2099
|
+
/^(deb(?:-src)?)\s+(?:\[(?<options>[^\]]+)\]\s+)?(?<uri>\S+)\s+(?<suite>\S+)(?:\s+(?<components>.+))?$/,
|
|
2100
|
+
);
|
|
2101
|
+
if (!match?.groups?.uri) {
|
|
2102
|
+
continue;
|
|
2103
|
+
}
|
|
2104
|
+
const options = parseRepositoryOptionString(match.groups.options);
|
|
2105
|
+
entries.push({
|
|
2106
|
+
name: deriveRepositoryDisplayName(match.groups.uri, sourceFile),
|
|
2107
|
+
path: sourceFile,
|
|
2108
|
+
repoType: isPpaRepository(match.groups.uri) ? "ppa-source" : "apt-source",
|
|
2109
|
+
release: match.groups.suite,
|
|
2110
|
+
url: match.groups.uri,
|
|
2111
|
+
description: line,
|
|
2112
|
+
enabled: true,
|
|
2113
|
+
keyReferences: extractRepositoryKeyReferences(options["signed-by"]),
|
|
2114
|
+
properties: uniqueProperties([
|
|
2115
|
+
{ name: "internal:SrcFile", value: sourceFile },
|
|
2116
|
+
{ name: "cdx:os:repo:kind", value: match[1] },
|
|
2117
|
+
{ name: "cdx:os:repo:url", value: match.groups.uri },
|
|
2118
|
+
{ name: "cdx:os:repo:release", value: match.groups.suite },
|
|
2119
|
+
...(match.groups.components
|
|
2120
|
+
? [
|
|
2121
|
+
{
|
|
2122
|
+
name: "cdx:os:repo:components",
|
|
2123
|
+
value: match.groups.components,
|
|
2124
|
+
},
|
|
2125
|
+
]
|
|
2126
|
+
: []),
|
|
2127
|
+
...(options.arch
|
|
2128
|
+
? [{ name: "cdx:os:repo:architectures", value: options.arch }]
|
|
2129
|
+
: []),
|
|
2130
|
+
...(options["signed-by"]
|
|
2131
|
+
? [{ name: "cdx:os:repo:signedBy", value: options["signed-by"] }]
|
|
2132
|
+
: []),
|
|
2133
|
+
]),
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
2136
|
+
return entries;
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
function parseDeb822AptRepositorySources(data, sourceFile) {
|
|
2140
|
+
const entries = [];
|
|
2141
|
+
for (const stanza of data.split(/\n\s*\n/)) {
|
|
2142
|
+
const fields = parseDeb822Fields(stanza);
|
|
2143
|
+
const uris = splitRepositoryField(fields.uris);
|
|
2144
|
+
const suites = splitRepositoryField(fields.suites || fields.suite);
|
|
2145
|
+
if (!uris.length) {
|
|
2146
|
+
continue;
|
|
2147
|
+
}
|
|
2148
|
+
for (const uri of uris) {
|
|
2149
|
+
entries.push({
|
|
2150
|
+
name: deriveRepositoryDisplayName(uri, sourceFile),
|
|
2151
|
+
path: sourceFile,
|
|
2152
|
+
repoType: isPpaRepository(uri) ? "ppa-source" : "apt-source",
|
|
2153
|
+
release: suites.join(",") || "configured",
|
|
2154
|
+
url: uri,
|
|
2155
|
+
description: stanza.trim(),
|
|
2156
|
+
enabled: !["no", "false", "0"].includes(
|
|
2157
|
+
`${fields.enabled || "yes"}`.toLowerCase(),
|
|
2158
|
+
),
|
|
2159
|
+
keyReferences: extractRepositoryKeyReferences(fields["signed-by"]),
|
|
2160
|
+
properties: uniqueProperties([
|
|
2161
|
+
{ name: "internal:SrcFile", value: sourceFile },
|
|
2162
|
+
{ name: "cdx:os:repo:kind", value: fields.types || "deb" },
|
|
2163
|
+
{ name: "cdx:os:repo:url", value: uri },
|
|
2164
|
+
...(suites.length
|
|
2165
|
+
? [{ name: "cdx:os:repo:release", value: suites.join(",") }]
|
|
2166
|
+
: []),
|
|
2167
|
+
...(fields.components
|
|
2168
|
+
? [{ name: "cdx:os:repo:components", value: fields.components }]
|
|
2169
|
+
: []),
|
|
2170
|
+
...(fields.architectures
|
|
2171
|
+
? [
|
|
2172
|
+
{
|
|
2173
|
+
name: "cdx:os:repo:architectures",
|
|
2174
|
+
value: fields.architectures,
|
|
2175
|
+
},
|
|
2176
|
+
]
|
|
2177
|
+
: []),
|
|
2178
|
+
...(fields["signed-by"]
|
|
2179
|
+
? [
|
|
2180
|
+
{
|
|
2181
|
+
name: "cdx:os:repo:signedBy",
|
|
2182
|
+
value: fields["signed-by"],
|
|
2183
|
+
},
|
|
2184
|
+
]
|
|
2185
|
+
: []),
|
|
2186
|
+
]),
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
return entries;
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
function parseYumRepositorySources(basePath) {
|
|
2194
|
+
const entries = [];
|
|
2195
|
+
for (const repoFile of walkRootfsFiles(basePath, "/etc/yum.repos.d").filter(
|
|
2196
|
+
(f) => f.endsWith(".repo"),
|
|
2197
|
+
)) {
|
|
2198
|
+
const data = readRootfsTextFile(basePath, repoFile);
|
|
2199
|
+
if (!data) {
|
|
2200
|
+
continue;
|
|
2201
|
+
}
|
|
2202
|
+
let currentSection;
|
|
2203
|
+
let currentConfig = {};
|
|
2204
|
+
const flushCurrentSection = () => {
|
|
2205
|
+
if (!currentSection) {
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
const url =
|
|
2209
|
+
currentConfig.baseurl ||
|
|
2210
|
+
currentConfig.mirrorlist ||
|
|
2211
|
+
currentConfig.metalink;
|
|
2212
|
+
if (!url) {
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
entries.push({
|
|
2216
|
+
name: currentSection,
|
|
2217
|
+
path: repoFile,
|
|
2218
|
+
repoType: "yum-source",
|
|
2219
|
+
release:
|
|
2220
|
+
`${currentConfig.enabled || "1"}` === "1" ? "enabled" : "disabled",
|
|
2221
|
+
url,
|
|
2222
|
+
description: `${repoFile}#${currentSection}`,
|
|
2223
|
+
enabled: `${currentConfig.enabled || "1"}` === "1",
|
|
2224
|
+
keyReferences: extractRepositoryKeyReferences(currentConfig.gpgkey),
|
|
2225
|
+
properties: uniqueProperties([
|
|
2226
|
+
{ name: "internal:SrcFile", value: repoFile },
|
|
2227
|
+
{ name: "cdx:os:repo:url", value: url },
|
|
2228
|
+
...(currentConfig.baseurl
|
|
2229
|
+
? [{ name: "cdx:os:repo:baseurl", value: currentConfig.baseurl }]
|
|
2230
|
+
: []),
|
|
2231
|
+
...(currentConfig.mirrorlist
|
|
2232
|
+
? [
|
|
2233
|
+
{
|
|
2234
|
+
name: "cdx:os:repo:mirrorlist",
|
|
2235
|
+
value: currentConfig.mirrorlist,
|
|
2236
|
+
},
|
|
2237
|
+
]
|
|
2238
|
+
: []),
|
|
2239
|
+
...(currentConfig.metalink
|
|
2240
|
+
? [{ name: "cdx:os:repo:metalink", value: currentConfig.metalink }]
|
|
2241
|
+
: []),
|
|
2242
|
+
{
|
|
2243
|
+
name: "cdx:os:repo:enabled",
|
|
2244
|
+
value: `${currentConfig.enabled || "1"}`,
|
|
2245
|
+
},
|
|
2246
|
+
...(currentConfig.gpgcheck
|
|
2247
|
+
? [{ name: "cdx:os:repo:gpgcheck", value: currentConfig.gpgcheck }]
|
|
2248
|
+
: []),
|
|
2249
|
+
...(currentConfig.gpgkey
|
|
2250
|
+
? [{ name: "cdx:os:repo:gpgkey", value: currentConfig.gpgkey }]
|
|
2251
|
+
: []),
|
|
2252
|
+
]),
|
|
2253
|
+
});
|
|
2254
|
+
};
|
|
2255
|
+
for (const rawLine of data.split(/\r?\n/)) {
|
|
2256
|
+
const line = rawLine.trim();
|
|
2257
|
+
if (!line || line.startsWith("#") || line.startsWith(";")) {
|
|
2258
|
+
continue;
|
|
2259
|
+
}
|
|
2260
|
+
if (line.startsWith("[") && line.endsWith("]")) {
|
|
2261
|
+
flushCurrentSection();
|
|
2262
|
+
currentSection = line.slice(1, -1).trim();
|
|
2263
|
+
currentConfig = {};
|
|
2264
|
+
continue;
|
|
2265
|
+
}
|
|
2266
|
+
const equalsIndex = line.indexOf("=");
|
|
2267
|
+
if (equalsIndex === -1) {
|
|
2268
|
+
continue;
|
|
2269
|
+
}
|
|
2270
|
+
currentConfig[line.slice(0, equalsIndex).trim().toLowerCase()] = line
|
|
2271
|
+
.slice(equalsIndex + 1)
|
|
2272
|
+
.trim();
|
|
2273
|
+
}
|
|
2274
|
+
flushCurrentSection();
|
|
2275
|
+
}
|
|
2276
|
+
return entries;
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
function createRepositorySourceComponent(entry) {
|
|
2280
|
+
const version = entry.release || "configured";
|
|
2281
|
+
const purl = build({
|
|
2282
|
+
type: "generic",
|
|
2283
|
+
namespace: "os-repository",
|
|
2284
|
+
name: entry.name,
|
|
2285
|
+
version: version || null,
|
|
2286
|
+
});
|
|
2287
|
+
return {
|
|
2288
|
+
"bom-ref": decodeURIComponent(purl),
|
|
2289
|
+
purl,
|
|
2290
|
+
name: entry.name,
|
|
2291
|
+
type: "data",
|
|
2292
|
+
version,
|
|
2293
|
+
description: entry.description || entry.url,
|
|
2294
|
+
properties: uniqueProperties(
|
|
2295
|
+
[
|
|
2296
|
+
{ name: "internal:SrcFile", value: entry.path },
|
|
2297
|
+
{ name: "cdx:os:repo:type", value: entry.repoType },
|
|
2298
|
+
{ name: "cdx:os:repo:url", value: entry.url },
|
|
2299
|
+
{
|
|
2300
|
+
name: "cdx:os:repo:enabled",
|
|
2301
|
+
value: entry.enabled === false ? "false" : "true",
|
|
2302
|
+
},
|
|
2303
|
+
].concat(entry.properties || []),
|
|
2304
|
+
),
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
function uniqueRepositoryEntries(entries) {
|
|
2309
|
+
const seen = new Set();
|
|
2310
|
+
const results = [];
|
|
2311
|
+
for (const entry of entries || []) {
|
|
2312
|
+
if (!entry?.name || !entry?.path || !entry?.url) {
|
|
2313
|
+
continue;
|
|
2314
|
+
}
|
|
2315
|
+
const key = `${entry.repoType}\u0000${entry.path}\u0000${entry.url}\u0000${entry.release || ""}`;
|
|
2316
|
+
if (seen.has(key)) {
|
|
2317
|
+
continue;
|
|
2318
|
+
}
|
|
2319
|
+
seen.add(key);
|
|
2320
|
+
results.push(entry);
|
|
2321
|
+
}
|
|
2322
|
+
return results;
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
function deriveRepositoryDisplayName(url, sourceFile) {
|
|
2326
|
+
try {
|
|
2327
|
+
const parsedUrl = new URL(url);
|
|
2328
|
+
const repoPath = parsedUrl.pathname.replace(/\/+$/, "") || "/";
|
|
2329
|
+
return `${parsedUrl.hostname}${repoPath}`;
|
|
2330
|
+
} catch (_err) {
|
|
2331
|
+
return basename(sourceFile);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
function isPpaRepository(url) {
|
|
2336
|
+
try {
|
|
2337
|
+
const hostname = new URL(`${url || ""}`).hostname.toLowerCase();
|
|
2338
|
+
return (
|
|
2339
|
+
hostname === "ppa.launchpadcontent.net" ||
|
|
2340
|
+
hostname === "ppa.launchpad.net"
|
|
2341
|
+
);
|
|
2342
|
+
} catch (_err) {
|
|
2343
|
+
return false;
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
function parseRepositoryOptionString(optionString) {
|
|
2348
|
+
const options = {};
|
|
2349
|
+
for (const token of `${optionString || ""}`.split(/\s+/).filter(Boolean)) {
|
|
2350
|
+
const [key, ...valueParts] = token.split("=");
|
|
2351
|
+
if (!key || !valueParts.length) {
|
|
2352
|
+
continue;
|
|
2353
|
+
}
|
|
2354
|
+
options[key.toLowerCase()] = valueParts.join("=");
|
|
2355
|
+
}
|
|
2356
|
+
return options;
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
function parseDeb822Fields(stanza) {
|
|
2360
|
+
const fields = {};
|
|
2361
|
+
let currentKey;
|
|
2362
|
+
for (const rawLine of stanza.split(/\r?\n/)) {
|
|
2363
|
+
if (!rawLine.trim()) {
|
|
2364
|
+
continue;
|
|
2365
|
+
}
|
|
2366
|
+
if (/^[ \t]/.test(rawLine) && currentKey) {
|
|
2367
|
+
fields[currentKey] = `${fields[currentKey]} ${rawLine.trim()}`.trim();
|
|
2368
|
+
continue;
|
|
2369
|
+
}
|
|
2370
|
+
const separatorIndex = rawLine.indexOf(":");
|
|
2371
|
+
if (separatorIndex === -1) {
|
|
2372
|
+
continue;
|
|
2373
|
+
}
|
|
2374
|
+
currentKey = rawLine.slice(0, separatorIndex).trim().toLowerCase();
|
|
2375
|
+
fields[currentKey] = rawLine.slice(separatorIndex + 1).trim();
|
|
2376
|
+
}
|
|
2377
|
+
return fields;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
function splitRepositoryField(value) {
|
|
2381
|
+
return `${value || ""}`.split(/\s+/).filter(Boolean);
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
function extractRepositoryKeyReferences(value) {
|
|
2385
|
+
return uniqueSortedStrings(
|
|
2386
|
+
`${value || ""}`
|
|
2387
|
+
.split(/[\s,]+/)
|
|
2388
|
+
.map((part) => normalizeLocalRepositoryReference(part))
|
|
2389
|
+
.filter(Boolean),
|
|
2390
|
+
);
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
function normalizeLocalRepositoryReference(value) {
|
|
2394
|
+
if (!value) {
|
|
2395
|
+
return undefined;
|
|
2396
|
+
}
|
|
2397
|
+
const trimmedValue = `${value}`.trim();
|
|
2398
|
+
if (!trimmedValue || trimmedValue.includes("BEGIN PGP PUBLIC KEY BLOCK")) {
|
|
2399
|
+
return undefined;
|
|
2400
|
+
}
|
|
2401
|
+
if (trimmedValue.startsWith("file://")) {
|
|
2402
|
+
return normalizeContainerPath(trimmedValue.slice("file://".length));
|
|
2403
|
+
}
|
|
2404
|
+
if (trimmedValue.startsWith("/")) {
|
|
2405
|
+
return normalizeContainerPath(trimmedValue);
|
|
2406
|
+
}
|
|
2407
|
+
return undefined;
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
function readRootfsTextFile(basePath, normalizedPath) {
|
|
2411
|
+
const absolutePath = join(basePath, normalizedPath.replace(/^\/+/, ""));
|
|
2412
|
+
if (!safeExistsSync(absolutePath)) {
|
|
2413
|
+
return undefined;
|
|
2414
|
+
}
|
|
2415
|
+
try {
|
|
2416
|
+
return readFileSync(absolutePath, "utf-8");
|
|
2417
|
+
} catch (_err) {
|
|
2418
|
+
return undefined;
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
async function createOSPackageFileComponents(basePath, osPackageEntries) {
|
|
2423
|
+
const components = [];
|
|
2424
|
+
const dependenciesList = [];
|
|
2425
|
+
const ownedFilePaths = new Set();
|
|
2426
|
+
const services = [];
|
|
2427
|
+
const componentByPath = new Map();
|
|
2428
|
+
for (const packageEntry of osPackageEntries) {
|
|
2429
|
+
const commandPathSet = new Set(packageEntry.commandPaths || []);
|
|
2430
|
+
const providedRefs = new Set();
|
|
2431
|
+
for (const filePath of packageEntry.files || []) {
|
|
2432
|
+
if (!filePath) {
|
|
2433
|
+
continue;
|
|
2434
|
+
}
|
|
2435
|
+
ownedFilePaths.add(filePath);
|
|
2436
|
+
let fileComponent = componentByPath.get(filePath);
|
|
2437
|
+
if (!fileComponent) {
|
|
2438
|
+
fileComponent = await createOSPackageFileComponent(
|
|
2439
|
+
basePath,
|
|
2440
|
+
filePath,
|
|
2441
|
+
commandPathSet,
|
|
2442
|
+
);
|
|
2443
|
+
if (!fileComponent) {
|
|
2444
|
+
continue;
|
|
2445
|
+
}
|
|
2446
|
+
componentByPath.set(filePath, fileComponent);
|
|
2447
|
+
components.push(fileComponent);
|
|
2448
|
+
}
|
|
2449
|
+
providedRefs.add(fileComponent["bom-ref"]);
|
|
2450
|
+
}
|
|
2451
|
+
const serviceResults = await createOSPackageServices(
|
|
2452
|
+
basePath,
|
|
2453
|
+
packageEntry,
|
|
2454
|
+
componentByPath,
|
|
2455
|
+
);
|
|
2456
|
+
for (const service of serviceResults.services) {
|
|
2457
|
+
services.push(service);
|
|
2458
|
+
providedRefs.add(service["bom-ref"]);
|
|
2459
|
+
}
|
|
2460
|
+
if (serviceResults.dependenciesList.length) {
|
|
2461
|
+
dependenciesList.push(...serviceResults.dependenciesList);
|
|
2462
|
+
}
|
|
2463
|
+
if (providedRefs.size) {
|
|
2464
|
+
dependenciesList.push({
|
|
2465
|
+
ref: packageEntry.packageRef,
|
|
2466
|
+
provides: Array.from(providedRefs).sort(),
|
|
2467
|
+
});
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
return {
|
|
2471
|
+
components,
|
|
2472
|
+
dependenciesList,
|
|
2473
|
+
ownedFilePaths: Array.from(ownedFilePaths).sort(),
|
|
2474
|
+
services: dedupeServices(services),
|
|
2475
|
+
};
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
async function createOSPackageFileComponent(
|
|
2479
|
+
basePath,
|
|
2480
|
+
filePath,
|
|
2481
|
+
commandPathSet,
|
|
2482
|
+
) {
|
|
2483
|
+
const normalizedFilePath = normalizeContainerPath(filePath);
|
|
2484
|
+
if (!normalizedFilePath) {
|
|
2485
|
+
return undefined;
|
|
2486
|
+
}
|
|
2487
|
+
let hashes;
|
|
2488
|
+
try {
|
|
2489
|
+
const hashValues = await multiChecksumFile(
|
|
2490
|
+
["md5", "sha1"],
|
|
2491
|
+
join(basePath, normalizedFilePath.replace(/^\/+/, "")),
|
|
2492
|
+
);
|
|
2493
|
+
hashes = [
|
|
2494
|
+
{ alg: "MD5", content: hashValues.md5 },
|
|
2495
|
+
{ alg: "SHA-1", content: hashValues.sha1 },
|
|
2496
|
+
];
|
|
2497
|
+
} catch (_e) {
|
|
2498
|
+
// ignore
|
|
2499
|
+
}
|
|
2500
|
+
const fileName = basename(normalizedFilePath);
|
|
2501
|
+
const stats = statSync(
|
|
2502
|
+
join(basePath, normalizedFilePath.replace(/^\/+/, "")),
|
|
2503
|
+
{
|
|
2504
|
+
throwIfNoEntry: false,
|
|
2505
|
+
},
|
|
2506
|
+
);
|
|
2507
|
+
if (!stats || stats.isDirectory()) {
|
|
2508
|
+
return undefined;
|
|
2509
|
+
}
|
|
2510
|
+
let linkedName;
|
|
2511
|
+
try {
|
|
2512
|
+
const resolvedPath = realpathSync(
|
|
2513
|
+
join(basePath, normalizedFilePath.replace(/^\/+/, "")),
|
|
2514
|
+
);
|
|
2515
|
+
const linkStats = lstatSync(
|
|
2516
|
+
join(basePath, normalizedFilePath.replace(/^\/+/, "")),
|
|
2517
|
+
);
|
|
2518
|
+
if (linkStats?.isSymbolicLink()) {
|
|
2519
|
+
linkedName = basename(resolvedPath);
|
|
2520
|
+
recordSymlinkResolution(
|
|
2521
|
+
join(basePath, normalizedFilePath.replace(/^\/+/, "")),
|
|
2522
|
+
resolvedPath,
|
|
2523
|
+
{
|
|
2524
|
+
basePath,
|
|
2525
|
+
metadata: {
|
|
2526
|
+
resolutionKind: "container-os-package-file",
|
|
2527
|
+
},
|
|
2528
|
+
},
|
|
2529
|
+
);
|
|
2530
|
+
}
|
|
2531
|
+
} catch (_e) {
|
|
2532
|
+
// ignore
|
|
2533
|
+
}
|
|
2534
|
+
const fileType = determineOwnedFileType(
|
|
2535
|
+
normalizedFilePath,
|
|
2536
|
+
stats,
|
|
2537
|
+
commandPathSet,
|
|
2538
|
+
);
|
|
2539
|
+
const properties = [{ name: "internal:SrcFile", value: normalizedFilePath }];
|
|
2540
|
+
if (fileType === "executable") {
|
|
2541
|
+
properties.push({ name: "internal:is_executable", value: "true" });
|
|
2542
|
+
} else if (fileType === "shared_library") {
|
|
2543
|
+
properties.push({ name: "internal:is_shared_library", value: "true" });
|
|
2544
|
+
} else {
|
|
2545
|
+
properties.push({ name: "internal:is_file", value: "true" });
|
|
2546
|
+
}
|
|
2547
|
+
properties.push(...createContainerRiskProperties(fileName, linkedName));
|
|
2548
|
+
properties.push(...createGtfoBinsProperties(fileName, linkedName));
|
|
2549
|
+
// The file path is a purl subpath, not a qualifier: `path` is not among the
|
|
2550
|
+
// qualifiers the generic type allows. The SrcFile property above already
|
|
2551
|
+
// carries the absolute path, so the subpath is stripped to a relative one as
|
|
2552
|
+
// the spec requires.
|
|
2553
|
+
const purl = tryBuildPurl({
|
|
2554
|
+
type: "generic",
|
|
2555
|
+
name: fileName,
|
|
2556
|
+
subpath: normalizedFilePath ? normalizedFilePath.replace(/^\/+/, "") : null,
|
|
2557
|
+
});
|
|
2558
|
+
return {
|
|
2559
|
+
name: fileName,
|
|
2560
|
+
type: "file",
|
|
2561
|
+
...(purl ? { purl, "bom-ref": purl } : {}),
|
|
2562
|
+
hashes,
|
|
2563
|
+
properties,
|
|
2564
|
+
evidence: {
|
|
2565
|
+
identity: [
|
|
2566
|
+
{
|
|
2567
|
+
field: "purl",
|
|
2568
|
+
confidence: 0,
|
|
2569
|
+
methods: [
|
|
2570
|
+
{
|
|
2571
|
+
technique: "filename",
|
|
2572
|
+
confidence: 0,
|
|
2573
|
+
value: normalizedFilePath,
|
|
2574
|
+
},
|
|
2575
|
+
],
|
|
2576
|
+
concludedValue: normalizedFilePath,
|
|
2577
|
+
},
|
|
2578
|
+
],
|
|
2579
|
+
},
|
|
2580
|
+
};
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
async function createOSPackageServices(
|
|
2584
|
+
basePath,
|
|
2585
|
+
packageEntry,
|
|
2586
|
+
componentByPath,
|
|
2587
|
+
) {
|
|
2588
|
+
const services = [];
|
|
2589
|
+
const dependenciesList = [];
|
|
2590
|
+
for (const filePath of packageEntry.files || []) {
|
|
2591
|
+
const serviceDescriptor = parseOwnedServiceFile(basePath, filePath);
|
|
2592
|
+
if (!serviceDescriptor) {
|
|
2593
|
+
continue;
|
|
2594
|
+
}
|
|
2595
|
+
const service = buildOwnedServiceComponent(packageEntry, serviceDescriptor);
|
|
2596
|
+
services.push(service);
|
|
2597
|
+
const dependsOn = new Set([packageEntry.packageRef]);
|
|
2598
|
+
const fileComponent = componentByPath.get(filePath);
|
|
2599
|
+
if (fileComponent?.["bom-ref"]) {
|
|
2600
|
+
dependsOn.add(fileComponent["bom-ref"]);
|
|
2601
|
+
}
|
|
2602
|
+
for (const execPath of serviceDescriptor.execPaths) {
|
|
2603
|
+
const execComponent = componentByPath.get(execPath);
|
|
2604
|
+
if (execComponent?.["bom-ref"]) {
|
|
2605
|
+
dependsOn.add(execComponent["bom-ref"]);
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
dependenciesList.push({
|
|
2609
|
+
ref: service["bom-ref"],
|
|
2610
|
+
dependsOn: Array.from(dependsOn).sort(),
|
|
2611
|
+
});
|
|
2612
|
+
}
|
|
2613
|
+
return { services, dependenciesList };
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
function parseOwnedServiceFile(basePath, filePath) {
|
|
2617
|
+
const normalizedFilePath = normalizeContainerPath(filePath);
|
|
2618
|
+
if (!normalizedFilePath) {
|
|
2619
|
+
return undefined;
|
|
2620
|
+
}
|
|
2621
|
+
const fileContentPath = join(
|
|
2622
|
+
basePath,
|
|
2623
|
+
normalizedFilePath.replace(/^\/+/, ""),
|
|
2624
|
+
);
|
|
2625
|
+
if (!safeExistsSync(fileContentPath)) {
|
|
2626
|
+
return undefined;
|
|
2627
|
+
}
|
|
2628
|
+
if (isSystemdServiceFile(normalizedFilePath)) {
|
|
2629
|
+
const unitData = readFileSync(fileContentPath, "utf-8");
|
|
2630
|
+
const unitMetadata = parseSystemdUnitFile(unitData);
|
|
2631
|
+
return {
|
|
2632
|
+
description: unitMetadata.description,
|
|
2633
|
+
execPaths: unitMetadata.execPaths,
|
|
2634
|
+
filePath: normalizedFilePath,
|
|
2635
|
+
manager: "systemd",
|
|
2636
|
+
name: basename(normalizedFilePath).replace(/\.[^.]+$/, ""),
|
|
2637
|
+
properties: [
|
|
2638
|
+
{ name: "cdx:service:manager", value: "systemd" },
|
|
2639
|
+
{
|
|
2640
|
+
name: "cdx:service:unitType",
|
|
2641
|
+
value: basename(normalizedFilePath).split(".").pop() || "service",
|
|
2642
|
+
},
|
|
2643
|
+
...unitMetadata.properties,
|
|
2644
|
+
],
|
|
2645
|
+
};
|
|
2646
|
+
}
|
|
2647
|
+
if (isInitServiceFile(normalizedFilePath)) {
|
|
2648
|
+
const initData = readFileSync(fileContentPath, "utf-8");
|
|
2649
|
+
const initMetadata = parseInitScriptMetadata(initData);
|
|
2650
|
+
return {
|
|
2651
|
+
description: initMetadata.description,
|
|
2652
|
+
execPaths: initMetadata.execPaths,
|
|
2653
|
+
filePath: normalizedFilePath,
|
|
2654
|
+
manager: "sysvinit",
|
|
2655
|
+
name: initMetadata.name || basename(normalizedFilePath),
|
|
2656
|
+
properties: [
|
|
2657
|
+
{ name: "cdx:service:manager", value: "sysvinit" },
|
|
2658
|
+
...initMetadata.properties,
|
|
2659
|
+
],
|
|
2660
|
+
};
|
|
2661
|
+
}
|
|
2662
|
+
return undefined;
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
function buildOwnedServiceComponent(packageEntry, serviceDescriptor) {
|
|
2666
|
+
const serviceName = sanitizeServiceName(serviceDescriptor.name);
|
|
2667
|
+
const manager = serviceDescriptor.manager;
|
|
2668
|
+
return {
|
|
2669
|
+
"bom-ref": `urn:service:${manager}:${sanitizeServiceRefToken(packageEntry.packageRef)}:${sanitizeServiceRefToken(serviceName)}`,
|
|
2670
|
+
name: serviceName,
|
|
2671
|
+
version: packageEntry.packageVersion || "latest",
|
|
2672
|
+
group: packageEntry.packageName || manager,
|
|
2673
|
+
description: serviceDescriptor.description,
|
|
2674
|
+
properties: uniqueProperties(
|
|
2675
|
+
[
|
|
2676
|
+
{ name: "internal:SrcFile", value: serviceDescriptor.filePath },
|
|
2677
|
+
{ name: "cdx:service:packageRef", value: packageEntry.packageRef },
|
|
2678
|
+
{ name: "cdx:service:packageName", value: packageEntry.packageName },
|
|
2679
|
+
].concat(serviceDescriptor.properties || []),
|
|
2680
|
+
),
|
|
2681
|
+
};
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
function parseSystemdUnitFile(unitData) {
|
|
2685
|
+
const properties = [];
|
|
2686
|
+
const execPaths = new Set();
|
|
2687
|
+
let description;
|
|
2688
|
+
let currentSection = "";
|
|
2689
|
+
for (const rawLine of unitData.split(/\r?\n/)) {
|
|
2690
|
+
const line = rawLine.trim();
|
|
2691
|
+
if (!line || line.startsWith("#") || line.startsWith(";")) {
|
|
2692
|
+
continue;
|
|
2693
|
+
}
|
|
2694
|
+
if (line.startsWith("[") && line.endsWith("]")) {
|
|
2695
|
+
currentSection = line.slice(1, -1);
|
|
2696
|
+
continue;
|
|
2697
|
+
}
|
|
2698
|
+
const equalsIndex = line.indexOf("=");
|
|
2699
|
+
if (equalsIndex === -1) {
|
|
2700
|
+
continue;
|
|
2701
|
+
}
|
|
2702
|
+
const key = line.slice(0, equalsIndex).trim();
|
|
2703
|
+
const value = line.slice(equalsIndex + 1).trim();
|
|
2704
|
+
if (!value) {
|
|
2705
|
+
continue;
|
|
2706
|
+
}
|
|
2707
|
+
if (currentSection === "Unit" && key === "Description") {
|
|
2708
|
+
description = value;
|
|
2709
|
+
}
|
|
2710
|
+
if (currentSection === "Service") {
|
|
2711
|
+
if (key.startsWith("Exec")) {
|
|
2712
|
+
properties.push({ name: `cdx:service:${key}`, value });
|
|
2713
|
+
const execPath = extractExecPath(value);
|
|
2714
|
+
if (execPath) {
|
|
2715
|
+
execPaths.add(execPath);
|
|
2716
|
+
}
|
|
2717
|
+
} else if (
|
|
2718
|
+
["Type", "User", "Group", "WorkingDirectory", "Restart"].includes(key)
|
|
2719
|
+
) {
|
|
2720
|
+
properties.push({ name: `cdx:service:${key}`, value });
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
if (
|
|
2724
|
+
currentSection === "Install" &&
|
|
2725
|
+
["WantedBy", "RequiredBy", "Alias"].includes(key)
|
|
2726
|
+
) {
|
|
2727
|
+
properties.push({ name: `cdx:service:${key}`, value });
|
|
2728
|
+
}
|
|
2729
|
+
if (
|
|
2730
|
+
currentSection === "Unit" &&
|
|
2731
|
+
["After", "Requires", "Wants"].includes(key)
|
|
2732
|
+
) {
|
|
2733
|
+
properties.push({ name: `cdx:service:${key}`, value });
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
return {
|
|
2737
|
+
description,
|
|
2738
|
+
execPaths: Array.from(execPaths).sort(),
|
|
2739
|
+
properties: uniqueProperties(properties),
|
|
2740
|
+
};
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
function parseInitScriptMetadata(initData) {
|
|
2744
|
+
const properties = [];
|
|
2745
|
+
const execPaths = new Set();
|
|
2746
|
+
let description;
|
|
2747
|
+
let name;
|
|
2748
|
+
for (const rawLine of initData.split(/\r?\n/)) {
|
|
2749
|
+
const line = rawLine.trim();
|
|
2750
|
+
if (!line.startsWith("#")) {
|
|
2751
|
+
const execPath = extractExecPath(line);
|
|
2752
|
+
if (execPath) {
|
|
2753
|
+
execPaths.add(execPath);
|
|
2754
|
+
}
|
|
2755
|
+
continue;
|
|
2756
|
+
}
|
|
2757
|
+
const normalized = line.replace(/^#+\s*/, "");
|
|
2758
|
+
if (normalized.startsWith("Provides:")) {
|
|
2759
|
+
const providedName = normalized
|
|
2760
|
+
.replace(/^Provides:\s*/, "")
|
|
2761
|
+
.split(/\s+/)[0];
|
|
2762
|
+
if (providedName) {
|
|
2763
|
+
name = providedName;
|
|
2764
|
+
properties.push({ name: "cdx:service:Provides", value: providedName });
|
|
2765
|
+
}
|
|
2766
|
+
continue;
|
|
2767
|
+
}
|
|
2768
|
+
if (normalized.startsWith("Short-Description:")) {
|
|
2769
|
+
description = normalized.replace(/^Short-Description:\s*/, "");
|
|
2770
|
+
continue;
|
|
2771
|
+
}
|
|
2772
|
+
if (normalized.startsWith("Description:")) {
|
|
2773
|
+
description = normalized.replace(/^Description:\s*/, "");
|
|
2774
|
+
continue;
|
|
2775
|
+
}
|
|
2776
|
+
if (normalized.startsWith("Required-Start:")) {
|
|
2777
|
+
properties.push({
|
|
2778
|
+
name: "cdx:service:RequiredStart",
|
|
2779
|
+
value: normalized.replace(/^Required-Start:\s*/, ""),
|
|
2780
|
+
});
|
|
2781
|
+
continue;
|
|
2782
|
+
}
|
|
2783
|
+
if (normalized.startsWith("Default-Start:")) {
|
|
2784
|
+
properties.push({
|
|
2785
|
+
name: "cdx:service:DefaultStart",
|
|
2786
|
+
value: normalized.replace(/^Default-Start:\s*/, ""),
|
|
2787
|
+
});
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
return {
|
|
2791
|
+
description,
|
|
2792
|
+
execPaths: Array.from(execPaths).sort(),
|
|
2793
|
+
name,
|
|
2794
|
+
properties: uniqueProperties(properties),
|
|
2795
|
+
};
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
function extractExecPath(commandLine) {
|
|
2799
|
+
if (!commandLine) {
|
|
2800
|
+
return undefined;
|
|
2801
|
+
}
|
|
2802
|
+
const sanitized = commandLine.replace(/^[\-@:+!|]+/, "").trim();
|
|
2803
|
+
const match = sanitized.match(/^("[^"]+"|'[^']+'|\S+)/);
|
|
2804
|
+
if (!match?.[1]) {
|
|
2805
|
+
return undefined;
|
|
2806
|
+
}
|
|
2807
|
+
const candidate = match[1].replace(/^['"]|['"]$/g, "");
|
|
2808
|
+
return candidate.startsWith("/")
|
|
2809
|
+
? normalizeContainerPath(candidate)
|
|
2810
|
+
: undefined;
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
function determineOwnedFileType(filePath, stats, commandPathSet) {
|
|
2814
|
+
if (!stats) {
|
|
2815
|
+
return "file";
|
|
2816
|
+
}
|
|
2817
|
+
if (isSharedLibraryPath(filePath)) {
|
|
2818
|
+
return "shared_library";
|
|
2819
|
+
}
|
|
2820
|
+
if (commandPathSet?.has(filePath)) {
|
|
2821
|
+
return "executable";
|
|
2822
|
+
}
|
|
2823
|
+
if (stats.mode & 0o111) {
|
|
2824
|
+
return "executable";
|
|
2825
|
+
}
|
|
2826
|
+
return "file";
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
function isSharedLibraryPath(filePath) {
|
|
2830
|
+
return /(?:^|\/)[^/]+\.(?:so(?:\.[^/]+)?|a|lib|dll)$/i.test(filePath);
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
function isSystemdServiceFile(filePath) {
|
|
2834
|
+
return /\/(?:etc|lib|usr\/lib)\/systemd\/system\/.+\.(?:service|socket|timer|mount|path|target|slice|automount)$/i.test(
|
|
2835
|
+
filePath,
|
|
2836
|
+
);
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
function isInitServiceFile(filePath) {
|
|
2840
|
+
return /\/(?:etc\/init\.d|etc\/rc\.d\/init\.d)\/.+/i.test(filePath);
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
function sanitizeServiceName(value) {
|
|
2844
|
+
return String(value || "service").trim() || "service";
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
function sanitizeServiceRefToken(value) {
|
|
2848
|
+
return (
|
|
2849
|
+
String(value || "service")
|
|
2850
|
+
.toLowerCase()
|
|
2851
|
+
.replace(/[^a-z0-9._:-]+/g, "-")
|
|
2852
|
+
.replace(/^-+|-+$/g, "") || "service"
|
|
2853
|
+
);
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
function uniqueProperties(properties) {
|
|
2857
|
+
const seen = new Set();
|
|
2858
|
+
const uniqueValues = [];
|
|
2859
|
+
for (const property of properties || []) {
|
|
2860
|
+
if (!property?.name || !property?.value) {
|
|
2861
|
+
continue;
|
|
2862
|
+
}
|
|
2863
|
+
const key = `${property.name}\u0000${property.value}`;
|
|
2864
|
+
if (seen.has(key)) {
|
|
2865
|
+
continue;
|
|
2866
|
+
}
|
|
2867
|
+
seen.add(key);
|
|
2868
|
+
uniqueValues.push(property);
|
|
2869
|
+
}
|
|
2870
|
+
return uniqueValues;
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2873
|
+
function uniqueSortedStrings(values) {
|
|
2874
|
+
return Array.from(new Set((values || []).filter(Boolean))).sort();
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
function dedupeServices(services) {
|
|
2878
|
+
const serviceMap = new Map();
|
|
2879
|
+
for (const service of services || []) {
|
|
2880
|
+
if (!service?.["bom-ref"]) {
|
|
2881
|
+
continue;
|
|
2882
|
+
}
|
|
2883
|
+
if (!serviceMap.has(service["bom-ref"])) {
|
|
2884
|
+
serviceMap.set(service["bom-ref"], {
|
|
2885
|
+
...service,
|
|
2886
|
+
properties: uniqueProperties(service.properties),
|
|
2887
|
+
});
|
|
2888
|
+
continue;
|
|
2889
|
+
}
|
|
2890
|
+
const existing = serviceMap.get(service["bom-ref"]);
|
|
2891
|
+
existing.properties = uniqueProperties(
|
|
2892
|
+
(existing.properties || []).concat(service.properties || []),
|
|
2893
|
+
);
|
|
2894
|
+
}
|
|
2895
|
+
return Array.from(serviceMap.values());
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
function normalizeContainerPath(filePath) {
|
|
2899
|
+
if (!filePath) {
|
|
2900
|
+
return undefined;
|
|
2901
|
+
}
|
|
2902
|
+
const normalized = filePath.replace(/\\/g, "/").replace(/^\/+/, "/");
|
|
2903
|
+
return normalized.startsWith("/") ? normalized : `/${normalized}`;
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
/**
|
|
2907
|
+
* Remove the `modularitylabel` qualifier from a component's purl and return its
|
|
2908
|
+
* decoded value.
|
|
2909
|
+
*
|
|
2910
|
+
* The label is a Red Hat modularity concept that trivy expresses as a purl
|
|
2911
|
+
* qualifier. It is not a qualifier the rpm purl type defines, and its value is
|
|
2912
|
+
* a colon-separated stream that trivy percent-encodes, which a canonical purl
|
|
2913
|
+
* does not. Either alone makes the purl unparseable, so the label travels as a
|
|
2914
|
+
* component property instead.
|
|
2915
|
+
*
|
|
2916
|
+
* @param {object} comp Component whose purl is rewritten in place
|
|
2917
|
+
* @returns {string|undefined} The decoded label, when one was present
|
|
2918
|
+
*/
|
|
2919
|
+
function extractModularityLabel(comp) {
|
|
2920
|
+
const [base, query] = `${comp.purl}`.split("?");
|
|
2921
|
+
if (!query) {
|
|
2922
|
+
return undefined;
|
|
2923
|
+
}
|
|
2924
|
+
let label;
|
|
2925
|
+
const kept = query.split("&").filter((pair) => {
|
|
2926
|
+
if (!pair.startsWith("modularitylabel=")) {
|
|
2927
|
+
return true;
|
|
2928
|
+
}
|
|
2929
|
+
try {
|
|
2930
|
+
label = decodeURIComponent(pair.slice("modularitylabel=".length));
|
|
2931
|
+
} catch {
|
|
2932
|
+
label = pair.slice("modularitylabel=".length);
|
|
2933
|
+
}
|
|
2934
|
+
return false;
|
|
2935
|
+
});
|
|
2936
|
+
if (label === undefined) {
|
|
2937
|
+
return undefined;
|
|
2938
|
+
}
|
|
2939
|
+
comp.purl = kept.length ? `${base}?${kept.join("&")}` : base;
|
|
2940
|
+
return label;
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2943
|
+
// Detect common sdks and runtimes from the name
|
|
2944
|
+
function detectSdksRuntimes(comp, bundledSdks, bundledRuntimes) {
|
|
2945
|
+
if (!comp?.name) {
|
|
2946
|
+
return;
|
|
2947
|
+
}
|
|
2948
|
+
if (/dotnet[6-9]?-sdk/.test(comp.name)) {
|
|
2949
|
+
bundledSdks.add(comp.name);
|
|
2950
|
+
}
|
|
2951
|
+
if (
|
|
2952
|
+
/dotnet[6-9]?-runtime/.test(comp.name) ||
|
|
2953
|
+
comp.name.includes("aspnet-runtime") ||
|
|
2954
|
+
/aspnetcore[6-9]?-runtime/.test(comp.name)
|
|
2955
|
+
) {
|
|
2956
|
+
bundledRuntimes.add(comp.name);
|
|
2957
|
+
}
|
|
2958
|
+
// TODO: Need to test this for a range of base images
|
|
2959
|
+
if (COMMON_RUNTIMES.includes(comp.name)) {
|
|
2960
|
+
bundledRuntimes.add(comp.name);
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
const retrieveDependencies = (tmpDependencies, origBomRef, comp) => {
|
|
2965
|
+
try {
|
|
2966
|
+
const tmpDependsOn =
|
|
2967
|
+
tmpDependencies[origBomRef] || tmpDependencies[comp["bom-ref"]] || [];
|
|
2968
|
+
const dependsOn = new Set();
|
|
2969
|
+
tmpDependsOn.forEach((d) => {
|
|
2970
|
+
try {
|
|
2971
|
+
const compPurl = Purl.parse(comp.purl);
|
|
2972
|
+
const tmpPurl = Purl.parse(d.replace("none", compPurl.type));
|
|
2973
|
+
tmpPurl.type = compPurl.type;
|
|
2974
|
+
// FIXME: Check if this hack is still needed with the latest trivy
|
|
2975
|
+
if (OS_PURL_TYPES.includes(compPurl.type)) {
|
|
2976
|
+
tmpPurl.namespace = compPurl.namespace;
|
|
2977
|
+
tmpPurl.qualifiers = tmpPurl.qualifiers || {};
|
|
2978
|
+
if (compPurl.qualifiers) {
|
|
2979
|
+
if (compPurl.qualifiers.distro_name) {
|
|
2980
|
+
tmpPurl.qualifiers.distro_name = compPurl.qualifiers.distro_name;
|
|
2981
|
+
}
|
|
2982
|
+
if (compPurl.qualifiers.distro) {
|
|
2983
|
+
tmpPurl.qualifiers.distro = compPurl.qualifiers.distro;
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
if (tmpPurl.qualifiers) {
|
|
2988
|
+
if (
|
|
2989
|
+
tmpPurl.qualifiers.epoch &&
|
|
2990
|
+
!tmpPurl.version.startsWith(`${tmpPurl.qualifiers.epoch}:`)
|
|
2991
|
+
) {
|
|
2992
|
+
tmpPurl.version = `${tmpPurl.qualifiers.epoch}:${tmpPurl.version}`;
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
// Prevents purls ending with ?
|
|
2996
|
+
if (!Object.keys(tmpPurl.qualifiers).length) {
|
|
2997
|
+
tmpPurl.qualifiers = undefined;
|
|
2998
|
+
}
|
|
2999
|
+
dependsOn.add(decodeURIComponent(tmpPurl.toString()));
|
|
3000
|
+
} catch (_e) {
|
|
3001
|
+
// ignore
|
|
3002
|
+
}
|
|
3003
|
+
});
|
|
3004
|
+
return { ref: comp["bom-ref"], dependsOn: Array.from(dependsOn).sort() };
|
|
3005
|
+
} catch (_e) {
|
|
3006
|
+
// ignore
|
|
3007
|
+
}
|
|
3008
|
+
return undefined;
|
|
3009
|
+
};
|
|
3010
|
+
|
|
3011
|
+
function isHostInspectionPath(value) {
|
|
3012
|
+
if (!value) {
|
|
3013
|
+
return false;
|
|
3014
|
+
}
|
|
3015
|
+
if (platform === "darwin") {
|
|
3016
|
+
return value.startsWith("/");
|
|
3017
|
+
}
|
|
3018
|
+
if (platform === "windows") {
|
|
3019
|
+
return /^[a-z]:\\/i.test(value) || /^\\\\/.test(value);
|
|
3020
|
+
}
|
|
3021
|
+
return false;
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
function isDarwinSystemHostPath(value) {
|
|
3025
|
+
if (typeof value !== "string") {
|
|
3026
|
+
return false;
|
|
3027
|
+
}
|
|
3028
|
+
return (
|
|
3029
|
+
value.startsWith("/bin/") ||
|
|
3030
|
+
value.startsWith("/sbin/") ||
|
|
3031
|
+
value.startsWith("/System/") ||
|
|
3032
|
+
value.startsWith("/usr/bin/") ||
|
|
3033
|
+
value.startsWith("/usr/libexec/") ||
|
|
3034
|
+
value.startsWith("/usr/sbin/") ||
|
|
3035
|
+
value.startsWith("/Library/Apple/System/") ||
|
|
3036
|
+
value.startsWith("/System/Volumes/Preboot/Cryptexes/")
|
|
3037
|
+
);
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
function shouldInspectComponentHostPath(value) {
|
|
3041
|
+
if (!isHostInspectionPath(value)) {
|
|
3042
|
+
return false;
|
|
3043
|
+
}
|
|
3044
|
+
if (platform === "darwin") {
|
|
3045
|
+
return !(
|
|
3046
|
+
value.toLowerCase().endsWith(".plist") || isDarwinSystemHostPath(value)
|
|
3047
|
+
);
|
|
3048
|
+
}
|
|
3049
|
+
return true;
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
function shouldInspectComponentTrust(component) {
|
|
3053
|
+
if (platform !== "darwin") {
|
|
3054
|
+
return true;
|
|
3055
|
+
}
|
|
3056
|
+
const queryCategory = `${
|
|
3057
|
+
(component?.properties || []).find(
|
|
3058
|
+
(property) => property?.name === "cdx:osquery:category",
|
|
3059
|
+
)?.value || ""
|
|
3060
|
+
}`.trim();
|
|
3061
|
+
if (!queryCategory) {
|
|
3062
|
+
return true;
|
|
3063
|
+
}
|
|
3064
|
+
return new Set(["launchd_services", "startup_items", "running_apps"]).has(
|
|
3065
|
+
queryCategory,
|
|
3066
|
+
);
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
function extractComponentHostPaths(component) {
|
|
3070
|
+
if (!shouldInspectComponentTrust(component)) {
|
|
3071
|
+
return [];
|
|
3072
|
+
}
|
|
3073
|
+
const pathPropertyNames = new Set([
|
|
3074
|
+
"path",
|
|
3075
|
+
"bundle_path",
|
|
3076
|
+
"bundle_executable",
|
|
3077
|
+
"executable",
|
|
3078
|
+
"program",
|
|
3079
|
+
"image_path",
|
|
3080
|
+
"binary_path",
|
|
3081
|
+
"action_path",
|
|
3082
|
+
]);
|
|
3083
|
+
const paths = [];
|
|
3084
|
+
for (const property of component?.properties || []) {
|
|
3085
|
+
const propertyName = `${property?.name || ""}`.toLowerCase();
|
|
3086
|
+
if (!pathPropertyNames.has(propertyName)) {
|
|
3087
|
+
continue;
|
|
3088
|
+
}
|
|
3089
|
+
const normalizedValue = `${property?.value || ""}`.trim();
|
|
3090
|
+
if (shouldInspectComponentHostPath(normalizedValue)) {
|
|
3091
|
+
paths.push(normalizedValue);
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
return uniqueSortedStrings(paths);
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
function createHostTrustFindingComponent(finding) {
|
|
3098
|
+
if (!finding?.kind || !finding?.name) {
|
|
3099
|
+
return undefined;
|
|
3100
|
+
}
|
|
3101
|
+
const purl = build({
|
|
3102
|
+
type: "generic",
|
|
3103
|
+
namespace: "host-trust",
|
|
3104
|
+
name: finding.name,
|
|
3105
|
+
version: finding.version || "observed",
|
|
3106
|
+
});
|
|
3107
|
+
const component = {
|
|
3108
|
+
"bom-ref": decodeURIComponent(purl),
|
|
3109
|
+
name: finding.name,
|
|
3110
|
+
version: finding.version || "observed",
|
|
3111
|
+
description: finding.description,
|
|
3112
|
+
purl,
|
|
3113
|
+
type: "data",
|
|
3114
|
+
properties: uniqueProperties(
|
|
3115
|
+
(finding.properties || []).concat([
|
|
3116
|
+
{ name: "cdx:trustinspector:kind", value: finding.kind },
|
|
3117
|
+
...(finding.path
|
|
3118
|
+
? [{ name: "internal:SrcFile", value: finding.path }]
|
|
3119
|
+
: []),
|
|
3120
|
+
]),
|
|
3121
|
+
),
|
|
3122
|
+
};
|
|
3123
|
+
if (finding.sha256) {
|
|
3124
|
+
component.hashes = [{ alg: "SHA-256", content: finding.sha256 }];
|
|
3125
|
+
}
|
|
3126
|
+
attachIdentityTools(component, trustInspectorToolRefs());
|
|
3127
|
+
return component;
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
/**
|
|
3131
|
+
* Batch-enrich operating-system components with host-path trust data using the
|
|
3132
|
+
* `trustinspector` helper on darwin and Windows.
|
|
3133
|
+
*
|
|
3134
|
+
* @param {Object[]} [components=[]] OS components to enrich.
|
|
3135
|
+
* @returns {{components: Object[], tools: Object[]}} The enriched components
|
|
3136
|
+
* and any trust-inspector tool components that should be attached to the BOM
|
|
3137
|
+
* metadata.
|
|
3138
|
+
*/
|
|
3139
|
+
export function enrichOSComponentsWithTrustData(components = []) {
|
|
3140
|
+
if (!["darwin", "windows"].includes(platform) || !TRUSTINSPECTOR_BIN) {
|
|
3141
|
+
return { components, tools: [] };
|
|
3142
|
+
}
|
|
3143
|
+
const mergedComponents = [...components];
|
|
3144
|
+
const pathInspectionCandidates = uniqueSortedStrings(
|
|
3145
|
+
components.flatMap((component) => extractComponentHostPaths(component)),
|
|
3146
|
+
);
|
|
3147
|
+
if (pathInspectionCandidates.length) {
|
|
3148
|
+
const inspectionMap = new Map();
|
|
3149
|
+
const batchSize = 200;
|
|
3150
|
+
for (
|
|
3151
|
+
let offset = 0;
|
|
3152
|
+
offset < pathInspectionCandidates.length;
|
|
3153
|
+
offset += batchSize
|
|
3154
|
+
) {
|
|
3155
|
+
const inspectionBatch = pathInspectionCandidates.slice(
|
|
3156
|
+
offset,
|
|
3157
|
+
offset + batchSize,
|
|
3158
|
+
);
|
|
3159
|
+
const inspectionData = executeTrustInspector(
|
|
3160
|
+
["paths", ...inspectionBatch],
|
|
3161
|
+
{
|
|
3162
|
+
target: `${inspectionBatch.length} path(s)`,
|
|
3163
|
+
},
|
|
3164
|
+
);
|
|
3165
|
+
for (const inspection of inspectionData?.inspections || []) {
|
|
3166
|
+
inspectionMap.set(
|
|
3167
|
+
inspection.path,
|
|
3168
|
+
uniqueProperties(inspection.properties || []),
|
|
3169
|
+
);
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
if (inspectionMap.size) {
|
|
3173
|
+
for (const component of mergedComponents) {
|
|
3174
|
+
const matchingProperties = extractComponentHostPaths(component)
|
|
3175
|
+
.map((candidatePath) => inspectionMap.get(candidatePath))
|
|
3176
|
+
.filter(Boolean)
|
|
3177
|
+
.flat();
|
|
3178
|
+
if (!matchingProperties.length) {
|
|
3179
|
+
continue;
|
|
3180
|
+
}
|
|
3181
|
+
component.properties = uniqueProperties(
|
|
3182
|
+
(component.properties || []).concat(matchingProperties),
|
|
3183
|
+
);
|
|
3184
|
+
attachIdentityTools(component, trustInspectorToolRefs());
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
const hostData = executeTrustInspector(["host"], { target: platform });
|
|
3189
|
+
const hostComponents = (hostData?.hostFindings || [])
|
|
3190
|
+
.map((finding) => createHostTrustFindingComponent(finding))
|
|
3191
|
+
.filter(Boolean);
|
|
3192
|
+
if (hostComponents.length) {
|
|
3193
|
+
mergedComponents.push(...hostComponents);
|
|
3194
|
+
}
|
|
3195
|
+
return {
|
|
3196
|
+
components: mergedComponents,
|
|
3197
|
+
tools:
|
|
3198
|
+
pathInspectionCandidates.length || hostComponents.length
|
|
3199
|
+
? getPluginToolComponents(["trustinspector"])
|
|
3200
|
+
: [],
|
|
3201
|
+
};
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
/**
|
|
3205
|
+
* Execute a SQL query against the bundled osquery binary and return the parsed
|
|
3206
|
+
* JSON result.
|
|
3207
|
+
*
|
|
3208
|
+
* @param {string} query The SQL query string to run.
|
|
3209
|
+
* @returns {Object|undefined} The parsed JSON result, or undefined when the
|
|
3210
|
+
* binary is unavailable, dry-run blocks execution, or the output cannot be
|
|
3211
|
+
* parsed.
|
|
3212
|
+
*/
|
|
3213
|
+
export function executeOsQuery(query) {
|
|
3214
|
+
if (isDryRun) {
|
|
3215
|
+
recordActivity({
|
|
3216
|
+
kind: "osquery",
|
|
3217
|
+
reason:
|
|
3218
|
+
"Dry run mode blocks osquery execution and reports the query instead.",
|
|
3219
|
+
status: "blocked",
|
|
3220
|
+
target: query,
|
|
3221
|
+
});
|
|
3222
|
+
return undefined;
|
|
3223
|
+
}
|
|
3224
|
+
if (OSQUERY_BIN) {
|
|
3225
|
+
if (!query.endsWith(";")) {
|
|
3226
|
+
query = `${query};`;
|
|
3227
|
+
}
|
|
3228
|
+
const args = ["--S", "--disable_database", "--json", query];
|
|
3229
|
+
// On darwin, we need to disable the safety check and run cdxgen with sudo
|
|
3230
|
+
// https://github.com/osquery/osquery/issues/1382
|
|
3231
|
+
if (platform === "darwin") {
|
|
3232
|
+
args.push("--allow_unsafe");
|
|
3233
|
+
args.push("--disable_logging");
|
|
3234
|
+
args.push("--disable_events");
|
|
3235
|
+
}
|
|
3236
|
+
if (DEBUG_MODE) {
|
|
3237
|
+
console.log("Executing", OSQUERY_BIN, args.join(" "));
|
|
3238
|
+
}
|
|
3239
|
+
const result = safeSpawnSync(OSQUERY_BIN, args);
|
|
3240
|
+
if (result.status !== 0 || result.error) {
|
|
3241
|
+
if (
|
|
3242
|
+
DEBUG_MODE &&
|
|
3243
|
+
result.stderr &&
|
|
3244
|
+
!result.stderr.includes("no such table")
|
|
3245
|
+
) {
|
|
3246
|
+
console.error(result.stdout, result.stderr);
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
if (result) {
|
|
3250
|
+
const stdout = result.stdout;
|
|
3251
|
+
if (stdout) {
|
|
3252
|
+
const cmdOutput = stdout;
|
|
3253
|
+
if (cmdOutput !== "") {
|
|
3254
|
+
try {
|
|
3255
|
+
return JSON.parse(cmdOutput);
|
|
3256
|
+
} catch (_err) {
|
|
3257
|
+
// ignore
|
|
3258
|
+
if (DEBUG_MODE) {
|
|
3259
|
+
console.log("Unable to parse the output from query", query);
|
|
3260
|
+
console.log(
|
|
3261
|
+
"This could be due to the amount of data returned or the query being invalid for the given platform.",
|
|
3262
|
+
);
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
return undefined;
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
return undefined;
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3273
|
+
/**
|
|
3274
|
+
* Method to execute dosai to create slices for dotnet
|
|
3275
|
+
*
|
|
3276
|
+
* @param {string} src Source Path
|
|
3277
|
+
* @param {string} slicesFile Slices file name
|
|
3278
|
+
* @returns boolean
|
|
3279
|
+
*/
|
|
3280
|
+
export function getDotnetSlices(src, slicesFile) {
|
|
3281
|
+
if (!DOSAI_BIN) {
|
|
3282
|
+
return false;
|
|
3283
|
+
}
|
|
3284
|
+
const args = ["methods", "--path", src, "--o", slicesFile];
|
|
3285
|
+
if (DEBUG_MODE) {
|
|
3286
|
+
console.log("Executing", DOSAI_BIN, args.join(" "));
|
|
3287
|
+
}
|
|
3288
|
+
const result = safeSpawnSync(DOSAI_BIN, args, {
|
|
3289
|
+
cwd: src,
|
|
3290
|
+
});
|
|
3291
|
+
if (
|
|
3292
|
+
result?.stdout?.includes(
|
|
3293
|
+
"You must install or update .NET to run this application",
|
|
3294
|
+
) ||
|
|
3295
|
+
result?.stderr?.includes(
|
|
3296
|
+
"You must install or update .NET to run this application",
|
|
3297
|
+
)
|
|
3298
|
+
) {
|
|
3299
|
+
console.log(
|
|
3300
|
+
"Dotnet SDK is not installed. Please use the cdxgen dotnet container images to generate slices for this project.",
|
|
3301
|
+
);
|
|
3302
|
+
console.log(
|
|
3303
|
+
"Alternatively, download the dosai self-contained binary (-full suffix) from https://github.com/owasp-dep-scan/dosai/releases and set the environment variable DOSAI_CMD with its location.",
|
|
3304
|
+
);
|
|
3305
|
+
}
|
|
3306
|
+
if (result.status !== 0 || result.error) {
|
|
3307
|
+
if (DEBUG_MODE && result.error) {
|
|
3308
|
+
if (result.stderr) {
|
|
3309
|
+
console.error(result.stdout, result.stderr);
|
|
3310
|
+
} else {
|
|
3311
|
+
console.log("Check if dosai plugin was installed successfully.");
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
return false;
|
|
3315
|
+
}
|
|
3316
|
+
return true;
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
/**
|
|
3320
|
+
* Method to generate binary SBOM using blint
|
|
3321
|
+
*
|
|
3322
|
+
* @param {string} src Path to binary or its directory
|
|
3323
|
+
* @param {string} binaryBomFile Path to binary
|
|
3324
|
+
* @param {boolean} deepMode Deep mode flag
|
|
3325
|
+
*
|
|
3326
|
+
* @return {boolean} Result of the generation
|
|
3327
|
+
*/
|
|
3328
|
+
export function getBinaryBom(src, binaryBomFile, deepMode) {
|
|
3329
|
+
if (!BLINT_BIN) {
|
|
3330
|
+
return false;
|
|
3331
|
+
}
|
|
3332
|
+
const args = ["sbom", "-i", resolve(src), "-o", binaryBomFile];
|
|
3333
|
+
if (deepMode) {
|
|
3334
|
+
args.push("--deep");
|
|
3335
|
+
}
|
|
3336
|
+
if (DEBUG_MODE) {
|
|
3337
|
+
console.log("Executing", BLINT_BIN, args.join(" "));
|
|
3338
|
+
}
|
|
3339
|
+
const lstatResult = lstatSync(src, { throwIfNoEntry: false });
|
|
3340
|
+
if (!lstatResult) {
|
|
3341
|
+
console.log(`Source path ${src} does not exist`);
|
|
3342
|
+
return false;
|
|
3343
|
+
}
|
|
3344
|
+
const cwd = lstatResult.isDirectory() ? src : dirname(src);
|
|
3345
|
+
const result = safeSpawnSync(BLINT_BIN, args, {
|
|
3346
|
+
cwd,
|
|
3347
|
+
});
|
|
3348
|
+
if (result.status !== 0 || result.error) {
|
|
3349
|
+
if (result.stderr) {
|
|
3350
|
+
console.error(result.stdout, result.stderr);
|
|
3351
|
+
} else {
|
|
3352
|
+
console.log(
|
|
3353
|
+
"Install blint using 'pip install blint' or use the cdxgen container image.",
|
|
3354
|
+
);
|
|
3355
|
+
}
|
|
3356
|
+
return false;
|
|
3357
|
+
}
|
|
3358
|
+
return true;
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
async function fileComponents(basePath, fileList, fileType) {
|
|
3362
|
+
const hashResults = await mapWithConcurrency(fileList, (f) =>
|
|
3363
|
+
multiChecksumFile(["md5", "sha1"], join(basePath, f))
|
|
3364
|
+
.then((hashValues) => [
|
|
3365
|
+
{ alg: "MD5", content: hashValues["md5"] },
|
|
3366
|
+
{ alg: "SHA-1", content: hashValues["sha1"] },
|
|
3367
|
+
])
|
|
3368
|
+
.catch(() => undefined),
|
|
3369
|
+
);
|
|
3370
|
+
const components = [];
|
|
3371
|
+
for (let i = 0; i < fileList.length; i++) {
|
|
3372
|
+
let f = fileList[i];
|
|
3373
|
+
const hashes = hashResults[i];
|
|
3374
|
+
if (!f.startsWith("/")) {
|
|
3375
|
+
f = `/${f}`;
|
|
3376
|
+
}
|
|
3377
|
+
const name = basename(f);
|
|
3378
|
+
let linkedName;
|
|
3379
|
+
try {
|
|
3380
|
+
const resolvedPath = realpathSync(join(basePath, f.replace(/^\/+/, "")));
|
|
3381
|
+
const linkStats = lstatSync(join(basePath, f.replace(/^\/+/, "")));
|
|
3382
|
+
if (linkStats?.isSymbolicLink()) {
|
|
3383
|
+
linkedName = basename(resolvedPath);
|
|
3384
|
+
recordSymlinkResolution(
|
|
3385
|
+
join(basePath, f.replace(/^\/+/, "")),
|
|
3386
|
+
resolvedPath,
|
|
3387
|
+
{
|
|
3388
|
+
basePath,
|
|
3389
|
+
metadata: {
|
|
3390
|
+
resolutionKind: "container-binary",
|
|
3391
|
+
},
|
|
3392
|
+
},
|
|
3393
|
+
);
|
|
3394
|
+
}
|
|
3395
|
+
} catch (_e) {
|
|
3396
|
+
// ignore
|
|
3397
|
+
}
|
|
3398
|
+
const purl = genericPurl(name);
|
|
3399
|
+
let isExecutable;
|
|
3400
|
+
let isSetuid;
|
|
3401
|
+
let isSetgid;
|
|
3402
|
+
let isSticky;
|
|
3403
|
+
try {
|
|
3404
|
+
const stats = statSync(join(basePath, f.replace(/^\/+/, "")));
|
|
3405
|
+
const mode = stats.mode;
|
|
3406
|
+
isExecutable = !!(mode & 0o111);
|
|
3407
|
+
isSetuid = !!(mode & 0o4000);
|
|
3408
|
+
isSetgid = !!(mode & 0o2000);
|
|
3409
|
+
isSticky = !!(mode & 0o1000);
|
|
3410
|
+
} catch (_e) {
|
|
3411
|
+
// ignore
|
|
3412
|
+
}
|
|
3413
|
+
const properties = [{ name: "internal:SrcFile", value: f }];
|
|
3414
|
+
if (fileType === "executable" && isExecutable !== undefined) {
|
|
3415
|
+
properties.push({
|
|
3416
|
+
name: `internal:is_${fileType}`,
|
|
3417
|
+
value: isExecutable.toString(),
|
|
3418
|
+
});
|
|
3419
|
+
} else {
|
|
3420
|
+
properties.push({ name: `internal:is_${fileType}`, value: "true" });
|
|
3421
|
+
}
|
|
3422
|
+
if (isSetuid) {
|
|
3423
|
+
properties.push({ name: "internal:has_setuid", value: "true" });
|
|
3424
|
+
}
|
|
3425
|
+
if (isSetgid) {
|
|
3426
|
+
properties.push({ name: "internal:has_setgid", value: "true" });
|
|
3427
|
+
}
|
|
3428
|
+
if (isSticky) {
|
|
3429
|
+
properties.push({ name: "internal:has_sticky", value: "true" });
|
|
3430
|
+
}
|
|
3431
|
+
properties.push(...createContainerRiskProperties(name, linkedName));
|
|
3432
|
+
properties.push(...createGtfoBinsProperties(name, linkedName));
|
|
3433
|
+
components.push({
|
|
3434
|
+
name,
|
|
3435
|
+
type: "file",
|
|
3436
|
+
purl,
|
|
3437
|
+
"bom-ref": purl,
|
|
3438
|
+
hashes,
|
|
3439
|
+
properties,
|
|
3440
|
+
evidence: {
|
|
3441
|
+
identity: [
|
|
3442
|
+
{
|
|
3443
|
+
field: "purl",
|
|
3444
|
+
confidence: 0,
|
|
3445
|
+
methods: [
|
|
3446
|
+
{
|
|
3447
|
+
technique: "filename",
|
|
3448
|
+
confidence: 0,
|
|
3449
|
+
value: f,
|
|
3450
|
+
},
|
|
3451
|
+
],
|
|
3452
|
+
concludedValue: f,
|
|
3453
|
+
},
|
|
3454
|
+
],
|
|
3455
|
+
},
|
|
3456
|
+
});
|
|
3457
|
+
}
|
|
3458
|
+
return components;
|
|
3459
|
+
}
|