@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
package/src/common/HTTPClient.js
CHANGED
|
@@ -171,9 +171,9 @@ HTTPClient.prototype.getScanProjectById = function getScanProjectById(config) {
|
|
|
171
171
|
return requestUtils.sendRequest({ method: 'get', options })
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
HTTPClient.prototype.getGlobalProperties = function getGlobalProperties() {
|
|
174
|
+
HTTPClient.prototype.getGlobalProperties = function getGlobalProperties(host) {
|
|
175
175
|
const options = _.cloneDeep(this.requestOptions)
|
|
176
|
-
let url = createGlobalPropertiesUrl(
|
|
176
|
+
let url = createGlobalPropertiesUrl(host)
|
|
177
177
|
options.url = url
|
|
178
178
|
return requestUtils.sendRequest({ method: 'get', options })
|
|
179
179
|
}
|
|
@@ -216,6 +216,36 @@ HTTPClient.prototype.sendSnapshot = function sendSnapshot(requestBody, config) {
|
|
|
216
216
|
return requestUtils.sendRequest({ method: 'post', options })
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
HTTPClient.prototype.scaServiceIngest = function scaServiceIngest(
|
|
220
|
+
requestBody,
|
|
221
|
+
config
|
|
222
|
+
) {
|
|
223
|
+
const options = _.cloneDeep(this.requestOptions)
|
|
224
|
+
let url = createScaServiceIngestURL(config)
|
|
225
|
+
options.url = url
|
|
226
|
+
options.body = requestBody
|
|
227
|
+
return requestUtils.sendRequest({ method: 'post', options })
|
|
228
|
+
}
|
|
229
|
+
HTTPClient.prototype.scaServiceReport = function scaServiceReport(
|
|
230
|
+
config,
|
|
231
|
+
reportId
|
|
232
|
+
) {
|
|
233
|
+
const options = _.cloneDeep(this.requestOptions)
|
|
234
|
+
let url = createScaServiceReportURL(config, reportId)
|
|
235
|
+
options.url = url
|
|
236
|
+
return requestUtils.sendRequest({ method: 'get', options })
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
HTTPClient.prototype.scaServiceReportStatus = function scaServiceReport(
|
|
240
|
+
config,
|
|
241
|
+
reportId
|
|
242
|
+
) {
|
|
243
|
+
const options = _.cloneDeep(this.requestOptions)
|
|
244
|
+
let url = createScaServiceReportStatusURL(config, reportId)
|
|
245
|
+
options.url = url
|
|
246
|
+
return requestUtils.sendRequest({ method: 'get', options })
|
|
247
|
+
}
|
|
248
|
+
|
|
219
249
|
HTTPClient.prototype.getReportById = function getReportById(config, reportId) {
|
|
220
250
|
const options = _.cloneDeep(this.requestOptions)
|
|
221
251
|
if (config.ignoreDev) {
|
|
@@ -416,6 +446,18 @@ function createSnapshotURL(config) {
|
|
|
416
446
|
return `${config.host}/Contrast/api/ng/sca/organizations/${config.organizationId}/applications/${config.applicationId}/snapshots`
|
|
417
447
|
}
|
|
418
448
|
|
|
449
|
+
function createScaServiceReportURL(config, reportId) {
|
|
450
|
+
return ``
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function createScaServiceReportStatusURL(config, reportId) {
|
|
454
|
+
return ``
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function createScaServiceIngestURL(config) {
|
|
458
|
+
return ``
|
|
459
|
+
}
|
|
460
|
+
|
|
419
461
|
const createAppCreateURL = config => {
|
|
420
462
|
return `${config.host}/Contrast/api/ng/sca/organizations/${config.organizationId}/applications/create`
|
|
421
463
|
}
|
|
@@ -38,8 +38,7 @@ const reportFailureError = () => {
|
|
|
38
38
|
console.log(i18n.__('auditReportFailureMessage'))
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
const genericError = (
|
|
42
|
-
console.log(missingCliOption)
|
|
41
|
+
const genericError = () => {
|
|
43
42
|
console.error(i18n.__('genericErrorMessage'))
|
|
44
43
|
process.exit(1)
|
|
45
44
|
}
|
package/src/common/fail.js
CHANGED
|
@@ -25,11 +25,15 @@ const isSeverityViolation = (severity, reportResults) => {
|
|
|
25
25
|
count += reportResults.high + reportResults.critical
|
|
26
26
|
break
|
|
27
27
|
case 'medium':
|
|
28
|
-
count +=
|
|
28
|
+
count +=
|
|
29
|
+
reportResults.medium + reportResults.high + reportResults.critical
|
|
29
30
|
break
|
|
30
31
|
case 'low':
|
|
31
32
|
count +=
|
|
32
|
-
reportResults.high +
|
|
33
|
+
reportResults.high +
|
|
34
|
+
reportResults.critical +
|
|
35
|
+
reportResults.medium +
|
|
36
|
+
reportResults.low
|
|
33
37
|
break
|
|
34
38
|
case 'note':
|
|
35
39
|
if (reportResults.note == reportResults.total) {
|
|
@@ -51,7 +55,7 @@ const failPipeline = (message = '') => {
|
|
|
51
55
|
' *********************************\n' +
|
|
52
56
|
i18n.__(message)
|
|
53
57
|
)
|
|
54
|
-
process.exit(
|
|
58
|
+
process.exit(2)
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
const parseSeverity = severity => {
|
|
@@ -6,7 +6,7 @@ import commonApi from '../utils/commonApi'
|
|
|
6
6
|
import { constants } from 'http2'
|
|
7
7
|
import { ContrastConf } from '../utils/getConfig'
|
|
8
8
|
|
|
9
|
-
const getLatestVersion = async (config:
|
|
9
|
+
export const getLatestVersion = async (config: ContrastConf) => {
|
|
10
10
|
const client = commonApi.getHttpClient(config)
|
|
11
11
|
try {
|
|
12
12
|
const res = await client.getLatestVersion()
|
|
@@ -14,18 +14,28 @@ const getLatestVersion = async (config: any) => {
|
|
|
14
14
|
return res.body
|
|
15
15
|
}
|
|
16
16
|
} catch (e) {
|
|
17
|
-
return
|
|
17
|
+
return undefined
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export async function findLatestCLIVersion(config: ContrastConf) {
|
|
22
22
|
const isCI = process.env.CONTRAST_CODESEC_CI
|
|
23
|
-
? JSON.parse(process.env.CONTRAST_CODESEC_CI)
|
|
23
|
+
? JSON.parse(process.env.CONTRAST_CODESEC_CI.toLowerCase())
|
|
24
24
|
: false
|
|
25
|
+
|
|
25
26
|
if (!isCI) {
|
|
26
|
-
let latestCLIVersion
|
|
27
|
-
|
|
28
|
-
latestCLIVersion
|
|
27
|
+
let latestCLIVersion = await getLatestVersion(config)
|
|
28
|
+
|
|
29
|
+
if (latestCLIVersion === undefined) {
|
|
30
|
+
config.set('numOfRuns', 0)
|
|
31
|
+
console.log(
|
|
32
|
+
'Failed to retrieve latest version info. Continuing execution.'
|
|
33
|
+
)
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//strip key and remove new lines
|
|
38
|
+
latestCLIVersion = latestCLIVersion.substring(8).replace('\n', '')
|
|
29
39
|
|
|
30
40
|
if (semver.lt(APP_VERSION, latestCLIVersion)) {
|
|
31
41
|
const updateAvailableMessage = `Update available ${chalk.yellow(
|
package/src/constants/locales.js
CHANGED
|
@@ -26,8 +26,7 @@ const en_locales = () => {
|
|
|
26
26
|
unauthenticatedErrorMessage:
|
|
27
27
|
'Please check the following keys are correct:\n--organization-id, --api-key or --authorization',
|
|
28
28
|
badRequestErrorHeader: '400 error - Bad Request',
|
|
29
|
-
badRequestErrorMessage:
|
|
30
|
-
'Please check the following key is correct: \n--application-id',
|
|
29
|
+
badRequestErrorMessage: 'Please check your parameters and try again',
|
|
31
30
|
badRequestCatalogueErrorMessage:
|
|
32
31
|
'The application name already exists, please use a unique name',
|
|
33
32
|
forbiddenRequestErrorHeader: '403 error - Forbidden',
|
|
@@ -123,6 +122,8 @@ const en_locales = () => {
|
|
|
123
122
|
'Provide this if you want to catalogue an application',
|
|
124
123
|
failOptionErrorMessage:
|
|
125
124
|
' FAIL - CVEs have been detected that match at least the cve_severity option specified.',
|
|
125
|
+
failOptionMessage:
|
|
126
|
+
' Use with contrast scan or contrast audit. Detects failures based on the severity level specified with the --severity command. For example, "contrast scan --fail --severity high". Returns all failures if no severity level is specified.',
|
|
126
127
|
constantsLanguage:
|
|
127
128
|
'Valid values are JAVA, DOTNET, NODE, PYTHON and RUBY. If there are multiple project configuration files in the project_path, language is also required. Also, provide this when cataloguing an application',
|
|
128
129
|
constantsFilePath: `Specify a directory or the file where dependencies are declared. (By default, CodeSec will search for project files in the current directory.)`,
|
|
@@ -148,7 +149,7 @@ const en_locales = () => {
|
|
|
148
149
|
failSeverityOptionErrorMessage:
|
|
149
150
|
' FAIL - Results detected vulnerabilities over accepted severity level',
|
|
150
151
|
constantsSeverity:
|
|
151
|
-
'
|
|
152
|
+
'Use with "contrast scan --fail --severity high" or "contrast audit --fail --severity high". Set the severity level to detect vulnerabilities or dependencies. Severity levels are critical, high, medium, low or note.',
|
|
152
153
|
constantsCount: 'The number of CVEs that must be exceeded to fail a build',
|
|
153
154
|
constantsHeader: 'CodeSec by Contrast Security',
|
|
154
155
|
configHeader2: 'Config options',
|
|
@@ -171,7 +172,7 @@ const en_locales = () => {
|
|
|
171
172
|
constantsConfigUsageContents: 'view / clear the configuration',
|
|
172
173
|
constantsPrerequisitesContent:
|
|
173
174
|
'To scan a Java project you will need a .jar or .war file for analysis\n' +
|
|
174
|
-
'To scan a Javascript project you will need a .js or.zip
|
|
175
|
+
'To scan a Javascript project you will need a single .js or a .zip of multiple .js files\n' +
|
|
175
176
|
'To scan a .NET c# webforms project you will need a .exe or a .zip file for analysis\n',
|
|
176
177
|
constantsUsage: 'Usage',
|
|
177
178
|
constantsUsageCommandExample: 'contrast [command] [options]',
|
|
@@ -318,7 +319,17 @@ const en_locales = () => {
|
|
|
318
319
|
scanOptionsVerboseSummary: ' Returns extended information to the terminal.',
|
|
319
320
|
authSuccessMessage: 'Authentication successful',
|
|
320
321
|
runAuthSuccessMessage:
|
|
321
|
-
|
|
322
|
+
chalk.bold('CodeSec by Contrast') +
|
|
323
|
+
'\nScan, secure and ship your code in minutes for FREE. \n' +
|
|
324
|
+
chalk.bold('\nRun\n') +
|
|
325
|
+
chalk.bold('\ncontrast scan') +
|
|
326
|
+
" to run Contrast's industry leading SAST scanner. \nSupports Java, JavaScript and .Net \n" +
|
|
327
|
+
chalk.bold('\ncontrast audit') +
|
|
328
|
+
' to find vulnerabilities in your open source dependencies.\nSupports Java, .NET, Node, Ruby, Python, Go and PHP \n' +
|
|
329
|
+
chalk.bold('\ncontrast lambda') +
|
|
330
|
+
' to secure your AWS serverless functions. \nSupports Java and Python \n' +
|
|
331
|
+
chalk.bold('\ncontrast help') +
|
|
332
|
+
' to learn more about the capabilities.',
|
|
322
333
|
authWaitingMessage: 'Waiting for auth...',
|
|
323
334
|
authTimedOutMessage: 'Auth Timed out, try again',
|
|
324
335
|
zipErrorScan:
|
|
@@ -326,10 +337,7 @@ const en_locales = () => {
|
|
|
326
337
|
unknownFileErrorScan: 'Unsupported file selected for Scan.',
|
|
327
338
|
foundScanFile: 'Found: %s',
|
|
328
339
|
foundDetailedVulnerabilities:
|
|
329
|
-
chalk.bold('%s
|
|
330
|
-
' | ' +
|
|
331
|
-
chalk.bold('%s High') +
|
|
332
|
-
' | %s Medium | %s Low | %s Note',
|
|
340
|
+
chalk.bold('%s') + ' | ' + chalk.bold('%s') + ' | %s | %s | %s ',
|
|
333
341
|
requiredParams: 'All required parameters are not present.',
|
|
334
342
|
timeoutScan: 'Timeout set to 5 minutes.',
|
|
335
343
|
searchingScanFileDirectory: 'Searching for file to scan from %s...',
|
package/src/constants.js
CHANGED
|
@@ -115,7 +115,7 @@ const scanOptionDefinitions = [
|
|
|
115
115
|
'{bold ' +
|
|
116
116
|
i18n.__('constantsOptional') +
|
|
117
117
|
'}: ' +
|
|
118
|
-
i18n.__('
|
|
118
|
+
i18n.__('failOptionMessage')
|
|
119
119
|
},
|
|
120
120
|
{
|
|
121
121
|
name: 'severity',
|
|
@@ -247,7 +247,7 @@ const auditOptionDefinitions = [
|
|
|
247
247
|
'{bold ' +
|
|
248
248
|
i18n.__('constantsOptional') +
|
|
249
249
|
'}: ' +
|
|
250
|
-
i18n.__('
|
|
250
|
+
i18n.__('failOptionMessage')
|
|
251
251
|
},
|
|
252
252
|
{
|
|
253
253
|
name: 'severity',
|
package/src/index.ts
CHANGED
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
} from './common/versionChecker'
|
|
16
16
|
import { findCommandOnError } from './common/errorHandling'
|
|
17
17
|
import { sendTelemetryConfigAsConfObj } from './telemetry/telemetry'
|
|
18
|
-
|
|
19
18
|
const {
|
|
20
19
|
commandLineDefinitions: { mainUsageGuide, mainDefinition }
|
|
21
20
|
} = constants
|
|
@@ -58,7 +57,7 @@ const start = async () => {
|
|
|
58
57
|
config.set('numOfRuns', config.get('numOfRuns') + 1)
|
|
59
58
|
|
|
60
59
|
// @ts-ignore
|
|
61
|
-
if (config.get('numOfRuns') >=
|
|
60
|
+
if (config.get('numOfRuns') >= 10) {
|
|
62
61
|
await findLatestCLIVersion(config)
|
|
63
62
|
config.set('numOfRuns', 0)
|
|
64
63
|
}
|
|
@@ -96,9 +95,8 @@ const start = async () => {
|
|
|
96
95
|
? console.log(
|
|
97
96
|
`Unknown Command: Did you mean "${foundCommand}"? \nUse "${foundCommand} --help" for the full list of options`
|
|
98
97
|
)
|
|
99
|
-
: console.log(
|
|
100
|
-
|
|
101
|
-
)
|
|
98
|
+
: console.log(`\nUnknown Command: ${command} \n`)
|
|
99
|
+
console.log(mainUsageGuide)
|
|
102
100
|
await sendTelemetryConfigAsConfObj(
|
|
103
101
|
config,
|
|
104
102
|
command,
|
|
@@ -107,9 +105,8 @@ const start = async () => {
|
|
|
107
105
|
'undefined'
|
|
108
106
|
)
|
|
109
107
|
} else {
|
|
110
|
-
console.log(
|
|
111
|
-
|
|
112
|
-
)
|
|
108
|
+
console.log(`\nUnknown Command: ${command}\n`)
|
|
109
|
+
console.log(mainUsageGuide)
|
|
113
110
|
await sendTelemetryConfigAsConfObj(
|
|
114
111
|
config,
|
|
115
112
|
command,
|
package/src/lambda/lambda.ts
CHANGED
|
@@ -17,6 +17,7 @@ import ora from '../utils/oraWrapper'
|
|
|
17
17
|
import { postAnalytics } from './analytics'
|
|
18
18
|
import { LambdaOptions, AnalyticsOption, StatusType, EventType } from './types'
|
|
19
19
|
import { APP_VERSION } from '../constants/constants'
|
|
20
|
+
import chalk from 'chalk'
|
|
20
21
|
|
|
21
22
|
type ApiParams = {
|
|
22
23
|
organizationId: string
|
|
@@ -114,6 +115,9 @@ const processLambda = async (argv: string[]) => {
|
|
|
114
115
|
await postAnalytics(endCommandAnalytics).catch((error: Error) => {
|
|
115
116
|
/* ignore */
|
|
116
117
|
})
|
|
118
|
+
|
|
119
|
+
postRunMessage()
|
|
120
|
+
|
|
117
121
|
if (errorMsg) {
|
|
118
122
|
process.exit(1)
|
|
119
123
|
}
|
|
@@ -122,7 +126,7 @@ const processLambda = async (argv: string[]) => {
|
|
|
122
126
|
|
|
123
127
|
const getAvailableFunctions = async (lambdaOptions: LambdaOptions) => {
|
|
124
128
|
const lambdas = await getAllLambdas(lambdaOptions)
|
|
125
|
-
printAvailableLambdas(lambdas, { runtimes: ['python', 'java'] })
|
|
129
|
+
printAvailableLambdas(lambdas, { runtimes: ['python', 'java', 'node'] })
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
const actualProcessLambda = async (lambdaOptions: LambdaOptions) => {
|
|
@@ -221,4 +225,12 @@ const handleLambdaHelp = () => {
|
|
|
221
225
|
process.exit(0)
|
|
222
226
|
}
|
|
223
227
|
|
|
228
|
+
const postRunMessage = () => {
|
|
229
|
+
console.log('\n' + chalk.underline.bold('Other Codesec Features:'))
|
|
230
|
+
console.log("'contrast scan' to run CodeSec’s industry leading SAST scanner")
|
|
231
|
+
console.log(
|
|
232
|
+
"'contrast audit' to find vulnerabilities in your open source dependencies\n"
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
224
236
|
export { processLambda, LambdaOptions, ApiParams, getAvailableFunctions }
|
|
@@ -11,7 +11,7 @@ import ora from '../utils/oraWrapper'
|
|
|
11
11
|
import { LambdaOptions } from './lambda'
|
|
12
12
|
import { log, getReadableFileSize } from './logUtils'
|
|
13
13
|
|
|
14
|
-
type RuntimeLanguage = 'java' | 'python'
|
|
14
|
+
type RuntimeLanguage = 'java' | 'python' | 'node'
|
|
15
15
|
|
|
16
16
|
type FilterLambdas = {
|
|
17
17
|
runtimes: RuntimeLanguage[]
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const {
|
|
2
|
+
getSeverityCounts,
|
|
3
|
+
createSummaryMessageTop,
|
|
4
|
+
printVulnInfo,
|
|
5
|
+
getReportTable,
|
|
6
|
+
getIssueRow,
|
|
7
|
+
printNoVulnFoundMsg
|
|
8
|
+
} = require('../../audit/report/commonReportingFunctions')
|
|
9
|
+
const { orderBy } = require('lodash')
|
|
10
|
+
const { assignBySeverity } = require('../../scan/formatScanOutput')
|
|
11
|
+
const chalk = require('chalk')
|
|
12
|
+
const { CE_URL } = require('../../constants/constants')
|
|
13
|
+
const common = require('../../common/fail')
|
|
14
|
+
|
|
15
|
+
const processAuditReport = (config, results) => {
|
|
16
|
+
let severityCounts = {}
|
|
17
|
+
if (results !== undefined) {
|
|
18
|
+
severityCounts = formatScaServicesReport(config, results)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (config.fail) {
|
|
22
|
+
common.processFail(config, severityCounts)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const formatScaServicesReport = (config, results) => {
|
|
26
|
+
const projectOverviewCount = getSeverityCounts(results)
|
|
27
|
+
|
|
28
|
+
if (projectOverviewCount.total === 0) {
|
|
29
|
+
printNoVulnFoundMsg()
|
|
30
|
+
return projectOverviewCount
|
|
31
|
+
} else {
|
|
32
|
+
let total = 0
|
|
33
|
+
const numberOfCves = results.length
|
|
34
|
+
const table = getReportTable()
|
|
35
|
+
let contrastHeaderNumCounter = 0
|
|
36
|
+
let assignPriorityToResults = results.map(result =>
|
|
37
|
+
assignBySeverity(result, result)
|
|
38
|
+
)
|
|
39
|
+
const numberOfVulns = results
|
|
40
|
+
.map(result => result.vulnerabilities)
|
|
41
|
+
.reduce((a, b) => {
|
|
42
|
+
return (total += b.length)
|
|
43
|
+
}, 0)
|
|
44
|
+
const outputOrderedByLowestSeverityAndLowestNumOfCvesFirst = orderBy(
|
|
45
|
+
assignPriorityToResults,
|
|
46
|
+
[
|
|
47
|
+
reportListItem => {
|
|
48
|
+
return reportListItem.priority
|
|
49
|
+
},
|
|
50
|
+
reportListItem => {
|
|
51
|
+
return reportListItem.vulnerabilities.length
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
['asc', 'desc']
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
for (const result of outputOrderedByLowestSeverityAndLowestNumOfCvesFirst) {
|
|
58
|
+
contrastHeaderNumCounter++
|
|
59
|
+
const cvesNum = result.vulnerabilities.length
|
|
60
|
+
const grammaticallyCorrectVul =
|
|
61
|
+
result.vulnerabilities.length > 1 ? 'vulnerabilities' : 'vulnerability'
|
|
62
|
+
|
|
63
|
+
const headerColour = chalk.hex(result.colour)
|
|
64
|
+
const headerRow = [
|
|
65
|
+
headerColour(
|
|
66
|
+
`CONTRAST-${contrastHeaderNumCounter.toString().padStart(3, '0')}`
|
|
67
|
+
),
|
|
68
|
+
headerColour(`-`),
|
|
69
|
+
headerColour(`[${result.severity}] `) +
|
|
70
|
+
headerColour.bold(`${result.artifactName}`) +
|
|
71
|
+
` introduces ${cvesNum} ${grammaticallyCorrectVul}`
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
const adviceRow = [
|
|
75
|
+
chalk.bold(`Advice`),
|
|
76
|
+
chalk.bold(`:`),
|
|
77
|
+
`Change to version ${result.remediationAdvice.latestStableVersion}`
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
let assignPriorityToVulns = result.vulnerabilities.map(result =>
|
|
81
|
+
assignBySeverity(result, result)
|
|
82
|
+
)
|
|
83
|
+
const issueRow = getIssueRow(assignPriorityToVulns)
|
|
84
|
+
|
|
85
|
+
table.push(headerRow, issueRow, adviceRow)
|
|
86
|
+
console.log()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.log()
|
|
90
|
+
createSummaryMessageTop(numberOfCves, numberOfVulns)
|
|
91
|
+
console.log(table.toString() + '\n')
|
|
92
|
+
printVulnInfo(projectOverviewCount)
|
|
93
|
+
|
|
94
|
+
if (config.host !== CE_URL) {
|
|
95
|
+
console.log(
|
|
96
|
+
'\n' + chalk.bold('View your full dependency tree in Contrast:')
|
|
97
|
+
)
|
|
98
|
+
console.log(
|
|
99
|
+
`${config.host}/Contrast/static/ng/index.html#/${config.organizationId}/applications/${config.applicationId}/libs/dependency-tree`
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
return projectOverviewCount
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
module.exports = {
|
|
106
|
+
formatScaServicesReport,
|
|
107
|
+
processAuditReport
|
|
108
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const commonApi = require('../../utils/commonApi')
|
|
2
|
+
const { APP_VERSION } = require('../../constants/constants')
|
|
3
|
+
const requestUtils = require('../../utils/requestUtils')
|
|
4
|
+
|
|
5
|
+
const scaTreeUpload = async (analysis, config) => {
|
|
6
|
+
const requestBody = {
|
|
7
|
+
applicationId: config.applicationId,
|
|
8
|
+
dependencyTree: analysis,
|
|
9
|
+
organizationId: config.organizationId,
|
|
10
|
+
language: config.language,
|
|
11
|
+
tool: {
|
|
12
|
+
name: 'Contrast Codesec',
|
|
13
|
+
version: APP_VERSION
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const client = commonApi.getHttpClient(config)
|
|
18
|
+
const reportID = await client
|
|
19
|
+
.scaServiceIngest(requestBody, config)
|
|
20
|
+
.then(res => {
|
|
21
|
+
if (res.statusCode === 201) {
|
|
22
|
+
return res.body.libraryIngestJobId
|
|
23
|
+
} else {
|
|
24
|
+
throw new Error(res.statusCode + ` error ingesting dependencies`)
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
.catch(err => {
|
|
28
|
+
throw err
|
|
29
|
+
})
|
|
30
|
+
console.log(' polling report')
|
|
31
|
+
|
|
32
|
+
let keepChecking = true
|
|
33
|
+
let res
|
|
34
|
+
while (keepChecking) {
|
|
35
|
+
res = await client.scaServiceReportStatus(config, reportID).then(res => {
|
|
36
|
+
console.log(res.statusCode)
|
|
37
|
+
console.log(res.body)
|
|
38
|
+
if (res.body.status == 'COMPLETED') {
|
|
39
|
+
keepChecking = false
|
|
40
|
+
return client.scaServiceReport(config, reportID).then(res => {
|
|
41
|
+
return res.body
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
if (!keepChecking) {
|
|
47
|
+
return res
|
|
48
|
+
}
|
|
49
|
+
await requestUtils.sleep(5000)
|
|
50
|
+
}
|
|
51
|
+
return res
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = {
|
|
55
|
+
scaTreeUpload
|
|
56
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const analysis = require('./analysis')
|
|
2
2
|
const i18n = require('i18n')
|
|
3
3
|
const formatMessage = require('../common/formatMessage')
|
|
4
|
+
const scaServiceParser = require('./scaServiceParser')
|
|
4
5
|
|
|
5
6
|
const jsAnalysis = async (config, languageFiles) => {
|
|
6
7
|
checkForCorrectFiles(languageFiles)
|
|
@@ -13,6 +14,9 @@ const jsAnalysis = async (config, languageFiles) => {
|
|
|
13
14
|
const buildNodeTree = async (config, files) => {
|
|
14
15
|
let analysis = await readFiles(config, files)
|
|
15
16
|
const rawNode = await parseFiles(config, files, analysis)
|
|
17
|
+
if (config.experimental) {
|
|
18
|
+
return scaServiceParser.parseJS(rawNode)
|
|
19
|
+
}
|
|
16
20
|
return formatMessage.createJavaScriptTSMessage(rawNode)
|
|
17
21
|
}
|
|
18
22
|
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
const parseJS = rawNode => {
|
|
2
|
+
let dependencyTree = {}
|
|
3
|
+
let combinedPackageJSONDep = {
|
|
4
|
+
...rawNode.packageJSON?.dependencies,
|
|
5
|
+
...rawNode.packageJSON?.devDependencies
|
|
6
|
+
}
|
|
7
|
+
let analyseLock = chooseLockFile(rawNode)
|
|
8
|
+
|
|
9
|
+
if (analyseLock.type === 'yarn') {
|
|
10
|
+
dependencyTree = yarnCreateDepTree(
|
|
11
|
+
dependencyTree,
|
|
12
|
+
combinedPackageJSONDep,
|
|
13
|
+
analyseLock.lockFile,
|
|
14
|
+
rawNode
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (analyseLock.type === 'npm') {
|
|
19
|
+
dependencyTree = npmCreateDepTree(
|
|
20
|
+
dependencyTree,
|
|
21
|
+
combinedPackageJSONDep,
|
|
22
|
+
analyseLock.lockFile,
|
|
23
|
+
rawNode
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return dependencyTree
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const npmCreateDepTree = (
|
|
31
|
+
dependencyTree,
|
|
32
|
+
combinedPackageJSONDep,
|
|
33
|
+
packageLock,
|
|
34
|
+
rawNode
|
|
35
|
+
) => {
|
|
36
|
+
for (const [key, value] of Object.entries(packageLock)) {
|
|
37
|
+
dependencyTree[key] = {
|
|
38
|
+
name: key,
|
|
39
|
+
version: getResolvedVersion(key, packageLock),
|
|
40
|
+
group: null,
|
|
41
|
+
isProduction: checkIfInPackageJSON(rawNode.packageJSON.dependencies, key),
|
|
42
|
+
directDependency: checkIfInPackageJSON(combinedPackageJSONDep, key),
|
|
43
|
+
dependencies: createNPMChildDependencies(packageLock, key)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return dependencyTree
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const yarnCreateDepTree = (
|
|
50
|
+
dependencyTree,
|
|
51
|
+
combinedPackageJSONDep,
|
|
52
|
+
packageLock,
|
|
53
|
+
rawNode
|
|
54
|
+
) => {
|
|
55
|
+
for (const [key, value] of Object.entries(packageLock)) {
|
|
56
|
+
let gav = getNameFromGAV(key)
|
|
57
|
+
let nag = getDepNameWithoutVersion(key)
|
|
58
|
+
dependencyTree[key] = {
|
|
59
|
+
name: gav,
|
|
60
|
+
version: getResolvedVersion(key, packageLock),
|
|
61
|
+
group: null,
|
|
62
|
+
isProduction: checkIfInPackageJSON(rawNode.packageJSON.dependencies, nag),
|
|
63
|
+
directDependency: checkIfInPackageJSON(combinedPackageJSONDep, nag),
|
|
64
|
+
dependencies: createChildDependencies(packageLock, key)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return dependencyTree
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const chooseLockFile = rawNode => {
|
|
71
|
+
if (rawNode?.yarn?.yarnLockFile !== undefined) {
|
|
72
|
+
return { lockFile: rawNode?.yarn?.yarnLockFile?.object, type: 'yarn' }
|
|
73
|
+
} else if (rawNode.npmLockFile !== undefined) {
|
|
74
|
+
return { lockFile: rawNode?.npmLockFile?.dependencies, type: 'npm' }
|
|
75
|
+
} else {
|
|
76
|
+
return undefined
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const createKeyName = (dep, version) => {
|
|
81
|
+
return dep + '@' + version
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const checkIfInPackageJSON = (list, dep) => {
|
|
85
|
+
return Object.keys(list).includes(dep)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const createChildDependencies = (lockFileDep, currentDep) => {
|
|
89
|
+
let depArray = []
|
|
90
|
+
if (lockFileDep[currentDep]?.dependencies) {
|
|
91
|
+
for (const [key, value] of Object.entries(
|
|
92
|
+
lockFileDep[currentDep]?.dependencies
|
|
93
|
+
)) {
|
|
94
|
+
depArray.push(createKeyName(key, value))
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return depArray
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const createNPMChildDependencies = (lockFileDep, currentDep) => {
|
|
101
|
+
let depArray = []
|
|
102
|
+
if (lockFileDep[currentDep]?.requires) {
|
|
103
|
+
for (const [key, value] of Object.entries(
|
|
104
|
+
lockFileDep[currentDep]?.requires
|
|
105
|
+
)) {
|
|
106
|
+
depArray.push(key)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return depArray
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const getDepNameWithoutVersion = depKey => {
|
|
113
|
+
let dependency = depKey.split('@')
|
|
114
|
+
if (dependency.length - 1 > 1) {
|
|
115
|
+
return '@' + dependency[1]
|
|
116
|
+
}
|
|
117
|
+
return dependency[0]
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const getNameFromGAV = depKey => {
|
|
121
|
+
let dependency = depKey.split('/')
|
|
122
|
+
if (dependency.length == 2) {
|
|
123
|
+
dependency = getDepNameWithoutVersion(dependency[1])
|
|
124
|
+
return dependency
|
|
125
|
+
}
|
|
126
|
+
if (dependency.length == 1) {
|
|
127
|
+
dependency = getDepNameWithoutVersion(depKey)
|
|
128
|
+
return dependency
|
|
129
|
+
}
|
|
130
|
+
//what should we do if there's no version? The service will fall over but do we want to throw error for only one wrong version?
|
|
131
|
+
return depKey
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const getResolvedVersion = (depKey, packageLock) => {
|
|
135
|
+
return packageLock[depKey]?.version
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = {
|
|
139
|
+
parseJS,
|
|
140
|
+
checkIfInPackageJSON,
|
|
141
|
+
getNameFromGAV,
|
|
142
|
+
getResolvedVersion,
|
|
143
|
+
chooseLockFile,
|
|
144
|
+
createNPMChildDependencies
|
|
145
|
+
}
|