@contrast/contrast 1.0.12 → 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/audit/processAudit.js +10 -0
- package/dist/commands/scan/processScan.js +9 -0
- package/dist/commands/scan/sca/scaAnalysis.js +2 -0
- package/dist/common/HTTPClient.js +30 -2
- package/dist/common/errorHandling.js +1 -2
- package/dist/common/fail.js +7 -3
- package/dist/common/versionChecker.js +11 -5
- package/dist/constants/constants.js +1 -1
- package/dist/constants/locales.js +16 -8
- package/dist/constants.js +2 -2
- package/dist/index.js +5 -3
- package/dist/lambda/lambda.js +8 -1
- package/dist/scaAnalysis/common/auditReport.js +78 -0
- package/dist/scaAnalysis/common/scaServicesUpload.js +53 -0
- package/dist/scaAnalysis/javascript/index.js +4 -0
- package/dist/scaAnalysis/javascript/scaServiceParser.js +109 -0
- package/dist/scaAnalysis/ruby/analysis.js +106 -9
- package/dist/scaAnalysis/ruby/index.js +6 -1
- package/dist/scan/formatScanOutput.js +4 -29
- package/dist/scan/scanResults.js +1 -1
- package/dist/{audit/languageAnalysisEngine/util → utils}/capabilities.js +0 -0
- package/dist/{audit/languageAnalysisEngine/util → utils}/generalAPI.js +14 -5
- 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/audit/processAudit.ts +8 -0
- package/src/commands/scan/processScan.js +14 -0
- package/src/commands/scan/sca/scaAnalysis.js +9 -0
- package/src/common/HTTPClient.js +44 -2
- package/src/common/errorHandling.ts +1 -2
- package/src/common/fail.js +7 -3
- package/src/common/versionChecker.ts +16 -6
- package/src/constants/constants.js +1 -1
- package/src/constants/locales.js +17 -9
- package/src/constants.js +2 -2
- package/src/index.ts +5 -8
- package/src/lambda/lambda.ts +13 -1
- package/src/lambda/lambdaUtils.ts +1 -1
- package/src/scaAnalysis/common/auditReport.js +108 -0
- package/src/scaAnalysis/common/scaServicesUpload.js +56 -0
- package/src/scaAnalysis/javascript/index.js +4 -0
- package/src/scaAnalysis/javascript/scaServiceParser.js +145 -0
- package/src/scaAnalysis/ruby/analysis.js +137 -9
- package/src/scaAnalysis/ruby/index.js +6 -1
- package/src/scan/formatScanOutput.ts +5 -42
- package/src/scan/scanResults.js +1 -1
- package/src/{audit/languageAnalysisEngine/util → utils}/capabilities.js +0 -0
- package/src/{audit/languageAnalysisEngine/util → utils}/generalAPI.js +16 -6
- package/src/audit/report/commonReportingFunctions.ts +0 -355
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
const i18n = require('i18n')
|
|
3
3
|
|
|
4
|
+
const getRubyDeps = (config, languageFiles) => {
|
|
5
|
+
try {
|
|
6
|
+
checkForCorrectFiles(languageFiles)
|
|
7
|
+
const parsedGem = readAndParseGemfile(config.file)
|
|
8
|
+
const parsedLock = readAndParseGemLockFile(config.file)
|
|
9
|
+
if (config.experimental) {
|
|
10
|
+
const rubyArray = removeRedundantAndPopulateDefinedElements(
|
|
11
|
+
parsedLock.sources
|
|
12
|
+
)
|
|
13
|
+
let rubyTree = createRubyTree(rubyArray)
|
|
14
|
+
findChildrenDependencies(rubyTree)
|
|
15
|
+
processRootDependencies(parsedLock.dependencies, rubyTree)
|
|
16
|
+
return rubyTree
|
|
17
|
+
} else {
|
|
18
|
+
return { gemfilesDependanceies: parsedGem, gemfileLock: parsedLock }
|
|
19
|
+
}
|
|
20
|
+
} catch (err) {
|
|
21
|
+
throw err
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
4
25
|
const readAndParseGemfile = file => {
|
|
5
26
|
const gemFile = fs.readFileSync(file + '/Gemfile', 'utf8')
|
|
6
27
|
const rubyArray = gemFile.split('\n')
|
|
@@ -242,16 +263,119 @@ const buildSourceDependencyWithVersion = (
|
|
|
242
263
|
return dependencies
|
|
243
264
|
}
|
|
244
265
|
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const parsedGem = readAndParseGemfile(config.file)
|
|
249
|
-
const parsedLock = readAndParseGemLockFile(config.file)
|
|
266
|
+
const processRootDependencies = (rootDependencies, rubyTree) => {
|
|
267
|
+
const getParentObjectByName = queryToken =>
|
|
268
|
+
Object.values(rubyTree).filter(({ name }) => name === queryToken)
|
|
250
269
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
270
|
+
for (let parent in rootDependencies) {
|
|
271
|
+
let parentObject = getParentObjectByName(parent)
|
|
272
|
+
|
|
273
|
+
// ignore root dependencies that don't have a resolved version
|
|
274
|
+
if (parentObject[0]) {
|
|
275
|
+
let gav =
|
|
276
|
+
parentObject[0].group +
|
|
277
|
+
'/' +
|
|
278
|
+
parentObject[0].name +
|
|
279
|
+
'@' +
|
|
280
|
+
parentObject[0].version
|
|
281
|
+
|
|
282
|
+
rubyTree[gav] = parentObject[0]
|
|
283
|
+
rubyTree[gav].directDependency = true
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return rubyTree
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const createRubyTree = rubyArray => {
|
|
290
|
+
let rubyTree = {}
|
|
291
|
+
for (let x in rubyArray) {
|
|
292
|
+
let version = rubyArray[x].resolved
|
|
293
|
+
|
|
294
|
+
let gav = rubyArray[x].group + '/' + rubyArray[x].name + '@' + version
|
|
295
|
+
rubyTree[gav] = rubyArray[x]
|
|
296
|
+
rubyTree[gav].directDependency = false
|
|
297
|
+
rubyTree[gav].version = version
|
|
298
|
+
|
|
299
|
+
// add dependency array if none exists
|
|
300
|
+
if (!rubyTree[gav].dependencies) {
|
|
301
|
+
rubyTree[gav].dependencies = []
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
delete rubyTree[gav].resolved
|
|
305
|
+
}
|
|
306
|
+
return rubyTree
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const findChildrenDependencies = rubyTree => {
|
|
310
|
+
for (let dep in rubyTree) {
|
|
311
|
+
let unResolvedChildDepsKey = Object.keys(rubyTree[dep].dependencies)
|
|
312
|
+
rubyTree[dep].dependencies = resolveVersionOfChildDependencies(
|
|
313
|
+
unResolvedChildDepsKey,
|
|
314
|
+
rubyTree
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const resolveVersionOfChildDependencies = (
|
|
320
|
+
unResolvedChildDepsKey,
|
|
321
|
+
rubyObject
|
|
322
|
+
) => {
|
|
323
|
+
const getParentObjectByName = queryToken =>
|
|
324
|
+
Object.values(rubyObject).filter(({ name }) => name === queryToken)
|
|
325
|
+
let resolvedChildrenDependencies = []
|
|
326
|
+
for (let childDep in unResolvedChildDepsKey) {
|
|
327
|
+
let childDependencyName = unResolvedChildDepsKey[childDep]
|
|
328
|
+
let parent = getParentObjectByName(childDependencyName)
|
|
329
|
+
resolvedChildrenDependencies.push(
|
|
330
|
+
'null/' + childDependencyName + '@' + parent[0].version
|
|
331
|
+
)
|
|
254
332
|
}
|
|
333
|
+
return resolvedChildrenDependencies
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const removeRedundantAndPopulateDefinedElements = deps => {
|
|
337
|
+
return deps.map(element => {
|
|
338
|
+
if (element.sourceType === 'GIT') {
|
|
339
|
+
delete element.sourceType
|
|
340
|
+
delete element.remote
|
|
341
|
+
delete element.platform
|
|
342
|
+
|
|
343
|
+
element.group = null
|
|
344
|
+
element.isProduction = true
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (element.sourceType === 'GEM') {
|
|
348
|
+
element.group = null
|
|
349
|
+
element.isProduction = true
|
|
350
|
+
|
|
351
|
+
delete element.sourceType
|
|
352
|
+
delete element.remote
|
|
353
|
+
delete element.platform
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (element.sourceType === 'PATH') {
|
|
357
|
+
element.group = null
|
|
358
|
+
element.isProduction = true
|
|
359
|
+
|
|
360
|
+
delete element.platform
|
|
361
|
+
delete element.sourceType
|
|
362
|
+
delete element.remote
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (element.sourceType === 'BUNDLED WITH') {
|
|
366
|
+
element.group = null
|
|
367
|
+
element.isProduction = true
|
|
368
|
+
|
|
369
|
+
delete element.sourceType
|
|
370
|
+
delete element.remote
|
|
371
|
+
delete element.branch
|
|
372
|
+
delete element.revision
|
|
373
|
+
delete element.depthLevel
|
|
374
|
+
delete element.specs
|
|
375
|
+
delete element.platform
|
|
376
|
+
}
|
|
377
|
+
return element
|
|
378
|
+
})
|
|
255
379
|
}
|
|
256
380
|
|
|
257
381
|
const checkForCorrectFiles = languageFiles => {
|
|
@@ -281,5 +405,9 @@ module.exports = {
|
|
|
281
405
|
getPatchLevel,
|
|
282
406
|
formatSourceArr,
|
|
283
407
|
getSourceArray,
|
|
284
|
-
checkForCorrectFiles
|
|
408
|
+
checkForCorrectFiles,
|
|
409
|
+
removeRedundantAndPopulateDefinedElements,
|
|
410
|
+
createRubyTree,
|
|
411
|
+
findChildrenDependencies,
|
|
412
|
+
processRootDependencies
|
|
285
413
|
}
|
|
@@ -3,7 +3,12 @@ const { createRubyTSMessage } = require('../common/formatMessage')
|
|
|
3
3
|
|
|
4
4
|
const rubyAnalysis = (config, languageFiles) => {
|
|
5
5
|
const rubyDeps = analysis.getRubyDeps(config, languageFiles.RUBY)
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
if (config.experimental) {
|
|
8
|
+
return rubyDeps
|
|
9
|
+
} else {
|
|
10
|
+
return createRubyTSMessage(rubyDeps)
|
|
11
|
+
}
|
|
7
12
|
}
|
|
8
13
|
|
|
9
14
|
module.exports = {
|
|
@@ -15,11 +15,15 @@ import {
|
|
|
15
15
|
MEDIUM_COLOUR,
|
|
16
16
|
NOTE_COLOUR
|
|
17
17
|
} from '../constants/constants'
|
|
18
|
+
import {
|
|
19
|
+
getSeverityCounts,
|
|
20
|
+
printVulnInfo
|
|
21
|
+
} from '../audit/report/commonReportingFunctions'
|
|
18
22
|
|
|
19
23
|
export function formatScanOutput(scanResults: ScanResultsModel) {
|
|
20
24
|
const { scanResultsInstances } = scanResults
|
|
21
25
|
|
|
22
|
-
const projectOverview =
|
|
26
|
+
const projectOverview = getSeverityCounts(scanResultsInstances.content)
|
|
23
27
|
if (scanResultsInstances.content.length === 0) {
|
|
24
28
|
console.log(i18n.__('scanNoVulnerabilitiesFound'))
|
|
25
29
|
console.log(i18n.__('scanNoVulnerabilitiesFoundSecureCode'))
|
|
@@ -108,47 +112,6 @@ export function formatScanOutput(scanResults: ScanResultsModel) {
|
|
|
108
112
|
return projectOverview
|
|
109
113
|
}
|
|
110
114
|
|
|
111
|
-
function printVulnInfo(projectOverview: any) {
|
|
112
|
-
const totalVulnerabilities = projectOverview.total
|
|
113
|
-
|
|
114
|
-
const vulMessage =
|
|
115
|
-
totalVulnerabilities === 1 ? `vulnerability` : `vulnerabilities`
|
|
116
|
-
console.log(chalk.bold(`Found ${totalVulnerabilities} ${vulMessage}`))
|
|
117
|
-
console.log(
|
|
118
|
-
i18n.__(
|
|
119
|
-
'foundDetailedVulnerabilities',
|
|
120
|
-
String(projectOverview.critical),
|
|
121
|
-
String(projectOverview.high),
|
|
122
|
-
String(projectOverview.medium),
|
|
123
|
-
String(projectOverview.low),
|
|
124
|
-
String(projectOverview.note)
|
|
125
|
-
)
|
|
126
|
-
)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function getProjectOverview(scanResultsInstances: ScanResultsInstances) {
|
|
130
|
-
const acc: any = {
|
|
131
|
-
critical: 0,
|
|
132
|
-
high: 0,
|
|
133
|
-
medium: 0,
|
|
134
|
-
low: 0,
|
|
135
|
-
note: 0,
|
|
136
|
-
total: 0
|
|
137
|
-
}
|
|
138
|
-
if (
|
|
139
|
-
scanResultsInstances?.content &&
|
|
140
|
-
scanResultsInstances.content.length > 0
|
|
141
|
-
) {
|
|
142
|
-
scanResultsInstances.content.forEach((i: ResultContent) => {
|
|
143
|
-
acc[i.severity.toLowerCase()] += 1
|
|
144
|
-
acc.total += 1
|
|
145
|
-
return acc
|
|
146
|
-
})
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return acc
|
|
150
|
-
}
|
|
151
|
-
|
|
152
115
|
export function formatLinks(objName: string, entry: any[]) {
|
|
153
116
|
const line = chalk.bold(objName + ' : ')
|
|
154
117
|
if (entry.length === 1) {
|
package/src/scan/scanResults.js
CHANGED
|
@@ -94,7 +94,7 @@ const returnScanResults = async (
|
|
|
94
94
|
)
|
|
95
95
|
|
|
96
96
|
const isCI = process.env.CONTRAST_CODESEC_CI
|
|
97
|
-
? JSON.parse(process.env.CONTRAST_CODESEC_CI)
|
|
97
|
+
? JSON.parse(process.env.CONTRAST_CODESEC_CI.toLowerCase())
|
|
98
98
|
: false
|
|
99
99
|
if (!isCI) {
|
|
100
100
|
const retry = await retryScanPrompt()
|
|
File without changes
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
const { featuresTeamServer } = require('./capabilities')
|
|
2
2
|
const semver = require('semver')
|
|
3
|
-
const { handleResponseErrors } = require('
|
|
4
|
-
const
|
|
3
|
+
const { handleResponseErrors } = require('../common/errorHandling')
|
|
4
|
+
const commonApi = require('./commonApi')
|
|
5
|
+
const { isNil } = require('lodash')
|
|
5
6
|
|
|
6
7
|
const getGlobalProperties = async config => {
|
|
7
|
-
const client = getHttpClient(config)
|
|
8
|
-
|
|
8
|
+
const client = commonApi.getHttpClient(config)
|
|
9
9
|
return client
|
|
10
|
-
.getGlobalProperties(config)
|
|
10
|
+
.getGlobalProperties(config.host)
|
|
11
11
|
.then(res => {
|
|
12
12
|
if (res.statusCode === 200) {
|
|
13
13
|
return res.body
|
|
@@ -20,6 +20,15 @@ const getGlobalProperties = async config => {
|
|
|
20
20
|
})
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
const getMode = async config => {
|
|
24
|
+
const features = await getGlobalProperties(config)
|
|
25
|
+
|
|
26
|
+
if (!isNil(features?.mode)) {
|
|
27
|
+
return features.mode
|
|
28
|
+
}
|
|
29
|
+
return ''
|
|
30
|
+
}
|
|
31
|
+
|
|
23
32
|
const getFeatures = version => {
|
|
24
33
|
const featuresEnabled = []
|
|
25
34
|
|
|
@@ -39,5 +48,6 @@ const isFeatureEnabled = (features, featureName) => {
|
|
|
39
48
|
module.exports = {
|
|
40
49
|
getGlobalProperties,
|
|
41
50
|
getFeatures,
|
|
42
|
-
isFeatureEnabled
|
|
51
|
+
isFeatureEnabled,
|
|
52
|
+
getMode
|
|
43
53
|
}
|
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
import { getHttpClient, handleResponseErrors } from '../../utils/commonApi'
|
|
2
|
-
import {
|
|
3
|
-
ReportCompositeKey,
|
|
4
|
-
ReportList,
|
|
5
|
-
ReportModelStructure
|
|
6
|
-
} from './models/reportListModel'
|
|
7
|
-
import { ReportSeverityModel } from './models/reportSeverityModel'
|
|
8
|
-
import { orderBy } from 'lodash'
|
|
9
|
-
import chalk from 'chalk'
|
|
10
|
-
import { ReportCVEModel, ReportLibraryModel } from './models/reportLibraryModel'
|
|
11
|
-
import {
|
|
12
|
-
countVulnerableLibrariesBySeverity,
|
|
13
|
-
findCVESeveritiesAndOrderByHighestPriority,
|
|
14
|
-
findHighestSeverityCVE,
|
|
15
|
-
findNameAndVersion,
|
|
16
|
-
severityCountAllCVEs
|
|
17
|
-
} from './utils/reportUtils'
|
|
18
|
-
import { SeverityCountModel } from './models/severityCountModel'
|
|
19
|
-
import {
|
|
20
|
-
ReportOutputBodyModel,
|
|
21
|
-
ReportOutputHeaderModel,
|
|
22
|
-
ReportOutputModel
|
|
23
|
-
} from './models/reportOutputModel'
|
|
24
|
-
import {
|
|
25
|
-
CE_URL,
|
|
26
|
-
CRITICAL_COLOUR,
|
|
27
|
-
HIGH_COLOUR,
|
|
28
|
-
LOW_COLOUR,
|
|
29
|
-
MEDIUM_COLOUR,
|
|
30
|
-
NOTE_COLOUR
|
|
31
|
-
} from '../../constants/constants'
|
|
32
|
-
import Table from 'cli-table3'
|
|
33
|
-
import { ReportGuidanceModel } from './models/reportGuidanceModel'
|
|
34
|
-
|
|
35
|
-
export const createSummaryMessageTop = (
|
|
36
|
-
numberOfVulnerableLibraries: number,
|
|
37
|
-
numberOfCves: number
|
|
38
|
-
) => {
|
|
39
|
-
numberOfVulnerableLibraries === 1
|
|
40
|
-
? console.log(`Found 1 vulnerable library containing ${numberOfCves} CVE`)
|
|
41
|
-
: console.log(
|
|
42
|
-
`Found ${numberOfVulnerableLibraries} vulnerable libraries containing ${numberOfCves} CVEs`
|
|
43
|
-
)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export const createSummaryMessageBottom = (
|
|
47
|
-
numberOfVulnerableLibraries: number
|
|
48
|
-
) => {
|
|
49
|
-
numberOfVulnerableLibraries === 1
|
|
50
|
-
? console.log(`Found 1 vulnerable library`)
|
|
51
|
-
: console.log(`Found ${numberOfVulnerableLibraries} vulnerable libraries`)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export const getReport = async (config: any, reportId: string) => {
|
|
55
|
-
const client = getHttpClient(config)
|
|
56
|
-
return client
|
|
57
|
-
.getReportById(config, reportId)
|
|
58
|
-
.then((res: { statusCode: number; body: any }) => {
|
|
59
|
-
if (res.statusCode === 200) {
|
|
60
|
-
return res.body
|
|
61
|
-
} else {
|
|
62
|
-
console.log(JSON.stringify(res))
|
|
63
|
-
handleResponseErrors(res, 'report')
|
|
64
|
-
}
|
|
65
|
-
})
|
|
66
|
-
.catch((err: any) => {
|
|
67
|
-
console.log(err)
|
|
68
|
-
})
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export const printVulnerabilityResponse = (
|
|
72
|
-
config: any,
|
|
73
|
-
vulnerableLibraries: ReportLibraryModel[],
|
|
74
|
-
numberOfVulnerableLibraries: number,
|
|
75
|
-
numberOfCves: number,
|
|
76
|
-
guidance: any
|
|
77
|
-
) => {
|
|
78
|
-
let hasSomeVulnerabilitiesReported = false
|
|
79
|
-
printFormattedOutput(
|
|
80
|
-
config,
|
|
81
|
-
vulnerableLibraries,
|
|
82
|
-
numberOfVulnerableLibraries,
|
|
83
|
-
numberOfCves,
|
|
84
|
-
guidance
|
|
85
|
-
)
|
|
86
|
-
if (Object.keys(vulnerableLibraries).length > 0) {
|
|
87
|
-
hasSomeVulnerabilitiesReported = true
|
|
88
|
-
}
|
|
89
|
-
return hasSomeVulnerabilitiesReported
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export const printFormattedOutput = (
|
|
93
|
-
config: any,
|
|
94
|
-
libraries: ReportLibraryModel[],
|
|
95
|
-
numberOfVulnerableLibraries: number,
|
|
96
|
-
numberOfCves: number,
|
|
97
|
-
guidance: any
|
|
98
|
-
) => {
|
|
99
|
-
createSummaryMessageTop(numberOfVulnerableLibraries, numberOfCves)
|
|
100
|
-
console.log()
|
|
101
|
-
const report = new ReportList()
|
|
102
|
-
|
|
103
|
-
for (const library of libraries) {
|
|
104
|
-
const { name, version } = findNameAndVersion(library, config)
|
|
105
|
-
|
|
106
|
-
const newOutputModel = new ReportModelStructure(
|
|
107
|
-
new ReportCompositeKey(
|
|
108
|
-
name,
|
|
109
|
-
version,
|
|
110
|
-
findHighestSeverityCVE(library.cveArray) as ReportSeverityModel,
|
|
111
|
-
severityCountAllCVEs(
|
|
112
|
-
library.cveArray,
|
|
113
|
-
new SeverityCountModel()
|
|
114
|
-
).getTotal
|
|
115
|
-
),
|
|
116
|
-
library.cveArray
|
|
117
|
-
)
|
|
118
|
-
report.reportOutputList.push(newOutputModel)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const outputOrderedByLowestSeverityAndLowestNumOfCvesFirst = orderBy(
|
|
122
|
-
report.reportOutputList,
|
|
123
|
-
[
|
|
124
|
-
(reportListItem: ReportModelStructure) => {
|
|
125
|
-
return reportListItem.compositeKey.highestSeverity.priority
|
|
126
|
-
},
|
|
127
|
-
(reportListItem: ReportModelStructure) => {
|
|
128
|
-
return reportListItem.compositeKey.numberOfSeverities
|
|
129
|
-
}
|
|
130
|
-
],
|
|
131
|
-
['asc', 'desc']
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
let contrastHeaderNumCounter = 0
|
|
135
|
-
for (const reportModel of outputOrderedByLowestSeverityAndLowestNumOfCvesFirst) {
|
|
136
|
-
contrastHeaderNumCounter++
|
|
137
|
-
const { libraryName, libraryVersion, highestSeverity } =
|
|
138
|
-
reportModel.compositeKey
|
|
139
|
-
const numOfCVEs = reportModel.cveArray.length
|
|
140
|
-
|
|
141
|
-
const table = new Table({
|
|
142
|
-
chars: {
|
|
143
|
-
top: '',
|
|
144
|
-
'top-mid': '',
|
|
145
|
-
'top-left': '',
|
|
146
|
-
'top-right': '',
|
|
147
|
-
bottom: '',
|
|
148
|
-
'bottom-mid': '',
|
|
149
|
-
'bottom-left': '',
|
|
150
|
-
'bottom-right': '',
|
|
151
|
-
left: '',
|
|
152
|
-
'left-mid': '',
|
|
153
|
-
mid: '',
|
|
154
|
-
'mid-mid': '',
|
|
155
|
-
right: '',
|
|
156
|
-
'right-mid': '',
|
|
157
|
-
middle: ' '
|
|
158
|
-
},
|
|
159
|
-
style: { 'padding-left': 0, 'padding-right': 0 },
|
|
160
|
-
colAligns: ['right'],
|
|
161
|
-
wordWrap: true,
|
|
162
|
-
colWidths: [12, 1, 100]
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
const header = buildHeader(
|
|
166
|
-
highestSeverity,
|
|
167
|
-
contrastHeaderNumCounter,
|
|
168
|
-
libraryName,
|
|
169
|
-
libraryVersion,
|
|
170
|
-
numOfCVEs
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
const advice = gatherRemediationAdvice(
|
|
174
|
-
guidance,
|
|
175
|
-
libraryName,
|
|
176
|
-
libraryVersion
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
const body = buildBody(reportModel.cveArray, advice)
|
|
180
|
-
|
|
181
|
-
const reportOutputModel = new ReportOutputModel(header, body)
|
|
182
|
-
|
|
183
|
-
table.push(
|
|
184
|
-
reportOutputModel.body.issueMessage,
|
|
185
|
-
reportOutputModel.body.adviceMessage
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
console.log(
|
|
189
|
-
reportOutputModel.header.vulnMessage,
|
|
190
|
-
reportOutputModel.header.introducesMessage
|
|
191
|
-
)
|
|
192
|
-
console.log(table.toString() + '\n')
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
createSummaryMessageBottom(numberOfVulnerableLibraries)
|
|
196
|
-
const {
|
|
197
|
-
criticalMessage,
|
|
198
|
-
highMessage,
|
|
199
|
-
mediumMessage,
|
|
200
|
-
lowMessage,
|
|
201
|
-
noteMessage
|
|
202
|
-
} = buildFooter(outputOrderedByLowestSeverityAndLowestNumOfCvesFirst)
|
|
203
|
-
console.log(
|
|
204
|
-
`${criticalMessage} | ${highMessage} | ${mediumMessage} | ${lowMessage} | ${noteMessage}`
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
if (config.host !== CE_URL) {
|
|
208
|
-
console.log(
|
|
209
|
-
'\n' + chalk.bold('View your full dependency tree in Contrast:')
|
|
210
|
-
)
|
|
211
|
-
console.log(
|
|
212
|
-
`${config.host}/Contrast/static/ng/index.html#/${config.organizationId}/applications/${config.applicationId}/libs/dependency-tree`
|
|
213
|
-
)
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
export function buildHeader(
|
|
218
|
-
highestSeverity: ReportSeverityModel,
|
|
219
|
-
contrastHeaderNum: number,
|
|
220
|
-
libraryName: string,
|
|
221
|
-
version: string,
|
|
222
|
-
numOfCVEs: number
|
|
223
|
-
) {
|
|
224
|
-
const vulnerabilityPluralised =
|
|
225
|
-
numOfCVEs > 1 ? 'vulnerabilities' : 'vulnerability'
|
|
226
|
-
const formattedHeaderNum = buildFormattedHeaderNum(contrastHeaderNum)
|
|
227
|
-
|
|
228
|
-
const headerColour = chalk.hex(highestSeverity.outputColour)
|
|
229
|
-
const headerNumAndSeverity = headerColour(
|
|
230
|
-
`${formattedHeaderNum} - [${highestSeverity.severity}]`
|
|
231
|
-
)
|
|
232
|
-
const libraryNameAndVersion = headerColour.bold(`${libraryName}-${version}`)
|
|
233
|
-
const vulnMessage = `${headerNumAndSeverity} ${libraryNameAndVersion}`
|
|
234
|
-
|
|
235
|
-
const introducesMessage = `introduces ${numOfCVEs} ${vulnerabilityPluralised}`
|
|
236
|
-
|
|
237
|
-
return new ReportOutputHeaderModel(vulnMessage, introducesMessage)
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
export function buildBody(
|
|
241
|
-
cveArray: ReportCVEModel[],
|
|
242
|
-
advice: ReportGuidanceModel
|
|
243
|
-
) {
|
|
244
|
-
const cveMessages: string[] = []
|
|
245
|
-
|
|
246
|
-
findCVESeveritiesAndOrderByHighestPriority(cveArray).forEach(
|
|
247
|
-
reportSeverityModel => {
|
|
248
|
-
// @ts-ignore
|
|
249
|
-
const { outputColour, severity, cveName } = reportSeverityModel
|
|
250
|
-
|
|
251
|
-
const severityShorthand = chalk
|
|
252
|
-
.hex(outputColour)
|
|
253
|
-
.bold(`[${severity.charAt(0).toUpperCase()}]`)
|
|
254
|
-
|
|
255
|
-
const builtMessage = severityShorthand + cveName
|
|
256
|
-
cveMessages.push(builtMessage)
|
|
257
|
-
}
|
|
258
|
-
)
|
|
259
|
-
|
|
260
|
-
const numAndSeverityType = getNumOfAndSeverityType(cveArray)
|
|
261
|
-
|
|
262
|
-
const issueMessage = [
|
|
263
|
-
chalk.bold('Issue'),
|
|
264
|
-
':',
|
|
265
|
-
`${numAndSeverityType} ${cveMessages.join(', ')}`
|
|
266
|
-
]
|
|
267
|
-
|
|
268
|
-
//todo different advice based on remediationGuidance being available or now
|
|
269
|
-
// console.log(advice)
|
|
270
|
-
|
|
271
|
-
const minOrMax = advice.maximum ? advice.maximum : advice.minimum
|
|
272
|
-
const displayAdvice = minOrMax
|
|
273
|
-
? `Change to version ${chalk.bold(minOrMax)}`
|
|
274
|
-
: 'No recommendation is available according to our data. Upgrade to the latest stable is the best advice we can give.'
|
|
275
|
-
|
|
276
|
-
const adviceMessage = [chalk.bold('Advice'), ':', displayAdvice]
|
|
277
|
-
|
|
278
|
-
return new ReportOutputBodyModel(issueMessage, adviceMessage)
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
export function gatherRemediationAdvice(
|
|
282
|
-
guidance: any,
|
|
283
|
-
libraryName: string,
|
|
284
|
-
libraryVersion: string
|
|
285
|
-
) {
|
|
286
|
-
const guidanceModel = new ReportGuidanceModel()
|
|
287
|
-
|
|
288
|
-
const data = guidance[libraryName + '@' + libraryVersion]
|
|
289
|
-
|
|
290
|
-
if (data) {
|
|
291
|
-
guidanceModel.minimum = data.minUpgradeVersion
|
|
292
|
-
guidanceModel.maximum = data.maxUpgradeVersion
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return guidanceModel
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
export function buildFormattedHeaderNum(contrastHeaderNum: number) {
|
|
299
|
-
return `CONTRAST-${contrastHeaderNum.toString().padStart(3, '0')}`
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
export function getNumOfAndSeverityType(cveArray: ReportCVEModel[]) {
|
|
303
|
-
const { critical, high, medium, low, note } = severityCountAllCVEs(
|
|
304
|
-
cveArray,
|
|
305
|
-
new SeverityCountModel()
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
const criticalNumCheck = critical > 0
|
|
309
|
-
|
|
310
|
-
const highNumCheck = high > 0
|
|
311
|
-
const highDivider = highNumCheck ? '|' : ''
|
|
312
|
-
|
|
313
|
-
const mediumNumCheck = medium > 0
|
|
314
|
-
const mediumDivider = mediumNumCheck ? '|' : ''
|
|
315
|
-
|
|
316
|
-
const lowNumCheck = low > 0
|
|
317
|
-
const lowDivider = lowNumCheck ? '|' : ''
|
|
318
|
-
|
|
319
|
-
const noteNumCheck = low > 0
|
|
320
|
-
const noteDivider = noteNumCheck ? '|' : ''
|
|
321
|
-
|
|
322
|
-
const criticalMessage = criticalNumCheck
|
|
323
|
-
? `${critical} Critical ${highDivider}`
|
|
324
|
-
: ''
|
|
325
|
-
const highMessage = highNumCheck ? `${high} High ${mediumDivider}` : ''
|
|
326
|
-
const mediumMessage = mediumNumCheck ? `${medium} Medium ${lowDivider}` : ''
|
|
327
|
-
const lowMessage = lowNumCheck ? `${low} Low ${noteDivider}` : ''
|
|
328
|
-
const noteMessage = noteNumCheck ? `${note} Note` : ''
|
|
329
|
-
|
|
330
|
-
//removes/trims whitespace to single spaces
|
|
331
|
-
return `${criticalMessage} ${highMessage} ${mediumMessage} ${lowMessage} ${noteMessage}`
|
|
332
|
-
.replace(/\s+/g, ' ')
|
|
333
|
-
.trim()
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
const buildFooter = (reportModelStructure: ReportModelStructure[]) => {
|
|
337
|
-
const { critical, high, medium, low, note } =
|
|
338
|
-
countVulnerableLibrariesBySeverity(reportModelStructure)
|
|
339
|
-
|
|
340
|
-
const criticalMessage = chalk
|
|
341
|
-
.hex(CRITICAL_COLOUR)
|
|
342
|
-
.bold(`${critical} Critical`)
|
|
343
|
-
const highMessage = chalk.hex(HIGH_COLOUR).bold(`${high} High`)
|
|
344
|
-
const mediumMessage = chalk.hex(MEDIUM_COLOUR).bold(`${medium} Medium`)
|
|
345
|
-
const lowMessage = chalk.hex(LOW_COLOUR).bold(`${low} Low`)
|
|
346
|
-
const noteMessage = chalk.hex(NOTE_COLOUR).bold(`${note} Note`)
|
|
347
|
-
|
|
348
|
-
return {
|
|
349
|
-
criticalMessage,
|
|
350
|
-
highMessage,
|
|
351
|
-
mediumMessage,
|
|
352
|
-
lowMessage,
|
|
353
|
-
noteMessage
|
|
354
|
-
}
|
|
355
|
-
}
|