@cdxgen/cdxgen 13.1.0 → 13.3.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 +32 -15
- package/bin/cdxgen.js +243 -122
- package/bin/evinse.js +7 -0
- package/bin/sign.js +88 -14
- package/bin/tracebom.js +64 -4
- package/bin/validate.js +29 -7
- package/bin/verify.js +79 -39
- package/data/crypto-oid.json +88 -12
- package/data/remediations.json +47 -0
- package/data/rules/ai-bom.yaml +78 -0
- package/data/rules/cargo-rust.yaml +132 -0
- package/data/rules/mcp-servers.yaml +122 -0
- package/lib/audit/targets.js +6 -1
- package/lib/cli/bomAssembly.js +117 -15
- package/lib/cli/cliOptions.js +93 -1
- package/lib/cli/index.js +235 -5
- package/lib/cli/jsBom.js +108 -26
- package/lib/cli/jvmBom.js +68 -12
- package/lib/cli/managedBom.js +264 -41
- package/lib/cli/nativeBom.js +923 -15
- package/lib/core/activity.js +120 -2
- package/lib/core/env.js +44 -7
- package/lib/core/fs.js +174 -6
- package/lib/core/metadataProperties.js +97 -0
- package/lib/core/projectConfig.js +31 -4
- package/lib/core/userAgent.js +151 -0
- package/lib/ecosystems/bunutils.js +269 -101
- package/lib/ecosystems/cargoCache.js +364 -0
- package/lib/ecosystems/caxa.js +447 -7
- package/lib/ecosystems/cmakeResolver.js +66 -0
- package/lib/ecosystems/cppEvidence.js +104 -33
- package/lib/ecosystems/ecosystems.js +260 -99
- package/lib/ecosystems/golem.js +11 -1
- package/lib/ecosystems/gradleutils.js +185 -9
- package/lib/ecosystems/jsEvidence.js +19 -20
- package/lib/ecosystems/kosi.js +1263 -0
- package/lib/ecosystems/npmutils.js +110 -9
- package/lib/ecosystems/parsers-crystal.js +179 -0
- package/lib/ecosystems/parsers-dotnet.js +120 -49
- package/lib/ecosystems/parsers-elm.js +816 -0
- package/lib/ecosystems/parsers-gleam.js +1 -1
- package/lib/ecosystems/parsers-go.js +47 -9
- package/lib/ecosystems/parsers-js.js +171 -50
- package/lib/ecosystems/parsers-julia.js +268 -0
- package/lib/ecosystems/parsers-lua.js +218 -0
- package/lib/ecosystems/parsers-misc.js +919 -161
- package/lib/ecosystems/parsers-nim.js +221 -0
- package/lib/ecosystems/parsers-opam.js +180 -0
- package/lib/ecosystems/parsers-perl.js +277 -0
- package/lib/ecosystems/parsers-python.js +27 -15
- package/lib/ecosystems/parsers-r.js +148 -0
- package/lib/ecosystems/parsers-rebar.js +411 -0
- package/lib/ecosystems/parsers-rust.js +954 -33
- package/lib/ecosystems/parsers-spack.js +204 -0
- package/lib/ecosystems/parsers-stack.js +264 -0
- package/lib/ecosystems/parsers-terraform.js +194 -0
- package/lib/ecosystems/rubyutils.js +1426 -166
- package/lib/ecosystems/rusi.js +158 -7
- package/lib/evinser/evinser.js +547 -129
- package/lib/evinser/swiftsem.js +1914 -302
- package/lib/helpers/agenticToolutils.js +925 -0
- package/lib/helpers/bomSigner.js +652 -221
- package/lib/inventory/agentFormulationParser.js +57 -4
- package/lib/inventory/aiCollector.js +504 -10
- package/lib/inventory/aiInventory.js +12 -0
- package/lib/inventory/analyzer.js +488 -30
- package/lib/inventory/astWorker.js +6 -0
- package/lib/inventory/atomUtils.js +568 -31
- package/lib/inventory/auditCategories.js +8 -0
- package/lib/inventory/cbomutils.js +30 -0
- package/lib/inventory/cdxrs.js +110 -14
- package/lib/inventory/communityAiConfigParser.js +215 -2
- package/lib/inventory/depsUtils.js +183 -11
- package/lib/inventory/display.js +4 -5
- package/lib/inventory/dosai.js +627 -13
- package/lib/inventory/dynamic.js +39 -1
- package/lib/inventory/envcontext.js +9 -6
- package/lib/inventory/evidenceUtils.js +26 -5
- package/lib/inventory/fetchBatch.js +190 -19
- package/lib/inventory/fetchRate.js +74 -11
- package/lib/inventory/forgeEnricher.js +3 -2
- package/lib/inventory/jvmToolEnv.js +6 -5
- package/lib/inventory/mcpConfigParser.js +166 -2
- package/lib/inventory/mcpDiscovery.js +11 -0
- package/lib/inventory/mcpServerManifest.js +389 -0
- package/lib/inventory/mcpbBundle.js +419 -0
- package/lib/inventory/plugins.js +3 -0
- package/lib/inventory/protobom.js +62 -89
- package/lib/inventory/protobomLoader.js +4 -0
- package/lib/inventory/purl.js +152 -3
- package/lib/inventory/rustFormulationParser.js +1 -1
- package/lib/inventory/sbomDocument.js +2 -70
- package/lib/inventory/source.js +47 -23
- package/lib/inventory/stylePackageRefs.js +41 -0
- package/lib/inventory/svelteUtils.js +524 -0
- package/lib/inventory/traceRunner.js +210 -21
- package/lib/managers/binary.js +115 -24
- package/lib/managers/docker.js +440 -20
- package/lib/managers/oci.js +39 -1
- package/lib/parsers/cmakeLists.js +452 -0
- package/lib/parsers/huggingfaceUtils.js +15 -7
- package/lib/parsers/largeJson.js +454 -0
- package/lib/parsers/npmrc.js +19 -1
- package/lib/parsers/onnx.js +294 -0
- package/lib/parsers/safetensors.js +114 -0
- package/lib/stages/postgen/introspection/markers.js +29 -6
- package/lib/stages/postgen/postgen.js +237 -16
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
- package/lib/validator/bomValidator.js +6 -16
- package/lib/validator/index.js +48 -10
- package/lib/validator/reporters/console.js +3 -0
- package/lib/validator/reporters/json.js +1 -0
- package/lib/validator/reporters/sarif.js +7 -0
- package/package.json +27 -27
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +2 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +33 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +2 -2
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +23 -0
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts +33 -0
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +88 -8
- 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 +21 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +55 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/metadataProperties.d.ts +39 -0
- package/types/lib/core/metadataProperties.d.ts.map +1 -0
- package/types/lib/core/projectConfig.d.ts +6 -2
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/userAgent.d.ts +56 -0
- package/types/lib/core/userAgent.d.ts.map +1 -0
- package/types/lib/ecosystems/bunutils.d.ts +12 -5
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
- package/types/lib/ecosystems/cargoCache.d.ts +75 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
- package/types/lib/ecosystems/caxa.d.ts +14 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cmakeResolver.d.ts +23 -0
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts +2 -2
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +15 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +40 -4
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +86 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -0
- package/types/lib/ecosystems/npmutils.d.ts +42 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
- package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-go.d.ts +10 -0
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +10 -4
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
- package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-misc.d.ts +90 -3
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
- package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
- package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
- package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-r.d.ts +35 -0
- package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
- package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
- package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +143 -2
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts +13 -0
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +81 -2
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +280 -15
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/helpers/agenticToolutils.d.ts +87 -0
- package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
- package/types/lib/helpers/bomSigner.d.ts +106 -13
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/aiInventory.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +145 -3
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +4 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/cdxrs.d.ts +8 -3
- package/types/lib/inventory/cdxrs.d.ts.map +1 -1
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts +15 -0
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +87 -1
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +34 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchRate.d.ts +9 -6
- package/types/lib/inventory/fetchRate.d.ts.map +1 -1
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +5 -4
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
- package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
- package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
- package/types/lib/inventory/mcpbBundle.d.ts +19 -0
- package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
- package/types/lib/inventory/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts +18 -13
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
- package/types/lib/inventory/purl.d.ts +71 -2
- package/types/lib/inventory/purl.d.ts.map +1 -1
- package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
- package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
- package/types/lib/inventory/svelteUtils.d.ts +124 -0
- package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
- package/types/lib/inventory/traceRunner.d.ts +47 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +30 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +29 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +14 -0
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/parsers/cmakeLists.d.ts +76 -0
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
- package/types/lib/parsers/largeJson.d.ts +52 -0
- package/types/lib/parsers/largeJson.d.ts.map +1 -0
- package/types/lib/parsers/npmrc.d.ts +5 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- package/types/lib/parsers/onnx.d.ts +28 -0
- package/types/lib/parsers/onnx.d.ts.map +1 -0
- package/types/lib/parsers/safetensors.d.ts +22 -0
- package/types/lib/parsers/safetensors.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts +3 -2
- package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +33 -0
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +10 -1
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
package/lib/core/activity.js
CHANGED
|
@@ -3,7 +3,7 @@ import process from "node:process";
|
|
|
3
3
|
|
|
4
4
|
import { createHttpClient, getDefaultHttpTimeoutMs } from "./httpClient.js";
|
|
5
5
|
import { traceLog } from "./logger.js";
|
|
6
|
-
import {
|
|
6
|
+
import { getUserAgent } from "./userAgent.js";
|
|
7
7
|
|
|
8
8
|
/** True when CDXGEN_DEBUG_MODE or SCAN_DEBUG_MODE requests debug/verbose output. */
|
|
9
9
|
export const DEBUG_MODE =
|
|
@@ -85,6 +85,30 @@ const LOCKFILE_ACTIVITY_HINTS = new Map([
|
|
|
85
85
|
label: "Bundler lockfile",
|
|
86
86
|
},
|
|
87
87
|
],
|
|
88
|
+
[
|
|
89
|
+
"cpanfile.snapshot",
|
|
90
|
+
{
|
|
91
|
+
classification: "lockfile",
|
|
92
|
+
ecosystem: "cpan",
|
|
93
|
+
label: "Carton snapshot",
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
[
|
|
97
|
+
"manifest.toml",
|
|
98
|
+
{
|
|
99
|
+
classification: "lockfile",
|
|
100
|
+
ecosystem: "gleam",
|
|
101
|
+
label: "Gleam or Julia lockfile",
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
[
|
|
105
|
+
"nimble.lock",
|
|
106
|
+
{
|
|
107
|
+
classification: "lockfile",
|
|
108
|
+
ecosystem: "nim",
|
|
109
|
+
label: "Nimble lockfile",
|
|
110
|
+
},
|
|
111
|
+
],
|
|
88
112
|
[
|
|
89
113
|
"package-lock.json",
|
|
90
114
|
{ classification: "lockfile", ecosystem: "npm", label: "npm lockfile" },
|
|
@@ -125,6 +149,62 @@ const LOCKFILE_ACTIVITY_HINTS = new Map([
|
|
|
125
149
|
label: "PEP 751 lockfile",
|
|
126
150
|
},
|
|
127
151
|
],
|
|
152
|
+
[
|
|
153
|
+
"renv.lock",
|
|
154
|
+
{
|
|
155
|
+
classification: "lockfile",
|
|
156
|
+
ecosystem: "cran",
|
|
157
|
+
label: "renv lockfile",
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
[
|
|
161
|
+
"rebar.lock",
|
|
162
|
+
{
|
|
163
|
+
classification: "lockfile",
|
|
164
|
+
ecosystem: "hex",
|
|
165
|
+
label: "rebar3 lockfile",
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
[
|
|
169
|
+
"shard.lock",
|
|
170
|
+
{
|
|
171
|
+
classification: "lockfile",
|
|
172
|
+
ecosystem: "crystal",
|
|
173
|
+
label: "Crystal shards lockfile",
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
[
|
|
177
|
+
"spack.lock",
|
|
178
|
+
{
|
|
179
|
+
classification: "lockfile",
|
|
180
|
+
ecosystem: "spack",
|
|
181
|
+
label: "Spack lockfile",
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
[
|
|
185
|
+
"stack.lock",
|
|
186
|
+
{
|
|
187
|
+
classification: "lockfile",
|
|
188
|
+
ecosystem: "hackage",
|
|
189
|
+
label: "Stack lockfile",
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
[
|
|
193
|
+
"stack.yaml.lock",
|
|
194
|
+
{
|
|
195
|
+
classification: "lockfile",
|
|
196
|
+
ecosystem: "hackage",
|
|
197
|
+
label: "Stack lockfile",
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
[
|
|
201
|
+
".terraform.lock.hcl",
|
|
202
|
+
{
|
|
203
|
+
classification: "lockfile",
|
|
204
|
+
ecosystem: "terraform",
|
|
205
|
+
label: "Terraform provider lockfile",
|
|
206
|
+
},
|
|
207
|
+
],
|
|
128
208
|
[
|
|
129
209
|
"uv.lock",
|
|
130
210
|
{ classification: "lockfile", ecosystem: "python", label: "uv lockfile" },
|
|
@@ -167,6 +247,18 @@ const MANIFEST_ACTIVITY_HINTS = new Map([
|
|
|
167
247
|
"pom.xml",
|
|
168
248
|
{ classification: "manifest", ecosystem: "maven", label: "Maven manifest" },
|
|
169
249
|
],
|
|
250
|
+
[
|
|
251
|
+
"cpanfile",
|
|
252
|
+
{ classification: "manifest", ecosystem: "cpan", label: "CPAN manifest" },
|
|
253
|
+
],
|
|
254
|
+
[
|
|
255
|
+
"project.toml",
|
|
256
|
+
{
|
|
257
|
+
classification: "manifest",
|
|
258
|
+
ecosystem: "julia",
|
|
259
|
+
label: "Julia project manifest",
|
|
260
|
+
},
|
|
261
|
+
],
|
|
170
262
|
[
|
|
171
263
|
"pyproject.toml",
|
|
172
264
|
{
|
|
@@ -942,10 +1034,35 @@ function inferNetworkIntent(requestUrl) {
|
|
|
942
1034
|
return "metadata-fetch";
|
|
943
1035
|
}
|
|
944
1036
|
|
|
1037
|
+
/**
|
|
1038
|
+
* Set the request's user-agent to the value resolved for its destination host,
|
|
1039
|
+
* so that a `CDXGEN_USER_AGENT_<REGISTRY>` override reaches the registry it
|
|
1040
|
+
* names. A user-agent a caller set explicitly is left alone; only the client's
|
|
1041
|
+
* own default, which carries no host context, is replaced.
|
|
1042
|
+
*
|
|
1043
|
+
* @param {Object} options Request options being prepared by the HTTP client.
|
|
1044
|
+
* @returns {void}
|
|
1045
|
+
*/
|
|
1046
|
+
function applyUserAgent(options) {
|
|
1047
|
+
const headers = options.headers || (options.headers = {});
|
|
1048
|
+
const existingKey = Object.keys(headers).find(
|
|
1049
|
+
(name) => name.toLowerCase() === "user-agent",
|
|
1050
|
+
);
|
|
1051
|
+
if (existingKey && headers[existingKey] !== getUserAgent()) {
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
if (existingKey) {
|
|
1055
|
+
delete headers[existingKey];
|
|
1056
|
+
}
|
|
1057
|
+
headers["user-agent"] = getUserAgent(options.url?.hostname);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
945
1060
|
/** The shared got-compatible HTTP client for all outbound requests, enforcing dry-run, host-allow-list, and secure-mode policies via beforeRequest/afterResponse/beforeError hooks. */
|
|
946
1061
|
export const cdxgenAgent = createHttpClient({
|
|
947
1062
|
headers: {
|
|
948
|
-
|
|
1063
|
+
// Per-host overrides are applied in beforeRequest; this is the fallback for
|
|
1064
|
+
// any code path that reads the client's configured headers directly.
|
|
1065
|
+
"user-agent": getUserAgent(),
|
|
949
1066
|
},
|
|
950
1067
|
// Bound every request so an unresponsive host cannot hang a scan. Overridable
|
|
951
1068
|
// via CDXGEN_HTTP_TIMEOUT_MS; individual calls may pass a smaller `timeout`.
|
|
@@ -959,6 +1076,7 @@ export const cdxgenAgent = createHttpClient({
|
|
|
959
1076
|
(options) => {
|
|
960
1077
|
const networkIntent =
|
|
961
1078
|
options.context?.activityIntent || inferNetworkIntent(options.url);
|
|
1079
|
+
applyUserAgent(options);
|
|
962
1080
|
const allowedHostsEnv = readEnvironmentVariable("CDXGEN_ALLOWED_HOSTS");
|
|
963
1081
|
const hostAllowed = isAllowedHttpHost(
|
|
964
1082
|
options.url.hostname,
|
package/lib/core/env.js
CHANGED
|
@@ -256,6 +256,19 @@ if (readEnvironmentVariable("CARGO_CMD")) {
|
|
|
256
256
|
CARGO_CMD = readEnvironmentVariable("CARGO_CMD");
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Target triple the cargo resolve graph is filtered to, so that a
|
|
261
|
+
* cross-compiled project can be described for the triple it ships on rather
|
|
262
|
+
* than for the union of every triple its lock file covers.
|
|
263
|
+
*
|
|
264
|
+
* `CDXGEN_CARGO_TARGET` is cdxgen's own name for it; `CARGO_BUILD_TARGET` is
|
|
265
|
+
* cargo's standard variable, which a cross-compiling build already sets.
|
|
266
|
+
*/
|
|
267
|
+
export const CARGO_TARGET =
|
|
268
|
+
readEnvironmentVariable("CDXGEN_CARGO_TARGET") ||
|
|
269
|
+
readEnvironmentVariable("CARGO_BUILD_TARGET") ||
|
|
270
|
+
undefined;
|
|
271
|
+
|
|
259
272
|
/** Resolved Clojure CLI executable command (CLJ_CMD env var, or "clj"). */
|
|
260
273
|
export let CLJ_CMD = "clj";
|
|
261
274
|
if (readEnvironmentVariable("CLJ_CMD")) {
|
|
@@ -307,6 +320,7 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
307
320
|
"java24",
|
|
308
321
|
"java25",
|
|
309
322
|
"java26",
|
|
323
|
+
"java27",
|
|
310
324
|
"groovy",
|
|
311
325
|
"kotlin",
|
|
312
326
|
"kt",
|
|
@@ -339,6 +353,9 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
339
353
|
"nodejs20",
|
|
340
354
|
"nodejs22",
|
|
341
355
|
"nodejs23",
|
|
356
|
+
"nodejs24",
|
|
357
|
+
"nodejs25",
|
|
358
|
+
"nodejs26",
|
|
342
359
|
"node",
|
|
343
360
|
"node8",
|
|
344
361
|
"node10",
|
|
@@ -349,11 +366,16 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
349
366
|
"node20",
|
|
350
367
|
"node22",
|
|
351
368
|
"node23",
|
|
369
|
+
"node24",
|
|
370
|
+
"node25",
|
|
371
|
+
"node26",
|
|
352
372
|
"js",
|
|
353
373
|
"javascript",
|
|
354
374
|
"typescript",
|
|
355
375
|
"ts",
|
|
356
376
|
"tsx",
|
|
377
|
+
"svelte",
|
|
378
|
+
"sveltekit",
|
|
357
379
|
"yarn",
|
|
358
380
|
"rush",
|
|
359
381
|
"bun",
|
|
@@ -361,6 +383,7 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
361
383
|
],
|
|
362
384
|
mcp: ["mcp"],
|
|
363
385
|
"ai-skill": ["ai-skill", "skill", "skills"],
|
|
386
|
+
agentic: ["agentic", "agentic-cli", "ai-tools", "ai-cli"],
|
|
364
387
|
py: [
|
|
365
388
|
"py",
|
|
366
389
|
"python",
|
|
@@ -372,6 +395,8 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
372
395
|
"python311",
|
|
373
396
|
"python312",
|
|
374
397
|
"python313",
|
|
398
|
+
"python314",
|
|
399
|
+
"python315",
|
|
375
400
|
"pixi",
|
|
376
401
|
"pip",
|
|
377
402
|
"poetry",
|
|
@@ -398,6 +423,8 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
398
423
|
"dotnet7",
|
|
399
424
|
"dotnet8",
|
|
400
425
|
"dotnet9",
|
|
426
|
+
"dotnet10",
|
|
427
|
+
"dotnet11",
|
|
401
428
|
"dotnet-framework",
|
|
402
429
|
"dotnet-framework47",
|
|
403
430
|
"dotnet-framework48",
|
|
@@ -417,8 +444,19 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
417
444
|
"hmiproj",
|
|
418
445
|
],
|
|
419
446
|
dart: ["dart", "flutter", "pub"],
|
|
420
|
-
haskell: ["haskell", "hackage", "cabal"],
|
|
447
|
+
haskell: ["haskell", "hackage", "cabal", "stack", "stackage"],
|
|
421
448
|
elixir: ["elixir", "hex", "mix"],
|
|
449
|
+
erlang: ["erlang", "rebar", "rebar3"],
|
|
450
|
+
julia: ["julia"],
|
|
451
|
+
r: ["r", "cran", "renv"],
|
|
452
|
+
ocaml: ["ocaml", "opam", "dune"],
|
|
453
|
+
elm: ["elm"],
|
|
454
|
+
crystal: ["crystal", "shards"],
|
|
455
|
+
nim: ["nim", "nimble"],
|
|
456
|
+
lua: ["lua", "luarocks"],
|
|
457
|
+
perl: ["perl", "cpan", "carton", "cpanfile"],
|
|
458
|
+
spack: ["spack"],
|
|
459
|
+
terraform: ["terraform", "opentofu", "tf"],
|
|
422
460
|
c: ["c", "cpp", "c++", "conan", "collider"],
|
|
423
461
|
clojure: ["clojure", "edn", "clj", "leiningen"],
|
|
424
462
|
github: ["github", "actions"],
|
|
@@ -453,7 +491,7 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
453
491
|
"visionos",
|
|
454
492
|
],
|
|
455
493
|
binary: ["binary", "blint"],
|
|
456
|
-
oci: ["docker", "oci", "container", "podman", "rootfs", "oci-dir"],
|
|
494
|
+
oci: ["docker", "image", "oci", "container", "podman", "rootfs", "oci-dir"],
|
|
457
495
|
cocoa: ["cocoa", "cocoapods", "objective-c", "swift", "ios"],
|
|
458
496
|
scala: ["scala", "scala3", "sbt", "mill"],
|
|
459
497
|
nix: ["nix", "nixos", "flake"],
|
|
@@ -605,11 +643,10 @@ export function hasAnyProjectType(projectTypes, options, defaultStatus = true) {
|
|
|
605
643
|
const allProjectTypes = [...projectTypes];
|
|
606
644
|
// Convert the project types into base types
|
|
607
645
|
const baseProjectTypes = [];
|
|
608
|
-
// Support for arbitrary versioned ruby type
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
) {
|
|
646
|
+
// Support for arbitrary versioned ruby type: a requested ruby3.4.1 is a
|
|
647
|
+
// ruby project. The test is on the requested types; testing the queried
|
|
648
|
+
// ones made every scan a ruby scan whenever "ruby" was asked about.
|
|
649
|
+
if (options.projectType?.some((p) => p.startsWith("ruby"))) {
|
|
613
650
|
baseProjectTypes.push("ruby");
|
|
614
651
|
}
|
|
615
652
|
// Versioned JVM build tool types such as maven3.9.9 or gradle8.14 are java
|
package/lib/core/fs.js
CHANGED
|
@@ -3,6 +3,7 @@ import { spawnSync } from "node:child_process";
|
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import {
|
|
5
5
|
accessSync,
|
|
6
|
+
closeSync,
|
|
6
7
|
copyFileSync,
|
|
7
8
|
createReadStream,
|
|
8
9
|
existsSync,
|
|
@@ -10,10 +11,14 @@ import {
|
|
|
10
11
|
lstatSync,
|
|
11
12
|
mkdirSync,
|
|
12
13
|
mkdtempSync,
|
|
14
|
+
openSync,
|
|
13
15
|
readFileSync,
|
|
16
|
+
renameSync,
|
|
14
17
|
rmSync,
|
|
18
|
+
statSync,
|
|
15
19
|
unlinkSync,
|
|
16
20
|
writeFileSync,
|
|
21
|
+
writeSync,
|
|
17
22
|
} from "node:fs";
|
|
18
23
|
import { tmpdir } from "node:os";
|
|
19
24
|
import path, { basename, dirname, join, resolve } from "node:path";
|
|
@@ -144,6 +149,53 @@ export function safeIsExecutableSync(filePath) {
|
|
|
144
149
|
* @returns {void}
|
|
145
150
|
*/
|
|
146
151
|
export function safeWriteSync(filePath, data, options) {
|
|
152
|
+
if (isWriteBlocked(filePath)) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
writeFileSync(filePath, data, options);
|
|
156
|
+
recordFilesystemActivity("write", filePath, "completed");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Permission- and dry-run-aware write of a sequence of chunks, for content too
|
|
161
|
+
* large to join into one string first. The chunks go to a temporary file next
|
|
162
|
+
* to the target, which is renamed into place once complete, so a chunk may be
|
|
163
|
+
* read from the file being replaced and a failed write leaves the target as it
|
|
164
|
+
* was.
|
|
165
|
+
*
|
|
166
|
+
* @param {string} filePath File path to write.
|
|
167
|
+
* @param {Iterable<string|Buffer>} chunks Chunks written in order.
|
|
168
|
+
* @returns {void}
|
|
169
|
+
*/
|
|
170
|
+
export function safeWriteChunksSync(filePath, chunks) {
|
|
171
|
+
if (isWriteBlocked(filePath)) {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
const tempPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
175
|
+
const fd = openSync(tempPath, "w");
|
|
176
|
+
try {
|
|
177
|
+
for (const chunk of chunks) {
|
|
178
|
+
const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
179
|
+
let written = 0;
|
|
180
|
+
while (written < bytes.length) {
|
|
181
|
+
written += writeSync(fd, bytes, written);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
closeSync(fd);
|
|
185
|
+
renameSync(tempPath, filePath);
|
|
186
|
+
} catch (err) {
|
|
187
|
+
try {
|
|
188
|
+
closeSync(fd);
|
|
189
|
+
} catch {
|
|
190
|
+
// Already closed before the rename failed.
|
|
191
|
+
}
|
|
192
|
+
rmSync(tempPath, { force: true });
|
|
193
|
+
throw err;
|
|
194
|
+
}
|
|
195
|
+
recordFilesystemActivity("write", filePath, "completed");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function isWriteBlocked(filePath) {
|
|
147
199
|
if (isDryRun) {
|
|
148
200
|
recordFilesystemActivity(
|
|
149
201
|
"write",
|
|
@@ -151,7 +203,7 @@ export function safeWriteSync(filePath, data, options) {
|
|
|
151
203
|
"blocked",
|
|
152
204
|
"Dry run mode blocks filesystem writes.",
|
|
153
205
|
);
|
|
154
|
-
return
|
|
206
|
+
return true;
|
|
155
207
|
}
|
|
156
208
|
if (!hasWritePermission(filePath)) {
|
|
157
209
|
if (DEBUG_MODE) {
|
|
@@ -163,10 +215,9 @@ export function safeWriteSync(filePath, data, options) {
|
|
|
163
215
|
"blocked",
|
|
164
216
|
"cdxgen lacks write permission for this path.",
|
|
165
217
|
);
|
|
166
|
-
return
|
|
218
|
+
return true;
|
|
167
219
|
}
|
|
168
|
-
|
|
169
|
-
recordFilesystemActivity("write", filePath, "completed");
|
|
220
|
+
return false;
|
|
170
221
|
}
|
|
171
222
|
|
|
172
223
|
/**
|
|
@@ -483,6 +534,116 @@ const VERSION_PROBE_ARGS = new Set(["--version", "-version", "version"]);
|
|
|
483
534
|
const POSIX_SHELL_METACHARACTERS = /[;&|<>$`\\\n\r]/;
|
|
484
535
|
const WINDOWS_SHELL_METACHARACTERS = /[&|<>^%\n\r]/;
|
|
485
536
|
|
|
537
|
+
/**
|
|
538
|
+
* Quote one argument of a shell command line that is built as a single
|
|
539
|
+
* string. The shell flag exists so cmd.exe can resolve bare command names,
|
|
540
|
+
* `.cmd` shims and scripts, and every argument is data (paths, flags) that
|
|
541
|
+
* must arrive as one token whatever it contains: joined unquoted, an
|
|
542
|
+
* argument with spaces splits into several and hands the tool the wrong
|
|
543
|
+
* tokens.
|
|
544
|
+
*
|
|
545
|
+
* Windows follows the quoting rules cmd.exe and the MSVC runtimes share:
|
|
546
|
+
* wrap in double quotes, doubling the backslashes that precede a quote and
|
|
547
|
+
* escaping the quote itself. Inside double quotes cmd.exe already treats
|
|
548
|
+
* `&`, `|`, `<`, `>`, `^` and parentheses as literals, and the
|
|
549
|
+
* shell-metacharacter block above refuses `%` and the rest before this
|
|
550
|
+
* runs. A POSIX shell gets single quotes, which no expansion can leave.
|
|
551
|
+
*
|
|
552
|
+
* The same quoting is applied to the command itself when it names an
|
|
553
|
+
* existing file under a path with spaces; a command the shell must parse
|
|
554
|
+
* (a bare name, `sh -c`) never names a file as a whole and stays verbatim.
|
|
555
|
+
*
|
|
556
|
+
* @param {string} arg Argument to quote
|
|
557
|
+
* @param {boolean} [forWindows] Quote for cmd.exe rather than a POSIX shell; defaults to the current platform
|
|
558
|
+
* @returns {string} The argument, quoted when the shell needs it
|
|
559
|
+
*/
|
|
560
|
+
export function shellQuoteArgument(arg, forWindows = isWin) {
|
|
561
|
+
const value = `${arg}`;
|
|
562
|
+
if (forWindows) {
|
|
563
|
+
if (value === "") {
|
|
564
|
+
return '""';
|
|
565
|
+
}
|
|
566
|
+
if (!/[\s"]/.test(value)) {
|
|
567
|
+
return value;
|
|
568
|
+
}
|
|
569
|
+
// Every backslash run that precedes a quote doubles, each quote escapes,
|
|
570
|
+
// and the trailing run doubles for the closing quote this returns. A
|
|
571
|
+
// character scan keeps it linear; the equivalent `(\\*)"` pattern is
|
|
572
|
+
// polynomial on runs of backslashes that no quote ends.
|
|
573
|
+
let quoted = "";
|
|
574
|
+
let backslashes = 0;
|
|
575
|
+
for (const ch of value) {
|
|
576
|
+
if (ch === "\\") {
|
|
577
|
+
backslashes++;
|
|
578
|
+
quoted += ch;
|
|
579
|
+
} else if (ch === '"') {
|
|
580
|
+
quoted += "\\".repeat(backslashes) + '\\"';
|
|
581
|
+
backslashes = 0;
|
|
582
|
+
} else {
|
|
583
|
+
quoted += ch;
|
|
584
|
+
backslashes = 0;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
quoted += "\\".repeat(backslashes);
|
|
588
|
+
return `"${quoted}"`;
|
|
589
|
+
}
|
|
590
|
+
if (value === "") {
|
|
591
|
+
return "''";
|
|
592
|
+
}
|
|
593
|
+
if (!/[^-\w@%+=:,./]/.test(value)) {
|
|
594
|
+
return value;
|
|
595
|
+
}
|
|
596
|
+
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Whether the path names an existing regular file, without throwing on the
|
|
601
|
+
* many values that are not paths at all (a bare command name, a command with
|
|
602
|
+
* arguments, anything the shell is meant to parse).
|
|
603
|
+
*
|
|
604
|
+
* @param {string} file Candidate path
|
|
605
|
+
* @returns {boolean} True when a regular file exists at the path
|
|
606
|
+
*/
|
|
607
|
+
function isFileNoThrow(file) {
|
|
608
|
+
try {
|
|
609
|
+
return statSync(`${file}`).isFile();
|
|
610
|
+
} catch (_err) {
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Build the single command string a shell spawn runs. Every argument is
|
|
617
|
+
* quoted for the platform shell, so an argument containing spaces stays one
|
|
618
|
+
* token and cannot alter the command, and a command that is itself a file
|
|
619
|
+
* under a path with spaces (`C:\Program Files\...\node.exe`) is quoted too;
|
|
620
|
+
* a command the shell must parse (`sh -c`, a bare name) never names an
|
|
621
|
+
* existing file as a whole and is left verbatim.
|
|
622
|
+
*
|
|
623
|
+
* The one exception is a caller that composes shell fragments itself (sbt
|
|
624
|
+
* command lines embed their own quoting for the shell to strip): it passes
|
|
625
|
+
* `rawShellArgs` and keeps the verbatim join, under the shell-metacharacter
|
|
626
|
+
* block in safeSpawnSync like every other argument.
|
|
627
|
+
*
|
|
628
|
+
* @param {string} command The executable
|
|
629
|
+
* @param {string[]|undefined} args Arguments, when the caller passed any
|
|
630
|
+
* @param {boolean} [rawShellArgs] Join the arguments verbatim instead of quoting them
|
|
631
|
+
* @returns {string} The command line the shell will run
|
|
632
|
+
*/
|
|
633
|
+
export function joinShellCommand(command, args, rawShellArgs = false) {
|
|
634
|
+
let commandToken = `${command}`;
|
|
635
|
+
if (/\s/.test(commandToken) && isFileNoThrow(commandToken)) {
|
|
636
|
+
commandToken = shellQuoteArgument(commandToken);
|
|
637
|
+
}
|
|
638
|
+
if (!args?.length) {
|
|
639
|
+
return commandToken;
|
|
640
|
+
}
|
|
641
|
+
return [
|
|
642
|
+
commandToken,
|
|
643
|
+
...(rawShellArgs ? args : args.map((arg) => shellQuoteArgument(arg))),
|
|
644
|
+
].join(" ");
|
|
645
|
+
}
|
|
646
|
+
|
|
486
647
|
function hasShellMetacharacters(value) {
|
|
487
648
|
if (value === undefined || value === null) {
|
|
488
649
|
return false;
|
|
@@ -696,9 +857,12 @@ export function safeSpawnSync(command, args, options) {
|
|
|
696
857
|
}
|
|
697
858
|
}
|
|
698
859
|
}
|
|
860
|
+
// Read before the option is stripped below: a caller composing shell
|
|
861
|
+
// fragments itself opts out of argument quoting with this flag.
|
|
862
|
+
const rawShellArgs = options?.cdxgenRawShellArgs === true;
|
|
699
863
|
if (!options) {
|
|
700
864
|
options = {};
|
|
701
|
-
} else if (options.cdxgenActivity) {
|
|
865
|
+
} else if (options.cdxgenActivity || options.cdxgenRawShellArgs) {
|
|
702
866
|
options = {
|
|
703
867
|
...options,
|
|
704
868
|
};
|
|
@@ -706,6 +870,9 @@ export function safeSpawnSync(command, args, options) {
|
|
|
706
870
|
if (options.cdxgenActivity) {
|
|
707
871
|
delete options.cdxgenActivity;
|
|
708
872
|
}
|
|
873
|
+
if (options.cdxgenRawShellArgs) {
|
|
874
|
+
delete options.cdxgenRawShellArgs;
|
|
875
|
+
}
|
|
709
876
|
if (options.shell === true) {
|
|
710
877
|
const unsafeShellToken = getUnsafeShellToken(command, args);
|
|
711
878
|
if (unsafeShellToken !== undefined) {
|
|
@@ -817,8 +984,9 @@ export function safeSpawnSync(command, args, options) {
|
|
|
817
984
|
commandsExecuted.add(command);
|
|
818
985
|
// Fix for DEP0190 warning
|
|
819
986
|
if (options?.shell === true) {
|
|
987
|
+
const joined = joinShellCommand(command, args, rawShellArgs);
|
|
988
|
+
command = joined;
|
|
820
989
|
if (args?.length) {
|
|
821
|
-
command = `${command} ${args.join(" ")}`;
|
|
822
990
|
args = undefined;
|
|
823
991
|
}
|
|
824
992
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The spellings under which a config file may list custom metadata
|
|
3
|
+
* properties: those of the `--metadata-property` flag, and the plural that
|
|
4
|
+
* the CDXGEN_METADATA_PROPERTIES environment variable maps to.
|
|
5
|
+
*/
|
|
6
|
+
export const METADATA_PROPERTY_CONFIG_KEYS = [
|
|
7
|
+
"metadata-property",
|
|
8
|
+
"metadataProperty",
|
|
9
|
+
"metadata-properties",
|
|
10
|
+
"metadataProperties",
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
// Namespaces whose properties cdxgen itself asserts: `cdx:` is its registered
|
|
14
|
+
// taxonomy, and `internal:` carries its own bookkeeping
|
|
15
|
+
const RESERVED_METADATA_PROPERTY_PREFIXES = ["cdx:", "internal:"];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Report whether a property name belongs to a namespace cdxgen reserves for
|
|
19
|
+
* the facts it establishes itself, such as `cdx:bom:componentSrcFiles`.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} name Property name
|
|
22
|
+
* @returns {boolean} true for a reserved name
|
|
23
|
+
*/
|
|
24
|
+
export function isReservedMetadataPropertyName(name) {
|
|
25
|
+
return RESERVED_METADATA_PROPERTY_PREFIXES.some((prefix) =>
|
|
26
|
+
`${name}`.startsWith(prefix),
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Normalize user-supplied metadata properties into `[{name, value}]`
|
|
32
|
+
* (discussion 4391).
|
|
33
|
+
*
|
|
34
|
+
* Accepted shapes:
|
|
35
|
+
* - a `name=value` string, or a JSON document holding any of the shapes below
|
|
36
|
+
* — an environment variable
|
|
37
|
+
* - an array of `name=value` strings or `{name, value}` objects — the
|
|
38
|
+
* `--metadata-property` flag and a JSON config file
|
|
39
|
+
* - a plain object mapping a name to a value, or to a list of values for a
|
|
40
|
+
* property that repeats — a JSON document or a YAML config file
|
|
41
|
+
*
|
|
42
|
+
* Values must be strings, numbers, or booleans. An entry without a name, a
|
|
43
|
+
* `name=value` string without a separator, and an empty or structured value
|
|
44
|
+
* are dropped rather than guessed at: a property with a mangled name or an
|
|
45
|
+
* `[object Object]` value is worse than none.
|
|
46
|
+
*
|
|
47
|
+
* @param {unknown} input Properties in any accepted shape
|
|
48
|
+
* @returns {{name: string, value: string}[]} Normalized properties
|
|
49
|
+
*/
|
|
50
|
+
export function parseMetadataProperties(input) {
|
|
51
|
+
const properties = [];
|
|
52
|
+
if (typeof input === "string") {
|
|
53
|
+
let parsed;
|
|
54
|
+
try {
|
|
55
|
+
parsed = JSON.parse(input);
|
|
56
|
+
} catch (_err) {
|
|
57
|
+
parsed = undefined;
|
|
58
|
+
}
|
|
59
|
+
input =
|
|
60
|
+
parsed && typeof parsed === "object"
|
|
61
|
+
? parsed
|
|
62
|
+
: [typeof parsed === "string" ? parsed : input];
|
|
63
|
+
}
|
|
64
|
+
const append = (name, value) => {
|
|
65
|
+
if (typeof name !== "string" && typeof name !== "number") {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const propName = `${name}`.trim();
|
|
69
|
+
if (
|
|
70
|
+
!propName.length ||
|
|
71
|
+
!["string", "number", "boolean"].includes(typeof value) ||
|
|
72
|
+
!`${value}`.length
|
|
73
|
+
) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
properties.push({ name: propName, value: `${value}` });
|
|
77
|
+
};
|
|
78
|
+
if (Array.isArray(input)) {
|
|
79
|
+
for (const entry of input) {
|
|
80
|
+
if (typeof entry === "string") {
|
|
81
|
+
const separator = entry.indexOf("=");
|
|
82
|
+
if (separator > 0) {
|
|
83
|
+
append(entry.slice(0, separator), entry.slice(separator + 1));
|
|
84
|
+
}
|
|
85
|
+
} else if (entry && typeof entry === "object") {
|
|
86
|
+
append(entry.name, entry.value);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} else if (input && typeof input === "object") {
|
|
90
|
+
for (const [name, value] of Object.entries(input)) {
|
|
91
|
+
for (const avalue of Array.isArray(value) ? value : [value]) {
|
|
92
|
+
append(name, avalue);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return properties;
|
|
97
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
isReservedMetadataPropertyName,
|
|
5
|
+
METADATA_PROPERTY_CONFIG_KEYS,
|
|
6
|
+
parseMetadataProperties,
|
|
7
|
+
} from "./metadataProperties.js";
|
|
3
8
|
import { toCamel } from "./paths.js";
|
|
4
9
|
|
|
5
10
|
/**
|
|
@@ -87,19 +92,41 @@ function isContainedPath(configDir, value) {
|
|
|
87
92
|
* whenever that tree is. Path options are therefore confined to the config's
|
|
88
93
|
* own directory - a scanned project may choose where in itself the BOM lands,
|
|
89
94
|
* but not write outside it - and options that redirect data off the host or
|
|
90
|
-
* widen what cdxgen executes are announced on the console.
|
|
95
|
+
* widen what cdxgen executes are announced on the console. A project may
|
|
96
|
+
* describe itself with custom metadata properties, but not in the namespaces
|
|
97
|
+
* cdxgen reserves for the facts it establishes, so a scanned tree cannot pass
|
|
98
|
+
* its own claims off as cdxgen's.
|
|
91
99
|
*
|
|
92
100
|
* @param {Object} config parsed config file contents
|
|
93
101
|
* @param {string} configDir directory the config file was read from
|
|
94
|
-
* @returns {{config: Object, rejected: string[], announced: string[]}} sanitized config and what it did
|
|
102
|
+
* @returns {{config: Object, rejected: string[], announced: string[], reservedProperties: string[]}} sanitized config and what it did
|
|
95
103
|
*/
|
|
96
104
|
export function sanitizeProjectConfig(config, configDir) {
|
|
97
105
|
if (!config || typeof config !== "object") {
|
|
98
|
-
return { announced: [], config: {}, rejected: [] };
|
|
106
|
+
return { announced: [], config: {}, rejected: [], reservedProperties: [] };
|
|
99
107
|
}
|
|
100
108
|
const sanitized = { ...config };
|
|
101
109
|
const rejected = [];
|
|
102
110
|
const announced = [];
|
|
111
|
+
const reservedProperties = [];
|
|
112
|
+
for (const key of METADATA_PROPERTY_CONFIG_KEYS) {
|
|
113
|
+
if (sanitized[key] === undefined) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const properties = [];
|
|
117
|
+
for (const aprop of parseMetadataProperties(sanitized[key])) {
|
|
118
|
+
if (isReservedMetadataPropertyName(aprop.name)) {
|
|
119
|
+
reservedProperties.push(aprop.name);
|
|
120
|
+
} else {
|
|
121
|
+
properties.push(aprop);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (properties.length) {
|
|
125
|
+
sanitized[key] = properties;
|
|
126
|
+
} else {
|
|
127
|
+
delete sanitized[key];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
103
130
|
for (const option of PATH_OPTIONS) {
|
|
104
131
|
for (const key of optionSpellings(option)) {
|
|
105
132
|
if (sanitized[key] === undefined) {
|
|
@@ -118,5 +145,5 @@ export function sanitizeProjectConfig(config, configDir) {
|
|
|
118
145
|
}
|
|
119
146
|
}
|
|
120
147
|
}
|
|
121
|
-
return { announced, config: sanitized, rejected };
|
|
148
|
+
return { announced, config: sanitized, rejected, reservedProperties };
|
|
122
149
|
}
|