@cdxgen/cdxgen 13.5.0 → 13.7.0
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 +17 -10
- package/bin/audit.js +7 -4
- package/bin/cdxgen.js +260 -37
- package/bin/convert.js +146 -22
- package/bin/evinse.js +28 -3
- package/bin/repl.js +47 -3
- package/bin/sign.js +211 -34
- package/bin/tracebom.js +18 -6
- package/bin/validate.js +24 -3
- package/bin/verify.js +113 -5
- package/data/behavior-taxonomy.schema.json +1491 -0
- package/data/cyclonedx-2.0-bundled.schema.json +18606 -5528
- package/data/cyclonedx-2.0-cryptography-defs.schema.json +857 -0
- package/data/license-aliases.json +24 -2
- package/data/pqc-migration.json +239 -0
- package/data/remediations.json +176 -0
- package/data/rules/ai-bom.yaml +1 -1
- package/data/rules/brotli-decoder.yaml +107 -0
- package/data/rules/cbom-security.yaml +4 -3
- package/data/rules/package-integrity.yaml +28 -0
- package/data/rules/vscode-extensions.yaml +75 -0
- package/data/spdx-license-list.json +121 -7
- package/data/spdx-licenses.json +69 -0
- package/data/spdx.schema.json +845 -2
- package/lib/audit/index.js +25 -3
- package/lib/cli/bomAssembly.js +172 -4
- package/lib/cli/cliOptions.js +121 -17
- package/lib/cli/index.js +720 -902
- package/lib/cli/jsBom.js +156 -64
- package/lib/cli/jvmBom.js +602 -86
- package/lib/cli/managedBom.js +161 -19
- package/lib/cli/nativeBom.js +52 -5
- package/lib/core/boundedFile.js +344 -0
- package/lib/core/brotliProbe.js +182 -0
- package/lib/core/env.js +34 -0
- package/lib/core/fs.js +138 -3
- package/lib/core/globPatterns.js +218 -0
- package/lib/core/httpClient.js +98 -1
- package/lib/core/lockPaths.js +38 -0
- package/lib/core/paths.js +24 -1
- package/lib/core/processTree.js +100 -8
- package/lib/core/projectConfig.js +332 -25
- package/lib/core/toolRegistry.js +184 -0
- package/lib/ecosystems/cppEvidence.js +263 -106
- package/lib/ecosystems/cppIncludeResolver.js +16 -5
- package/lib/ecosystems/denoutils.js +2 -2
- package/lib/ecosystems/ecosystems.js +25 -10
- package/lib/ecosystems/gemHostProbe.js +301 -0
- package/lib/ecosystems/golem.js +3 -8
- package/lib/ecosystems/gradleutils.js +1909 -229
- package/lib/ecosystems/jsEvidence.js +90 -38
- package/lib/ecosystems/kosi.js +3 -5
- package/lib/ecosystems/npmVersionResolution.js +376 -0
- package/lib/ecosystems/parsers-bazel.js +107 -6
- package/lib/ecosystems/parsers-js.js +505 -22
- package/lib/ecosystems/parsers-misc.js +209 -20
- package/lib/ecosystems/parsers-python.js +178 -30
- package/lib/ecosystems/parsers-rust.js +117 -15
- package/lib/ecosystems/pythonutils.js +128 -2
- package/lib/ecosystems/remote/huggingface.js +22 -0
- package/lib/ecosystems/rubyutils.js +538 -31
- package/lib/ecosystems/rusi.js +2 -4
- package/lib/ecosystems/scalacliutils.js +2 -0
- package/lib/ecosystems/scalasem.js +798 -152
- package/lib/ecosystems/vendoredCode.js +237 -3
- package/lib/ecosystems/venvProbe.js +360 -0
- package/lib/evinser/evinser.js +536 -71
- package/lib/evinser/scalasem.js +5 -1
- package/lib/helpers/agenticToolutils.js +525 -94
- package/lib/helpers/bomSigner.js +43 -9
- package/lib/helpers/jssSigner.js +1276 -0
- package/lib/helpers/vsixutils.js +1574 -315
- package/lib/inventory/agentBlueprint.js +193 -0
- package/lib/inventory/agentToolNames.js +145 -0
- package/lib/inventory/aiCollector.js +153 -7
- package/lib/inventory/aiInventory.js +44 -8
- package/lib/inventory/aiProviders.js +324 -0
- package/lib/inventory/atomUtils.js +77 -0
- package/lib/inventory/bomUtils.js +8 -1
- package/lib/inventory/brotliDecoder.js +142 -0
- package/lib/inventory/buildToolRateLimit.js +1 -1
- package/lib/inventory/cbomutils.js +56 -7
- package/lib/inventory/ciParsers/githubActionsLock.js +821 -0
- package/lib/inventory/citations.js +31 -2
- package/lib/inventory/communityAiConfigParser.js +978 -80
- package/lib/inventory/cyclonedx20.js +152 -0
- package/lib/inventory/cyclonedx20Common.js +286 -0
- package/lib/inventory/cyclonedx20Derived.js +159 -0
- package/lib/inventory/cyclonedx20Parties.js +552 -0
- package/lib/inventory/cyclonedx20Read.js +768 -0
- package/lib/inventory/cyclonedx20ReadCommon.js +374 -0
- package/lib/inventory/cyclonedx20ReadComponents.js +692 -0
- package/lib/inventory/cyclonedx20ReadContext.js +380 -0
- package/lib/inventory/cyclonedx20ReadEvidence.js +466 -0
- package/lib/inventory/cyclonedx20ReadSections.js +599 -0
- package/lib/inventory/cyclonedx20Render.js +766 -0
- package/lib/inventory/cyclonedx20RenderComponents.js +716 -0
- package/lib/inventory/cyclonedx20RenderDefinitions.js +405 -0
- package/lib/inventory/cyclonedx20RenderEvidence.js +726 -0
- package/lib/inventory/cyclonedx20RenderHardware.js +203 -0
- package/lib/inventory/cyclonedx20RenderPatents.js +174 -0
- package/lib/inventory/cyclonedx20RenderRefIds.js +314 -0
- package/lib/inventory/cyclonedx20RenderSections.js +499 -0
- package/lib/inventory/cyclonedx20Tables.js +594 -0
- package/lib/inventory/deps.js +141 -13
- package/lib/inventory/dosai.js +375 -6
- package/lib/inventory/dosaiParsers.js +3 -6
- package/lib/inventory/dotnetRuntime.js +367 -0
- package/lib/inventory/ecosystemFormulationParsers.js +979 -0
- package/lib/inventory/envcontext.js +436 -48
- package/lib/inventory/evidenceUtils.js +13 -1
- package/lib/inventory/fetchBatch.js +1 -1
- package/lib/inventory/formulationParsers.js +124 -14
- package/lib/inventory/gtfobins.js +13 -0
- package/lib/inventory/hbom.js +45 -6
- package/lib/inventory/hbomLoader.js +166 -0
- package/lib/inventory/hostRuntimeProbe.js +686 -0
- package/lib/inventory/jvmLocalRepos.js +9 -2
- package/lib/inventory/jvmToolEnv.js +29 -0
- package/lib/inventory/licenseEnhancer.js +12 -0
- package/lib/inventory/mavenCentralMirror.js +534 -0
- package/lib/inventory/mcpConfigParser.js +5 -0
- package/lib/inventory/nodeBuildInfo.js +218 -0
- package/lib/inventory/nodeHostProbe.js +202 -0
- package/lib/inventory/occurrenceKey.js +90 -0
- package/lib/inventory/osPackageResolver.js +182 -0
- package/lib/inventory/phpBuildInfo.js +10 -59
- package/lib/inventory/phpHostProbe.js +32 -488
- package/lib/inventory/pqcMigration.js +426 -0
- package/lib/inventory/rubyBuildInfo.js +972 -0
- package/lib/inventory/rubyHostProbe.js +218 -0
- package/lib/inventory/rustFormulationParser.js +21 -10
- package/lib/inventory/scalaCoords.js +4 -3
- package/lib/inventory/scalaDedupe.js +440 -0
- package/lib/inventory/source.js +114 -8
- package/lib/managers/binary.js +337 -3
- package/lib/managers/docker.js +79 -0
- package/lib/parsers/huggingfaceManifest.js +9 -1
- package/lib/parsers/huggingfaceUtils.js +18 -0
- package/lib/parsers/kiroPermissions.js +166 -0
- package/lib/parsers/largeJson.js +28 -11
- package/lib/parsers/vscodeExtensionId.js +183 -0
- package/lib/server/server.js +48 -35
- package/lib/stages/postgen/postgen.js +41 -1
- package/lib/stages/postgen/ruleEngine.js +13 -5
- package/lib/stages/postgen/specVersionCompat.js +76 -0
- package/lib/stages/pregen/envAudit.js +1 -0
- package/lib/stages/pregen/pregen.js +114 -57
- package/lib/validator/bomValidator.js +14 -2
- package/lib/validator/complianceEngine.js +30 -3
- package/lib/validator/complianceRules.js +1416 -4
- package/lib/validator/generated/validate-1.6.mjs +1 -1
- package/lib/validator/generated/validate-1.7.mjs +1 -1
- package/lib/validator/generated/validate-2.0.mjs +57554 -13851
- package/lib/validator/index.js +49 -8
- package/lib/validator/reporters/json.js +6 -0
- package/package.json +11 -10
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +6 -2
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +29 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +6 -0
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +3 -18
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +15 -0
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/boundedFile.d.ts +100 -0
- package/types/lib/core/boundedFile.d.ts.map +1 -0
- package/types/lib/core/brotliProbe.d.ts +37 -0
- package/types/lib/core/brotliProbe.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +14 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +4 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/globPatterns.d.ts +70 -0
- package/types/lib/core/globPatterns.d.ts.map +1 -0
- package/types/lib/core/httpClient.d.ts +24 -0
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/lockPaths.d.ts +19 -0
- package/types/lib/core/lockPaths.d.ts.map +1 -0
- package/types/lib/core/paths.d.ts +9 -0
- package/types/lib/core/paths.d.ts.map +1 -1
- package/types/lib/core/processTree.d.ts +17 -4
- package/types/lib/core/processTree.d.ts.map +1 -1
- package/types/lib/core/projectConfig.d.ts +47 -8
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/toolRegistry.d.ts +36 -0
- package/types/lib/core/toolRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +40 -13
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +2 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gemHostProbe.d.ts +28 -0
- package/types/lib/ecosystems/gemHostProbe.d.ts.map +1 -0
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +294 -20
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts +14 -1
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts.map +1 -1
- package/types/lib/ecosystems/npmVersionResolution.d.ts +119 -0
- package/types/lib/ecosystems/npmVersionResolution.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +17 -20
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +19 -0
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +10 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +19 -1
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +144 -12
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalasem.d.ts +58 -16
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +26 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -1
- package/types/lib/ecosystems/venvProbe.d.ts +40 -0
- package/types/lib/ecosystems/venvProbe.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +120 -13
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/scalasem.d.ts.map +1 -1
- package/types/lib/helpers/agenticToolutils.d.ts +53 -19
- package/types/lib/helpers/agenticToolutils.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +20 -3
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/helpers/jssSigner.d.ts +288 -0
- package/types/lib/helpers/jssSigner.d.ts.map +1 -0
- package/types/lib/helpers/vsixutils.d.ts +303 -19
- package/types/lib/helpers/vsixutils.d.ts.map +1 -1
- package/types/lib/inventory/agentBlueprint.d.ts +70 -0
- package/types/lib/inventory/agentBlueprint.d.ts.map +1 -0
- package/types/lib/inventory/agentToolNames.d.ts +51 -0
- package/types/lib/inventory/agentToolNames.d.ts.map +1 -0
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/aiInventory.d.ts.map +1 -1
- package/types/lib/inventory/aiProviders.d.ts +85 -0
- package/types/lib/inventory/aiProviders.d.ts.map +1 -0
- package/types/lib/inventory/atomUtils.d.ts +19 -0
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/bomUtils.d.ts.map +1 -1
- package/types/lib/inventory/brotliDecoder.d.ts +58 -0
- package/types/lib/inventory/brotliDecoder.d.ts.map +1 -0
- package/types/lib/inventory/cbomutils.d.ts +17 -0
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/ciParsers/githubActionsLock.d.ts +101 -0
- package/types/lib/inventory/ciParsers/githubActionsLock.d.ts.map +1 -0
- package/types/lib/inventory/citations.d.ts +5 -0
- package/types/lib/inventory/citations.d.ts.map +1 -1
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/cyclonedx20.d.ts +99 -0
- package/types/lib/inventory/cyclonedx20.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Common.d.ts +102 -0
- package/types/lib/inventory/cyclonedx20Common.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Derived.d.ts +74 -0
- package/types/lib/inventory/cyclonedx20Derived.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Parties.d.ts +91 -0
- package/types/lib/inventory/cyclonedx20Parties.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Read.d.ts +51 -0
- package/types/lib/inventory/cyclonedx20Read.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadCommon.d.ts +69 -0
- package/types/lib/inventory/cyclonedx20ReadCommon.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadComponents.d.ts +77 -0
- package/types/lib/inventory/cyclonedx20ReadComponents.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadContext.d.ts +118 -0
- package/types/lib/inventory/cyclonedx20ReadContext.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts +40 -0
- package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadSections.d.ts +45 -0
- package/types/lib/inventory/cyclonedx20ReadSections.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Render.d.ts +53 -0
- package/types/lib/inventory/cyclonedx20Render.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderComponents.d.ts +73 -0
- package/types/lib/inventory/cyclonedx20RenderComponents.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts +29 -0
- package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts +110 -0
- package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderHardware.d.ts +42 -0
- package/types/lib/inventory/cyclonedx20RenderHardware.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderPatents.d.ts +26 -0
- package/types/lib/inventory/cyclonedx20RenderPatents.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts +55 -0
- package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderSections.d.ts +45 -0
- package/types/lib/inventory/cyclonedx20RenderSections.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Tables.d.ts +134 -0
- package/types/lib/inventory/cyclonedx20Tables.d.ts.map +1 -0
- package/types/lib/inventory/deps.d.ts +35 -2
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +36 -1
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
- package/types/lib/inventory/dotnetRuntime.d.ts +91 -0
- package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -0
- package/types/lib/inventory/ecosystemFormulationParsers.d.ts +73 -0
- package/types/lib/inventory/ecosystemFormulationParsers.d.ts.map +1 -0
- package/types/lib/inventory/envcontext.d.ts +74 -12
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
- package/types/lib/inventory/gtfobins.d.ts.map +1 -1
- package/types/lib/inventory/hbom.d.ts +11 -0
- package/types/lib/inventory/hbom.d.ts.map +1 -1
- package/types/lib/inventory/hbomLoader.d.ts +54 -0
- package/types/lib/inventory/hbomLoader.d.ts.map +1 -1
- package/types/lib/inventory/hostRuntimeProbe.d.ts +44 -0
- package/types/lib/inventory/hostRuntimeProbe.d.ts.map +1 -0
- package/types/lib/inventory/jvmLocalRepos.d.ts +5 -1
- package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +12 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
- package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -1
- package/types/lib/inventory/mavenCentralMirror.d.ts +170 -0
- package/types/lib/inventory/mavenCentralMirror.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/nodeBuildInfo.d.ts +95 -0
- package/types/lib/inventory/nodeBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/nodeHostProbe.d.ts +15 -0
- package/types/lib/inventory/nodeHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/occurrenceKey.d.ts +52 -0
- package/types/lib/inventory/occurrenceKey.d.ts.map +1 -0
- package/types/lib/inventory/osPackageResolver.d.ts +38 -0
- package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
- package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -1
- package/types/lib/inventory/phpHostProbe.d.ts.map +1 -1
- package/types/lib/inventory/pqcMigration.d.ts +84 -0
- package/types/lib/inventory/pqcMigration.d.ts.map +1 -0
- package/types/lib/inventory/rubyBuildInfo.d.ts +271 -0
- package/types/lib/inventory/rubyBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/rubyHostProbe.d.ts +47 -0
- package/types/lib/inventory/rubyHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/rustFormulationParser.d.ts +7 -2
- package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/scalaCoords.d.ts.map +1 -1
- package/types/lib/inventory/scalaDedupe.d.ts +43 -0
- package/types/lib/inventory/scalaDedupe.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +25 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +12 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/parsers/huggingfaceManifest.d.ts +10 -1
- package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts +12 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
- package/types/lib/parsers/kiroPermissions.d.ts +62 -0
- package/types/lib/parsers/kiroPermissions.d.ts.map +1 -0
- package/types/lib/parsers/largeJson.d.ts +4 -3
- package/types/lib/parsers/largeJson.d.ts.map +1 -1
- package/types/lib/parsers/vscodeExtensionId.d.ts +48 -0
- package/types/lib/parsers/vscodeExtensionId.d.ts.map +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +7 -0
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts +25 -0
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +12 -8
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts +5 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/complianceEngine.d.ts +4 -4
- package/types/lib/validator/complianceEngine.d.ts.map +1 -1
- package/types/lib/validator/complianceRules.d.ts +14 -2
- package/types/lib/validator/complianceRules.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +10 -0
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { readFileSync, statSync } from "node:fs";
|
|
10
|
-
import { basename, dirname, relative, resolve, sep } from "node:path";
|
|
10
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
11
11
|
|
|
12
12
|
import { getAllFiles } from "../core/fs.js";
|
|
13
13
|
import { isInsideDir } from "../inventory/cmakeBuildDirs.js";
|
|
@@ -69,6 +69,222 @@ function licenseOf(file) {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/** A version header or CMakeLists.txt larger than this is not read. */
|
|
73
|
+
const MAX_VERSION_FILE_BYTES = 256 * 1024;
|
|
74
|
+
|
|
75
|
+
function readSmall(file) {
|
|
76
|
+
try {
|
|
77
|
+
const stat = statSync(file);
|
|
78
|
+
if (!stat.isFile() || stat.size > MAX_VERSION_FILE_BYTES) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
return readFileSync(file, "utf-8");
|
|
82
|
+
} catch (_err) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function defineValue(text, name) {
|
|
88
|
+
for (const line of text.split(/\r?\n/)) {
|
|
89
|
+
const trimmed = line.trim();
|
|
90
|
+
if (!trimmed.startsWith("#")) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const parts = trimmed.replace(/^#\s*/, "").split(/\s+/);
|
|
94
|
+
if (parts[0] === "define" && parts[1] === name && parts.length >= 3) {
|
|
95
|
+
return parts.slice(2).join(" ").replace(/^"|"$/g, "");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function numericTriple(text, names) {
|
|
102
|
+
const values = names.map((name) => defineValue(text, name));
|
|
103
|
+
return values.every((value) => /^\d+$/.test(value || ""))
|
|
104
|
+
? values.join(".")
|
|
105
|
+
: undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function packedBrotliVersion(value) {
|
|
109
|
+
if (!/^0x[0-9a-fA-F]{1,8}$/.test(value || "")) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
const packed = Number.parseInt(value, 16);
|
|
113
|
+
return `${packed >>> 24}.${(packed >>> 12) & 0xfff}.${packed & 0xfff}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Libraries that are often copied into C and C++ trees, the header each keeps
|
|
118
|
+
* its version in, and the CPE NVD names it by. The header is the library's
|
|
119
|
+
* own, so finding it at that path is what identifies the copy.
|
|
120
|
+
*/
|
|
121
|
+
const VENDORED_VERSION_HEADERS = [
|
|
122
|
+
{
|
|
123
|
+
file: "c/common/version.h",
|
|
124
|
+
// 1.1 and later define MAJOR/MINOR/PATCH; 1.0.x only the packed
|
|
125
|
+
// BROTLI_VERSION 0xMMMnnnppp (1.0.9 is 0x1000009).
|
|
126
|
+
read: (text) =>
|
|
127
|
+
numericTriple(text, [
|
|
128
|
+
"BROTLI_VERSION_MAJOR",
|
|
129
|
+
"BROTLI_VERSION_MINOR",
|
|
130
|
+
"BROTLI_VERSION_PATCH",
|
|
131
|
+
]) || packedBrotliVersion(defineValue(text, "BROTLI_VERSION")),
|
|
132
|
+
cpe: "google:brotli",
|
|
133
|
+
// google/brotli 0ed4268 (CVE-2026-111497) added this error code.
|
|
134
|
+
fixMarker: {
|
|
135
|
+
file: "c/include/brotli/decode.h",
|
|
136
|
+
text: "BLOCK_SWITCH",
|
|
137
|
+
property: "cdx:brotli:c:blockSwitchFix",
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
file: "zlib.h",
|
|
142
|
+
read: (text) => defineValue(text, "ZLIB_VERSION"),
|
|
143
|
+
cpe: "zlib:zlib",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
file: "lib/zstd.h",
|
|
147
|
+
read: (text) =>
|
|
148
|
+
numericTriple(text, [
|
|
149
|
+
"ZSTD_VERSION_MAJOR",
|
|
150
|
+
"ZSTD_VERSION_MINOR",
|
|
151
|
+
"ZSTD_VERSION_RELEASE",
|
|
152
|
+
]),
|
|
153
|
+
cpe: "facebook:zstandard",
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
file: "lib/lz4.h",
|
|
157
|
+
read: (text) =>
|
|
158
|
+
numericTriple(text, [
|
|
159
|
+
"LZ4_VERSION_MAJOR",
|
|
160
|
+
"LZ4_VERSION_MINOR",
|
|
161
|
+
"LZ4_VERSION_RELEASE",
|
|
162
|
+
]),
|
|
163
|
+
cpe: "lz4_project:lz4",
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
file: "png.h",
|
|
167
|
+
read: (text) => defineValue(text, "PNG_LIBPNG_VER_STRING"),
|
|
168
|
+
cpe: "libpng:libpng",
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
file: "sqlite3.h",
|
|
172
|
+
read: (text) => defineValue(text, "SQLITE_VERSION"),
|
|
173
|
+
cpe: "sqlite:sqlite",
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
file: "lib/expat.h",
|
|
177
|
+
read: (text) =>
|
|
178
|
+
numericTriple(text, [
|
|
179
|
+
"XML_MAJOR_VERSION",
|
|
180
|
+
"XML_MINOR_VERSION",
|
|
181
|
+
"XML_MICRO_VERSION",
|
|
182
|
+
]),
|
|
183
|
+
cpe: "libexpat_project:libexpat",
|
|
184
|
+
},
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
const PLAIN_VERSION_RE = /^\d+(\.\d+){1,3}$/;
|
|
188
|
+
|
|
189
|
+
// `project(brotli VERSION 1.2.0 LANGUAGES C)`, on one line.
|
|
190
|
+
function cmakeProjectVersion(dir) {
|
|
191
|
+
const text = readSmall(join(dir, "CMakeLists.txt"));
|
|
192
|
+
if (!text) {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
for (const line of text.split(/\r?\n/)) {
|
|
196
|
+
const trimmed = line.trim();
|
|
197
|
+
if (!trimmed.toLowerCase().startsWith("project(")) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const args = trimmed.slice("project(".length).split(/[\s)]+/);
|
|
201
|
+
const index = args.findIndex((arg) => arg.toUpperCase() === "VERSION");
|
|
202
|
+
if (index !== -1 && PLAIN_VERSION_RE.test(args[index + 1] || "")) {
|
|
203
|
+
return args[index + 1];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Read the version of a vendored directory from the library's own version
|
|
211
|
+
* header, else from its CMake project() call.
|
|
212
|
+
*
|
|
213
|
+
* @param {string} dir Absolute path of the vendored directory
|
|
214
|
+
* @returns {{version?: string, source?: string, cpe?: string, properties: Object[]}|undefined}
|
|
215
|
+
* undefined when nothing is known, and without a version when only the fix
|
|
216
|
+
* marker could be read
|
|
217
|
+
*/
|
|
218
|
+
export function vendoredVersionOf(dir) {
|
|
219
|
+
for (const entry of VENDORED_VERSION_HEADERS) {
|
|
220
|
+
const text = readSmall(join(dir, entry.file));
|
|
221
|
+
if (text === undefined) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
// The fix marker is read whether or not a version could be: a tree whose
|
|
225
|
+
// version header changed shape still shows whether its decoder is fixed.
|
|
226
|
+
const properties = [];
|
|
227
|
+
if (entry.fixMarker) {
|
|
228
|
+
const markerText = readSmall(join(dir, entry.fixMarker.file));
|
|
229
|
+
if (markerText !== undefined) {
|
|
230
|
+
properties.push({
|
|
231
|
+
name: entry.fixMarker.property,
|
|
232
|
+
value: markerText.includes(entry.fixMarker.text) ? "true" : "false",
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const version = entry.read(text);
|
|
237
|
+
if (version && PLAIN_VERSION_RE.test(version)) {
|
|
238
|
+
return {
|
|
239
|
+
version,
|
|
240
|
+
source: entry.file,
|
|
241
|
+
cpe: `cpe:2.3:a:${entry.cpe}:${version}:*:*:*:*:*:*:*`,
|
|
242
|
+
properties,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
if (properties.length) {
|
|
246
|
+
return { properties };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const version = cmakeProjectVersion(dir);
|
|
250
|
+
return version
|
|
251
|
+
? { version, source: "CMakeLists.txt", properties: [] }
|
|
252
|
+
: undefined;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Directory names under which a project keeps other projects' code.
|
|
257
|
+
*/
|
|
258
|
+
const VENDORED_DIR_NAMES = new Set([
|
|
259
|
+
"third_party",
|
|
260
|
+
"third-party",
|
|
261
|
+
"thirdparty",
|
|
262
|
+
"vendor",
|
|
263
|
+
"vendored",
|
|
264
|
+
"external",
|
|
265
|
+
]);
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Whether a file lies under a directory conventionally holding another
|
|
269
|
+
* project's code (`third_party/`, `vendor/`, `external/`) below the scan root.
|
|
270
|
+
* Build files there belong to the copied project: building them runs that
|
|
271
|
+
* project's build, and their dependencies are its build tooling.
|
|
272
|
+
*
|
|
273
|
+
* @param {string} root Project scan root
|
|
274
|
+
* @param {string} file File path
|
|
275
|
+
* @returns {boolean}
|
|
276
|
+
*/
|
|
277
|
+
export function isInVendoredDir(root, file) {
|
|
278
|
+
const rel = relative(resolve(root), resolve(file));
|
|
279
|
+
if (!rel || rel.startsWith("..")) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
return rel
|
|
283
|
+
.split(/[\\/]+/)
|
|
284
|
+
.slice(0, -1)
|
|
285
|
+
.some((segment) => VENDORED_DIR_NAMES.has(segment.toLowerCase()));
|
|
286
|
+
}
|
|
287
|
+
|
|
72
288
|
function licenseChoice(id) {
|
|
73
289
|
return id.includes(" ") ? { expression: id } : { license: { id } };
|
|
74
290
|
}
|
|
@@ -120,17 +336,32 @@ export function findVendoredCode(root, options = {}, excludeDirs = []) {
|
|
|
120
336
|
}
|
|
121
337
|
const path = relDir(file);
|
|
122
338
|
const name = basename(dir);
|
|
123
|
-
const
|
|
339
|
+
const versionInfo = vendoredVersionOf(dir);
|
|
340
|
+
const purl = tryBuildPurl({
|
|
341
|
+
type: "generic",
|
|
342
|
+
name,
|
|
343
|
+
version: versionInfo?.version || null,
|
|
344
|
+
subpath: path,
|
|
345
|
+
});
|
|
124
346
|
const licenseFile = relative(absRoot, file).split(sep).join("/");
|
|
125
347
|
const component = {
|
|
126
348
|
type: "library",
|
|
127
349
|
name,
|
|
128
|
-
version: "",
|
|
350
|
+
version: versionInfo?.version || "",
|
|
129
351
|
licenses: [licenseChoice(id)],
|
|
130
352
|
properties: [
|
|
131
353
|
{ name: "cdx:vendored", value: "true" },
|
|
132
354
|
{ name: "cdx:vendored:path", value: path },
|
|
133
355
|
{ name: "cdx:vendored:licenseFile", value: licenseFile },
|
|
356
|
+
...(versionInfo?.version
|
|
357
|
+
? [
|
|
358
|
+
{
|
|
359
|
+
name: "cdx:vendored:versionSource",
|
|
360
|
+
value: `${path}/${versionInfo.source}`,
|
|
361
|
+
},
|
|
362
|
+
]
|
|
363
|
+
: []),
|
|
364
|
+
...(versionInfo?.properties || []),
|
|
134
365
|
],
|
|
135
366
|
evidence: {
|
|
136
367
|
identity: {
|
|
@@ -143,6 +374,9 @@ export function findVendoredCode(root, options = {}, excludeDirs = []) {
|
|
|
143
374
|
licenses: [licenseChoice(id)],
|
|
144
375
|
},
|
|
145
376
|
};
|
|
377
|
+
if (versionInfo?.cpe) {
|
|
378
|
+
component.cpe = versionInfo.cpe;
|
|
379
|
+
}
|
|
146
380
|
if (purl) {
|
|
147
381
|
component.purl = purl;
|
|
148
382
|
component["bom-ref"] = decodeURIComponent(purl);
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
// Bounded, read-only probe for the Python packages of virtual environments and
|
|
2
|
+
// pyenv installs on a live host, for `-t os` / obom.
|
|
3
|
+
//
|
|
4
|
+
// osquery's python_packages table reads the interpreter's own site-packages
|
|
5
|
+
// directories, so a package installed into a virtual environment (an app's
|
|
6
|
+
// /opt/app/venv, a Poetry or pipenv environment under a home directory) or into
|
|
7
|
+
// a pyenv-built Python is missing from an OBOM. Brotli and brotlicffi are
|
|
8
|
+
// usually installed exactly there, pulled in by requests, aiohttp or httpx.
|
|
9
|
+
//
|
|
10
|
+
// Environments are found in fixed places, one wildcard level at a time: a
|
|
11
|
+
// directory holding pyvenv.cfg is a virtual environment, and a pyenv
|
|
12
|
+
// `versions/*` directory is an interpreter install. Only `*.dist-info/METADATA`
|
|
13
|
+
// files are read; nothing is executed.
|
|
14
|
+
|
|
15
|
+
import { readdirSync, statSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import process from "node:process";
|
|
18
|
+
|
|
19
|
+
import { isDryRun, recordActivity } from "../core/activity.js";
|
|
20
|
+
import { safeExistsSync, safeReadFileSync } from "../core/fs.js";
|
|
21
|
+
import { thoughtLog } from "../core/logger.js";
|
|
22
|
+
import { applyPurl, tryBuildPurl } from "../inventory/purl.js";
|
|
23
|
+
import { parseBdistMetadata } from "./ecosystems.js";
|
|
24
|
+
|
|
25
|
+
const MAX_ENVIRONMENTS = 64;
|
|
26
|
+
const MAX_DIRECTORIES = 512;
|
|
27
|
+
const MAX_PACKAGES = 8192;
|
|
28
|
+
const MAX_METADATA_BYTES = 4 * 1024 * 1024;
|
|
29
|
+
|
|
30
|
+
// Relative to the root.
|
|
31
|
+
const ROOT_PATTERNS = [
|
|
32
|
+
"opt/*",
|
|
33
|
+
"opt/*/*",
|
|
34
|
+
"srv/*",
|
|
35
|
+
"srv/*/*",
|
|
36
|
+
"app",
|
|
37
|
+
"app/*",
|
|
38
|
+
"usr/local/*",
|
|
39
|
+
];
|
|
40
|
+
// Relative to a home directory.
|
|
41
|
+
const HOME_PATTERNS = [
|
|
42
|
+
"*",
|
|
43
|
+
"*/*",
|
|
44
|
+
".virtualenvs/*",
|
|
45
|
+
".local/share/virtualenvs/*",
|
|
46
|
+
".cache/pypoetry/virtualenvs/*",
|
|
47
|
+
"Library/Caches/pypoetry/virtualenvs/*",
|
|
48
|
+
".pyenv/versions/*",
|
|
49
|
+
];
|
|
50
|
+
const HOME_PARENTS = { linux: ["home"], darwin: ["Users"] };
|
|
51
|
+
// Folders of a macOS home that privacy protection (TCC) guards. Looking inside
|
|
52
|
+
// one makes macOS ask the user to grant the terminal access, so a wildcard
|
|
53
|
+
// never enters them; Library is skipped too, for the Mail, Messages and cloud
|
|
54
|
+
// storage folders under it. Patterns that name a folder below Library, such as
|
|
55
|
+
// Poetry's cache, still reach it.
|
|
56
|
+
const MACOS_PROTECTED_HOME_FOLDERS = new Set([
|
|
57
|
+
"Desktop",
|
|
58
|
+
"Documents",
|
|
59
|
+
"Downloads",
|
|
60
|
+
"Library",
|
|
61
|
+
"Movies",
|
|
62
|
+
"Music",
|
|
63
|
+
"Pictures",
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
function listDirectory(directory) {
|
|
67
|
+
try {
|
|
68
|
+
return readdirSync(directory).sort();
|
|
69
|
+
} catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isDirectory(path) {
|
|
75
|
+
try {
|
|
76
|
+
return statSync(path).isDirectory();
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isFile(path) {
|
|
83
|
+
try {
|
|
84
|
+
return statSync(path).isFile();
|
|
85
|
+
} catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// `skipFirst` names folders a wildcard in the pattern's first segment never
|
|
91
|
+
// enters.
|
|
92
|
+
function expand(base, pattern, skipFirst) {
|
|
93
|
+
let current = [base];
|
|
94
|
+
for (const [depth, segment] of pattern.split("/").entries()) {
|
|
95
|
+
const next = [];
|
|
96
|
+
for (const directory of current) {
|
|
97
|
+
if (segment !== "*") {
|
|
98
|
+
next.push(join(directory, segment));
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
for (const name of listDirectory(directory)) {
|
|
102
|
+
// Hidden directories are only entered when a pattern names them.
|
|
103
|
+
if (name.startsWith(".") || (depth === 0 && skipFirst?.has(name))) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
next.push(join(directory, name));
|
|
107
|
+
if (next.length >= MAX_DIRECTORIES) {
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
current = next.filter(isDirectory).slice(0, MAX_DIRECTORIES);
|
|
113
|
+
}
|
|
114
|
+
return current;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Find the virtual environments and pyenv interpreters under the fixed
|
|
119
|
+
* places of a root.
|
|
120
|
+
*
|
|
121
|
+
* @param {string} rootPath root directory
|
|
122
|
+
* @param {string} platform `linux` or `darwin`
|
|
123
|
+
* @returns {{path: string, kind: string}[]} environments, at most MAX_ENVIRONMENTS
|
|
124
|
+
*/
|
|
125
|
+
export function findPythonEnvironments(rootPath, platform) {
|
|
126
|
+
const bases = [{ base: rootPath, patterns: ROOT_PATTERNS }];
|
|
127
|
+
const homes = platform === "linux" ? [join(rootPath, "root")] : [];
|
|
128
|
+
for (const parent of HOME_PARENTS[platform] || []) {
|
|
129
|
+
for (const name of listDirectory(join(rootPath, parent))) {
|
|
130
|
+
homes.push(join(rootPath, parent, name));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const skipFirst =
|
|
134
|
+
platform === "darwin" ? MACOS_PROTECTED_HOME_FOLDERS : undefined;
|
|
135
|
+
for (const home of homes.filter(isDirectory)) {
|
|
136
|
+
bases.push({ base: home, patterns: HOME_PATTERNS, skipFirst });
|
|
137
|
+
}
|
|
138
|
+
const environments = [];
|
|
139
|
+
const seen = new Set();
|
|
140
|
+
for (const { base, patterns, skipFirst } of bases) {
|
|
141
|
+
for (const pattern of patterns) {
|
|
142
|
+
for (const candidate of expand(base, pattern, skipFirst)) {
|
|
143
|
+
if (seen.has(candidate)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
let kind;
|
|
147
|
+
if (isFile(join(candidate, "pyvenv.cfg"))) {
|
|
148
|
+
kind = "virtualenv";
|
|
149
|
+
} else if (pattern.endsWith(".pyenv/versions/*")) {
|
|
150
|
+
kind = "pyenv";
|
|
151
|
+
}
|
|
152
|
+
// A directory with a nested .venv is the project, not the environment.
|
|
153
|
+
if (!kind && isFile(join(candidate, ".venv", "pyvenv.cfg"))) {
|
|
154
|
+
const nested = join(candidate, ".venv");
|
|
155
|
+
if (!seen.has(nested)) {
|
|
156
|
+
seen.add(nested);
|
|
157
|
+
environments.push({ path: nested, kind: "virtualenv" });
|
|
158
|
+
}
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (!kind) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
seen.add(candidate);
|
|
165
|
+
environments.push({ path: candidate, kind });
|
|
166
|
+
if (environments.length >= MAX_ENVIRONMENTS) {
|
|
167
|
+
return environments;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return environments;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function sitePackagesOf(environment) {
|
|
176
|
+
const lib = join(environment, "lib");
|
|
177
|
+
const directories = [];
|
|
178
|
+
for (const name of listDirectory(lib)) {
|
|
179
|
+
if (!name.startsWith("python")) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const sitePackages = join(lib, name, "site-packages");
|
|
183
|
+
if (isDirectory(sitePackages)) {
|
|
184
|
+
directories.push(sitePackages);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return directories;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function readMetadata(file) {
|
|
191
|
+
try {
|
|
192
|
+
const stats = statSync(file);
|
|
193
|
+
if (!stats.isFile() || stats.size > MAX_METADATA_BYTES) {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
} catch {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
const text = safeReadFileSync(file, "utf-8");
|
|
200
|
+
return typeof text === "string" ? text : undefined;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Inventory the packages installed in the virtual environments and pyenv
|
|
205
|
+
* interpreters of a host. One component per distinct name and version, with
|
|
206
|
+
* an occurrence for each dist-info directory it was found in, so no host path
|
|
207
|
+
* reaches a purl.
|
|
208
|
+
*
|
|
209
|
+
* @param {Object} [options] options
|
|
210
|
+
* @param {string} [options.rootPath="/"] root directory; tests point it at a built tree
|
|
211
|
+
* @param {string} [options.platform] `linux` or `darwin`
|
|
212
|
+
* @returns {Object[]} pypi components
|
|
213
|
+
*/
|
|
214
|
+
export function probeHostPythonEnvironments(options = {}) {
|
|
215
|
+
const rootPath = options.rootPath || "/";
|
|
216
|
+
const platform = options.platform || process.platform;
|
|
217
|
+
if (!HOME_PARENTS[platform]) {
|
|
218
|
+
return [];
|
|
219
|
+
}
|
|
220
|
+
if (isDryRun) {
|
|
221
|
+
recordActivity({
|
|
222
|
+
capability: "python-env-probe",
|
|
223
|
+
kind: "read",
|
|
224
|
+
reason:
|
|
225
|
+
"Dry run mode blocks the Python environment probe, which reads package metadata outside the project.",
|
|
226
|
+
status: "blocked",
|
|
227
|
+
target: rootPath,
|
|
228
|
+
});
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
if (!safeExistsSync(rootPath)) {
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
const environments = findPythonEnvironments(rootPath, platform);
|
|
235
|
+
const byRef = new Map();
|
|
236
|
+
let packages = 0;
|
|
237
|
+
for (const environment of environments) {
|
|
238
|
+
for (const sitePackages of sitePackagesOf(environment.path)) {
|
|
239
|
+
for (const name of listDirectory(sitePackages)) {
|
|
240
|
+
if (!name.endsWith(".dist-info") || packages >= MAX_PACKAGES) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const distInfo = join(sitePackages, name);
|
|
244
|
+
const metadataFile = join(distInfo, "METADATA");
|
|
245
|
+
const raw = readMetadata(metadataFile);
|
|
246
|
+
if (raw === undefined) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const [pkg] = parseBdistMetadata(metadataFile, raw);
|
|
250
|
+
if (!pkg?.name || !pkg.version) {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
packages += 1;
|
|
254
|
+
const purl = tryBuildPurl({
|
|
255
|
+
type: "pypi",
|
|
256
|
+
name: pkg.name,
|
|
257
|
+
version: pkg.version,
|
|
258
|
+
});
|
|
259
|
+
if (!purl) {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const ref = decodeURIComponent(purl);
|
|
263
|
+
let component = byRef.get(ref);
|
|
264
|
+
if (!component) {
|
|
265
|
+
component = applyPurl(
|
|
266
|
+
{
|
|
267
|
+
type: "library",
|
|
268
|
+
name: pkg.name,
|
|
269
|
+
version: pkg.version,
|
|
270
|
+
...(pkg.description ? { description: pkg.description } : {}),
|
|
271
|
+
properties: [
|
|
272
|
+
{ name: "cdx:python:environment", value: environment.kind },
|
|
273
|
+
],
|
|
274
|
+
evidence: { occurrences: [] },
|
|
275
|
+
},
|
|
276
|
+
purl,
|
|
277
|
+
);
|
|
278
|
+
byRef.set(ref, component);
|
|
279
|
+
} else if (
|
|
280
|
+
!component.properties.some(
|
|
281
|
+
(prop) =>
|
|
282
|
+
prop.name === "cdx:python:environment" &&
|
|
283
|
+
prop.value === environment.kind,
|
|
284
|
+
)
|
|
285
|
+
) {
|
|
286
|
+
component.properties.push({
|
|
287
|
+
name: "cdx:python:environment",
|
|
288
|
+
value: environment.kind,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
component.evidence.occurrences.push({ location: distInfo });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
recordActivity({
|
|
296
|
+
capability: "python-env-probe",
|
|
297
|
+
kind: "read",
|
|
298
|
+
reason: `Read ${packages} package(s) from ${environments.length} Python environment(s) in the fixed places.`,
|
|
299
|
+
status: "completed",
|
|
300
|
+
target: rootPath,
|
|
301
|
+
});
|
|
302
|
+
if (byRef.size) {
|
|
303
|
+
thoughtLog(
|
|
304
|
+
`I found ${byRef.size} Python package(s) in ${environments.length} virtual environment(s) or pyenv install(s).`,
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
return Array.from(byRef.values()).sort((a, b) =>
|
|
308
|
+
a["bom-ref"] < b["bom-ref"] ? -1 : a["bom-ref"] > b["bom-ref"] ? 1 : 0,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Add the packages of the host's Python environments to an OBOM's component
|
|
314
|
+
* list. A package osquery already listed from the interpreter's own
|
|
315
|
+
* site-packages keeps its component; the environment's dist-info becomes one
|
|
316
|
+
* more occurrence of it, and its cdx:python:environment property says where
|
|
317
|
+
* else it is installed.
|
|
318
|
+
*
|
|
319
|
+
* @param {Object[]} components OBOM components
|
|
320
|
+
* @param {Object[]} envPackages result of probeHostPythonEnvironments
|
|
321
|
+
* @returns {Object[]} the components, with the environment packages merged in
|
|
322
|
+
*/
|
|
323
|
+
export function mergeHostPythonEnvironmentPackages(components, envPackages) {
|
|
324
|
+
if (!envPackages?.length) {
|
|
325
|
+
return components;
|
|
326
|
+
}
|
|
327
|
+
const byPurl = new Map();
|
|
328
|
+
for (const component of components) {
|
|
329
|
+
if (typeof component?.purl === "string") {
|
|
330
|
+
byPurl.set(decodeURIComponent(component.purl), component);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const added = [];
|
|
334
|
+
for (const pkg of envPackages) {
|
|
335
|
+
const existing = byPurl.get(decodeURIComponent(pkg.purl));
|
|
336
|
+
if (!existing) {
|
|
337
|
+
added.push(pkg);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
existing.evidence = existing.evidence || {};
|
|
341
|
+
const occurrences = existing.evidence.occurrences || [];
|
|
342
|
+
for (const occurrence of pkg.evidence?.occurrences || []) {
|
|
343
|
+
if (!occurrences.some((o) => o.location === occurrence.location)) {
|
|
344
|
+
occurrences.push(occurrence);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
existing.evidence.occurrences = occurrences;
|
|
348
|
+
existing.properties = existing.properties || [];
|
|
349
|
+
for (const prop of pkg.properties || []) {
|
|
350
|
+
if (
|
|
351
|
+
!existing.properties.some(
|
|
352
|
+
(p) => p.name === prop.name && p.value === prop.value,
|
|
353
|
+
)
|
|
354
|
+
) {
|
|
355
|
+
existing.properties.push(prop);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return added.length ? components.concat(added) : components;
|
|
360
|
+
}
|