@contrast/contrast 1.0.13 → 1.0.14
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/dist/audit/report/commonReportingFunctions.js +175 -116
- package/dist/audit/report/models/reportSeverityModel.js +3 -3
- package/dist/audit/report/reportingFeature.js +1 -10
- package/dist/audit/report/utils/reportUtils.js +4 -4
- package/dist/commands/scan/sca/scaAnalysis.js +0 -1
- package/dist/common/errorHandling.js +1 -2
- package/dist/constants/constants.js +1 -1
- package/dist/constants/locales.js +15 -7
- package/dist/constants.js +2 -2
- package/dist/index.js +4 -2
- package/dist/lambda/lambda.js +1 -1
- package/dist/scaAnalysis/common/auditReport.js +78 -0
- package/dist/scaAnalysis/common/scaServicesUpload.js +12 -11
- package/dist/scan/formatScanOutput.js +4 -29
- package/package.json +4 -1
- package/src/audit/report/commonReportingFunctions.js +432 -0
- package/src/audit/report/models/reportSeverityModel.ts +6 -6
- package/src/audit/report/reportingFeature.ts +2 -16
- package/src/audit/report/utils/reportUtils.ts +2 -8
- package/src/commands/scan/sca/scaAnalysis.js +3 -4
- package/src/common/errorHandling.ts +1 -2
- package/src/constants/constants.js +1 -1
- package/src/constants/locales.js +16 -7
- package/src/constants.js +2 -2
- package/src/index.ts +4 -6
- package/src/lambda/lambda.ts +1 -1
- package/src/lambda/lambdaUtils.ts +1 -1
- package/src/scaAnalysis/common/auditReport.js +108 -0
- package/src/scaAnalysis/common/scaServicesUpload.js +17 -15
- package/src/scan/formatScanOutput.ts +5 -42
- package/src/audit/report/commonReportingFunctions.ts +0 -355
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const { getSeverityCounts, createSummaryMessageTop, printVulnInfo, getReportTable, getIssueRow, printNoVulnFoundMsg } = require('../../audit/report/commonReportingFunctions');
|
|
3
|
+
const { orderBy } = require('lodash');
|
|
4
|
+
const { assignBySeverity } = require('../../scan/formatScanOutput');
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const { CE_URL } = require('../../constants/constants');
|
|
7
|
+
const common = require('../../common/fail');
|
|
8
|
+
const processAuditReport = (config, results) => {
|
|
9
|
+
let severityCounts = {};
|
|
10
|
+
if (results !== undefined) {
|
|
11
|
+
severityCounts = formatScaServicesReport(config, results);
|
|
12
|
+
}
|
|
13
|
+
if (config.fail) {
|
|
14
|
+
common.processFail(config, severityCounts);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const formatScaServicesReport = (config, results) => {
|
|
18
|
+
const projectOverviewCount = getSeverityCounts(results);
|
|
19
|
+
if (projectOverviewCount.total === 0) {
|
|
20
|
+
printNoVulnFoundMsg();
|
|
21
|
+
return projectOverviewCount;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
let total = 0;
|
|
25
|
+
const numberOfCves = results.length;
|
|
26
|
+
const table = getReportTable();
|
|
27
|
+
let contrastHeaderNumCounter = 0;
|
|
28
|
+
let assignPriorityToResults = results.map(result => assignBySeverity(result, result));
|
|
29
|
+
const numberOfVulns = results
|
|
30
|
+
.map(result => result.vulnerabilities)
|
|
31
|
+
.reduce((a, b) => {
|
|
32
|
+
return (total += b.length);
|
|
33
|
+
}, 0);
|
|
34
|
+
const outputOrderedByLowestSeverityAndLowestNumOfCvesFirst = orderBy(assignPriorityToResults, [
|
|
35
|
+
reportListItem => {
|
|
36
|
+
return reportListItem.priority;
|
|
37
|
+
},
|
|
38
|
+
reportListItem => {
|
|
39
|
+
return reportListItem.vulnerabilities.length;
|
|
40
|
+
}
|
|
41
|
+
], ['asc', 'desc']);
|
|
42
|
+
for (const result of outputOrderedByLowestSeverityAndLowestNumOfCvesFirst) {
|
|
43
|
+
contrastHeaderNumCounter++;
|
|
44
|
+
const cvesNum = result.vulnerabilities.length;
|
|
45
|
+
const grammaticallyCorrectVul = result.vulnerabilities.length > 1 ? 'vulnerabilities' : 'vulnerability';
|
|
46
|
+
const headerColour = chalk.hex(result.colour);
|
|
47
|
+
const headerRow = [
|
|
48
|
+
headerColour(`CONTRAST-${contrastHeaderNumCounter.toString().padStart(3, '0')}`),
|
|
49
|
+
headerColour(`-`),
|
|
50
|
+
headerColour(`[${result.severity}] `) +
|
|
51
|
+
headerColour.bold(`${result.artifactName}`) +
|
|
52
|
+
` introduces ${cvesNum} ${grammaticallyCorrectVul}`
|
|
53
|
+
];
|
|
54
|
+
const adviceRow = [
|
|
55
|
+
chalk.bold(`Advice`),
|
|
56
|
+
chalk.bold(`:`),
|
|
57
|
+
`Change to version ${result.remediationAdvice.latestStableVersion}`
|
|
58
|
+
];
|
|
59
|
+
let assignPriorityToVulns = result.vulnerabilities.map(result => assignBySeverity(result, result));
|
|
60
|
+
const issueRow = getIssueRow(assignPriorityToVulns);
|
|
61
|
+
table.push(headerRow, issueRow, adviceRow);
|
|
62
|
+
console.log();
|
|
63
|
+
}
|
|
64
|
+
console.log();
|
|
65
|
+
createSummaryMessageTop(numberOfCves, numberOfVulns);
|
|
66
|
+
console.log(table.toString() + '\n');
|
|
67
|
+
printVulnInfo(projectOverviewCount);
|
|
68
|
+
if (config.host !== CE_URL) {
|
|
69
|
+
console.log('\n' + chalk.bold('View your full dependency tree in Contrast:'));
|
|
70
|
+
console.log(`${config.host}/Contrast/static/ng/index.html#/${config.organizationId}/applications/${config.applicationId}/libs/dependency-tree`);
|
|
71
|
+
}
|
|
72
|
+
return projectOverviewCount;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
module.exports = {
|
|
76
|
+
formatScaServicesReport,
|
|
77
|
+
processAuditReport
|
|
78
|
+
};
|
|
@@ -7,7 +7,7 @@ const scaTreeUpload = async (analysis, config) => {
|
|
|
7
7
|
applicationId: config.applicationId,
|
|
8
8
|
dependencyTree: analysis,
|
|
9
9
|
organizationId: config.organizationId,
|
|
10
|
-
language:
|
|
10
|
+
language: config.language,
|
|
11
11
|
tool: {
|
|
12
12
|
name: 'Contrast Codesec',
|
|
13
13
|
version: APP_VERSION
|
|
@@ -27,25 +27,26 @@ const scaTreeUpload = async (analysis, config) => {
|
|
|
27
27
|
.catch(err => {
|
|
28
28
|
throw err;
|
|
29
29
|
});
|
|
30
|
+
console.log(' polling report');
|
|
30
31
|
let keepChecking = true;
|
|
32
|
+
let res;
|
|
31
33
|
while (keepChecking) {
|
|
32
|
-
|
|
33
|
-
.
|
|
34
|
-
.then(res => {
|
|
34
|
+
res = await client.scaServiceReportStatus(config, reportID).then(res => {
|
|
35
|
+
console.log(res.statusCode);
|
|
35
36
|
console.log(res.body);
|
|
36
37
|
if (res.body.status == 'COMPLETED') {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
keepChecking = false;
|
|
39
|
+
return client.scaServiceReport(config, reportID).then(res => {
|
|
40
|
+
return res.body;
|
|
40
41
|
});
|
|
41
|
-
return (keepChecking = false);
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
return (keepChecking = true);
|
|
45
42
|
}
|
|
46
43
|
});
|
|
44
|
+
if (!keepChecking) {
|
|
45
|
+
return res;
|
|
46
|
+
}
|
|
47
47
|
await requestUtils.sleep(5000);
|
|
48
48
|
}
|
|
49
|
+
return res;
|
|
49
50
|
};
|
|
50
51
|
module.exports = {
|
|
51
52
|
scaTreeUpload
|
|
@@ -3,16 +3,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.assignBySeverity = exports.stripTags = exports.getCodeFlowInfo = exports.getSourceLineNumber = exports.getLocationsSyncInfo = exports.editVulName = exports.getDefaultView = exports.formatLinks = exports.
|
|
6
|
+
exports.assignBySeverity = exports.stripTags = exports.getCodeFlowInfo = exports.getSourceLineNumber = exports.getLocationsSyncInfo = exports.editVulName = exports.getDefaultView = exports.formatLinks = exports.formatScanOutput = void 0;
|
|
7
7
|
const i18n_1 = __importDefault(require("i18n"));
|
|
8
8
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
9
|
const groupedResultsModel_1 = require("./models/groupedResultsModel");
|
|
10
10
|
const lodash_1 = require("lodash");
|
|
11
11
|
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
12
12
|
const constants_1 = require("../constants/constants");
|
|
13
|
+
const commonReportingFunctions_1 = require("../audit/report/commonReportingFunctions");
|
|
13
14
|
function formatScanOutput(scanResults) {
|
|
14
15
|
const { scanResultsInstances } = scanResults;
|
|
15
|
-
const projectOverview =
|
|
16
|
+
const projectOverview = (0, commonReportingFunctions_1.getSeverityCounts)(scanResultsInstances.content);
|
|
16
17
|
if (scanResultsInstances.content.length === 0) {
|
|
17
18
|
console.log(i18n_1.default.__('scanNoVulnerabilitiesFound'));
|
|
18
19
|
console.log(i18n_1.default.__('scanNoVulnerabilitiesFoundSecureCode'));
|
|
@@ -89,36 +90,10 @@ function formatScanOutput(scanResults) {
|
|
|
89
90
|
console.log();
|
|
90
91
|
});
|
|
91
92
|
}
|
|
92
|
-
printVulnInfo(projectOverview);
|
|
93
|
+
(0, commonReportingFunctions_1.printVulnInfo)(projectOverview);
|
|
93
94
|
return projectOverview;
|
|
94
95
|
}
|
|
95
96
|
exports.formatScanOutput = formatScanOutput;
|
|
96
|
-
function printVulnInfo(projectOverview) {
|
|
97
|
-
const totalVulnerabilities = projectOverview.total;
|
|
98
|
-
const vulMessage = totalVulnerabilities === 1 ? `vulnerability` : `vulnerabilities`;
|
|
99
|
-
console.log(chalk_1.default.bold(`Found ${totalVulnerabilities} ${vulMessage}`));
|
|
100
|
-
console.log(i18n_1.default.__('foundDetailedVulnerabilities', String(projectOverview.critical), String(projectOverview.high), String(projectOverview.medium), String(projectOverview.low), String(projectOverview.note)));
|
|
101
|
-
}
|
|
102
|
-
function getProjectOverview(scanResultsInstances) {
|
|
103
|
-
const acc = {
|
|
104
|
-
critical: 0,
|
|
105
|
-
high: 0,
|
|
106
|
-
medium: 0,
|
|
107
|
-
low: 0,
|
|
108
|
-
note: 0,
|
|
109
|
-
total: 0
|
|
110
|
-
};
|
|
111
|
-
if (scanResultsInstances?.content &&
|
|
112
|
-
scanResultsInstances.content.length > 0) {
|
|
113
|
-
scanResultsInstances.content.forEach((i) => {
|
|
114
|
-
acc[i.severity.toLowerCase()] += 1;
|
|
115
|
-
acc.total += 1;
|
|
116
|
-
return acc;
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
return acc;
|
|
120
|
-
}
|
|
121
|
-
exports.getProjectOverview = getProjectOverview;
|
|
122
97
|
function formatLinks(objName, entry) {
|
|
123
98
|
const line = chalk_1.default.bold(objName + ' : ');
|
|
124
99
|
if (entry.length === 1) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/contrast",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"description": "Contrast Security's command line tool",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,10 @@
|
|
|
25
25
|
"test-int-scan": "jest ./test-integration/scan",
|
|
26
26
|
"test-int-audit": "jest test-integration/audit",
|
|
27
27
|
"test-int-audit-reports": "jest test-integration/audit/audit-language-reports.spec.js",
|
|
28
|
+
"test-int-scan-errors": "jest test-integration/scan/scanLocalErrors.spec.js",
|
|
29
|
+
"test-int-scan-reports": "jest test-integration/scan/scanReport.spec.js",
|
|
28
30
|
"test-int-audit-features": "jest test-integration/audit/auditFeatures/",
|
|
31
|
+
"test-int-audit-experimental": "jest test-integration/audit/audit-experimental.spec.js",
|
|
29
32
|
"format": "prettier --write \"**/*.{ts,tsx,js,json,md,yml}\" .eslintrc.*",
|
|
30
33
|
"check-format": "prettier --check \"**/*.{ts,tsx,js,json,md,yml}\" .eslintrc.*",
|
|
31
34
|
"coverage-local": "nyc --reporter=text mocha './test/**/*.spec.js'",
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
const commonApi = require('../../utils/commonApi')
|
|
2
|
+
const {
|
|
3
|
+
ReportCompositeKey,
|
|
4
|
+
ReportList,
|
|
5
|
+
ReportModelStructure
|
|
6
|
+
} = require('./models/reportListModel')
|
|
7
|
+
const { orderBy } = require('lodash')
|
|
8
|
+
const chalk = require('chalk')
|
|
9
|
+
const {
|
|
10
|
+
countVulnerableLibrariesBySeverity,
|
|
11
|
+
orderByHighestPriority,
|
|
12
|
+
findHighestSeverityCVE,
|
|
13
|
+
findNameAndVersion,
|
|
14
|
+
severityCountAllCVEs,
|
|
15
|
+
findCVESeverity
|
|
16
|
+
} = require('./utils/reportUtils')
|
|
17
|
+
const { SeverityCountModel } = require('./models/severityCountModel')
|
|
18
|
+
const {
|
|
19
|
+
ReportOutputBodyModel,
|
|
20
|
+
ReportOutputHeaderModel,
|
|
21
|
+
ReportOutputModel
|
|
22
|
+
} = require('./models/reportOutputModel')
|
|
23
|
+
const {
|
|
24
|
+
CE_URL,
|
|
25
|
+
CRITICAL_COLOUR,
|
|
26
|
+
HIGH_COLOUR,
|
|
27
|
+
LOW_COLOUR,
|
|
28
|
+
MEDIUM_COLOUR,
|
|
29
|
+
NOTE_COLOUR
|
|
30
|
+
} = require('../../constants/constants')
|
|
31
|
+
const Table = require('cli-table3')
|
|
32
|
+
const { ReportGuidanceModel } = require('./models/reportGuidanceModel')
|
|
33
|
+
const i18n = require('i18n')
|
|
34
|
+
|
|
35
|
+
const createSummaryMessageTop = (numberOfVulnerableLibraries, numberOfCves) => {
|
|
36
|
+
numberOfVulnerableLibraries === 1
|
|
37
|
+
? console.log(`Found 1 vulnerable library containing ${numberOfCves} CVE`)
|
|
38
|
+
: console.log(
|
|
39
|
+
`Found ${numberOfVulnerableLibraries} vulnerable libraries containing ${numberOfCves} CVEs`
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const createSummaryMessageBottom = numberOfVulnerableLibraries => {
|
|
44
|
+
numberOfVulnerableLibraries === 1
|
|
45
|
+
? console.log(`Found 1 vulnerability`)
|
|
46
|
+
: console.log(`Found ${numberOfVulnerableLibraries} vulnerabilities`)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const getReport = async (config, reportId) => {
|
|
50
|
+
const client = commonApi.getHttpClient(config)
|
|
51
|
+
return client
|
|
52
|
+
.getReportById(config, reportId)
|
|
53
|
+
.then(res => {
|
|
54
|
+
if (res.statusCode === 200) {
|
|
55
|
+
return res.body
|
|
56
|
+
} else {
|
|
57
|
+
console.log(JSON.stringify(res.statusCode))
|
|
58
|
+
commonApi.handleResponseErrors(res, 'report')
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
.catch(err => {
|
|
62
|
+
console.log(err)
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const printVulnerabilityResponse = (
|
|
67
|
+
config,
|
|
68
|
+
vulnerableLibraries,
|
|
69
|
+
numberOfVulnerableLibraries,
|
|
70
|
+
numberOfCves,
|
|
71
|
+
guidance
|
|
72
|
+
) => {
|
|
73
|
+
let hasSomeVulnerabilitiesReported = false
|
|
74
|
+
printFormattedOutput(
|
|
75
|
+
config,
|
|
76
|
+
vulnerableLibraries,
|
|
77
|
+
numberOfVulnerableLibraries,
|
|
78
|
+
numberOfCves,
|
|
79
|
+
guidance
|
|
80
|
+
)
|
|
81
|
+
if (Object.keys(vulnerableLibraries).length > 0) {
|
|
82
|
+
hasSomeVulnerabilitiesReported = true
|
|
83
|
+
}
|
|
84
|
+
return hasSomeVulnerabilitiesReported
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const printFormattedOutput = (
|
|
88
|
+
config,
|
|
89
|
+
libraries,
|
|
90
|
+
numberOfVulnerableLibraries,
|
|
91
|
+
numberOfCves,
|
|
92
|
+
guidance
|
|
93
|
+
) => {
|
|
94
|
+
createSummaryMessageTop(numberOfVulnerableLibraries, numberOfCves)
|
|
95
|
+
console.log()
|
|
96
|
+
const report = new ReportList()
|
|
97
|
+
|
|
98
|
+
for (const library of libraries) {
|
|
99
|
+
const { name, version } = findNameAndVersion(library, config)
|
|
100
|
+
|
|
101
|
+
const newOutputModel = new ReportModelStructure(
|
|
102
|
+
new ReportCompositeKey(
|
|
103
|
+
name,
|
|
104
|
+
version,
|
|
105
|
+
findHighestSeverityCVE(library.cveArray),
|
|
106
|
+
severityCountAllCVEs(
|
|
107
|
+
library.cveArray,
|
|
108
|
+
new SeverityCountModel()
|
|
109
|
+
).getTotal
|
|
110
|
+
),
|
|
111
|
+
library.cveArray
|
|
112
|
+
)
|
|
113
|
+
report.reportOutputList.push(newOutputModel)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const outputOrderedByLowestSeverityAndLowestNumOfCvesFirst = orderBy(
|
|
117
|
+
report.reportOutputList,
|
|
118
|
+
[
|
|
119
|
+
reportListItem => {
|
|
120
|
+
return reportListItem.compositeKey.highestSeverity.priority
|
|
121
|
+
},
|
|
122
|
+
reportListItem => {
|
|
123
|
+
return reportListItem.compositeKey.numberOfSeverities
|
|
124
|
+
}
|
|
125
|
+
],
|
|
126
|
+
['asc', 'desc']
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
let contrastHeaderNumCounter = 0
|
|
130
|
+
for (const reportModel of outputOrderedByLowestSeverityAndLowestNumOfCvesFirst) {
|
|
131
|
+
contrastHeaderNumCounter++
|
|
132
|
+
const { libraryName, libraryVersion, highestSeverity } =
|
|
133
|
+
reportModel.compositeKey
|
|
134
|
+
const numOfCVEs = reportModel.cveArray.length
|
|
135
|
+
|
|
136
|
+
const table = getReportTable()
|
|
137
|
+
|
|
138
|
+
const header = buildHeader(
|
|
139
|
+
highestSeverity,
|
|
140
|
+
contrastHeaderNumCounter,
|
|
141
|
+
libraryName,
|
|
142
|
+
libraryVersion,
|
|
143
|
+
numOfCVEs
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
const advice = gatherRemediationAdvice(
|
|
147
|
+
guidance,
|
|
148
|
+
libraryName,
|
|
149
|
+
libraryVersion
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
const body = buildBody(reportModel.cveArray, advice)
|
|
153
|
+
|
|
154
|
+
const reportOutputModel = new ReportOutputModel(header, body)
|
|
155
|
+
|
|
156
|
+
table.push(
|
|
157
|
+
reportOutputModel.body.issueMessage,
|
|
158
|
+
reportOutputModel.body.adviceMessage
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
console.log(
|
|
162
|
+
reportOutputModel.header.vulnMessage,
|
|
163
|
+
reportOutputModel.header.introducesMessage
|
|
164
|
+
)
|
|
165
|
+
console.log(table.toString() + '\n')
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
createSummaryMessageBottom(numberOfVulnerableLibraries)
|
|
169
|
+
const {
|
|
170
|
+
criticalMessage,
|
|
171
|
+
highMessage,
|
|
172
|
+
mediumMessage,
|
|
173
|
+
lowMessage,
|
|
174
|
+
noteMessage
|
|
175
|
+
} = buildFooter(outputOrderedByLowestSeverityAndLowestNumOfCvesFirst)
|
|
176
|
+
console.log(
|
|
177
|
+
`${criticalMessage} | ${highMessage} | ${mediumMessage} | ${lowMessage} | ${noteMessage}`
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if (config.host !== CE_URL) {
|
|
181
|
+
console.log(
|
|
182
|
+
'\n' + chalk.bold('View your full dependency tree in Contrast:')
|
|
183
|
+
)
|
|
184
|
+
console.log(
|
|
185
|
+
`${config.host}/Contrast/static/ng/index.html#/${config.organizationId}/applications/${config.applicationId}/libs/dependency-tree`
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function getReportTable() {
|
|
191
|
+
return new Table({
|
|
192
|
+
chars: {
|
|
193
|
+
top: '',
|
|
194
|
+
'top-mid': '',
|
|
195
|
+
'top-left': '',
|
|
196
|
+
'top-right': '',
|
|
197
|
+
bottom: '',
|
|
198
|
+
'bottom-mid': '',
|
|
199
|
+
'bottom-left': '',
|
|
200
|
+
'bottom-right': '',
|
|
201
|
+
left: '',
|
|
202
|
+
'left-mid': '',
|
|
203
|
+
mid: '',
|
|
204
|
+
'mid-mid': '',
|
|
205
|
+
right: '',
|
|
206
|
+
'right-mid': '',
|
|
207
|
+
middle: ' '
|
|
208
|
+
},
|
|
209
|
+
style: { 'padding-left': 0, 'padding-right': 0 },
|
|
210
|
+
colAligns: ['right'],
|
|
211
|
+
wordWrap: true,
|
|
212
|
+
colWidths: [12, 1, 100]
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
function buildHeader(
|
|
216
|
+
highestSeverity,
|
|
217
|
+
contrastHeaderNum,
|
|
218
|
+
libraryName,
|
|
219
|
+
version,
|
|
220
|
+
numOfCVEs
|
|
221
|
+
) {
|
|
222
|
+
const vulnerabilityPluralised =
|
|
223
|
+
numOfCVEs > 1 ? 'vulnerabilities' : 'vulnerability'
|
|
224
|
+
const formattedHeaderNum = buildFormattedHeaderNum(contrastHeaderNum)
|
|
225
|
+
|
|
226
|
+
const headerColour = chalk.hex(highestSeverity.colour)
|
|
227
|
+
const headerNumAndSeverity = headerColour(
|
|
228
|
+
`${formattedHeaderNum} - [${highestSeverity.severity}]`
|
|
229
|
+
)
|
|
230
|
+
const libraryNameAndVersion = headerColour.bold(`${libraryName}-${version}`)
|
|
231
|
+
const vulnMessage = `${headerNumAndSeverity} ${libraryNameAndVersion}`
|
|
232
|
+
|
|
233
|
+
const introducesMessage = `introduces ${numOfCVEs} ${vulnerabilityPluralised}`
|
|
234
|
+
|
|
235
|
+
return new ReportOutputHeaderModel(vulnMessage, introducesMessage)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function buildBody(cveArray, advice) {
|
|
239
|
+
let assignPriorityToVulns = cveArray.map(result => findCVESeverity(result))
|
|
240
|
+
|
|
241
|
+
const issueMessage = getIssueRow(assignPriorityToVulns)
|
|
242
|
+
|
|
243
|
+
//todo different advice based on remediationGuidance being available or now
|
|
244
|
+
// console.log(advice)
|
|
245
|
+
|
|
246
|
+
const minOrMax = advice.maximum ? advice.maximum : advice.minimum
|
|
247
|
+
const displayAdvice = minOrMax
|
|
248
|
+
? `Change to version ${chalk.bold(minOrMax)}`
|
|
249
|
+
: 'No recommendation is available according to our data. Upgrade to the latest stable is the best advice we can give.'
|
|
250
|
+
|
|
251
|
+
const adviceMessage = [chalk.bold('Advice'), ':', displayAdvice]
|
|
252
|
+
|
|
253
|
+
return new ReportOutputBodyModel(issueMessage, adviceMessage)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function getIssueRow(cveArray) {
|
|
257
|
+
orderByHighestPriority(cveArray)
|
|
258
|
+
const cveMessagesList = getIssueCveMsgList(cveArray)
|
|
259
|
+
const cveNumbers = getSeverityCounts(cveArray)
|
|
260
|
+
const numAndSeverityTypeDesc = getNumOfAndSeverityType(cveNumbers)
|
|
261
|
+
return [
|
|
262
|
+
chalk.bold('Issue'),
|
|
263
|
+
':',
|
|
264
|
+
`${numAndSeverityTypeDesc} ${cveMessagesList.join(', ')}`
|
|
265
|
+
]
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function gatherRemediationAdvice(guidance, libraryName, libraryVersion) {
|
|
269
|
+
const guidanceModel = new ReportGuidanceModel()
|
|
270
|
+
|
|
271
|
+
const data = guidance[libraryName + '@' + libraryVersion]
|
|
272
|
+
|
|
273
|
+
if (data) {
|
|
274
|
+
guidanceModel.minimum = data.minUpgradeVersion
|
|
275
|
+
guidanceModel.maximum = data.maxUpgradeVersion
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return guidanceModel
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function buildFormattedHeaderNum(contrastHeaderNum) {
|
|
282
|
+
return `CONTRAST-${contrastHeaderNum.toString().padStart(3, '0')}`
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function getNumOfAndSeverityType(cveNumbers) {
|
|
286
|
+
const { critical, high, medium, low, note } = cveNumbers
|
|
287
|
+
|
|
288
|
+
const criticalMsg = critical > 0 ? `${critical} Critical | ` : ''
|
|
289
|
+
const highMsg = high > 0 ? `${high} High | ` : ''
|
|
290
|
+
const mediumMsg = medium > 0 ? `${medium} Medium | ` : ''
|
|
291
|
+
const lowMsg = low > 0 ? `${low} Low | ` : ''
|
|
292
|
+
const noteMsg = note > 0 ? `${note} Note` : ''
|
|
293
|
+
|
|
294
|
+
//removes/trims whitespace to single spaces
|
|
295
|
+
return `${criticalMsg} ${highMsg} ${mediumMsg} ${lowMsg} ${noteMsg}`
|
|
296
|
+
.replace(/\s+/g, ' ')
|
|
297
|
+
.trim()
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const buildFooter = reportModelStructure => {
|
|
301
|
+
const { critical, high, medium, low, note } =
|
|
302
|
+
countVulnerableLibrariesBySeverity(reportModelStructure)
|
|
303
|
+
|
|
304
|
+
const criticalMessage = chalk
|
|
305
|
+
.hex(CRITICAL_COLOUR)
|
|
306
|
+
.bold(`${critical} Critical`)
|
|
307
|
+
const highMessage = chalk.hex(HIGH_COLOUR).bold(`${high} High`)
|
|
308
|
+
const mediumMessage = chalk.hex(MEDIUM_COLOUR).bold(`${medium} Medium`)
|
|
309
|
+
const lowMessage = chalk.hex(LOW_COLOUR).bold(`${low} Low`)
|
|
310
|
+
const noteMessage = chalk.hex(NOTE_COLOUR).bold(`${note} Note`)
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
criticalMessage,
|
|
314
|
+
highMessage,
|
|
315
|
+
mediumMessage,
|
|
316
|
+
lowMessage,
|
|
317
|
+
noteMessage
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const getIssueCveMsgList = results => {
|
|
322
|
+
const cveMessages = []
|
|
323
|
+
|
|
324
|
+
results.forEach(reportSeverityModel => {
|
|
325
|
+
// @ts-ignore
|
|
326
|
+
const { colour, severity, name } = reportSeverityModel
|
|
327
|
+
|
|
328
|
+
const severityShorthand = chalk
|
|
329
|
+
.hex(colour)
|
|
330
|
+
.bold(`[${severity.charAt(0).toUpperCase()}]`)
|
|
331
|
+
|
|
332
|
+
const builtMessage = severityShorthand + name
|
|
333
|
+
cveMessages.push(builtMessage)
|
|
334
|
+
})
|
|
335
|
+
return cveMessages
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const getSeverityCounts = results => {
|
|
339
|
+
const acc = {
|
|
340
|
+
critical: 0,
|
|
341
|
+
high: 0,
|
|
342
|
+
medium: 0,
|
|
343
|
+
low: 0,
|
|
344
|
+
note: 0,
|
|
345
|
+
total: 0
|
|
346
|
+
}
|
|
347
|
+
if (results && results.length > 0) {
|
|
348
|
+
results.forEach(i => {
|
|
349
|
+
acc[i.severity.toLowerCase()] += 1
|
|
350
|
+
acc.total += 1
|
|
351
|
+
return acc
|
|
352
|
+
})
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return acc
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const printNoVulnFoundMsg = () => {
|
|
359
|
+
console.log(i18n.__('scanNoVulnerabilitiesFound'))
|
|
360
|
+
console.log(i18n.__('scanNoVulnerabilitiesFoundSecureCode'))
|
|
361
|
+
console.log(i18n.__('scanNoVulnerabilitiesFoundGoodWork'))
|
|
362
|
+
console.log(chalk.bold(`Found 0 vulnerabilities`))
|
|
363
|
+
console.log(
|
|
364
|
+
i18n.__(
|
|
365
|
+
'foundDetailedVulnerabilities',
|
|
366
|
+
String(0),
|
|
367
|
+
String(0),
|
|
368
|
+
String(0),
|
|
369
|
+
String(0),
|
|
370
|
+
String(0)
|
|
371
|
+
)
|
|
372
|
+
)
|
|
373
|
+
}
|
|
374
|
+
const printVulnInfo = projectOverview => {
|
|
375
|
+
const totalVulnerabilities = projectOverview.total
|
|
376
|
+
|
|
377
|
+
createSummaryMessageBottom(totalVulnerabilities)
|
|
378
|
+
const formattedValues = severityFormatted(projectOverview)
|
|
379
|
+
console.log(
|
|
380
|
+
i18n.__(
|
|
381
|
+
'foundDetailedVulnerabilities',
|
|
382
|
+
String(formattedValues.criticalFormatted),
|
|
383
|
+
String(formattedValues.highFormatted),
|
|
384
|
+
String(formattedValues.mediumFormatted),
|
|
385
|
+
String(formattedValues.lowFormatted),
|
|
386
|
+
String(formattedValues.noteFormatted)
|
|
387
|
+
)
|
|
388
|
+
)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const severityFormatted = projectOverview => {
|
|
392
|
+
const criticalFormatted = chalk
|
|
393
|
+
.hex(CRITICAL_COLOUR)
|
|
394
|
+
.bold(`${projectOverview.critical} Critical`)
|
|
395
|
+
const highFormatted = chalk
|
|
396
|
+
.hex(HIGH_COLOUR)
|
|
397
|
+
.bold(`${projectOverview.high} High`)
|
|
398
|
+
const mediumFormatted = chalk
|
|
399
|
+
.hex(MEDIUM_COLOUR)
|
|
400
|
+
.bold(`${projectOverview.medium} Medium`)
|
|
401
|
+
const lowFormatted = chalk.hex(LOW_COLOUR).bold(`${projectOverview.low} Low`)
|
|
402
|
+
const noteFormatted = chalk
|
|
403
|
+
.hex(NOTE_COLOUR)
|
|
404
|
+
.bold(`${projectOverview.note} Note`)
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
criticalFormatted,
|
|
408
|
+
highFormatted,
|
|
409
|
+
mediumFormatted,
|
|
410
|
+
lowFormatted,
|
|
411
|
+
noteFormatted
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
module.exports = {
|
|
416
|
+
createSummaryMessageTop,
|
|
417
|
+
getReport,
|
|
418
|
+
createSummaryMessageBottom,
|
|
419
|
+
printVulnerabilityResponse,
|
|
420
|
+
printFormattedOutput,
|
|
421
|
+
getReportTable,
|
|
422
|
+
buildHeader,
|
|
423
|
+
buildBody,
|
|
424
|
+
getIssueRow,
|
|
425
|
+
gatherRemediationAdvice,
|
|
426
|
+
buildFormattedHeaderNum,
|
|
427
|
+
getNumOfAndSeverityType,
|
|
428
|
+
getIssueCveMsgList,
|
|
429
|
+
getSeverityCounts,
|
|
430
|
+
printNoVulnFoundMsg,
|
|
431
|
+
printVulnInfo
|
|
432
|
+
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
export class ReportSeverityModel {
|
|
2
2
|
severity: string
|
|
3
3
|
priority: number
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
colour: string
|
|
5
|
+
name: string
|
|
6
6
|
|
|
7
7
|
constructor(
|
|
8
8
|
severity: string,
|
|
9
9
|
priority: number,
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
colour: string,
|
|
11
|
+
name: string
|
|
12
12
|
) {
|
|
13
13
|
this.severity = severity
|
|
14
14
|
this.priority = priority
|
|
15
|
-
this.
|
|
16
|
-
this.
|
|
15
|
+
this.colour = colour
|
|
16
|
+
this.name = name
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getReport,
|
|
3
|
+
printNoVulnFoundMsg,
|
|
3
4
|
printVulnerabilityResponse
|
|
4
5
|
} from './commonReportingFunctions'
|
|
5
6
|
import {
|
|
@@ -58,22 +59,7 @@ export function formatVulnerabilityOutput(
|
|
|
58
59
|
const numberOfVulnerableLibraries = vulnerableLibraries.length
|
|
59
60
|
|
|
60
61
|
if (numberOfVulnerableLibraries === 0) {
|
|
61
|
-
|
|
62
|
-
console.log(i18n.__('scanNoVulnerabilitiesFoundSecureCode'))
|
|
63
|
-
console.log(i18n.__('scanNoVulnerabilitiesFoundGoodWork'))
|
|
64
|
-
console.log(
|
|
65
|
-
chalk.bold(`Found ${numberOfVulnerableLibraries} vulnerabilities`)
|
|
66
|
-
)
|
|
67
|
-
console.log(
|
|
68
|
-
i18n.__(
|
|
69
|
-
'foundDetailedVulnerabilities',
|
|
70
|
-
String(0),
|
|
71
|
-
String(0),
|
|
72
|
-
String(0),
|
|
73
|
-
String(0),
|
|
74
|
-
String(0)
|
|
75
|
-
)
|
|
76
|
-
)
|
|
62
|
+
printNoVulnFoundMsg()
|
|
77
63
|
return [false, 0, [new SeverityCountModel()]]
|
|
78
64
|
} else {
|
|
79
65
|
let numberOfCves = 0
|