@trustify-da/trustify-da-javascript-client 0.3.0-ea.61444d4 → 0.3.0-ea.62b88e5
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 +22 -8
- package/dist/package.json +6 -5
- package/dist/src/analysis.d.ts +40 -19
- package/dist/src/analysis.js +41 -5
- package/dist/src/cli.js +95 -17
- package/dist/src/config.d.ts +120 -0
- package/dist/src/config.js +260 -0
- package/dist/src/cyclone_dx_sbom.d.ts +13 -0
- package/dist/src/cyclone_dx_sbom.js +39 -1
- package/dist/src/index.d.ts +23 -23
- package/dist/src/index.js +23 -39
- package/dist/src/license/index.d.ts +2 -2
- package/dist/src/license/index.js +9 -6
- package/dist/src/license/licenses_api.d.ts +2 -2
- package/dist/src/license/licenses_api.js +1 -1
- package/dist/src/package_version.d.ts +8 -0
- package/dist/src/package_version.js +31 -0
- package/dist/src/providers/base_java.d.ts +53 -0
- package/dist/src/providers/base_java.js +64 -17
- package/dist/src/providers/base_javascript.d.ts +54 -0
- package/dist/src/providers/base_javascript.js +106 -4
- package/dist/src/providers/golang_gomodules.js +6 -5
- package/dist/src/providers/java_gradle.d.ts +48 -0
- package/dist/src/providers/java_gradle.js +204 -20
- package/dist/src/providers/java_maven.d.ts +28 -8
- package/dist/src/providers/java_maven.js +104 -10
- package/dist/src/providers/javascript_bun.d.ts +12 -0
- package/dist/src/providers/javascript_bun.js +42 -1
- package/dist/src/providers/javascript_npm.d.ts +13 -0
- package/dist/src/providers/javascript_npm.js +40 -1
- package/dist/src/providers/javascript_pnpm.d.ts +13 -0
- package/dist/src/providers/javascript_pnpm.js +47 -1
- package/dist/src/providers/javascript_yarn.d.ts +12 -0
- package/dist/src/providers/javascript_yarn.js +75 -2
- package/dist/src/providers/manifest.js +6 -3
- package/dist/src/providers/processors/yarn_berry_processor.d.ts +5 -1
- package/dist/src/providers/processors/yarn_berry_processor.js +3 -2
- package/dist/src/providers/processors/yarn_classic_processor.d.ts +5 -1
- package/dist/src/providers/processors/yarn_classic_processor.js +8 -6
- package/dist/src/providers/python_uv.js +6 -0
- package/dist/src/providers/requirements_parser.js +1 -1
- package/dist/src/providers/rust_cargo.js +34 -52
- package/dist/src/remediate.d.ts +90 -4
- package/dist/src/remediate.js +153 -58
- package/dist/src/remediation.d.ts +54 -30
- package/dist/src/remediation.js +92 -53
- package/dist/src/remediation_report.d.ts +3 -20
- package/dist/src/remediation_report.js +37 -16
- package/dist/src/sbom.d.ts +11 -0
- package/dist/src/sbom.js +10 -0
- package/dist/src/tools.d.ts +9 -11
- package/dist/src/tools.js +34 -13
- package/dist/src/updaters/maven_updater.d.ts +40 -8
- package/dist/src/updaters/maven_updater.js +26 -3
- package/dist/src/updaters/toml_updater.d.ts +42 -13
- package/dist/src/updaters/toml_updater.js +36 -4
- package/dist/src/workspace.d.ts +2 -1
- package/dist/src/workspace.js +2 -1
- package/package.json +7 -6
package/dist/src/remediation.js
CHANGED
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
import { PackageURL } from 'packageurl-js';
|
|
2
|
+
/**
|
|
3
|
+
* A single per-CVE vulnerability carried by a remediation: one CVE with its own severity and the
|
|
4
|
+
* advisories attributed to it.
|
|
5
|
+
* @typedef {{id: string, severity: string, advisories: Array<{id: string, url: string}>}} Vulnerability
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* A single applicable remediation as produced by {@link extractRemediations}: a dependency, the
|
|
9
|
+
* version that fixes it, and the per-CVE `vulnerabilities` it resolves.
|
|
10
|
+
* @typedef {{
|
|
11
|
+
* purl: string,
|
|
12
|
+
* groupId: string,
|
|
13
|
+
* artifactId: string,
|
|
14
|
+
* currentVersion: string,
|
|
15
|
+
* fixedInVersion: string,
|
|
16
|
+
* fixedInPurl: string,
|
|
17
|
+
* provider: string,
|
|
18
|
+
* source: string,
|
|
19
|
+
* vulnerabilities: Vulnerability[]
|
|
20
|
+
* }} Remediation
|
|
21
|
+
*/
|
|
2
22
|
/**
|
|
3
23
|
* Extracts the major version segment from a version string.
|
|
4
24
|
* @param {string} version
|
|
@@ -7,11 +27,14 @@ import { PackageURL } from 'packageurl-js';
|
|
|
7
27
|
function getMajorVersion(version) {
|
|
8
28
|
return version.split('.')[0] || '';
|
|
9
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {{selectVersion: (fixedInVersions: string[], currentVersion: string) => string, resolveConflict: (existing: ConflictCandidate, candidate: ConflictCandidate) => 'existing'|'candidate'}} VersionStrategy
|
|
32
|
+
*/
|
|
10
33
|
/**
|
|
11
34
|
* Version selection strategy that prefers the closest compatible version
|
|
12
35
|
* within the same major version stream. Falls back to the lowest cross-major
|
|
13
36
|
* version when no same-major option exists.
|
|
14
|
-
* @type {
|
|
37
|
+
* @type {VersionStrategy}
|
|
15
38
|
*/
|
|
16
39
|
export const closestCoverageStrategy = {
|
|
17
40
|
selectVersion(fixedInVersions, currentVersion) {
|
|
@@ -47,7 +70,7 @@ export const closestCoverageStrategy = {
|
|
|
47
70
|
* Version selection strategy that always picks the highest version regardless
|
|
48
71
|
* of major version distance. Guarantees maximum CVE coverage but may produce
|
|
49
72
|
* large version jumps. This is the original behavior before pluggable strategies.
|
|
50
|
-
* @type {
|
|
73
|
+
* @type {VersionStrategy}
|
|
51
74
|
*/
|
|
52
75
|
export const highestStrategy = {
|
|
53
76
|
selectVersion(fixedInVersions) {
|
|
@@ -73,14 +96,16 @@ export const highestStrategy = {
|
|
|
73
96
|
* version selection strategy to resolve conflicts. Dependencies with no remediation
|
|
74
97
|
* data are skipped.
|
|
75
98
|
*
|
|
76
|
-
* @param {
|
|
99
|
+
* @param {import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport} analysisReport - raw DA AnalysisReport JSON response
|
|
77
100
|
* @param {object} [options] - extraction options
|
|
78
101
|
* @param {string[]} [options.providerPriority] - provider names in descending priority order.
|
|
79
102
|
* The first entry has the highest priority. Providers not listed share the lowest priority.
|
|
80
103
|
* When omitted or empty, all providers are treated equally and the highest fix version wins.
|
|
81
|
-
* @param {
|
|
104
|
+
* @param {VersionStrategy} [options.versionStrategy] - version selection strategy with selectVersion
|
|
82
105
|
* and resolveConflict methods. Defaults to closestCoverageStrategy.
|
|
83
|
-
* @returns {
|
|
106
|
+
* @returns {Remediation[]} `vulnerabilities` is the sole source of vulnerability data — each entry
|
|
107
|
+
* holds one CVE with its own severity and advisories. Use {@link maxSeverity} to derive a
|
|
108
|
+
* dependency-level severity.
|
|
84
109
|
*/
|
|
85
110
|
export function extractRemediations(analysisReport, options = {}) {
|
|
86
111
|
if (!analysisReport || !analysisReport.providers) {
|
|
@@ -88,6 +113,7 @@ export function extractRemediations(analysisReport, options = {}) {
|
|
|
88
113
|
}
|
|
89
114
|
const priorityMap = buildPriorityMap(options.providerPriority);
|
|
90
115
|
const strategy = options.versionStrategy || closestCoverageStrategy;
|
|
116
|
+
/** @type {Map<string, Remediation & { _fromTrustedContent?: boolean}>} */
|
|
91
117
|
const remediationsByDep = new Map();
|
|
92
118
|
const rankByDep = new Map();
|
|
93
119
|
for (const [providerName, providerReport] of Object.entries(analysisReport.providers)) {
|
|
@@ -120,12 +146,12 @@ function buildPriorityMap(providerPriority) {
|
|
|
120
146
|
}
|
|
121
147
|
/**
|
|
122
148
|
* Extracts remediations from the sources/dependencies/issues tree of a provider report.
|
|
123
|
-
* @param {
|
|
149
|
+
* @param {import('@trustify-da/trustify-da-api-model/model/v5/ProviderReport.js').ProviderReport} providerReport
|
|
124
150
|
* @param {string} providerName
|
|
125
151
|
* @param {number} providerRank - numeric priority rank for this provider
|
|
126
|
-
* @param {Map<string,
|
|
152
|
+
* @param {Map<string, Remediation & { _fromTrustedContent?: boolean }>} remediationsByDep - accumulator keyed by dependency PURL
|
|
127
153
|
* @param {Map<string, number>} rankByDep - tracks current winning rank per dependency
|
|
128
|
-
* @param {
|
|
154
|
+
* @param {VersionStrategy} strategy - version selection strategy
|
|
129
155
|
*/
|
|
130
156
|
function extractFromSources(providerReport, providerName, providerRank, remediationsByDep, rankByDep, strategy) {
|
|
131
157
|
if (!providerReport.sources) {
|
|
@@ -147,14 +173,14 @@ function extractFromSources(providerReport, providerName, providerRank, remediat
|
|
|
147
173
|
}
|
|
148
174
|
/**
|
|
149
175
|
* Processes a single issue's remediation data and merges it into the accumulator.
|
|
150
|
-
* @param {
|
|
151
|
-
* @param {
|
|
176
|
+
* @param {import('@trustify-da/trustify-da-api-model/model/v5/Issue.js').Issue} issue - issue object containing remediation and CVE data
|
|
177
|
+
* @param {import('@trustify-da/trustify-da-api-model/model/v5/DependencyReport.js').DependencyReport} dep - dependency object containing the ref PURL
|
|
152
178
|
* @param {string} providerName
|
|
153
179
|
* @param {string} sourceName
|
|
154
180
|
* @param {number} providerRank
|
|
155
|
-
* @param {Map<string,
|
|
181
|
+
* @param {Map<string, Remediation & { _fromTrustedContent?: boolean }>} remediationsByDep
|
|
156
182
|
* @param {Map<string, number>} rankByDep
|
|
157
|
-
* @param {
|
|
183
|
+
* @param {VersionStrategy} strategy - version selection strategy
|
|
158
184
|
*/
|
|
159
185
|
function processIssueRemediation(issue, dep, providerName, sourceName, providerRank, remediationsByDep, rankByDep, strategy) {
|
|
160
186
|
const depPurl = dep.ref;
|
|
@@ -185,12 +211,12 @@ function processIssueRemediation(issue, dep, providerName, sourceName, providerR
|
|
|
185
211
|
if (!fixedInVersion) {
|
|
186
212
|
return;
|
|
187
213
|
}
|
|
188
|
-
const cveId = issue.id
|
|
214
|
+
const cveId = issue.id;
|
|
189
215
|
const severity = issue.severity || 'UNKNOWN';
|
|
190
216
|
const advisories = extractAdvisories(issue);
|
|
191
217
|
const existing = remediationsByDep.get(depPurl);
|
|
192
218
|
if (!existing) {
|
|
193
|
-
|
|
219
|
+
const entry = {
|
|
194
220
|
purl: depPurl,
|
|
195
221
|
groupId: parsedDep.namespace || '',
|
|
196
222
|
artifactId: parsedDep.name,
|
|
@@ -199,25 +225,21 @@ function processIssueRemediation(issue, dep, providerName, sourceName, providerR
|
|
|
199
225
|
fixedInPurl,
|
|
200
226
|
provider: providerName,
|
|
201
227
|
source: sourceName,
|
|
202
|
-
|
|
203
|
-
severity: severity.toUpperCase(),
|
|
204
|
-
cves: cveId ? [cveId] : [],
|
|
228
|
+
vulnerabilities: [],
|
|
205
229
|
_fromTrustedContent: isTrustedContent,
|
|
206
|
-
}
|
|
230
|
+
};
|
|
231
|
+
addVulnerability(entry, cveId, severity, advisories);
|
|
232
|
+
remediationsByDep.set(depPurl, entry);
|
|
207
233
|
rankByDep.set(depPurl, providerRank);
|
|
208
234
|
return;
|
|
209
235
|
}
|
|
210
|
-
|
|
211
|
-
existing.cves.push(cveId);
|
|
212
|
-
}
|
|
213
|
-
mergeAdvisories(existing.advisories, advisories);
|
|
236
|
+
addVulnerability(existing, cveId, severity, advisories);
|
|
214
237
|
const existingRank = rankByDep.get(depPurl);
|
|
215
238
|
if (providerRank > existingRank) {
|
|
216
239
|
existing.fixedInVersion = fixedInVersion;
|
|
217
240
|
existing.fixedInPurl = fixedInPurl;
|
|
218
241
|
existing.provider = providerName;
|
|
219
242
|
existing.source = sourceName;
|
|
220
|
-
existing.severity = higherSeverity(existing.severity, severity);
|
|
221
243
|
existing._fromTrustedContent = isTrustedContent;
|
|
222
244
|
rankByDep.set(depPurl, providerRank);
|
|
223
245
|
}
|
|
@@ -230,16 +252,15 @@ function processIssueRemediation(issue, dep, providerName, sourceName, providerR
|
|
|
230
252
|
existing.source = sourceName;
|
|
231
253
|
existing._fromTrustedContent = isTrustedContent;
|
|
232
254
|
}
|
|
233
|
-
existing.severity = higherSeverity(existing.severity, severity);
|
|
234
255
|
}
|
|
235
256
|
}
|
|
236
257
|
/**
|
|
237
258
|
* Extracts remediations from the recommendations section of a provider report.
|
|
238
259
|
* Merges CVEs and advisories into existing entries when present.
|
|
239
|
-
* @param {
|
|
260
|
+
* @param {import('@trustify-da/trustify-da-api-model/model/v5/ProviderReport.js').ProviderReport} providerReport
|
|
240
261
|
* @param {string} providerName
|
|
241
262
|
* @param {number} providerRank
|
|
242
|
-
* @param {Map<string,
|
|
263
|
+
* @param {Map<string, Remediation & { _fromTrustedContent?: boolean }>} remediationsByDep
|
|
243
264
|
* @param {Map<string, number>} rankByDep
|
|
244
265
|
*/
|
|
245
266
|
function extractFromRecommendations(providerReport, providerName, providerRank, remediationsByDep, rankByDep) {
|
|
@@ -279,9 +300,7 @@ function extractFromRecommendations(providerReport, providerName, providerRank,
|
|
|
279
300
|
fixedInPurl: recommendedPurl,
|
|
280
301
|
provider: providerName,
|
|
281
302
|
source: 'recommendation',
|
|
282
|
-
|
|
283
|
-
severity: 'UNKNOWN',
|
|
284
|
-
cves: [],
|
|
303
|
+
vulnerabilities: [],
|
|
285
304
|
});
|
|
286
305
|
rankByDep.set(depPurl, providerRank);
|
|
287
306
|
continue;
|
|
@@ -308,9 +327,9 @@ function extractFromRecommendations(providerReport, providerName, providerRank,
|
|
|
308
327
|
* Gets the fixedIn PURL from an issue's remediation, preferring trustedContent.
|
|
309
328
|
* When fixedIn is an array of version strings (not PURLs), uses the strategy's
|
|
310
329
|
* selectVersion to pick the best candidate and constructs a PURL from the dependency ref.
|
|
311
|
-
* @param {
|
|
330
|
+
* @param {import('@trustify-da/trustify-da-api-model/model/v5/Issue.js').Issue} issue
|
|
312
331
|
* @param {string} depPurl - the dependency PURL, used to construct fixedIn PURLs from version strings
|
|
313
|
-
* @param {
|
|
332
|
+
* @param {VersionStrategy} strategy - version selection strategy
|
|
314
333
|
* @param {string} currentVersion - the dependency's current version
|
|
315
334
|
* @returns {string|undefined}
|
|
316
335
|
*/
|
|
@@ -318,17 +337,11 @@ function getFixedInPurl(issue, depPurl, strategy, currentVersion) {
|
|
|
318
337
|
if (!issue.remediation) {
|
|
319
338
|
return undefined;
|
|
320
339
|
}
|
|
321
|
-
if (issue.remediation.trustedContent
|
|
340
|
+
if (issue.remediation.trustedContent?.ref) {
|
|
322
341
|
return issue.remediation.trustedContent.ref;
|
|
323
342
|
}
|
|
324
343
|
const fixedIn = issue.remediation.fixedIn;
|
|
325
|
-
if (
|
|
326
|
-
return undefined;
|
|
327
|
-
}
|
|
328
|
-
if (typeof fixedIn === 'string') {
|
|
329
|
-
return fixedIn;
|
|
330
|
-
}
|
|
331
|
-
if (Array.isArray(fixedIn) && fixedIn.length > 0) {
|
|
344
|
+
if (fixedIn?.length > 0) {
|
|
332
345
|
const version = fixedIn.length > 1
|
|
333
346
|
? strategy.selectVersion(fixedIn, currentVersion)
|
|
334
347
|
: fixedIn[0];
|
|
@@ -348,27 +361,44 @@ function getFixedInPurl(issue, depPurl, strategy, currentVersion) {
|
|
|
348
361
|
}
|
|
349
362
|
return undefined;
|
|
350
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* Adds a per-CVE vulnerability entry to a remediation, deduplicating by CVE id. When the
|
|
366
|
+
* CVE is already present, the higher severity is kept and its advisories are merged.
|
|
367
|
+
* Issues without a CVE id contribute no vulnerability entry.
|
|
368
|
+
* @param {object} entry - remediation accumulator entry with a `vulnerabilities` array
|
|
369
|
+
* @param {string|undefined} cveId - the CVE identifier for this issue
|
|
370
|
+
* @param {string} severity - the issue's severity
|
|
371
|
+
* @param {Array<{id: string, url: string}>} advisories - advisories attributed to this issue
|
|
372
|
+
*/
|
|
373
|
+
function addVulnerability(entry, cveId, severity, advisories) {
|
|
374
|
+
if (!cveId) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
const normalizedSeverity = (severity || 'UNKNOWN').toUpperCase();
|
|
378
|
+
const existingVuln = entry.vulnerabilities.find(v => v.id === cveId);
|
|
379
|
+
if (existingVuln) {
|
|
380
|
+
existingVuln.severity = higherSeverity(existingVuln.severity, normalizedSeverity);
|
|
381
|
+
mergeAdvisories(existingVuln.advisories, advisories);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
entry.vulnerabilities.push({
|
|
385
|
+
id: cveId,
|
|
386
|
+
severity: normalizedSeverity,
|
|
387
|
+
advisories: [...advisories],
|
|
388
|
+
});
|
|
389
|
+
}
|
|
351
390
|
/**
|
|
352
391
|
* Extracts advisory objects from an issue.
|
|
353
|
-
* @param {
|
|
392
|
+
* @param {import('@trustify-da/trustify-da-api-model/model/v5/Issue.js').Issue} issue
|
|
354
393
|
* @returns {Array<{id: string, url: string}>}
|
|
355
394
|
*/
|
|
356
395
|
function extractAdvisories(issue) {
|
|
357
396
|
const advisories = [];
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
if (tc.advisory) {
|
|
361
|
-
advisories.push({
|
|
362
|
-
id: tc.advisory.id || tc.advisory,
|
|
363
|
-
url: tc.advisory.url || '',
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
if (issue.advisories) {
|
|
368
|
-
for (const adv of issue.advisories) {
|
|
397
|
+
for (const advisory of issue.remediation?.advisories ?? []) {
|
|
398
|
+
if (advisory.advisory?.id) {
|
|
369
399
|
advisories.push({
|
|
370
|
-
id:
|
|
371
|
-
url:
|
|
400
|
+
id: advisory.advisory.id,
|
|
401
|
+
url: advisory.advisory.url || '',
|
|
372
402
|
});
|
|
373
403
|
}
|
|
374
404
|
}
|
|
@@ -434,3 +464,12 @@ function higherSeverity(a, b) {
|
|
|
434
464
|
const indexB = SEVERITY_ORDER.indexOf(upperB);
|
|
435
465
|
return indexA >= indexB ? upperA : upperB;
|
|
436
466
|
}
|
|
467
|
+
/**
|
|
468
|
+
* Derives a dependency-level severity as the max across a list of vulnerabilities.
|
|
469
|
+
* Returns 'UNKNOWN' for an empty or missing list.
|
|
470
|
+
* @param {Array<{severity: string}>} [vulnerabilities]
|
|
471
|
+
* @returns {string}
|
|
472
|
+
*/
|
|
473
|
+
export function maxSeverity(vulnerabilities) {
|
|
474
|
+
return (vulnerabilities || []).reduce((acc, v) => higherSeverity(acc, v.severity), 'UNKNOWN');
|
|
475
|
+
}
|
|
@@ -1,32 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generates a formatted report from an array of remediation entries.
|
|
3
3
|
*
|
|
4
|
-
* @param {
|
|
5
|
-
* fixedInVersion: string, fixedInPurl: string, provider: string, source: string,
|
|
6
|
-
* advisories: Array<{id: string, url: string}>, severity: string, cves: string[]}>} remediations
|
|
4
|
+
* @param {import('./remediation.js').Remediation[]} remediations
|
|
7
5
|
* @param {object} [options]
|
|
8
6
|
* @param {'dependency'|'bundle'} [options.groupBy='dependency'] - grouping strategy
|
|
9
7
|
* @param {'markdown'|'json'} [options.format='markdown'] - output format
|
|
10
8
|
* @param {boolean} [options.dryRun=false] - when true, produces tabular summary
|
|
11
9
|
* @returns {string}
|
|
12
10
|
*/
|
|
13
|
-
export function generateReport(remediations:
|
|
14
|
-
|
|
15
|
-
groupId: string;
|
|
16
|
-
artifactId: string;
|
|
17
|
-
currentVersion: string;
|
|
18
|
-
fixedInVersion: string;
|
|
19
|
-
fixedInPurl: string;
|
|
20
|
-
provider: string;
|
|
21
|
-
source: string;
|
|
22
|
-
advisories: Array<{
|
|
23
|
-
id: string;
|
|
24
|
-
url: string;
|
|
25
|
-
}>;
|
|
26
|
-
severity: string;
|
|
27
|
-
cves: string[];
|
|
28
|
-
}>, options?: {
|
|
29
|
-
groupBy?: "dependency" | "bundle" | undefined;
|
|
11
|
+
export function generateReport(remediations: import("./remediation.js").Remediation[], options?: {
|
|
12
|
+
groupBy?: "bundle" | "dependency" | undefined;
|
|
30
13
|
format?: "markdown" | "json" | undefined;
|
|
31
14
|
dryRun?: boolean | undefined;
|
|
32
15
|
}): string;
|
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
* Report generator that transforms remediation extractor output into structured
|
|
3
3
|
* markdown for PR bodies, CLI dry-run output, and JSON.
|
|
4
4
|
*/
|
|
5
|
-
import { SEVERITY_ORDER } from './remediation.js';
|
|
5
|
+
import { SEVERITY_ORDER, maxSeverity } from './remediation.js';
|
|
6
6
|
/**
|
|
7
7
|
* Generates a formatted report from an array of remediation entries.
|
|
8
8
|
*
|
|
9
|
-
* @param {
|
|
10
|
-
* fixedInVersion: string, fixedInPurl: string, provider: string, source: string,
|
|
11
|
-
* advisories: Array<{id: string, url: string}>, severity: string, cves: string[]}>} remediations
|
|
9
|
+
* @param {import('./remediation.js').Remediation[]} remediations
|
|
12
10
|
* @param {object} [options]
|
|
13
11
|
* @param {'dependency'|'bundle'} [options.groupBy='dependency'] - grouping strategy
|
|
14
12
|
* @param {'markdown'|'json'} [options.format='markdown'] - output format
|
|
@@ -33,7 +31,11 @@ export function generateReport(remediations, options = {}) {
|
|
|
33
31
|
}
|
|
34
32
|
/**
|
|
35
33
|
* Generates a per-dependency markdown report with one section per remediation entry.
|
|
36
|
-
*
|
|
34
|
+
*
|
|
35
|
+
* Each vulnerability row is rendered from its own per-CVE severity and advisories
|
|
36
|
+
* (from `rem.vulnerabilities`), so a Moderate CVE is no longer inflated to the
|
|
37
|
+
* dependency's max severity.
|
|
38
|
+
* @param {import('./remediation.js').Remediation[]} remediations
|
|
37
39
|
* @returns {string}
|
|
38
40
|
*/
|
|
39
41
|
function generatePerDependencyReport(remediations) {
|
|
@@ -47,14 +49,14 @@ function generatePerDependencyReport(remediations) {
|
|
|
47
49
|
`**Provider:** ${rem.provider} | **Source:** ${rem.source}`,
|
|
48
50
|
'',
|
|
49
51
|
];
|
|
50
|
-
|
|
52
|
+
const vulnerabilities = rem.vulnerabilities || [];
|
|
53
|
+
if (vulnerabilities.length > 0) {
|
|
51
54
|
lines.push('### Vulnerabilities resolved');
|
|
52
55
|
lines.push('');
|
|
53
56
|
lines.push('| CVE | Severity | Advisory |');
|
|
54
57
|
lines.push('| --- | --- | --- |');
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
lines.push(`| ${cve} | ${rem.severity} | ${advisoryLinks} |`);
|
|
58
|
+
for (const v of vulnerabilities) {
|
|
59
|
+
lines.push(`| ${v.id} | ${v.severity} | ${formatAdvisoryLinks(v.advisories)} |`);
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
62
|
return lines.join('\n');
|
|
@@ -63,7 +65,7 @@ function generatePerDependencyReport(remediations) {
|
|
|
63
65
|
}
|
|
64
66
|
/**
|
|
65
67
|
* Generates a bundled markdown report grouping all remediations by severity.
|
|
66
|
-
* @param {
|
|
68
|
+
* @param {import('./remediation.js').Remediation[]} remediations
|
|
67
69
|
* @returns {string}
|
|
68
70
|
*/
|
|
69
71
|
function generateBundledReport(remediations) {
|
|
@@ -82,8 +84,9 @@ function generateBundledReport(remediations) {
|
|
|
82
84
|
const depName = rem.groupId
|
|
83
85
|
? `${rem.groupId}:${rem.artifactId}`
|
|
84
86
|
: rem.artifactId;
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
+
const vulnerabilities = rem.vulnerabilities || [];
|
|
88
|
+
const cves = vulnerabilities.map(v => v.id).join(', ');
|
|
89
|
+
const advisoryLinks = formatAdvisoryLinks(collectAdvisories(vulnerabilities));
|
|
87
90
|
lines.push(`| ${depName} | ${rem.currentVersion} | ${rem.fixedInVersion}`
|
|
88
91
|
+ ` | ${rem.provider} | ${cves} | ${advisoryLinks} |`);
|
|
89
92
|
}
|
|
@@ -93,7 +96,7 @@ function generateBundledReport(remediations) {
|
|
|
93
96
|
}
|
|
94
97
|
/**
|
|
95
98
|
* Generates a tabular dry-run summary of proposed changes.
|
|
96
|
-
* @param {
|
|
99
|
+
* @param {import('./remediation.js').Remediation[]} remediations
|
|
97
100
|
* @returns {string}
|
|
98
101
|
*/
|
|
99
102
|
function generateDryRunReport(remediations) {
|
|
@@ -108,13 +111,13 @@ function generateDryRunReport(remediations) {
|
|
|
108
111
|
? `${rem.groupId}:${rem.artifactId}`
|
|
109
112
|
: rem.artifactId;
|
|
110
113
|
lines.push(`| ${depName} | ${rem.currentVersion} | ${rem.fixedInVersion}`
|
|
111
|
-
+ ` | ${rem.
|
|
114
|
+
+ ` | ${maxSeverity(rem.vulnerabilities)} | ${rem.provider} |`);
|
|
112
115
|
}
|
|
113
116
|
return lines.join('\n');
|
|
114
117
|
}
|
|
115
118
|
/**
|
|
116
119
|
* Groups remediations by their severity.
|
|
117
|
-
* @param {
|
|
120
|
+
* @param {import('./remediation.js').Remediation[]} remediations
|
|
118
121
|
* @returns {Map<string, Array<object>>}
|
|
119
122
|
*/
|
|
120
123
|
function groupBySeverity(remediations) {
|
|
@@ -123,7 +126,7 @@ function groupBySeverity(remediations) {
|
|
|
123
126
|
map.set(severity, []);
|
|
124
127
|
}
|
|
125
128
|
for (const rem of remediations) {
|
|
126
|
-
const sev = rem.
|
|
129
|
+
const sev = maxSeverity(rem.vulnerabilities);
|
|
127
130
|
if (!map.has(sev)) {
|
|
128
131
|
map.set(sev, []);
|
|
129
132
|
}
|
|
@@ -131,6 +134,24 @@ function groupBySeverity(remediations) {
|
|
|
131
134
|
}
|
|
132
135
|
return map;
|
|
133
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Collects the de-duplicated union of advisories across a list of vulnerabilities.
|
|
139
|
+
* @param {Array<{advisories: Array<{id: string, url: string}>}>} vulnerabilities
|
|
140
|
+
* @returns {Array<{id: string, url: string}>}
|
|
141
|
+
*/
|
|
142
|
+
function collectAdvisories(vulnerabilities) {
|
|
143
|
+
const merged = [];
|
|
144
|
+
const seen = new Set();
|
|
145
|
+
for (const v of vulnerabilities) {
|
|
146
|
+
for (const adv of v.advisories || []) {
|
|
147
|
+
if (!seen.has(adv.id)) {
|
|
148
|
+
seen.add(adv.id);
|
|
149
|
+
merged.push(adv);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return merged;
|
|
154
|
+
}
|
|
134
155
|
/**
|
|
135
156
|
* Formats advisory entries into markdown links or plain text.
|
|
136
157
|
* @param {Array<{id: string, url: string}>} advisories
|
package/dist/src/sbom.d.ts
CHANGED
|
@@ -33,6 +33,17 @@ export default class Sbom {
|
|
|
33
33
|
alg: string;
|
|
34
34
|
content: string;
|
|
35
35
|
}>): CycloneDxSbom;
|
|
36
|
+
/**
|
|
37
|
+
* Attach hashes to existing components by matching their PURL. Post-processing
|
|
38
|
+
* step used by ecosystem-specific providers (e.g. Maven) to enrich the SBOM
|
|
39
|
+
* with artifact hashes without leaking their concern into the shared parser.
|
|
40
|
+
* @param {Map<string, Array<{alg: string, content: string}>>} hashMap - PURL→hashes map
|
|
41
|
+
* @return {Sbom}
|
|
42
|
+
*/
|
|
43
|
+
attachHashes(hashMap: Map<string, Array<{
|
|
44
|
+
alg: string;
|
|
45
|
+
content: string;
|
|
46
|
+
}>>): Sbom;
|
|
36
47
|
/**
|
|
37
48
|
* @return String sbom json in a string format
|
|
38
49
|
*/
|
package/dist/src/sbom.js
CHANGED
|
@@ -50,6 +50,16 @@ export default class Sbom {
|
|
|
50
50
|
addDependency(sourceRef, targetRef, scope, targetHashes) {
|
|
51
51
|
return this.sbomModel.addDependency(sourceRef, targetRef, scope, targetHashes);
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Attach hashes to existing components by matching their PURL. Post-processing
|
|
55
|
+
* step used by ecosystem-specific providers (e.g. Maven) to enrich the SBOM
|
|
56
|
+
* with artifact hashes without leaking their concern into the shared parser.
|
|
57
|
+
* @param {Map<string, Array<{alg: string, content: string}>>} hashMap - PURL→hashes map
|
|
58
|
+
* @return {Sbom}
|
|
59
|
+
*/
|
|
60
|
+
attachHashes(hashMap) {
|
|
61
|
+
return this.sbomModel.attachHashes(hashMap);
|
|
62
|
+
}
|
|
53
63
|
/**
|
|
54
64
|
* @return String sbom json in a string format
|
|
55
65
|
*/
|
package/dist/src/tools.d.ts
CHANGED
|
@@ -6,24 +6,22 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export function logValueFromObjects(key: string, opts?: {}, defValue: string): void;
|
|
8
8
|
/**
|
|
9
|
-
* Utility function
|
|
10
|
-
*
|
|
11
|
-
* if not present, or not string will return the default value supplied which default to null.
|
|
9
|
+
* Utility function returns the value for key from opts, then environment variables,
|
|
10
|
+
* then the supplied default. Values from opts are used only if they are strings.
|
|
12
11
|
* @param {string} key the key to look for in the environment variables and the opts object
|
|
13
12
|
* @param {string|null} [def=null] the value to return if nothing else found
|
|
14
|
-
* @param {{}} [opts={}] the options object to
|
|
15
|
-
* @returns {string|null} the value of the key found in the
|
|
13
|
+
* @param {{}} [opts={}] the options object to check before the environment
|
|
14
|
+
* @returns {string|null} the value of the key found in the options object, environment, or the
|
|
16
15
|
* default supplied
|
|
17
16
|
*/
|
|
18
17
|
export function getCustom(key: string, def?: string | null, opts?: {}): string | null;
|
|
19
18
|
/**
|
|
20
19
|
* Utility function for looking up custom variable for a binary path.
|
|
21
|
-
*
|
|
22
|
-
* uppercase version of
|
|
23
|
-
* found.
|
|
20
|
+
* Looks in opts, then environment variables, for a key with TRUSTIFY_DA_x_PATH, where x is an
|
|
21
|
+
* uppercase version of the supplied name. The name is returned if neither contains the key.
|
|
24
22
|
* @param name the binary name to look for, will be returned as value in nothing else found
|
|
25
|
-
* @param {{}} [opts={}] the options object to
|
|
26
|
-
* @returns {string|null} the value of the key found in the
|
|
23
|
+
* @param {{}} [opts={}] the options object to check before the environment
|
|
24
|
+
* @returns {string|null} the value of the key found in the options object, environment, or the
|
|
27
25
|
* original name supplied
|
|
28
26
|
*/
|
|
29
27
|
export function getCustomPath(name: any, opts?: {}): string | null;
|
|
@@ -31,7 +29,7 @@ export function getCustomPath(name: any, opts?: {}): string | null;
|
|
|
31
29
|
* Utility function for determining whether wrappers for build tools such as gradlew/mvnw should be
|
|
32
30
|
* preferred over invoking the binary directly.
|
|
33
31
|
* @param {string} name - binary for which to search for its wrapper
|
|
34
|
-
* @param {{}} opts - the options object to
|
|
32
|
+
* @param {{}} opts - the options object to check before the environment
|
|
35
33
|
* @returns {boolean} whether to prefer the wrapper if exists or not
|
|
36
34
|
*/
|
|
37
35
|
export function getWrapperPreference(name: string, opts?: {}): boolean;
|
package/dist/src/tools.js
CHANGED
|
@@ -27,39 +27,60 @@ export function logValueFromObjects(key, opts, defValue) {
|
|
|
27
27
|
console.log(`default value for ${key} = ${defValue} ${EOL}`);
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
|
-
* Utility function
|
|
31
|
-
*
|
|
32
|
-
* if not present, or not string will return the default value supplied which default to null.
|
|
30
|
+
* Utility function returns the value for key from opts, then environment variables,
|
|
31
|
+
* then the supplied default. Values from opts are used only if they are strings.
|
|
33
32
|
* @param {string} key the key to look for in the environment variables and the opts object
|
|
34
33
|
* @param {string|null} [def=null] the value to return if nothing else found
|
|
35
|
-
* @param {{}} [opts={}] the options object to
|
|
36
|
-
* @returns {string|null} the value of the key found in the
|
|
34
|
+
* @param {{}} [opts={}] the options object to check before the environment
|
|
35
|
+
* @returns {string|null} the value of the key found in the options object, environment, or the
|
|
37
36
|
* default supplied
|
|
38
37
|
*/
|
|
39
38
|
export function getCustom(key, def = null, opts = {}) {
|
|
40
39
|
if (process.env["TRUSTIFY_DA_DEBUG"] === "true" && !key.match(RegexNotToBeLogged)) {
|
|
41
40
|
logValueFromObjects(key, opts, def);
|
|
42
41
|
}
|
|
43
|
-
return key in
|
|
42
|
+
return key in opts && typeof opts[key] === 'string' ? opts[key] : key in process.env ? process.env[key] : def;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Validates that an executable path does not use directory traversal or relative segments.
|
|
46
|
+
* @param {string} binPath - The executable path to validate.
|
|
47
|
+
* @returns {string} The validated path.
|
|
48
|
+
* @throws {Error} If the path contains '..' segments or is a relative path with separators.
|
|
49
|
+
*/
|
|
50
|
+
function validateExecutablePath(binPath) {
|
|
51
|
+
if (typeof binPath !== 'string' || binPath.length === 0) {
|
|
52
|
+
throw new Error('Executable path rejected: expected a non-empty string');
|
|
53
|
+
}
|
|
54
|
+
if (binPath.startsWith('./') || binPath.startsWith('.\\')) {
|
|
55
|
+
throw new Error(`Executable path rejected: relative paths starting with './' are not allowed: ${binPath}`);
|
|
56
|
+
}
|
|
57
|
+
const segments = binPath.split(/[/\\]/);
|
|
58
|
+
if (segments.includes('..')) {
|
|
59
|
+
throw new Error(`Executable path rejected: path contains directory traversal segment (..): ${binPath}`);
|
|
60
|
+
}
|
|
61
|
+
if ((binPath.includes('/') || binPath.includes('\\')) && !path.isAbsolute(binPath)) {
|
|
62
|
+
throw new Error(`Executable path rejected: relative paths are not allowed, use an absolute path or a bare command name: ${binPath}`);
|
|
63
|
+
}
|
|
64
|
+
return binPath;
|
|
44
65
|
}
|
|
45
66
|
/**
|
|
46
67
|
* Utility function for looking up custom variable for a binary path.
|
|
47
|
-
*
|
|
48
|
-
* uppercase version of
|
|
49
|
-
* found.
|
|
68
|
+
* Looks in opts, then environment variables, for a key with TRUSTIFY_DA_x_PATH, where x is an
|
|
69
|
+
* uppercase version of the supplied name. The name is returned if neither contains the key.
|
|
50
70
|
* @param name the binary name to look for, will be returned as value in nothing else found
|
|
51
|
-
* @param {{}} [opts={}] the options object to
|
|
52
|
-
* @returns {string|null} the value of the key found in the
|
|
71
|
+
* @param {{}} [opts={}] the options object to check before the environment
|
|
72
|
+
* @returns {string|null} the value of the key found in the options object, environment, or the
|
|
53
73
|
* original name supplied
|
|
54
74
|
*/
|
|
55
75
|
export function getCustomPath(name, opts = {}) {
|
|
56
|
-
|
|
76
|
+
const resolvedPath = getCustom(`TRUSTIFY_DA_${name.toUpperCase()}_PATH`, name, opts);
|
|
77
|
+
return validateExecutablePath(resolvedPath);
|
|
57
78
|
}
|
|
58
79
|
/**
|
|
59
80
|
* Utility function for determining whether wrappers for build tools such as gradlew/mvnw should be
|
|
60
81
|
* preferred over invoking the binary directly.
|
|
61
82
|
* @param {string} name - binary for which to search for its wrapper
|
|
62
|
-
* @param {{}} opts - the options object to
|
|
83
|
+
* @param {{}} opts - the options object to check before the environment
|
|
63
84
|
* @returns {boolean} whether to prefer the wrapper if exists or not
|
|
64
85
|
*/
|
|
65
86
|
export function getWrapperPreference(name, opts = {}) {
|