@cyclonedx/cdxgen 12.2.0 → 12.3.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 +242 -90
- package/bin/audit.js +191 -0
- package/bin/cdxgen.js +532 -168
- package/bin/convert.js +99 -0
- package/bin/evinse.js +23 -0
- package/bin/repl.js +339 -8
- package/bin/sign.js +8 -0
- package/bin/validate.js +8 -0
- package/bin/verify.js +8 -0
- package/data/container-knowledge-index.json +125 -0
- package/data/gtfobins-index.json +6296 -0
- package/data/lolbas-index.json +150 -0
- package/data/queries-darwin.json +63 -3
- package/data/queries-win.json +45 -3
- package/data/queries.json +74 -2
- package/data/rules/chrome-extensions.yaml +240 -0
- package/data/rules/ci-permissions.yaml +478 -18
- package/data/rules/container-risk.yaml +270 -0
- package/data/rules/obom-runtime.yaml +891 -0
- package/data/rules/package-integrity.yaml +49 -0
- package/data/spdx-export.schema.json +6794 -0
- package/data/spdx-model-v3.0.1.jsonld +15999 -0
- package/lib/audit/index.js +1924 -0
- package/lib/audit/index.poku.js +1488 -0
- package/lib/audit/progress.js +137 -0
- package/lib/audit/progress.poku.js +188 -0
- package/lib/audit/reporters.js +618 -0
- package/lib/audit/scoring.js +310 -0
- package/lib/audit/scoring.poku.js +341 -0
- package/lib/audit/targets.js +260 -0
- package/lib/audit/targets.poku.js +331 -0
- package/lib/cli/index.js +276 -68
- package/lib/cli/index.poku.js +368 -0
- package/lib/helpers/analyzer.js +1052 -5
- package/lib/helpers/analyzer.poku.js +301 -0
- package/lib/helpers/annotationFormatter.js +49 -0
- package/lib/helpers/annotationFormatter.poku.js +44 -0
- package/lib/helpers/bomUtils.js +36 -0
- package/lib/helpers/bomUtils.poku.js +51 -0
- package/lib/helpers/caxa.js +2 -2
- package/lib/helpers/chromextutils.js +1153 -0
- package/lib/helpers/chromextutils.poku.js +493 -0
- package/lib/helpers/ciParsers/githubActions.js +1632 -45
- package/lib/helpers/ciParsers/githubActions.poku.js +853 -1
- package/lib/helpers/containerRisk.js +186 -0
- package/lib/helpers/containerRisk.poku.js +52 -0
- package/lib/helpers/depsUtils.js +16 -0
- package/lib/helpers/depsUtils.poku.js +58 -1
- package/lib/helpers/display.js +245 -61
- package/lib/helpers/display.poku.js +162 -2
- package/lib/helpers/exportUtils.js +123 -0
- package/lib/helpers/exportUtils.poku.js +60 -0
- package/lib/helpers/formulationParsers.js +69 -0
- package/lib/helpers/formulationParsers.poku.js +44 -0
- package/lib/helpers/gtfobins.js +189 -0
- package/lib/helpers/gtfobins.poku.js +49 -0
- package/lib/helpers/lolbas.js +267 -0
- package/lib/helpers/lolbas.poku.js +39 -0
- package/lib/helpers/osqueryTransform.js +84 -0
- package/lib/helpers/osqueryTransform.poku.js +49 -0
- package/lib/helpers/provenanceUtils.js +193 -0
- package/lib/helpers/provenanceUtils.poku.js +145 -0
- package/lib/helpers/pylockutils.js +281 -0
- package/lib/helpers/pylockutils.poku.js +48 -0
- package/lib/helpers/registryProvenance.js +793 -0
- package/lib/helpers/registryProvenance.poku.js +452 -0
- package/lib/helpers/remote/dependency-track.js +84 -0
- package/lib/helpers/remote/dependency-track.poku.js +119 -0
- package/lib/helpers/source.js +1267 -0
- package/lib/helpers/source.poku.js +771 -0
- package/lib/helpers/spdxUtils.js +97 -0
- package/lib/helpers/spdxUtils.poku.js +70 -0
- package/lib/helpers/table.js +384 -0
- package/lib/helpers/table.poku.js +186 -0
- package/lib/helpers/unicodeScan.js +147 -0
- package/lib/helpers/unicodeScan.poku.js +45 -0
- package/lib/helpers/utils.js +882 -136
- package/lib/helpers/utils.poku.js +995 -91
- package/lib/managers/binary.js +29 -5
- package/lib/managers/docker.js +179 -52
- package/lib/managers/docker.poku.js +327 -28
- package/lib/managers/oci.js +107 -23
- package/lib/managers/oci.poku.js +132 -0
- package/lib/server/openapi.yaml +50 -0
- package/lib/server/server.js +228 -331
- package/lib/server/server.poku.js +220 -5
- package/lib/stages/postgen/annotator.js +7 -0
- package/lib/stages/postgen/annotator.poku.js +40 -0
- package/lib/stages/postgen/auditBom.js +20 -5
- package/lib/stages/postgen/auditBom.poku.js +1729 -67
- package/lib/stages/postgen/postgen.js +40 -0
- package/lib/stages/postgen/postgen.poku.js +47 -0
- package/lib/stages/postgen/ruleEngine.js +80 -2
- package/lib/stages/postgen/spdxConverter.js +796 -0
- package/lib/stages/postgen/spdxConverter.poku.js +341 -0
- package/lib/validator/bomValidator.js +232 -0
- package/lib/validator/bomValidator.poku.js +70 -0
- package/lib/validator/complianceRules.js +70 -7
- package/lib/validator/complianceRules.poku.js +30 -0
- package/lib/validator/reporters/annotations.js +2 -2
- package/lib/validator/reporters/console.js +13 -2
- package/lib/validator/reporters.poku.js +13 -0
- package/package.json +10 -8
- package/types/bin/audit.d.ts +3 -0
- package/types/bin/audit.d.ts.map +1 -0
- package/types/bin/convert.d.ts +3 -0
- package/types/bin/convert.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +115 -0
- package/types/lib/audit/index.d.ts.map +1 -0
- package/types/lib/audit/progress.d.ts +27 -0
- package/types/lib/audit/progress.d.ts.map +1 -0
- package/types/lib/audit/reporters.d.ts +35 -0
- package/types/lib/audit/reporters.d.ts.map +1 -0
- package/types/lib/audit/scoring.d.ts +35 -0
- package/types/lib/audit/scoring.d.ts.map +1 -0
- package/types/lib/audit/targets.d.ts +63 -0
- package/types/lib/audit/targets.d.ts.map +1 -0
- package/types/lib/cli/index.d.ts +8 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts +13 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/annotationFormatter.d.ts +23 -0
- package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
- package/types/lib/helpers/bomUtils.d.ts +5 -0
- package/types/lib/helpers/bomUtils.d.ts.map +1 -0
- package/types/lib/helpers/chromextutils.d.ts +97 -0
- package/types/lib/helpers/chromextutils.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts +3 -8
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/containerRisk.d.ts +17 -0
- package/types/lib/helpers/containerRisk.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts +4 -1
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/exportUtils.d.ts +40 -0
- package/types/lib/helpers/exportUtils.d.ts.map +1 -0
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
- package/types/lib/helpers/gtfobins.d.ts +17 -0
- package/types/lib/helpers/gtfobins.d.ts.map +1 -0
- package/types/lib/helpers/lolbas.d.ts +16 -0
- package/types/lib/helpers/lolbas.d.ts.map +1 -0
- package/types/lib/helpers/osqueryTransform.d.ts +7 -0
- package/types/lib/helpers/osqueryTransform.d.ts.map +1 -0
- package/types/lib/helpers/provenanceUtils.d.ts +90 -0
- package/types/lib/helpers/provenanceUtils.d.ts.map +1 -0
- package/types/lib/helpers/pylockutils.d.ts +51 -0
- package/types/lib/helpers/pylockutils.d.ts.map +1 -0
- package/types/lib/helpers/registryProvenance.d.ts +17 -0
- package/types/lib/helpers/registryProvenance.d.ts.map +1 -0
- package/types/lib/helpers/remote/dependency-track.d.ts +16 -0
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
- package/types/lib/helpers/source.d.ts +141 -0
- package/types/lib/helpers/source.d.ts.map +1 -0
- package/types/lib/helpers/spdxUtils.d.ts +2 -0
- package/types/lib/helpers/spdxUtils.d.ts.map +1 -0
- package/types/lib/helpers/table.d.ts +6 -0
- package/types/lib/helpers/table.d.ts.map +1 -0
- package/types/lib/helpers/unicodeScan.d.ts +46 -0
- package/types/lib/helpers/unicodeScan.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +30 -11
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +0 -35
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/spdxConverter.d.ts +11 -0
- package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
- package/types/lib/validator/bomValidator.d.ts +1 -0
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/complianceRules.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/bin/dependencies.d.ts +0 -3
- package/types/bin/dependencies.d.ts.map +0 -1
- package/types/bin/licenses.d.ts +0 -3
- package/types/bin/licenses.d.ts.map +0 -1
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
import { buildAnnotationText } from "../helpers/annotationFormatter.js";
|
|
2
|
+
import { table } from "../helpers/table.js";
|
|
3
|
+
import { getTimestamp } from "../helpers/utils.js";
|
|
4
|
+
import { severityMeetsThreshold } from "./scoring.js";
|
|
5
|
+
|
|
6
|
+
const SARIF_VERSION = "2.1.0";
|
|
7
|
+
const SARIF_SCHEMA =
|
|
8
|
+
"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/Schemata/sarif-schema-2.1.0.json";
|
|
9
|
+
const AUDIT_ERROR_RULE_ID = "AUDIT-ERROR";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Filter results by final severity threshold.
|
|
13
|
+
*
|
|
14
|
+
* @param {object[]} results results list
|
|
15
|
+
* @param {string} minSeverity threshold severity
|
|
16
|
+
* @returns {object[]} filtered results
|
|
17
|
+
*/
|
|
18
|
+
function filterResults(results, minSeverity) {
|
|
19
|
+
return results.filter((result) =>
|
|
20
|
+
severityMeetsThreshold(result?.assessment?.severity || "none", minSeverity),
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function effectiveResults(report) {
|
|
25
|
+
return report.groupedResults?.length
|
|
26
|
+
? report.groupedResults
|
|
27
|
+
: report.results || [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function severityToSarifLevel(severity) {
|
|
31
|
+
switch (severity) {
|
|
32
|
+
case "critical":
|
|
33
|
+
case "high":
|
|
34
|
+
return "error";
|
|
35
|
+
case "medium":
|
|
36
|
+
return "warning";
|
|
37
|
+
default:
|
|
38
|
+
return "note";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function splitCsv(value) {
|
|
43
|
+
return String(value || "")
|
|
44
|
+
.split(",")
|
|
45
|
+
.map((entry) => entry.trim())
|
|
46
|
+
.filter(Boolean);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function extractLocalDispatchEdge(finding) {
|
|
50
|
+
const senderFile = finding?.location?.file;
|
|
51
|
+
const receiverFiles = splitCsv(finding?.evidence?.localReceiverWorkflowFiles);
|
|
52
|
+
const receiverNames = splitCsv(finding?.evidence?.localReceiverWorkflowNames);
|
|
53
|
+
const matchBasis = splitCsv(finding?.evidence?.localReceiverMatchBasis);
|
|
54
|
+
const hasLocalDispatchReceiver =
|
|
55
|
+
finding?.evidence?.hasLocalDispatchReceiver === "true" ||
|
|
56
|
+
receiverFiles.length > 0 ||
|
|
57
|
+
receiverNames.length > 0;
|
|
58
|
+
if (!senderFile || !hasLocalDispatchReceiver) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
matchBasis,
|
|
63
|
+
receiverFiles,
|
|
64
|
+
receiverNames,
|
|
65
|
+
senderFile,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function formatLocalDispatchEdge(edge) {
|
|
70
|
+
if (!edge) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const receiverLabel = edge.receiverNames[0] || edge.receiverFiles[0];
|
|
74
|
+
if (!receiverLabel) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
return `${edge.senderFile} -> ${receiverLabel}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function targetSarifLocations(result, findingLocation) {
|
|
81
|
+
const bomRef =
|
|
82
|
+
findingLocation?.bomRef ||
|
|
83
|
+
result?.target?.bomRefs?.[0] ||
|
|
84
|
+
result?.target?.purl ||
|
|
85
|
+
result?.grouping?.label;
|
|
86
|
+
if (findingLocation?.file) {
|
|
87
|
+
return [
|
|
88
|
+
{
|
|
89
|
+
physicalLocation: {
|
|
90
|
+
artifactLocation: {
|
|
91
|
+
uri: findingLocation.file,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
logicalLocations: bomRef
|
|
95
|
+
? [{ fullyQualifiedName: bomRef, kind: "package" }]
|
|
96
|
+
: undefined,
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
if (bomRef) {
|
|
101
|
+
return [
|
|
102
|
+
{
|
|
103
|
+
logicalLocations: [{ fullyQualifiedName: bomRef, kind: "package" }],
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
return [
|
|
108
|
+
{
|
|
109
|
+
logicalLocations: [{ fullyQualifiedName: "cdx-audit", kind: "tool" }],
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function resultProperties(result) {
|
|
115
|
+
const properties = {
|
|
116
|
+
auditSeverity: result?.assessment?.severity || "none",
|
|
117
|
+
confidence: result?.assessment?.confidenceLabel,
|
|
118
|
+
reasons: result?.assessment?.reasons || [],
|
|
119
|
+
score: result?.assessment?.score,
|
|
120
|
+
status: result?.status,
|
|
121
|
+
target: {
|
|
122
|
+
bomRefs: result?.target?.bomRefs || [],
|
|
123
|
+
name: result?.target?.name,
|
|
124
|
+
namespace: result?.target?.namespace,
|
|
125
|
+
purl: result?.target?.purl,
|
|
126
|
+
type: result?.target?.type,
|
|
127
|
+
version: result?.target?.version,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
if (result?.grouping) {
|
|
131
|
+
properties.grouping = result.grouping;
|
|
132
|
+
}
|
|
133
|
+
if (result?.repoUrl) {
|
|
134
|
+
properties.repoUrl = result.repoUrl;
|
|
135
|
+
}
|
|
136
|
+
if (result?.sourceDirectoryConfidence) {
|
|
137
|
+
properties.sourceDirectoryConfidence = result.sourceDirectoryConfidence;
|
|
138
|
+
}
|
|
139
|
+
return properties;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function findingProperties(finding) {
|
|
143
|
+
const properties = {
|
|
144
|
+
attackTactics: finding?.attackTactics,
|
|
145
|
+
attackTechniques: finding?.attackTechniques,
|
|
146
|
+
category: finding?.category,
|
|
147
|
+
mitigation: finding?.mitigation,
|
|
148
|
+
severity: finding?.severity,
|
|
149
|
+
tags: attackTags(finding),
|
|
150
|
+
};
|
|
151
|
+
const localDispatchEdge = extractLocalDispatchEdge(finding);
|
|
152
|
+
if (localDispatchEdge) {
|
|
153
|
+
properties.localDispatchEdge = formatLocalDispatchEdge(localDispatchEdge);
|
|
154
|
+
properties.localDispatchReceiverFiles = localDispatchEdge.receiverFiles;
|
|
155
|
+
properties.localDispatchReceiverNames = localDispatchEdge.receiverNames;
|
|
156
|
+
properties.localDispatchMatchBasis = localDispatchEdge.matchBasis;
|
|
157
|
+
}
|
|
158
|
+
return properties;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function sarifRelatedLocations(finding) {
|
|
162
|
+
const localDispatchEdge = extractLocalDispatchEdge(finding);
|
|
163
|
+
if (!localDispatchEdge?.receiverFiles?.length) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
return localDispatchEdge.receiverFiles.map((receiverFile, index) => ({
|
|
167
|
+
id: index + 1,
|
|
168
|
+
logicalLocations: localDispatchEdge.receiverNames[index]
|
|
169
|
+
? [
|
|
170
|
+
{
|
|
171
|
+
fullyQualifiedName: localDispatchEdge.receiverNames[index],
|
|
172
|
+
kind: "function",
|
|
173
|
+
},
|
|
174
|
+
]
|
|
175
|
+
: undefined,
|
|
176
|
+
message: {
|
|
177
|
+
text: `Local dispatch receiver: ${
|
|
178
|
+
localDispatchEdge.receiverNames[index] || receiverFile
|
|
179
|
+
}`,
|
|
180
|
+
},
|
|
181
|
+
physicalLocation: {
|
|
182
|
+
artifactLocation: {
|
|
183
|
+
uri: receiverFile,
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function sarifHelp(finding, result) {
|
|
190
|
+
const helpText = [];
|
|
191
|
+
if (finding?.mitigation) {
|
|
192
|
+
helpText.push(finding.mitigation);
|
|
193
|
+
}
|
|
194
|
+
const upstreamEscalation = summarizeUpstreamEscalation(result);
|
|
195
|
+
if (upstreamEscalation) {
|
|
196
|
+
helpText.push(upstreamEscalation);
|
|
197
|
+
}
|
|
198
|
+
if (!helpText.length) {
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
markdown: helpText
|
|
203
|
+
.map((entry, index) =>
|
|
204
|
+
index === 0
|
|
205
|
+
? `**Remediation:** ${entry}`
|
|
206
|
+
: `**External maintainer path:** ${entry}`,
|
|
207
|
+
)
|
|
208
|
+
.join("\n\n"),
|
|
209
|
+
text: helpText.join(" "),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function attackTags(finding) {
|
|
214
|
+
return [
|
|
215
|
+
...(finding?.attackTactics || []),
|
|
216
|
+
...(finding?.attackTechniques || []),
|
|
217
|
+
].map((id) => `ATT&CK:${id}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function deriveSarifRules(entries) {
|
|
221
|
+
const rulesById = new Map();
|
|
222
|
+
for (const entry of entries) {
|
|
223
|
+
const finding = entry.finding;
|
|
224
|
+
const result = entry.result;
|
|
225
|
+
if (rulesById.has(finding.ruleId)) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
rulesById.set(finding.ruleId, {
|
|
229
|
+
id: finding.ruleId,
|
|
230
|
+
name: finding.name || finding.ruleId,
|
|
231
|
+
shortDescription: {
|
|
232
|
+
text: finding.name || finding.ruleId,
|
|
233
|
+
},
|
|
234
|
+
fullDescription: {
|
|
235
|
+
text: finding.description || finding.name || finding.ruleId,
|
|
236
|
+
},
|
|
237
|
+
defaultConfiguration: {
|
|
238
|
+
level: severityToSarifLevel(finding.severity),
|
|
239
|
+
},
|
|
240
|
+
properties: {
|
|
241
|
+
attackTactics: finding.attackTactics,
|
|
242
|
+
attackTechniques: finding.attackTechniques,
|
|
243
|
+
category: finding.category,
|
|
244
|
+
engine: finding.engine || "cdx-audit",
|
|
245
|
+
tags: attackTags(finding),
|
|
246
|
+
},
|
|
247
|
+
help: sarifHelp(finding, result),
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return [...rulesById.values()];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function findingToSarifResult(finding, result) {
|
|
254
|
+
const nextAction = summarizeNextAction(result);
|
|
255
|
+
const upstreamEscalation = summarizeUpstreamEscalation(result);
|
|
256
|
+
return {
|
|
257
|
+
level: severityToSarifLevel(
|
|
258
|
+
finding?.severity || result?.assessment?.severity,
|
|
259
|
+
),
|
|
260
|
+
locations: targetSarifLocations(result, finding?.location),
|
|
261
|
+
message: {
|
|
262
|
+
text: finding?.message || finding?.description || finding?.ruleId,
|
|
263
|
+
},
|
|
264
|
+
properties: {
|
|
265
|
+
...resultProperties(result),
|
|
266
|
+
...findingProperties(finding),
|
|
267
|
+
nextAction,
|
|
268
|
+
upstreamEscalation,
|
|
269
|
+
},
|
|
270
|
+
relatedLocations: sarifRelatedLocations(finding),
|
|
271
|
+
ruleId: finding?.ruleId || AUDIT_ERROR_RULE_ID,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function errorToSarifEntry(result) {
|
|
276
|
+
const severity = result?.assessment?.severity || "high";
|
|
277
|
+
return {
|
|
278
|
+
category: result?.errorType || "runtime",
|
|
279
|
+
description:
|
|
280
|
+
"cdx-audit could not complete predictive analysis for the resolved target.",
|
|
281
|
+
message: result?.error || "cdx-audit failed to analyze the target.",
|
|
282
|
+
name: "Target analysis error",
|
|
283
|
+
ruleId: AUDIT_ERROR_RULE_ID,
|
|
284
|
+
severity,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function consoleTargetLabel(result) {
|
|
289
|
+
if (result?.grouping?.label) {
|
|
290
|
+
return result.grouping.label;
|
|
291
|
+
}
|
|
292
|
+
if (result?.target?.purl) {
|
|
293
|
+
return result.target.purl;
|
|
294
|
+
}
|
|
295
|
+
const namespacePrefix = result?.target?.namespace
|
|
296
|
+
? `${result.target.namespace}/`
|
|
297
|
+
: "";
|
|
298
|
+
const versionSuffix = result?.target?.version
|
|
299
|
+
? `@${result.target.version}`
|
|
300
|
+
: "";
|
|
301
|
+
return `${result?.target?.type || "pkg"}:${namespacePrefix}${result?.target?.name || "unknown"}${versionSuffix}`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function topFinding(result) {
|
|
305
|
+
return result?.findings?.[0];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function summarizeWhy(result) {
|
|
309
|
+
const finding = topFinding(result);
|
|
310
|
+
const localDispatchEdge = extractLocalDispatchEdge(finding);
|
|
311
|
+
if (finding?.message && localDispatchEdge) {
|
|
312
|
+
return `${finding.ruleId} — ${finding.message} (${formatLocalDispatchEdge(localDispatchEdge)})`;
|
|
313
|
+
}
|
|
314
|
+
if (finding?.message) {
|
|
315
|
+
return `${finding.ruleId} — ${finding.message}`;
|
|
316
|
+
}
|
|
317
|
+
if (result?.error) {
|
|
318
|
+
return result.error;
|
|
319
|
+
}
|
|
320
|
+
return (
|
|
321
|
+
result?.assessment?.reasons?.[0] || "Review the predictive audit details."
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function groupedPurlPreview(result) {
|
|
326
|
+
if (!result?.grouping?.groupedPurls?.length) {
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
329
|
+
const preview = result.grouping.groupedPurls.slice(0, 2).join(", ");
|
|
330
|
+
return result.grouping.groupedPurls.length > 2 ? `${preview}, …` : preview;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function summarizeReviewFocus(result) {
|
|
334
|
+
const finding = topFinding(result);
|
|
335
|
+
const localDispatchEdge = extractLocalDispatchEdge(finding);
|
|
336
|
+
if (localDispatchEdge?.receiverFiles?.length) {
|
|
337
|
+
return `Review sender '${localDispatchEdge.senderFile}' together with receiver '${localDispatchEdge.receiverFiles[0]}' for the flagged workflow-dispatch chain.`;
|
|
338
|
+
}
|
|
339
|
+
if (finding?.location?.file && result?.repoUrl) {
|
|
340
|
+
return `Review '${finding.location.file}' in ${result.repoUrl}.`;
|
|
341
|
+
}
|
|
342
|
+
if (finding?.location?.file) {
|
|
343
|
+
return `Review '${finding.location.file}' for the flagged workflow or release step.`;
|
|
344
|
+
}
|
|
345
|
+
if (result?.grouping?.memberCount > 1) {
|
|
346
|
+
return `Start with ${groupedPurlPreview(result) || result.grouping.label} and inspect the shared repository or workflow pattern.`;
|
|
347
|
+
}
|
|
348
|
+
if (result?.repoUrl) {
|
|
349
|
+
return `Review ${result.repoUrl} for the flagged release workflow, provenance, or publish behavior.`;
|
|
350
|
+
}
|
|
351
|
+
if (finding?.location?.purl) {
|
|
352
|
+
return `Inspect ${finding.location.purl} in your dependency tree and verify its source and release posture.`;
|
|
353
|
+
}
|
|
354
|
+
if (result?.target?.purl) {
|
|
355
|
+
return `Inspect ${result.target.purl} and verify its source repository, release workflow, and provenance signals.`;
|
|
356
|
+
}
|
|
357
|
+
return "Review the reported target and verify the associated repository, workflow, or package metadata.";
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function summarizeUpstreamEscalation(result) {
|
|
361
|
+
const finding = topFinding(result);
|
|
362
|
+
if (finding?.location?.file && result?.repoUrl) {
|
|
363
|
+
return `If you do not maintain this repository, open an issue or discussion with the upstream maintainers and reference '${finding.location.file}'.`;
|
|
364
|
+
}
|
|
365
|
+
if (result?.grouping?.memberCount > 1) {
|
|
366
|
+
return `If these dependencies are maintained externally, open an issue or discussion with the upstream maintainers and reference ${result.grouping.label}.`;
|
|
367
|
+
}
|
|
368
|
+
if (result?.target?.purl) {
|
|
369
|
+
return `If this dependency is maintained externally, open an issue or discussion with the upstream maintainers and reference ${result.target.purl}.`;
|
|
370
|
+
}
|
|
371
|
+
if (result?.repoUrl) {
|
|
372
|
+
return "If you do not maintain this repository, open an issue or discussion with the upstream maintainers and share the predictive audit finding.";
|
|
373
|
+
}
|
|
374
|
+
return undefined;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function summarizeNextAction(result) {
|
|
378
|
+
const finding = topFinding(result);
|
|
379
|
+
if (result?.error) {
|
|
380
|
+
return `${summarizeReviewFocus(result)} Verify repository access, source resolution, and clone permissions before re-running the audit.`;
|
|
381
|
+
}
|
|
382
|
+
const nextSteps = [summarizeReviewFocus(result)];
|
|
383
|
+
if (finding?.mitigation) {
|
|
384
|
+
nextSteps.push(finding.mitigation);
|
|
385
|
+
}
|
|
386
|
+
const upstreamEscalation = summarizeUpstreamEscalation(result);
|
|
387
|
+
if (upstreamEscalation) {
|
|
388
|
+
nextSteps.push(upstreamEscalation);
|
|
389
|
+
}
|
|
390
|
+
return nextSteps.join(" ");
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function renderActionTable(results) {
|
|
394
|
+
const rows = [
|
|
395
|
+
["Severity", "Target", "Why this needs action", "What to do next"],
|
|
396
|
+
];
|
|
397
|
+
results.forEach((result) => {
|
|
398
|
+
rows.push([
|
|
399
|
+
result?.assessment?.severity?.toUpperCase() || "NONE",
|
|
400
|
+
consoleTargetLabel(result),
|
|
401
|
+
summarizeWhy(result),
|
|
402
|
+
summarizeNextAction(result),
|
|
403
|
+
]);
|
|
404
|
+
});
|
|
405
|
+
return table(rows, {
|
|
406
|
+
columns: [{ width: 10 }, { width: 36 }, { width: 52 }, { width: 68 }],
|
|
407
|
+
columnDefault: { wrapWord: false },
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export function renderSarifReport(report, options = {}) {
|
|
412
|
+
const minSeverity = options.minSeverity || "low";
|
|
413
|
+
const visibleResults = filterResults(effectiveResults(report), minSeverity);
|
|
414
|
+
const entries = [];
|
|
415
|
+
const sarifResults = [];
|
|
416
|
+
for (const result of visibleResults) {
|
|
417
|
+
if (result?.findings?.length) {
|
|
418
|
+
for (const finding of result.findings) {
|
|
419
|
+
entries.push({ finding, result });
|
|
420
|
+
sarifResults.push(findingToSarifResult(finding, result));
|
|
421
|
+
}
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
if (result?.error) {
|
|
425
|
+
const errorEntry = errorToSarifEntry(result);
|
|
426
|
+
entries.push({ finding: errorEntry, result });
|
|
427
|
+
sarifResults.push(findingToSarifResult(errorEntry, result));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
const toolName = report?.tool?.name || "cdx-audit";
|
|
431
|
+
const toolVersion = report?.tool?.version || "v12";
|
|
432
|
+
const log = {
|
|
433
|
+
$schema: SARIF_SCHEMA,
|
|
434
|
+
version: SARIF_VERSION,
|
|
435
|
+
runs: [
|
|
436
|
+
{
|
|
437
|
+
tool: {
|
|
438
|
+
driver: {
|
|
439
|
+
informationUri: "https://cdxgen.github.io/cdxgen/",
|
|
440
|
+
name: toolName,
|
|
441
|
+
rules: deriveSarifRules(entries),
|
|
442
|
+
version: toolVersion,
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
invocations: [
|
|
446
|
+
{
|
|
447
|
+
executionSuccessful: report?.summary?.erroredTargets === 0,
|
|
448
|
+
},
|
|
449
|
+
],
|
|
450
|
+
properties: {
|
|
451
|
+
aggregateReportFile: report?.aggregateReportFile,
|
|
452
|
+
generatedAt: report?.generatedAt,
|
|
453
|
+
inputs: report?.inputs || [],
|
|
454
|
+
summary: report?.summary,
|
|
455
|
+
},
|
|
456
|
+
results: sarifResults,
|
|
457
|
+
},
|
|
458
|
+
],
|
|
459
|
+
};
|
|
460
|
+
return `${JSON.stringify(log, null, 2)}\n`;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Render an audit report as pretty JSON.
|
|
465
|
+
*
|
|
466
|
+
* @param {object} report aggregate report
|
|
467
|
+
* @returns {string} JSON output
|
|
468
|
+
*/
|
|
469
|
+
export function renderJsonReport(report) {
|
|
470
|
+
return `${JSON.stringify(report, null, 2)}\n`;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Render an audit report for terminal output.
|
|
475
|
+
*
|
|
476
|
+
* @param {object} report aggregate report
|
|
477
|
+
* @param {object} options render options
|
|
478
|
+
* @returns {string} console report text
|
|
479
|
+
*/
|
|
480
|
+
export function renderConsoleReport(report, options = {}) {
|
|
481
|
+
const minSeverity = options.minSeverity || "low";
|
|
482
|
+
const visibleResults = filterResults(effectiveResults(report), minSeverity);
|
|
483
|
+
const lines = [];
|
|
484
|
+
lines.push("cdx-audit — predictive supply-chain exposure audit");
|
|
485
|
+
lines.push("");
|
|
486
|
+
lines.push(`Input BOMs: ${report.summary.inputBomCount}`);
|
|
487
|
+
lines.push(`Candidate targets: ${report.summary.totalTargets}`);
|
|
488
|
+
lines.push(`Scanned targets: ${report.summary.scannedTargets}`);
|
|
489
|
+
lines.push(`Errored targets: ${report.summary.erroredTargets}`);
|
|
490
|
+
lines.push(`Skipped targets: ${report.summary.skippedTargets}`);
|
|
491
|
+
if (report.summary.groupedResultCount) {
|
|
492
|
+
lines.push(
|
|
493
|
+
`Consolidated alert groups: ${report.summary.groupedResultCount}`,
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
lines.push("");
|
|
497
|
+
if (!visibleResults.length) {
|
|
498
|
+
lines.push("No dependencies require your attention right now.");
|
|
499
|
+
lines.push(
|
|
500
|
+
`No predictive findings met or exceeded the configured severity threshold ('${minSeverity}').`,
|
|
501
|
+
);
|
|
502
|
+
return `${lines.join("\n")}\n`;
|
|
503
|
+
}
|
|
504
|
+
lines.push("Dependencies requiring your attention:");
|
|
505
|
+
lines.push("");
|
|
506
|
+
lines.push(renderActionTable(visibleResults));
|
|
507
|
+
lines.push("");
|
|
508
|
+
lines.push(
|
|
509
|
+
"Next step: review the file, repository, or package listed in 'What to do next'. If you maintain it, make the remediation directly; otherwise, open an upstream issue or discussion with the relevant maintainers, then re-run cdx-audit or cdxgen --bom-audit.",
|
|
510
|
+
);
|
|
511
|
+
return `${lines.join("\n")}\n`;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Render the requested report format.
|
|
516
|
+
*
|
|
517
|
+
* @param {string} reportType format name
|
|
518
|
+
* @param {object} report aggregate report
|
|
519
|
+
* @param {object} options render options
|
|
520
|
+
* @returns {string} rendered report
|
|
521
|
+
*/
|
|
522
|
+
export function renderAuditReport(reportType, report, options = {}) {
|
|
523
|
+
if ((reportType || "console") === "json") {
|
|
524
|
+
return renderJsonReport(report);
|
|
525
|
+
}
|
|
526
|
+
if ((reportType || "console") === "sarif") {
|
|
527
|
+
return renderSarifReport(report, options);
|
|
528
|
+
}
|
|
529
|
+
return renderConsoleReport(report, options);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Convert predictive audit results into CycloneDX annotations.
|
|
534
|
+
*
|
|
535
|
+
* @param {object} report aggregate audit report
|
|
536
|
+
* @param {object} bomJson root CycloneDX BOM
|
|
537
|
+
* @param {object} [options] annotation options
|
|
538
|
+
* @returns {object[]} annotations
|
|
539
|
+
*/
|
|
540
|
+
export function formatPredictiveAnnotations(report, bomJson, options = {}) {
|
|
541
|
+
const cdxgenAnnotator = bomJson?.metadata?.tools?.components?.find(
|
|
542
|
+
(component) => component.name === "cdxgen",
|
|
543
|
+
);
|
|
544
|
+
if (!cdxgenAnnotator) {
|
|
545
|
+
return [];
|
|
546
|
+
}
|
|
547
|
+
const minSeverity = options.minSeverity || "low";
|
|
548
|
+
const actionableResults = filterResults(
|
|
549
|
+
report.results || [],
|
|
550
|
+
minSeverity,
|
|
551
|
+
).filter((result) => (result?.assessment?.severity || "none") !== "none");
|
|
552
|
+
return actionableResults.map((result) => {
|
|
553
|
+
const nextAction = summarizeNextAction(result);
|
|
554
|
+
const upstreamEscalation = summarizeUpstreamEscalation(result);
|
|
555
|
+
const properties = [
|
|
556
|
+
{ name: "cdx:audit:engine", value: "cdx-audit" },
|
|
557
|
+
{ name: "cdx:audit:severity", value: result.assessment.severity },
|
|
558
|
+
{
|
|
559
|
+
name: "cdx:audit:confidence",
|
|
560
|
+
value: result.assessment.confidenceLabel,
|
|
561
|
+
},
|
|
562
|
+
{ name: "cdx:audit:score", value: String(result.assessment.score) },
|
|
563
|
+
{ name: "cdx:audit:nextAction", value: nextAction },
|
|
564
|
+
{ name: "cdx:audit:target:purl", value: result.target.purl },
|
|
565
|
+
];
|
|
566
|
+
if (upstreamEscalation) {
|
|
567
|
+
properties.push({
|
|
568
|
+
name: "cdx:audit:upstreamGuidance",
|
|
569
|
+
value: upstreamEscalation,
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
if (result.repoUrl) {
|
|
573
|
+
properties.push({
|
|
574
|
+
name: "cdx:audit:target:repoUrl",
|
|
575
|
+
value: result.repoUrl,
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
if (result.findings?.length) {
|
|
579
|
+
const localDispatchEdge = extractLocalDispatchEdge(result.findings[0]);
|
|
580
|
+
properties.push({
|
|
581
|
+
name: "cdx:audit:topFinding:ruleId",
|
|
582
|
+
value: result.findings[0].ruleId,
|
|
583
|
+
});
|
|
584
|
+
if (localDispatchEdge) {
|
|
585
|
+
properties.push({
|
|
586
|
+
name: "cdx:audit:dispatch:edge",
|
|
587
|
+
value: formatLocalDispatchEdge(localDispatchEdge),
|
|
588
|
+
});
|
|
589
|
+
if (localDispatchEdge.receiverFiles.length) {
|
|
590
|
+
properties.push({
|
|
591
|
+
name: "cdx:audit:dispatch:receiverFiles",
|
|
592
|
+
value: localDispatchEdge.receiverFiles.join(","),
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
if (localDispatchEdge.receiverNames.length) {
|
|
596
|
+
properties.push({
|
|
597
|
+
name: "cdx:audit:dispatch:receiverNames",
|
|
598
|
+
value: localDispatchEdge.receiverNames.join(","),
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return {
|
|
604
|
+
annotator: {
|
|
605
|
+
component: cdxgenAnnotator,
|
|
606
|
+
},
|
|
607
|
+
subjects: result.target.bomRefs?.length
|
|
608
|
+
? result.target.bomRefs
|
|
609
|
+
: [bomJson.serialNumber],
|
|
610
|
+
text: buildAnnotationText(
|
|
611
|
+
`Predictive audit score ${result.assessment.score} (${result.assessment.severity}) for ${result.target.purl}.`,
|
|
612
|
+
properties,
|
|
613
|
+
[result.assessment.reasons?.[0] || "", `Next action: ${nextAction}`],
|
|
614
|
+
),
|
|
615
|
+
timestamp: getTimestamp(),
|
|
616
|
+
};
|
|
617
|
+
});
|
|
618
|
+
}
|