@cdxgen/cdxgen 13.3.0 → 13.5.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 +12 -9
- package/bin/cdxgen-secure.js +15 -0
- package/bin/cdxgen.js +115 -31
- package/bin/evinse.js +23 -1
- package/bin/hbom.js +15 -7
- package/bin/repl.js +115 -0
- package/bin/sign.js +45 -15
- package/bin/tracebom.js +5 -1
- package/bin/validate.js +84 -18
- package/bin/verify.js +155 -86
- package/data/cbomosdb-queries.json +13 -1
- package/data/component-tags.json +8 -0
- package/data/helpers/init.gradle +46 -21
- package/data/queries-darwin.json +25 -1
- package/data/queries-win.json +19 -1
- package/data/queries.json +41 -0
- package/data/remediations.json +344 -1
- package/data/rules/cbom-security.yaml +235 -4
- package/data/rules/dependency-sources.yaml +67 -0
- package/data/rules/go-binaries.yaml +46 -0
- package/data/rules/golem-go.yaml +80 -0
- package/data/rules/hbom-compliance.yaml +40 -22
- package/data/rules/hbom-performance.yaml +7 -1
- package/data/rules/host-topology.yaml +1 -1
- package/data/rules/obom-runtime.yaml +31 -2
- package/data/rules/php-runtime.yaml +143 -0
- package/data/rules/scala-evidence.yaml +115 -0
- package/lib/audit/index.js +52 -6
- package/lib/cli/bomAssembly.js +28 -3
- package/lib/cli/cliOptions.js +110 -3
- package/lib/cli/index.js +186 -58
- package/lib/cli/jsBom.js +50 -29
- package/lib/cli/jvmBom.js +779 -146
- package/lib/cli/managedBom.js +1021 -261
- package/lib/cli/nativeBom.js +364 -58
- package/lib/core/activity.js +12 -0
- package/lib/core/env.js +47 -17
- package/lib/core/fs.js +31 -21
- package/lib/core/httpClient.js +233 -69
- package/lib/core/pluginRun.js +184 -0
- package/lib/core/processTree.js +100 -0
- package/lib/core/projectConfig.js +1 -0
- package/lib/core/projectTooling.js +97 -0
- package/lib/core/propertySanitizer.js +72 -0
- package/lib/core/runState.js +39 -0
- package/lib/core/toml.js +72 -0
- package/lib/ecosystems/asarutils.js +4 -2
- package/lib/ecosystems/bunutils.js +5 -7
- package/lib/ecosystems/cargoCache.js +231 -8
- package/lib/ecosystems/cmakeResolver.js +10 -21
- package/lib/ecosystems/cppBuildContext.js +631 -0
- package/lib/ecosystems/cppEvidence.js +337 -22
- package/lib/ecosystems/cppIncludeResolver.js +294 -0
- package/lib/ecosystems/denoutils.js +93 -66
- package/lib/ecosystems/ecosystems.js +1967 -788
- package/lib/ecosystems/golem.js +578 -18
- package/lib/ecosystems/gradleutils.js +340 -46
- package/lib/ecosystems/jsEvidence.js +85 -24
- package/lib/ecosystems/kosi.js +21 -4
- package/lib/ecosystems/npmutils.js +64 -0
- package/lib/ecosystems/parsers-dotnet.js +668 -153
- package/lib/ecosystems/parsers-elm.js +46 -28
- package/lib/ecosystems/parsers-gleam.js +3 -4
- package/lib/ecosystems/parsers-go.js +1283 -297
- package/lib/ecosystems/parsers-js.js +92 -26
- package/lib/ecosystems/parsers-julia.js +2 -3
- package/lib/ecosystems/parsers-jvm.js +463 -29
- package/lib/ecosystems/parsers-misc.js +405 -93
- package/lib/ecosystems/parsers-python.js +152 -20
- package/lib/ecosystems/parsers-rust.js +4 -4
- package/lib/ecosystems/parsers-terraform.js +1573 -19
- package/lib/ecosystems/pythonutils.js +102 -6
- package/lib/ecosystems/remote/huggingface.js +3 -0
- package/lib/ecosystems/rubyutils.js +73 -20
- package/lib/ecosystems/rusi.js +32 -9
- package/lib/ecosystems/sbtutils.js +243 -187
- package/lib/ecosystems/scalacliutils.js +343 -0
- package/lib/ecosystems/scalasem.js +2620 -0
- package/lib/ecosystems/terraformRegistry.js +288 -0
- package/lib/ecosystems/utils.js +2 -0
- package/lib/ecosystems/vendoredCode.js +156 -0
- package/lib/evinser/evinser.js +695 -78
- package/lib/evinser/swiftsem.js +68 -5
- package/lib/helpers/agenticToolutils.js +2 -2
- package/lib/helpers/bomSigner.js +576 -128
- package/lib/inventory/agentSkillSpec.js +17 -0
- package/lib/inventory/aiCollector.js +4 -1
- package/lib/inventory/analyzer.js +110 -127
- package/lib/inventory/apkInstalledDb.js +243 -0
- package/lib/inventory/atomUtils.js +204 -48
- package/lib/inventory/auditCategories.js +10 -3
- package/lib/inventory/buildToolRateLimit.js +144 -0
- package/lib/inventory/cacheCommands.js +2 -1
- package/lib/inventory/cacheDir.js +52 -9
- package/lib/inventory/certificates.js +2371 -0
- package/lib/inventory/cmakeBuildDirs.js +254 -0
- package/lib/inventory/communityAiConfigParser.js +4 -9
- package/lib/inventory/deps.js +507 -128
- package/lib/inventory/display.js +200 -3
- package/lib/inventory/dosai.js +747 -101
- package/lib/inventory/dosaiParsers.js +310 -15
- package/lib/inventory/dynamic.js +96 -8
- package/lib/inventory/envcontext.js +168 -5
- package/lib/inventory/evidenceUtils.js +590 -132
- package/lib/inventory/fetchBatch.js +574 -38
- package/lib/inventory/fetchDiskCache.js +597 -0
- package/lib/inventory/forgeEnricher.js +14 -5
- package/lib/inventory/formulationParsers.js +3 -2
- package/lib/inventory/goBuildInfo.js +140 -0
- package/lib/inventory/hbom.js +67 -7
- package/lib/inventory/hbomAnalysis.js +16 -0
- package/lib/inventory/hostTopology.js +21 -0
- package/lib/inventory/jvmLocalRepos.js +917 -0
- package/lib/inventory/mcpConfigParser.js +2 -3
- package/lib/inventory/osPackagePurl.js +244 -0
- package/lib/inventory/osPackageResolver.js +89 -87
- package/lib/inventory/osinfo.js +79 -31
- package/lib/inventory/osqueryTransform.js +114 -5
- package/lib/inventory/parentSelection.js +146 -0
- package/lib/inventory/phpBuildInfo.js +487 -0
- package/lib/inventory/phpHostProbe.js +641 -0
- package/lib/inventory/rootfsCerts.js +426 -0
- package/lib/inventory/rustFormulationParser.js +3 -4
- package/lib/inventory/scalaCoords.js +153 -0
- package/lib/inventory/source.js +215 -11
- package/lib/inventory/spdx.js +245 -0
- package/lib/inventory/traceRunner.js +93 -8
- package/lib/inventory/tracedCrypto.js +423 -0
- package/lib/managers/binary.js +529 -183
- package/lib/managers/docker.js +199 -44
- package/lib/managers/oci.js +24 -2
- package/lib/parsers/cmakeCache.js +69 -0
- package/lib/parsers/cmakeLists.js +16 -2
- package/lib/parsers/cmakePresets.js +400 -0
- package/lib/parsers/compileCommands.js +557 -0
- package/lib/parsers/email.js +277 -0
- package/lib/parsers/largeJson.js +34 -2
- package/lib/parsers/strictJson.js +322 -0
- package/lib/parsers/terraformConfig.js +1073 -0
- package/lib/parsers/terraformSource.js +652 -0
- package/lib/server/server.js +209 -1
- package/lib/stages/postgen/annotator.js +17 -1
- package/lib/stages/postgen/introspection/markers.js +1 -1
- package/lib/stages/postgen/introspection/reflect.js +4 -1
- package/lib/stages/postgen/postgen.js +20 -6
- package/lib/stages/postgen/ruleEngine.js +58 -1
- package/lib/stages/postgen/specVersionCompat.js +62 -0
- package/lib/stages/pregen/pregen.js +117 -5
- package/lib/validator/bomValidator.js +26 -5
- package/lib/validator/generated/runtime.mjs +13 -0
- package/lib/validator/generated/validate-1.6.mjs +26 -7
- package/lib/validator/generated/validate-1.7.mjs +31 -12
- package/lib/validator/generated/validate-2.0.mjs +30 -11
- package/lib/validator/index.js +25 -10
- package/lib/validator/reporters/json.js +1 -0
- package/package.json +23 -23
- package/types/bin/cdxgen-secure.d.ts +3 -0
- package/types/bin/cdxgen-secure.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +9 -2
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +43 -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.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +44 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts +7 -4
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +3 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/activity.d.ts.map +1 -1
- package/types/lib/core/env.d.ts +27 -2
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/httpClient.d.ts +13 -8
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/pluginRun.d.ts +97 -0
- package/types/lib/core/pluginRun.d.ts.map +1 -0
- package/types/lib/core/processTree.d.ts +36 -0
- package/types/lib/core/processTree.d.ts.map +1 -0
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/projectTooling.d.ts +41 -0
- package/types/lib/core/projectTooling.d.ts.map +1 -0
- package/types/lib/core/propertySanitizer.d.ts +26 -0
- package/types/lib/core/propertySanitizer.d.ts.map +1 -1
- package/types/lib/core/runState.d.ts +29 -0
- package/types/lib/core/runState.d.ts.map +1 -0
- package/types/lib/core/toml.d.ts +20 -0
- package/types/lib/core/toml.d.ts.map +1 -0
- package/types/lib/ecosystems/asarutils.d.ts.map +1 -1
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
- package/types/lib/ecosystems/cargoCache.d.ts +12 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -1
- package/types/lib/ecosystems/cmakeResolver.d.ts +5 -3
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
- package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +19 -3
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
- package/types/lib/ecosystems/denoutils.d.ts +9 -3
- package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +62 -13
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/golem.d.ts +81 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +59 -7
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -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/npmutils.d.ts +15 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts +155 -5
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +119 -5
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +6 -1
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts +74 -3
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +21 -59
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +57 -6
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-terraform.d.ts +38 -21
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +26 -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 +21 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/sbtutils.d.ts +88 -2
- package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
- package/types/lib/ecosystems/scalasem.d.ts +311 -0
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
- package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
- package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/utils.d.ts +2 -2
- package/types/lib/ecosystems/utils.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +100 -8
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +10 -0
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +80 -4
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/inventory/agentSkillSpec.d.ts +13 -0
- package/types/lib/inventory/agentSkillSpec.d.ts.map +1 -0
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/apkInstalledDb.d.ts +42 -0
- package/types/lib/inventory/apkInstalledDb.d.ts.map +1 -0
- package/types/lib/inventory/atomUtils.d.ts +96 -10
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +3 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
- package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
- package/types/lib/inventory/cacheCommands.d.ts +2 -1
- package/types/lib/inventory/cacheCommands.d.ts.map +1 -1
- package/types/lib/inventory/cacheDir.d.ts +12 -4
- package/types/lib/inventory/cacheDir.d.ts.map +1 -1
- package/types/lib/inventory/certificates.d.ts +205 -0
- package/types/lib/inventory/certificates.d.ts.map +1 -0
- package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
- package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/deps.d.ts +61 -2
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts +18 -0
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +202 -14
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dosaiParsers.d.ts +53 -7
- package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
- package/types/lib/inventory/dynamic.d.ts +16 -1
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts +40 -2
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts +48 -14
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +83 -14
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
- package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
- package/types/lib/inventory/goBuildInfo.d.ts +10 -0
- package/types/lib/inventory/goBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/hbom.d.ts.map +1 -1
- package/types/lib/inventory/hbomAnalysis.d.ts +10 -0
- package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -1
- package/types/lib/inventory/hostTopology.d.ts.map +1 -1
- package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
- package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/osPackagePurl.d.ts +99 -0
- package/types/lib/inventory/osPackagePurl.d.ts.map +1 -0
- package/types/lib/inventory/osPackageResolver.d.ts +6 -2
- package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
- package/types/lib/inventory/osinfo.d.ts +31 -6
- package/types/lib/inventory/osinfo.d.ts.map +1 -1
- package/types/lib/inventory/osqueryTransform.d.ts +51 -0
- package/types/lib/inventory/osqueryTransform.d.ts.map +1 -1
- package/types/lib/inventory/parentSelection.d.ts +34 -0
- package/types/lib/inventory/parentSelection.d.ts.map +1 -0
- package/types/lib/inventory/phpBuildInfo.d.ts +142 -0
- package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/phpHostProbe.d.ts +35 -0
- package/types/lib/inventory/phpHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/rootfsCerts.d.ts +27 -0
- package/types/lib/inventory/rootfsCerts.d.ts.map +1 -0
- package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/scalaCoords.d.ts +80 -0
- package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +28 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/spdx.d.ts +24 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +16 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/inventory/tracedCrypto.d.ts +64 -0
- package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts +10 -0
- 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/managers/oci.d.ts +3 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/parsers/cmakeCache.d.ts +16 -0
- package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
- package/types/lib/parsers/cmakeLists.d.ts +2 -1
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -1
- package/types/lib/parsers/cmakePresets.d.ts +78 -0
- package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
- package/types/lib/parsers/compileCommands.d.ts +115 -0
- package/types/lib/parsers/compileCommands.d.ts.map +1 -0
- package/types/lib/parsers/email.d.ts +19 -0
- package/types/lib/parsers/email.d.ts.map +1 -0
- package/types/lib/parsers/largeJson.d.ts +13 -0
- package/types/lib/parsers/largeJson.d.ts.map +1 -1
- package/types/lib/parsers/strictJson.d.ts +46 -0
- package/types/lib/parsers/strictJson.d.ts.map +1 -0
- package/types/lib/parsers/terraformConfig.d.ts +70 -0
- package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
- package/types/lib/parsers/terraformSource.d.ts +66 -0
- package/types/lib/parsers/terraformSource.d.ts.map +1 -0
- package/types/lib/server/server.d.ts +8 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts +7 -4
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +14 -1
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +8 -0
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { platform } from "node:os";
|
|
2
|
-
import { extname } from "node:path";
|
|
2
|
+
import { basename, dirname, extname } from "node:path";
|
|
3
3
|
|
|
4
4
|
import { build } from "@cdxgen/cdx-purl";
|
|
5
5
|
|
|
6
6
|
import { safeExistsSync } from "../core/fs.js";
|
|
7
7
|
import { thoughtLog } from "../core/logger.js";
|
|
8
|
+
import { sanitizeBomUrl } from "../core/propertySanitizer.js";
|
|
9
|
+
import { isAgentSkillSpecValid } from "./agentSkillSpec.js";
|
|
8
10
|
import {
|
|
9
11
|
addDosaiReachabilityProperties,
|
|
10
12
|
readDosaiMethodsReport,
|
|
@@ -12,22 +14,34 @@ import {
|
|
|
12
14
|
import {
|
|
13
15
|
addDosaiSetValue,
|
|
14
16
|
buildDosaiPurlAliasMap,
|
|
17
|
+
dosaiCallSiteLocation,
|
|
15
18
|
dosaiSourceLocation,
|
|
16
19
|
dosaiSourceLocationFromNode,
|
|
20
|
+
pickDosaiComponentByLocation,
|
|
17
21
|
resolveDosaiComponentPurl,
|
|
18
22
|
} from "./dosaiParsers.js";
|
|
19
23
|
import { createGtfoBinsPropertiesFromRow } from "./gtfobins.js";
|
|
20
24
|
import { createLolbasProperties } from "./lolbas.js";
|
|
25
|
+
import { isOsPackagePurlType } from "./osPackagePurl.js";
|
|
21
26
|
import {
|
|
22
27
|
createOsQueryFallbackBomRef,
|
|
28
|
+
createOsQueryPackageIdentity,
|
|
23
29
|
createOsQueryPurl,
|
|
24
30
|
deriveOsQueryDescription,
|
|
25
31
|
deriveOsQueryName,
|
|
26
32
|
deriveOsQueryPublisher,
|
|
27
33
|
deriveOsQueryVersion,
|
|
34
|
+
OS_PACKAGE_LICENSE_COLUMNS,
|
|
35
|
+
osPackagePropertyName,
|
|
28
36
|
sanitizeOsQueryIdentity,
|
|
29
37
|
shouldCreateOsQueryPurl,
|
|
30
38
|
} from "./osqueryTransform.js";
|
|
39
|
+
import {
|
|
40
|
+
adjustLicenseInformation,
|
|
41
|
+
findLicenseId,
|
|
42
|
+
isSpdxLicenseExpression,
|
|
43
|
+
spdxLicenses,
|
|
44
|
+
} from "./spdx.js";
|
|
31
45
|
|
|
32
46
|
/**
|
|
33
47
|
* Build a CycloneDX occurrence evidence object from a location string.
|
|
@@ -240,27 +254,47 @@ function addDotnetIdentityMethod(apkg, value) {
|
|
|
240
254
|
* Enrich .NET package components with occurrence evidence and imported module/method
|
|
241
255
|
* information from a dosai dependency slices file.
|
|
242
256
|
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
257
|
+
* Each dosai record names its package by purl, resolved in the project of the
|
|
258
|
+
* record's file, and is attached to the component of that version (see
|
|
259
|
+
* {@link resolveDosaiComponentPurl}). The DLL files of each package
|
|
260
|
+
* (`internal:PackageFiles`) only fill in for a record whose purl names no
|
|
261
|
+
* component, and only when the DLL belongs to one component or the record's
|
|
262
|
+
* location says which: two versions of a package ship the same DLL names.
|
|
263
|
+
*
|
|
264
|
+
* The fallback is needed because dosai names a `System.*` assembly it cannot
|
|
265
|
+
* map to a package with a framework purl (`pkg:nuget/System.Runtime`), as it
|
|
266
|
+
* does whenever the tree has lock files but no restore output: the DLL of
|
|
267
|
+
* System.Data.SQLite.Core is still the package's. A DLL that only a package's
|
|
268
|
+
* build-time folders carry is not listed in `internal:PackageFiles`, so it
|
|
269
|
+
* credits no package with the project's framework calls (issue 4441).
|
|
246
270
|
*
|
|
247
271
|
* @param {Object[]} pkgList Array of .NET package component objects to enrich
|
|
248
272
|
* @param {string} slicesFile Path to the dosai dependency slices JSON file
|
|
273
|
+
* @param {Object} [options] Options
|
|
274
|
+
* @param {string} [options.srcPath] Directory dosai analyzed, which its relative locations
|
|
275
|
+
* and the components' relative manifest paths are relative to
|
|
249
276
|
* @returns {Object[]} The enriched package list (same array, mutated in place)
|
|
250
277
|
*/
|
|
251
|
-
export function addEvidenceForDotnet(pkgList, slicesFile) {
|
|
278
|
+
export function addEvidenceForDotnet(pkgList, slicesFile, options = {}) {
|
|
252
279
|
// We need two datastructures.
|
|
253
280
|
// dll to purl mapping from the pkgList
|
|
254
281
|
// purl to occurrences list using the slicesFile
|
|
255
282
|
if (!slicesFile || !safeExistsSync(slicesFile)) {
|
|
256
283
|
return pkgList;
|
|
257
284
|
}
|
|
258
|
-
const
|
|
285
|
+
const pkgFilePurls = {};
|
|
259
286
|
const purlLocationMap = {};
|
|
260
287
|
const purlModulesMap = {};
|
|
261
288
|
const purlMethodsMap = {};
|
|
262
289
|
const purlReachabilityMap = {};
|
|
263
|
-
const purlAliasMap = buildDosaiPurlAliasMap(pkgList);
|
|
290
|
+
const purlAliasMap = buildDosaiPurlAliasMap(pkgList, options);
|
|
291
|
+
const componentPurls = new Set(pkgList.map((apkg) => apkg.purl));
|
|
292
|
+
// A dosai purl that names a component of this list, resolved in the
|
|
293
|
+
// record's project.
|
|
294
|
+
const componentPurlFor = (purl, location) => {
|
|
295
|
+
const resolved = resolveDosaiComponentPurl(purl, purlAliasMap, location);
|
|
296
|
+
return componentPurls.has(resolved) ? resolved : undefined;
|
|
297
|
+
};
|
|
264
298
|
for (const apkg of pkgList) {
|
|
265
299
|
if (apkg.properties && Array.isArray(apkg.properties)) {
|
|
266
300
|
apkg.properties
|
|
@@ -270,20 +304,32 @@ export function addEvidenceForDotnet(pkgList, slicesFile) {
|
|
|
270
304
|
const tmpA = aprop.value.split(", ");
|
|
271
305
|
if (tmpA?.length) {
|
|
272
306
|
tmpA.forEach((dllFile) => {
|
|
273
|
-
|
|
307
|
+
pkgFilePurls[dllFile] ??= new Set();
|
|
308
|
+
pkgFilePurls[dllFile].add(apkg.purl);
|
|
274
309
|
});
|
|
275
310
|
}
|
|
276
311
|
}
|
|
277
312
|
});
|
|
278
313
|
}
|
|
279
314
|
}
|
|
315
|
+
// The component a DLL belongs to: its only package, else the candidate of the
|
|
316
|
+
// record's project.
|
|
317
|
+
const purlForDll = (dllFile, location) =>
|
|
318
|
+
pkgFilePurls[dllFile]
|
|
319
|
+
? pickDosaiComponentByLocation(
|
|
320
|
+
pkgFilePurls[dllFile],
|
|
321
|
+
purlAliasMap,
|
|
322
|
+
location,
|
|
323
|
+
)
|
|
324
|
+
: undefined;
|
|
280
325
|
// Only MethodCalls into a package .dll produce evidence below, so a report
|
|
281
326
|
// too large to parse whole keeps just those.
|
|
282
327
|
const slicesData = readDosaiMethodsReport(slicesFile, {
|
|
283
328
|
keepMethodCall: (methodCall) =>
|
|
284
329
|
Boolean(
|
|
285
|
-
methodCall?.
|
|
286
|
-
|
|
330
|
+
methodCall?.Purl ||
|
|
331
|
+
(methodCall?.Module?.endsWith(".dll") &&
|
|
332
|
+
pkgFilePurls[methodCall.Module]),
|
|
287
333
|
),
|
|
288
334
|
});
|
|
289
335
|
if (!slicesData) {
|
|
@@ -295,43 +341,36 @@ export function addEvidenceForDotnet(pkgList, slicesFile) {
|
|
|
295
341
|
);
|
|
296
342
|
if (slicesData.Dependencies) {
|
|
297
343
|
for (const adep of slicesData.Dependencies) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
);
|
|
311
|
-
}
|
|
344
|
+
const location = dosaiSourceLocation(adep);
|
|
345
|
+
const resolvedPurl = adep.Purl
|
|
346
|
+
? componentPurlFor(adep.Purl, adep.Path)
|
|
347
|
+
: undefined;
|
|
348
|
+
if (resolvedPurl) {
|
|
349
|
+
addDosaiSetValue(purlLocationMap, resolvedPurl, location);
|
|
350
|
+
addDosaiSetValue(
|
|
351
|
+
purlModulesMap,
|
|
352
|
+
resolvedPurl,
|
|
353
|
+
adep.Name || adep.Namespace,
|
|
354
|
+
);
|
|
355
|
+
continue;
|
|
312
356
|
}
|
|
313
357
|
// Case 1: Dependencies slice has the .dll file
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if (!purlLocationMap[modPurl]) {
|
|
331
|
-
purlLocationMap[modPurl] = new Set();
|
|
332
|
-
}
|
|
333
|
-
purlLocationMap[modPurl].add(`${adep.Path}#${adep.LineNumber}`);
|
|
334
|
-
}
|
|
358
|
+
// Case 2: System packages where the .dll information is missing. In
|
|
359
|
+
// this case, the dll file name is the name followed by dll.
|
|
360
|
+
const moduleDll = adep.Module?.endsWith(".dll")
|
|
361
|
+
? adep.Module
|
|
362
|
+
: adep?.Name &&
|
|
363
|
+
(adep?.Namespace?.startsWith("System") ||
|
|
364
|
+
adep?.Namespace?.startsWith("Microsoft"))
|
|
365
|
+
? `${adep.Name}.dll`
|
|
366
|
+
: undefined;
|
|
367
|
+
const modPurl = moduleDll && purlForDll(moduleDll, adep.Path);
|
|
368
|
+
if (modPurl) {
|
|
369
|
+
addDosaiSetValue(
|
|
370
|
+
purlLocationMap,
|
|
371
|
+
modPurl,
|
|
372
|
+
`${adep.Path}#${adep.LineNumber}`,
|
|
373
|
+
);
|
|
335
374
|
}
|
|
336
375
|
}
|
|
337
376
|
}
|
|
@@ -342,95 +381,113 @@ export function addEvidenceForDotnet(pkgList, slicesFile) {
|
|
|
342
381
|
const graphNodes = Object.fromEntries(
|
|
343
382
|
(slicesData.CallGraph?.Nodes || []).map((node) => [node.Id, node]),
|
|
344
383
|
);
|
|
345
|
-
for
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
//
|
|
354
|
-
//
|
|
355
|
-
|
|
384
|
+
// The first fact for a component wins, so a fact that names the
|
|
385
|
+
// component's exact purl goes before one that reached it by name.
|
|
386
|
+
const facts = [...slicesData.PackageReachability].sort(
|
|
387
|
+
(a, b) =>
|
|
388
|
+
Number(!componentPurls.has(a.Purl)) -
|
|
389
|
+
Number(!componentPurls.has(b.Purl)),
|
|
390
|
+
);
|
|
391
|
+
for (const reachability of facts) {
|
|
392
|
+
// A fact can span projects (a versionless purl from unrestored
|
|
393
|
+
// projects), so each location is resolved in its own project, and the
|
|
394
|
+
// fact applies to every component one of its locations reached.
|
|
395
|
+
const factPurls = new Set();
|
|
396
|
+
// Modules and methods of call-graph items that name no source file
|
|
397
|
+
// (frames inside the package), held for the fact's own version.
|
|
398
|
+
const unplaced = [];
|
|
399
|
+
const resolveAt = (location) =>
|
|
400
|
+
resolveDosaiComponentPurl(reachability.Purl, purlAliasMap, location);
|
|
356
401
|
let hasExplicitSourceLocations = false;
|
|
357
402
|
for (const sourceLocation of reachability.SourceLocations || []) {
|
|
358
403
|
const location = dosaiSourceLocation(sourceLocation);
|
|
404
|
+
const modPurl = resolveAt(location);
|
|
359
405
|
addDosaiSetValue(purlLocationMap, modPurl, location);
|
|
406
|
+
if (modPurl) {
|
|
407
|
+
factPurls.add(modPurl);
|
|
408
|
+
}
|
|
360
409
|
hasExplicitSourceLocations ||= Boolean(location);
|
|
361
410
|
}
|
|
362
411
|
for (const edgeId of reachability.EdgeIds || []) {
|
|
363
412
|
const edge = graphEdges[edgeId];
|
|
413
|
+
const location = dosaiSourceLocation(edge);
|
|
414
|
+
const modPurl = resolveAt(location ?? edge?.Path);
|
|
415
|
+
const method = edge?.CalledMethodName || edge?.TargetName;
|
|
416
|
+
if (!modPurl) {
|
|
417
|
+
unplaced.push([purlMethodsMap, method]);
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
factPurls.add(modPurl);
|
|
364
421
|
if (!hasExplicitSourceLocations) {
|
|
365
|
-
addDosaiSetValue(
|
|
366
|
-
purlLocationMap,
|
|
367
|
-
modPurl,
|
|
368
|
-
dosaiSourceLocation(edge),
|
|
369
|
-
);
|
|
422
|
+
addDosaiSetValue(purlLocationMap, modPurl, location);
|
|
370
423
|
}
|
|
371
|
-
addDosaiSetValue(
|
|
372
|
-
purlMethodsMap,
|
|
373
|
-
modPurl,
|
|
374
|
-
edge?.CalledMethodName || edge?.TargetName,
|
|
375
|
-
);
|
|
424
|
+
addDosaiSetValue(purlMethodsMap, modPurl, method);
|
|
376
425
|
}
|
|
377
426
|
for (const nodeId of reachability.NodeIds || []) {
|
|
378
427
|
const node = graphNodes[nodeId];
|
|
428
|
+
const location = dosaiSourceLocationFromNode(node);
|
|
429
|
+
const modPurl = resolveAt(location);
|
|
430
|
+
const module = node?.ClassName || node?.Module;
|
|
431
|
+
const method = node?.Name || node?.Identity?.MethodName;
|
|
432
|
+
if (!modPurl) {
|
|
433
|
+
unplaced.push([purlModulesMap, module], [purlMethodsMap, method]);
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
factPurls.add(modPurl);
|
|
379
437
|
if (!hasExplicitSourceLocations) {
|
|
380
|
-
addDosaiSetValue(
|
|
381
|
-
purlLocationMap,
|
|
382
|
-
modPurl,
|
|
383
|
-
dosaiSourceLocationFromNode(node),
|
|
384
|
-
);
|
|
438
|
+
addDosaiSetValue(purlLocationMap, modPurl, location);
|
|
385
439
|
}
|
|
386
|
-
addDosaiSetValue(
|
|
387
|
-
|
|
388
|
-
modPurl,
|
|
389
|
-
node?.ClassName || node?.Module,
|
|
390
|
-
);
|
|
391
|
-
addDosaiSetValue(
|
|
392
|
-
purlMethodsMap,
|
|
393
|
-
modPurl,
|
|
394
|
-
node?.Name || node?.Identity?.MethodName,
|
|
395
|
-
);
|
|
440
|
+
addDosaiSetValue(purlModulesMap, modPurl, module);
|
|
441
|
+
addDosaiSetValue(purlMethodsMap, modPurl, method);
|
|
396
442
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (
|
|
402
|
-
amethodCall.Module?.endsWith(".dll") &&
|
|
403
|
-
pkgFilePurlMap[amethodCall.Module]
|
|
404
|
-
) {
|
|
405
|
-
const modPurl = pkgFilePurlMap[amethodCall.Module];
|
|
406
|
-
if (!purlLocationMap[modPurl]) {
|
|
407
|
-
purlLocationMap[modPurl] = new Set();
|
|
408
|
-
}
|
|
409
|
-
if (!purlModulesMap[modPurl]) {
|
|
410
|
-
purlModulesMap[modPurl] = new Set();
|
|
443
|
+
if (!factPurls.size) {
|
|
444
|
+
const modPurl = resolveAt(undefined);
|
|
445
|
+
if (modPurl) {
|
|
446
|
+
factPurls.add(modPurl);
|
|
411
447
|
}
|
|
412
|
-
|
|
413
|
-
|
|
448
|
+
}
|
|
449
|
+
if (factPurls.size === 1) {
|
|
450
|
+
const [modPurl] = factPurls;
|
|
451
|
+
for (const [map, value] of unplaced) {
|
|
452
|
+
addDosaiSetValue(map, modPurl, value);
|
|
414
453
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
454
|
+
}
|
|
455
|
+
// Confidence/evidence facts for this purl, applied to the component below so an
|
|
456
|
+
// unbuilt tree stays distinguishable from an only-imported package.
|
|
457
|
+
for (const modPurl of factPurls) {
|
|
458
|
+
purlReachabilityMap[modPurl] ??= reachability;
|
|
420
459
|
}
|
|
421
460
|
}
|
|
422
461
|
}
|
|
423
|
-
if (slicesData.
|
|
424
|
-
for (const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
462
|
+
if (slicesData.MethodCalls) {
|
|
463
|
+
for (const amethodCall of slicesData.MethodCalls) {
|
|
464
|
+
// dosai's purl is resolved in the caller's project; the DLL name is the
|
|
465
|
+
// same for every version of the package, and fills in for a purl that
|
|
466
|
+
// names no component, such as the framework purl dosai gives a
|
|
467
|
+
// System.* assembly it cannot map to a package.
|
|
468
|
+
const modPurl =
|
|
469
|
+
(amethodCall.Purl
|
|
470
|
+
? componentPurlFor(amethodCall.Purl, amethodCall.Path)
|
|
471
|
+
: undefined) ||
|
|
472
|
+
(amethodCall.Module?.endsWith(".dll")
|
|
473
|
+
? purlForDll(amethodCall.Module, amethodCall.Path)
|
|
474
|
+
: undefined);
|
|
475
|
+
if (!modPurl) {
|
|
476
|
+
continue;
|
|
431
477
|
}
|
|
478
|
+
addDosaiSetValue(
|
|
479
|
+
purlLocationMap,
|
|
480
|
+
modPurl,
|
|
481
|
+
dosaiCallSiteLocation(amethodCall, options.srcPath),
|
|
482
|
+
);
|
|
483
|
+
addDosaiSetValue(purlModulesMap, modPurl, amethodCall.ClassName);
|
|
484
|
+
addDosaiSetValue(purlMethodsMap, modPurl, amethodCall.CalledMethod);
|
|
432
485
|
}
|
|
433
486
|
}
|
|
487
|
+
// AssemblyInformation used to fill in a missing version by assembly name.
|
|
488
|
+
// An assembly version is not the package version (Newtonsoft.Json 13.0.3
|
|
489
|
+
// ships assembly 13.0.0.0), the purl and bom-ref kept no version, and the
|
|
490
|
+
// last same-named assembly won, so the component described two packages.
|
|
434
491
|
}
|
|
435
492
|
if (Object.keys(purlLocationMap).length) {
|
|
436
493
|
for (const apkg of pkgList) {
|
|
@@ -495,20 +552,55 @@ export function convertOSQueryResults(
|
|
|
495
552
|
enhance = false,
|
|
496
553
|
osPackageListers = undefined,
|
|
497
554
|
) {
|
|
555
|
+
if (queryCategory === "ai_agent_skills") {
|
|
556
|
+
return convertAgentSkillRows(results);
|
|
557
|
+
}
|
|
498
558
|
const pkgList = [];
|
|
559
|
+
// deb, rpm, apk, pacman, Homebrew, MacPorts, snap and flatpak rows describe a
|
|
560
|
+
// package, not a file or an app: the package manager names them, and nothing
|
|
561
|
+
// in such a row is a path inside the package.
|
|
562
|
+
const isOsPackage = isOsPackagePurlType(queryObj.purlType);
|
|
563
|
+
const isLocatedPackage =
|
|
564
|
+
LOCATED_PACKAGE_PURL_TYPES.has(queryObj.purlType) ||
|
|
565
|
+
LOCATED_PACKAGE_CATEGORIES.has(queryCategory);
|
|
499
566
|
if (results?.length) {
|
|
500
567
|
for (const res of results) {
|
|
501
|
-
|
|
568
|
+
let version = deriveOsQueryVersion(res);
|
|
502
569
|
let name = deriveOsQueryName(res, results.length === 1, queryObj.name);
|
|
503
570
|
if (queryObj.purlType === "chrome-extension") {
|
|
504
|
-
|
|
571
|
+
// osquery computes identifier from the manifest key, which an
|
|
572
|
+
// unpacked extension may lack; the profile still knows its ID.
|
|
573
|
+
name = (
|
|
574
|
+
res.identifier ||
|
|
575
|
+
res.referenced_identifier ||
|
|
576
|
+
res.extension_id ||
|
|
577
|
+
name ||
|
|
578
|
+
""
|
|
579
|
+
).toLowerCase();
|
|
580
|
+
}
|
|
581
|
+
let npmScope;
|
|
582
|
+
if (queryObj.purlType === "npm") {
|
|
583
|
+
const scoped = /^@([^/]+)\/(.+)$/.exec(`${name || ""}`);
|
|
584
|
+
if (scoped) {
|
|
585
|
+
[, npmScope, name] = scoped;
|
|
586
|
+
}
|
|
505
587
|
}
|
|
506
588
|
let group = "";
|
|
507
|
-
const subpath =
|
|
589
|
+
const subpath =
|
|
590
|
+
isOsPackage || isLocatedPackage
|
|
591
|
+
? undefined
|
|
592
|
+
: res.path || res.admindir || res.source;
|
|
508
593
|
const publisher = deriveOsQueryPublisher(res);
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
594
|
+
if (queryObj.purlType === "vscode-extension") {
|
|
595
|
+
// osquery names a VS Code extension by its ID, <publisher>.<name>,
|
|
596
|
+
// which the vscode-extension type splits into namespace and name. Its
|
|
597
|
+
// publisher column is the publisher's display name.
|
|
598
|
+
const extensionId = /^([^.]+)\.(.+)$/u.exec(`${name || ""}`);
|
|
599
|
+
if (extensionId) {
|
|
600
|
+
[, group, name] = extensionId;
|
|
601
|
+
} else if (publisher) {
|
|
602
|
+
group = publisher.toLowerCase();
|
|
603
|
+
}
|
|
512
604
|
}
|
|
513
605
|
let scope;
|
|
514
606
|
const compScope = res.priority;
|
|
@@ -523,25 +615,53 @@ export function convertOSQueryResults(
|
|
|
523
615
|
qualifiers = {
|
|
524
616
|
tag_id: res.identifying_number.replace("{", "").replace("}", ""),
|
|
525
617
|
};
|
|
618
|
+
} else if (queryCategory === "firefox_addons" && res.identifier) {
|
|
619
|
+
// The profile an add-on was found in is not part of its purl, so the
|
|
620
|
+
// add-on ID is what tells apart two add-ons that share a name.
|
|
621
|
+
qualifiers = { tag_id: firefoxAddonTagId(res.identifier) };
|
|
526
622
|
}
|
|
527
623
|
if (name) {
|
|
528
624
|
name = sanitizeOsQueryIdentity(name);
|
|
529
|
-
|
|
625
|
+
// sanitizeOsQueryIdentity drops a leading @, which an npm scope keeps.
|
|
626
|
+
group = npmScope
|
|
627
|
+
? `@${sanitizeOsQueryIdentity(npmScope)}`
|
|
628
|
+
: sanitizeOsQueryIdentity(group);
|
|
530
629
|
const isCryptoAsset = queryObj.componentType === "cryptographic-asset";
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
630
|
+
let purl;
|
|
631
|
+
if (isOsPackage) {
|
|
632
|
+
// The version of such a row is part of its identity: an rpm's is its
|
|
633
|
+
// version and release, behind its epoch.
|
|
634
|
+
const identity = createOsQueryPackageIdentity(
|
|
635
|
+
queryObj.purlType,
|
|
636
|
+
res,
|
|
637
|
+
name,
|
|
638
|
+
);
|
|
639
|
+
version = identity.version;
|
|
640
|
+
purl = shouldCreateOsQueryPurl(queryObj.componentType)
|
|
641
|
+
? identity.purl
|
|
642
|
+
: undefined;
|
|
643
|
+
} else if (shouldCreateOsQueryPurl(queryObj.componentType)) {
|
|
644
|
+
purl = createOsQueryPurl(
|
|
645
|
+
queryObj.purlType,
|
|
646
|
+
group,
|
|
647
|
+
name,
|
|
648
|
+
version,
|
|
649
|
+
qualifiers,
|
|
650
|
+
subpath,
|
|
651
|
+
);
|
|
652
|
+
}
|
|
541
653
|
const props = [{ name: "cdx:osquery:category", value: queryCategory }];
|
|
542
654
|
props.push(...createLolbasProperties(queryCategory, res));
|
|
543
655
|
if (platform() === "linux") {
|
|
544
|
-
|
|
656
|
+
// The GTFOBins matcher reads a description for command names, and
|
|
657
|
+
// the free text of a package's description matches by chance: zlib's
|
|
658
|
+
// "the deflate compression method found in gzip" is no use of gzip.
|
|
659
|
+
props.push(
|
|
660
|
+
...createGtfoBinsPropertiesFromRow(
|
|
661
|
+
queryCategory,
|
|
662
|
+
isOsPackage ? { ...res, description: undefined } : res,
|
|
663
|
+
),
|
|
664
|
+
);
|
|
545
665
|
}
|
|
546
666
|
let providesList;
|
|
547
667
|
if (enhance) {
|
|
@@ -575,10 +695,12 @@ export function convertOSQueryResults(
|
|
|
575
695
|
version: version || "",
|
|
576
696
|
description,
|
|
577
697
|
publisher,
|
|
698
|
+
// Without a purl, the bom-ref is built from the row, and the path of
|
|
699
|
+
// a located package is not part of what it is.
|
|
578
700
|
"bom-ref": createOsQueryBomRef(
|
|
579
701
|
queryCategory,
|
|
580
702
|
queryObj.componentType,
|
|
581
|
-
res,
|
|
703
|
+
isLocatedPackage ? { ...res, path: undefined } : res,
|
|
582
704
|
name,
|
|
583
705
|
version,
|
|
584
706
|
purl,
|
|
@@ -599,25 +721,361 @@ export function convertOSQueryResults(
|
|
|
599
721
|
if (cryptoProperties) {
|
|
600
722
|
apkg.cryptoProperties = cryptoProperties;
|
|
601
723
|
}
|
|
724
|
+
const licenseColumns = isOsPackage
|
|
725
|
+
? OS_PACKAGE_LICENSE_COLUMNS[queryObj.purlType]
|
|
726
|
+
: undefined;
|
|
727
|
+
if (licenseColumns) {
|
|
728
|
+
const licenses = osPackageLicenses(
|
|
729
|
+
res[licenseColumns.license],
|
|
730
|
+
licenseColumns.separator,
|
|
731
|
+
);
|
|
732
|
+
if (licenses.length) {
|
|
733
|
+
apkg.licenses = licenses;
|
|
734
|
+
}
|
|
735
|
+
const website = licenseColumns.website
|
|
736
|
+
? osPackageWebsite(res[licenseColumns.website])
|
|
737
|
+
: undefined;
|
|
738
|
+
if (website) {
|
|
739
|
+
apkg.externalReferences = [{ type: "website", url: website }];
|
|
740
|
+
}
|
|
741
|
+
}
|
|
602
742
|
for (const k of Object.keys(res).filter((p) => {
|
|
603
743
|
if (["version", "description", "publisher"].includes(p)) {
|
|
604
744
|
return false;
|
|
605
745
|
}
|
|
606
746
|
return !(queryObj.purlType !== "chrome-extension" && p === "name");
|
|
607
747
|
})) {
|
|
608
|
-
|
|
748
|
+
const propertyName = isOsPackage
|
|
749
|
+
? osPackagePropertyName(queryObj.purlType, k)
|
|
750
|
+
: k;
|
|
751
|
+
// (none) is what rpm's database says for a tag a record lacks, such
|
|
752
|
+
// as the source rpm of a key record.
|
|
753
|
+
if (
|
|
754
|
+
propertyName &&
|
|
755
|
+
res[k] &&
|
|
756
|
+
res[k] !== "null" &&
|
|
757
|
+
!(isOsPackage && res[k] === "(none)")
|
|
758
|
+
) {
|
|
609
759
|
props.push({
|
|
610
|
-
name:
|
|
760
|
+
name: propertyName,
|
|
611
761
|
value: res[k],
|
|
612
762
|
});
|
|
613
763
|
}
|
|
614
764
|
}
|
|
765
|
+
if (isLocatedPackage) {
|
|
766
|
+
// osquery writes a JSON null as "null", which is the path Firefox
|
|
767
|
+
// gives its default theme.
|
|
768
|
+
const occurrence = createOccurrenceEvidence(
|
|
769
|
+
[res.path, res.directory].find(
|
|
770
|
+
(value) => value && value !== "null",
|
|
771
|
+
),
|
|
772
|
+
);
|
|
773
|
+
if (occurrence) {
|
|
774
|
+
apkg.evidence = { occurrences: [occurrence] };
|
|
775
|
+
}
|
|
776
|
+
}
|
|
615
777
|
apkg.properties = props;
|
|
616
778
|
pkgList.push(apkg);
|
|
617
779
|
}
|
|
618
780
|
}
|
|
619
781
|
}
|
|
620
|
-
return pkgList;
|
|
782
|
+
return isLocatedPackage ? mergeLocatedPackages(pkgList) : pkgList;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// npm and pypi rows name a package found at a path on the host, Chromium and
|
|
786
|
+
// VS Code extension rows an extension found in a browser profile or an editor's
|
|
787
|
+
// extensions folder, and a firefox_addons row an add-on found in a Firefox
|
|
788
|
+
// profile. Where it was found is not part of what it is, so the path is an
|
|
789
|
+
// occurrence rather than a subpath: a home directory stays out of the purl, and
|
|
790
|
+
// one package installed in two places is one component with two occurrences.
|
|
791
|
+
const LOCATED_PACKAGE_PURL_TYPES = new Set([
|
|
792
|
+
"npm",
|
|
793
|
+
"pypi",
|
|
794
|
+
"chrome-extension",
|
|
795
|
+
"vscode-extension",
|
|
796
|
+
]);
|
|
797
|
+
const LOCATED_PACKAGE_CATEGORIES = new Set(["firefox_addons"]);
|
|
798
|
+
|
|
799
|
+
const FIREFOX_ADDON_GUID =
|
|
800
|
+
/^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/iu;
|
|
801
|
+
|
|
802
|
+
// A Firefox add-on ID is an email-like string or a GUID in braces. A swid
|
|
803
|
+
// tag_id that is a GUID must be in lower case, and is written without braces.
|
|
804
|
+
function firefoxAddonTagId(identifier) {
|
|
805
|
+
const id = `${identifier}`.trim();
|
|
806
|
+
const guid = FIREFOX_ADDON_GUID.exec(id);
|
|
807
|
+
return guid ? guid[1].toLowerCase() : id;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function mergeLocatedPackages(components) {
|
|
811
|
+
const byRef = new Map();
|
|
812
|
+
for (const component of components) {
|
|
813
|
+
const existing = byRef.get(component["bom-ref"]);
|
|
814
|
+
if (!existing) {
|
|
815
|
+
byRef.set(component["bom-ref"], component);
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
const occurrences = existing.evidence?.occurrences || [];
|
|
819
|
+
for (const occurrence of component.evidence?.occurrences || []) {
|
|
820
|
+
if (
|
|
821
|
+
!occurrences.some((known) => known.location === occurrence.location)
|
|
822
|
+
) {
|
|
823
|
+
occurrences.push(occurrence);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
if (occurrences.length) {
|
|
827
|
+
existing.evidence = { ...existing.evidence, occurrences };
|
|
828
|
+
}
|
|
829
|
+
for (const property of component.properties) {
|
|
830
|
+
if (
|
|
831
|
+
!existing.properties.some(
|
|
832
|
+
(known) =>
|
|
833
|
+
known.name === property.name && known.value === property.value,
|
|
834
|
+
)
|
|
835
|
+
) {
|
|
836
|
+
existing.properties.push(property);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
for (const component of byRef.values()) {
|
|
841
|
+
component.evidence?.occurrences?.sort((a, b) =>
|
|
842
|
+
a.location.localeCompare(b.location),
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
return [...byRef.values()];
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// The licence an OS package declares. An apk `L:` field is a list of SPDX ids
|
|
849
|
+
// separated by spaces on older packages and an SPDX expression on newer ones;
|
|
850
|
+
// pacman keeps one licence per entry, each an SPDX id or expression, which
|
|
851
|
+
// osquery joins with commas; a snap or a flatpak declares one expression. Only
|
|
852
|
+
// an explicit operator or a parenthesis makes a text an expression: the SPDX
|
|
853
|
+
// parser's own check takes the `or` inside `LGPL-2.0-or-later` for an operator,
|
|
854
|
+
// and would turn a plain list into one expression. A token is an id only when
|
|
855
|
+
// the SPDX list has what it maps to; any other is kept by name rather than
|
|
856
|
+
// dropped.
|
|
857
|
+
const MAX_OS_PACKAGE_LICENSES = 64;
|
|
858
|
+
const SPDX_OPERATORS = new Set(["AND", "OR", "WITH"]);
|
|
859
|
+
|
|
860
|
+
function isExplicitSpdxExpression(text) {
|
|
861
|
+
// No SPDX expression holds a comma, but the expression parser lets one
|
|
862
|
+
// through, which would take a whole pacman licence list for one expression.
|
|
863
|
+
return (
|
|
864
|
+
!text.includes(",") &&
|
|
865
|
+
(text.split(/\s+/u).some((token) => SPDX_OPERATORS.has(token)) ||
|
|
866
|
+
text.includes("(")) &&
|
|
867
|
+
isSpdxLicenseExpression(text)
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function osPackageLicenses(text, separator) {
|
|
872
|
+
const license = `${text ?? ""}`.split(/\s+/u).filter(Boolean).join(" ");
|
|
873
|
+
if (!license) {
|
|
874
|
+
return [];
|
|
875
|
+
}
|
|
876
|
+
if (isExplicitSpdxExpression(license)) {
|
|
877
|
+
return [{ expression: license }];
|
|
878
|
+
}
|
|
879
|
+
const entries = [];
|
|
880
|
+
const items = separator ? license.split(separator) : [license];
|
|
881
|
+
for (const item of items.slice(0, MAX_OS_PACKAGE_LICENSES)) {
|
|
882
|
+
const entry = item.trim();
|
|
883
|
+
if (!entry || SPDX_OPERATORS.has(entry)) {
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
if (isExplicitSpdxExpression(entry)) {
|
|
887
|
+
entries.push({ expression: entry });
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
const id = findLicenseId(entry);
|
|
891
|
+
entries.push(
|
|
892
|
+
spdxLicenses.includes(id)
|
|
893
|
+
? { license: { id } }
|
|
894
|
+
: { license: { name: entry } },
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
// A list that holds an expression must become one expression, since a
|
|
898
|
+
// component's licenses are either licences or a single expression. Every
|
|
899
|
+
// entry applies, so they are joined with AND.
|
|
900
|
+
if (entries.length > 1 && entries.some((entry) => entry.expression)) {
|
|
901
|
+
const parts = entries.map((entry) =>
|
|
902
|
+
entry.expression ? `(${entry.expression})` : entry.license.id,
|
|
903
|
+
);
|
|
904
|
+
const expression = parts.join(" AND ");
|
|
905
|
+
if (parts.every(Boolean) && isSpdxLicenseExpression(expression)) {
|
|
906
|
+
return [{ expression }];
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
return adjustLicenseInformation(entries);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// The home page of a package. A URL a package carries is untrusted text: only
|
|
913
|
+
// an http(s) URL is kept, without credentials, query or fragment.
|
|
914
|
+
function osPackageWebsite(value) {
|
|
915
|
+
const cleaned = sanitizeBomUrl(`${value ?? ""}`.trim());
|
|
916
|
+
try {
|
|
917
|
+
const { protocol } = new URL(cleaned);
|
|
918
|
+
return protocol === "http:" || protocol === "https:" ? cleaned : undefined;
|
|
919
|
+
} catch {
|
|
920
|
+
return undefined;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Convert the packages of an apk installed database into components.
|
|
926
|
+
*
|
|
927
|
+
* The packages are named as the container scan names an Alpine, Wolfi or
|
|
928
|
+
* Chainguard package, and grouped with the other OS package categories under
|
|
929
|
+
* `cdx:osquery:category` = `apk_packages`. Beyond what an osquery package row
|
|
930
|
+
* gives, an apk record knows its licence and home page, which become
|
|
931
|
+
* `licenses` and a `website` external reference.
|
|
932
|
+
*
|
|
933
|
+
* @param {Object[]} packages Packages from readApkInstalledDb()
|
|
934
|
+
* @returns {Object[]} Components
|
|
935
|
+
*/
|
|
936
|
+
export function convertApkPackages(packages) {
|
|
937
|
+
const components = [];
|
|
938
|
+
for (const pkg of packages || []) {
|
|
939
|
+
const row = { name: pkg.name, version: pkg.version };
|
|
940
|
+
for (const column of ["arch", "origin", "maintainer"]) {
|
|
941
|
+
if (pkg[column]) {
|
|
942
|
+
row[column] = pkg[column];
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
const [component] = convertOSQueryResults(
|
|
946
|
+
"apk_packages",
|
|
947
|
+
{ purlType: "apk" },
|
|
948
|
+
[row],
|
|
949
|
+
);
|
|
950
|
+
if (!component) {
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
953
|
+
// The description is set here and not in the row. The GTFOBins matcher reads
|
|
954
|
+
// a row's description for command names, which a deb or rpm row has no
|
|
955
|
+
// column for, and the free text of an apk description matches by chance:
|
|
956
|
+
// "Alpine base dir structure" is no use of the alpine mail client.
|
|
957
|
+
if (pkg.description) {
|
|
958
|
+
component.description = pkg.description;
|
|
959
|
+
}
|
|
960
|
+
const licenses = osPackageLicenses(pkg.license, /\s+/u);
|
|
961
|
+
if (licenses.length) {
|
|
962
|
+
component.licenses = licenses;
|
|
963
|
+
}
|
|
964
|
+
const website = osPackageWebsite(pkg.url);
|
|
965
|
+
if (website) {
|
|
966
|
+
component.externalReferences = [{ type: "website", url: website }];
|
|
967
|
+
}
|
|
968
|
+
components.push(component);
|
|
969
|
+
}
|
|
970
|
+
return components;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// The frontmatter and osquery columns of a skill row that become properties,
|
|
974
|
+
// named as the ai-skill project scan names them where it has a name.
|
|
975
|
+
const AGENT_SKILL_PROPERTY_NAMES = [
|
|
976
|
+
["name", "cdx:skill:name"],
|
|
977
|
+
["description", "cdx:skill:description"],
|
|
978
|
+
["license", "cdx:skill:license"],
|
|
979
|
+
["compatibility", "cdx:skill:compatibility"],
|
|
980
|
+
["agent", "cdx:skill:agent"],
|
|
981
|
+
["scope", "cdx:skill:scope"],
|
|
982
|
+
["allowed_tools", "cdx:skill:allowedTools"],
|
|
983
|
+
["resource_count", "cdx:skill:resourceCount"],
|
|
984
|
+
["script_count", "cdx:skill:scriptCount"],
|
|
985
|
+
["username", "username"],
|
|
986
|
+
];
|
|
987
|
+
const SHA256_HEX = /^[0-9a-f]{64}$/u;
|
|
988
|
+
|
|
989
|
+
// A skill's licence is free text ("Complete terms in LICENSE.txt"), so it
|
|
990
|
+
// becomes `licenses` only when it is an SPDX id or expression.
|
|
991
|
+
function agentSkillLicenses(text) {
|
|
992
|
+
const license = `${text ?? ""}`.trim();
|
|
993
|
+
if (!license) {
|
|
994
|
+
return [];
|
|
995
|
+
}
|
|
996
|
+
if (isExplicitSpdxExpression(license)) {
|
|
997
|
+
return [{ expression: license }];
|
|
998
|
+
}
|
|
999
|
+
const id = findLicenseId(license);
|
|
1000
|
+
return spdxLicenses.includes(id) ? [{ license: { id } }] : [];
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* Convert the rows of the `ai_agent_skills` query into components.
|
|
1005
|
+
*
|
|
1006
|
+
* A skill installed on the host is what the `ai-skill` project scan finds in a
|
|
1007
|
+
* repository, so it is a `file` component with the same `cdx:file:kind` and
|
|
1008
|
+
* `cdx:skill:*` properties, and no purl: a skill has no package identity. Its
|
|
1009
|
+
* bom-ref is the SHA-256 of its SKILL.md rather than the path, which is under a
|
|
1010
|
+
* home directory, and where it was found is an occurrence. The same skill
|
|
1011
|
+
* found under several roots, such as a plugin marketplace checkout and the
|
|
1012
|
+
* plugin cache, is one component with one occurrence for each.
|
|
1013
|
+
*
|
|
1014
|
+
* @param {Object[]} results Rows of `ai_agent_skills` joined with `hash`
|
|
1015
|
+
* @returns {Object[]} Components
|
|
1016
|
+
*/
|
|
1017
|
+
export function convertAgentSkillRows(results) {
|
|
1018
|
+
const components = [];
|
|
1019
|
+
for (const row of results || []) {
|
|
1020
|
+
const skillFile = `${row.path ?? ""}`;
|
|
1021
|
+
const directory = `${row.directory ?? ""}` || dirname(skillFile);
|
|
1022
|
+
// A SKILL.md whose frontmatter osquery cannot read has no name; the
|
|
1023
|
+
// project scan names such a skill after its directory too.
|
|
1024
|
+
const name = sanitizeOsQueryIdentity(row.name || basename(directory));
|
|
1025
|
+
if (!skillFile || !name) {
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1028
|
+
const version = row.version || "";
|
|
1029
|
+
const sha256 = SHA256_HEX.test(`${row.sha256 ?? ""}`)
|
|
1030
|
+
? row.sha256
|
|
1031
|
+
: undefined;
|
|
1032
|
+
const properties = [
|
|
1033
|
+
{ name: "cdx:osquery:category", value: "ai_agent_skills" },
|
|
1034
|
+
{ name: "cdx:file:kind", value: "skill-file" },
|
|
1035
|
+
{ name: "cdx:agent:inventorySource", value: "osquery" },
|
|
1036
|
+
];
|
|
1037
|
+
for (const [column, propertyName] of AGENT_SKILL_PROPERTY_NAMES) {
|
|
1038
|
+
const value = `${row[column] ?? ""}`.trim();
|
|
1039
|
+
if (value && value !== "null") {
|
|
1040
|
+
properties.push({ name: propertyName, value });
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
if (Number(row.script_count) > 0) {
|
|
1044
|
+
properties.push({ name: "cdx:skill:hasScripts", value: "true" });
|
|
1045
|
+
}
|
|
1046
|
+
properties.push({
|
|
1047
|
+
name: "cdx:skill:specValid",
|
|
1048
|
+
value: isAgentSkillSpecValid(row) ? "true" : "false",
|
|
1049
|
+
});
|
|
1050
|
+
const component = {
|
|
1051
|
+
name,
|
|
1052
|
+
version,
|
|
1053
|
+
description: row.description || "",
|
|
1054
|
+
type: "file",
|
|
1055
|
+
"bom-ref": createOsQueryFallbackBomRef(
|
|
1056
|
+
"ai_agent_skills",
|
|
1057
|
+
"file",
|
|
1058
|
+
name,
|
|
1059
|
+
version,
|
|
1060
|
+
sha256 ? "sha256" : "scope",
|
|
1061
|
+
sha256 || `${row.agent || ""}/${row.scope || ""}`,
|
|
1062
|
+
),
|
|
1063
|
+
properties,
|
|
1064
|
+
};
|
|
1065
|
+
if (sha256) {
|
|
1066
|
+
component.hashes = [{ alg: "SHA-256", content: sha256 }];
|
|
1067
|
+
}
|
|
1068
|
+
const licenses = agentSkillLicenses(row.license);
|
|
1069
|
+
if (licenses.length) {
|
|
1070
|
+
component.licenses = licenses;
|
|
1071
|
+
}
|
|
1072
|
+
const occurrence = createOccurrenceEvidence(skillFile);
|
|
1073
|
+
if (occurrence) {
|
|
1074
|
+
component.evidence = { occurrences: [occurrence] };
|
|
1075
|
+
}
|
|
1076
|
+
components.push(component);
|
|
1077
|
+
}
|
|
1078
|
+
return mergeLocatedPackages(components);
|
|
621
1079
|
}
|
|
622
1080
|
function createOsQueryCryptoProperties(queryCategory, res, version) {
|
|
623
1081
|
switch (queryCategory) {
|