@cyclonedx/cdxgen 12.4.3 → 12.4.4
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 +6 -0
- package/bin/audit.js +7 -0
- package/bin/cdxgen.js +48 -2
- package/bin/evinse.js +7 -0
- package/lib/audit/index.js +165 -2
- package/lib/audit/index.poku.js +462 -0
- package/lib/cli/index.js +317 -169
- package/lib/evinser/evinser.js +31 -9
- package/lib/helpers/analyzer.js +890 -0
- package/lib/helpers/analyzer.poku.js +341 -0
- package/lib/helpers/atomUtils.js +445 -0
- package/lib/helpers/atomUtils.poku.js +137 -0
- package/lib/helpers/bomUtils.js +71 -0
- package/lib/helpers/bomUtils.poku.js +45 -0
- package/lib/helpers/depsUtils.js +146 -0
- package/lib/helpers/depsUtils.poku.js +183 -0
- package/lib/helpers/utils.js +585 -191
- package/lib/helpers/utils.poku.js +357 -4
- package/lib/managers/binary.js +18 -9
- package/lib/stages/postgen/postgen.js +215 -0
- package/lib/stages/postgen/postgen.poku.js +218 -3
- package/lib/validator/bomValidator.js +11 -2
- package/package.json +8 -8
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/atomUtils.d.ts +18 -0
- package/types/lib/helpers/atomUtils.d.ts.map +1 -0
- package/types/lib/helpers/bomUtils.d.ts +10 -0
- package/types/lib/helpers/bomUtils.d.ts.map +1 -1
- package/types/lib/helpers/depsUtils.d.ts +9 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -1
- package/types/lib/helpers/utils.d.ts +19 -0
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +2 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -403,6 +403,12 @@ The default specification used by cdxgen is 1.7. To generate BOM for a different
|
|
|
403
403
|
cdxgen -r -o bom.json --spec-version 1.6
|
|
404
404
|
```
|
|
405
405
|
|
|
406
|
+
Use repeated `--component-type` values to include only selected CycloneDX component types. This is a filter, not an inventory enabler: passing a type such as `machine-learning-model` does not enable machine-learning model discovery, and filtering to a type that the scan does not generate can produce an empty component list. The allowed values are validated against `--spec-version`; for example, `cryptographic-asset` is rejected for CycloneDX 1.5 and unsupported component types are pruned during compatibility downgrades. The dedicated `cbom` command does not accept `--component-type`; use `cdxgen --include-crypto` for normal generation plus filtering.
|
|
407
|
+
|
|
408
|
+
```shell
|
|
409
|
+
cdxgen -t docker alpine:3.20 --spec-version 1.5 --component-type library --component-type data
|
|
410
|
+
```
|
|
411
|
+
|
|
406
412
|
To generate SBOM for C or Python, ensure Java >= 21 is installed.
|
|
407
413
|
|
|
408
414
|
```shell
|
package/bin/audit.js
CHANGED
|
@@ -109,6 +109,12 @@ const args = yargs(hideBin(process.argv))
|
|
|
109
109
|
"Optional JSON array or newline-delimited file of purl prefixes to exclude from predictive audit target selection in addition to the built-in well-known allowlist.",
|
|
110
110
|
type: "string",
|
|
111
111
|
})
|
|
112
|
+
.option("skip-default-branch-recheck", {
|
|
113
|
+
default: false,
|
|
114
|
+
description:
|
|
115
|
+
"Skip rechecking critical/high findings against the default branch to detect issues already fixed upstream.",
|
|
116
|
+
type: "boolean",
|
|
117
|
+
})
|
|
112
118
|
.check((argv) => {
|
|
113
119
|
if (!argv.bom && !argv.bomDir) {
|
|
114
120
|
throw new Error("Specify --bom or --bom-dir.");
|
|
@@ -187,6 +193,7 @@ function writeOrPrint(output, outputPath) {
|
|
|
187
193
|
reportsDir: args.reportsDir,
|
|
188
194
|
rulesDir: args.rulesDir,
|
|
189
195
|
scope: args.scope === "required" ? "required" : undefined,
|
|
196
|
+
skipDefaultBranchRecheck: args.skipDefaultBranchRecheck,
|
|
190
197
|
trusted: args.onlyTrusted
|
|
191
198
|
? "only"
|
|
192
199
|
: args.includeTrusted
|
package/bin/cdxgen.js
CHANGED
|
@@ -20,7 +20,13 @@ import { hideBin } from "yargs/helpers";
|
|
|
20
20
|
|
|
21
21
|
import { createBom, submitBom } from "../lib/cli/index.js";
|
|
22
22
|
import { signBom, verifyBom } from "../lib/helpers/bomSigner.js";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
getSupportedCycloneDxComponentTypes,
|
|
25
|
+
isCycloneDxBom,
|
|
26
|
+
isCycloneDxComponentTypeEnabled,
|
|
27
|
+
normalizeCycloneDxComponentTypeFilter,
|
|
28
|
+
toCycloneDxSpecVersionString,
|
|
29
|
+
} from "../lib/helpers/bomUtils.js";
|
|
24
30
|
import {
|
|
25
31
|
displaySelfThreatModel,
|
|
26
32
|
printActivitySummary,
|
|
@@ -135,6 +141,7 @@ const invokedCommandName = basename(process.argv[1] || "cdxgen").replace(
|
|
|
135
141
|
/\.(?:[cm]?js|exe)$/u,
|
|
136
142
|
"",
|
|
137
143
|
);
|
|
144
|
+
const defaultComponentTypeChoices = getSupportedCycloneDxComponentTypes(1.7);
|
|
138
145
|
|
|
139
146
|
const args = _yargs
|
|
140
147
|
.env("CDXGEN")
|
|
@@ -408,6 +415,7 @@ const args = _yargs
|
|
|
408
415
|
.option("exclude", {
|
|
409
416
|
alias: "exclude-regex",
|
|
410
417
|
description: "Additional glob pattern(s) to ignore",
|
|
418
|
+
nargs: 1,
|
|
411
419
|
type: "array",
|
|
412
420
|
})
|
|
413
421
|
.option("export-proto", {
|
|
@@ -483,6 +491,12 @@ const args = _yargs
|
|
|
483
491
|
"filename",
|
|
484
492
|
],
|
|
485
493
|
})
|
|
494
|
+
.option("component-type", {
|
|
495
|
+
description:
|
|
496
|
+
"CycloneDX component type(s) to include. Choices are validated against --spec-version.",
|
|
497
|
+
choices: defaultComponentTypeChoices,
|
|
498
|
+
type: "string",
|
|
499
|
+
})
|
|
486
500
|
.option("tlp-classification", {
|
|
487
501
|
description:
|
|
488
502
|
"Traffic Light Protocol (TLP) is a classification system for identifying the potential risk associated with an artefact, including whether it is subject to certain types of legal, financial, or technical threats. Refer to [https://www.first.org/tlp/](https://www.first.org/tlp/) for further information.",
|
|
@@ -567,6 +581,29 @@ const args = _yargs
|
|
|
567
581
|
.array("standard")
|
|
568
582
|
.array("feature-flags")
|
|
569
583
|
.array("technique")
|
|
584
|
+
.array("componentType")
|
|
585
|
+
.check((argv) => {
|
|
586
|
+
const requestedComponentTypes = normalizeCycloneDxComponentTypeFilter(
|
|
587
|
+
argv.componentType,
|
|
588
|
+
);
|
|
589
|
+
if (!requestedComponentTypes.length) {
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
const normalizedSpecVersion =
|
|
593
|
+
toCycloneDxSpecVersionString(argv.specVersion) || "1.7";
|
|
594
|
+
const supportedComponentTypes = getSupportedCycloneDxComponentTypes(
|
|
595
|
+
normalizedSpecVersion,
|
|
596
|
+
);
|
|
597
|
+
const unsupportedComponentTypes = requestedComponentTypes.filter(
|
|
598
|
+
(componentType) => !supportedComponentTypes.includes(componentType),
|
|
599
|
+
);
|
|
600
|
+
if (unsupportedComponentTypes.length) {
|
|
601
|
+
throw new Error(
|
|
602
|
+
`Unsupported --component-type value(s) for CycloneDX ${normalizedSpecVersion}: ${unsupportedComponentTypes.join(", ")}. Supported values: ${supportedComponentTypes.join(", ")}`,
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
return true;
|
|
606
|
+
})
|
|
570
607
|
.option("auto-compositions", {
|
|
571
608
|
type: "boolean",
|
|
572
609
|
default: true,
|
|
@@ -740,6 +777,12 @@ if (!options.projectType) {
|
|
|
740
777
|
// Handle dedicated cbom and saasbom commands
|
|
741
778
|
if (["cbom", "saasbom"].includes(invokedCommandName)) {
|
|
742
779
|
if (invokedCommandName.includes("cbom")) {
|
|
780
|
+
if (normalizeCycloneDxComponentTypeFilter(options.componentType).length) {
|
|
781
|
+
console.error(
|
|
782
|
+
"The cbom command does not support --component-type. Use cdxgen with --include-crypto when you need component-type filtering.",
|
|
783
|
+
);
|
|
784
|
+
process.exit(1);
|
|
785
|
+
}
|
|
743
786
|
thoughtLog(
|
|
744
787
|
"Ok, the user wants to generate Cryptographic Bill-of-Materials (CBOM).",
|
|
745
788
|
);
|
|
@@ -1676,7 +1719,10 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
|
|
|
1676
1719
|
reachablesSlicesFile: options.reachablesSlicesFile,
|
|
1677
1720
|
semanticsSlicesFile: options.semanticsSlicesFile,
|
|
1678
1721
|
openapiSpecFile: options.openapiSpecFile,
|
|
1679
|
-
|
|
1722
|
+
componentType: options.componentType,
|
|
1723
|
+
includeCrypto:
|
|
1724
|
+
options.includeCrypto &&
|
|
1725
|
+
isCycloneDxComponentTypeEnabled("cryptographic-asset", options),
|
|
1680
1726
|
specVersion: options.specVersion,
|
|
1681
1727
|
profile: options.profile,
|
|
1682
1728
|
jsonPretty: options.jsonPretty,
|
package/bin/evinse.js
CHANGED
|
@@ -113,6 +113,13 @@ const args = yargs(hideBin(process.argv))
|
|
|
113
113
|
default: false,
|
|
114
114
|
type: "boolean",
|
|
115
115
|
})
|
|
116
|
+
.option("exclude", {
|
|
117
|
+
alias: "exclude-regex",
|
|
118
|
+
description:
|
|
119
|
+
"Additional glob pattern(s) to ignore during Atom evidence generation.",
|
|
120
|
+
nargs: 1,
|
|
121
|
+
type: "array",
|
|
122
|
+
})
|
|
116
123
|
.option("usages-slices-file", {
|
|
117
124
|
description: "Use an existing usages slices file.",
|
|
118
125
|
default: "usages.slices.json",
|
package/lib/audit/index.js
CHANGED
|
@@ -1597,6 +1597,131 @@ function buildChildOptions(options, target) {
|
|
|
1597
1597
|
};
|
|
1598
1598
|
}
|
|
1599
1599
|
|
|
1600
|
+
function normalizeFindingFile(filePath) {
|
|
1601
|
+
return String(filePath)
|
|
1602
|
+
.replace(/\\/g, "/")
|
|
1603
|
+
.replace(/\/+/g, "/")
|
|
1604
|
+
.replace(/^\.\//, "");
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
function sourceFindingKey(finding) {
|
|
1608
|
+
if (!finding?.ruleId || !finding?.location?.file) {
|
|
1609
|
+
return undefined;
|
|
1610
|
+
}
|
|
1611
|
+
return `${finding.ruleId}|${normalizeFindingFile(finding.location.file)}`;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
/**
|
|
1615
|
+
* Recheck high/critical findings against the default branch to identify issues
|
|
1616
|
+
* that have already been fixed upstream. This reduces false positives caused by
|
|
1617
|
+
* scanning a version-specific tag where an issue existed but has since been
|
|
1618
|
+
* resolved in the project's main development line.
|
|
1619
|
+
*
|
|
1620
|
+
* @param {object[]} findings findings from version-specific scan
|
|
1621
|
+
* @param {object} target audit target
|
|
1622
|
+
* @param {object} resolution repository resolution metadata
|
|
1623
|
+
* @param {string[]} categories active audit categories
|
|
1624
|
+
* @param {object} options CLI options
|
|
1625
|
+
* @returns {Promise<{findings: object[], fixedInDefaultBranch: number}|undefined>}
|
|
1626
|
+
*/
|
|
1627
|
+
async function recheckFindingsAgainstDefaultBranch(
|
|
1628
|
+
findings,
|
|
1629
|
+
target,
|
|
1630
|
+
resolution,
|
|
1631
|
+
categories,
|
|
1632
|
+
options,
|
|
1633
|
+
) {
|
|
1634
|
+
let defaultBranchDir;
|
|
1635
|
+
try {
|
|
1636
|
+
defaultBranchDir = safeMkdtempSync(
|
|
1637
|
+
join(getTmpDir(), `${targetSlug(target)}-default-`),
|
|
1638
|
+
);
|
|
1639
|
+
await cloneRepositoryToDirWithRetry(
|
|
1640
|
+
resolution.repoUrl,
|
|
1641
|
+
defaultBranchDir,
|
|
1642
|
+
undefined,
|
|
1643
|
+
);
|
|
1644
|
+
const defaultSourceSelection = resolveTargetSourceDirectory(
|
|
1645
|
+
defaultBranchDir,
|
|
1646
|
+
target,
|
|
1647
|
+
resolution,
|
|
1648
|
+
);
|
|
1649
|
+
const childOptions = buildChildOptions(options, target);
|
|
1650
|
+
const bomNSData =
|
|
1651
|
+
(await createBom(defaultSourceSelection.scanDir, childOptions)) || {};
|
|
1652
|
+
if (!bomNSData?.bomJson) {
|
|
1653
|
+
return undefined;
|
|
1654
|
+
}
|
|
1655
|
+
const processedBomNSData = postProcess(
|
|
1656
|
+
bomNSData,
|
|
1657
|
+
childOptions,
|
|
1658
|
+
defaultSourceSelection.scanDir,
|
|
1659
|
+
);
|
|
1660
|
+
const defaultBranchFindings = await auditBom(processedBomNSData.bomJson, {
|
|
1661
|
+
bomAuditCategories: categories.join(","),
|
|
1662
|
+
bomAuditMinSeverity: options.minSeverity || "low",
|
|
1663
|
+
bomAuditRulesDir: options.rulesDir,
|
|
1664
|
+
});
|
|
1665
|
+
const defaultBranchSourceFindings = defaultBranchFindings.concat(
|
|
1666
|
+
buildPythonSourceHeuristicFindings(
|
|
1667
|
+
defaultSourceSelection.scanDir,
|
|
1668
|
+
target,
|
|
1669
|
+
),
|
|
1670
|
+
);
|
|
1671
|
+
// Build a set of finding signatures present in the default branch
|
|
1672
|
+
const defaultBranchFindingKeys = new Set();
|
|
1673
|
+
for (const finding of defaultBranchSourceFindings) {
|
|
1674
|
+
const key = sourceFindingKey(finding);
|
|
1675
|
+
if (key) {
|
|
1676
|
+
defaultBranchFindingKeys.add(key);
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
// Retain findings that are still present in the default branch or are
|
|
1680
|
+
// contextual/heuristic findings (not from rule evaluation against source).
|
|
1681
|
+
// Source-derived findings have a location.file pointing to a repo file,
|
|
1682
|
+
// while contextual findings reference purls or bomRefs only.
|
|
1683
|
+
const retainedFindings = [];
|
|
1684
|
+
let fixedCount = 0;
|
|
1685
|
+
for (const finding of findings) {
|
|
1686
|
+
const isSourceDerivedFinding = Boolean(finding?.location?.file);
|
|
1687
|
+
if (!isSourceDerivedFinding) {
|
|
1688
|
+
// Contextual/heuristic findings are not source-dependent
|
|
1689
|
+
retainedFindings.push(finding);
|
|
1690
|
+
continue;
|
|
1691
|
+
}
|
|
1692
|
+
if (defaultBranchFindingKeys.has(sourceFindingKey(finding))) {
|
|
1693
|
+
retainedFindings.push(finding);
|
|
1694
|
+
} else {
|
|
1695
|
+
fixedCount += 1;
|
|
1696
|
+
thoughtLog("Finding fixed in default branch, removing.", {
|
|
1697
|
+
ruleId: finding.ruleId,
|
|
1698
|
+
file: finding?.location?.file,
|
|
1699
|
+
purl: target.purl,
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
if (fixedCount === 0) {
|
|
1704
|
+
return undefined;
|
|
1705
|
+
}
|
|
1706
|
+
return {
|
|
1707
|
+
findings: retainedFindings,
|
|
1708
|
+
fixedInDefaultBranch: fixedCount,
|
|
1709
|
+
};
|
|
1710
|
+
} catch (error) {
|
|
1711
|
+
const errorMessage = error?.message ?? String(error);
|
|
1712
|
+
thoughtLog("Default branch recheck failed, keeping original findings.", {
|
|
1713
|
+
error: errorMessage,
|
|
1714
|
+
errorType: error?.errorType,
|
|
1715
|
+
purl: target.purl,
|
|
1716
|
+
});
|
|
1717
|
+
return undefined;
|
|
1718
|
+
} finally {
|
|
1719
|
+
if (defaultBranchDir) {
|
|
1720
|
+
cleanupSourceDir(defaultBranchDir);
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1600
1725
|
/**
|
|
1601
1726
|
* Analyze a single purl target by generating a child SBOM and auditing it.
|
|
1602
1727
|
*
|
|
@@ -1756,17 +1881,55 @@ export async function auditTarget(target, options) {
|
|
|
1756
1881
|
sourceSelection.scanDir,
|
|
1757
1882
|
target,
|
|
1758
1883
|
);
|
|
1759
|
-
|
|
1884
|
+
let predictiveFindings = findings.concat(
|
|
1760
1885
|
contextualFindings,
|
|
1761
1886
|
pythonSourceFindings,
|
|
1762
1887
|
);
|
|
1763
|
-
|
|
1888
|
+
let assessment = scoreTargetRisk(predictiveFindings, target, {
|
|
1764
1889
|
bomJson: processedBomJson,
|
|
1765
1890
|
repoReused: Boolean(checkout?.reused || cacheHit),
|
|
1766
1891
|
resolution,
|
|
1767
1892
|
sourceDirectoryConfidence: sourceSelection.confidence,
|
|
1768
1893
|
versionMatched,
|
|
1769
1894
|
});
|
|
1895
|
+
// When we scanned a version-specific ref and the severity is high or
|
|
1896
|
+
// critical, recheck against the default branch to reduce false positives
|
|
1897
|
+
// from issues already fixed upstream.
|
|
1898
|
+
if (
|
|
1899
|
+
versionMatched &&
|
|
1900
|
+
!options.skipDefaultBranchRecheck &&
|
|
1901
|
+
severityMeetsThreshold(assessment.severity, "high") &&
|
|
1902
|
+
resolution?.repoUrl
|
|
1903
|
+
) {
|
|
1904
|
+
emitProgress(options, {
|
|
1905
|
+
index: targetIndex,
|
|
1906
|
+
label: targetLabel,
|
|
1907
|
+
target,
|
|
1908
|
+
total: targetTotal,
|
|
1909
|
+
type: "target:stage",
|
|
1910
|
+
stage: "rechecking findings against default branch",
|
|
1911
|
+
});
|
|
1912
|
+
const recheckResult = await recheckFindingsAgainstDefaultBranch(
|
|
1913
|
+
predictiveFindings,
|
|
1914
|
+
target,
|
|
1915
|
+
resolution,
|
|
1916
|
+
categories,
|
|
1917
|
+
options,
|
|
1918
|
+
);
|
|
1919
|
+
if (recheckResult) {
|
|
1920
|
+
predictiveFindings = recheckResult.findings;
|
|
1921
|
+
assessment = scoreTargetRisk(predictiveFindings, target, {
|
|
1922
|
+
bomJson: processedBomJson,
|
|
1923
|
+
repoReused: Boolean(checkout?.reused || cacheHit),
|
|
1924
|
+
resolution,
|
|
1925
|
+
sourceDirectoryConfidence: sourceSelection.confidence,
|
|
1926
|
+
versionMatched,
|
|
1927
|
+
});
|
|
1928
|
+
assessment.defaultBranchRechecked = true;
|
|
1929
|
+
assessment.findingsFixedInDefaultBranch =
|
|
1930
|
+
recheckResult.fixedInDefaultBranch;
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1770
1933
|
return persistAuditArtifacts(
|
|
1771
1934
|
{
|
|
1772
1935
|
assessment,
|