@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
package/lib/core/activity.js
CHANGED
|
@@ -285,6 +285,18 @@ const MANIFEST_ACTIVITY_HINTS = new Map([
|
|
|
285
285
|
],
|
|
286
286
|
]);
|
|
287
287
|
const SENSITIVE_CONFIG_ACTIVITY_HINTS = [
|
|
288
|
+
{
|
|
289
|
+
matcher: (_lowerPath, baseName) =>
|
|
290
|
+
baseName === ".terraformrc" ||
|
|
291
|
+
baseName === "terraform.rc" ||
|
|
292
|
+
baseName === "credentials.tfrc.json",
|
|
293
|
+
metadata: {
|
|
294
|
+
classification: "credential",
|
|
295
|
+
ecosystem: "terraform",
|
|
296
|
+
label: "Terraform CLI configuration",
|
|
297
|
+
sensitive: true,
|
|
298
|
+
},
|
|
299
|
+
},
|
|
288
300
|
{
|
|
289
301
|
matcher: (lowerPath, _baseName) =>
|
|
290
302
|
lowerPath.includes("/.cargo/config.toml") ||
|
package/lib/core/env.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
1
|
+
import { join, win32 } from "node:path";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
buildReadCountSuffix,
|
|
@@ -111,19 +111,27 @@ export function shouldFetchLicense() {
|
|
|
111
111
|
);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Determines whether registry provenance, such as publish times, publishers
|
|
116
|
+
* and trusted-publishing signals, is wanted. `--bom-audit` turns it on through
|
|
117
|
+
* CDXGEN_FETCH_PKG_METADATA. Local data cannot answer these questions, so a
|
|
118
|
+
* package whose licence and description are known locally is still looked up.
|
|
119
|
+
*
|
|
120
|
+
* @returns {boolean} True if CDXGEN_FETCH_PKG_METADATA is "true" or "1"
|
|
121
|
+
*/
|
|
122
|
+
export function shouldFetchRegistryProvenance() {
|
|
123
|
+
return ["true", "1"].includes(
|
|
124
|
+
readEnvironmentVariable("CDXGEN_FETCH_PKG_METADATA"),
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
114
128
|
/**
|
|
115
129
|
* Determines whether remote package metadata should be fetched for enrichment.
|
|
116
130
|
*
|
|
117
131
|
* @returns {boolean} True when registry metadata enrichment is enabled.
|
|
118
132
|
*/
|
|
119
133
|
export function shouldFetchPackageMetadata() {
|
|
120
|
-
return (
|
|
121
|
-
shouldFetchLicense() ||
|
|
122
|
-
(readEnvironmentVariable("CDXGEN_FETCH_PKG_METADATA") &&
|
|
123
|
-
["true", "1"].includes(
|
|
124
|
-
readEnvironmentVariable("CDXGEN_FETCH_PKG_METADATA"),
|
|
125
|
-
))
|
|
126
|
-
);
|
|
134
|
+
return shouldFetchLicense() || shouldFetchRegistryProvenance();
|
|
127
135
|
}
|
|
128
136
|
|
|
129
137
|
/**
|
|
@@ -293,6 +301,13 @@ export const SWIFT_CMD = readEnvironmentVariable("SWIFT_CMD") || "swift";
|
|
|
293
301
|
/** Resolved Ruby executable command (RUBY_CMD env var, or "ruby"). */
|
|
294
302
|
export const RUBY_CMD = readEnvironmentVariable("RUBY_CMD") || "ruby";
|
|
295
303
|
|
|
304
|
+
/** Resolved PHP executable command (PHP_CMD env var, or "php"). */
|
|
305
|
+
export const PHP_CMD = readEnvironmentVariable("PHP_CMD") || "php";
|
|
306
|
+
|
|
307
|
+
/** Resolved Composer executable command (COMPOSER_CMD env var, or "composer"). */
|
|
308
|
+
export const COMPOSER_CMD =
|
|
309
|
+
readEnvironmentVariable("COMPOSER_CMD") || "composer";
|
|
310
|
+
|
|
296
311
|
/** Python package components that can be excluded from the generated BOM. */
|
|
297
312
|
export const PYTHON_EXCLUDED_COMPONENTS = [
|
|
298
313
|
"pip",
|
|
@@ -457,7 +472,7 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
457
472
|
perl: ["perl", "cpan", "carton", "cpanfile"],
|
|
458
473
|
spack: ["spack"],
|
|
459
474
|
terraform: ["terraform", "opentofu", "tf"],
|
|
460
|
-
c: ["c", "cpp", "c++", "conan", "collider"],
|
|
475
|
+
c: ["c", "cpp", "c++", "conan", "collider", "cmake", "meson", "vcpkg"],
|
|
461
476
|
clojure: ["clojure", "edn", "clj", "leiningen"],
|
|
462
477
|
github: ["github", "actions"],
|
|
463
478
|
hbom: ["hbom", "hardware"],
|
|
@@ -493,7 +508,7 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
493
508
|
binary: ["binary", "blint"],
|
|
494
509
|
oci: ["docker", "image", "oci", "container", "podman", "rootfs", "oci-dir"],
|
|
495
510
|
cocoa: ["cocoa", "cocoapods", "objective-c", "swift", "ios"],
|
|
496
|
-
scala: ["scala", "scala3", "sbt", "mill"],
|
|
511
|
+
scala: ["scala", "scala3", "sbt", "mill", "scala-cli"],
|
|
497
512
|
nix: ["nix", "nixos", "flake"],
|
|
498
513
|
zig: ["zig", "zon"],
|
|
499
514
|
gleam: ["gleam"],
|
|
@@ -699,23 +714,38 @@ export function hasAnyProjectType(projectTypes, options, defaultStatus = true) {
|
|
|
699
714
|
return shouldInclude;
|
|
700
715
|
}
|
|
701
716
|
|
|
717
|
+
/**
|
|
718
|
+
* Extract the lowercase command name from an invoked command path, without
|
|
719
|
+
* directories or a trailing `.js`, `.cjs`, `.mjs` or `.exe` extension. Both
|
|
720
|
+
* `/` and `\\` separators are handled, so a Windows path is inspected the
|
|
721
|
+
* same way on any host.
|
|
722
|
+
*
|
|
723
|
+
* @param {string} [commandPath] Invoked command path or name
|
|
724
|
+
* @returns {string} Lowercase command name
|
|
725
|
+
*/
|
|
726
|
+
export function getCommandName(commandPath) {
|
|
727
|
+
return win32
|
|
728
|
+
.basename(`${commandPath || ""}`)
|
|
729
|
+
.toLowerCase()
|
|
730
|
+
.replace(/\.(?:[cm]?js|exe)$/u, "");
|
|
731
|
+
}
|
|
732
|
+
|
|
702
733
|
/**
|
|
703
734
|
* Determine whether the predictive dependency audit should run for the current
|
|
704
735
|
* CLI invocation.
|
|
705
736
|
*
|
|
706
|
-
* OBOM
|
|
707
|
-
*
|
|
737
|
+
* OBOM and HBOM runs (commands named `obom` or `hbom`, including `hbom-slim`)
|
|
738
|
+
* and explicit `-t os` / OS / hardware project types alone keep the direct BOM
|
|
739
|
+
* audit findings but skip the predictive dependency audit. Only the command
|
|
740
|
+
* name is tested, never the directories leading to it.
|
|
708
741
|
*
|
|
709
742
|
* @param {object} options CLI options
|
|
710
743
|
* @param {string} [commandPath] Invoked command path or name
|
|
711
744
|
* @returns {boolean} True when predictive dependency audit should run
|
|
712
745
|
*/
|
|
713
746
|
export function shouldRunPredictiveBomAudit(options, commandPath) {
|
|
714
|
-
const
|
|
715
|
-
if (
|
|
716
|
-
return false;
|
|
717
|
-
}
|
|
718
|
-
if (normalizedCommandPath.includes("hbom")) {
|
|
747
|
+
const commandName = getCommandName(commandPath);
|
|
748
|
+
if (commandName.startsWith("obom") || commandName.startsWith("hbom")) {
|
|
719
749
|
return false;
|
|
720
750
|
}
|
|
721
751
|
const projectTypes = Array.isArray(options?.projectType)
|
package/lib/core/fs.js
CHANGED
|
@@ -522,8 +522,13 @@ function isWindowsShellHijackRisk(command, options) {
|
|
|
522
522
|
const absCwd = resolve(cwd);
|
|
523
523
|
for (const candidate of candidates) {
|
|
524
524
|
const candidatePath = path.join(absCwd, candidate);
|
|
525
|
-
|
|
526
|
-
|
|
525
|
+
// cmd.exe only runs files, so a directory named like the tool is harmless.
|
|
526
|
+
try {
|
|
527
|
+
if (statSync(candidatePath).isFile()) {
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
} catch (_err) {
|
|
531
|
+
// Not present.
|
|
527
532
|
}
|
|
528
533
|
}
|
|
529
534
|
return false;
|
|
@@ -577,7 +582,7 @@ export function shellQuoteArgument(arg, forWindows = isWin) {
|
|
|
577
582
|
backslashes++;
|
|
578
583
|
quoted += ch;
|
|
579
584
|
} else if (ch === '"') {
|
|
580
|
-
quoted += "\\".repeat(backslashes)
|
|
585
|
+
quoted += `${"\\".repeat(backslashes)}\\"`;
|
|
581
586
|
backslashes = 0;
|
|
582
587
|
} else {
|
|
583
588
|
quoted += ch;
|
|
@@ -831,24 +836,28 @@ export function safeSpawnSync(command, args, options) {
|
|
|
831
836
|
error: new Error("No execute permission"),
|
|
832
837
|
};
|
|
833
838
|
}
|
|
839
|
+
// cmd.exe looks in the working directory before PATH, so a scanned project
|
|
840
|
+
// could otherwise replace the tool cdxgen means to run. This holds outside
|
|
841
|
+
// secure mode too, since --no-install-deps scans still run version probes
|
|
842
|
+
// and read-only tools with the project as the working directory.
|
|
843
|
+
if (isWindowsShellHijackRisk(command, options)) {
|
|
844
|
+
const blockedReason = `${command} matches local file in cwd (Windows shell hijack risk)`;
|
|
845
|
+
console.warn(`\x1b[1;31mSecurity Alert: ${blockedReason}\x1b[0m`);
|
|
846
|
+
recordActivity({
|
|
847
|
+
kind: activityDescriptor.kind,
|
|
848
|
+
...activityDescriptor.metadata,
|
|
849
|
+
reason: blockedReason,
|
|
850
|
+
status: "blocked",
|
|
851
|
+
target: activityDescriptor.target,
|
|
852
|
+
});
|
|
853
|
+
return {
|
|
854
|
+
status: 1,
|
|
855
|
+
stdout: undefined,
|
|
856
|
+
stderr: undefined,
|
|
857
|
+
error: new Error(blockedReason),
|
|
858
|
+
};
|
|
859
|
+
}
|
|
834
860
|
if (isSecureMode) {
|
|
835
|
-
if (isWindowsShellHijackRisk(command, options)) {
|
|
836
|
-
const blockedReason = `${command} matches local file in cwd (Windows shell hijack risk)`;
|
|
837
|
-
console.warn(`\x1b[1;31mSecurity Alert: ${blockedReason}\x1b[0m`);
|
|
838
|
-
recordActivity({
|
|
839
|
-
kind: activityDescriptor.kind,
|
|
840
|
-
...activityDescriptor.metadata,
|
|
841
|
-
reason: blockedReason,
|
|
842
|
-
status: "blocked",
|
|
843
|
-
target: activityDescriptor.target,
|
|
844
|
-
});
|
|
845
|
-
return {
|
|
846
|
-
status: 1,
|
|
847
|
-
stdout: undefined,
|
|
848
|
-
stderr: undefined,
|
|
849
|
-
error: new Error(blockedReason),
|
|
850
|
-
};
|
|
851
|
-
}
|
|
852
861
|
if (options?.cwd && options.cwd !== resolve(options.cwd)) {
|
|
853
862
|
if (DEBUG_MODE) {
|
|
854
863
|
console.log(
|
|
@@ -1025,7 +1034,8 @@ export const MAX_BUFFER =
|
|
|
1025
1034
|
export function getAllFiles(dirPath, pattern, options = {}) {
|
|
1026
1035
|
let ignoreList = [];
|
|
1027
1036
|
if (!options.noIgnore) {
|
|
1028
|
-
|
|
1037
|
+
// .chen holds chen's AST cache, which atom writes into a C/C++ source tree
|
|
1038
|
+
ignoreList.push("**/.hg/**", "**/.git/**", "**/.chen/**");
|
|
1029
1039
|
// Only ignore node_modules if the caller is not looking for package.json
|
|
1030
1040
|
if (!pattern.includes("package.json") && !options.includeNodeModulesDir) {
|
|
1031
1041
|
ignoreList.push("**/node_modules/**");
|
package/lib/core/httpClient.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import process from "node:process";
|
|
3
4
|
import { brotliDecompressSync, gunzipSync, inflateSync } from "node:zlib";
|
|
4
5
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
interceptors,
|
|
9
|
-
request as undiciRequest,
|
|
10
|
-
} from "undici";
|
|
6
|
+
import { Agent, EnvHttpProxyAgent, request as undiciRequest } from "undici";
|
|
7
|
+
|
|
8
|
+
import { registerRunReset } from "./runState.js";
|
|
11
9
|
|
|
12
10
|
// Environment variables here are read with raw `process.env` rather than
|
|
13
11
|
// `readEnvironmentVariable`: that helper lives in activity.js, which imports
|
|
@@ -41,9 +39,8 @@ import {
|
|
|
41
39
|
* `Content-Encoding` header, matching `got`'s transparent decoding.
|
|
42
40
|
* - Proxy support via the standard `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY`
|
|
43
41
|
* environment variables (through undici's `EnvHttpProxyAgent`).
|
|
44
|
-
* - An in-memory GET response cache
|
|
45
|
-
*
|
|
46
|
-
* cache cdxgen previously relied on.
|
|
42
|
+
* - An in-memory GET response cache that replaces the got + Keyv cache cdxgen
|
|
43
|
+
* previously relied on. A request opts out with `cache: false`.
|
|
47
44
|
* - Response objects exposing `statusCode`, `headers`, `body`, `rawBody`,
|
|
48
45
|
* `url` and `request.options`.
|
|
49
46
|
* - A lazily-resolved `.json()` method on the returned promise, mirroring
|
|
@@ -137,7 +134,12 @@ function mergeHeaders(base = {}, override = {}) {
|
|
|
137
134
|
* `beforeError` arrays.
|
|
138
135
|
*/
|
|
139
136
|
function mergeHooks(base = {}, override = {}) {
|
|
140
|
-
const hookNames = [
|
|
137
|
+
const hookNames = [
|
|
138
|
+
"beforeRequest",
|
|
139
|
+
"beforeNetwork",
|
|
140
|
+
"afterResponse",
|
|
141
|
+
"beforeError",
|
|
142
|
+
];
|
|
141
143
|
const merged = {};
|
|
142
144
|
for (const name of hookNames) {
|
|
143
145
|
merged[name] = [...(base?.[name] || []), ...(override?.[name] || [])];
|
|
@@ -253,6 +255,11 @@ function decompressBody(rawBody, contentEncoding) {
|
|
|
253
255
|
* Read and decode an undici response body according to the requested
|
|
254
256
|
* `responseType`.
|
|
255
257
|
*
|
|
258
|
+
* An error response often carries a body in another format than the one asked
|
|
259
|
+
* for, such as the HTML page a CDN sends with a 429. Its JSON body is decoded
|
|
260
|
+
* when it parses and kept as text otherwise, so the caller sees the status
|
|
261
|
+
* rather than a parse error.
|
|
262
|
+
*
|
|
256
263
|
* @param {Object} undiciResponse Response returned by `undici.request`.
|
|
257
264
|
* @param {string} [responseType] One of `"json"`, `"buffer"` or `"text"`.
|
|
258
265
|
* @returns {Promise<{body: any, rawBody: Buffer}>} Decoded body and raw bytes.
|
|
@@ -272,7 +279,17 @@ async function readBody(undiciResponse, responseType) {
|
|
|
272
279
|
body = rawBody;
|
|
273
280
|
} else if (responseType === "json") {
|
|
274
281
|
const text = rawBody.toString("utf-8");
|
|
275
|
-
|
|
282
|
+
if (!text.length) {
|
|
283
|
+
body = undefined;
|
|
284
|
+
} else if (undiciResponse.statusCode >= 400) {
|
|
285
|
+
try {
|
|
286
|
+
body = JSON.parse(text);
|
|
287
|
+
} catch (_err) {
|
|
288
|
+
body = text;
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
body = JSON.parse(text);
|
|
292
|
+
}
|
|
276
293
|
} else {
|
|
277
294
|
body = rawBody.toString("utf-8");
|
|
278
295
|
}
|
|
@@ -304,20 +321,100 @@ function resolveRequestBody(options) {
|
|
|
304
321
|
|
|
305
322
|
// In-memory cache of successful GET responses, mirroring the HTTP cache cdxgen
|
|
306
323
|
// historically kept via got + Keyv. Repeated metadata/license lookups during a
|
|
307
|
-
// scan are served from here instead of hitting the network again.
|
|
308
|
-
// CDXGEN_NO_CACHE
|
|
324
|
+
// scan are served from here instead of hitting the network again. It lasts for
|
|
325
|
+
// one run; `CDXGEN_NO_CACHE` and `--no-cache` turn off only the on-disk caches,
|
|
326
|
+
// because nothing gains from asking a registry the same question twice in one
|
|
327
|
+
// scan.
|
|
328
|
+
//
|
|
329
|
+
// Entries expire after an hour and the cache is bounded in size, least
|
|
330
|
+
// recently used first, so a long-running server neither serves stale registry
|
|
331
|
+
// documents nor grows without limit.
|
|
309
332
|
const responseCache = new Map();
|
|
333
|
+
const RESPONSE_CACHE_TTL_MS = 60 * 60 * 1000;
|
|
334
|
+
const RESPONSE_CACHE_MAX_BYTES = 512 * 1024 * 1024;
|
|
335
|
+
let responseCacheLimits = {
|
|
336
|
+
ttlMs: RESPONSE_CACHE_TTL_MS,
|
|
337
|
+
maxBytes: RESPONSE_CACHE_MAX_BYTES,
|
|
338
|
+
};
|
|
339
|
+
let responseCacheBytes = 0;
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Approximate the memory a cached response holds. The raw bytes are kept,
|
|
343
|
+
* plus the decoded body for text and JSON responses.
|
|
344
|
+
*
|
|
345
|
+
* @param {Object} response Response object.
|
|
346
|
+
* @returns {number} Size in bytes.
|
|
347
|
+
*/
|
|
348
|
+
function cachedResponseBytes(response) {
|
|
349
|
+
const raw = response?.rawBody?.length || 0;
|
|
350
|
+
return response?.body === response?.rawBody ? raw : raw * 3;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Read a cached response, dropping it when it has expired and marking it as
|
|
355
|
+
* recently used otherwise.
|
|
356
|
+
*
|
|
357
|
+
* @param {string} key Cache key.
|
|
358
|
+
* @returns {Object|undefined} The cached response.
|
|
359
|
+
*/
|
|
360
|
+
function cacheGet(key) {
|
|
361
|
+
const entry = responseCache.get(key);
|
|
362
|
+
if (!entry) {
|
|
363
|
+
return undefined;
|
|
364
|
+
}
|
|
365
|
+
responseCache.delete(key);
|
|
366
|
+
if (entry.expiresAt <= Date.now()) {
|
|
367
|
+
responseCacheBytes -= entry.bytes;
|
|
368
|
+
return undefined;
|
|
369
|
+
}
|
|
370
|
+
responseCache.set(key, entry);
|
|
371
|
+
return entry.response;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Store a response, evicting the least recently used entries beyond the size
|
|
376
|
+
* bound.
|
|
377
|
+
*
|
|
378
|
+
* @param {string} key Cache key.
|
|
379
|
+
* @param {Object} response Response object.
|
|
380
|
+
* @returns {void}
|
|
381
|
+
*/
|
|
382
|
+
function cacheSet(key, response) {
|
|
383
|
+
const bytes = cachedResponseBytes(response);
|
|
384
|
+
if (bytes > responseCacheLimits.maxBytes) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const previous = responseCache.get(key);
|
|
388
|
+
if (previous) {
|
|
389
|
+
responseCache.delete(key);
|
|
390
|
+
responseCacheBytes -= previous.bytes;
|
|
391
|
+
}
|
|
392
|
+
responseCache.set(key, {
|
|
393
|
+
response,
|
|
394
|
+
bytes,
|
|
395
|
+
expiresAt: Date.now() + responseCacheLimits.ttlMs,
|
|
396
|
+
});
|
|
397
|
+
responseCacheBytes += bytes;
|
|
398
|
+
for (const [oldestKey, oldest] of responseCache) {
|
|
399
|
+
if (responseCacheBytes <= responseCacheLimits.maxBytes) {
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
responseCache.delete(oldestKey);
|
|
403
|
+
responseCacheBytes -= oldest.bytes;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
310
406
|
|
|
311
407
|
/**
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
* callers can toggle it at runtime.
|
|
408
|
+
* Override the response cache bounds. Returns the previous bounds so tests can
|
|
409
|
+
* restore them. Not part of the public API.
|
|
315
410
|
*
|
|
316
|
-
* @
|
|
411
|
+
* @param {{ttlMs?: number, maxBytes?: number}} limits New bounds.
|
|
412
|
+
* @returns {{ttlMs: number, maxBytes: number}} Previous bounds.
|
|
317
413
|
*/
|
|
318
|
-
export function
|
|
319
|
-
const
|
|
320
|
-
|
|
414
|
+
export function _setHttpCacheLimits(limits) {
|
|
415
|
+
const previous = responseCacheLimits;
|
|
416
|
+
responseCacheLimits = { ...responseCacheLimits, ...limits };
|
|
417
|
+
return previous;
|
|
321
418
|
}
|
|
322
419
|
|
|
323
420
|
/**
|
|
@@ -327,8 +424,11 @@ export function isCacheDisabled() {
|
|
|
327
424
|
*/
|
|
328
425
|
export function clearHttpCache() {
|
|
329
426
|
responseCache.clear();
|
|
427
|
+
responseCacheBytes = 0;
|
|
330
428
|
}
|
|
331
429
|
|
|
430
|
+
registerRunReset("http:responses", clearHttpCache);
|
|
431
|
+
|
|
332
432
|
// ---------------------------------------------------------------------------
|
|
333
433
|
// Test cassette interceptor — the single network seam.
|
|
334
434
|
//
|
|
@@ -397,34 +497,45 @@ export function clearHttpInterceptor() {
|
|
|
397
497
|
}
|
|
398
498
|
|
|
399
499
|
/**
|
|
400
|
-
* Build the cache key for a request.
|
|
401
|
-
*
|
|
500
|
+
* Build the cache key for a request.
|
|
501
|
+
*
|
|
502
|
+
* Besides the method and URL, the key carries the response type, which
|
|
503
|
+
* changes how the same bytes are decoded, and a digest of every request
|
|
504
|
+
* header. A response fetched with one credential is therefore never served to
|
|
505
|
+
* a request made with another credential or with none, and a JSON request
|
|
506
|
+
* never receives a text body. Header values only enter the key as a SHA-256
|
|
507
|
+
* digest, so no credential is held in it.
|
|
402
508
|
*
|
|
403
509
|
* @param {string} method Uppercased HTTP method.
|
|
404
510
|
* @param {URL} url Request URL.
|
|
511
|
+
* @param {Object} [headers] Request headers as sent.
|
|
512
|
+
* @param {string} [responseType] Requested body decoding.
|
|
405
513
|
* @returns {string} Cache key.
|
|
406
514
|
*/
|
|
407
|
-
function cacheKeyFor(method, url) {
|
|
408
|
-
|
|
515
|
+
function cacheKeyFor(method, url, headers = {}, responseType = undefined) {
|
|
516
|
+
const headerEntries = Object.entries(headers || {})
|
|
517
|
+
.filter(([, value]) => value !== undefined && value !== null)
|
|
518
|
+
.map(([name, value]) => [
|
|
519
|
+
name.toLowerCase(),
|
|
520
|
+
Array.isArray(value) ? value.join(", ") : String(value),
|
|
521
|
+
])
|
|
522
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
523
|
+
const headerDigest = headerEntries.length
|
|
524
|
+
? createHash("sha256").update(JSON.stringify(headerEntries)).digest("hex")
|
|
525
|
+
: "";
|
|
526
|
+
return `${method}:${responseType || "text"}:${url.toString()}:${headerDigest}`;
|
|
409
527
|
}
|
|
410
528
|
|
|
411
529
|
// The maximum number of redirects to follow when `followRedirect` is enabled.
|
|
412
530
|
const MAX_REDIRECTIONS = 10;
|
|
413
531
|
|
|
414
|
-
// Bun ships its own bundled implementation of the `undici` module which
|
|
415
|
-
//
|
|
416
|
-
// `interceptors.redirect` interceptor that undici v7 uses for redirect handling
|
|
417
|
-
// (see cdxgen issue #4245). Detect the Bun runtime so we can fall back to plain
|
|
418
|
-
// dispatchers and follow redirects manually instead of crashing at module load.
|
|
532
|
+
// Bun ships its own bundled implementation of the `undici` module, which lacks
|
|
533
|
+
// `EnvHttpProxyAgent` (see cdxgen issue #4245).
|
|
419
534
|
const isBun = typeof globalThis.Bun !== "undefined";
|
|
420
535
|
|
|
421
|
-
// Reusable pooled dispatchers
|
|
422
|
-
//
|
|
423
|
-
//
|
|
424
|
-
// allocating a fresh Agent per request (which would defeat connection pooling).
|
|
425
|
-
// On Bun, `compose`/`interceptors.redirect` are unavailable, so the redirect
|
|
426
|
-
// dispatchers are just the plain agents and redirects are handled in
|
|
427
|
-
// `requestWithRedirects` below.
|
|
536
|
+
// Reusable pooled dispatchers, one per TLS verification setting, so requests
|
|
537
|
+
// share connections instead of allocating a fresh Agent each time. Redirects
|
|
538
|
+
// are followed in `requestWithRedirects` below on every runtime.
|
|
428
539
|
// Whether standard proxy environment variables are set. When they are, requests
|
|
429
540
|
// must be routed through the proxy. Because this client always passes an
|
|
430
541
|
// explicit per-request `dispatcher`, it overrides Node's global (potentially
|
|
@@ -461,51 +572,72 @@ const defaultAgent = createBaseAgent();
|
|
|
461
572
|
const insecureAgent = createBaseAgent({
|
|
462
573
|
connect: { rejectUnauthorized: false },
|
|
463
574
|
});
|
|
464
|
-
const redirectDispatcher = isBun
|
|
465
|
-
? defaultAgent
|
|
466
|
-
: defaultAgent.compose(
|
|
467
|
-
interceptors.redirect({ maxRedirections: MAX_REDIRECTIONS }),
|
|
468
|
-
);
|
|
469
|
-
const insecureRedirectDispatcher = isBun
|
|
470
|
-
? insecureAgent
|
|
471
|
-
: insecureAgent.compose(
|
|
472
|
-
interceptors.redirect({ maxRedirections: MAX_REDIRECTIONS }),
|
|
473
|
-
);
|
|
474
575
|
|
|
475
576
|
/**
|
|
476
|
-
* Pick the undici dispatcher matching the request's
|
|
477
|
-
*
|
|
577
|
+
* Pick the undici dispatcher matching the request's TLS verification
|
|
578
|
+
* preference. Redirects are followed by `requestWithRedirects`, not by the
|
|
579
|
+
* dispatcher.
|
|
478
580
|
*
|
|
479
|
-
* @param {boolean} followRedirect Whether HTTP redirects should be followed.
|
|
480
581
|
* @param {boolean} insecure Whether TLS certificate verification is disabled.
|
|
481
582
|
* @returns {import("undici").Dispatcher} The dispatcher to use for the request.
|
|
482
583
|
*/
|
|
483
|
-
function selectDispatcher(
|
|
484
|
-
|
|
485
|
-
return followRedirect ? insecureRedirectDispatcher : insecureAgent;
|
|
486
|
-
}
|
|
487
|
-
return followRedirect ? redirectDispatcher : defaultAgent;
|
|
584
|
+
function selectDispatcher(insecure) {
|
|
585
|
+
return insecure ? insecureAgent : defaultAgent;
|
|
488
586
|
}
|
|
489
587
|
|
|
490
588
|
// HTTP status codes that represent a redirect with a `location` header.
|
|
491
589
|
const REDIRECT_STATUS_CODES = new Set([301, 302, 303, 307, 308]);
|
|
492
590
|
|
|
591
|
+
// Request headers that may follow a redirect to a different origin. Everything
|
|
592
|
+
// else, such as Authorization, cookies and registry API keys like X-Api-Key or
|
|
593
|
+
// PRIVATE-TOKEN, stays with the origin the caller addressed.
|
|
594
|
+
const CROSS_ORIGIN_REDIRECT_HEADERS = new Set([
|
|
595
|
+
"accept",
|
|
596
|
+
"accept-encoding",
|
|
597
|
+
"accept-language",
|
|
598
|
+
"user-agent",
|
|
599
|
+
]);
|
|
600
|
+
|
|
493
601
|
/**
|
|
494
|
-
*
|
|
602
|
+
* Keep only the request headers that may be sent to a different origin.
|
|
495
603
|
*
|
|
496
|
-
*
|
|
497
|
-
*
|
|
498
|
-
|
|
499
|
-
|
|
604
|
+
* @param {Object} headers Request headers.
|
|
605
|
+
* @returns {Object} Headers safe to send cross-origin.
|
|
606
|
+
*/
|
|
607
|
+
function crossOriginRedirectHeaders(headers = {}) {
|
|
608
|
+
const kept = {};
|
|
609
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
610
|
+
if (CROSS_ORIGIN_REDIRECT_HEADERS.has(name.toLowerCase())) {
|
|
611
|
+
kept[name] = value;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return kept;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Issue an undici request and follow redirects one hop at a time.
|
|
619
|
+
*
|
|
620
|
+
* Redirects are followed here rather than by undici's redirect interceptor so
|
|
621
|
+
* that every hop goes through `beforeRedirect`, which runs the same host
|
|
622
|
+
* allowlist, protocol and dry-run checks as the first request, and so that
|
|
623
|
+
* credentials are not forwarded to another origin. Each intermediate body is
|
|
624
|
+
* drained and relative `location` headers are resolved, up to
|
|
500
625
|
* `MAX_REDIRECTIONS` hops.
|
|
501
626
|
*
|
|
502
627
|
* @param {URL} url Request URL.
|
|
503
628
|
* @param {Object} requestOptions Options passed to `undiciRequest`.
|
|
504
629
|
* @param {boolean} followRedirect Whether redirects should be followed.
|
|
630
|
+
* @param {function(URL): Promise<void>} beforeRedirect Called with the next URL
|
|
631
|
+
* before each hop; throws to stop the request.
|
|
505
632
|
* @returns {Promise<Object>} The final undici response.
|
|
506
633
|
*/
|
|
507
|
-
async function requestWithRedirects(
|
|
508
|
-
|
|
634
|
+
async function requestWithRedirects(
|
|
635
|
+
url,
|
|
636
|
+
requestOptions,
|
|
637
|
+
followRedirect,
|
|
638
|
+
beforeRedirect,
|
|
639
|
+
) {
|
|
640
|
+
if (!followRedirect) {
|
|
509
641
|
return await undiciRequest(url, requestOptions);
|
|
510
642
|
}
|
|
511
643
|
let currentUrl = url;
|
|
@@ -526,7 +658,14 @@ async function requestWithRedirects(url, requestOptions, followRedirect) {
|
|
|
526
658
|
} catch {
|
|
527
659
|
// Ignore drain failures; the socket will be discarded.
|
|
528
660
|
}
|
|
529
|
-
|
|
661
|
+
const nextUrl = new URL(location, currentUrl);
|
|
662
|
+
await beforeRedirect(nextUrl);
|
|
663
|
+
if (nextUrl.origin !== currentUrl.origin) {
|
|
664
|
+
requestOptions.headers = crossOriginRedirectHeaders(
|
|
665
|
+
requestOptions.headers,
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
currentUrl = nextUrl;
|
|
530
669
|
redirections += 1;
|
|
531
670
|
// 303, and 301/302 for anything other than GET/HEAD, must switch to GET
|
|
532
671
|
// without a body, matching how undici's redirect interceptor behaves.
|
|
@@ -587,11 +726,23 @@ async function doRequest(mergedOptions) {
|
|
|
587
726
|
const { body, headers } = resolveRequestBody(options);
|
|
588
727
|
const method = (options.method || "GET").toUpperCase();
|
|
589
728
|
|
|
590
|
-
// Serve cacheable GET requests from the in-memory cache when enabled.
|
|
591
|
-
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
729
|
+
// Serve cacheable GET requests from the in-memory cache when enabled. A
|
|
730
|
+
// request opts out with `cache: false`, as token exchanges do.
|
|
731
|
+
const cacheable = method === "GET" && options.cache !== false;
|
|
732
|
+
const cacheKey = cacheable
|
|
733
|
+
? cacheKeyFor(method, url, headers, options.responseType)
|
|
734
|
+
: undefined;
|
|
735
|
+
if (cacheable) {
|
|
736
|
+
const cached = cacheGet(cacheKey);
|
|
737
|
+
if (cached) {
|
|
738
|
+
return cached;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// Runs only when the request is about to reach the network, so a caller
|
|
743
|
+
// that rate-limits a host does not wait for a response already cached.
|
|
744
|
+
for (const hook of options.hooks?.beforeNetwork || []) {
|
|
745
|
+
await hook(options);
|
|
595
746
|
}
|
|
596
747
|
|
|
597
748
|
const timeoutMs = resolveTimeout(options.timeout);
|
|
@@ -603,9 +754,21 @@ async function doRequest(mergedOptions) {
|
|
|
603
754
|
method,
|
|
604
755
|
headers,
|
|
605
756
|
body: method === "GET" || method === "HEAD" ? undefined : body,
|
|
606
|
-
dispatcher: selectDispatcher(
|
|
757
|
+
dispatcher: selectDispatcher(insecure),
|
|
607
758
|
signal,
|
|
608
759
|
};
|
|
760
|
+
// A redirect target must pass the same beforeRequest checks, such as the
|
|
761
|
+
// host allowlist, as the URL the caller asked for.
|
|
762
|
+
const beforeRedirect = async (nextUrl) => {
|
|
763
|
+
const hopOptions = {
|
|
764
|
+
...options,
|
|
765
|
+
url: nextUrl,
|
|
766
|
+
context: { ...options.context },
|
|
767
|
+
};
|
|
768
|
+
for (const hook of options.hooks?.beforeRequest || []) {
|
|
769
|
+
await hook(hopOptions);
|
|
770
|
+
}
|
|
771
|
+
};
|
|
609
772
|
|
|
610
773
|
let undiciResponse;
|
|
611
774
|
try {
|
|
@@ -613,6 +776,7 @@ async function doRequest(mergedOptions) {
|
|
|
613
776
|
url,
|
|
614
777
|
requestOptions,
|
|
615
778
|
followRedirect,
|
|
779
|
+
beforeRedirect,
|
|
616
780
|
);
|
|
617
781
|
} catch (err) {
|
|
618
782
|
const requestError = new RequestError(err, options);
|
|
@@ -652,7 +816,7 @@ async function doRequest(mergedOptions) {
|
|
|
652
816
|
|
|
653
817
|
// Only successful responses are cached, matching HTTP cache semantics.
|
|
654
818
|
if (cacheable && finalResponse.statusCode < 400) {
|
|
655
|
-
|
|
819
|
+
cacheSet(cacheKey, finalResponse);
|
|
656
820
|
}
|
|
657
821
|
return finalResponse;
|
|
658
822
|
}
|