@contrast/contrast 1.0.4 → 1.0.7
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/.prettierignore +0 -3
- package/dist/audit/autodetection/autoDetectLanguage.js +32 -0
- package/dist/audit/catalogueApplication/catalogueApplication.js +2 -11
- package/dist/audit/javaAnalysisEngine/parseMavenProjectFileContents.js +4 -2
- package/dist/audit/languageAnalysisEngine/checkForMultipleIdentifiedLanguages.js +2 -1
- package/dist/audit/languageAnalysisEngine/checkForMultipleIdentifiedProjectFiles.js +2 -1
- package/dist/audit/languageAnalysisEngine/checkIdentifiedLanguageHasProjectFile.js +2 -1
- package/dist/audit/languageAnalysisEngine/languageAnalysisFactory.js +6 -2
- package/dist/audit/languageAnalysisEngine/reduceIdentifiedLanguages.js +39 -1
- package/dist/audit/languageAnalysisEngine/report/commonReportingFunctions.js +69 -30
- package/dist/audit/languageAnalysisEngine/report/models/reportOutputModel.js +24 -0
- package/dist/audit/languageAnalysisEngine/report/models/reportSeverityModel.js +3 -1
- package/dist/audit/languageAnalysisEngine/report/models/severityCountModel.js +13 -0
- package/dist/audit/languageAnalysisEngine/report/reportingFeature.js +2 -2
- package/dist/audit/languageAnalysisEngine/report/utils/reportUtils.js +56 -45
- package/dist/audit/languageAnalysisEngine/sendSnapshot.js +65 -17
- package/dist/commands/audit/auditConfig.js +8 -2
- package/dist/commands/audit/auditController.js +9 -3
- package/dist/commands/audit/processAudit.js +1 -1
- package/dist/commands/scan/processScan.js +7 -4
- package/dist/commands/scan/sca/scaAnalysis.js +60 -0
- package/dist/common/HTTPClient.js +50 -16
- package/dist/common/errorHandling.js +11 -16
- package/dist/common/versionChecker.js +1 -1
- package/dist/constants/constants.js +24 -2
- package/dist/constants/locales.js +31 -36
- package/dist/constants.js +20 -0
- package/dist/lambda/analytics.js +11 -0
- package/dist/lambda/lambda.js +35 -4
- package/dist/lambda/types.js +13 -0
- package/dist/scaAnalysis/common/formatMessage.js +35 -0
- package/dist/scaAnalysis/common/treeUpload.js +29 -0
- package/dist/scaAnalysis/go/goAnalysis.js +17 -0
- package/dist/scaAnalysis/go/goParseDeps.js +158 -0
- package/dist/scaAnalysis/go/goReadDepFile.js +23 -0
- package/dist/scaAnalysis/java/analysis.js +105 -0
- package/dist/scaAnalysis/java/index.js +18 -0
- package/dist/scaAnalysis/java/javaBuildDepsParser.js +339 -0
- package/dist/scaAnalysis/python/analysis.js +41 -0
- package/dist/scaAnalysis/python/index.js +10 -0
- package/dist/scaAnalysis/ruby/analysis.js +226 -0
- package/dist/scaAnalysis/ruby/index.js +10 -0
- package/dist/scan/autoDetection.js +50 -1
- package/dist/scan/fileUtils.js +80 -1
- package/dist/scan/formatScanOutput.js +213 -0
- package/dist/scan/help.js +3 -1
- package/dist/scan/models/groupedResultsModel.js +2 -1
- package/dist/scan/models/scanResultsModel.js +3 -1
- package/dist/scan/populateProjectIdAndProjectName.js +2 -1
- package/dist/scan/scan.js +6 -99
- package/dist/scan/scanConfig.js +6 -1
- package/dist/scan/scanController.js +26 -7
- package/dist/scan/scanResults.js +20 -20
- package/dist/utils/commonApi.js +4 -1
- package/dist/utils/oraWrapper.js +5 -1
- package/package.json +12 -7
- package/src/audit/autodetection/autoDetectLanguage.ts +40 -0
- package/src/audit/catalogueApplication/catalogueApplication.js +3 -16
- package/src/audit/javaAnalysisEngine/parseMavenProjectFileContents.js +11 -8
- package/src/audit/languageAnalysisEngine/checkForMultipleIdentifiedLanguages.js +2 -1
- package/src/audit/languageAnalysisEngine/checkForMultipleIdentifiedProjectFiles.js +2 -1
- package/src/audit/languageAnalysisEngine/checkIdentifiedLanguageHasProjectFile.js +2 -1
- package/src/audit/languageAnalysisEngine/languageAnalysisFactory.js +17 -5
- package/src/audit/languageAnalysisEngine/reduceIdentifiedLanguages.js +76 -3
- package/src/audit/languageAnalysisEngine/report/commonReportingFunctions.ts +122 -40
- package/src/audit/languageAnalysisEngine/report/models/reportLibraryModel.ts +3 -3
- package/src/audit/languageAnalysisEngine/report/models/reportListModel.ts +15 -11
- package/src/audit/languageAnalysisEngine/report/models/reportOutputModel.ts +29 -0
- package/src/audit/languageAnalysisEngine/report/models/reportSeverityModel.ts +12 -3
- package/src/audit/languageAnalysisEngine/report/models/severityCountModel.ts +16 -0
- package/src/audit/languageAnalysisEngine/report/reportingFeature.ts +3 -3
- package/src/audit/languageAnalysisEngine/report/utils/reportUtils.ts +87 -65
- package/src/audit/languageAnalysisEngine/sendSnapshot.js +78 -25
- package/src/commands/audit/auditConfig.ts +12 -3
- package/src/commands/audit/auditController.ts +9 -3
- package/src/commands/audit/processAudit.ts +4 -1
- package/src/commands/scan/processScan.js +10 -4
- package/src/commands/scan/sca/scaAnalysis.js +83 -0
- package/src/common/HTTPClient.js +65 -25
- package/src/common/errorHandling.ts +14 -22
- package/src/common/versionChecker.ts +1 -1
- package/src/constants/constants.js +24 -2
- package/src/constants/locales.js +33 -50
- package/src/constants.js +22 -0
- package/src/lambda/analytics.ts +9 -0
- package/src/lambda/arn.ts +2 -1
- package/src/lambda/lambda.ts +37 -17
- package/src/lambda/types.ts +35 -0
- package/src/lambda/utils.ts +2 -7
- package/src/scaAnalysis/common/formatMessage.js +38 -0
- package/src/scaAnalysis/common/treeUpload.js +30 -0
- package/src/scaAnalysis/go/goAnalysis.js +19 -0
- package/src/scaAnalysis/go/goParseDeps.js +203 -0
- package/src/scaAnalysis/go/goReadDepFile.js +32 -0
- package/src/scaAnalysis/java/analysis.js +142 -0
- package/src/scaAnalysis/java/index.js +21 -0
- package/src/scaAnalysis/java/javaBuildDepsParser.js +404 -0
- package/src/scaAnalysis/python/analysis.js +48 -0
- package/src/scaAnalysis/python/index.js +11 -0
- package/src/scaAnalysis/ruby/analysis.js +282 -0
- package/src/scaAnalysis/ruby/index.js +11 -0
- package/src/scan/autoDetection.js +58 -1
- package/src/scan/fileUtils.js +99 -1
- package/src/scan/formatScanOutput.ts +249 -0
- package/src/scan/help.js +3 -1
- package/src/scan/models/groupedResultsModel.ts +7 -5
- package/src/scan/models/resultContentModel.ts +2 -2
- package/src/scan/models/scanResultsModel.ts +5 -2
- package/src/scan/populateProjectIdAndProjectName.js +3 -1
- package/src/scan/scan.ts +8 -136
- package/src/scan/scanConfig.js +5 -1
- package/src/scan/scanController.js +30 -10
- package/src/scan/scanResults.js +31 -18
- package/src/utils/commonApi.js +4 -1
- package/src/utils/oraWrapper.js +6 -1
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ScanResultsInstances,
|
|
3
|
+
ScanResultsModel
|
|
4
|
+
} from './models/scanResultsModel'
|
|
5
|
+
import i18n from 'i18n'
|
|
6
|
+
import chalk from 'chalk'
|
|
7
|
+
import { ResultContent } from './models/resultContentModel'
|
|
8
|
+
import { GroupedResultsModel } from './models/groupedResultsModel'
|
|
9
|
+
import { sortBy } from 'lodash'
|
|
10
|
+
import Table from 'cli-table3'
|
|
11
|
+
import {
|
|
12
|
+
CRITICAL_COLOUR,
|
|
13
|
+
HIGH_COLOUR,
|
|
14
|
+
LOW_COLOUR,
|
|
15
|
+
MEDIUM_COLOUR,
|
|
16
|
+
NOTE_COLOUR
|
|
17
|
+
} from '../constants/constants'
|
|
18
|
+
|
|
19
|
+
export function formatScanOutput(scanResults: ScanResultsModel) {
|
|
20
|
+
const { scanResultsInstances } = scanResults
|
|
21
|
+
|
|
22
|
+
const projectOverview = getProjectOverview(scanResultsInstances)
|
|
23
|
+
if (scanResultsInstances.content.length === 0) {
|
|
24
|
+
console.log(i18n.__('scanNoVulnerabilitiesFound'))
|
|
25
|
+
console.log(i18n.__('scanNoVulnerabilitiesFoundSecureCode'))
|
|
26
|
+
console.log(i18n.__('scanNoVulnerabilitiesFoundGoodWork'))
|
|
27
|
+
} else {
|
|
28
|
+
const message =
|
|
29
|
+
projectOverview.critical || projectOverview.high
|
|
30
|
+
? 'Here are your top priorities to fix'
|
|
31
|
+
: "No major issues, here's what we found"
|
|
32
|
+
console.log(chalk.bold(message))
|
|
33
|
+
console.log()
|
|
34
|
+
|
|
35
|
+
let defaultView = getDefaultView(scanResultsInstances.content)
|
|
36
|
+
|
|
37
|
+
let count = defaultView.length
|
|
38
|
+
defaultView.forEach(entry => {
|
|
39
|
+
let table = new Table({
|
|
40
|
+
chars: {
|
|
41
|
+
top: '',
|
|
42
|
+
'top-mid': '',
|
|
43
|
+
'top-left': '',
|
|
44
|
+
'top-right': '',
|
|
45
|
+
bottom: '',
|
|
46
|
+
'bottom-mid': '',
|
|
47
|
+
'bottom-left': '',
|
|
48
|
+
'bottom-right': '',
|
|
49
|
+
left: '',
|
|
50
|
+
'left-mid': '',
|
|
51
|
+
mid: '',
|
|
52
|
+
'mid-mid': '',
|
|
53
|
+
right: '',
|
|
54
|
+
'right-mid': '',
|
|
55
|
+
middle: ' '
|
|
56
|
+
},
|
|
57
|
+
style: { 'padding-left': 0, 'padding-right': 0 },
|
|
58
|
+
colAligns: ['right'],
|
|
59
|
+
wordWrap: true,
|
|
60
|
+
colWidths: [12, 1, 100]
|
|
61
|
+
})
|
|
62
|
+
let learnRow: string[] = []
|
|
63
|
+
let adviceRow = []
|
|
64
|
+
const headerRow = [
|
|
65
|
+
chalk
|
|
66
|
+
.hex(entry.colour)
|
|
67
|
+
.bold(`CONTRAST-${count.toString().padStart(3, '0')}`),
|
|
68
|
+
chalk.hex(entry.colour).bold('-'),
|
|
69
|
+
chalk.hex(entry.colour).bold(`[${entry.severity}] ${entry.ruleId}`) +
|
|
70
|
+
entry.message
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
const codePath = entry.codePath?.replace(/^@/, '')
|
|
74
|
+
|
|
75
|
+
const codeRow = [
|
|
76
|
+
chalk.hex('#F6F5F5').bold(`Code`),
|
|
77
|
+
chalk.hex('#F6F5F5').bold(`:`),
|
|
78
|
+
chalk.hex('#F6F5F5').bold(`${codePath}`)
|
|
79
|
+
]
|
|
80
|
+
const issueRow = [chalk.bold(`Issue`), chalk.bold(`:`), `${entry.issue}`]
|
|
81
|
+
|
|
82
|
+
table.push(headerRow, codeRow, issueRow)
|
|
83
|
+
|
|
84
|
+
if (entry?.advice) {
|
|
85
|
+
adviceRow = [
|
|
86
|
+
chalk.bold('Advice'),
|
|
87
|
+
chalk.bold(`:`),
|
|
88
|
+
stripTags(entry.advice)
|
|
89
|
+
]
|
|
90
|
+
table.push(adviceRow)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (entry?.learn && entry?.learn.length > 0) {
|
|
94
|
+
learnRow = [
|
|
95
|
+
chalk.bold('Learn'),
|
|
96
|
+
chalk.bold(`:`),
|
|
97
|
+
chalk.hex('#97f7f7').bold.underline(entry.learn[0])
|
|
98
|
+
]
|
|
99
|
+
table.push(learnRow)
|
|
100
|
+
}
|
|
101
|
+
count--
|
|
102
|
+
console.log(table.toString())
|
|
103
|
+
console.log()
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
printVulnInfo(projectOverview)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function printVulnInfo(projectOverview: any) {
|
|
110
|
+
const totalVulnerabilities = projectOverview.total
|
|
111
|
+
|
|
112
|
+
const vulMessage =
|
|
113
|
+
totalVulnerabilities === 1 ? `vulnerability` : `vulnerabilities`
|
|
114
|
+
console.log(chalk.bold(`Found ${totalVulnerabilities} ${vulMessage}`))
|
|
115
|
+
console.log(
|
|
116
|
+
i18n.__(
|
|
117
|
+
'foundDetailedVulnerabilities',
|
|
118
|
+
String(projectOverview.critical),
|
|
119
|
+
String(projectOverview.high),
|
|
120
|
+
String(projectOverview.medium),
|
|
121
|
+
String(projectOverview.low),
|
|
122
|
+
String(projectOverview.note)
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function getProjectOverview(scanResultsInstances: ScanResultsInstances) {
|
|
128
|
+
const acc: any = {
|
|
129
|
+
critical: 0,
|
|
130
|
+
high: 0,
|
|
131
|
+
medium: 0,
|
|
132
|
+
low: 0,
|
|
133
|
+
note: 0,
|
|
134
|
+
total: 0
|
|
135
|
+
}
|
|
136
|
+
if (
|
|
137
|
+
scanResultsInstances?.content &&
|
|
138
|
+
scanResultsInstances.content.length > 0
|
|
139
|
+
) {
|
|
140
|
+
scanResultsInstances.content.forEach((i: ResultContent) => {
|
|
141
|
+
acc[i.severity.toLowerCase()] += 1
|
|
142
|
+
acc.total += 1
|
|
143
|
+
return acc
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return acc
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function formatLinks(objName: string, entry: any[]) {
|
|
151
|
+
const line = chalk.bold(objName + ' : ')
|
|
152
|
+
if (entry.length === 1) {
|
|
153
|
+
console.log(line + chalk.hex('#97DCF7').bold.underline(entry[0]))
|
|
154
|
+
} else {
|
|
155
|
+
console.log(line)
|
|
156
|
+
entry.forEach(link => {
|
|
157
|
+
console.log(chalk.hex('#97DCF7').bold.underline(link))
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function getDefaultView(content: ResultContent[]) {
|
|
163
|
+
const groupTypeResults = [] as GroupedResultsModel[]
|
|
164
|
+
|
|
165
|
+
content.forEach(resultEntry => {
|
|
166
|
+
const groupResultsObj = new GroupedResultsModel(resultEntry.ruleId)
|
|
167
|
+
groupResultsObj.severity = resultEntry.severity
|
|
168
|
+
groupResultsObj.ruleId = resultEntry.ruleId
|
|
169
|
+
groupResultsObj.issue = stripTags(resultEntry.issue)
|
|
170
|
+
groupResultsObj.advice = resultEntry.advice
|
|
171
|
+
groupResultsObj.learn = resultEntry.learn
|
|
172
|
+
groupResultsObj.message = resultEntry.message?.text
|
|
173
|
+
? editVulName(resultEntry.message.text) +
|
|
174
|
+
':' +
|
|
175
|
+
getSourceLineNumber(resultEntry)
|
|
176
|
+
: ''
|
|
177
|
+
groupResultsObj.codePath = getLocationsSyncInfo(resultEntry)
|
|
178
|
+
groupTypeResults.push(groupResultsObj)
|
|
179
|
+
assignBySeverity(resultEntry, groupResultsObj)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
return sortBy(groupTypeResults, ['priority']).reverse()
|
|
183
|
+
}
|
|
184
|
+
export function editVulName(message: string) {
|
|
185
|
+
return message.substring(message.indexOf(' in '))
|
|
186
|
+
}
|
|
187
|
+
export function getLocationsSyncInfo(resultEntry: ResultContent) {
|
|
188
|
+
const locationsMessage =
|
|
189
|
+
resultEntry.locations[0]?.physicalLocation?.artifactLocation?.uri || ''
|
|
190
|
+
const locationsLineNumber =
|
|
191
|
+
resultEntry.locations[0]?.physicalLocation?.region?.startLine || ''
|
|
192
|
+
|
|
193
|
+
if (!locationsLineNumber) {
|
|
194
|
+
return '@' + locationsMessage
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return '@' + locationsMessage + ':' + locationsLineNumber
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function getSourceLineNumber(resultEntry: ResultContent) {
|
|
201
|
+
const locationsLineNumber =
|
|
202
|
+
resultEntry.locations[0]?.physicalLocation?.region?.startLine || ''
|
|
203
|
+
let codeFlowLineNumber = getCodeFlowInfo(resultEntry)
|
|
204
|
+
|
|
205
|
+
return codeFlowLineNumber ? codeFlowLineNumber : locationsLineNumber
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function getCodeFlowInfo(resultEntry: ResultContent) {
|
|
209
|
+
let result: any
|
|
210
|
+
resultEntry.codeFlows[0]?.threadFlows.forEach((i: { locations: any[] }) => {
|
|
211
|
+
return (result = i.locations.find(
|
|
212
|
+
(locations: { importance: string }) =>
|
|
213
|
+
locations.importance === 'essential'
|
|
214
|
+
))
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
return result?.location?.physicalLocation?.region?.startLine
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function stripTags(oldString: string) {
|
|
221
|
+
return oldString.replace(/\n/g, ' ').replace(/\s+/g, ' ').trim()
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function assignBySeverity(
|
|
225
|
+
entry: ResultContent,
|
|
226
|
+
assignedObj: GroupedResultsModel
|
|
227
|
+
) {
|
|
228
|
+
if (entry.severity.toUpperCase() === 'CRITICAL') {
|
|
229
|
+
assignedObj.priority = 1
|
|
230
|
+
assignedObj.colour = CRITICAL_COLOUR
|
|
231
|
+
return assignedObj
|
|
232
|
+
} else if (entry.severity.toUpperCase() === 'HIGH') {
|
|
233
|
+
assignedObj.priority = 2
|
|
234
|
+
assignedObj.colour = HIGH_COLOUR
|
|
235
|
+
return assignedObj
|
|
236
|
+
} else if (entry.severity.toUpperCase() === 'MEDIUM') {
|
|
237
|
+
assignedObj.priority = 3
|
|
238
|
+
assignedObj.colour = MEDIUM_COLOUR
|
|
239
|
+
return assignedObj
|
|
240
|
+
} else if (entry.severity.toUpperCase() === 'LOW') {
|
|
241
|
+
assignedObj.priority = 4
|
|
242
|
+
assignedObj.colour = LOW_COLOUR
|
|
243
|
+
return assignedObj
|
|
244
|
+
} else if (entry.severity.toUpperCase() === 'NOTE') {
|
|
245
|
+
assignedObj.priority = 5
|
|
246
|
+
assignedObj.colour = NOTE_COLOUR
|
|
247
|
+
return assignedObj
|
|
248
|
+
}
|
|
249
|
+
}
|
package/src/scan/help.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
export class GroupedResultsModel {
|
|
2
2
|
ruleId: string
|
|
3
|
-
|
|
3
|
+
codePathSet: Set<string>
|
|
4
4
|
cwe?: string[]
|
|
5
|
-
owasp?: string[]
|
|
6
5
|
reference?: string[]
|
|
7
|
-
recommendation?: string
|
|
8
6
|
severity?: string
|
|
9
7
|
advice?: string
|
|
10
8
|
learn?: string[]
|
|
11
9
|
issue?: string
|
|
12
|
-
|
|
10
|
+
priority?: number
|
|
11
|
+
message?: string | undefined
|
|
12
|
+
colour: string
|
|
13
|
+
codePath?: string
|
|
13
14
|
|
|
14
15
|
constructor(ruleId: string) {
|
|
15
16
|
this.ruleId = ruleId
|
|
16
|
-
this.
|
|
17
|
+
this.colour = '#999999'
|
|
18
|
+
this.codePathSet = new Set<string>()
|
|
17
19
|
}
|
|
18
20
|
}
|
|
@@ -5,7 +5,7 @@ interface ArtifactLocation {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
interface Region {
|
|
8
|
-
startLine:
|
|
8
|
+
startLine: string
|
|
9
9
|
snippet: Snippet
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -52,7 +52,7 @@ export interface CodeFlow {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
export interface ResultContent {
|
|
55
|
-
message
|
|
55
|
+
message?: { text: string }
|
|
56
56
|
id: string
|
|
57
57
|
organizationId: string
|
|
58
58
|
projectId: string
|
|
@@ -4,11 +4,14 @@ export class ScanResultsModel {
|
|
|
4
4
|
projectOverview: ProjectOverview
|
|
5
5
|
scanDetail: ScanDetail
|
|
6
6
|
scanResultsInstances: ScanResultsInstances
|
|
7
|
+
newProject: boolean
|
|
7
8
|
|
|
8
9
|
constructor(scan: any) {
|
|
9
10
|
this.projectOverview = scan.projectOverview as ProjectOverview
|
|
10
11
|
this.scanDetail = scan.scanDetail as ScanDetail
|
|
11
|
-
this.scanResultsInstances =
|
|
12
|
+
this.scanResultsInstances =
|
|
13
|
+
scan.scanResultsInstances as ScanResultsInstances
|
|
14
|
+
this.newProject = scan.newProject
|
|
12
15
|
}
|
|
13
16
|
}
|
|
14
17
|
|
|
@@ -49,4 +52,4 @@ export interface ScanDetail {
|
|
|
49
52
|
|
|
50
53
|
export interface ScanResultsInstances {
|
|
51
54
|
content: ResultContent[]
|
|
52
|
-
}
|
|
55
|
+
}
|
|
@@ -8,9 +8,11 @@ const populateProjectId = async config => {
|
|
|
8
8
|
proj = await getExistingProjectIdByName(config, client).then(res => {
|
|
9
9
|
return res
|
|
10
10
|
})
|
|
11
|
+
|
|
12
|
+
return { projectId: proj, isNewProject: false }
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
return proj
|
|
15
|
+
return { projectId: proj, isNewProject: true }
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
const createProjectId = async (config, client) => {
|
package/src/scan/scan.ts
CHANGED
|
@@ -2,10 +2,6 @@ import commonApi from '../utils/commonApi.js'
|
|
|
2
2
|
import fileUtils from '../scan/fileUtils'
|
|
3
3
|
import i18n from 'i18n'
|
|
4
4
|
import oraWrapper from '../utils/oraWrapper'
|
|
5
|
-
import chalk from 'chalk'
|
|
6
|
-
import { ProjectOverview, ScanResultsModel } from './models/scanResultsModel'
|
|
7
|
-
import { Location, ResultContent } from './models/resultContentModel'
|
|
8
|
-
import { GroupedResultsModel } from './models/groupedResultsModel'
|
|
9
5
|
|
|
10
6
|
export const allowedFileTypes = ['.jar', '.war', '.js', '.zip', '.exe']
|
|
11
7
|
|
|
@@ -44,149 +40,25 @@ export const sendScan = async (config: any) => {
|
|
|
44
40
|
return res.body.id
|
|
45
41
|
} else {
|
|
46
42
|
if (config.debug) {
|
|
47
|
-
console.log(res.statusCode)
|
|
48
43
|
console.log(config)
|
|
44
|
+
oraWrapper.failSpinner(
|
|
45
|
+
startUploadSpinner,
|
|
46
|
+
i18n.__('uploadingScanFail')
|
|
47
|
+
)
|
|
48
|
+
console.log(i18n.__('genericServiceError', res.statusCode))
|
|
49
49
|
}
|
|
50
|
-
oraWrapper.failSpinner(
|
|
51
|
-
startUploadSpinner,
|
|
52
|
-
i18n.__('uploadingScanFail')
|
|
53
|
-
)
|
|
54
50
|
if (res.statusCode === 403) {
|
|
55
51
|
console.log(i18n.__('permissionsError'))
|
|
56
52
|
process.exit(1)
|
|
57
53
|
}
|
|
58
|
-
|
|
54
|
+
oraWrapper.stopSpinner(startUploadSpinner)
|
|
55
|
+
console.log('Contrast Scan Finished')
|
|
59
56
|
process.exit(1)
|
|
60
57
|
}
|
|
61
58
|
})
|
|
62
59
|
.catch(err => {
|
|
60
|
+
oraWrapper.stopSpinner(startUploadSpinner)
|
|
63
61
|
console.log(err)
|
|
64
62
|
})
|
|
65
63
|
}
|
|
66
64
|
}
|
|
67
|
-
|
|
68
|
-
export function formatScanOutput(scanResults: ScanResultsModel) {
|
|
69
|
-
const { projectOverview, scanResultsInstances } = scanResults
|
|
70
|
-
|
|
71
|
-
if (scanResultsInstances.content.length === 0) {
|
|
72
|
-
console.log(i18n.__('scanNoVulnerabilitiesFound'))
|
|
73
|
-
} else {
|
|
74
|
-
const message =
|
|
75
|
-
projectOverview.critical || projectOverview.high
|
|
76
|
-
? 'Here are your top priorities to fix'
|
|
77
|
-
: "No major issues, here's what we found"
|
|
78
|
-
console.log(chalk.bold(message))
|
|
79
|
-
console.log()
|
|
80
|
-
|
|
81
|
-
const groups = getGroups(scanResultsInstances.content)
|
|
82
|
-
|
|
83
|
-
groups.forEach(entry => {
|
|
84
|
-
console.log(
|
|
85
|
-
chalk.bold(
|
|
86
|
-
`[ ${entry.severity} ] | ${entry.ruleId} (${entry.lineInfoSet.size}) - ` +
|
|
87
|
-
`${entry.message}`
|
|
88
|
-
)
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
let count = 1
|
|
92
|
-
entry.lineInfoSet.forEach(lineInfo => {
|
|
93
|
-
console.log(`\t ${count}. ${lineInfo}`)
|
|
94
|
-
count++
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
if (entry?.issue) {
|
|
98
|
-
console.log(chalk.bold('Issue' + ': ') + entry.issue)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (entry?.advice) {
|
|
102
|
-
console.log(chalk.bold('Advice' + ': ') + entry.advice)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (entry?.learn && entry?.learn.length > 0) {
|
|
106
|
-
formatLinks('Learn', entry.learn)
|
|
107
|
-
}
|
|
108
|
-
console.log()
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
printVulnInfo(projectOverview)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function printVulnInfo(projectOverview: ProjectOverview) {
|
|
116
|
-
const totalVulnerabilities = getTotalVulns(projectOverview)
|
|
117
|
-
|
|
118
|
-
const vulMessage =
|
|
119
|
-
totalVulnerabilities === 1 ? `vulnerability` : `vulnerabilities`
|
|
120
|
-
console.log(chalk.bold(`Found ${totalVulnerabilities} ${vulMessage}`))
|
|
121
|
-
console.log(
|
|
122
|
-
i18n.__(
|
|
123
|
-
'foundDetailedVulnerabilities',
|
|
124
|
-
String(projectOverview.critical),
|
|
125
|
-
String(projectOverview.high),
|
|
126
|
-
String(projectOverview.medium),
|
|
127
|
-
String(projectOverview.low),
|
|
128
|
-
String(projectOverview.note)
|
|
129
|
-
)
|
|
130
|
-
)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function getTotalVulns(projectOverview: ProjectOverview) {
|
|
134
|
-
return (
|
|
135
|
-
projectOverview.critical +
|
|
136
|
-
projectOverview.high +
|
|
137
|
-
projectOverview.medium +
|
|
138
|
-
projectOverview.low +
|
|
139
|
-
projectOverview.note
|
|
140
|
-
)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export function formatLinks(objName: string, entry: any[]) {
|
|
144
|
-
console.log(chalk.bold(objName + ':'))
|
|
145
|
-
entry.forEach(link => {
|
|
146
|
-
console.log(link)
|
|
147
|
-
})
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export function getGroups(content: ResultContent[]) {
|
|
151
|
-
const groupTypeSet = new Set(content.map(({ ruleId }) => ruleId))
|
|
152
|
-
const groupTypeResults = [] as GroupedResultsModel[]
|
|
153
|
-
|
|
154
|
-
groupTypeSet.forEach(groupName => {
|
|
155
|
-
const groupResultsObj = new GroupedResultsModel(groupName)
|
|
156
|
-
|
|
157
|
-
content.forEach(resultEntry => {
|
|
158
|
-
if (resultEntry.ruleId === groupName) {
|
|
159
|
-
groupResultsObj.severity = resultEntry.severity
|
|
160
|
-
groupResultsObj.issue = stripMustacheTags(resultEntry.issue)
|
|
161
|
-
groupResultsObj.advice = resultEntry.advice
|
|
162
|
-
groupResultsObj.learn = resultEntry.learn
|
|
163
|
-
groupResultsObj.message = resultEntry.message?.text
|
|
164
|
-
|
|
165
|
-
groupResultsObj.lineInfoSet.add(getMessage(resultEntry.locations))
|
|
166
|
-
}
|
|
167
|
-
})
|
|
168
|
-
groupTypeResults.push(groupResultsObj)
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
return groupTypeResults
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export function getMessage(locations: Location[]) {
|
|
175
|
-
const message = locations[0]?.physicalLocation?.artifactLocation?.uri || ''
|
|
176
|
-
const lineNumber = locations[0]?.physicalLocation?.region?.startLine || ''
|
|
177
|
-
|
|
178
|
-
if (!lineNumber) {
|
|
179
|
-
return '@' + message
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return '@' + message + ':' + lineNumber
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export function stripMustacheTags(oldString: string) {
|
|
186
|
-
return oldString
|
|
187
|
-
.replace(/\n/g, ' ')
|
|
188
|
-
.replace(/{{.*?}}/g, '\n')
|
|
189
|
-
.replace(/\$\$LINK_DELIM\$\$/g, '\n')
|
|
190
|
-
.replace(/\s+/g, ' ')
|
|
191
|
-
.trim()
|
|
192
|
-
}
|
package/src/scan/scanConfig.js
CHANGED
|
@@ -31,11 +31,15 @@ const getScanConfig = argv => {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// if no name, take the full file path and use it as the project name
|
|
34
|
+
let projectNameSource
|
|
34
35
|
if (!scanParams.name && scanParams.file) {
|
|
35
36
|
scanParams.name = getFileName(scanParams.file)
|
|
37
|
+
projectNameSource = 'AUTO'
|
|
38
|
+
} else {
|
|
39
|
+
projectNameSource = 'USER'
|
|
36
40
|
}
|
|
37
41
|
|
|
38
|
-
return { ...paramsAuth, ...scanParams }
|
|
42
|
+
return { ...paramsAuth, ...scanParams, projectNameSource }
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
const getFileName = file => {
|
|
@@ -2,7 +2,8 @@ const i18n = require('i18n')
|
|
|
2
2
|
const {
|
|
3
3
|
returnOra,
|
|
4
4
|
startSpinner,
|
|
5
|
-
succeedSpinner
|
|
5
|
+
succeedSpinner,
|
|
6
|
+
stopSpinner
|
|
6
7
|
} = require('../utils/oraWrapper')
|
|
7
8
|
const populateProjectIdAndProjectName = require('./populateProjectIdAndProjectName')
|
|
8
9
|
const scan = require('./scan')
|
|
@@ -28,6 +29,11 @@ const fileAndLanguageLogic = async configToUse => {
|
|
|
28
29
|
console.log(i18n.__('fileNotExist'))
|
|
29
30
|
process.exit(1)
|
|
30
31
|
}
|
|
32
|
+
|
|
33
|
+
if (fileFunctions.fileIsEmpty(configToUse.file)) {
|
|
34
|
+
console.log(i18n.__('scanFileIsEmpty'))
|
|
35
|
+
process.exit(1)
|
|
36
|
+
}
|
|
31
37
|
return configToUse
|
|
32
38
|
} else {
|
|
33
39
|
if (configToUse.file === undefined || configToUse.file === null) {
|
|
@@ -40,10 +46,15 @@ const startScan = async configToUse => {
|
|
|
40
46
|
const startTime = performance.now()
|
|
41
47
|
await fileAndLanguageLogic(configToUse)
|
|
42
48
|
|
|
49
|
+
let newProject
|
|
50
|
+
|
|
43
51
|
if (!configToUse.projectId) {
|
|
44
|
-
|
|
45
|
-
configToUse
|
|
46
|
-
|
|
52
|
+
const { projectId, isNewProject } =
|
|
53
|
+
await populateProjectIdAndProjectName.populateProjectId(configToUse)
|
|
54
|
+
configToUse.projectId = projectId
|
|
55
|
+
newProject = isNewProject
|
|
56
|
+
} else {
|
|
57
|
+
newProject = false
|
|
47
58
|
}
|
|
48
59
|
const codeArtifactId = await scan.sendScan(configToUse)
|
|
49
60
|
|
|
@@ -53,6 +64,7 @@ const startScan = async configToUse => {
|
|
|
53
64
|
const scanDetail = await scanResults.returnScanResults(
|
|
54
65
|
configToUse,
|
|
55
66
|
codeArtifactId,
|
|
67
|
+
newProject,
|
|
56
68
|
getTimeout(configToUse),
|
|
57
69
|
startScanSpinner
|
|
58
70
|
)
|
|
@@ -64,12 +76,20 @@ const startScan = async configToUse => {
|
|
|
64
76
|
|
|
65
77
|
const endTime = performance.now()
|
|
66
78
|
const scanDurationMs = endTime - startTime
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
if (scanResultsInstances.statusCode !== 200) {
|
|
80
|
+
stopSpinner(startScanSpinner)
|
|
81
|
+
console.log('Result Service is unavailable, please try again later')
|
|
82
|
+
process.exit(1)
|
|
83
|
+
} else {
|
|
84
|
+
succeedSpinner(startScanSpinner, 'Contrast Scan complete')
|
|
85
|
+
console.log(
|
|
86
|
+
`----- Scan completed in ${(scanDurationMs / 1000).toFixed(2)}s -----`
|
|
87
|
+
)
|
|
88
|
+
return {
|
|
89
|
+
scanDetail,
|
|
90
|
+
scanResultsInstances: scanResultsInstances.body
|
|
91
|
+
}
|
|
92
|
+
}
|
|
73
93
|
}
|
|
74
94
|
}
|
|
75
95
|
|
package/src/scan/scanResults.js
CHANGED
|
@@ -3,6 +3,7 @@ const requestUtils = require('../../src/utils/requestUtils')
|
|
|
3
3
|
const oraFunctions = require('../utils/oraWrapper')
|
|
4
4
|
const _ = require('lodash')
|
|
5
5
|
const i18n = require('i18n')
|
|
6
|
+
const oraWrapper = require('../utils/oraWrapper')
|
|
6
7
|
|
|
7
8
|
const getScanId = async (config, codeArtifactId, client) => {
|
|
8
9
|
return client
|
|
@@ -30,11 +31,21 @@ const pollScanResults = async (config, scanId, client) => {
|
|
|
30
31
|
const returnScanResults = async (
|
|
31
32
|
config,
|
|
32
33
|
codeArtifactId,
|
|
34
|
+
newProject,
|
|
33
35
|
timeout,
|
|
34
36
|
startScanSpinner
|
|
35
37
|
) => {
|
|
36
38
|
const client = commonApi.getHttpClient(config)
|
|
37
39
|
let scanId = await getScanId(config, codeArtifactId, client)
|
|
40
|
+
|
|
41
|
+
// send metrics event to sast-event-collector
|
|
42
|
+
if (
|
|
43
|
+
process.env.CODESEC_INVOCATION_ENVIRONMENT &&
|
|
44
|
+
process.env.CODESEC_INVOCATION_ENVIRONMENT.toUpperCase() === 'GITHUB'
|
|
45
|
+
) {
|
|
46
|
+
await client.createNewEvent(config, scanId, newProject)
|
|
47
|
+
}
|
|
48
|
+
|
|
38
49
|
let startTime = new Date()
|
|
39
50
|
let complete = false
|
|
40
51
|
if (!_.isNil(scanId)) {
|
|
@@ -47,17 +58,27 @@ const returnScanResults = async (
|
|
|
47
58
|
}
|
|
48
59
|
if (result.body.status === 'FAILED') {
|
|
49
60
|
complete = true
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
if (config.debug) {
|
|
62
|
+
oraFunctions.failSpinner(
|
|
63
|
+
startScanSpinner,
|
|
64
|
+
i18n.__(
|
|
65
|
+
'scanNotCompleted',
|
|
66
|
+
'https://docs.contrastsecurity.com/en/binary-package-preparation.html'
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
}
|
|
52
70
|
if (
|
|
53
|
-
result
|
|
71
|
+
result?.body?.errorMessage ===
|
|
54
72
|
'Unable to determine language for code artifact'
|
|
55
73
|
) {
|
|
74
|
+
console.log(result.body.errorMessage)
|
|
56
75
|
console.log(
|
|
57
76
|
'Try scanning again using --language param. ',
|
|
58
77
|
i18n.__('scanOptionsLanguageSummary')
|
|
59
78
|
)
|
|
60
79
|
}
|
|
80
|
+
oraWrapper.stopSpinner(startScanSpinner)
|
|
81
|
+
console.log('Contrast Scan Finished')
|
|
61
82
|
process.exit(1)
|
|
62
83
|
}
|
|
63
84
|
}
|
|
@@ -80,23 +101,16 @@ const returnScanResultsInstances = async (config, scanId) => {
|
|
|
80
101
|
try {
|
|
81
102
|
result = await client.getScanResultsInstances(config, scanId)
|
|
82
103
|
if (JSON.stringify(result.statusCode) == 200) {
|
|
83
|
-
return result.body
|
|
104
|
+
return { body: result.body, statusCode: result.statusCode }
|
|
84
105
|
}
|
|
85
|
-
} catch (e) {
|
|
86
|
-
console.log(e.message.toString())
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
106
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
let result
|
|
93
|
-
try {
|
|
94
|
-
result = await client.getScanProjectById(config)
|
|
95
|
-
if (JSON.stringify(result.statusCode) == 200) {
|
|
96
|
-
return result.body
|
|
107
|
+
if (JSON.stringify(result.statusCode) == 503) {
|
|
108
|
+
return { statusCode: result.statusCode }
|
|
97
109
|
}
|
|
98
110
|
} catch (e) {
|
|
99
|
-
|
|
111
|
+
if (config.debug) {
|
|
112
|
+
console.log(e.message.toString())
|
|
113
|
+
}
|
|
100
114
|
}
|
|
101
115
|
}
|
|
102
116
|
|
|
@@ -104,6 +118,5 @@ module.exports = {
|
|
|
104
118
|
getScanId: getScanId,
|
|
105
119
|
returnScanResults: returnScanResults,
|
|
106
120
|
pollScanResults: pollScanResults,
|
|
107
|
-
returnScanResultsInstances: returnScanResultsInstances
|
|
108
|
-
returnScanProjectById: returnScanProjectById
|
|
121
|
+
returnScanResultsInstances: returnScanResultsInstances
|
|
109
122
|
}
|