@cdxgen/cdxgen 13.6.0 → 13.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -8
- package/bin/audit.js +7 -4
- package/bin/cdxgen.js +256 -37
- package/bin/convert.js +146 -22
- package/bin/evinse.js +25 -3
- package/bin/repl.js +47 -3
- package/bin/sign.js +211 -34
- package/bin/tracebom.js +18 -6
- package/bin/validate.js +18 -3
- package/bin/verify.js +113 -5
- package/data/behavior-taxonomy.schema.json +1491 -0
- package/data/cyclonedx-2.0-bundled.schema.json +18606 -5528
- package/data/cyclonedx-2.0-cryptography-defs.schema.json +857 -0
- package/data/pqc-migration.json +239 -0
- package/data/remediations.json +52 -0
- package/data/rules/cbom-security.yaml +4 -3
- package/data/rules/package-integrity.yaml +28 -0
- package/lib/audit/index.js +25 -3
- package/lib/cli/bomAssembly.js +80 -1
- package/lib/cli/cliOptions.js +121 -17
- package/lib/cli/index.js +609 -898
- package/lib/cli/jsBom.js +3 -0
- package/lib/cli/jvmBom.js +365 -40
- package/lib/cli/managedBom.js +19 -3
- package/lib/cli/nativeBom.js +10 -0
- package/lib/core/env.js +26 -0
- package/lib/core/fs.js +126 -3
- package/lib/core/globPatterns.js +218 -0
- package/lib/core/processTree.js +99 -12
- package/lib/core/projectConfig.js +332 -25
- package/lib/core/toolRegistry.js +15 -1
- package/lib/ecosystems/denoutils.js +2 -2
- package/lib/ecosystems/golem.js +3 -8
- package/lib/ecosystems/gradleutils.js +1082 -6
- package/lib/ecosystems/jsEvidence.js +90 -38
- package/lib/ecosystems/kosi.js +3 -5
- package/lib/ecosystems/npmVersionResolution.js +376 -0
- package/lib/ecosystems/parsers-python.js +89 -28
- package/lib/ecosystems/parsers-rust.js +117 -15
- package/lib/ecosystems/remote/huggingface.js +22 -0
- package/lib/ecosystems/rusi.js +2 -4
- package/lib/ecosystems/scalasem.js +2 -1
- package/lib/evinser/evinser.js +196 -34
- package/lib/evinser/scalasem.js +5 -1
- package/lib/helpers/agenticToolutils.js +112 -1
- package/lib/helpers/bomSigner.js +43 -9
- package/lib/helpers/jssSigner.js +1276 -0
- package/lib/inventory/agentBlueprint.js +193 -0
- package/lib/inventory/agentToolNames.js +145 -0
- package/lib/inventory/aiCollector.js +153 -7
- package/lib/inventory/aiInventory.js +44 -8
- package/lib/inventory/aiProviders.js +324 -0
- package/lib/inventory/bomUtils.js +8 -1
- package/lib/inventory/ciParsers/githubActionsLock.js +821 -0
- package/lib/inventory/citations.js +31 -2
- package/lib/inventory/communityAiConfigParser.js +978 -80
- package/lib/inventory/cyclonedx20.js +152 -0
- package/lib/inventory/cyclonedx20Common.js +286 -0
- package/lib/inventory/cyclonedx20Derived.js +159 -0
- package/lib/inventory/cyclonedx20Parties.js +552 -0
- package/lib/inventory/cyclonedx20Read.js +768 -0
- package/lib/inventory/cyclonedx20ReadCommon.js +374 -0
- package/lib/inventory/cyclonedx20ReadComponents.js +692 -0
- package/lib/inventory/cyclonedx20ReadContext.js +380 -0
- package/lib/inventory/cyclonedx20ReadEvidence.js +466 -0
- package/lib/inventory/cyclonedx20ReadSections.js +599 -0
- package/lib/inventory/cyclonedx20Render.js +766 -0
- package/lib/inventory/cyclonedx20RenderComponents.js +716 -0
- package/lib/inventory/cyclonedx20RenderDefinitions.js +405 -0
- package/lib/inventory/cyclonedx20RenderEvidence.js +726 -0
- package/lib/inventory/cyclonedx20RenderHardware.js +203 -0
- package/lib/inventory/cyclonedx20RenderPatents.js +174 -0
- package/lib/inventory/cyclonedx20RenderRefIds.js +314 -0
- package/lib/inventory/cyclonedx20RenderSections.js +499 -0
- package/lib/inventory/cyclonedx20Tables.js +594 -0
- package/lib/inventory/deps.js +106 -13
- package/lib/inventory/dosai.js +236 -1
- package/lib/inventory/dosaiParsers.js +3 -6
- package/lib/inventory/dotnetRuntime.js +164 -14
- package/lib/inventory/ecosystemFormulationParsers.js +979 -0
- package/lib/inventory/envcontext.js +132 -38
- package/lib/inventory/evidenceUtils.js +2 -1
- package/lib/inventory/formulationParsers.js +95 -13
- package/lib/inventory/hbom.js +45 -6
- package/lib/inventory/hbomLoader.js +166 -0
- package/lib/inventory/mcpConfigParser.js +5 -0
- package/lib/inventory/occurrenceKey.js +90 -0
- package/lib/inventory/pqcMigration.js +426 -0
- package/lib/inventory/rustFormulationParser.js +21 -10
- package/lib/inventory/source.js +65 -6
- package/lib/parsers/huggingfaceManifest.js +9 -1
- package/lib/parsers/huggingfaceUtils.js +18 -0
- package/lib/parsers/kiroPermissions.js +166 -0
- package/lib/server/server.js +36 -34
- package/lib/stages/postgen/postgen.js +41 -1
- package/lib/stages/postgen/specVersionCompat.js +76 -0
- package/lib/validator/bomValidator.js +8 -1
- package/lib/validator/complianceEngine.js +30 -3
- package/lib/validator/complianceRules.js +1416 -4
- package/lib/validator/generated/validate-2.0.mjs +57554 -13851
- package/lib/validator/index.js +48 -7
- package/lib/validator/reporters/json.js +6 -0
- package/package.json +9 -9
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +6 -2
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +16 -0
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +6 -0
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +2 -1
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +15 -0
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/boundedFile.d.ts +30 -0
- package/types/lib/core/boundedFile.d.ts.map +1 -1
- package/types/lib/core/env.d.ts +10 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +4 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/globPatterns.d.ts +70 -0
- package/types/lib/core/globPatterns.d.ts.map +1 -0
- package/types/lib/core/paths.d.ts +9 -0
- package/types/lib/core/paths.d.ts.map +1 -1
- package/types/lib/core/processTree.d.ts +16 -5
- package/types/lib/core/processTree.d.ts.map +1 -1
- package/types/lib/core/projectConfig.d.ts +47 -8
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/toolRegistry.d.ts.map +1 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +248 -8
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts +14 -1
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts.map +1 -1
- package/types/lib/ecosystems/npmVersionResolution.d.ts +119 -0
- package/types/lib/ecosystems/npmVersionResolution.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +1 -1
- package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/scalasem.d.ts +58 -16
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +61 -9
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/scalasem.d.ts.map +1 -1
- package/types/lib/helpers/agenticToolutils.d.ts +53 -19
- package/types/lib/helpers/agenticToolutils.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +20 -3
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/helpers/jssSigner.d.ts +288 -0
- package/types/lib/helpers/jssSigner.d.ts.map +1 -0
- package/types/lib/helpers/vsixutils.d.ts +303 -19
- package/types/lib/helpers/vsixutils.d.ts.map +1 -1
- package/types/lib/inventory/agentBlueprint.d.ts +70 -0
- package/types/lib/inventory/agentBlueprint.d.ts.map +1 -0
- package/types/lib/inventory/agentToolNames.d.ts +51 -0
- package/types/lib/inventory/agentToolNames.d.ts.map +1 -0
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/aiInventory.d.ts.map +1 -1
- package/types/lib/inventory/aiProviders.d.ts +85 -0
- package/types/lib/inventory/aiProviders.d.ts.map +1 -0
- package/types/lib/inventory/bomUtils.d.ts.map +1 -1
- package/types/lib/inventory/ciParsers/githubActionsLock.d.ts +101 -0
- package/types/lib/inventory/ciParsers/githubActionsLock.d.ts.map +1 -0
- package/types/lib/inventory/citations.d.ts +5 -0
- package/types/lib/inventory/citations.d.ts.map +1 -1
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/cyclonedx20.d.ts +99 -0
- package/types/lib/inventory/cyclonedx20.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Common.d.ts +102 -0
- package/types/lib/inventory/cyclonedx20Common.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Derived.d.ts +74 -0
- package/types/lib/inventory/cyclonedx20Derived.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Parties.d.ts +91 -0
- package/types/lib/inventory/cyclonedx20Parties.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Read.d.ts +51 -0
- package/types/lib/inventory/cyclonedx20Read.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadCommon.d.ts +69 -0
- package/types/lib/inventory/cyclonedx20ReadCommon.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadComponents.d.ts +77 -0
- package/types/lib/inventory/cyclonedx20ReadComponents.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadContext.d.ts +118 -0
- package/types/lib/inventory/cyclonedx20ReadContext.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts +40 -0
- package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadSections.d.ts +45 -0
- package/types/lib/inventory/cyclonedx20ReadSections.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Render.d.ts +53 -0
- package/types/lib/inventory/cyclonedx20Render.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderComponents.d.ts +73 -0
- package/types/lib/inventory/cyclonedx20RenderComponents.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts +29 -0
- package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts +110 -0
- package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderHardware.d.ts +42 -0
- package/types/lib/inventory/cyclonedx20RenderHardware.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderPatents.d.ts +26 -0
- package/types/lib/inventory/cyclonedx20RenderPatents.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts +55 -0
- package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderSections.d.ts +45 -0
- package/types/lib/inventory/cyclonedx20RenderSections.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Tables.d.ts +134 -0
- package/types/lib/inventory/cyclonedx20Tables.d.ts.map +1 -0
- package/types/lib/inventory/deps.d.ts +35 -2
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +19 -0
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
- package/types/lib/inventory/dotnetRuntime.d.ts +51 -4
- package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -1
- package/types/lib/inventory/ecosystemFormulationParsers.d.ts +73 -0
- package/types/lib/inventory/ecosystemFormulationParsers.d.ts.map +1 -0
- package/types/lib/inventory/envcontext.d.ts +22 -6
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
- package/types/lib/inventory/hbom.d.ts +11 -0
- package/types/lib/inventory/hbom.d.ts.map +1 -1
- package/types/lib/inventory/hbomLoader.d.ts +54 -0
- package/types/lib/inventory/hbomLoader.d.ts.map +1 -1
- package/types/lib/inventory/jvmLocalRepos.d.ts +5 -1
- package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -1
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/occurrenceKey.d.ts +52 -0
- package/types/lib/inventory/occurrenceKey.d.ts.map +1 -0
- package/types/lib/inventory/pqcMigration.d.ts +84 -0
- package/types/lib/inventory/pqcMigration.d.ts.map +1 -0
- package/types/lib/inventory/rustFormulationParser.d.ts +7 -2
- package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/scalaCoords.d.ts.map +1 -1
- package/types/lib/inventory/scalaDedupe.d.ts +43 -0
- package/types/lib/inventory/scalaDedupe.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +15 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/parsers/huggingfaceManifest.d.ts +10 -1
- package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts +12 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
- package/types/lib/parsers/kiroPermissions.d.ts +62 -0
- package/types/lib/parsers/kiroPermissions.d.ts.map +1 -0
- package/types/lib/parsers/vscodeExtensionId.d.ts +48 -0
- package/types/lib/parsers/vscodeExtensionId.d.ts.map +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +7 -0
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts +25 -0
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts +5 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/complianceEngine.d.ts +4 -4
- package/types/lib/validator/complianceEngine.d.ts.map +1 -1
- package/types/lib/validator/complianceRules.d.ts +14 -2
- package/types/lib/validator/complianceRules.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +10 -0
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
|
@@ -47,6 +47,10 @@ import { diagnosticStream } from "../core/logger.js";
|
|
|
47
47
|
import { dirNameStr, isMac, isWin } from "../core/paths.js";
|
|
48
48
|
import { isProjectToolingAllowed } from "../core/projectTooling.js";
|
|
49
49
|
import { brotliDecoderProperties, readBrotliDecoder } from "./brotliDecoder.js";
|
|
50
|
+
import {
|
|
51
|
+
createHostDotnetRuntimeComponent,
|
|
52
|
+
parseDotnetListRuntimes,
|
|
53
|
+
} from "./dotnetRuntime.js";
|
|
50
54
|
import { isValidSdkmanVersion } from "./jvmToolEnv.js";
|
|
51
55
|
import { createNodeRuntimeComponent } from "./nodeBuildInfo.js";
|
|
52
56
|
import {
|
|
@@ -179,7 +183,8 @@ export function getOriginUrl(dir) {
|
|
|
179
183
|
* @param {string} configKey Git config key
|
|
180
184
|
* @param {string} dir repo directory
|
|
181
185
|
*
|
|
182
|
-
* @returns
|
|
186
|
+
* @returns {string|undefined} The branch name, or undefined when git fails,
|
|
187
|
+
* for example when the directory is not a git working tree or has no commit
|
|
183
188
|
*/
|
|
184
189
|
export function getBranch(_configKey, dir) {
|
|
185
190
|
return execGitCommand(dir, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
@@ -212,20 +217,20 @@ export function gitTreeHashes(dir) {
|
|
|
212
217
|
}
|
|
213
218
|
|
|
214
219
|
/**
|
|
215
|
-
* Retrieves the files
|
|
216
|
-
*
|
|
220
|
+
* Retrieves the files git tracks under a directory at HEAD. Only the
|
|
221
|
+
* directory's own subtree is listed, with names relative to it, so a scan of
|
|
222
|
+
* one directory of a larger repository does not record the rest of that
|
|
223
|
+
* repository.
|
|
217
224
|
*
|
|
218
|
-
* @
|
|
225
|
+
* @param {string} dir directory inside a git working tree
|
|
226
|
+
*
|
|
227
|
+
* @returns {Object[]} files with their blob hash, name, OmniBOR id and SWHID;
|
|
228
|
+
* empty when git fails, for example when the directory is not a git working
|
|
229
|
+
* tree or has no commit
|
|
219
230
|
*/
|
|
220
231
|
export function listFiles(dir) {
|
|
221
232
|
const filesList = [];
|
|
222
|
-
const output = execGitCommand(dir, [
|
|
223
|
-
"ls-tree",
|
|
224
|
-
"-l",
|
|
225
|
-
"-r",
|
|
226
|
-
"--full-tree",
|
|
227
|
-
"HEAD",
|
|
228
|
-
]);
|
|
233
|
+
const output = execGitCommand(dir, ["ls-tree", "-l", "-r", "HEAD"]);
|
|
229
234
|
if (output) {
|
|
230
235
|
output.split("\n").forEach((l) => {
|
|
231
236
|
l = l.replaceAll("\r", "");
|
|
@@ -253,7 +258,10 @@ export function listFiles(dir) {
|
|
|
253
258
|
* @param {string} dir Repo directory
|
|
254
259
|
* @param {Array} args arguments to git command
|
|
255
260
|
*
|
|
256
|
-
* @returns
|
|
261
|
+
* @returns {string|undefined} git's standard output, or undefined when git
|
|
262
|
+
* could not run or exited with a non-zero status. Text git writes to standard
|
|
263
|
+
* error, including warnings of a successful command, is never part of the
|
|
264
|
+
* result.
|
|
257
265
|
*/
|
|
258
266
|
export function execGitCommand(dir, args) {
|
|
259
267
|
// Prepend hardening `-c` overrides before the subcommand and run git with a
|
|
@@ -270,6 +278,8 @@ export function execGitCommand(dir, args) {
|
|
|
270
278
|
{
|
|
271
279
|
env: hardenedGitEnv(),
|
|
272
280
|
shell: false,
|
|
281
|
+
requireSuccess: true,
|
|
282
|
+
includeStderr: false,
|
|
273
283
|
},
|
|
274
284
|
);
|
|
275
285
|
}
|
|
@@ -407,6 +417,22 @@ function recordToolProbeFailure(tool, ecosystem, command, detail) {
|
|
|
407
417
|
}
|
|
408
418
|
}
|
|
409
419
|
|
|
420
|
+
/**
|
|
421
|
+
* Collapse command output to one line. The `\r` of a Windows line ending goes
|
|
422
|
+
* with the break it belongs to, and the empty lines of trailing output are
|
|
423
|
+
* dropped.
|
|
424
|
+
*
|
|
425
|
+
* @param {string} output command output
|
|
426
|
+
* @param {string} separator what to join the lines with
|
|
427
|
+
* @returns {string} the non-empty lines joined into one line
|
|
428
|
+
*/
|
|
429
|
+
function collapseOutputLines(output, separator) {
|
|
430
|
+
return `${output || ""}`
|
|
431
|
+
.split(/\r?\n/)
|
|
432
|
+
.filter((line) => line !== "")
|
|
433
|
+
.join(separator);
|
|
434
|
+
}
|
|
435
|
+
|
|
410
436
|
/**
|
|
411
437
|
* Collect Java version and installed modules
|
|
412
438
|
*
|
|
@@ -414,9 +440,9 @@ function recordToolProbeFailure(tool, ecosystem, command, detail) {
|
|
|
414
440
|
* @returns Object containing the java details
|
|
415
441
|
*/
|
|
416
442
|
export function collectJavaInfo(dir) {
|
|
417
|
-
const versionDesc =
|
|
443
|
+
const versionDesc = probeCommandOutput(getJavaCommand(), dir, ["--version"]);
|
|
418
444
|
const moduleDesc =
|
|
419
|
-
|
|
445
|
+
probeCommandOutput(getJavaCommand(), dir, ["--list-modules"]) || "";
|
|
420
446
|
if (versionDesc) {
|
|
421
447
|
const info = {
|
|
422
448
|
type: "platform",
|
|
@@ -426,7 +452,7 @@ export function collectJavaInfo(dir) {
|
|
|
426
452
|
properties: [
|
|
427
453
|
{
|
|
428
454
|
name: "java:modules",
|
|
429
|
-
value: moduleDesc
|
|
455
|
+
value: collapseOutputLines(moduleDesc, ", "),
|
|
430
456
|
},
|
|
431
457
|
],
|
|
432
458
|
};
|
|
@@ -440,20 +466,41 @@ export function collectJavaInfo(dir) {
|
|
|
440
466
|
/**
|
|
441
467
|
* Collect dotnet version
|
|
442
468
|
*
|
|
469
|
+
* `version` is the version `dotnet --version` prints, which is the SDK's. .NET
|
|
470
|
+
* advisories name runtime versions, so the component itself carries no purl
|
|
471
|
+
* and no CPE. Each shared framework `dotnet --list-runtimes` reports is a
|
|
472
|
+
* nested component built by createHostDotnetRuntimeComponent, without a
|
|
473
|
+
* bom-ref like the other toolchains.
|
|
474
|
+
*
|
|
443
475
|
* @param {string} dir Working directory
|
|
444
476
|
* @returns Object containing dotnet details
|
|
445
477
|
*/
|
|
446
478
|
export function collectDotnetInfo(dir) {
|
|
447
|
-
const versionDesc =
|
|
479
|
+
const versionDesc = probeCommandOutput(DOTNET_CMD, dir, ["--version"]);
|
|
448
480
|
const moduleDesc =
|
|
449
|
-
|
|
481
|
+
probeCommandOutput(DOTNET_CMD, dir, ["--list-runtimes"]) || "";
|
|
450
482
|
if (versionDesc) {
|
|
451
483
|
const info = {
|
|
452
484
|
type: "platform",
|
|
453
485
|
name: "dotnet",
|
|
454
486
|
version: versionDesc.trim(),
|
|
455
|
-
description: moduleDesc
|
|
487
|
+
description: collapseOutputLines(moduleDesc, "\\n"),
|
|
456
488
|
};
|
|
489
|
+
const components = [];
|
|
490
|
+
const seen = new Set();
|
|
491
|
+
for (const entry of parseDotnetListRuntimes(moduleDesc)) {
|
|
492
|
+
const key = `${entry.framework} ${entry.version}`;
|
|
493
|
+
if (seen.has(key)) {
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
seen.add(key);
|
|
497
|
+
components.push(
|
|
498
|
+
formulationToolchain(createHostDotnetRuntimeComponent(entry)),
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
if (components.length) {
|
|
502
|
+
info.components = components;
|
|
503
|
+
}
|
|
457
504
|
recordToolProbeResolved("dotnet", "csharp", info.version);
|
|
458
505
|
return info;
|
|
459
506
|
}
|
|
@@ -469,15 +516,15 @@ export function collectDotnetInfo(dir) {
|
|
|
469
516
|
*/
|
|
470
517
|
export function collectPythonInfo(dir) {
|
|
471
518
|
const pythonCmd = getPythonCommand();
|
|
472
|
-
const versionDesc =
|
|
519
|
+
const versionDesc = probeCommandOutput(pythonCmd, dir, ["-S", "--version"]);
|
|
473
520
|
const moduleDesc =
|
|
474
|
-
|
|
521
|
+
probeCommandOutput(pythonCmd, dir, ["-I", "-m", "pip", "--version"]) || "";
|
|
475
522
|
if (versionDesc) {
|
|
476
523
|
const info = {
|
|
477
524
|
type: "platform",
|
|
478
525
|
name: "python",
|
|
479
526
|
version: versionDesc.replace("Python ", ""),
|
|
480
|
-
description: moduleDesc
|
|
527
|
+
description: collapseOutputLines(moduleDesc, "\\n"),
|
|
481
528
|
};
|
|
482
529
|
recordToolProbeResolved("python", "python", info.version);
|
|
483
530
|
return info;
|
|
@@ -510,7 +557,7 @@ export function collectNodeInfo(dir) {
|
|
|
510
557
|
nodeInfo.cpe = runtime.cpe;
|
|
511
558
|
annotateNodeBrotli(nodeInfo.components);
|
|
512
559
|
}
|
|
513
|
-
const moduleDesc =
|
|
560
|
+
const moduleDesc = probeCommandOutput(NPM_CMD, dir, ["--version"]);
|
|
514
561
|
if (moduleDesc) {
|
|
515
562
|
nodeInfo.description = `npm: ${moduleDesc}`;
|
|
516
563
|
recordToolProbeResolved("npm", "npm", moduleDesc);
|
|
@@ -557,14 +604,14 @@ function annotateNodeBrotli(components) {
|
|
|
557
604
|
* @returns Object containing gcc details
|
|
558
605
|
*/
|
|
559
606
|
export function collectGccInfo(dir) {
|
|
560
|
-
const versionDesc =
|
|
561
|
-
const moduleDesc =
|
|
607
|
+
const versionDesc = probeCommandOutput(GCC_CMD, dir, ["--version"]);
|
|
608
|
+
const moduleDesc = probeCommandOutput(GCC_CMD, dir, ["-print-search-dirs"]);
|
|
562
609
|
if (versionDesc) {
|
|
563
610
|
const info = {
|
|
564
611
|
type: "platform",
|
|
565
612
|
name: "gcc",
|
|
566
613
|
version: versionDesc.split("\n")[0],
|
|
567
|
-
description: (moduleDesc
|
|
614
|
+
description: collapseOutputLines(moduleDesc, "\\n"),
|
|
568
615
|
};
|
|
569
616
|
recordToolProbeResolved("gcc", "c", info.version);
|
|
570
617
|
return info;
|
|
@@ -580,8 +627,8 @@ export function collectGccInfo(dir) {
|
|
|
580
627
|
* @returns Object containing rust details
|
|
581
628
|
*/
|
|
582
629
|
export function collectRustInfo(dir) {
|
|
583
|
-
const versionDesc =
|
|
584
|
-
const moduleDesc =
|
|
630
|
+
const versionDesc = probeCommandOutput(RUSTC_CMD, dir, ["--version"]);
|
|
631
|
+
const moduleDesc = probeCommandOutput(CARGO_CMD, dir, ["--version"]);
|
|
585
632
|
if (versionDesc) {
|
|
586
633
|
const info = {
|
|
587
634
|
type: "platform",
|
|
@@ -603,7 +650,7 @@ export function collectRustInfo(dir) {
|
|
|
603
650
|
* @returns Object containing go details
|
|
604
651
|
*/
|
|
605
652
|
export function collectGoInfo(dir) {
|
|
606
|
-
const versionDesc =
|
|
653
|
+
const versionDesc = probeCommandOutput(GO_CMD, dir, ["version"]);
|
|
607
654
|
if (versionDesc) {
|
|
608
655
|
const info = {
|
|
609
656
|
type: "platform",
|
|
@@ -624,7 +671,7 @@ export function collectGoInfo(dir) {
|
|
|
624
671
|
* @returns Object containing swift details
|
|
625
672
|
*/
|
|
626
673
|
export function collectSwiftInfo(dir) {
|
|
627
|
-
const versionDesc =
|
|
674
|
+
const versionDesc = probeCommandOutput(SWIFT_CMD, dir, ["--version"]);
|
|
628
675
|
if (versionDesc) {
|
|
629
676
|
const info = {
|
|
630
677
|
type: "platform",
|
|
@@ -710,7 +757,7 @@ function rubyKeepsCpe(dir) {
|
|
|
710
757
|
const target = isWin
|
|
711
758
|
? ""
|
|
712
759
|
: firstOutputLine(
|
|
713
|
-
|
|
760
|
+
probeCommandOutput(RUBY_CMD, dir, ["-e", "print RbConfig.ruby"]),
|
|
714
761
|
);
|
|
715
762
|
if (!isAbsolute(target)) {
|
|
716
763
|
return false;
|
|
@@ -738,7 +785,7 @@ function rubyKeepsCpe(dir) {
|
|
|
738
785
|
* @returns Object containing Ruby details
|
|
739
786
|
*/
|
|
740
787
|
export function collectRubyInfo(dir) {
|
|
741
|
-
const versionDesc =
|
|
788
|
+
const versionDesc = probeCommandOutput(RUBY_CMD, dir, ["--version"]);
|
|
742
789
|
const banner = firstOutputLine(versionDesc);
|
|
743
790
|
const parsed = parseRubyDescription(banner);
|
|
744
791
|
if (parsed) {
|
|
@@ -800,7 +847,9 @@ export function isRubyVersionActive(info, version) {
|
|
|
800
847
|
* @returns {Object|undefined} the RubyGems component
|
|
801
848
|
*/
|
|
802
849
|
export function collectRubyGemsInfo(dir) {
|
|
803
|
-
const token = firstOutputLine(
|
|
850
|
+
const token = firstOutputLine(
|
|
851
|
+
probeCommandOutput(GEM_CMD, dir, ["--version"]),
|
|
852
|
+
);
|
|
804
853
|
if (/^\d+\.\d+\.\d+$/.test(token)) {
|
|
805
854
|
recordToolProbeResolved("gem", "ruby", token);
|
|
806
855
|
return formulationToolchain(
|
|
@@ -828,7 +877,7 @@ export function collectRubyGemsInfo(dir) {
|
|
|
828
877
|
*/
|
|
829
878
|
export function collectBundlerInfo(dir) {
|
|
830
879
|
const line = firstOutputLine(
|
|
831
|
-
|
|
880
|
+
probeCommandOutput(BUNDLE_CMD, dir, ["--version"]),
|
|
832
881
|
);
|
|
833
882
|
const token = line.startsWith("Bundler version ")
|
|
834
883
|
? line.slice("Bundler version ".length).trim()
|
|
@@ -892,7 +941,7 @@ function formulationToolchain(component) {
|
|
|
892
941
|
* @returns Object containing the PHP details
|
|
893
942
|
*/
|
|
894
943
|
export function collectPhpInfo(dir) {
|
|
895
|
-
const output =
|
|
944
|
+
const output = probeCommandOutput(PHP_CMD, dir, ["-n", "-v"]);
|
|
896
945
|
const banner = firstOutputLine(output);
|
|
897
946
|
const reported = banner.startsWith("PHP ")
|
|
898
947
|
? parsePhpReportedVersion(banner.slice("PHP ".length))
|
|
@@ -934,7 +983,7 @@ export function collectPhpInfo(dir) {
|
|
|
934
983
|
* @returns Object containing the Composer details
|
|
935
984
|
*/
|
|
936
985
|
export function collectComposerInfo(dir) {
|
|
937
|
-
const output =
|
|
986
|
+
const output = probeCommandOutput(COMPOSER_CMD, dir, [
|
|
938
987
|
"--version",
|
|
939
988
|
"--no-plugins",
|
|
940
989
|
"--no-scripts",
|
|
@@ -1053,16 +1102,55 @@ export function collectEnvInfo(dir, options = undefined) {
|
|
|
1053
1102
|
return infoComponents;
|
|
1054
1103
|
}
|
|
1055
1104
|
|
|
1105
|
+
/**
|
|
1106
|
+
* Longest a toolchain version probe may run. A `--version` call answers in
|
|
1107
|
+
* well under a second; a tool that sets itself up on first use, such as
|
|
1108
|
+
* dotnet or gem in a fresh home directory, or one that waits on a prompt,
|
|
1109
|
+
* must not hold up a scan for the general spawn timeout.
|
|
1110
|
+
*/
|
|
1111
|
+
const VERSION_PROBE_TIMEOUT_MS = 60000;
|
|
1112
|
+
|
|
1113
|
+
/**
|
|
1114
|
+
* Run a toolchain version probe with VERSION_PROBE_TIMEOUT_MS as its limit.
|
|
1115
|
+
*
|
|
1116
|
+
* A probe that fails gives no output. Its text is then an error message, not
|
|
1117
|
+
* a version: `dotnet --version` under a global.json that pins an SDK the host
|
|
1118
|
+
* lacks exits with an error and lists every installed SDK, which would
|
|
1119
|
+
* otherwise become the version of the dotnet component in the formulation.
|
|
1120
|
+
*
|
|
1121
|
+
* @param {string} cmd Command to execute
|
|
1122
|
+
* @param {string} dir Working directory
|
|
1123
|
+
* @param {string[]} args Arguments
|
|
1124
|
+
* @returns {string|undefined} Output of the command, or undefined when it
|
|
1125
|
+
* could not run or exited with a non-zero status
|
|
1126
|
+
*/
|
|
1127
|
+
const probeCommandOutput = (cmd, dir, args) =>
|
|
1128
|
+
getCommandOutput(cmd, dir, args, {
|
|
1129
|
+
requireSuccess: true,
|
|
1130
|
+
timeout: VERSION_PROBE_TIMEOUT_MS,
|
|
1131
|
+
});
|
|
1132
|
+
|
|
1056
1133
|
/**
|
|
1057
1134
|
* Execute any command to retrieve the output
|
|
1058
1135
|
*
|
|
1059
1136
|
* @param {*} cmd Command to execute
|
|
1060
1137
|
* @param {*} dir working directory
|
|
1061
1138
|
* @param {*} args arguments
|
|
1062
|
-
* @param {Object} [
|
|
1063
|
-
*
|
|
1139
|
+
* @param {Object} [options] extra options merged into spawnSync (e.g. env),
|
|
1140
|
+
* plus `requireSuccess`, which gives undefined for a command that exits with
|
|
1141
|
+
* a non-zero status, and `includeStderr`, which joins the command's standard
|
|
1142
|
+
* error into the result (the default). With `includeStderr` false only the
|
|
1143
|
+
* standard output is returned, so text a command writes to standard error
|
|
1144
|
+
* cannot reach a parser of its output.
|
|
1145
|
+
* @returns {string|undefined} Output of the command, or undefined when it
|
|
1146
|
+
* could not run or printed nothing
|
|
1064
1147
|
*/
|
|
1065
|
-
const getCommandOutput = (cmd, dir, args,
|
|
1148
|
+
const getCommandOutput = (cmd, dir, args, options = {}) => {
|
|
1149
|
+
const {
|
|
1150
|
+
requireSuccess = false,
|
|
1151
|
+
includeStderr = true,
|
|
1152
|
+
...spawnOverrides
|
|
1153
|
+
} = options;
|
|
1066
1154
|
let commandToUse = cmd;
|
|
1067
1155
|
// If the command includes space, automatically move it to the front of the args.
|
|
1068
1156
|
if (cmd?.trim().includes(" ")) {
|
|
@@ -1091,7 +1179,13 @@ const getCommandOutput = (cmd, dir, args, spawnOverrides = {}) => {
|
|
|
1091
1179
|
maxBuffer: MAX_BUFFER * 2,
|
|
1092
1180
|
...spawnOverrides,
|
|
1093
1181
|
});
|
|
1182
|
+
if (requireSuccess && (result.error || result.status !== 0)) {
|
|
1183
|
+
return undefined;
|
|
1184
|
+
}
|
|
1094
1185
|
const stdout = result.stdout ? result.stdout.toString() : "";
|
|
1186
|
+
if (!includeStderr) {
|
|
1187
|
+
return stdout.trim() || undefined;
|
|
1188
|
+
}
|
|
1095
1189
|
const stderr = result.stderr ? result.stderr.toString() : "";
|
|
1096
1190
|
return `${stdout}\n${stderr}`.trim() || undefined;
|
|
1097
1191
|
};
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
} from "./dosaiParsers.js";
|
|
24
24
|
import { createGtfoBinsPropertiesFromRow } from "./gtfobins.js";
|
|
25
25
|
import { createLolbasProperties } from "./lolbas.js";
|
|
26
|
+
import { occurrenceLocationKey } from "./occurrenceKey.js";
|
|
26
27
|
import { isOsPackagePurlType } from "./osPackagePurl.js";
|
|
27
28
|
import {
|
|
28
29
|
createOsQueryFallbackBomRef,
|
|
@@ -370,7 +371,7 @@ export function addEvidenceForDotnet(pkgList, slicesFile, options = {}) {
|
|
|
370
371
|
addDosaiSetValue(
|
|
371
372
|
purlLocationMap,
|
|
372
373
|
modPurl,
|
|
373
|
-
|
|
374
|
+
occurrenceLocationKey(adep.Path, adep.LineNumber),
|
|
374
375
|
);
|
|
375
376
|
}
|
|
376
377
|
}
|
|
@@ -14,6 +14,13 @@ import { githubActionsParser } from "./ciParsers/githubActions.js";
|
|
|
14
14
|
import { gitlabCiParser } from "./ciParsers/gitlabCi.js";
|
|
15
15
|
import { jenkinsParser } from "./ciParsers/jenkins.js";
|
|
16
16
|
import { trimComponents } from "./depsUtils.js";
|
|
17
|
+
import {
|
|
18
|
+
dotnetProjectFormulationParser,
|
|
19
|
+
formulationSourcePath,
|
|
20
|
+
goModuleFormulationParser,
|
|
21
|
+
makefileFormulationParser,
|
|
22
|
+
pythonRequirementsFormulationParser,
|
|
23
|
+
} from "./ecosystemFormulationParsers.js";
|
|
17
24
|
import {
|
|
18
25
|
collectEnvInfo,
|
|
19
26
|
getBranch,
|
|
@@ -49,8 +56,9 @@ function buildReadmeSecurityComponents(discoveryPath, options) {
|
|
|
49
56
|
if (!scan.hasHiddenUnicode) {
|
|
50
57
|
continue;
|
|
51
58
|
}
|
|
59
|
+
const sourcePath = formulationSourcePath(filePath, discoveryPath);
|
|
52
60
|
const properties = [
|
|
53
|
-
{ name: "internal:SrcFile", value:
|
|
61
|
+
{ name: "internal:SrcFile", value: sourcePath },
|
|
54
62
|
{ name: "cdx:file:kind", value: "readme" },
|
|
55
63
|
{ name: "cdx:file:hasHiddenUnicode", value: "true" },
|
|
56
64
|
{
|
|
@@ -73,7 +81,7 @@ function buildReadmeSecurityComponents(discoveryPath, options) {
|
|
|
73
81
|
});
|
|
74
82
|
}
|
|
75
83
|
components.push({
|
|
76
|
-
"bom-ref": `file:${
|
|
84
|
+
"bom-ref": `file:${sourcePath}`,
|
|
77
85
|
name: basename(filePath),
|
|
78
86
|
properties,
|
|
79
87
|
type: "file",
|
|
@@ -92,13 +100,22 @@ function buildReadmeSecurityComponents(discoveryPath, options) {
|
|
|
92
100
|
* {
|
|
93
101
|
* id: string, // unique stable identifier
|
|
94
102
|
* patterns: string[], // non-empty array of glob patterns for file discovery
|
|
95
|
-
* parse(files: string[], options: Object
|
|
103
|
+
* parse(files: string[], options: Object, context: { rootDir: string }):
|
|
104
|
+
* // synchronous function
|
|
96
105
|
* { workflows?, components?, services?, properties?, dependencies? }
|
|
97
106
|
* }
|
|
98
107
|
* ```
|
|
108
|
+
*
|
|
109
|
+
* `context.rootDir` is the scanned project root. A parser that records file
|
|
110
|
+
* paths records them relative to it, so no path of the scanning machine
|
|
111
|
+
* reaches the BOM.
|
|
99
112
|
*/
|
|
100
113
|
const _parsers = [
|
|
101
114
|
rustFormulationParser,
|
|
115
|
+
makefileFormulationParser,
|
|
116
|
+
goModuleFormulationParser,
|
|
117
|
+
pythonRequirementsFormulationParser,
|
|
118
|
+
dotnetProjectFormulationParser,
|
|
102
119
|
githubActionsParser,
|
|
103
120
|
gitlabCiParser,
|
|
104
121
|
jenkinsParser,
|
|
@@ -189,6 +206,49 @@ const ENV_BLOCKLIST = [
|
|
|
189
206
|
"webhook",
|
|
190
207
|
];
|
|
191
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Remove the parts of every URL in an environment value that can carry
|
|
211
|
+
* credentials: the userinfo before `@`, the query string and the fragment.
|
|
212
|
+
* The value is scanned with indexOf, one URL at a time, so the work stays
|
|
213
|
+
* linear in its length.
|
|
214
|
+
*
|
|
215
|
+
* @param {string} value environment variable value
|
|
216
|
+
* @returns {string} the value with URL credentials, queries and fragments removed
|
|
217
|
+
*/
|
|
218
|
+
function stripUrlCredentials(value) {
|
|
219
|
+
const delimiters = " \t\r\n\"'`,;<>|";
|
|
220
|
+
let result = "";
|
|
221
|
+
let cursor = 0;
|
|
222
|
+
let schemeEnd = value.indexOf("://");
|
|
223
|
+
while (schemeEnd !== -1) {
|
|
224
|
+
const start = schemeEnd + 3;
|
|
225
|
+
let end = start;
|
|
226
|
+
while (end < value.length && !delimiters.includes(value[end])) {
|
|
227
|
+
end += 1;
|
|
228
|
+
}
|
|
229
|
+
const url = value.slice(start, end);
|
|
230
|
+
let authorityEnd = url.length;
|
|
231
|
+
for (const marker of ["/", "?", "#"]) {
|
|
232
|
+
const at = url.indexOf(marker);
|
|
233
|
+
if (at !== -1 && at < authorityEnd) {
|
|
234
|
+
authorityEnd = at;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const authority = url.slice(0, authorityEnd);
|
|
238
|
+
let rest = url.slice(authorityEnd);
|
|
239
|
+
for (const marker of ["?", "#"]) {
|
|
240
|
+
const at = rest.indexOf(marker);
|
|
241
|
+
if (at !== -1) {
|
|
242
|
+
rest = rest.slice(0, at);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
result += `${value.slice(cursor, start)}${authority.slice(authority.lastIndexOf("@") + 1)}${rest}`;
|
|
246
|
+
cursor = end;
|
|
247
|
+
schemeEnd = value.indexOf("://", end);
|
|
248
|
+
}
|
|
249
|
+
return result + value.slice(cursor);
|
|
250
|
+
}
|
|
251
|
+
|
|
192
252
|
// The project types a scan asked for, as given on the command line.
|
|
193
253
|
function requestedProjectTypes(options) {
|
|
194
254
|
return [].concat(options?.projectType || []).flatMap((type) =>
|
|
@@ -302,9 +362,16 @@ export function addFormulationSection(filePath, options, context = {}) {
|
|
|
302
362
|
// scanning host's tools say nothing about it.
|
|
303
363
|
// Only an explicit container type counts: hasAnyProjectType treats
|
|
304
364
|
// `universal` as every type, and a universal source scan runs on the host.
|
|
305
|
-
const
|
|
365
|
+
const requestedTypes = requestedProjectTypes(options);
|
|
366
|
+
const describesScanningHost = !requestedTypes.some((type) =>
|
|
306
367
|
PROJECT_TYPE_ALIASES.oci.includes(type),
|
|
307
368
|
);
|
|
369
|
+
// An OS inventory (obom) describes the live host. Its path is usually the
|
|
370
|
+
// working directory, such as a home directory or /, so the build-input,
|
|
371
|
+
// CI and README discovery below would walk a tree that is not a project.
|
|
372
|
+
const describesLiveHost =
|
|
373
|
+
requestedTypes.length > 0 &&
|
|
374
|
+
requestedTypes.every((type) => PROJECT_TYPE_ALIASES.os.includes(type));
|
|
308
375
|
if (!describesScanningHost) {
|
|
309
376
|
thoughtLog(
|
|
310
377
|
"Skipping the toolchain and OS crypto library probes for formulation, since they would describe the scanning host and not the image.",
|
|
@@ -320,8 +387,15 @@ export function addFormulationSection(filePath, options, context = {}) {
|
|
|
320
387
|
components = components.concat(infoComponents);
|
|
321
388
|
}
|
|
322
389
|
|
|
323
|
-
// OS crypto libraries (cbom mode)
|
|
324
|
-
|
|
390
|
+
// OS crypto libraries (cbom mode). The queries also read the host's
|
|
391
|
+
// certificates, authorized keys and keychain items, so they run only when
|
|
392
|
+
// formulation was requested explicitly, not when CI, a profile, an OS type
|
|
393
|
+
// or the cbom command turned it on.
|
|
394
|
+
if (
|
|
395
|
+
options.includeCrypto &&
|
|
396
|
+
describesScanningHost &&
|
|
397
|
+
!options.formulationAutomatic
|
|
398
|
+
) {
|
|
325
399
|
const cryptoLibs = collectOSCryptoLibs(options, context?.executeOsQuery);
|
|
326
400
|
if (cryptoLibs?.length) {
|
|
327
401
|
components = components.concat(cryptoLibs);
|
|
@@ -335,8 +409,13 @@ export function addFormulationSection(filePath, options, context = {}) {
|
|
|
335
409
|
const ciProperties = [];
|
|
336
410
|
|
|
337
411
|
const discoveryPath = projectPath || ".";
|
|
412
|
+
if (describesLiveHost) {
|
|
413
|
+
thoughtLog(
|
|
414
|
+
"Skipping build-input, CI and README discovery for formulation, since an OS inventory describes the host and not a project tree.",
|
|
415
|
+
);
|
|
416
|
+
}
|
|
338
417
|
|
|
339
|
-
for (const parser of _parsers) {
|
|
418
|
+
for (const parser of describesLiveHost ? [] : _parsers) {
|
|
340
419
|
const matchedFiles = [];
|
|
341
420
|
for (const pattern of parser.patterns) {
|
|
342
421
|
const found = getAllFiles(discoveryPath, pattern, options);
|
|
@@ -351,7 +430,9 @@ export function addFormulationSection(filePath, options, context = {}) {
|
|
|
351
430
|
|
|
352
431
|
let result;
|
|
353
432
|
try {
|
|
354
|
-
result = parser.parse(uniqueMatchedFiles, options
|
|
433
|
+
result = parser.parse(uniqueMatchedFiles, options, {
|
|
434
|
+
rootDir: discoveryPath,
|
|
435
|
+
});
|
|
355
436
|
} catch (err) {
|
|
356
437
|
// A broken parser must not kill SBOM generation
|
|
357
438
|
console.warn(
|
|
@@ -383,10 +464,9 @@ export function addFormulationSection(filePath, options, context = {}) {
|
|
|
383
464
|
components = components.concat(ciComponents);
|
|
384
465
|
}
|
|
385
466
|
|
|
386
|
-
const readmeSecurityComponents =
|
|
387
|
-
|
|
388
|
-
options
|
|
389
|
-
);
|
|
467
|
+
const readmeSecurityComponents = describesLiveHost
|
|
468
|
+
? []
|
|
469
|
+
: buildReadmeSecurityComponents(discoveryPath, options);
|
|
390
470
|
if (readmeSecurityComponents.length) {
|
|
391
471
|
components = components.concat(readmeSecurityComponents);
|
|
392
472
|
}
|
|
@@ -405,7 +485,9 @@ export function addFormulationSection(filePath, options, context = {}) {
|
|
|
405
485
|
!ENV_BLOCKLIST.some((b) => value.toLowerCase().includes(b)) &&
|
|
406
486
|
value.length
|
|
407
487
|
) {
|
|
408
|
-
|
|
488
|
+
// A registry or mirror URL can carry a password before `@` or a
|
|
489
|
+
// token in its query string, so only the scheme, host and path stay.
|
|
490
|
+
environmentVars.push({ name: aevar, value: stripUrlCredentials(value) });
|
|
409
491
|
}
|
|
410
492
|
}
|
|
411
493
|
|
package/lib/inventory/hbom.js
CHANGED
|
@@ -8,6 +8,9 @@ import {
|
|
|
8
8
|
readEnvironmentVariable,
|
|
9
9
|
recordActivity,
|
|
10
10
|
} from "../core/activity.js";
|
|
11
|
+
import { thoughtLog } from "../core/logger.js";
|
|
12
|
+
import { isCycloneDx20SpecVersion } from "./bomUtils.js";
|
|
13
|
+
import { readCycloneDx20 } from "./cyclonedx20.js";
|
|
11
14
|
import { getHbomCommandDiagnosticSummary } from "./hbomAnalysis.js";
|
|
12
15
|
import { importHbomModule } from "./hbomLoader.js";
|
|
13
16
|
import { resolveCdxgenPlugins, resolvePluginBinary } from "./plugins.js";
|
|
@@ -411,8 +414,9 @@ export function ensureSupportedHbomSpecVersion(specVersion) {
|
|
|
411
414
|
if (specVersion === undefined || specVersion === null || specVersion === "") {
|
|
412
415
|
return;
|
|
413
416
|
}
|
|
414
|
-
|
|
415
|
-
|
|
417
|
+
// cdxgen renders 2.0 itself from the 1.7 collector output.
|
|
418
|
+
if (Number(specVersion) !== 1.7 && Number(specVersion) !== 2) {
|
|
419
|
+
throw new Error("HBOM generation supports only CycloneDX 1.7 and 2.0.");
|
|
416
420
|
}
|
|
417
421
|
}
|
|
418
422
|
|
|
@@ -474,6 +478,10 @@ export function normalizeHbomOptions(options = {}) {
|
|
|
474
478
|
includeSensitiveIdentifiers:
|
|
475
479
|
options.includeSensitiveIdentifiers ?? options.sensitive ?? false,
|
|
476
480
|
platform: options.platform,
|
|
481
|
+
// Always 1.7: cdxgen's own pipeline (postProcess and its 2.0 renderer)
|
|
482
|
+
// turns the 1.7 collector output into 2.0, the way it renders every
|
|
483
|
+
// other BOM. A cdx-hbom that can emit 2.0 itself is never asked to.
|
|
484
|
+
specVersion: "1.7",
|
|
477
485
|
timeoutMs:
|
|
478
486
|
Number.isNaN(timeoutMs) || timeoutMs <= 0 ? undefined : timeoutMs,
|
|
479
487
|
};
|
|
@@ -722,6 +730,38 @@ export function addHbomAnalysisProperties(bomJson) {
|
|
|
722
730
|
return bomJson;
|
|
723
731
|
}
|
|
724
732
|
|
|
733
|
+
/**
|
|
734
|
+
* The 1.x-shaped model of a collector result. cdxgen asks the collector for
|
|
735
|
+
* 1.7; a collector that still answers with a CycloneDX 2.0 document (a
|
|
736
|
+
* `specFormat`, or specVersion 2.0) is read with readCycloneDx20, so the
|
|
737
|
+
* 1.x pipeline that follows never mistakes it for a 1.x BOM and loses its
|
|
738
|
+
* inventories.
|
|
739
|
+
*
|
|
740
|
+
* @param {object} collected What collectHardware returned
|
|
741
|
+
* @returns {object} The 1.x-shaped HBOM
|
|
742
|
+
*/
|
|
743
|
+
export function toInternalHbom(collected) {
|
|
744
|
+
if (
|
|
745
|
+
!collected ||
|
|
746
|
+
typeof collected !== "object" ||
|
|
747
|
+
!(collected.specFormat || isCycloneDx20SpecVersion(collected.specVersion))
|
|
748
|
+
) {
|
|
749
|
+
return collected;
|
|
750
|
+
}
|
|
751
|
+
const { internalBom, losses } = readCycloneDx20(collected);
|
|
752
|
+
if (losses.length) {
|
|
753
|
+
thoughtLog(
|
|
754
|
+
`The HBOM collector returned CycloneDX 2.0; reading it into the 1.x model reported ${losses.length} loss(es).`,
|
|
755
|
+
losses,
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
// The pipeline renders 2.0 again from the 1.x model, so the model says
|
|
759
|
+
// what the collector was asked for.
|
|
760
|
+
internalBom.specVersion = "1.7";
|
|
761
|
+
delete internalBom.specFormat;
|
|
762
|
+
return internalBom;
|
|
763
|
+
}
|
|
764
|
+
|
|
725
765
|
/**
|
|
726
766
|
* Generate an HBOM using the optional cdx-hbom package.
|
|
727
767
|
*
|
|
@@ -750,12 +790,11 @@ export async function createHbomDocument(options = {}) {
|
|
|
750
790
|
});
|
|
751
791
|
return createDryRunHbomDocument(options);
|
|
752
792
|
}
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
);
|
|
793
|
+
const collected = await hbomModule.collectHardware(normalizedOptions);
|
|
794
|
+
const bomJson = addHbomAnalysisProperties(toInternalHbom(collected));
|
|
756
795
|
if (isDryRun) {
|
|
757
796
|
recordHbomCollectorTrace(
|
|
758
|
-
hbomModule.getCollectorTrace?.(
|
|
797
|
+
hbomModule.getCollectorTrace?.(collected) ?? normalizedOptions.trace,
|
|
759
798
|
);
|
|
760
799
|
}
|
|
761
800
|
return bomJson;
|