@cdxgen/cdxgen 13.0.0 → 13.1.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 +26 -13
- package/bin/audit.js +6 -0
- package/bin/cdxgen.js +140 -8
- package/bin/convert.js +7 -0
- package/bin/evinse.js +1 -1
- package/bin/hbom.js +13 -4
- package/bin/tracebom.js +9 -1
- package/data/component-tags.json +8 -1
- package/data/frameworks-list.json +86 -1
- package/data/remediations.json +1144 -0
- package/data/rules/asar-archives.yaml +1 -1
- package/data/rules/build-fidelity.yaml +782 -0
- package/data/rules/package-integrity.yaml +13 -13
- package/lib/audit/index.js +48 -4
- package/lib/audit/scoring.js +2 -8
- package/lib/cli/bomAssembly.js +83 -15
- package/lib/cli/cliOptions.js +124 -7
- package/lib/cli/index.js +76 -48
- package/lib/cli/jsBom.js +32 -3
- package/lib/cli/jvmBom.js +1494 -1315
- package/lib/cli/managedBom.js +97 -28
- package/lib/cli/nativeBom.js +189 -10
- package/lib/core/buildLedger.js +799 -0
- package/lib/core/deferredExit.js +141 -0
- package/lib/core/env.js +69 -1
- package/lib/core/fs.js +60 -0
- package/lib/core/logger.js +76 -25
- package/lib/core/severity.js +16 -0
- package/lib/core/syncFileWriter.js +106 -0
- package/lib/core/ui.js +46 -2
- package/lib/ecosystems/ecosystems.js +9 -0
- package/lib/ecosystems/gradleutils.js +355 -15
- package/lib/ecosystems/parsers-js.js +26 -0
- package/lib/ecosystems/parsers-jvm.js +9 -0
- package/lib/ecosystems/parsers-misc.js +16 -2
- package/lib/ecosystems/parsers-python.js +171 -35
- package/lib/ecosystems/parsers-rust.js +12 -2
- package/lib/ecosystems/pypiClassifiers.js +326 -0
- package/lib/ecosystems/pythonutils.js +49 -1
- package/lib/ecosystems/rubyutils.js +13 -0
- package/lib/ecosystems/utils.js +9 -1
- package/lib/evinser/evinser.js +30 -12
- package/lib/helpers/remote/dependency-track.js +81 -14
- package/lib/inventory/auditCategories.js +9 -0
- package/lib/inventory/depsUtils.js +72 -0
- package/lib/inventory/dosai.js +431 -22
- package/lib/inventory/envcontext.js +211 -19
- package/lib/inventory/fetchBatch.js +57 -0
- package/lib/inventory/jvmToolEnv.js +593 -0
- package/lib/inventory/osinfo.js +123 -16
- package/lib/inventory/table.js +9 -5
- package/lib/inventory/toolRequirements.js +903 -0
- package/lib/managers/binary.js +216 -40
- package/lib/managers/docker.js +26 -5
- package/lib/server/server.js +1 -1
- package/lib/stages/postgen/annotationBuilder.js +97 -0
- package/lib/stages/postgen/auditBom.js +76 -79
- package/lib/stages/postgen/introspection/annotate.js +320 -0
- package/lib/stages/postgen/introspection/commandFacts.js +293 -0
- package/lib/stages/postgen/introspection/emit.js +192 -0
- package/lib/stages/postgen/introspection/formulationEvidence.js +427 -0
- package/lib/stages/postgen/introspection/markers.js +227 -0
- package/lib/stages/postgen/introspection/reflect.js +1214 -0
- package/lib/stages/postgen/introspection/report.js +1208 -0
- package/lib/stages/postgen/introspection/score.js +1067 -0
- package/lib/stages/postgen/introspection/shapeCommand.js +304 -0
- package/lib/stages/postgen/parentEdge.js +156 -0
- package/lib/stages/postgen/postgen.js +115 -2
- package/lib/stages/postgen/ruleEngine.js +17 -0
- package/lib/stages/pregen/envAudit.js +42 -2
- package/lib/stages/pregen/pregen.js +1171 -6
- package/lib/validator/bomValidator.js +11 -2
- package/package.json +14 -14
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/audit/scoring.d.ts +2 -8
- package/types/lib/audit/scoring.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +14 -0
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +2 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +8 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts.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/buildLedger.d.ts +272 -0
- package/types/lib/core/buildLedger.d.ts.map +1 -0
- package/types/lib/core/deferredExit.d.ts +84 -0
- package/types/lib/core/deferredExit.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +22 -1
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +21 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/logger.d.ts +14 -1
- package/types/lib/core/logger.d.ts.map +1 -1
- package/types/lib/core/severity.d.ts +11 -0
- package/types/lib/core/severity.d.ts.map +1 -0
- package/types/lib/core/syncFileWriter.d.ts +33 -0
- package/types/lib/core/syncFileWriter.d.ts.map +1 -0
- package/types/lib/core/ui.d.ts +1 -1
- package/types/lib/core/ui.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +33 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- 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/pypiClassifiers.d.ts +80 -0
- package/types/lib/ecosystems/pypiClassifiers.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +20 -0
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/utils.d.ts +4 -4
- package/types/lib/ecosystems/utils.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +5 -1
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/remote/dependency-track.d.ts +45 -4
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +8 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +20 -0
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +9 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +189 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -0
- package/types/lib/inventory/osinfo.d.ts +83 -2
- package/types/lib/inventory/osinfo.d.ts.map +1 -1
- package/types/lib/inventory/table.d.ts +4 -1
- package/types/lib/inventory/table.d.ts.map +1 -1
- package/types/lib/inventory/toolRequirements.d.ts +216 -0
- package/types/lib/inventory/toolRequirements.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts +71 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotationBuilder.d.ts +53 -0
- package/types/lib/stages/postgen/annotationBuilder.d.ts.map +1 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/introspection/annotate.d.ts +81 -0
- package/types/lib/stages/postgen/introspection/annotate.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts +32 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts +71 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts +154 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts +57 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts +77 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/report.d.ts +85 -0
- package/types/lib/stages/postgen/introspection/report.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/score.d.ts +149 -0
- package/types/lib/stages/postgen/introspection/score.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts +79 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts.map +1 -0
- package/types/lib/stages/postgen/parentEdge.d.ts +35 -0
- package/types/lib/stages/postgen/parentEdge.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +2 -2
- 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/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +131 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
package/lib/cli/managedBom.js
CHANGED
|
@@ -6,6 +6,8 @@ import process from "node:process";
|
|
|
6
6
|
import { build } from "@cdxgen/cdx-purl";
|
|
7
7
|
|
|
8
8
|
import { DEBUG_MODE, readEnvironmentVariable } from "../core/activity.js";
|
|
9
|
+
import { recordDegradation } from "../core/buildLedger.js";
|
|
10
|
+
import { deferFailOnError } from "../core/deferredExit.js";
|
|
9
11
|
import {
|
|
10
12
|
DOTNET_CMD,
|
|
11
13
|
hasAnyProjectType,
|
|
@@ -94,6 +96,7 @@ import {
|
|
|
94
96
|
trimComponents,
|
|
95
97
|
} from "../inventory/depsUtils.js";
|
|
96
98
|
import {
|
|
99
|
+
collectDosaiServiceComponents,
|
|
97
100
|
collectDosaiServicesFromMethods,
|
|
98
101
|
createDosaiMethodsSlice,
|
|
99
102
|
isDosaiDotnetLanguage,
|
|
@@ -102,6 +105,7 @@ import {
|
|
|
102
105
|
} from "../inventory/dosai.js";
|
|
103
106
|
import { addEvidenceForDotnet } from "../inventory/evidenceUtils.js";
|
|
104
107
|
import { nugetPurl, pypiBomRef } from "../inventory/purl.js";
|
|
108
|
+
import { classifyProbeResult } from "../inventory/toolRequirements.js";
|
|
105
109
|
import { getPluginToolComponents } from "../managers/binary.js";
|
|
106
110
|
import { getTreeWithPlugin } from "../managers/piptree.js";
|
|
107
111
|
import {
|
|
@@ -408,7 +412,11 @@ export async function createPythonBom(path, options) {
|
|
|
408
412
|
}
|
|
409
413
|
let retMap = await parsePyLockData(lockData, f);
|
|
410
414
|
if (retMap?.workspaceWarningShown) {
|
|
411
|
-
options
|
|
415
|
+
deferFailOnError(options, {
|
|
416
|
+
ecosystem: "python",
|
|
417
|
+
tool: "python",
|
|
418
|
+
detail: "the python lock file could not be parsed",
|
|
419
|
+
});
|
|
412
420
|
}
|
|
413
421
|
if (retMap?.pyLockProperties?.length) {
|
|
414
422
|
parentComponent.properties = parentComponent.properties || [];
|
|
@@ -458,15 +466,6 @@ export async function createPythonBom(path, options) {
|
|
|
458
466
|
retMap.dependenciesList,
|
|
459
467
|
parentComponent,
|
|
460
468
|
);
|
|
461
|
-
if (retMap.rootList) {
|
|
462
|
-
const parentDependsOn = new Set();
|
|
463
|
-
for (const p of retMap.rootList)
|
|
464
|
-
parentDependsOn.add(pypiBomRef(p.name, p.version));
|
|
465
|
-
dependencies.splice(0, 0, {
|
|
466
|
-
ref: parentComponent["bom-ref"],
|
|
467
|
-
dependsOn: [...parentDependsOn].sort(),
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
469
|
} else {
|
|
471
470
|
let exportedReqs = "";
|
|
472
471
|
if (f.endsWith("poetry.lock")) {
|
|
@@ -493,6 +492,11 @@ export async function createPythonBom(path, options) {
|
|
|
493
492
|
exportedReqs = expCmd.stdout.toString();
|
|
494
493
|
}
|
|
495
494
|
if (exportedReqs) {
|
|
495
|
+
// The export contains the resolved closure, not just the first
|
|
496
|
+
// level, so it contributes components only. The root edge comes
|
|
497
|
+
// from parsePyLockData (seeded from the manifest); for locks
|
|
498
|
+
// without a neighbouring pyproject.toml the generic postgen
|
|
499
|
+
// parent-edge repair keeps the tree reachable.
|
|
496
500
|
const tmpReqFile = join(
|
|
497
501
|
tempDir,
|
|
498
502
|
`exported-${basename(basePath)}-reqs.txt`,
|
|
@@ -501,13 +505,6 @@ export async function createPythonBom(path, options) {
|
|
|
501
505
|
const dlist = await parseReqFile(tmpReqFile, false);
|
|
502
506
|
if (dlist?.length) {
|
|
503
507
|
pkgList = pkgList.concat(dlist);
|
|
504
|
-
const parentDependsOn = new Set();
|
|
505
|
-
for (const p of dlist)
|
|
506
|
-
parentDependsOn.add(pypiBomRef(p.name, p.version));
|
|
507
|
-
dependencies.splice(0, 0, {
|
|
508
|
-
ref: parentComponent["bom-ref"],
|
|
509
|
-
dependsOn: [...parentDependsOn].sort(),
|
|
510
|
-
});
|
|
511
508
|
}
|
|
512
509
|
}
|
|
513
510
|
}
|
|
@@ -577,14 +574,30 @@ export async function createPythonBom(path, options) {
|
|
|
577
574
|
safeSpawnSync("pipenv", ["install"], { cwd: path, shell: isWin });
|
|
578
575
|
const piplockFile = join(path, "Pipfile.lock");
|
|
579
576
|
if (safeExistsSync(piplockFile)) {
|
|
580
|
-
|
|
581
|
-
|
|
577
|
+
let dlist;
|
|
578
|
+
try {
|
|
579
|
+
const lockData = JSON.parse(readFileSync(piplockFile));
|
|
580
|
+
dlist = await parsePiplockData(lockData);
|
|
581
|
+
} catch (err) {
|
|
582
|
+
recordDegradation("python.lockfile-unparseable.pipenv", {
|
|
583
|
+
ecosystem: "python",
|
|
584
|
+
impact: "transitive-deps",
|
|
585
|
+
path: piplockFile,
|
|
586
|
+
detail:
|
|
587
|
+
"The Pipfile.lock could not be parsed, so no locked versions were captured from it.",
|
|
588
|
+
});
|
|
589
|
+
console.log(`Error while parsing the lock file ${piplockFile}.`, err);
|
|
590
|
+
}
|
|
582
591
|
if (dlist?.length) {
|
|
583
592
|
pkgList = pkgList.concat(dlist);
|
|
584
593
|
}
|
|
585
594
|
} else {
|
|
586
595
|
console.error("Pipfile.lock not found at", path);
|
|
587
|
-
options
|
|
596
|
+
deferFailOnError(options, {
|
|
597
|
+
ecosystem: "python",
|
|
598
|
+
tool: "pipenv",
|
|
599
|
+
detail: "Pipfile.lock was not found",
|
|
600
|
+
});
|
|
588
601
|
}
|
|
589
602
|
} else if (reqDirFiles?.length) {
|
|
590
603
|
for (const j in reqDirFiles) {
|
|
@@ -1087,13 +1100,33 @@ export function createPHPBom(path, options) {
|
|
|
1087
1100
|
shell: isWin,
|
|
1088
1101
|
});
|
|
1089
1102
|
if (versionResult.status !== 0 || versionResult.error) {
|
|
1103
|
+
// A composer the runtime or a policy denied is not an unavailable
|
|
1104
|
+
// composer; the version probes report that as degraded evidence.
|
|
1105
|
+
if (classifyProbeResult(versionResult) === "missing") {
|
|
1106
|
+
recordDegradation("php.composer.missing", {
|
|
1107
|
+
ecosystem: "php",
|
|
1108
|
+
tool: "composer",
|
|
1109
|
+
impact: "components",
|
|
1110
|
+
command: "composer --version",
|
|
1111
|
+
exitCode:
|
|
1112
|
+
typeof versionResult.status === "number"
|
|
1113
|
+
? versionResult.status
|
|
1114
|
+
: undefined,
|
|
1115
|
+
detail:
|
|
1116
|
+
"The composer executable could not be run, so the composer.lock generation could not be attempted.",
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1090
1119
|
console.error(
|
|
1091
1120
|
"No composer version found. Check if composer is installed and available in PATH.",
|
|
1092
1121
|
);
|
|
1093
1122
|
if (DEBUG_MODE) {
|
|
1094
1123
|
console.log(versionResult.error, versionResult.stderr);
|
|
1095
1124
|
}
|
|
1096
|
-
options
|
|
1125
|
+
deferFailOnError(options, {
|
|
1126
|
+
ecosystem: "php",
|
|
1127
|
+
tool: "composer",
|
|
1128
|
+
detail: "the composer version command failed",
|
|
1129
|
+
});
|
|
1097
1130
|
}
|
|
1098
1131
|
let composerVersion;
|
|
1099
1132
|
if (DEBUG_MODE) {
|
|
@@ -1129,7 +1162,11 @@ export function createPHPBom(path, options) {
|
|
|
1129
1162
|
console.log(result.stderr);
|
|
1130
1163
|
}
|
|
1131
1164
|
console.log(result.error);
|
|
1132
|
-
options
|
|
1165
|
+
deferFailOnError(options, {
|
|
1166
|
+
ecosystem: "php",
|
|
1167
|
+
tool: "composer",
|
|
1168
|
+
detail: "the composer install command failed",
|
|
1169
|
+
});
|
|
1133
1170
|
}
|
|
1134
1171
|
}
|
|
1135
1172
|
}
|
|
@@ -1206,6 +1243,15 @@ export function createPHPBom(path, options) {
|
|
|
1206
1243
|
parentComponent,
|
|
1207
1244
|
});
|
|
1208
1245
|
}
|
|
1246
|
+
if (composerJsonMode && !composerLockMode) {
|
|
1247
|
+
recordDegradation("php.no-lockfile", {
|
|
1248
|
+
ecosystem: "php",
|
|
1249
|
+
tool: "composer",
|
|
1250
|
+
impact: "components",
|
|
1251
|
+
detail:
|
|
1252
|
+
"No composer.lock was found or generated, and the lock file is the source the Composer parser reads.",
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1209
1255
|
return {};
|
|
1210
1256
|
}
|
|
1211
1257
|
|
|
@@ -1319,7 +1365,11 @@ export async function createRubyBom(path, options) {
|
|
|
1319
1365
|
"Bundle install has failed. Check if bundle is installed and available in PATH.",
|
|
1320
1366
|
);
|
|
1321
1367
|
console.log(result.error, result.stderr);
|
|
1322
|
-
options
|
|
1368
|
+
deferFailOnError(options, {
|
|
1369
|
+
ecosystem: "ruby",
|
|
1370
|
+
tool: "bundler",
|
|
1371
|
+
detail: "the bundler command failed",
|
|
1372
|
+
});
|
|
1323
1373
|
}
|
|
1324
1374
|
}
|
|
1325
1375
|
}
|
|
@@ -1361,7 +1411,11 @@ export async function createRubyBom(path, options) {
|
|
|
1361
1411
|
console.log(
|
|
1362
1412
|
"SBOM generation using only gemspec files is imprecise and results in an inaccurate dependency tree.",
|
|
1363
1413
|
);
|
|
1364
|
-
options
|
|
1414
|
+
deferFailOnError(options, {
|
|
1415
|
+
ecosystem: "ruby",
|
|
1416
|
+
tool: "bundler",
|
|
1417
|
+
detail: "only gemspec files were available to parse",
|
|
1418
|
+
});
|
|
1365
1419
|
}
|
|
1366
1420
|
for (const f of gemspecFiles) {
|
|
1367
1421
|
const gemspecData = readFileSync(f, { encoding: "utf-8" });
|
|
@@ -1634,7 +1688,11 @@ export async function createCsharpBom(path, options) {
|
|
|
1634
1688
|
"TIP: msbuild is not available for Linux. Try using a Windows build agent to generate an SBOM for this project.",
|
|
1635
1689
|
);
|
|
1636
1690
|
}
|
|
1637
|
-
options
|
|
1691
|
+
deferFailOnError(options, {
|
|
1692
|
+
ecosystem: "csharp",
|
|
1693
|
+
tool: "dotnet",
|
|
1694
|
+
detail: "the dotnet command failed",
|
|
1695
|
+
});
|
|
1638
1696
|
}
|
|
1639
1697
|
if (result?.stderr?.includes("To install missing framework")) {
|
|
1640
1698
|
console.log(
|
|
@@ -1683,7 +1741,11 @@ export async function createCsharpBom(path, options) {
|
|
|
1683
1741
|
console.log(result.stdout);
|
|
1684
1742
|
}
|
|
1685
1743
|
console.log("---------");
|
|
1686
|
-
options
|
|
1744
|
+
deferFailOnError(options, {
|
|
1745
|
+
ecosystem: "csharp",
|
|
1746
|
+
tool: "dotnet",
|
|
1747
|
+
detail: "the dotnet restore did not produce a usable assets file",
|
|
1748
|
+
});
|
|
1687
1749
|
}
|
|
1688
1750
|
}
|
|
1689
1751
|
// Collect the assets, lock, and nupkg files generated from restore
|
|
@@ -1786,7 +1848,11 @@ export async function createCsharpBom(path, options) {
|
|
|
1786
1848
|
console.log(
|
|
1787
1849
|
"Alternatively, try using the custom `ghcr.io/cdxgen/cdxgen-dotnet:v13` container image, which bundles a range of dotnet SDKs.",
|
|
1788
1850
|
);
|
|
1789
|
-
options
|
|
1851
|
+
deferFailOnError(options, {
|
|
1852
|
+
ecosystem: "csharp",
|
|
1853
|
+
tool: "dotnet",
|
|
1854
|
+
detail: "no dotnet dependency evidence could be collected",
|
|
1855
|
+
});
|
|
1790
1856
|
}
|
|
1791
1857
|
}
|
|
1792
1858
|
// packages.lock.json parsing for any project without a project.assets.json
|
|
@@ -1960,7 +2026,10 @@ export async function createCsharpBom(path, options) {
|
|
|
1960
2026
|
}
|
|
1961
2027
|
pkgList = addEvidenceForDotnet(pkgList, slicesFile);
|
|
1962
2028
|
const methodsSlice = readDosaiJsonFile(slicesFile);
|
|
1963
|
-
|
|
2029
|
+
// Provider-backed Services[] first so bom-refs, trust zones, and data
|
|
2030
|
+
// classifications survive; ApiEndpoints derivation fills in the rest.
|
|
2031
|
+
const servicesMap = collectDosaiServiceComponents(methodsSlice, {});
|
|
2032
|
+
collectDosaiServicesFromMethods(methodsSlice, servicesMap);
|
|
1964
2033
|
services = normalizeDosaiServiceMap(servicesMap);
|
|
1965
2034
|
}
|
|
1966
2035
|
}
|
package/lib/cli/nativeBom.js
CHANGED
|
@@ -8,6 +8,8 @@ import { build } from "@cdxgen/cdx-purl";
|
|
|
8
8
|
import { parse as loadYaml } from "yaml";
|
|
9
9
|
|
|
10
10
|
import { DEBUG_MODE, readEnvironmentVariable } from "../core/activity.js";
|
|
11
|
+
import { recordDegradation } from "../core/buildLedger.js";
|
|
12
|
+
import { deferFailOnError } from "../core/deferredExit.js";
|
|
11
13
|
import {
|
|
12
14
|
CARGO_CMD,
|
|
13
15
|
CLJ_CMD,
|
|
@@ -86,6 +88,10 @@ import { resolveZigGraph } from "../ecosystems/zigResolver.js";
|
|
|
86
88
|
import { mergeDependencies, trimComponents } from "../inventory/depsUtils.js";
|
|
87
89
|
import { convertOSQueryResults } from "../inventory/evidenceUtils.js";
|
|
88
90
|
import { applyPurl, nixBomRef, tryBuildPurl } from "../inventory/purl.js";
|
|
91
|
+
import {
|
|
92
|
+
classifyProbeResult,
|
|
93
|
+
classifySpawnRestriction,
|
|
94
|
+
} from "../inventory/toolRequirements.js";
|
|
89
95
|
import { executeOsQuery } from "../managers/binary.js";
|
|
90
96
|
import {
|
|
91
97
|
buildBomNSData,
|
|
@@ -357,13 +363,34 @@ export async function createGoBom(path, options) {
|
|
|
357
363
|
// So we support a fallback to only operate with go mod graph command output in such instances
|
|
358
364
|
console.log("go list -deps command has failed for", basePath);
|
|
359
365
|
shouldManuallyParse = true;
|
|
366
|
+
// A spawn the runtime or a policy denied is neither a missing
|
|
367
|
+
// toolchain nor an engaged fallback; the version probes report
|
|
368
|
+
// that as degraded evidence and the policy entries name the fix.
|
|
369
|
+
if (
|
|
370
|
+
!classifySpawnRestriction("go") &&
|
|
371
|
+
result.error &&
|
|
372
|
+
classifyProbeResult(result) === "missing"
|
|
373
|
+
) {
|
|
374
|
+
recordDegradation("go.toolchain.missing", {
|
|
375
|
+
ecosystem: "go",
|
|
376
|
+
tool: "go",
|
|
377
|
+
impact: "transitive-deps",
|
|
378
|
+
command: "go list -deps ./...",
|
|
379
|
+
detail:
|
|
380
|
+
"The go executable could not be run, so the module graph was not resolved.",
|
|
381
|
+
});
|
|
382
|
+
}
|
|
360
383
|
if (DEBUG_MODE && result.stdout) {
|
|
361
384
|
console.log(result.stdout);
|
|
362
385
|
}
|
|
363
386
|
if (DEBUG_MODE && result.stderr) {
|
|
364
387
|
console.log(result.stderr);
|
|
365
388
|
}
|
|
366
|
-
options
|
|
389
|
+
deferFailOnError(options, {
|
|
390
|
+
ecosystem: "go",
|
|
391
|
+
tool: "go",
|
|
392
|
+
detail: "the go module graph command failed",
|
|
393
|
+
});
|
|
367
394
|
}
|
|
368
395
|
const stdout = result.stdout;
|
|
369
396
|
if (stdout) {
|
|
@@ -411,7 +438,11 @@ export async function createGoBom(path, options) {
|
|
|
411
438
|
if (DEBUG_MODE && result.stderr) {
|
|
412
439
|
console.log(result.stderr);
|
|
413
440
|
}
|
|
414
|
-
options
|
|
441
|
+
deferFailOnError(options, {
|
|
442
|
+
ecosystem: "go",
|
|
443
|
+
tool: "go",
|
|
444
|
+
detail: "the go list command failed",
|
|
445
|
+
});
|
|
415
446
|
}
|
|
416
447
|
if (result.stdout) {
|
|
417
448
|
cmdOutput = Buffer.from(result.stdout).toString();
|
|
@@ -492,7 +523,11 @@ export async function createGoBom(path, options) {
|
|
|
492
523
|
console.log(
|
|
493
524
|
"3. Alternatively, consider generating a post-build SBOM from the built binary using blint. Use the official container image and invoke cdxgen with the arguments `-t binary --lifecycle post-build`.",
|
|
494
525
|
);
|
|
495
|
-
options
|
|
526
|
+
deferFailOnError(options, {
|
|
527
|
+
ecosystem: "go",
|
|
528
|
+
tool: "go",
|
|
529
|
+
detail: "no go dependency evidence could be collected",
|
|
530
|
+
});
|
|
496
531
|
}
|
|
497
532
|
}
|
|
498
533
|
}
|
|
@@ -510,6 +545,19 @@ export async function createGoBom(path, options) {
|
|
|
510
545
|
if (
|
|
511
546
|
!hasAnyProjectType(["docker", "oci", "container", "os"], options, false)
|
|
512
547
|
) {
|
|
548
|
+
// When the environment itself denies the go command, the version
|
|
549
|
+
// probes record the restriction as degraded evidence and the policy
|
|
550
|
+
// entries name the fix; the fallback is the finding only when the
|
|
551
|
+
// toolchain was free to run and could not resolve the graph.
|
|
552
|
+
if (!classifySpawnRestriction("go")) {
|
|
553
|
+
recordDegradation("go.mod-only-fallback", {
|
|
554
|
+
ecosystem: "go",
|
|
555
|
+
tool: "go",
|
|
556
|
+
impact: "transitive-deps",
|
|
557
|
+
detail:
|
|
558
|
+
"The go toolchain could not resolve the module graph, so go.mod was parsed manually and components carry only the declared requires.",
|
|
559
|
+
});
|
|
560
|
+
}
|
|
513
561
|
console.log(
|
|
514
562
|
"Manually parsing go.mod files. The resultant BOM would be incomplete.",
|
|
515
563
|
);
|
|
@@ -584,6 +632,31 @@ export async function createGoBom(path, options) {
|
|
|
584
632
|
return {};
|
|
585
633
|
}
|
|
586
634
|
|
|
635
|
+
/**
|
|
636
|
+
* Whether the cargo executable answers a version probe.
|
|
637
|
+
*
|
|
638
|
+
* A cargo command run through a shell — the Windows path — reports an absent
|
|
639
|
+
* executable as a non-zero exit with the shell's own complaint on stderr, not
|
|
640
|
+
* as a spawn error, so a failed build alone cannot tell a missing toolchain
|
|
641
|
+
* from a broken project. The probe answers that question directly.
|
|
642
|
+
*
|
|
643
|
+
* @param {string} cwd Directory to probe from.
|
|
644
|
+
* @returns {boolean} True when cargo answered.
|
|
645
|
+
*/
|
|
646
|
+
function cargoAnswersVersion(cwd) {
|
|
647
|
+
const probe = safeSpawnSync(CARGO_CMD, ["--version"], {
|
|
648
|
+
cdxgenActivity: {
|
|
649
|
+
kind: "probe",
|
|
650
|
+
metadata: { tool: "cargo" },
|
|
651
|
+
probeType: "build-tool-availability",
|
|
652
|
+
},
|
|
653
|
+
cwd,
|
|
654
|
+
encoding: "utf-8",
|
|
655
|
+
shell: isWin,
|
|
656
|
+
});
|
|
657
|
+
return classifyProbeResult(probe) === "found";
|
|
658
|
+
}
|
|
659
|
+
|
|
587
660
|
/**
|
|
588
661
|
* Function to create bom string for Rust projects
|
|
589
662
|
*
|
|
@@ -649,7 +722,27 @@ export async function createRustBom(path, options) {
|
|
|
649
722
|
if (cargoInstallResult.status !== 0 || cargoInstallResult.error) {
|
|
650
723
|
console.error("Error running the cargo command");
|
|
651
724
|
console.log(cargoInstallResult.error, cargoInstallResult.stderr);
|
|
652
|
-
|
|
725
|
+
// A spawn the runtime or a policy denied is neither a missing
|
|
726
|
+
// toolchain nor an engaged fallback; the version probes record
|
|
727
|
+
// that as degraded evidence.
|
|
728
|
+
const toolchainMissing = cargoInstallResult.error
|
|
729
|
+
? classifyProbeResult(cargoInstallResult) === "missing"
|
|
730
|
+
: !cargoAnswersVersion(basePath);
|
|
731
|
+
if (!classifySpawnRestriction(CARGO_CMD) && toolchainMissing) {
|
|
732
|
+
recordDegradation("rust.toolchain.missing", {
|
|
733
|
+
ecosystem: "rust",
|
|
734
|
+
tool: "cargo",
|
|
735
|
+
impact: "transitive-deps",
|
|
736
|
+
command: `${basename(CARGO_CMD)} ${cargoArgs.join(" ")}`,
|
|
737
|
+
detail:
|
|
738
|
+
"The cargo executable could not be run, so the lock file could not be generated.",
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
deferFailOnError(options, {
|
|
742
|
+
ecosystem: "rust",
|
|
743
|
+
tool: "cargo",
|
|
744
|
+
detail: "the cargo command failed",
|
|
745
|
+
});
|
|
653
746
|
}
|
|
654
747
|
}
|
|
655
748
|
}
|
|
@@ -667,6 +760,15 @@ export async function createRustBom(path, options) {
|
|
|
667
760
|
options,
|
|
668
761
|
);
|
|
669
762
|
const cargoLockMode = cargoLockFiles.length;
|
|
763
|
+
if (cargoFiles.length && !cargoLockMode) {
|
|
764
|
+
recordDegradation("rust.cargo-lock-missing", {
|
|
765
|
+
ecosystem: "rust",
|
|
766
|
+
tool: "cargo",
|
|
767
|
+
impact: "transitive-deps",
|
|
768
|
+
detail:
|
|
769
|
+
"No Cargo.lock was found beside the manifest, so components carry the declared dependencies without locked versions or the resolved crate graph.",
|
|
770
|
+
});
|
|
771
|
+
}
|
|
670
772
|
for (const f of cargoFiles) {
|
|
671
773
|
if (DEBUG_MODE) {
|
|
672
774
|
console.log(`Parsing ${f}`);
|
|
@@ -868,6 +970,15 @@ export async function createDartBom(path, options) {
|
|
|
868
970
|
`${options.multiProject ? "**/" : ""}pubspec.yaml`,
|
|
869
971
|
options,
|
|
870
972
|
);
|
|
973
|
+
if (pubSpecYamlFiles.length && !pubFiles.length) {
|
|
974
|
+
recordDegradation("dart.pub-get-needed", {
|
|
975
|
+
ecosystem: "dart",
|
|
976
|
+
tool: "dart",
|
|
977
|
+
impact: "versions",
|
|
978
|
+
detail:
|
|
979
|
+
"pubspec.yaml was parsed without pubspec.lock, so components carry version constraints instead of the locked versions.",
|
|
980
|
+
});
|
|
981
|
+
}
|
|
871
982
|
let dependencies = [];
|
|
872
983
|
let pkgList = [];
|
|
873
984
|
let parentComponent;
|
|
@@ -1367,8 +1478,22 @@ export function createClojureBom(path, options) {
|
|
|
1367
1478
|
if (result.status !== 0 || result.error) {
|
|
1368
1479
|
if (result.stderr) {
|
|
1369
1480
|
console.error(result.stdout, result.stderr);
|
|
1370
|
-
options
|
|
1481
|
+
deferFailOnError(options, {
|
|
1482
|
+
ecosystem: "clojure",
|
|
1483
|
+
tool: "lein",
|
|
1484
|
+
detail: "the leiningen command failed",
|
|
1485
|
+
});
|
|
1371
1486
|
}
|
|
1487
|
+
recordDegradation("clojure.lein.missing", {
|
|
1488
|
+
ecosystem: "clojure",
|
|
1489
|
+
tool: "lein",
|
|
1490
|
+
impact: "transitive-deps",
|
|
1491
|
+
command: `${LEIN_CMD} ${LEIN_ARGS.join(" ")}`,
|
|
1492
|
+
exitCode:
|
|
1493
|
+
typeof result.status === "number" ? result.status : undefined,
|
|
1494
|
+
detail:
|
|
1495
|
+
"lein did not produce a dependency list, so the project.clj manifest is parsed manually.",
|
|
1496
|
+
});
|
|
1372
1497
|
console.log(
|
|
1373
1498
|
"Check if the correct version of lein is installed and available in PATH. Falling back to manual parsing.",
|
|
1374
1499
|
);
|
|
@@ -1390,7 +1515,11 @@ export function createClojureBom(path, options) {
|
|
|
1390
1515
|
}
|
|
1391
1516
|
} else {
|
|
1392
1517
|
console.error("lein unexpectedly didn't return any output");
|
|
1393
|
-
options
|
|
1518
|
+
deferFailOnError(options, {
|
|
1519
|
+
ecosystem: "clojure",
|
|
1520
|
+
tool: "lein",
|
|
1521
|
+
detail: "the leiningen command produced no output",
|
|
1522
|
+
});
|
|
1394
1523
|
}
|
|
1395
1524
|
}
|
|
1396
1525
|
}
|
|
@@ -1413,8 +1542,22 @@ export function createClojureBom(path, options) {
|
|
|
1413
1542
|
if (result.status !== 0 || result.error) {
|
|
1414
1543
|
if (result.stderr) {
|
|
1415
1544
|
console.error(result.stdout, result.stderr);
|
|
1416
|
-
options
|
|
1545
|
+
deferFailOnError(options, {
|
|
1546
|
+
ecosystem: "clojure",
|
|
1547
|
+
tool: "clj",
|
|
1548
|
+
detail: "the clojure cli command failed",
|
|
1549
|
+
});
|
|
1417
1550
|
}
|
|
1551
|
+
recordDegradation("clojure.cli.missing", {
|
|
1552
|
+
ecosystem: "clojure",
|
|
1553
|
+
tool: "clojure",
|
|
1554
|
+
impact: "transitive-deps",
|
|
1555
|
+
command: `${CLJ_CMD} ${CLJ_ARGS.join(" ")}`,
|
|
1556
|
+
exitCode:
|
|
1557
|
+
typeof result.status === "number" ? result.status : undefined,
|
|
1558
|
+
detail:
|
|
1559
|
+
"The clojure CLI did not produce a dependency tree, so the deps.edn manifest is parsed manually.",
|
|
1560
|
+
});
|
|
1418
1561
|
console.log(
|
|
1419
1562
|
"Check if the correct version of clojure cli is installed and available in PATH. Falling back to manual parsing.",
|
|
1420
1563
|
);
|
|
@@ -1436,7 +1579,11 @@ export function createClojureBom(path, options) {
|
|
|
1436
1579
|
}
|
|
1437
1580
|
} else {
|
|
1438
1581
|
console.error("clj unexpectedly didn't return any output");
|
|
1439
|
-
options
|
|
1582
|
+
deferFailOnError(options, {
|
|
1583
|
+
ecosystem: "clojure",
|
|
1584
|
+
tool: "clj",
|
|
1585
|
+
detail: "the clojure cli command produced no output",
|
|
1586
|
+
});
|
|
1440
1587
|
}
|
|
1441
1588
|
}
|
|
1442
1589
|
}
|
|
@@ -1479,6 +1626,13 @@ export function createHaskellBom(path, options) {
|
|
|
1479
1626
|
filename: cabalFiles.join(", "),
|
|
1480
1627
|
});
|
|
1481
1628
|
}
|
|
1629
|
+
recordDegradation("haskell.freeze-missing", {
|
|
1630
|
+
ecosystem: "haskell",
|
|
1631
|
+
tool: "cabal",
|
|
1632
|
+
impact: "components",
|
|
1633
|
+
detail:
|
|
1634
|
+
"No cabal.project.freeze was found, and the freeze file is the source the parser reads for the package set.",
|
|
1635
|
+
});
|
|
1482
1636
|
return {};
|
|
1483
1637
|
}
|
|
1484
1638
|
|
|
@@ -1512,6 +1666,13 @@ export function createElixirBom(path, options) {
|
|
|
1512
1666
|
filename: mixFiles.join(", "),
|
|
1513
1667
|
});
|
|
1514
1668
|
}
|
|
1669
|
+
recordDegradation("elixir.deps-not-fetched", {
|
|
1670
|
+
ecosystem: "elixir",
|
|
1671
|
+
tool: "mix",
|
|
1672
|
+
impact: "components",
|
|
1673
|
+
detail:
|
|
1674
|
+
"No mix.lock was found, and the lock file written by fetching the dependencies is the source the parser reads.",
|
|
1675
|
+
});
|
|
1515
1676
|
return {};
|
|
1516
1677
|
}
|
|
1517
1678
|
|
|
@@ -1634,6 +1795,16 @@ export async function createSwiftBom(path, options) {
|
|
|
1634
1795
|
`It looks like the 'Sources' directory is missing, so we are missing the components and dependencies for '${basename(basePath)}'.`,
|
|
1635
1796
|
);
|
|
1636
1797
|
} else if (readEnvironmentVariable("CDXGEN_IN_CONTAINER") !== "true") {
|
|
1798
|
+
recordDegradation("swift.toolchain.missing", {
|
|
1799
|
+
ecosystem: "swift",
|
|
1800
|
+
tool: "swift",
|
|
1801
|
+
impact: "transitive-deps",
|
|
1802
|
+
command: `${swiftCommand} ${packageArgs.join(" ")}`,
|
|
1803
|
+
exitCode:
|
|
1804
|
+
typeof result.status === "number" ? result.status : undefined,
|
|
1805
|
+
detail:
|
|
1806
|
+
"The swift package command did not succeed, so the swift package graph was not resolved.",
|
|
1807
|
+
});
|
|
1637
1808
|
console.log(
|
|
1638
1809
|
"Consider using the cdxgen container image (`ghcr.io/cdxgen/cdxgen`), which includes Swift and additional build tools.",
|
|
1639
1810
|
);
|
|
@@ -1645,7 +1816,11 @@ export async function createSwiftBom(path, options) {
|
|
|
1645
1816
|
}
|
|
1646
1817
|
}
|
|
1647
1818
|
console.error(result.stderr);
|
|
1648
|
-
options
|
|
1819
|
+
deferFailOnError(options, {
|
|
1820
|
+
ecosystem: "swift",
|
|
1821
|
+
tool: "swift",
|
|
1822
|
+
detail: "the swift package command failed",
|
|
1823
|
+
});
|
|
1649
1824
|
}
|
|
1650
1825
|
}
|
|
1651
1826
|
}
|
|
@@ -1691,7 +1866,11 @@ export async function createCocoaBom(path, options) {
|
|
|
1691
1866
|
"No 'Podfile.lock' found and '--no-install-deps' is set -- A Podfile.lock is needed to parse dependencies!",
|
|
1692
1867
|
);
|
|
1693
1868
|
missingLockWarningShown = true;
|
|
1694
|
-
options
|
|
1869
|
+
deferFailOnError(options, {
|
|
1870
|
+
ecosystem: "cocoa",
|
|
1871
|
+
tool: "pod",
|
|
1872
|
+
detail: "no Podfile.lock was found",
|
|
1873
|
+
});
|
|
1695
1874
|
}
|
|
1696
1875
|
} else if (options.deep && options.installDeps) {
|
|
1697
1876
|
executePodCommand(["install"], projectPath, options);
|