@cdxgen/cdxgen 13.4.0 → 13.6.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 +9 -9
- package/bin/cdxgen-secure.js +15 -0
- package/bin/cdxgen.js +29 -5
- package/bin/evinse.js +8 -0
- package/bin/hbom.js +15 -7
- package/bin/repl.js +29 -0
- package/bin/validate.js +6 -0
- package/data/cbomosdb-queries.json +13 -1
- package/data/component-tags.json +8 -0
- package/data/license-aliases.json +24 -2
- package/data/queries-darwin.json +25 -1
- package/data/queries-win.json +19 -1
- package/data/queries.json +41 -0
- package/data/remediations.json +407 -1
- package/data/rules/ai-bom.yaml +1 -1
- package/data/rules/brotli-decoder.yaml +107 -0
- package/data/rules/cbom-security.yaml +235 -4
- 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/vscode-extensions.yaml +75 -0
- package/data/spdx-license-list.json +121 -7
- package/data/spdx-licenses.json +69 -0
- package/data/spdx.schema.json +845 -2
- package/lib/audit/index.js +14 -0
- package/lib/cli/bomAssembly.js +120 -6
- package/lib/cli/cliOptions.js +110 -3
- package/lib/cli/index.js +182 -6
- package/lib/cli/jsBom.js +153 -64
- package/lib/cli/jvmBom.js +363 -66
- package/lib/cli/managedBom.js +853 -87
- package/lib/cli/nativeBom.js +267 -33
- package/lib/core/boundedFile.js +344 -0
- package/lib/core/brotliProbe.js +182 -0
- package/lib/core/env.js +38 -8
- package/lib/core/fs.js +40 -19
- package/lib/core/httpClient.js +172 -46
- package/lib/core/lockPaths.js +38 -0
- package/lib/core/paths.js +24 -1
- package/lib/core/processTree.js +5 -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/toolRegistry.js +170 -0
- package/lib/ecosystems/cppEvidence.js +263 -106
- package/lib/ecosystems/cppIncludeResolver.js +16 -5
- package/lib/ecosystems/ecosystems.js +25 -10
- package/lib/ecosystems/gemHostProbe.js +301 -0
- package/lib/ecosystems/golem.js +550 -6
- package/lib/ecosystems/gradleutils.js +858 -244
- package/lib/ecosystems/parsers-bazel.js +107 -6
- package/lib/ecosystems/parsers-dotnet.js +117 -18
- package/lib/ecosystems/parsers-go.js +1090 -367
- package/lib/ecosystems/parsers-js.js +505 -22
- package/lib/ecosystems/parsers-jvm.js +259 -17
- package/lib/ecosystems/parsers-misc.js +438 -64
- package/lib/ecosystems/parsers-python.js +100 -12
- package/lib/ecosystems/pythonutils.js +128 -2
- package/lib/ecosystems/rubyutils.js +544 -34
- package/lib/ecosystems/rusi.js +10 -0
- package/lib/ecosystems/scalacliutils.js +4 -1
- package/lib/ecosystems/scalasem.js +798 -151
- package/lib/ecosystems/vendoredCode.js +237 -3
- package/lib/ecosystems/venvProbe.js +360 -0
- package/lib/evinser/evinser.js +472 -61
- package/lib/evinser/swiftsem.js +68 -5
- package/lib/helpers/agenticToolutils.js +413 -93
- package/lib/helpers/vsixutils.js +1574 -315
- package/lib/inventory/agentSkillSpec.js +17 -0
- package/lib/inventory/apkInstalledDb.js +243 -0
- package/lib/inventory/atomUtils.js +110 -0
- package/lib/inventory/auditCategories.js +3 -3
- package/lib/inventory/brotliDecoder.js +142 -0
- package/lib/inventory/buildToolRateLimit.js +1 -1
- package/lib/inventory/cacheDir.js +52 -9
- package/lib/inventory/cbomutils.js +56 -7
- package/lib/inventory/certificates.js +2371 -0
- package/lib/inventory/communityAiConfigParser.js +4 -9
- package/lib/inventory/deps.js +41 -4
- package/lib/inventory/display.js +200 -3
- package/lib/inventory/dosai.js +639 -63
- package/lib/inventory/dotnetRuntime.js +217 -0
- package/lib/inventory/dynamic.js +45 -3
- package/lib/inventory/envcontext.js +475 -18
- package/lib/inventory/evidenceUtils.js +447 -23
- package/lib/inventory/fetchBatch.js +1 -1
- package/lib/inventory/formulationParsers.js +32 -3
- package/lib/inventory/goBuildInfo.js +140 -0
- package/lib/inventory/gtfobins.js +13 -0
- package/lib/inventory/hbom.js +67 -7
- package/lib/inventory/hbomAnalysis.js +16 -0
- package/lib/inventory/hostRuntimeProbe.js +686 -0
- package/lib/inventory/hostTopology.js +21 -0
- package/lib/inventory/jvmLocalRepos.js +9 -2
- package/lib/inventory/jvmToolEnv.js +29 -0
- package/lib/inventory/licenseEnhancer.js +12 -0
- package/lib/inventory/mavenCentralMirror.js +534 -0
- package/lib/inventory/nodeBuildInfo.js +218 -0
- package/lib/inventory/nodeHostProbe.js +202 -0
- package/lib/inventory/osPackagePurl.js +244 -0
- package/lib/inventory/osPackageResolver.js +271 -87
- package/lib/inventory/osinfo.js +79 -31
- package/lib/inventory/osqueryTransform.js +114 -5
- package/lib/inventory/phpBuildInfo.js +438 -0
- package/lib/inventory/phpHostProbe.js +185 -0
- package/lib/inventory/rootfsCerts.js +426 -0
- package/lib/inventory/rubyBuildInfo.js +972 -0
- package/lib/inventory/rubyHostProbe.js +218 -0
- package/lib/inventory/scalaCoords.js +4 -3
- package/lib/inventory/scalaDedupe.js +440 -0
- package/lib/inventory/source.js +256 -5
- package/lib/inventory/spdx.js +144 -0
- package/lib/inventory/traceRunner.js +5 -1
- package/lib/inventory/tracedCrypto.js +129 -0
- package/lib/managers/binary.js +866 -186
- package/lib/managers/docker.js +245 -12
- package/lib/parsers/email.js +277 -0
- package/lib/parsers/largeJson.js +62 -13
- package/lib/parsers/vscodeExtensionId.js +183 -0
- package/lib/server/server.js +198 -2
- package/lib/stages/postgen/annotator.js +17 -1
- package/lib/stages/postgen/introspection/reflect.js +4 -1
- package/lib/stages/postgen/postgen.js +5 -5
- package/lib/stages/postgen/ruleEngine.js +71 -6
- package/lib/stages/postgen/specVersionCompat.js +30 -0
- package/lib/stages/pregen/envAudit.js +1 -0
- package/lib/stages/pregen/pregen.js +231 -62
- package/lib/validator/bomValidator.js +32 -6
- package/lib/validator/generated/runtime.mjs +13 -0
- package/lib/validator/generated/validate-1.6.mjs +27 -8
- package/lib/validator/generated/validate-1.7.mjs +32 -13
- package/lib/validator/generated/validate-2.0.mjs +31 -12
- package/lib/validator/index.js +1 -1
- package/package.json +19 -18
- 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 +22 -3
- 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 +1 -17
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +20 -0
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/boundedFile.d.ts +70 -0
- package/types/lib/core/boundedFile.d.ts.map +1 -0
- package/types/lib/core/brotliProbe.d.ts +37 -0
- package/types/lib/core/brotliProbe.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +22 -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 +24 -0
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/lockPaths.d.ts +19 -0
- package/types/lib/core/lockPaths.d.ts.map +1 -0
- package/types/lib/core/processTree.d.ts +2 -0
- package/types/lib/core/processTree.d.ts.map +1 -1
- 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/toolRegistry.d.ts +36 -0
- package/types/lib/core/toolRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +40 -13
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +2 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gemHostProbe.d.ts +28 -0
- package/types/lib/ecosystems/gemHostProbe.d.ts.map +1 -0
- package/types/lib/ecosystems/golem.d.ts +81 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +57 -21
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts +40 -4
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +85 -13
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +17 -20
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts +28 -2
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +37 -58
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +14 -3
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +19 -1
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +144 -12
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +26 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -1
- package/types/lib/ecosystems/venvProbe.d.ts +40 -0
- package/types/lib/ecosystems/venvProbe.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +88 -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/inventory/agentSkillSpec.d.ts +13 -0
- package/types/lib/inventory/agentSkillSpec.d.ts.map +1 -0
- 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 +33 -0
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/brotliDecoder.d.ts +58 -0
- package/types/lib/inventory/brotliDecoder.d.ts.map +1 -0
- package/types/lib/inventory/cacheDir.d.ts +12 -4
- package/types/lib/inventory/cacheDir.d.ts.map +1 -1
- package/types/lib/inventory/cbomutils.d.ts +17 -0
- package/types/lib/inventory/cbomutils.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/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/deps.d.ts +2 -1
- 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 +162 -7
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dotnetRuntime.d.ts +44 -0
- package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -0
- package/types/lib/inventory/dynamic.d.ts +6 -1
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts +92 -8
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts +29 -10
- package/types/lib/inventory/evidenceUtils.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/gtfobins.d.ts.map +1 -1
- 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/hostRuntimeProbe.d.ts +44 -0
- package/types/lib/inventory/hostRuntimeProbe.d.ts.map +1 -0
- package/types/lib/inventory/hostTopology.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +12 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
- package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -1
- package/types/lib/inventory/mavenCentralMirror.d.ts +170 -0
- package/types/lib/inventory/mavenCentralMirror.d.ts.map +1 -0
- package/types/lib/inventory/nodeBuildInfo.d.ts +95 -0
- package/types/lib/inventory/nodeBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/nodeHostProbe.d.ts +15 -0
- package/types/lib/inventory/nodeHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/osPackagePurl.d.ts +99 -0
- package/types/lib/inventory/osPackagePurl.d.ts.map +1 -0
- package/types/lib/inventory/osPackageResolver.d.ts +44 -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/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/rubyBuildInfo.d.ts +271 -0
- package/types/lib/inventory/rubyBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/rubyHostProbe.d.ts +47 -0
- package/types/lib/inventory/rubyHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +38 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/spdx.d.ts +15 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +4 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/inventory/tracedCrypto.d.ts +22 -0
- package/types/lib/inventory/tracedCrypto.d.ts.map +1 -1
- 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 +24 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- 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 +17 -3
- package/types/lib/parsers/largeJson.d.ts.map +1 -1
- 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/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/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +26 -9
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
package/lib/parsers/largeJson.js
CHANGED
|
@@ -54,9 +54,20 @@ function applySpec(value, spec) {
|
|
|
54
54
|
return value;
|
|
55
55
|
}
|
|
56
56
|
if (Array.isArray(value)) {
|
|
57
|
+
let elements = value;
|
|
58
|
+
if (spec.map) {
|
|
59
|
+
const mapped = [];
|
|
60
|
+
for (const element of elements) {
|
|
61
|
+
const transformed = spec.map(element);
|
|
62
|
+
if (transformed !== undefined) {
|
|
63
|
+
mapped.push(transformed);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
elements = mapped;
|
|
67
|
+
}
|
|
57
68
|
return spec.filter
|
|
58
|
-
?
|
|
59
|
-
:
|
|
69
|
+
? elements.filter((element) => spec.filter(element))
|
|
70
|
+
: elements;
|
|
60
71
|
}
|
|
61
72
|
if (!spec.members) {
|
|
62
73
|
return value;
|
|
@@ -256,8 +267,9 @@ class ByteCursor {
|
|
|
256
267
|
|
|
257
268
|
// The members of the object at the cursor, as { key, start, end } byte ranges
|
|
258
269
|
// of their values, in file order. A repeated key keeps its first position and
|
|
259
|
-
// its last range, which is what JSON.parse does.
|
|
260
|
-
|
|
270
|
+
// its last range, which is what JSON.parse does. With `stopAt`, the scan ends
|
|
271
|
+
// at the first member of that name, leaving the rest of the object unread.
|
|
272
|
+
function scanObjectMembers(cursor, stopAt = undefined) {
|
|
261
273
|
if (cursor.nextToken() !== OPEN_BRACE) {
|
|
262
274
|
cursor.fail("Expected an object");
|
|
263
275
|
}
|
|
@@ -280,6 +292,9 @@ function scanObjectMembers(cursor) {
|
|
|
280
292
|
const start = cursor.offset;
|
|
281
293
|
cursor.skipValue();
|
|
282
294
|
members.set(key, { start, end: cursor.offset });
|
|
295
|
+
if (key === stopAt) {
|
|
296
|
+
return members;
|
|
297
|
+
}
|
|
283
298
|
const delimiter = cursor.nextToken();
|
|
284
299
|
if (delimiter === CLOSE_BRACE) {
|
|
285
300
|
return members;
|
|
@@ -290,9 +305,10 @@ function scanObjectMembers(cursor) {
|
|
|
290
305
|
}
|
|
291
306
|
}
|
|
292
307
|
|
|
293
|
-
// Decode the array at the cursor in runs of whole elements,
|
|
294
|
-
// `
|
|
295
|
-
|
|
308
|
+
// Decode the array at the cursor in runs of whole elements, mapping each one
|
|
309
|
+
// through `spec.map` (an undefined result drops it) and keeping those that
|
|
310
|
+
// `spec.filter` accepts.
|
|
311
|
+
function readArrayElements(cursor, spec, batchBytes) {
|
|
296
312
|
if (cursor.nextToken() !== OPEN_BRACKET) {
|
|
297
313
|
cursor.fail("Expected an array");
|
|
298
314
|
}
|
|
@@ -306,8 +322,12 @@ function readArrayElements(cursor, filter, batchBytes) {
|
|
|
306
322
|
for (const element of JSON.parse(
|
|
307
323
|
`[${cursor.endCapture(cursor.pos - 1)}]`,
|
|
308
324
|
)) {
|
|
309
|
-
|
|
310
|
-
|
|
325
|
+
const mapped = spec?.map ? spec.map(element) : element;
|
|
326
|
+
if (mapped === undefined) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (!spec?.filter || spec.filter(mapped)) {
|
|
330
|
+
elements.push(mapped);
|
|
311
331
|
}
|
|
312
332
|
}
|
|
313
333
|
};
|
|
@@ -363,7 +383,7 @@ function decodeRange(fd, start, end, spec, options) {
|
|
|
363
383
|
);
|
|
364
384
|
}
|
|
365
385
|
} else if (first === OPEN_BRACKET) {
|
|
366
|
-
value = readArrayElements(cursor, spec
|
|
386
|
+
value = readArrayElements(cursor, spec, options.batchBytes);
|
|
367
387
|
} else {
|
|
368
388
|
// A string or number this large has no smaller parts; JSON.parse reports
|
|
369
389
|
// it the way it would for the whole file.
|
|
@@ -412,14 +432,43 @@ export function indexJsonObject(filePath, options = {}) {
|
|
|
412
432
|
});
|
|
413
433
|
}
|
|
414
434
|
|
|
435
|
+
/**
|
|
436
|
+
* Decode one member of the top-level object in a JSON file, reading the file
|
|
437
|
+
* only up to the end of that member. A member near the start of a large file
|
|
438
|
+
* costs a few bounded reads, however large the rest of the file is; the rest
|
|
439
|
+
* is not checked for well-formedness.
|
|
440
|
+
*
|
|
441
|
+
* @param {string} filePath JSON file whose top-level value is an object
|
|
442
|
+
* @param {string} name Member to decode
|
|
443
|
+
* @param {Object} [options] Batch and chunk size overrides, for tests
|
|
444
|
+
* @returns {*} The decoded value, or undefined when the object has no such member
|
|
445
|
+
* @throws {SyntaxError} When the object is malformed before the member ends
|
|
446
|
+
*/
|
|
447
|
+
export function readJsonMember(filePath, name, options = {}) {
|
|
448
|
+
const resolved = resolveOptions(options);
|
|
449
|
+
return withFile(filePath, (fd) => {
|
|
450
|
+
const cursor = new ByteCursor(
|
|
451
|
+
fd,
|
|
452
|
+
0,
|
|
453
|
+
statSync(filePath).size,
|
|
454
|
+
resolved.chunkBytes,
|
|
455
|
+
);
|
|
456
|
+
const range = scanObjectMembers(cursor, name).get(name);
|
|
457
|
+
return range
|
|
458
|
+
? decodeRange(fd, range.start, range.end, undefined, resolved)
|
|
459
|
+
: undefined;
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
415
463
|
/**
|
|
416
464
|
* Decode the JSON value stored in a byte range of a file, without ever holding
|
|
417
465
|
* more than a bounded run of it in one string.
|
|
418
466
|
*
|
|
419
467
|
* `spec` narrows what is kept. `{ members: { name: spec } }` keeps only the
|
|
420
|
-
* named members of an object, each narrowed by its own spec. `{
|
|
421
|
-
*
|
|
422
|
-
* keeps the
|
|
468
|
+
* named members of an object, each narrowed by its own spec. `{ map }`
|
|
469
|
+
* transforms each element of an array, an undefined result dropping it, and
|
|
470
|
+
* `{ filter }` keeps the mapped elements that `filter(element)` accepts.
|
|
471
|
+
* Anything else keeps the whole value.
|
|
423
472
|
*
|
|
424
473
|
* @param {string} filePath JSON file
|
|
425
474
|
* @param {{start: number, end: number}} range Byte range of the value, such as one returned by indexJsonObject
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// The one place that reads the identity of an installed VS Code-family
|
|
2
|
+
// extension from the name of its folder.
|
|
3
|
+
//
|
|
4
|
+
// An editor installs an extension into `<publisher>.<name>-<version>`, and a
|
|
5
|
+
// platform-specific build into `<publisher>.<name>-<version>-<platform>`, for
|
|
6
|
+
// example `anthropic.claude-code-2.1.313-darwin-arm64`. The extension scan and
|
|
7
|
+
// the osquery `vscode_extensions` rows both name the extension from this, so an
|
|
8
|
+
// extension found by `cdxgen -t vscode-extension` and by `obom` is the same
|
|
9
|
+
// component with the same purl.
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Target platforms a VS Code-family marketplace publishes an extension for.
|
|
13
|
+
* `universal` and `web` builds are the same artefact on every platform and are
|
|
14
|
+
* not part of the extension's identity.
|
|
15
|
+
*/
|
|
16
|
+
export const VSCODE_TARGET_PLATFORMS = new Set([
|
|
17
|
+
"win32-x64",
|
|
18
|
+
"win32-arm64",
|
|
19
|
+
"win32-ia32",
|
|
20
|
+
"linux-x64",
|
|
21
|
+
"linux-arm64",
|
|
22
|
+
"linux-armhf",
|
|
23
|
+
"alpine-x64",
|
|
24
|
+
"alpine-arm64",
|
|
25
|
+
"darwin-x64",
|
|
26
|
+
"darwin-arm64",
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
// Folder suffixes for builds that run on every platform.
|
|
30
|
+
const NEUTRAL_PLATFORMS = ["universal", "web"];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Normalize a target platform value from a manifest, an `extensions.json`
|
|
34
|
+
* record, or a folder name. Values that do not name a specific platform, such
|
|
35
|
+
* as `universal`, `web`, `undefined`, or an empty string, return an empty
|
|
36
|
+
* string.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} value Raw target platform
|
|
39
|
+
* @returns {string} A value from VSCODE_TARGET_PLATFORMS, or an empty string
|
|
40
|
+
*/
|
|
41
|
+
export function normalizeVscodeTargetPlatform(value) {
|
|
42
|
+
const platform = `${value || ""}`.trim().toLowerCase();
|
|
43
|
+
return VSCODE_TARGET_PLATFORMS.has(platform) ? platform : "";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Split an installed extension folder name into its publisher, name, version
|
|
48
|
+
* and target platform.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} dirName Folder name, such as `ms-python.python-2025.4.0` or
|
|
51
|
+
* `anthropic.claude-code-2.1.313-darwin-arm64`
|
|
52
|
+
* @returns {{publisher: string, name: string, version: string, platform: string}|undefined}
|
|
53
|
+
* The parts, or undefined when the name does not follow the pattern
|
|
54
|
+
*/
|
|
55
|
+
export function parseVscodeExtensionDirName(dirName) {
|
|
56
|
+
let value = `${dirName || ""}`.trim();
|
|
57
|
+
while (value.endsWith("/") || value.endsWith("\\")) {
|
|
58
|
+
value = value.slice(0, -1);
|
|
59
|
+
}
|
|
60
|
+
const dotIdx = value.indexOf(".");
|
|
61
|
+
if (dotIdx < 1) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const publisher = value.substring(0, dotIdx);
|
|
65
|
+
let rest = value.substring(dotIdx + 1);
|
|
66
|
+
let platform = "";
|
|
67
|
+
for (const candidate of [...VSCODE_TARGET_PLATFORMS, ...NEUTRAL_PLATFORMS]) {
|
|
68
|
+
if (rest.toLowerCase().endsWith(`-${candidate}`)) {
|
|
69
|
+
platform = normalizeVscodeTargetPlatform(candidate);
|
|
70
|
+
rest = rest.substring(0, rest.length - candidate.length - 1);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Marketplaces require semver versions, so the version starts at the first
|
|
75
|
+
// hyphen whose remainder is a whole semver: `python-3.11-tools-1.0.0` is the
|
|
76
|
+
// name `python-3.11-tools` at 1.0.0, and `1.0.0-beta-2` stays one version.
|
|
77
|
+
// A folder whose version is not semver falls back to the first hyphen
|
|
78
|
+
// followed by digits and a dot.
|
|
79
|
+
let versionStart = -1;
|
|
80
|
+
let fallbackStart = -1;
|
|
81
|
+
for (let i = 1; i < rest.length - 2; i++) {
|
|
82
|
+
if (rest[i] !== "-" || !isDigit(rest[i + 1])) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (isSemver(rest.substring(i + 1))) {
|
|
86
|
+
versionStart = i;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
let j = i + 1;
|
|
90
|
+
while (j < rest.length && isDigit(rest[j])) {
|
|
91
|
+
j++;
|
|
92
|
+
}
|
|
93
|
+
if (fallbackStart < 0 && rest[j] === ".") {
|
|
94
|
+
fallbackStart = i;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (versionStart < 0) {
|
|
98
|
+
versionStart = fallbackStart;
|
|
99
|
+
}
|
|
100
|
+
if (versionStart < 1) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
const name = rest.substring(0, versionStart);
|
|
104
|
+
const version = rest.substring(versionStart + 1);
|
|
105
|
+
if (!name || !version) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
return { publisher, name, version, platform };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function isDigit(character) {
|
|
112
|
+
return character >= "0" && character <= "9";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isIdentifierList(text, numericNoLeadingZero) {
|
|
116
|
+
if (!text) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
return text.split(".").every((part) => {
|
|
120
|
+
if (!part.length) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
for (const character of part) {
|
|
124
|
+
const ok =
|
|
125
|
+
isDigit(character) ||
|
|
126
|
+
character === "-" ||
|
|
127
|
+
(character >= "a" && character <= "z") ||
|
|
128
|
+
(character >= "A" && character <= "Z");
|
|
129
|
+
if (!ok) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (numericNoLeadingZero && [...part].every(isDigit)) {
|
|
134
|
+
return part === "0" || part[0] !== "0";
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Check a string is a semantic version: three numeric parts, then an optional
|
|
142
|
+
* `-prerelease` and `+build`.
|
|
143
|
+
*
|
|
144
|
+
* @param {string} text Candidate version
|
|
145
|
+
* @returns {boolean} true for a semver
|
|
146
|
+
*/
|
|
147
|
+
export function isSemver(text) {
|
|
148
|
+
const plus = text.indexOf("+");
|
|
149
|
+
const core = plus >= 0 ? text.substring(0, plus) : text;
|
|
150
|
+
if (plus >= 0 && !isIdentifierList(text.substring(plus + 1), false)) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
const dash = core.indexOf("-");
|
|
154
|
+
const numbers = dash >= 0 ? core.substring(0, dash) : core;
|
|
155
|
+
if (dash >= 0 && !isIdentifierList(core.substring(dash + 1), true)) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
const parts = numbers.split(".");
|
|
159
|
+
return (
|
|
160
|
+
parts.length === 3 &&
|
|
161
|
+
parts.every(
|
|
162
|
+
(part) =>
|
|
163
|
+
part.length > 0 &&
|
|
164
|
+
[...part].every(isDigit) &&
|
|
165
|
+
(part === "0" || part[0] !== "0"),
|
|
166
|
+
)
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Read the target platform from the path of an installed extension folder.
|
|
172
|
+
*
|
|
173
|
+
* @param {string} extensionPath Path to the extension folder, with either
|
|
174
|
+
* separator
|
|
175
|
+
* @returns {string} The target platform, or an empty string
|
|
176
|
+
*/
|
|
177
|
+
export function vscodeExtensionPlatformFromPath(extensionPath) {
|
|
178
|
+
const segments = `${extensionPath || ""}`
|
|
179
|
+
.split(/[/\\]/u)
|
|
180
|
+
.filter((segment) => segment.length);
|
|
181
|
+
const dirName = segments[segments.length - 1];
|
|
182
|
+
return parseVscodeExtensionDirName(dirName)?.platform || "";
|
|
183
|
+
}
|
package/lib/server/server.js
CHANGED
|
@@ -14,7 +14,9 @@ import {
|
|
|
14
14
|
isSecureMode,
|
|
15
15
|
readEnvironmentVariable,
|
|
16
16
|
} from "../core/activity.js";
|
|
17
|
+
import { PROJECT_TYPE_ALIASES } from "../core/env.js";
|
|
17
18
|
import { isWin } from "../core/paths.js";
|
|
19
|
+
import { isSecureModeActive } from "../core/projectTooling.js";
|
|
18
20
|
import { resetRunState } from "../core/runState.js";
|
|
19
21
|
import { CDXGEN_VERSION } from "../core/state.js";
|
|
20
22
|
import { installConsoleShim, ui } from "../core/ui.js";
|
|
@@ -27,6 +29,7 @@ import {
|
|
|
27
29
|
getGitAllowProtocol,
|
|
28
30
|
gitClone,
|
|
29
31
|
isAllowedPath,
|
|
32
|
+
isAllowedRealPath,
|
|
30
33
|
isAllowedWinPath,
|
|
31
34
|
maybePurlSource,
|
|
32
35
|
maybeRemotePath,
|
|
@@ -82,6 +85,11 @@ const ALLOWED_PARAMS = [
|
|
|
82
85
|
"tlpClassification",
|
|
83
86
|
];
|
|
84
87
|
|
|
88
|
+
// Options that install packages or run the scanned project's own build tools
|
|
89
|
+
// (for example `cargo check`, which runs build.rs), and so execute code from
|
|
90
|
+
// the scanned repository.
|
|
91
|
+
const CODE_EXECUTION_PARAMS = ["installDeps", "deep"];
|
|
92
|
+
|
|
85
93
|
const app = connect();
|
|
86
94
|
|
|
87
95
|
export { isAllowedHttpHost };
|
|
@@ -156,6 +164,7 @@ export function parseValue(raw) {
|
|
|
156
164
|
* @returns {Object} Populated options object
|
|
157
165
|
*/
|
|
158
166
|
export function parseQueryString(q, body = {}, options = {}) {
|
|
167
|
+
const operatorOptions = { ...options };
|
|
159
168
|
// Priority is query params followed by body
|
|
160
169
|
for (const param of ALLOWED_PARAMS) {
|
|
161
170
|
const raw = q[param] ?? body[param];
|
|
@@ -163,7 +172,7 @@ export function parseQueryString(q, body = {}, options = {}) {
|
|
|
163
172
|
options[param] = parseValue(raw);
|
|
164
173
|
}
|
|
165
174
|
}
|
|
166
|
-
options.projectType = options.type
|
|
175
|
+
options.projectType = toProjectTypeList(options.type);
|
|
167
176
|
delete options.type;
|
|
168
177
|
if (options.lifecycle === "pre-build") {
|
|
169
178
|
options.installDeps = false;
|
|
@@ -171,9 +180,170 @@ export function parseQueryString(q, body = {}, options = {}) {
|
|
|
171
180
|
if (options.profile) {
|
|
172
181
|
applyProfileOptions(options);
|
|
173
182
|
}
|
|
183
|
+
// failOnError ends the process, so one failing request would stop the server
|
|
184
|
+
// for every client.
|
|
185
|
+
options.failOnError = false;
|
|
186
|
+
restrictRequestOptions(options, operatorOptions);
|
|
174
187
|
return options;
|
|
175
188
|
}
|
|
176
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Normalise a `type` value from the request or the start-up options, which may
|
|
192
|
+
* be a comma separated string or an array, into a list of project types.
|
|
193
|
+
*
|
|
194
|
+
* @param {string|string[]|undefined} type Project type value
|
|
195
|
+
* @returns {string[]|undefined} Project types, or undefined when none are given
|
|
196
|
+
*/
|
|
197
|
+
function toProjectTypeList(type) {
|
|
198
|
+
if (type === undefined || type === null || type === "") {
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
const types = (Array.isArray(type) ? type : [type])
|
|
202
|
+
.flatMap((t) => String(t).split(","))
|
|
203
|
+
.map((t) => t.trim())
|
|
204
|
+
.filter(Boolean);
|
|
205
|
+
return types.length ? types : undefined;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Stop a request from turning on options that execute code from the scanned
|
|
210
|
+
* repository when the operator has not. In secure mode, or when the server was
|
|
211
|
+
* started with --no-install-deps, a request may turn these options off but
|
|
212
|
+
* never on.
|
|
213
|
+
*
|
|
214
|
+
* @param {Object} options Options parsed from the request
|
|
215
|
+
* @param {Object} operatorOptions Options the server was started with
|
|
216
|
+
* @returns {void}
|
|
217
|
+
*/
|
|
218
|
+
function restrictRequestOptions(options, operatorOptions) {
|
|
219
|
+
if (!isSecureModeActive() && operatorOptions.installDeps !== false) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
for (const param of CODE_EXECUTION_PARAMS) {
|
|
223
|
+
// Only an explicit false from the request changes the operator's choice.
|
|
224
|
+
// Values such as "" or 0 would otherwise pass gates that test `!== false`.
|
|
225
|
+
options[param] =
|
|
226
|
+
operatorOptions[param] === true && options[param] !== false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Project types that inventory the host the server runs on, such as installed
|
|
231
|
+
// packages, users, shell history, IDE and browser extensions, or the Maven,
|
|
232
|
+
// Gradle, sbt, Cargo and Helm caches in the server user's home directory.
|
|
233
|
+
// They ignore the scan path, so the path allowlist does not limit them.
|
|
234
|
+
const HOST_SCOPE_PROJECT_TYPES = new Set(
|
|
235
|
+
[
|
|
236
|
+
"os",
|
|
237
|
+
"hbom",
|
|
238
|
+
"cargo-cache",
|
|
239
|
+
"helm-index",
|
|
240
|
+
"gradle-index",
|
|
241
|
+
"sbt-index",
|
|
242
|
+
"maven-index",
|
|
243
|
+
"vscode-extension",
|
|
244
|
+
"chrome-extension",
|
|
245
|
+
"agentic",
|
|
246
|
+
]
|
|
247
|
+
.flatMap((key) => PROJECT_TYPE_ALIASES[key] || [key])
|
|
248
|
+
.concat(["maven-repo"]),
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Check request options that the server must refuse outright.
|
|
253
|
+
*
|
|
254
|
+
* @param {Object} reqOptions Options parsed from the request
|
|
255
|
+
* @param {Object} operatorOptions Options the server was started with
|
|
256
|
+
* @returns {Object|null} Error object with status, error and details, or null
|
|
257
|
+
*/
|
|
258
|
+
export function validateRequestOptions(reqOptions, operatorOptions = {}) {
|
|
259
|
+
const operatorTypes = new Set(
|
|
260
|
+
toProjectTypeList(operatorOptions.type ?? operatorOptions.projectType) ||
|
|
261
|
+
[],
|
|
262
|
+
);
|
|
263
|
+
const hostScopeType = (reqOptions.projectType || []).find(
|
|
264
|
+
(t) => HOST_SCOPE_PROJECT_TYPES.has(t) && !operatorTypes.has(t),
|
|
265
|
+
);
|
|
266
|
+
if (hostScopeType) {
|
|
267
|
+
return {
|
|
268
|
+
status: 403,
|
|
269
|
+
error: "Project Type Not Allowed",
|
|
270
|
+
details: `The '${hostScopeType}' project type scans the server host. Start the server with this type to allow it.`,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
const excludes = Array.isArray(reqOptions.exclude)
|
|
274
|
+
? reqOptions.exclude
|
|
275
|
+
: [reqOptions.exclude];
|
|
276
|
+
// Brace patterns expand before matching, so one short pattern such as
|
|
277
|
+
// {1..100000} can keep the server busy for minutes.
|
|
278
|
+
if (
|
|
279
|
+
reqOptions.exclude !== operatorOptions.exclude &&
|
|
280
|
+
excludes.some(
|
|
281
|
+
(e) => typeof e === "string" && (e.includes("{") || e.length > 512),
|
|
282
|
+
)
|
|
283
|
+
) {
|
|
284
|
+
return {
|
|
285
|
+
status: 400,
|
|
286
|
+
error: "Invalid Exclude Pattern",
|
|
287
|
+
details:
|
|
288
|
+
"Exclude patterns sent to the server cannot use braces or exceed 512 characters.",
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
if (
|
|
292
|
+
reqOptions.serverUrl &&
|
|
293
|
+
reqOptions.serverUrl !== operatorOptions.serverUrl
|
|
294
|
+
) {
|
|
295
|
+
const requestedOrigin = urlOrigin(reqOptions.serverUrl);
|
|
296
|
+
if (!requestedOrigin) {
|
|
297
|
+
return {
|
|
298
|
+
status: 400,
|
|
299
|
+
error: "Invalid Server URL",
|
|
300
|
+
details: "The Dependency-Track server URL is invalid.",
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
if (requestedOrigin !== urlOrigin(operatorOptions.serverUrl)) {
|
|
304
|
+
// The operator's API key only ever goes to the operator's server.
|
|
305
|
+
if (
|
|
306
|
+
operatorOptions.apiKey &&
|
|
307
|
+
reqOptions.apiKey === operatorOptions.apiKey
|
|
308
|
+
) {
|
|
309
|
+
return {
|
|
310
|
+
status: 400,
|
|
311
|
+
error: "API Key Required",
|
|
312
|
+
details:
|
|
313
|
+
"A request that names its own Dependency-Track server must send its own API key.",
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
// CDXGEN_SERVER_ALLOWED_HOSTS only covers git hosts, so in secure mode a
|
|
317
|
+
// server the operator did not configure must be on CDXGEN_ALLOWED_HOSTS.
|
|
318
|
+
if (isSecureMode && !readEnvironmentVariable("CDXGEN_ALLOWED_HOSTS")) {
|
|
319
|
+
return {
|
|
320
|
+
status: 403,
|
|
321
|
+
error: "Host Not Allowed",
|
|
322
|
+
details:
|
|
323
|
+
"Set CDXGEN_ALLOWED_HOSTS to let requests choose a Dependency-Track server.",
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (isSecureMode && reqOptions.includeFormulation && reqOptions.serverUrl) {
|
|
329
|
+
return {
|
|
330
|
+
status: 400,
|
|
331
|
+
error: "Formulation Not Allowed",
|
|
332
|
+
details:
|
|
333
|
+
"Formulation data may contain secrets and is not submitted to Dependency-Track in secure mode.",
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function urlOrigin(value) {
|
|
340
|
+
try {
|
|
341
|
+
return new URL(value).origin;
|
|
342
|
+
} catch (_err) {
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
177
347
|
/**
|
|
178
348
|
* Extracts query parameters from an incoming HTTP request object.
|
|
179
349
|
* Handles repeated keys by collecting their values into an array.
|
|
@@ -391,6 +561,18 @@ const start = (options) => {
|
|
|
391
561
|
}),
|
|
392
562
|
);
|
|
393
563
|
}
|
|
564
|
+
const optionsError = validateRequestOptions(reqOptions, options);
|
|
565
|
+
if (optionsError) {
|
|
566
|
+
res.writeHead(optionsError.status, {
|
|
567
|
+
"Content-Type": "application/json",
|
|
568
|
+
});
|
|
569
|
+
return res.end(
|
|
570
|
+
JSON.stringify({
|
|
571
|
+
error: optionsError.error,
|
|
572
|
+
details: optionsError.details,
|
|
573
|
+
}),
|
|
574
|
+
);
|
|
575
|
+
}
|
|
394
576
|
// Enforce the v13 CycloneDX spec floor on the HTTP entry point too, so the
|
|
395
577
|
// server cannot be used to request a generation target the CLI rejects.
|
|
396
578
|
if (reqOptions.specVersion !== undefined) {
|
|
@@ -477,7 +659,18 @@ const start = (options) => {
|
|
|
477
659
|
);
|
|
478
660
|
}
|
|
479
661
|
}
|
|
480
|
-
|
|
662
|
+
try {
|
|
663
|
+
cloneDir = gitClone(sourcePath, gitRef);
|
|
664
|
+
} catch (_err) {
|
|
665
|
+
console.log("Unable to clone repository. See the server log.");
|
|
666
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
667
|
+
return res.end(
|
|
668
|
+
JSON.stringify({
|
|
669
|
+
error: "Unable to clone repository",
|
|
670
|
+
details: "Cloning the repository failed. See the server log.",
|
|
671
|
+
}),
|
|
672
|
+
);
|
|
673
|
+
}
|
|
481
674
|
srcDir = cloneDir;
|
|
482
675
|
if (purlResolution?.type === "npm") {
|
|
483
676
|
const cloneRootDir = cloneDir;
|
|
@@ -502,8 +695,11 @@ const start = (options) => {
|
|
|
502
695
|
cleanup = true;
|
|
503
696
|
} else {
|
|
504
697
|
srcDir = sourcePath;
|
|
698
|
+
// Check where a symlinked path really points as well, so a link inside
|
|
699
|
+
// an allowed directory cannot open a scan of a directory outside it.
|
|
505
700
|
if (
|
|
506
701
|
!isAllowedPath(path.resolve(srcDir)) ||
|
|
702
|
+
!isAllowedRealPath(srcDir) ||
|
|
507
703
|
(isWin && !isAllowedWinPath(srcDir))
|
|
508
704
|
) {
|
|
509
705
|
res.writeHead(403, { "Content-Type": "application/json" });
|
|
@@ -463,6 +463,15 @@ export function textualMetadata(bomJson) {
|
|
|
463
463
|
const swidCount = bomJson?.components?.filter((c) =>
|
|
464
464
|
c?.purl?.startsWith("pkg:swid"),
|
|
465
465
|
).length;
|
|
466
|
+
// Homebrew packages are pkg:brew, not pkg:swid, so they are not counted as
|
|
467
|
+
// applications; formulae and casks are told apart by a property.
|
|
468
|
+
const brewComponents =
|
|
469
|
+
bomJson?.components?.filter((c) => c?.purl?.startsWith("pkg:brew/")) || [];
|
|
470
|
+
const brewCaskCount = brewComponents.filter((c) =>
|
|
471
|
+
c.properties?.some(
|
|
472
|
+
(p) => p.name === "cdx:homebrew:type" && p.value === "cask",
|
|
473
|
+
),
|
|
474
|
+
).length;
|
|
466
475
|
const { unpackagedExecutableCount, unpackagedSharedLibraryCount } =
|
|
467
476
|
getContainerFileInventoryStats(bomJson?.components);
|
|
468
477
|
const githubStats = getGitHubWorkflowStats(bomJson?.components);
|
|
@@ -711,7 +720,14 @@ export function textualMetadata(bomJson) {
|
|
|
711
720
|
text = `${text} The system appears to be set up for remote development, with ${vsixCount} Visual Studio Code extensions installed.`;
|
|
712
721
|
}
|
|
713
722
|
if (swidCount > 0) {
|
|
714
|
-
text = `${text} In addition, there are ${swidCount} applications installed on the system.`;
|
|
723
|
+
text = `${text} In addition, there ${swidCount === 1 ? "is 1 application" : `are ${swidCount} applications`} installed on the system.`;
|
|
724
|
+
}
|
|
725
|
+
if (brewComponents.length > 0) {
|
|
726
|
+
const brewCount = brewComponents.length;
|
|
727
|
+
const casks = brewCaskCount
|
|
728
|
+
? `, of which ${brewCaskCount} ${brewCaskCount === 1 ? "is a cask" : "are casks"}`
|
|
729
|
+
: "";
|
|
730
|
+
text = `${text} The system also has ${brewCount} Homebrew package${brewCount === 1 ? "" : "s"} installed${casks}.`;
|
|
715
731
|
}
|
|
716
732
|
}
|
|
717
733
|
if (bomType === "HBOM" && hbomSummary) {
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
isVersionedJvmToolProjectType,
|
|
37
37
|
PROJECT_TYPE_ALIASES,
|
|
38
38
|
} from "../../../core/env.js";
|
|
39
|
+
import { safeExistsSync } from "../../../core/fs.js";
|
|
39
40
|
import { dirNameStr } from "../../../core/paths.js";
|
|
40
41
|
import { CDXGEN_VERSION } from "../../../core/state.js";
|
|
41
42
|
import { evaluateRules, loadRules } from "../ruleEngine.js";
|
|
@@ -389,7 +390,9 @@ function resolveLedgerEvents(context) {
|
|
|
389
390
|
};
|
|
390
391
|
}
|
|
391
392
|
const sidecarPath = readEnvironmentVariable("CDXGEN_INTROSPECT_LEDGER");
|
|
392
|
-
|
|
393
|
+
// A sidecar the run could not create (the permission model denies writes
|
|
394
|
+
// outside the granted paths) holds nothing; the buffer still has the events.
|
|
395
|
+
if (sidecarPath && safeExistsSync(sidecarPath)) {
|
|
393
396
|
return { events: loadLedgerFile(sidecarPath), source: "sidecar" };
|
|
394
397
|
}
|
|
395
398
|
const events = getLedgerEvents();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
|
|
5
5
|
import { Purl } from "@cdxgen/cdx-purl";
|
|
@@ -943,10 +943,10 @@ function applyCustomMetadataProperties(bomJson, options) {
|
|
|
943
943
|
export function applyStandards(bomJson, options) {
|
|
944
944
|
if (options.standard && Array.isArray(options.standard)) {
|
|
945
945
|
for (let astandard of options.standard) {
|
|
946
|
-
// See issue: #1953
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
946
|
+
// See issue: #1953. Server requests can send either separator, and on
|
|
947
|
+
// Windows both "/" and "\" separate path segments, so keep only the
|
|
948
|
+
// last segment of either kind.
|
|
949
|
+
astandard = String(astandard).split(/[\\/]/).pop();
|
|
950
950
|
const templateFile = join(
|
|
951
951
|
dirNameStr,
|
|
952
952
|
"data",
|