@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/managers/binary.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
recordActivity,
|
|
19
19
|
recordSymlinkResolution,
|
|
20
20
|
} from "../core/activity.js";
|
|
21
|
+
import { recordDegradation } from "../core/buildLedger.js";
|
|
21
22
|
import { extractPathEnv } from "../core/env.js";
|
|
22
23
|
import {
|
|
23
24
|
getTmpDir,
|
|
@@ -37,7 +38,13 @@ import {
|
|
|
37
38
|
extractToolRefs,
|
|
38
39
|
} from "../inventory/evidenceUtils.js";
|
|
39
40
|
import { createGtfoBinsProperties } from "../inventory/gtfobins.js";
|
|
40
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
canonicalDistroQualifier,
|
|
43
|
+
isCleanDistroQualifier,
|
|
44
|
+
OS_DISTRO_ALIAS,
|
|
45
|
+
OS_NAMESPACE_ALIAS,
|
|
46
|
+
readOsRelease,
|
|
47
|
+
} from "../inventory/osinfo.js";
|
|
41
48
|
import {
|
|
42
49
|
collectExecutables,
|
|
43
50
|
collectSharedLibs,
|
|
@@ -57,13 +64,6 @@ import { getDirs } from "./containerutils.js";
|
|
|
57
64
|
|
|
58
65
|
const isWin = _platform() === "win32";
|
|
59
66
|
const OS_PURL_TYPES = ["deb", "apk", "rpm", "alpm", "qpkg"];
|
|
60
|
-
// Canonical distro vendor namespace. Trivy emits pkg:rpm/alma/... but the
|
|
61
|
-
// vulnerability feeds (AppThreat vuln-list / dep-scan) key AlmaLinux under
|
|
62
|
-
// "almalinux"; align so the purls match. Rocky is emitted as "rocky" already.
|
|
63
|
-
const OS_DISTRO_NAMESPACE_ALIAS = {
|
|
64
|
-
alma: "almalinux",
|
|
65
|
-
"rocky-linux": "rocky",
|
|
66
|
-
};
|
|
67
67
|
const pluginRuntime = setPluginsPathEnv(resolveCdxgenPlugins());
|
|
68
68
|
const platform = pluginRuntime.platform;
|
|
69
69
|
const CDXGEN_PLUGINS_DIR = pluginRuntime.pluginsDir;
|
|
@@ -555,6 +555,173 @@ export function executeSourcekitten(args) {
|
|
|
555
555
|
return undefined;
|
|
556
556
|
}
|
|
557
557
|
|
|
558
|
+
/**
|
|
559
|
+
* Canonicalise the distro vendor namespace of an OS package component
|
|
560
|
+
* produced by trivy, regardless of whether trivy expressed the vendor via the
|
|
561
|
+
* purl namespace or the component group (it is inconsistent between
|
|
562
|
+
* packages). e.g. pkg:rpm/alma/... -> pkg:rpm/almalinux/... and
|
|
563
|
+
* pkg:rpm/cbl-mariner/... -> pkg:rpm/azure-linux/... so the purls match the
|
|
564
|
+
* vulnerability feeds. Also aligns the ``distro`` qualifier vendor prefix
|
|
565
|
+
* (alma-9.8 -> almalinux-9.8, mariner-2.0 -> azure-linux-2.0), including
|
|
566
|
+
* spellings that disagree with the namespace, and rebuilds ``group`` and
|
|
567
|
+
* ``bom-ref`` from the rewritten purl.
|
|
568
|
+
*
|
|
569
|
+
* The namespace table is OS_NAMESPACE_ALIAS from lib/inventory/osinfo.js so
|
|
570
|
+
* the trivy path and the os-release path (getDistroInfo) cannot drift apart.
|
|
571
|
+
*
|
|
572
|
+
* A `distro` qualifier that is not purl-safe (trivy emits Amazon Linux as
|
|
573
|
+
* `amazon-2023.12.20260727+(Amazon Linux)`) is replaced with the os-release
|
|
574
|
+
* derived value, or dropped when there is none: an unusable qualifier
|
|
575
|
+
* invalidates the purl and fails validation for the whole BOM.
|
|
576
|
+
*
|
|
577
|
+
* @param {Object} comp trivy OS package component (mutated in place)
|
|
578
|
+
* @param {Object} purlObj parsed PackageURL of comp.purl (mutated in place)
|
|
579
|
+
* @param {string} name component package name (basename form)
|
|
580
|
+
* @param {string} group component group derived from the trivy component name
|
|
581
|
+
* @param {string} [distroId] canonical os-release distro id, e.g. "amazon-2023"
|
|
582
|
+
* @returns {string} the canonicalised group
|
|
583
|
+
*/
|
|
584
|
+
export function canonicaliseOsPackageNamespace(
|
|
585
|
+
comp,
|
|
586
|
+
purlObj,
|
|
587
|
+
name,
|
|
588
|
+
group,
|
|
589
|
+
distroId,
|
|
590
|
+
) {
|
|
591
|
+
if (!purlObj || !OS_PURL_TYPES.includes(purlObj.type)) {
|
|
592
|
+
return group;
|
|
593
|
+
}
|
|
594
|
+
const os_ns = purlObj.namespace || group || "";
|
|
595
|
+
const os_ns_alias = OS_NAMESPACE_ALIAS[os_ns];
|
|
596
|
+
const distroQual = purlObj.qualifiers?.distro;
|
|
597
|
+
let canonicalDistroQual = distroQual;
|
|
598
|
+
if (os_ns_alias && distroQual?.startsWith(`${os_ns}-`)) {
|
|
599
|
+
canonicalDistroQual = `${os_ns_alias}-${distroQual.slice(os_ns.length + 1)}`;
|
|
600
|
+
}
|
|
601
|
+
canonicalDistroQual = canonicalDistroQualifier(canonicalDistroQual);
|
|
602
|
+
if (canonicalDistroQual && !isCleanDistroQualifier(canonicalDistroQual)) {
|
|
603
|
+
canonicalDistroQual = isCleanDistroQualifier(distroId)
|
|
604
|
+
? distroId
|
|
605
|
+
: undefined;
|
|
606
|
+
}
|
|
607
|
+
if (!os_ns_alias && canonicalDistroQual === distroQual) {
|
|
608
|
+
return group;
|
|
609
|
+
}
|
|
610
|
+
group = os_ns_alias || purlObj.namespace || group || "";
|
|
611
|
+
comp.group = group;
|
|
612
|
+
purlObj.namespace = group;
|
|
613
|
+
if (canonicalDistroQual !== distroQual) {
|
|
614
|
+
if (canonicalDistroQual) {
|
|
615
|
+
purlObj.qualifiers.distro = canonicalDistroQual;
|
|
616
|
+
} else {
|
|
617
|
+
delete purlObj.qualifiers.distro;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
comp.purl = build({
|
|
621
|
+
type: purlObj.type,
|
|
622
|
+
namespace: purlObj.namespace || null,
|
|
623
|
+
name: name,
|
|
624
|
+
version: purlObj.version || null,
|
|
625
|
+
qualifiers: purlObj.qualifiers || null,
|
|
626
|
+
subpath: purlObj.subpath || null,
|
|
627
|
+
});
|
|
628
|
+
comp["bom-ref"] = decodeURIComponent(comp.purl);
|
|
629
|
+
return group;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Repair a `distro` qualifier that cannot survive purl parsing, in place.
|
|
634
|
+
*
|
|
635
|
+
* trivy stamps Amazon Linux with the full pretty version — the qualifier
|
|
636
|
+
* arrives as `distro=amazon-2023.12.20260727+%28Amazon+Linux%29`, whose spaces
|
|
637
|
+
* and parentheses `Purl.parse` rejects outright ("Invalid character in
|
|
638
|
+
* qualifier distro"). The component then bypasses every canonicalisation below
|
|
639
|
+
* (they all need a parsed purl) and reaches the BOM verbatim, where schema
|
|
640
|
+
* validation fails and the *entire* SBOM is discarded — an Amazon Linux scan
|
|
641
|
+
* produces no output at all today.
|
|
642
|
+
*
|
|
643
|
+
* The release is already known from os-release, so the unusable value is
|
|
644
|
+
* swapped for it; with no usable replacement the qualifier is dropped, since a
|
|
645
|
+
* missing qualifier costs one matching hint while an invalid one costs the
|
|
646
|
+
* whole document.
|
|
647
|
+
*
|
|
648
|
+
* @param {Object} comp component whose purl may carry an unusable qualifier
|
|
649
|
+
* @param {string} [distroId] canonical os-release distro id, e.g. "amazon-2023"
|
|
650
|
+
* @returns {Object} the same component
|
|
651
|
+
*/
|
|
652
|
+
export function repairOsComponentPurl(comp, distroId) {
|
|
653
|
+
const purl = comp?.purl;
|
|
654
|
+
if (!purl?.includes("distro=")) {
|
|
655
|
+
return comp;
|
|
656
|
+
}
|
|
657
|
+
const match = /([?&]distro=)([^&]*)/.exec(purl);
|
|
658
|
+
if (!match) {
|
|
659
|
+
return comp;
|
|
660
|
+
}
|
|
661
|
+
let decoded = match[2];
|
|
662
|
+
try {
|
|
663
|
+
decoded = decodeURIComponent(match[2].replace(/\+/g, " "));
|
|
664
|
+
} catch (_err) {
|
|
665
|
+
// A value that cannot even be decoded is definitely not purl-safe
|
|
666
|
+
}
|
|
667
|
+
if (isCleanDistroQualifier(decoded)) {
|
|
668
|
+
return comp;
|
|
669
|
+
}
|
|
670
|
+
const replacement = isCleanDistroQualifier(distroId)
|
|
671
|
+
? `${match[1]}${distroId}`
|
|
672
|
+
: "";
|
|
673
|
+
comp.purl = purl.replace(match[0], replacement).replace(/\?&/, "?");
|
|
674
|
+
if (comp.purl.endsWith("?")) {
|
|
675
|
+
comp.purl = comp.purl.slice(0, -1);
|
|
676
|
+
}
|
|
677
|
+
comp["bom-ref"] = decodeURIComponent(comp.purl);
|
|
678
|
+
return comp;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Drop source-package shadow components that duplicate a real installed
|
|
683
|
+
* package, in place.
|
|
684
|
+
*
|
|
685
|
+
* A shadow is emitted whenever an rpm/deb declares a source name different
|
|
686
|
+
* from its own (bzip2-libs -> bzip2) so that scanners keying on source names
|
|
687
|
+
* still match. When the source package is *itself* installed at the same
|
|
688
|
+
* version, the shadow's purl is identical to the real component's and the two
|
|
689
|
+
* collapse in trimComponents() — which keeps whichever was seen first and
|
|
690
|
+
* never merges `tags`. Seen-first is trivy's listing order, so the outcome is
|
|
691
|
+
* arbitrary: when the shadow wins, a genuinely installed package is reported
|
|
692
|
+
* with `tags: ["source"]` and identity confidence 0, the very marker the
|
|
693
|
+
* shadow carries so consumers can filter shadows out. That turns a real
|
|
694
|
+
* package into a filtered-away one.
|
|
695
|
+
*
|
|
696
|
+
* Dropping the redundant shadow is loss-free: it duplicates the real
|
|
697
|
+
* component's purl (so nothing that matches on purl changes) and its bom-ref
|
|
698
|
+
* is byte-identical, so no dependency reference can dangle.
|
|
699
|
+
*
|
|
700
|
+
* @param {Array} pkgList component list, mutated in place
|
|
701
|
+
* @returns {Array} the same list, for convenience
|
|
702
|
+
*/
|
|
703
|
+
export function dropRedundantSourceComponents(pkgList) {
|
|
704
|
+
if (!Array.isArray(pkgList) || !pkgList.length) {
|
|
705
|
+
return pkgList;
|
|
706
|
+
}
|
|
707
|
+
const installedPurls = new Set();
|
|
708
|
+
for (const comp of pkgList) {
|
|
709
|
+
if (comp?.purl && !comp?.tags?.includes("source")) {
|
|
710
|
+
installedPurls.add(comp.purl);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
if (!installedPurls.size) {
|
|
714
|
+
return pkgList;
|
|
715
|
+
}
|
|
716
|
+
for (let i = pkgList.length - 1; i >= 0; i--) {
|
|
717
|
+
const comp = pkgList[i];
|
|
718
|
+
if (comp?.tags?.includes("source") && installedPurls.has(comp.purl)) {
|
|
719
|
+
pkgList.splice(i, 1);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return pkgList;
|
|
723
|
+
}
|
|
724
|
+
|
|
558
725
|
/**
|
|
559
726
|
* Get the packages installed in the container image filesystem.
|
|
560
727
|
*
|
|
@@ -613,6 +780,16 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
613
780
|
.filter(Boolean),
|
|
614
781
|
),
|
|
615
782
|
).sort();
|
|
783
|
+
if (binPaths.length) {
|
|
784
|
+
recordDegradation("binary.paths.inferred", {
|
|
785
|
+
ecosystem: "generic",
|
|
786
|
+
tool: "trivy",
|
|
787
|
+
impact: "none",
|
|
788
|
+
path: src,
|
|
789
|
+
detail:
|
|
790
|
+
"The image config carried no PATH, so executable directories were inferred by scanning the filesystem.",
|
|
791
|
+
});
|
|
792
|
+
}
|
|
616
793
|
if (DEBUG_MODE && binPaths.length) {
|
|
617
794
|
console.log(
|
|
618
795
|
`Falling back to inferred binary paths for ${src}: ${binPaths.join(", ")}`,
|
|
@@ -721,9 +898,14 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
721
898
|
}
|
|
722
899
|
}
|
|
723
900
|
break;
|
|
901
|
+
case "alpaquita":
|
|
902
|
+
purl_type = "apk";
|
|
903
|
+
break;
|
|
724
904
|
default:
|
|
725
905
|
if (distro_id_like.includes("debian")) {
|
|
726
906
|
purl_type = "deb";
|
|
907
|
+
} else if (distro_id_like.includes("alpine")) {
|
|
908
|
+
purl_type = "apk";
|
|
727
909
|
} else if (
|
|
728
910
|
distro_id_like.includes("rhel") ||
|
|
729
911
|
distro_id_like.includes("centos") ||
|
|
@@ -739,6 +921,11 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
739
921
|
distro_codename = OS_DISTRO_ALIAS[distro_id];
|
|
740
922
|
}
|
|
741
923
|
}
|
|
924
|
+
// Canonicalise the distro qualifier vendor (e.g. "mariner-2.0" ->
|
|
925
|
+
// "azure-linux-2.0") so every spelling of one release yields a single
|
|
926
|
+
// qualifier value. This runs before distro_id is used to overwrite
|
|
927
|
+
// trivy's own qualifier below.
|
|
928
|
+
distro_id = canonicalDistroQualifier(distro_id);
|
|
742
929
|
const tmpDependencies = {};
|
|
743
930
|
tools = enrichToolComponents(
|
|
744
931
|
(Array.isArray(tmpBom?.metadata?.tools)
|
|
@@ -768,42 +955,23 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
768
955
|
comp.group = group;
|
|
769
956
|
comp.name = name;
|
|
770
957
|
const modularityLabel = extractModularityLabel(comp);
|
|
958
|
+
repairOsComponentPurl(comp, distro_id);
|
|
771
959
|
try {
|
|
772
960
|
purlObj = Purl.parse(comp.purl);
|
|
773
961
|
purlObj.qualifiers = purlObj.qualifiers || {};
|
|
774
962
|
} catch (_err) {
|
|
775
963
|
// continue regardless of error
|
|
776
964
|
}
|
|
777
|
-
// Canonicalise the distro vendor namespace
|
|
778
|
-
//
|
|
779
|
-
//
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
group = os_ns_alias;
|
|
788
|
-
comp.group = os_ns_alias;
|
|
789
|
-
purlObj.namespace = os_ns_alias;
|
|
790
|
-
const distroQual = purlObj.qualifiers?.distro;
|
|
791
|
-
if (distroQual?.startsWith(`${os_ns}-`)) {
|
|
792
|
-
purlObj.qualifiers.distro = `${os_ns_alias}-${distroQual.slice(
|
|
793
|
-
os_ns.length + 1,
|
|
794
|
-
)}`;
|
|
795
|
-
}
|
|
796
|
-
comp.purl = build({
|
|
797
|
-
type: purlObj.type,
|
|
798
|
-
namespace: os_ns_alias || null,
|
|
799
|
-
name: name,
|
|
800
|
-
version: purlObj.version || null,
|
|
801
|
-
qualifiers: purlObj.qualifiers || null,
|
|
802
|
-
subpath: purlObj.subpath || null,
|
|
803
|
-
});
|
|
804
|
-
comp["bom-ref"] = decodeURIComponent(comp.purl);
|
|
805
|
-
}
|
|
806
|
-
}
|
|
965
|
+
// Canonicalise the distro vendor namespace (see
|
|
966
|
+
// canonicaliseOsPackageNamespace) before any later bom-ref
|
|
967
|
+
// rebuild so the rewritten purl, group and bom-ref stay in sync.
|
|
968
|
+
group = canonicaliseOsPackageNamespace(
|
|
969
|
+
comp,
|
|
970
|
+
purlObj,
|
|
971
|
+
name,
|
|
972
|
+
group,
|
|
973
|
+
distro_id,
|
|
974
|
+
);
|
|
807
975
|
if (
|
|
808
976
|
group === "" &&
|
|
809
977
|
purlObj &&
|
|
@@ -812,8 +980,7 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
812
980
|
try {
|
|
813
981
|
if (purlObj?.namespace && purlObj.namespace !== "") {
|
|
814
982
|
group =
|
|
815
|
-
|
|
816
|
-
purlObj.namespace;
|
|
983
|
+
OS_NAMESPACE_ALIAS[purlObj.namespace] || purlObj.namespace;
|
|
817
984
|
comp.group = group;
|
|
818
985
|
purlObj.namespace = group;
|
|
819
986
|
}
|
|
@@ -1107,6 +1274,7 @@ export async function getOSPackages(src, imageConfig, options = {}) {
|
|
|
1107
1274
|
}
|
|
1108
1275
|
}
|
|
1109
1276
|
}
|
|
1277
|
+
dropRedundantSourceComponents(pkgList);
|
|
1110
1278
|
const rootfsRepositoryInventory = await collectRootfsRepositoryInventory(
|
|
1111
1279
|
src,
|
|
1112
1280
|
options,
|
|
@@ -3144,6 +3312,14 @@ export function getDotnetSlices(src, slicesFile) {
|
|
|
3144
3312
|
"You must install or update .NET to run this application",
|
|
3145
3313
|
)
|
|
3146
3314
|
) {
|
|
3315
|
+
recordDegradation("dotnet.sdk.missing", {
|
|
3316
|
+
ecosystem: "csharp",
|
|
3317
|
+
tool: "dotnet",
|
|
3318
|
+
impact: "components",
|
|
3319
|
+
command: `dosai ${args.join(" ")}`,
|
|
3320
|
+
detail:
|
|
3321
|
+
"dosai reported that no .NET SDK is installed, so dotnet-related evidence for this image is incomplete.",
|
|
3322
|
+
});
|
|
3147
3323
|
console.log(
|
|
3148
3324
|
"Dotnet SDK is not installed. Please use the cdxgen dotnet container images to generate slices for this project.",
|
|
3149
3325
|
);
|
package/lib/managers/docker.js
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
recordDecisionActivity,
|
|
22
22
|
recordSensitiveFileRead,
|
|
23
23
|
} from "../core/activity.js";
|
|
24
|
+
import { deferFailOnError } from "../core/deferredExit.js";
|
|
24
25
|
import { extractPathEnv } from "../core/env.js";
|
|
25
26
|
import {
|
|
26
27
|
getAllFiles,
|
|
@@ -1407,7 +1408,11 @@ export const extractTar = async (fullImageName, dir, options) => {
|
|
|
1407
1408
|
} else if (DEBUG_MODE) {
|
|
1408
1409
|
console.log(err.code, "is not handled yet in extractTar method.");
|
|
1409
1410
|
}
|
|
1410
|
-
options
|
|
1411
|
+
deferFailOnError(options, {
|
|
1412
|
+
ecosystem: "container",
|
|
1413
|
+
tool: "docker",
|
|
1414
|
+
detail: "extracting the image archive failed",
|
|
1415
|
+
});
|
|
1411
1416
|
return false;
|
|
1412
1417
|
}
|
|
1413
1418
|
};
|
|
@@ -1598,10 +1603,18 @@ export const exportArchive = async (fullImageName, options = {}) => {
|
|
|
1598
1603
|
return exportData;
|
|
1599
1604
|
}
|
|
1600
1605
|
console.log(`Unable to extract image archive to ${tempDir}`);
|
|
1601
|
-
options
|
|
1606
|
+
deferFailOnError(options, {
|
|
1607
|
+
ecosystem: "container",
|
|
1608
|
+
tool: "docker",
|
|
1609
|
+
detail: "the image archive could not be extracted",
|
|
1610
|
+
});
|
|
1602
1611
|
} catch (_err) {
|
|
1603
1612
|
// ignore
|
|
1604
|
-
options
|
|
1613
|
+
deferFailOnError(options, {
|
|
1614
|
+
ecosystem: "container",
|
|
1615
|
+
tool: "docker",
|
|
1616
|
+
detail: "the image archive could not be extracted",
|
|
1617
|
+
});
|
|
1605
1618
|
}
|
|
1606
1619
|
return undefined;
|
|
1607
1620
|
};
|
|
@@ -1686,7 +1699,11 @@ export const extractFromManifest = async (
|
|
|
1686
1699
|
}
|
|
1687
1700
|
} else {
|
|
1688
1701
|
console.log(err);
|
|
1689
|
-
options
|
|
1702
|
+
deferFailOnError(options, {
|
|
1703
|
+
ecosystem: "container",
|
|
1704
|
+
tool: "docker",
|
|
1705
|
+
detail: "reading the image manifest failed",
|
|
1706
|
+
});
|
|
1690
1707
|
}
|
|
1691
1708
|
}
|
|
1692
1709
|
}
|
|
@@ -1713,7 +1730,11 @@ export const extractFromManifest = async (
|
|
|
1713
1730
|
? join(allLayersExplodedDir, lastLayerConfig.config.WorkingDir)
|
|
1714
1731
|
: "";
|
|
1715
1732
|
} catch (_err) {
|
|
1716
|
-
options
|
|
1733
|
+
deferFailOnError(options, {
|
|
1734
|
+
ecosystem: "container",
|
|
1735
|
+
tool: "docker",
|
|
1736
|
+
detail: "reading the image manifest failed",
|
|
1737
|
+
});
|
|
1717
1738
|
}
|
|
1718
1739
|
}
|
|
1719
1740
|
}
|
package/lib/server/server.js
CHANGED
|
@@ -520,7 +520,7 @@ const start = (options) => {
|
|
|
520
520
|
ensureAiProvenanceProperties(bomNSData.bomJson, reqOptions);
|
|
521
521
|
await ensureAiOversightProperties(bomNSData.bomJson, reqOptions);
|
|
522
522
|
}
|
|
523
|
-
bomNSData = postProcess(
|
|
523
|
+
bomNSData = await postProcess(
|
|
524
524
|
bomNSData,
|
|
525
525
|
{ ...reqOptions, executeOsQuery },
|
|
526
526
|
srcDir,
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared assembly of CycloneDX document-level annotations.
|
|
3
|
+
*
|
|
4
|
+
* Two postgen features attach findings to the BOM document as annotations
|
|
5
|
+
* whose `subjects` is the BOM serial number: the BOM audit's rule findings and
|
|
6
|
+
* the build introspection's remediations. Both describe the document rather
|
|
7
|
+
* than a component, both are annotated by the cdxgen tool component from
|
|
8
|
+
* metadata, and both carry the document's own timestamp because an annotation
|
|
9
|
+
* describes the document it is attached to. The structured facts travel as
|
|
10
|
+
* `name`/`value` pairs rendered into the annotation text, one `:evidence:<key>`
|
|
11
|
+
* entry per evidence item.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { DEBUG_MODE } from "../../core/activity.js";
|
|
15
|
+
import { getTimestamp } from "../../core/fs.js";
|
|
16
|
+
import { buildAnnotationText } from "../../helpers/annotationFormatter.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Find the cdxgen tool component that annotates the document.
|
|
20
|
+
*
|
|
21
|
+
* @param {Object} bomJson CycloneDX BOM
|
|
22
|
+
* @returns {Object[]} The cdxgen tool components, empty when metadata carries none.
|
|
23
|
+
*/
|
|
24
|
+
function findCdxgenAnnotator(bomJson) {
|
|
25
|
+
return (
|
|
26
|
+
bomJson?.metadata?.tools?.components?.filter((c) => c.name === "cdxgen") ||
|
|
27
|
+
[]
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Flatten an evidence object into `:evidence:<key>` property entries. Nested
|
|
33
|
+
* values are serialized as JSON so every property value stays a string.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} propertyNamePrefix Property namespace, e.g. "cdx:audit".
|
|
36
|
+
* @param {Object} evidence Arbitrary key/value evidence facts.
|
|
37
|
+
* @returns {{name: string, value: string}[]} Property entries.
|
|
38
|
+
*/
|
|
39
|
+
export function evidenceProperties(propertyNamePrefix, evidence) {
|
|
40
|
+
const properties = [];
|
|
41
|
+
if (evidence && typeof evidence === "object") {
|
|
42
|
+
for (const [key, value] of Object.entries(evidence)) {
|
|
43
|
+
const propValue =
|
|
44
|
+
typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
45
|
+
properties.push({
|
|
46
|
+
name: `${propertyNamePrefix}:evidence:${key}`,
|
|
47
|
+
value: propValue,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return properties;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Build one document-level annotation. Returns undefined when the BOM carries
|
|
56
|
+
* no cdxgen tool component to annotate with; the caller decides whether that
|
|
57
|
+
* is worth a warning.
|
|
58
|
+
*
|
|
59
|
+
* @param {Object} params Annotation inputs.
|
|
60
|
+
* @param {Object} params.bomJson CycloneDX BOM the annotation describes.
|
|
61
|
+
* @param {string} params.message Leading annotation sentence.
|
|
62
|
+
* @param {{name: string, value: string}[]} params.properties Structured facts rendered into the annotation text.
|
|
63
|
+
* @returns {Object|undefined} CycloneDX annotation, or undefined without an annotator.
|
|
64
|
+
*/
|
|
65
|
+
export function buildDocumentAnnotation({ bomJson, message, properties }) {
|
|
66
|
+
const cdxgenAnnotator = findCdxgenAnnotator(bomJson);
|
|
67
|
+
if (!cdxgenAnnotator.length) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
subjects: [bomJson.serialNumber],
|
|
72
|
+
annotator: {
|
|
73
|
+
component: cdxgenAnnotator[0],
|
|
74
|
+
},
|
|
75
|
+
// An annotation describes the document it is attached to, so it carries
|
|
76
|
+
// that document's timestamp rather than the moment the annotation was
|
|
77
|
+
// built.
|
|
78
|
+
timestamp: bomJson.metadata?.timestamp || getTimestamp(),
|
|
79
|
+
text: buildAnnotationText(message, properties),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Warn that a document could not be annotated because it carries no cdxgen
|
|
85
|
+
* tool component. Suppressed outside debug mode, where the missing annotator
|
|
86
|
+
* is an expected shape for foreign BOMs.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} feature Feature that wanted to annotate.
|
|
89
|
+
* @returns {void}
|
|
90
|
+
*/
|
|
91
|
+
export function warnMissingAnnotator(feature) {
|
|
92
|
+
if (DEBUG_MODE) {
|
|
93
|
+
console.warn(
|
|
94
|
+
`Cannot create ${feature} annotations: cdxgen tool component not found in metadata`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|