@cdxgen/cdxgen 13.0.1 → 13.2.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 +30 -16
- package/bin/audit.js +6 -0
- package/bin/cdxgen.js +141 -7
- package/bin/evinse.js +3 -1
- package/data/component-tags.json +8 -1
- package/data/crypto-oid.json +88 -12
- package/data/frameworks-list.json +86 -1
- package/data/remediations.json +1167 -0
- package/data/rules/ai-bom.yaml +78 -0
- package/data/rules/asar-archives.yaml +1 -1
- package/data/rules/build-fidelity.yaml +782 -0
- package/data/rules/cargo-rust.yaml +132 -0
- package/data/rules/mcp-servers.yaml +122 -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/audit/targets.js +6 -1
- package/lib/cli/bomAssembly.js +32 -1
- package/lib/cli/cliOptions.js +94 -7
- package/lib/cli/index.js +268 -49
- package/lib/cli/jsBom.js +91 -5
- package/lib/cli/jvmBom.js +1515 -1314
- package/lib/cli/managedBom.js +249 -28
- package/lib/cli/nativeBom.js +986 -10
- package/lib/core/activity.js +120 -2
- package/lib/core/buildLedger.js +799 -0
- package/lib/core/deferredExit.js +141 -0
- package/lib/core/env.js +108 -2
- package/lib/core/fs.js +60 -0
- package/lib/core/logger.js +12 -99
- package/lib/core/severity.js +16 -0
- package/lib/core/syncFileWriter.js +106 -0
- package/lib/core/userAgent.js +151 -0
- package/lib/ecosystems/cargoCache.js +364 -0
- package/lib/ecosystems/caxa.js +13 -5
- package/lib/ecosystems/cppEvidence.js +40 -2
- package/lib/ecosystems/ecosystems.js +250 -91
- package/lib/ecosystems/golem.js +11 -1
- package/lib/ecosystems/gradleutils.js +498 -17
- package/lib/ecosystems/kosi.js +1256 -0
- package/lib/ecosystems/parsers-crystal.js +179 -0
- package/lib/ecosystems/parsers-dotnet.js +69 -39
- package/lib/ecosystems/parsers-elm.js +816 -0
- package/lib/ecosystems/parsers-gleam.js +1 -1
- package/lib/ecosystems/parsers-js.js +26 -0
- package/lib/ecosystems/parsers-julia.js +268 -0
- package/lib/ecosystems/parsers-jvm.js +9 -0
- package/lib/ecosystems/parsers-lua.js +218 -0
- package/lib/ecosystems/parsers-misc.js +188 -2
- package/lib/ecosystems/parsers-nim.js +221 -0
- package/lib/ecosystems/parsers-opam.js +180 -0
- package/lib/ecosystems/parsers-perl.js +277 -0
- package/lib/ecosystems/parsers-python.js +172 -36
- package/lib/ecosystems/parsers-r.js +148 -0
- package/lib/ecosystems/parsers-rebar.js +411 -0
- package/lib/ecosystems/parsers-rust.js +944 -19
- package/lib/ecosystems/parsers-spack.js +204 -0
- package/lib/ecosystems/parsers-stack.js +264 -0
- package/lib/ecosystems/parsers-terraform.js +194 -0
- package/lib/ecosystems/pypiClassifiers.js +326 -0
- package/lib/ecosystems/pythonutils.js +49 -1
- package/lib/ecosystems/rubyutils.js +13 -0
- package/lib/ecosystems/rusi.js +126 -6
- package/lib/evinser/evinser.js +141 -13
- package/lib/helpers/agenticToolutils.js +925 -0
- package/lib/helpers/remote/dependency-track.js +81 -14
- package/lib/inventory/agentFormulationParser.js +57 -4
- package/lib/inventory/aiCollector.js +504 -10
- package/lib/inventory/aiInventory.js +12 -0
- package/lib/inventory/analyzer.js +488 -30
- package/lib/inventory/astWorker.js +6 -0
- package/lib/inventory/auditCategories.js +17 -0
- package/lib/inventory/cbomutils.js +30 -0
- package/lib/inventory/cdxrs.js +7 -2
- package/lib/inventory/communityAiConfigParser.js +215 -2
- package/lib/inventory/depsUtils.js +72 -0
- package/lib/inventory/dosai.js +508 -22
- package/lib/inventory/envcontext.js +215 -24
- package/lib/inventory/evidenceUtils.js +13 -0
- package/lib/inventory/fetchBatch.js +131 -2
- package/lib/inventory/fetchRate.js +74 -11
- package/lib/inventory/forgeEnricher.js +3 -2
- package/lib/inventory/jvmToolEnv.js +594 -0
- package/lib/inventory/mcpConfigParser.js +166 -2
- package/lib/inventory/mcpDiscovery.js +11 -0
- package/lib/inventory/mcpServerManifest.js +389 -0
- package/lib/inventory/mcpbBundle.js +419 -0
- package/lib/inventory/osinfo.js +1 -0
- package/lib/inventory/plugins.js +3 -0
- package/lib/inventory/protobom.js +54 -88
- package/lib/inventory/protobomLoader.js +4 -0
- package/lib/inventory/rustFormulationParser.js +1 -1
- package/lib/inventory/source.js +10 -4
- package/lib/inventory/stylePackageRefs.js +41 -0
- package/lib/inventory/svelteUtils.js +524 -0
- package/lib/inventory/toolRequirements.js +903 -0
- package/lib/managers/binary.js +24 -0
- package/lib/managers/docker.js +26 -5
- package/lib/parsers/huggingfaceUtils.js +15 -7
- package/lib/parsers/onnx.js +294 -0
- package/lib/parsers/safetensors.js +114 -0
- 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 +250 -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 +284 -3
- 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/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
- package/lib/validator/bomValidator.js +11 -2
- package/package.json +26 -26
- 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/audit/targets.d.ts.map +1 -1
- 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 +10 -2
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +23 -0
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts +33 -0
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +88 -8
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/activity.d.ts.map +1 -1
- package/types/lib/core/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 +43 -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 +2 -11
- 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/userAgent.d.ts +56 -0
- package/types/lib/core/userAgent.d.ts.map +1 -0
- package/types/lib/ecosystems/cargoCache.d.ts +75 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +15 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +53 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +73 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
- package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
- package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-misc.d.ts +23 -0
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
- package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
- package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
- package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-r.d.ts +35 -0
- package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
- package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
- package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
- package/types/lib/ecosystems/pypiClassifiers.d.ts +80 -0
- package/types/lib/ecosystems/pypiClassifiers.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +21 -1
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +34 -1
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/agenticToolutils.d.ts +87 -0
- package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
- package/types/lib/helpers/remote/dependency-track.d.ts +45 -4
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
- package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/aiInventory.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +12 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +43 -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/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +43 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchRate.d.ts +9 -6
- package/types/lib/inventory/fetchRate.d.ts.map +1 -1
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +190 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
- package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
- package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
- package/types/lib/inventory/mcpbBundle.d.ts +19 -0
- package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
- package/types/lib/inventory/osinfo.d.ts.map +1 -1
- package/types/lib/inventory/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts +18 -13
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
- package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
- package/types/lib/inventory/svelteUtils.d.ts +124 -0
- package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
- package/types/lib/inventory/toolRequirements.d.ts +216 -0
- package/types/lib/inventory/toolRequirements.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
- package/types/lib/parsers/onnx.d.ts +28 -0
- package/types/lib/parsers/onnx.d.ts.map +1 -0
- package/types/lib/parsers/safetensors.d.ts +22 -0
- package/types/lib/parsers/safetensors.d.ts.map +1 -0
- package/types/lib/stages/postgen/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 +58 -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 +35 -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
|
@@ -7,10 +7,12 @@ import { Purl } from "@cdxgen/cdx-purl";
|
|
|
7
7
|
import {
|
|
8
8
|
DEBUG_MODE,
|
|
9
9
|
isDryRun,
|
|
10
|
+
isSecureMode,
|
|
10
11
|
readEnvironmentVariable,
|
|
11
12
|
resetActivityContext,
|
|
12
13
|
setActivityContext,
|
|
13
14
|
} from "../../core/activity.js";
|
|
15
|
+
import { getRemediationCatalog } from "../../core/buildLedger.js";
|
|
14
16
|
import { hasAnyProjectType } from "../../core/env.js";
|
|
15
17
|
import {
|
|
16
18
|
getTimestamp,
|
|
@@ -44,6 +46,23 @@ import { enhanceBom } from "../../inventory/licenseEnhancer.js";
|
|
|
44
46
|
import { applyMcpPinningState } from "../../inventory/mcpPinning.js";
|
|
45
47
|
import { buildReleaseNotesFromGit } from "../../inventory/source.js";
|
|
46
48
|
import { extractTags, findBomType, textualMetadata } from "./annotator.js";
|
|
49
|
+
import {
|
|
50
|
+
applyFormulationRunIdMarker,
|
|
51
|
+
applyIntrospectionToBom,
|
|
52
|
+
hasIntrospectionVerdict,
|
|
53
|
+
} from "./introspection/annotate.js";
|
|
54
|
+
import { collectCommandFacts } from "./introspection/commandFacts.js";
|
|
55
|
+
import {
|
|
56
|
+
blockRemediationsForDryRun,
|
|
57
|
+
emitIntrospectionReports,
|
|
58
|
+
printIntrospectionSummary,
|
|
59
|
+
} from "./introspection/emit.js";
|
|
60
|
+
import {
|
|
61
|
+
isIntrospectionEnabled,
|
|
62
|
+
reflectOnRun,
|
|
63
|
+
} from "./introspection/reflect.js";
|
|
64
|
+
import { scoreReflection } from "./introspection/score.js";
|
|
65
|
+
import { repairParentDependencyEdge } from "./parentEdge.js";
|
|
47
66
|
import { sortBomCollections } from "./sortBom.js";
|
|
48
67
|
import {
|
|
49
68
|
applyComponentTypeFilter,
|
|
@@ -435,6 +454,94 @@ function buildAuditFindingsCitation(bomJson) {
|
|
|
435
454
|
});
|
|
436
455
|
}
|
|
437
456
|
|
|
457
|
+
/**
|
|
458
|
+
* Run the build-introspection reflection over the final BOM, score it, write
|
|
459
|
+
* the reports, and carry the verdict inside the BOM as metadata properties and
|
|
460
|
+
* annotations. Introspection is opt-in; when disabled this is a no-op, and
|
|
461
|
+
* when enabled a reflection failure warns instead of failing the completed
|
|
462
|
+
* BOM.
|
|
463
|
+
*
|
|
464
|
+
* The CI gate is decided here but enforced by the CLI after the BOM is
|
|
465
|
+
* written, so a below-threshold score never costs the user their output.
|
|
466
|
+
*
|
|
467
|
+
* @param {Object} bomNSData BOM with namespaces object
|
|
468
|
+
* @param {Object} options CLI options
|
|
469
|
+
* @param {string} [filePath] Source path that was scanned
|
|
470
|
+
*
|
|
471
|
+
* @returns {Promise<Object>} The same bomNSData, with `reflection` (carrying its `scoring`) attached when introspection ran
|
|
472
|
+
*/
|
|
473
|
+
async function applyIntrospection(bomNSData, options, filePath) {
|
|
474
|
+
if (!isIntrospectionEnabled(options) || !bomNSData?.bomJson) {
|
|
475
|
+
return bomNSData;
|
|
476
|
+
}
|
|
477
|
+
// Evidence collection re-processes a finished BOM through a fresh wrapper,
|
|
478
|
+
// but that pass carries neither the scanned path nor the generation's
|
|
479
|
+
// ecosystem context: its ledger events belong to the evinse probes and its
|
|
480
|
+
// marker scan finds nothing, so it would grade a project it never saw. The
|
|
481
|
+
// verdict describes how the BOM was generated, so it stays with the pass
|
|
482
|
+
// that generated it — the document itself is what carries that across.
|
|
483
|
+
if (bomNSData.reflection || hasIntrospectionVerdict(bomNSData.bomJson)) {
|
|
484
|
+
return bomNSData;
|
|
485
|
+
}
|
|
486
|
+
try {
|
|
487
|
+
const reflection = await reflectOnRun(bomNSData.bomJson, options, {
|
|
488
|
+
projectPath: filePath || options.filePath || "",
|
|
489
|
+
});
|
|
490
|
+
reflection.scoring = scoreReflection(reflection, getRemediationCatalog(), {
|
|
491
|
+
secureMode: isSecureMode,
|
|
492
|
+
inContainer: readEnvironmentVariable("CDXGEN_IN_CONTAINER") === "true",
|
|
493
|
+
commandFacts: collectCommandFacts(filePath || options.filePath || ""),
|
|
494
|
+
});
|
|
495
|
+
// Report consumers correlate a report with the BOM it describes through
|
|
496
|
+
// these two facts, so they travel on the reflection document itself.
|
|
497
|
+
reflection.bom = {
|
|
498
|
+
serialNumber: bomNSData.bomJson?.serialNumber,
|
|
499
|
+
componentCount: Array.isArray(bomNSData.bomJson?.components)
|
|
500
|
+
? bomNSData.bomJson.components.length
|
|
501
|
+
: 0,
|
|
502
|
+
};
|
|
503
|
+
bomNSData.reflection = reflection;
|
|
504
|
+
if (isDryRun) {
|
|
505
|
+
blockRemediationsForDryRun(reflection.scoring);
|
|
506
|
+
}
|
|
507
|
+
// A document at a spec version that predates annotations still carries
|
|
508
|
+
// the metadata properties; only the annotations are version-gated.
|
|
509
|
+
const annotationsSupported =
|
|
510
|
+
normalizeCycloneDxSpecVersion(
|
|
511
|
+
options?.specVersion || bomNSData.bomJson?.specVersion,
|
|
512
|
+
) >= 1.5;
|
|
513
|
+
applyFormulationRunIdMarker(bomNSData.bomJson, reflection.runId);
|
|
514
|
+
applyIntrospectionToBom(bomNSData.bomJson, reflection, reflection.scoring, {
|
|
515
|
+
annotations: annotationsSupported,
|
|
516
|
+
});
|
|
517
|
+
const delivery = emitIntrospectionReports(
|
|
518
|
+
reflection,
|
|
519
|
+
reflection.scoring,
|
|
520
|
+
options,
|
|
521
|
+
);
|
|
522
|
+
printIntrospectionSummary(reflection.scoring, delivery);
|
|
523
|
+
const threshold = options?.introspectFailBelow;
|
|
524
|
+
if (typeof threshold === "number" && Number.isFinite(threshold)) {
|
|
525
|
+
const score = reflection.scoring.overallScore;
|
|
526
|
+
bomNSData.introspectionGate = {
|
|
527
|
+
threshold,
|
|
528
|
+
score,
|
|
529
|
+
passed: score >= threshold,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
if (!reflection.ledgerComplete) {
|
|
533
|
+
console.warn(
|
|
534
|
+
"Build introspection: the ledger is incomplete (buffer truncated or recorder event); treat the verdict with care and re-run with CDXGEN_INTROSPECT_LEDGER set.",
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
} catch (err) {
|
|
538
|
+
console.warn(
|
|
539
|
+
`Build introspection failed to complete and was skipped: ${err?.message}`,
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
return bomNSData;
|
|
543
|
+
}
|
|
544
|
+
|
|
438
545
|
/**
|
|
439
546
|
* Filter and enhance BOM post generation.
|
|
440
547
|
*
|
|
@@ -442,9 +549,9 @@ function buildAuditFindingsCitation(bomJson) {
|
|
|
442
549
|
* @param {Object} options CLI options
|
|
443
550
|
* @param {string} [filePath] Source path used for formulation and metadata context
|
|
444
551
|
*
|
|
445
|
-
* @returns {Object} Modified bomNSData
|
|
552
|
+
* @returns {Promise<Object>} Modified bomNSData
|
|
446
553
|
*/
|
|
447
|
-
export function postProcess(bomNSData, options, filePath) {
|
|
554
|
+
export async function postProcess(bomNSData, options, filePath) {
|
|
448
555
|
let jsonPayload = bomNSData.bomJson;
|
|
449
556
|
if (
|
|
450
557
|
typeof bomNSData.bomJson === "string" ||
|
|
@@ -458,14 +565,27 @@ export function postProcess(bomNSData, options, filePath) {
|
|
|
458
565
|
bomNSData.bomJson = applyMetadata(bomNSData.bomJson, options);
|
|
459
566
|
bomNSData.bomJson = applyLicenseEnhancement(bomNSData.bomJson, options);
|
|
460
567
|
bomNSData.bomJson = applyContainerInventoryMetadata(bomNSData.bomJson);
|
|
568
|
+
const buildOnlyComponents = extractBuildOnlyComponents(
|
|
569
|
+
bomNSData.bomJson,
|
|
570
|
+
options,
|
|
571
|
+
);
|
|
461
572
|
bomNSData.bomJson = applyFormulation(
|
|
462
573
|
bomNSData.bomJson,
|
|
463
574
|
options,
|
|
464
575
|
filePath,
|
|
465
|
-
|
|
576
|
+
buildOnlyComponents.length
|
|
577
|
+
? [...(bomNSData.formulationList || []), ...buildOnlyComponents]
|
|
578
|
+
: bomNSData.formulationList,
|
|
579
|
+
);
|
|
580
|
+
bomNSData.bomJson = applyBuildOnlyFormulation(
|
|
581
|
+
bomNSData.bomJson,
|
|
582
|
+
buildOnlyComponents,
|
|
466
583
|
);
|
|
467
584
|
bomNSData.bomJson = applyReleaseNotes(bomNSData.bomJson, options, filePath);
|
|
468
585
|
bomNSData.bomJson = applyComponentTypeFilter(bomNSData.bomJson, options);
|
|
586
|
+
// Filtering can leave the tree without a way in, so this runs on the final
|
|
587
|
+
// component set and before annotations, which report on reachability.
|
|
588
|
+
bomNSData.bomJson = repairParentDependencyEdge(bomNSData.bomJson);
|
|
469
589
|
bomNSData.bomJson = applySpecVersionCompatibility(bomNSData.bomJson, options);
|
|
470
590
|
bomNSData.bomJson = validateTlpClassification(bomNSData.bomJson, options);
|
|
471
591
|
// Support for automatic annotations
|
|
@@ -495,6 +615,9 @@ export function postProcess(bomNSData, options, filePath) {
|
|
|
495
615
|
cleanupEnv(options);
|
|
496
616
|
cleanupTmpDir();
|
|
497
617
|
sortBomCollections(bomNSData.bomJson);
|
|
618
|
+
// Reflection reads the finished BOM, so it runs after sorting; it returns
|
|
619
|
+
// its verdict alongside the BOM instead of mutating it.
|
|
620
|
+
await applyIntrospection(bomNSData, options, filePath);
|
|
498
621
|
return bomNSData;
|
|
499
622
|
}
|
|
500
623
|
|
|
@@ -851,6 +974,154 @@ function getIdentityTechniques(comp) {
|
|
|
851
974
|
return techniques;
|
|
852
975
|
}
|
|
853
976
|
|
|
977
|
+
/**
|
|
978
|
+
* Ensure the components moved out of the assembly have landed in the
|
|
979
|
+
* formulation section.
|
|
980
|
+
*
|
|
981
|
+
* With `--include-formulation` they were handed to the full formulation pass.
|
|
982
|
+
* Without it there is no formulation section, so a minimal one is created for
|
|
983
|
+
* them alone: moving a component out of `components[]` and then dropping it
|
|
984
|
+
* would lose it from the document.
|
|
985
|
+
*
|
|
986
|
+
* @param {Object} bomJson BOM JSON object
|
|
987
|
+
* @param {Object[]} buildOnlyComponents Components removed from `components[]`
|
|
988
|
+
*
|
|
989
|
+
* @returns {Object} The same `bomJson`
|
|
990
|
+
*/
|
|
991
|
+
function applyBuildOnlyFormulation(bomJson, buildOnlyComponents) {
|
|
992
|
+
if (!buildOnlyComponents?.length || !bomJson) {
|
|
993
|
+
return bomJson;
|
|
994
|
+
}
|
|
995
|
+
const alreadyPlaced = (bomJson.formulation || []).some((formula) =>
|
|
996
|
+
formula?.components?.some((comp) =>
|
|
997
|
+
buildOnlyComponents.some((moved) => moved["bom-ref"] === comp["bom-ref"]),
|
|
998
|
+
),
|
|
999
|
+
);
|
|
1000
|
+
if (alreadyPlaced) {
|
|
1001
|
+
return bomJson;
|
|
1002
|
+
}
|
|
1003
|
+
if (!bomJson.formulation) {
|
|
1004
|
+
bomJson.formulation = [];
|
|
1005
|
+
}
|
|
1006
|
+
bomJson.formulation.push({
|
|
1007
|
+
"bom-ref": `${bomJson.metadata?.component?.["bom-ref"] || "cdxgen"}#build-dependencies`,
|
|
1008
|
+
components: buildOnlyComponents,
|
|
1009
|
+
});
|
|
1010
|
+
return bomJson;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
/**
|
|
1014
|
+
* Report whether a component exists only to produce the build, rather than
|
|
1015
|
+
* being part of the delivered assembly. A cargo build dependency and a
|
|
1016
|
+
* proc-macro crate are both marked with `cdx:cargo:hostOnly`.
|
|
1017
|
+
*
|
|
1018
|
+
* @param {Object} comp Component
|
|
1019
|
+
* @returns {boolean} True for a build-time-only component
|
|
1020
|
+
*/
|
|
1021
|
+
function isBuildOnlyComponent(comp) {
|
|
1022
|
+
return !!comp?.properties?.some(
|
|
1023
|
+
(property) =>
|
|
1024
|
+
property.name === "cdx:cargo:hostOnly" && property.value === "true",
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/**
|
|
1029
|
+
* Move the build-time-only components out of `components[]`, which describes
|
|
1030
|
+
* the delivered assembly, and hand them to the formulation section, which is
|
|
1031
|
+
* where CycloneDX describes how the assembly was produced.
|
|
1032
|
+
*
|
|
1033
|
+
* Their bom-refs stay valid because the formulation components live in the same
|
|
1034
|
+
* document, so the dependency graph is left intact. `formulation` exists from
|
|
1035
|
+
* CycloneDX 1.5, so an older document keeps them in `components[]` with their
|
|
1036
|
+
* `cdx:cargo:hostOnly` marker rather than losing them.
|
|
1037
|
+
*
|
|
1038
|
+
* @param {Object} bomJson BOM JSON object
|
|
1039
|
+
* @param {Object} options CLI options
|
|
1040
|
+
*
|
|
1041
|
+
* @returns {Object[]} The components removed from `components[]`
|
|
1042
|
+
*/
|
|
1043
|
+
export function extractBuildOnlyComponents(bomJson, options) {
|
|
1044
|
+
if (options?.specVersion < 1.5 || !bomJson?.components?.length) {
|
|
1045
|
+
return [];
|
|
1046
|
+
}
|
|
1047
|
+
const buildOnlyComponents = [];
|
|
1048
|
+
const retainedComponents = [];
|
|
1049
|
+
for (const comp of bomJson.components) {
|
|
1050
|
+
if (isBuildOnlyComponent(comp)) {
|
|
1051
|
+
buildOnlyComponents.push(comp);
|
|
1052
|
+
} else {
|
|
1053
|
+
retainedComponents.push(comp);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
if (buildOnlyComponents.length) {
|
|
1057
|
+
bomJson.components = retainedComponents;
|
|
1058
|
+
}
|
|
1059
|
+
return buildOnlyComponents;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* Report whether a component carries evidence that it is actually used: an
|
|
1064
|
+
* occurrence in the analyzed sources, or a frame in a callstack.
|
|
1065
|
+
*
|
|
1066
|
+
* @param {Object} comp Component
|
|
1067
|
+
* @returns {boolean} True when usage evidence is attached
|
|
1068
|
+
*/
|
|
1069
|
+
function hasUsageEvidence(comp) {
|
|
1070
|
+
if (comp?.evidence?.occurrences?.length) {
|
|
1071
|
+
return true;
|
|
1072
|
+
}
|
|
1073
|
+
return !!comp?.evidence?.callstack?.frames?.length;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
/**
|
|
1077
|
+
* Re-apply the `--required-only` filter once the evidence stage has attached
|
|
1078
|
+
* occurrence and callstack data.
|
|
1079
|
+
*
|
|
1080
|
+
* A manifest can only say that a dependency is optional; the evidence says
|
|
1081
|
+
* whether it is reached. A component the analyzers observed in the sources is
|
|
1082
|
+
* promoted to `required` so it survives the filter, while an optional component
|
|
1083
|
+
* with no observed usage is dropped as before. A component scoped `excluded` -
|
|
1084
|
+
* a dev or test dependency - keeps that scope: an occurrence inside the test
|
|
1085
|
+
* sources is not evidence that it ships.
|
|
1086
|
+
*
|
|
1087
|
+
* @param {Object} bomJson BOM JSON object carrying evidence
|
|
1088
|
+
* @param {Object} options CLI options
|
|
1089
|
+
*
|
|
1090
|
+
* @returns {Object} Filtered BOM JSON
|
|
1091
|
+
*/
|
|
1092
|
+
export function applyEvidenceBasedFilter(bomJson, options) {
|
|
1093
|
+
if (!options?.requiredOnly || !bomJson?.components?.length) {
|
|
1094
|
+
return bomJson;
|
|
1095
|
+
}
|
|
1096
|
+
if (!(options.evidence || options.includeCrypto)) {
|
|
1097
|
+
return bomJson;
|
|
1098
|
+
}
|
|
1099
|
+
for (const comp of bomJson.components) {
|
|
1100
|
+
if (!hasUsageEvidence(comp)) {
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1103
|
+
if (!comp.properties) {
|
|
1104
|
+
comp.properties = [];
|
|
1105
|
+
}
|
|
1106
|
+
const observed = comp.evidence?.occurrences?.length
|
|
1107
|
+
? "occurrence"
|
|
1108
|
+
: "callstack";
|
|
1109
|
+
if (
|
|
1110
|
+
!comp.properties.some(
|
|
1111
|
+
(property) => property.name === "cdx:evidence:usage",
|
|
1112
|
+
)
|
|
1113
|
+
) {
|
|
1114
|
+
comp.properties.push({ name: "cdx:evidence:usage", value: observed });
|
|
1115
|
+
}
|
|
1116
|
+
if (comp.scope === "optional") {
|
|
1117
|
+
comp.scope = "required";
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
// `evidenceFilterStage` tells filterBom that the evidence is in hand, so the
|
|
1121
|
+
// scope filter it deferred earlier must run now.
|
|
1122
|
+
return filterBom(bomJson, { ...options, evidenceFilterStage: true });
|
|
1123
|
+
}
|
|
1124
|
+
|
|
854
1125
|
/**
|
|
855
1126
|
* Filter BOM based on options
|
|
856
1127
|
*
|
|
@@ -870,6 +1141,15 @@ export function filterBom(bomJson, options) {
|
|
|
870
1141
|
const allowedComponentTypes = new Set(
|
|
871
1142
|
normalizeCycloneDxComponentTypeFilter(options?.componentType),
|
|
872
1143
|
);
|
|
1144
|
+
// Evidence generation runs after this stage, so dropping the optional
|
|
1145
|
+
// components here would deny the analyzers the chance to prove that one is
|
|
1146
|
+
// used. The scope filter is re-applied by applyEvidenceBasedFilter once the
|
|
1147
|
+
// occurrence and callstack evidence is attached.
|
|
1148
|
+
const deferRequiredOnly = !!(
|
|
1149
|
+
options.requiredOnly &&
|
|
1150
|
+
!options.evidenceFilterStage &&
|
|
1151
|
+
(options.evidence || options.includeCrypto)
|
|
1152
|
+
);
|
|
873
1153
|
for (const comp of bomJson.components) {
|
|
874
1154
|
if (shouldExcludeInventoryType(comp, options)) {
|
|
875
1155
|
filtered = true;
|
|
@@ -912,6 +1192,7 @@ export function filterBom(bomJson, options) {
|
|
|
912
1192
|
}
|
|
913
1193
|
if (
|
|
914
1194
|
options.requiredOnly &&
|
|
1195
|
+
!deferRequiredOnly &&
|
|
915
1196
|
comp.scope &&
|
|
916
1197
|
["optional", "excluded"].includes(comp.scope)
|
|
917
1198
|
) {
|
|
@@ -494,6 +494,23 @@ function registerCdxHelpers(expression, helpers = {}) {
|
|
|
494
494
|
expression.registerFunction("formulationComponents", (bomJson) =>
|
|
495
495
|
getFormulationComponents(bomJson),
|
|
496
496
|
);
|
|
497
|
+
// Formulation-derived rules need to know whether the formulation section
|
|
498
|
+
// describes the run being reflected (whose probes the ledger already
|
|
499
|
+
// counted) or a foreign BOM's record. The caller that read the evidence
|
|
500
|
+
// passes its origin verdict through the options; without one, rules gated
|
|
501
|
+
// on a specific origin stay silent.
|
|
502
|
+
expression.registerFunction(
|
|
503
|
+
"formulationOrigin",
|
|
504
|
+
() => helpers?.formulationEvidence?.origin || "unknown",
|
|
505
|
+
);
|
|
506
|
+
// The toolchain mismatch a foreign BOM's formulation proves: the recorded
|
|
507
|
+
// version of this runtime's own family differs from the runtime in use, so
|
|
508
|
+
// the record describes another machine. Computed by the caller, which owns
|
|
509
|
+
// the runtime facts.
|
|
510
|
+
expression.registerFunction(
|
|
511
|
+
"formulationRuntimeMismatch",
|
|
512
|
+
() => helpers?.formulationEvidence?.runtimeMismatch,
|
|
513
|
+
);
|
|
497
514
|
return expression;
|
|
498
515
|
}
|
|
499
516
|
|
|
@@ -47,8 +47,44 @@ const RISKY_PRESENCE_VARS = [
|
|
|
47
47
|
// SSH_AUTH_SOCK (ends with _SOCK) and __CF_USER_TEXT_ENCODING (ends with _ENCODING)
|
|
48
48
|
// are NOT flagged as false positives.
|
|
49
49
|
// `cred` is kept alongside `credential(?:s)?` to also catch short-form names like MY_CRED.
|
|
50
|
+
// `session` must be followed by an id qualifier (SESSION_ID, SESSIONID, SESSION-ID):
|
|
51
|
+
// a bare _SESSION suffix names a session/desktop type (DESKTOP_SESSION, GDM_SESSION),
|
|
52
|
+
// not a secret-bearing session identifier.
|
|
50
53
|
const CREDENTIAL_VAR_PATTERN =
|
|
51
|
-
/_(?:token|key|secret|pass(?:word)?|credential(?:s)?|cred|user|email|auth|session)$/i;
|
|
54
|
+
/_(?:token|key|secret|pass(?:word)?|credential(?:s)?|cred|user|email|auth|session[_-]?id)$/i;
|
|
55
|
+
|
|
56
|
+
// OS/desktop-layer variables whose names match CREDENTIAL_VAR_PATTERN but whose
|
|
57
|
+
// values are public session or identity identifiers, never secrets. Each entry
|
|
58
|
+
// is set automatically by the operating system or desktop environment, so
|
|
59
|
+
// flagging it would warn on nearly every desktop/developer machine:
|
|
60
|
+
// - GNOME_DESKTOP_SESSION_ID: deprecated gnome-session marker, always the
|
|
61
|
+
// literal string "this-is-deprecated" (kept by GNOME ≤ 3.28 and RHEL 8).
|
|
62
|
+
// - KDE_SESSION_ID: numeric KDE session identifier (legacy ksmserver-era KDE).
|
|
63
|
+
// - SHELL_SESSION_ID: UUID assigned to each Konsole (KDE terminal) tab/session.
|
|
64
|
+
// - TERM_SESSION_ID: window/tab/process indices plus a UUID, set by Apple
|
|
65
|
+
// Terminal (documented in /etc/zshrc_Apple_Terminal) and iTerm2.
|
|
66
|
+
// - ITERM_SESSION_ID: iTerm2 per-pane identifier in the same format.
|
|
67
|
+
// - XDG_SESSION_ID: numeric logind audit-session id set by pam_systemd on
|
|
68
|
+
// every systemd login.
|
|
69
|
+
// - SUDO_USER / DOAS_USER: login name of the invoking user, set by sudo/doas.
|
|
70
|
+
// - XDG_ACTIVATION_TOKEN: single-use Wayland xdg-activation focus token set
|
|
71
|
+
// by GNOME/GLib and KDE launchers; only grants a newly launched app the
|
|
72
|
+
// right to focus its own window.
|
|
73
|
+
// Native Windows variables (USERNAME, SESSIONNAME, USERDOMAIN, …) use
|
|
74
|
+
// non-underscored names and cannot match the pattern, so none appear here.
|
|
75
|
+
// SSH_USER_AUTH is deliberately NOT exempted: sshd only sets it with the
|
|
76
|
+
// non-default `ExposeAuthInfo yes` and it points at key-fingerprint data.
|
|
77
|
+
const CREDENTIAL_VAR_EXCEPTIONS = new Set([
|
|
78
|
+
"GNOME_DESKTOP_SESSION_ID",
|
|
79
|
+
"KDE_SESSION_ID",
|
|
80
|
+
"SHELL_SESSION_ID",
|
|
81
|
+
"TERM_SESSION_ID",
|
|
82
|
+
"ITERM_SESSION_ID",
|
|
83
|
+
"XDG_SESSION_ID",
|
|
84
|
+
"SUDO_USER",
|
|
85
|
+
"DOAS_USER",
|
|
86
|
+
"XDG_ACTIVATION_TOKEN",
|
|
87
|
+
]);
|
|
52
88
|
|
|
53
89
|
// Proxy variables — NO_PROXY is a bypass-list and should not trigger a finding on its own.
|
|
54
90
|
const PROXY_VARS = ["HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy"];
|
|
@@ -283,7 +319,11 @@ export function auditEnvironment(env = process.env, options = {}) {
|
|
|
283
319
|
|
|
284
320
|
// Credential exposure — detect any env var whose name follows a credential-naming convention.
|
|
285
321
|
for (const [varName, varValue] of Object.entries(env)) {
|
|
286
|
-
if (
|
|
322
|
+
if (
|
|
323
|
+
varValue &&
|
|
324
|
+
CREDENTIAL_VAR_PATTERN.test(varName) &&
|
|
325
|
+
!CREDENTIAL_VAR_EXCEPTIONS.has(varName)
|
|
326
|
+
) {
|
|
287
327
|
recordEnvironmentRead(varName, { source: envSource });
|
|
288
328
|
findings.push({
|
|
289
329
|
type: "credential-exposure",
|