From 140a2a9457f13993b00b9770f3d9d1df7347717f Mon Sep 17 00:00:00 2001 From: Kaspar Lyngsie Date: Tue, 5 Dec 2023 14:20:06 +0100 Subject: [PATCH] feat: adding manifest file to vuln card if scanning multi-project --- src/lib/snyk-to-html.ts | 98 +++++++++++++++++++----------- template/test-report.header.hbs | 12 +++- template/test-report.vuln-card.hbs | 5 ++ 3 files changed, 78 insertions(+), 37 deletions(-) diff --git a/src/lib/snyk-to-html.ts b/src/lib/snyk-to-html.ts index 5ebe3a4..19f86f2 100755 --- a/src/lib/snyk-to-html.ts +++ b/src/lib/snyk-to-html.ts @@ -4,15 +4,13 @@ import * as isEmpty from 'lodash.isempty'; import * as orderBy from 'lodash.orderby'; import chalk from 'chalk'; import * as debugModule from 'debug'; +import { addIssueDataToPatch, getUpgrades, IacProjectType, severityMap } from './vuln'; +import { processSourceCode, } from './codeutil'; import fs = require('fs'); import Handlebars = require('handlebars'); import marked = require('marked'); import moment = require('moment'); import path = require('path'); -import { addIssueDataToPatch, getUpgrades, severityMap, IacProjectType } from './vuln'; -import { - processSourceCode, -} from './codeutil'; const debug = debugModule('snyk-to-html'); @@ -31,8 +29,8 @@ function readFile(filePath: string, encoding: string): Promise { function handleInvalidJson(reason: any) { if (reason.isInvalidJson) { - reason.message = reason.message + 'Error running `snyk-to-html`. Please check you are providing the correct parameters. ' + - 'Is the issue persists contact support@snyk.io'; + reason.message = reason.message + 'Error running `snyk-to-html`. Please check you are providing the correct parameters. ' + + 'Is the issue persists contact support@snyk.io'; } console.log(reason.message); } @@ -43,7 +41,7 @@ function promisedParseJSON(json) { resolve(JSON.parse(json)); } catch (error) { error.message = chalk.red.bold('The source provided is not a valid json! Please validate that the input provided to the CLI is an actual JSON\n\n' + - 'Tip: To find more information, try running `snyk-to-html` in debug mode by appending to the CLI the `-d` parameter\n\n'); + 'Tip: To find more information, try running `snyk-to-html` in debug mode by appending to the CLI the `-d` parameter\n\n'); debug(`Input provided to the CLI: \n${json}\n\n`); error.isInvalidJson = true; reject(error); @@ -99,7 +97,7 @@ class SnykToHtml { export { SnykToHtml }; function metadataForVuln(vuln: any) { - let {cveSpaced, cveLineBreaks} = concatenateCVEs(vuln) + let { cveSpaced, cveLineBreaks } = concatenateCVEs(vuln) return { id: vuln.id, @@ -126,18 +124,18 @@ function concatenateCVEs(vuln: any) { let cveLineBreaks = '' if (vuln.identifiers) { - vuln.identifiers.CVE.forEach(function(c) { + vuln.identifiers.CVE.forEach(function (c) { let cveLink = `${c}` cveSpaced += `${cveLink} ` cveLineBreaks += `${cveLink}
` }) } - return {cveSpaced, cveLineBreaks} + return { cveSpaced, cveLineBreaks } } function dateFromDateTimeString(dateTimeString: string) { - return dateTimeString.substr(0,10); + return dateTimeString.substr(0, 10); } function groupVulns(vulns) { @@ -148,7 +146,7 @@ function groupVulns(vulns) { if (vulns && Array.isArray(vulns)) { vulns.map(vuln => { if (!result[vuln.id]) { - result[vuln.id] = {list: [vuln], metadata: metadataForVuln(vuln)}; + result[vuln.id] = { list: [vuln], metadata: metadataForVuln(vuln) }; pathsCount++; uniqueCount++; } else { @@ -180,12 +178,12 @@ async function generateTemplate(data: any, template: string, showRemediation: boolean, summary: boolean): - Promise { + Promise { if (showRemediation && data.remediation) { data.showRemediations = showRemediation; - const {upgrade, pin, unresolved, patch} = data.remediation; + const { upgrade, pin, unresolved, patch } = data.remediation; data.anyRemediations = !isEmpty(upgrade) || - !isEmpty(patch) || !isEmpty(pin); + !isEmpty(patch) || !isEmpty(pin); data.anyUnresolved = !!unresolved?.vulnerabilities; data.unresolved = groupVulns(unresolved); data.upgrades = getUpgrades(upgrade, data.vulnerabilities); @@ -206,7 +204,7 @@ async function generateTemplate(data: any, data.uniqueCount = vulnMetadata.vulnerabilitiesUniqueCount; data.summary = vulnMetadata.vulnerabilitiesPathsCount + ' vulnerable dependency paths'; data.showSummaryOnly = summary; - if(data.paths?.length === 1){ + if (data.paths?.length === 1) { data.packageManager = data.paths[0].packageManager; } @@ -256,7 +254,22 @@ async function generateCodeTemplate( } function mergeData(dataArray: any[]): any { - const vulnsArrays = dataArray.map(project => project.vulnerabilities || []); + const vulnsArrays = dataArray.map((project) => { + if (!project.vulnerabilities) { + return []; + } + + // Add project data to each of the vulnerabilities to display more + // details on each vulnerability card, in order to properly distinguish + // from which project a vuln is connected, in case of displaying multiple + // projects. + const vulns = project.vulnerabilities.map((vuln) => ({ + ...vuln, + displayTargetFile: project.displayTargetFile, + path: project.path + })); + return vulns; + }); const aggregateVulnerabilities = [].concat(...vulnsArrays); const totalUniqueCount = @@ -264,7 +277,11 @@ function mergeData(dataArray: any[]): any { const totalDepCount = dataArray.reduce((acc, item) => acc + item.dependencyCount || 0, 0); - const paths = dataArray.map(project => ({ path: project.path, packageManager: project.packageManager })); + const paths = dataArray.map(project => ({ + path: project.path, + packageManager: project.packageManager, + displayTargetFile: project.displayTargetFile, + })); return { vulnerabilities: aggregateVulnerabilities, @@ -285,7 +302,7 @@ async function processIacData(data: any, template: string, summary: boolean): Pr return generateIacTemplate(data, template); } - const dataArray = Array.isArray(data)? data : [data]; + const dataArray = Array.isArray(data) ? data : [data]; dataArray.forEach(project => { project.infrastructureAsCodeIssues.forEach(issue => { issue.severityValue = severityMap[issue.severity]; @@ -325,7 +342,7 @@ async function processCodeData( const dataArray = Array.isArray(data) ? data : [data]; const OrderedIssuesArray = await processSourceCode(dataArray); - + const totalIssues = dataArray[0].runs[0].results.length; const processedData = { projects: OrderedIssuesArray, @@ -368,34 +385,43 @@ const hh = { // block helpers /* tslint:disable:only-arrow-functions */ /* tslint:disable:object-literal-shorthand */ - isDoubleArray: function(data, options) { + isDoubleArray: function (data, options) { return Array.isArray(data[0]) ? options.fn(data) : options.inverse(data); }, - if_eq: function(this: void, a, b, opts) { + if_eq: function (this: void, a, b, opts) { return (a === b) ? opts.fn(this) : opts.inverse(this); }, - if_gt: function(this: void, a, b, opts) { + if_gt: function (this: void, a, b, opts) { return (a > b) ? opts.fn(this) : opts.inverse(this); }, - if_not_eq: function(this: void, a, b, opts) { + if_not_eq: function (this: void, a, b, opts) { return (a !== b) ? opts.fn(this) : opts.inverse(this); }, - if_any: function(this: void, opts, ...args) { + if_any: function (this: void, opts, ...args) { return args.some(v => !!v) ? opts.fn(this) : opts.inverse(this); }, - ifCond: function(this: void, v1, operator, v2, options) { + ifCond: function (this: void, v1, operator, v2, options) { const choose = (pred: boolean) => pred ? options.fn(this) : options.inverse(this); switch (operator) { // tslint:disable-next-line:triple-equals - case '==': return choose(v1 == v2); - case '===': return choose(v1 === v2); - case '<': return choose(v1 < v2); - case '<=': return choose(v1 <= v2); - case '>': return choose(v1 > v2); - case '>=': return choose(v1 >= v2); - case '&&': return choose(v1 && v2); - case '||': return choose(v1 || v2); - default: return choose(false); + case '==': + return choose(v1 == v2); + case '===': + return choose(v1 === v2); + case '<': + return choose(v1 < v2); + case '<=': + return choose(v1 <= v2); + case '>': + return choose(v1 > v2); + case '>=': + return choose(v1 >= v2); + case '&&': + return choose(v1 && v2); + case '||': + return choose(v1 || v2); + default: + return choose(false); } }, getRemediation: (description, fixedIn) => { @@ -416,7 +442,7 @@ const hh = { severityLabel: (severity: string) => { return severity[0].toUpperCase(); }, - startsWith: function(str, start, options) { + startsWith: function (str, start, options) { return str.startsWith(start) ? options.fn(this) : options.inverse(this); }, }; diff --git a/template/test-report.header.hbs b/template/test-report.header.hbs index 26bc5cc..2c505a9 100644 --- a/template/test-report.header.hbs +++ b/template/test-report.header.hbs @@ -24,7 +24,13 @@
Scanned the following paths:
    - {{#each paths}}
  • {{path}} ({{packageManager}})
  • {{/each}} + {{#each paths}} + {{#if_not_eq packageManager "nuget"}} +
  • {{path}} ({{packageManager}})
  • + {{else}} +
  • {{path}}/{{displayTargetFile}} ({{packageManager}})
  • + {{/if_not_eq}} + {{/each}}
{{/if}} @@ -32,7 +38,11 @@
Scanned the following path:
    + {{#if_not_eq packageManager "nuget"}}
  • {{path}} ({{packageManager}})
  • + {{else}} +
  • {{path}}/{{dislpayTargetFile}} ({{packageManager}})
  • + {{/if_not_eq}}
{{/if}} diff --git a/template/test-report.vuln-card.hbs b/template/test-report.vuln-card.hbs index 0c28f02..e3b46e0 100644 --- a/template/test-report.vuln-card.hbs +++ b/template/test-report.vuln-card.hbs @@ -9,6 +9,11 @@
    + {{#if list.[0].displayTargetFile }} +
  • + Manifest file: {{list.[0].path}} {{list.[0].displayTargetFile}} +
  • + {{/if}}
  • Package Manager: {{metadata.packageManager}}