"Fossies" - the Fresh Open Source Software Archive 
Member "cli-1.1260.0/src/lib/formatters/remediation-based-format-issues.ts" (4 Dec 2023, 13040 Bytes) of package /linux/misc/snyk-cli-1.1260.0.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) TypeScript source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
1 import chalk from 'chalk';
2 import { icon } from '../theme';
3 import { TestOptions } from '../../lib/types';
4 import {
5 DependencyPins,
6 DependencyUpdates,
7 GroupedVuln,
8 IssueData,
9 LegalInstruction,
10 PatchRemediation,
11 PinRemediation,
12 RemediationChanges,
13 SEVERITY,
14 UpgradeRemediation,
15 } from '../../lib/snyk-test/legacy';
16 import { colorTextBySeverity } from '../../lib/snyk-test/common';
17 import { formatLegalInstructions } from './legal-license-instructions';
18 import { BasicVulnInfo, UpgradesByAffectedPackage } from './types';
19 import { PATH_SEPARATOR } from '../constants';
20 import { getSeverityValue } from './get-severity-value';
21 import { getVulnerabilityUrl } from './get-vuln-url';
22
23 export function formatIssuesWithRemediation(
24 vulns: GroupedVuln[],
25 remediationInfo: RemediationChanges,
26 options: TestOptions,
27 ): string[] {
28 const basicVulnInfo: {
29 [name: string]: BasicVulnInfo;
30 } = {};
31
32 const basicLicenseInfo: {
33 [name: string]: BasicVulnInfo;
34 } = {};
35 for (const vuln of vulns) {
36 const vulnData = {
37 title: vuln.title,
38 severity: vuln.severity,
39 originalSeverity: vuln.originalSeverity,
40 isNew: vuln.isNew,
41 name: vuln.name,
42 type: vuln.metadata.type,
43 version: vuln.version,
44 fixedIn: vuln.fixedIn,
45 note: vuln.note,
46 legalInstructions: vuln.legalInstructionsArray,
47 paths: vuln.list.map((v) => v.from),
48 };
49
50 if (vulnData.type === 'license') {
51 basicLicenseInfo[vuln.metadata.id] = vulnData;
52 } else {
53 basicVulnInfo[vuln.metadata.id] = vulnData;
54 }
55 }
56
57 const results = [''];
58
59 let upgradeTextArray: string[];
60 if (remediationInfo.pin && Object.keys(remediationInfo.pin).length) {
61 const upgradesByAffected: UpgradesByAffectedPackage = {};
62 for (const topLevelPkg of Object.keys(remediationInfo.upgrade)) {
63 for (const targetPkgStr of remediationInfo.upgrade[topLevelPkg]
64 .upgrades) {
65 if (!upgradesByAffected[targetPkgStr]) {
66 upgradesByAffected[targetPkgStr] = [];
67 }
68 upgradesByAffected[targetPkgStr].push({
69 name: topLevelPkg,
70 version: remediationInfo.upgrade[topLevelPkg].upgradeTo,
71 });
72 }
73 }
74 upgradeTextArray = constructPinText(
75 remediationInfo.pin,
76 upgradesByAffected,
77 basicVulnInfo,
78 options,
79 );
80 const allVulnIds = new Set();
81 Object.keys(remediationInfo.pin).forEach((name) =>
82 remediationInfo.pin[name].vulns.forEach((vid) => allVulnIds.add(vid)),
83 );
84 remediationInfo.unresolved = remediationInfo.unresolved.filter(
85 (issue) => !allVulnIds.has(issue.id),
86 );
87 } else {
88 upgradeTextArray = constructUpgradesText(
89 remediationInfo.upgrade,
90 basicVulnInfo,
91 options,
92 );
93 }
94 if (upgradeTextArray.length > 0) {
95 results.push(upgradeTextArray.join('\n'));
96 }
97
98 const patchedTextArray = constructPatchesText(
99 remediationInfo.patch,
100 basicVulnInfo,
101 options,
102 );
103
104 if (patchedTextArray.length > 0) {
105 results.push(patchedTextArray.join('\n'));
106 }
107
108 const unfixableIssuesTextArray = constructUnfixableText(
109 remediationInfo.unresolved,
110 basicVulnInfo,
111 options,
112 );
113
114 if (unfixableIssuesTextArray.length > 0) {
115 results.push(unfixableIssuesTextArray.join('\n'));
116 }
117
118 const licenseIssuesTextArray = constructLicenseText(
119 basicLicenseInfo,
120 options,
121 );
122
123 if (licenseIssuesTextArray.length > 0) {
124 results.push(licenseIssuesTextArray.join('\n'));
125 }
126
127 return results;
128 }
129
130 function constructLicenseText(
131 basicLicenseInfo: {
132 [name: string]: BasicVulnInfo;
133 },
134 testOptions: TestOptions,
135 ): string[] {
136 if (!(Object.keys(basicLicenseInfo).length > 0)) {
137 return [];
138 }
139
140 const licenseTextArray = [chalk.bold.green('\nLicense issues:')];
141
142 for (const id of Object.keys(basicLicenseInfo)) {
143 const licenseText = formatIssue(
144 id,
145 basicLicenseInfo[id].title,
146 basicLicenseInfo[id].severity,
147 basicLicenseInfo[id].isNew,
148 `${basicLicenseInfo[id].name}@${basicLicenseInfo[id].version}`,
149 basicLicenseInfo[id].paths,
150 testOptions,
151 basicLicenseInfo[id].note,
152 undefined, // We can never override license rules, so no originalSeverity here
153 basicLicenseInfo[id].legalInstructions,
154 );
155 licenseTextArray.push('\n' + licenseText);
156 }
157 return licenseTextArray;
158 }
159
160 function constructPatchesText(
161 patches: {
162 [name: string]: PatchRemediation;
163 },
164 basicVulnInfo: {
165 [name: string]: BasicVulnInfo;
166 },
167 testOptions: TestOptions,
168 ): string[] {
169 if (!(Object.keys(patches).length > 0)) {
170 return [];
171 }
172 const patchedTextArray = [chalk.bold.green('\nPatchable issues:')];
173 for (const id of Object.keys(patches)) {
174 if (!basicVulnInfo[id]) {
175 continue;
176 }
177 if (basicVulnInfo[id].type === 'license') {
178 continue;
179 }
180
181 // todo: add vulnToPatch package name
182 const packageAtVersion = `${basicVulnInfo[id].name}@${basicVulnInfo[id].version}`;
183 const patchedText = `\n Patch available for ${chalk.bold.whiteBright(
184 packageAtVersion,
185 )}\n`;
186 const thisPatchFixes = formatIssue(
187 id,
188 basicVulnInfo[id].title,
189 basicVulnInfo[id].severity,
190 basicVulnInfo[id].isNew,
191 `${basicVulnInfo[id].name}@${basicVulnInfo[id].version}`,
192 basicVulnInfo[id].paths,
193 testOptions,
194 basicVulnInfo[id].note,
195 basicVulnInfo[id].originalSeverity,
196 );
197 patchedTextArray.push(patchedText + thisPatchFixes);
198 }
199
200 return patchedTextArray;
201 }
202
203 function thisUpgradeFixes(
204 vulnIds: string[],
205 basicVulnInfo: Record<string, BasicVulnInfo>,
206 testOptions: TestOptions,
207 ) {
208 return vulnIds
209 .filter((id) => basicVulnInfo[id]) // basicVulnInfo only contains issues with the specified severity levels
210 .sort(
211 (a, b) =>
212 getSeverityValue(basicVulnInfo[a].severity) -
213 getSeverityValue(basicVulnInfo[b].severity),
214 )
215 .filter((id) => basicVulnInfo[id].type !== 'license')
216 .map((id) =>
217 formatIssue(
218 id,
219 basicVulnInfo[id].title,
220 basicVulnInfo[id].severity,
221 basicVulnInfo[id].isNew,
222 `${basicVulnInfo[id].name}@${basicVulnInfo[id].version}`,
223 basicVulnInfo[id].paths,
224 testOptions,
225 basicVulnInfo[id].note,
226 basicVulnInfo[id].originalSeverity,
227 [],
228 ),
229 )
230 .join('\n');
231 }
232
233 function processUpgrades(
234 sink: string[],
235 upgradesByDep: DependencyUpdates | DependencyPins,
236 deps: string[],
237 basicVulnInfo: Record<string, BasicVulnInfo>,
238 testOptions: TestOptions,
239 ) {
240 for (const dep of deps) {
241 const data = upgradesByDep[dep];
242 const upgradeDepTo = data.upgradeTo;
243 const vulnIds =
244 (data as UpgradeRemediation).vulns || (data as PinRemediation).vulns;
245 const upgradeText = `\n Upgrade ${chalk.bold.whiteBright(
246 dep,
247 )} to ${chalk.bold.whiteBright(upgradeDepTo)} to fix\n`;
248 sink.push(
249 upgradeText + thisUpgradeFixes(vulnIds, basicVulnInfo, testOptions),
250 );
251 }
252 }
253
254 function constructUpgradesText(
255 upgrades: DependencyUpdates,
256 basicVulnInfo: {
257 [name: string]: BasicVulnInfo;
258 },
259 testOptions: TestOptions,
260 ): string[] {
261 if (!(Object.keys(upgrades).length > 0)) {
262 return [];
263 }
264
265 const upgradeTextArray = [chalk.bold.green('\nIssues to fix by upgrading:')];
266 processUpgrades(
267 upgradeTextArray,
268 upgrades,
269 Object.keys(upgrades),
270 basicVulnInfo,
271 testOptions,
272 );
273 return upgradeTextArray;
274 }
275
276 function constructPinText(
277 pins: DependencyPins,
278 upgradesByAffected: UpgradesByAffectedPackage, // classical "remediation via top-level dep" upgrades
279 basicVulnInfo: Record<string, BasicVulnInfo>,
280 testOptions: TestOptions,
281 ): string[] {
282 if (!Object.keys(pins).length) {
283 return [];
284 }
285
286 const upgradeTextArray: string[] = [];
287 upgradeTextArray.push(
288 chalk.bold.green('\nIssues to fix by upgrading dependencies:'),
289 );
290
291 // First, direct upgrades
292
293 const upgradeables = Object.keys(pins).filter(
294 (name) => !pins[name].isTransitive,
295 );
296 if (upgradeables.length) {
297 processUpgrades(
298 upgradeTextArray,
299 pins,
300 upgradeables,
301 basicVulnInfo,
302 testOptions,
303 );
304 }
305
306 // Second, pins
307 const pinables = Object.keys(pins).filter((name) => pins[name].isTransitive);
308
309 if (pinables.length) {
310 for (const pkgName of pinables) {
311 const data = pins[pkgName];
312 const vulnIds = data.vulns;
313 const upgradeDepTo = data.upgradeTo;
314 const upgradeText = `\n Pin ${chalk.bold.whiteBright(
315 pkgName,
316 )} to ${chalk.bold.whiteBright(upgradeDepTo)} to fix`;
317 upgradeTextArray.push(upgradeText);
318 upgradeTextArray.push(
319 thisUpgradeFixes(vulnIds, basicVulnInfo, testOptions),
320 );
321
322 // Finally, if we have some upgrade paths that fix the same issues, suggest them as well.
323 const topLevelUpgradesAlreadySuggested = new Set();
324 for (const vid of vulnIds) {
325 for (const topLevelPkg of upgradesByAffected[
326 pkgName + '@' + basicVulnInfo[vid].version
327 ] || []) {
328 const setKey = `${topLevelPkg.name}\n${topLevelPkg.version}`;
329 if (!topLevelUpgradesAlreadySuggested.has(setKey)) {
330 topLevelUpgradesAlreadySuggested.add(setKey);
331 upgradeTextArray.push(
332 ' The issues above can also be fixed by upgrading top-level dependency ' +
333 `${topLevelPkg.name} to ${topLevelPkg.version}`,
334 );
335 }
336 }
337 }
338 }
339 }
340
341 return upgradeTextArray;
342 }
343
344 function constructUnfixableText(
345 unresolved: IssueData[],
346 basicVulnInfo: Record<string, BasicVulnInfo>,
347 testOptions: TestOptions,
348 ) {
349 if (!(unresolved.length > 0)) {
350 return [];
351 }
352 const unfixableIssuesTextArray = [
353 chalk.bold.white('\nIssues with no direct upgrade or patch:'),
354 ];
355 for (const issue of unresolved) {
356 const issueInfo = basicVulnInfo[issue.id];
357 if (!issueInfo) {
358 // basicVulnInfo only contains issues with the specified severity levels
359 continue;
360 }
361
362 const extraInfo =
363 issue.fixedIn && issue.fixedIn.length
364 ? `\n This issue was fixed in versions: ${chalk.bold(
365 issue.fixedIn.join(', '),
366 )}`
367 : '\n No upgrade or patch available';
368 unfixableIssuesTextArray.push(
369 formatIssue(
370 issue.id,
371 issue.title,
372 issue.severity,
373 issue.isNew,
374 `${issue.packageName}@${issue.version}`,
375 issueInfo.paths,
376 testOptions,
377 issueInfo.note,
378 issueInfo.originalSeverity,
379 [],
380 ) + `${extraInfo}`,
381 );
382 }
383
384 if (unfixableIssuesTextArray.length === 1) {
385 // seems we still only have
386 // the initial section title, so nothing to return
387 return [];
388 }
389
390 return unfixableIssuesTextArray;
391 }
392
393 export function printPath(path: string[], slice = 1) {
394 return path.slice(slice).join(PATH_SEPARATOR);
395 }
396
397 export function formatIssue(
398 id: string,
399 title: string,
400 severity: SEVERITY,
401 isNew: boolean,
402 vulnerableModule: string,
403 paths: string[][],
404 testOptions: TestOptions,
405 note: string | false,
406 originalSeverity?: SEVERITY,
407 legalInstructions?: LegalInstruction[],
408 ): string {
409 const newBadge = isNew ? ' (new)' : '';
410 const name = vulnerableModule ? ` in ${chalk.bold(vulnerableModule)}` : '';
411 let legalLicenseInstructionsText;
412 if (legalInstructions) {
413 legalLicenseInstructionsText = formatLegalInstructions(legalInstructions);
414 }
415
416 let introducedBy = '';
417
418 if (
419 testOptions.showVulnPaths === 'some' &&
420 paths &&
421 paths.find((p) => p.length > 1)
422 ) {
423 // In this mode, we show only one path by default, for compactness
424 const pathStr = printPath(paths[0]);
425 introducedBy =
426 paths.length === 1
427 ? `\n introduced by ${pathStr}`
428 : `\n introduced by ${pathStr} and ${chalk.cyanBright(
429 '' + (paths.length - 1),
430 )} other path(s)`;
431 } else if (testOptions.showVulnPaths === 'all' && paths) {
432 introducedBy =
433 '\n introduced by:' +
434 paths
435 .slice(0, 1000)
436 .map((p) => '\n ' + printPath(p))
437 .join('');
438 if (paths.length > 1000) {
439 introducedBy += `\n and ${chalk.cyanBright(
440 '' + (paths.length - 1),
441 )} other path(s)`;
442 }
443 }
444
445 let originalSeverityStr = '';
446 if (originalSeverity && originalSeverity !== severity) {
447 originalSeverityStr = ` (originally ${titleCaseText(originalSeverity)})`;
448 }
449
450 return (
451 colorTextBySeverity(
452 severity,
453 ` ${icon.ISSUE} ${chalk.bold(title)}${newBadge} [${titleCaseText(
454 severity,
455 )} Severity${originalSeverityStr}]`,
456 ) +
457 `[${getVulnerabilityUrl(id)}]` +
458 name +
459 introducedBy +
460 (legalLicenseInstructionsText
461 ? `${chalk.bold(
462 '\n Legal instructions',
463 )}:\n ${legalLicenseInstructionsText}`
464 : '') +
465 (note ? `${chalk.bold('\n Note')}:\n ${note}` : '')
466 );
467 }
468
469 function titleCaseText(text) {
470 return text[0].toUpperCase() + text.slice(1);
471 }