@contrast/contrast 1.0.2 → 1.0.3

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.
Files changed (50) hide show
  1. package/.prettierignore +1 -0
  2. package/README.md +6 -4
  3. package/dist/audit/languageAnalysisEngine/langugageAnalysisFactory.js +25 -0
  4. package/dist/commands/audit/saveFile.js +11 -0
  5. package/dist/commands/auth/auth.js +19 -1
  6. package/dist/commands/config/config.js +19 -8
  7. package/dist/commands/scan/processScan.js +4 -22
  8. package/dist/common/HTTPClient.js +11 -0
  9. package/dist/common/errorHandling.js +9 -23
  10. package/dist/common/{findLatestCLIVersion.js → versionChecker.js} +10 -3
  11. package/dist/constants/constants.js +4 -2
  12. package/dist/constants/locales.js +17 -7
  13. package/dist/constants.js +34 -2
  14. package/dist/index.js +48 -30
  15. package/dist/sbom/generateSbom.js +20 -0
  16. package/dist/scan/help.js +4 -2
  17. package/dist/scan/populateProjectIdAndProjectName.js +1 -0
  18. package/dist/scan/saveResults.js +9 -10
  19. package/dist/scan/scan.js +27 -2
  20. package/dist/scan/scanConfig.js +20 -1
  21. package/dist/scan/scanController.js +7 -2
  22. package/dist/scan/scanResults.js +5 -0
  23. package/dist/utils/requestUtils.js +1 -1
  24. package/dist/utils/saveFile.js +19 -0
  25. package/package.json +1 -1
  26. package/src/audit/languageAnalysisEngine/langugageAnalysisFactory.js +32 -0
  27. package/src/commands/audit/processAudit.ts +0 -1
  28. package/src/commands/audit/saveFile.ts +6 -0
  29. package/src/commands/auth/auth.js +25 -1
  30. package/src/commands/config/config.js +22 -8
  31. package/src/commands/scan/processScan.js +4 -23
  32. package/src/common/HTTPClient.js +13 -0
  33. package/src/common/errorHandling.ts +11 -24
  34. package/src/common/versionChecker.ts +39 -0
  35. package/src/constants/constants.js +5 -4
  36. package/src/constants/locales.js +23 -8
  37. package/src/constants.js +37 -2
  38. package/src/index.ts +63 -36
  39. package/src/sbom/generateSbom.ts +17 -0
  40. package/src/scan/help.js +4 -2
  41. package/src/scan/populateProjectIdAndProjectName.js +1 -0
  42. package/src/scan/saveResults.js +8 -9
  43. package/src/scan/scan.js +30 -2
  44. package/src/scan/scanConfig.js +26 -1
  45. package/src/scan/scanController.js +9 -2
  46. package/src/scan/scanResults.js +10 -0
  47. package/src/utils/getConfig.ts +2 -0
  48. package/src/utils/requestUtils.js +1 -1
  49. package/src/utils/saveFile.js +19 -0
  50. package/src/common/findLatestCLIVersion.ts +0 -27
@@ -172,13 +172,19 @@ const en_locales = () => {
172
172
  constantsSeverity:
173
173
  'Combined with the --report command, allows the user to report libraries with vulnerabilities above a chosen severity level. For example, cve_severity medium only reports libraries with vulnerabilities at medium or higher severity. Values for level are high, medium or low.',
174
174
  constantsCount: "The number of CVE's that must be exceeded to fail a build",
175
- constantsHeader: 'Contrast CLI',
175
+ constantsHeader: 'CodeSec by Contrast Security',
176
176
  constantsPrerequisitesContentScanLanguages: 'Java & JavaScript supported',
177
177
  constantsContrastContent:
178
- 'Use the Contrast CLI, the fastest and most accurate code scan, to help find and eliminate security bugs in your code.',
178
+ 'Use the Contrast CLI to run a scan(Java, JavaScript and .NET ) or lambda command (Java and Python) to find your vulnerabilities and start securing your code.',
179
179
  constantsUsageGuideContentRecommendation:
180
180
  'Our recommendation is that this is invoked as part of a CI pipeline so that running the cli is automated as part of your build process.',
181
181
  constantsPrerequisitesHeader: 'Pre-requisites',
182
+ constantsAuthUsageHeader: 'Usage',
183
+ constantsAuthUsageContents: 'contrast auth',
184
+ constantsAuthHeaderContents:
185
+ 'Authorize with external identity provider to perform scans on code',
186
+ configHeader: 'Config',
187
+ constantsConfigUsageContents: 'view / clear the configuration',
182
188
  constantsPrerequisitesContent:
183
189
  'To scan a Java project you will need a .jar or .war file for analysis\n' +
184
190
  'To scan a Javascript project you will need a .js or.zip file for analysis\n' +
@@ -186,7 +192,7 @@ const en_locales = () => {
186
192
  constantsUsage: 'Usage',
187
193
  constantsUsageCommandExample: 'contrast [command] [options]',
188
194
  constantsUsageCommandInfo:
189
- 'The file argument is optional. If no file is given, Contrast will search for a .jar, .war, .js, .exe or .zip file in the working directory.\n',
195
+ 'The file argument is optional. If no file is given, Contrast will search for a .jar, .war, .exe or .zip file in the working directory.\n',
190
196
  constantsUsageCommandInfo24Hours:
191
197
  'Submitted files are encrypted during upload and deleted in 24 hours.',
192
198
  constantsAnd: 'AND',
@@ -296,8 +302,7 @@ const en_locales = () => {
296
302
  'We only accept the following file types: \nJava - .jar, .war \nJavaScript - .js or .zip files',
297
303
  helpAuthSummary:
298
304
  'Authenticate Contrast using your Github or Google account',
299
- helpScanSummary:
300
- 'Searches for a .jar, .war, .js or .zip file in the working directory, uploads for analysis and returns the results',
305
+ helpScanSummary: 'Perform static analysis on binaries / code artifacts',
301
306
  helpLambdaSummary: 'Perform scan on AWS Lambda functions',
302
307
  helpVersionSummary: 'Displays version of Contrast CLI',
303
308
  helpConfigSummary: 'Displays stored credentials',
@@ -308,6 +313,7 @@ const en_locales = () => {
308
313
  versionName: 'version',
309
314
  configName: 'config',
310
315
  helpName: 'help',
316
+ scanOptionsLanguageSummary: 'Valid values are JAVA, JAVASCRIPT and DOTNET',
311
317
  scanOptionsLanguageSummaryOptional:
312
318
  'Language of file to send for analysis. ',
313
319
  scanOptionsLanguageSummaryRequired:
@@ -315,7 +321,7 @@ const en_locales = () => {
315
321
  scanOptionsTimeoutSummary:
316
322
  'Time in seconds to wait for scan to complete. Default value is 300 seconds.',
317
323
  scanOptionsFileNameSummary:
318
- 'Path of the file you want to scan. If no file is specified, Contrast searches for a .jar, .war, .js, .exe or .zip file in the working directory.',
324
+ 'Path of the file you want to scan. If no file is specified, Contrast searches for a .jar, .war, .exe or .zip file in the working directory.',
319
325
  scanOptionsVerboseSummary: ' Returns extended information to the terminal.',
320
326
  authSuccessMessage: 'Authentication successful',
321
327
  runAuthSuccessMessage:
@@ -326,7 +332,7 @@ const en_locales = () => {
326
332
  zipErrorScan:
327
333
  'We only support zip files for JAVASCRIPT language, please set the flag --language JAVASCRIPT',
328
334
  unknownFileErrorScan: 'Unsupported file selected for Scan.',
329
- foundScanFile: 'found: %s',
335
+ foundScanFile: 'Found: %s',
330
336
  foundDetailedVulnerabilities:
331
337
  chalk.bold('%s Critical') +
332
338
  ' | ' +
@@ -336,6 +342,7 @@ const en_locales = () => {
336
342
  timeoutScan: 'Timeout set to 5 minutes.',
337
343
  searchingScanFileDirectory: 'Searching for file to scan from %s...',
338
344
  scanHeader: 'Contrast Scan CLI',
345
+ authHeader: 'Auth',
339
346
  lambdaHeader: 'Contrast lambda help',
340
347
  lambdaSummary:
341
348
  'Performs static security scan on an AWS Lambda Function.\nProduces CVE (Vulnerable Dependencies) and Least Privilege violations/remediation results.',
@@ -406,7 +413,15 @@ const en_locales = () => {
406
413
  'saves the output in specified format Txt text, sbom',
407
414
  scanNoVulnerabilitiesFound: '👏 No vulnerabilities found',
408
415
  scanNoFiletypeSpecifiedForSave:
409
- 'Please specify file type to save results to',
416
+ 'Please specify file type to save results to, accepted value is SARIF',
417
+ auditSBOMSaveSuccess:
418
+ '\n Software Bill of Materials (SBOM) saved successfully',
419
+ auditNoFiletypeSpecifiedForSave: `\n ${chalk.yellow.bold(
420
+ 'No file type specified for --save option to save audit results to. Use audit --help to see valid --save options.'
421
+ )}`,
422
+ auditBadFiletypeSpecifiedForSave: `\n ${chalk.yellow.bold(
423
+ 'Bad file type specified for --save option. Use audit --help to see valid --save options.'
424
+ )}`,
410
425
  ...lambda
411
426
  }
412
427
  }
package/src/constants.js CHANGED
@@ -20,6 +20,15 @@ const scanOptionDefinitions = [
20
20
  '}: ' +
21
21
  i18n.__('constantsProjectName')
22
22
  },
23
+ {
24
+ name: 'language',
25
+ alias: 'l',
26
+ description:
27
+ '{bold ' +
28
+ i18n.__('constantsOptional') +
29
+ '}: ' +
30
+ i18n.__('scanOptionsLanguageSummary')
31
+ },
23
32
  {
24
33
  name: 'file',
25
34
  alias: 'f',
@@ -75,7 +84,6 @@ const scanOptionDefinitions = [
75
84
  },
76
85
  {
77
86
  name: 'host',
78
- alias: 'h',
79
87
  description:
80
88
  '{bold ' +
81
89
  i18n.__('constantsRequired') +
@@ -126,6 +134,7 @@ const scanOptionDefinitions = [
126
134
  },
127
135
  {
128
136
  name: 'help',
137
+ alias: 'h',
129
138
  type: Boolean
130
139
  },
131
140
  {
@@ -135,6 +144,29 @@ const scanOptionDefinitions = [
135
144
  }
136
145
  ]
137
146
 
147
+ const authOptionDefinitions = [
148
+ {
149
+ name: 'help',
150
+ alias: 'h',
151
+ type: Boolean
152
+ }
153
+ ]
154
+
155
+ const configOptionDefinitions = [
156
+ {
157
+ name: 'help',
158
+ alias: 'h',
159
+ type: Boolean,
160
+ description: 'Help text'
161
+ },
162
+ {
163
+ name: 'clear',
164
+ alias: 'c',
165
+ type: Boolean,
166
+ description: 'Clear the currently stored config'
167
+ }
168
+ ]
169
+
138
170
  const auditOptionDefinitions = [
139
171
  {
140
172
  name: 'application-id',
@@ -291,6 +323,7 @@ const mainUsageGuide = commandLineUsage([
291
323
  header: i18n.__('constantsCommands'),
292
324
  content: [
293
325
  { name: i18n.__('authName'), summary: i18n.__('helpAuthSummary') },
326
+ { name: i18n.__('scanName'), summary: i18n.__('helpScanSummary') },
294
327
  { name: i18n.__('lambdaName'), summary: i18n.__('helpLambdaSummary') },
295
328
  { name: i18n.__('versionName'), summary: i18n.__('helpVersionSummary') },
296
329
  { name: i18n.__('configName'), summary: i18n.__('helpConfigSummary') },
@@ -309,6 +342,8 @@ module.exports = {
309
342
  mainUsageGuide,
310
343
  mainDefinition,
311
344
  scanOptionDefinitions,
312
- auditOptionDefinitions
345
+ auditOptionDefinitions,
346
+ authOptionDefinitions,
347
+ configOptionDefinitions
313
348
  }
314
349
  }
package/src/index.ts CHANGED
@@ -7,8 +7,11 @@ import constants from './constants'
7
7
  import { APP_NAME, APP_VERSION } from './constants/constants'
8
8
  import { processLambda } from './lambda/lambda'
9
9
  import { localConfig } from './utils/getConfig'
10
- import findLatestCLIVersion from './common/findLatestCLIVersion'
11
- import { approximateCommandOnError } from './common/errorHandling'
10
+ import {
11
+ findLatestCLIVersion,
12
+ isCorrectNodeVersion
13
+ } from './common/versionChecker'
14
+ import { findCommandOnError } from './common/errorHandling'
12
15
 
13
16
  const {
14
17
  commandLineDefinitions: { mainUsageGuide, mainDefinition }
@@ -31,49 +34,73 @@ const getMainOption = () => {
31
34
  }
32
35
 
33
36
  const start = async () => {
34
- const { mainOptions, argv: argvMain } = getMainOption()
35
- const command =
36
- mainOptions.command != undefined ? mainOptions.command.toLowerCase() : ''
37
- if (
38
- command === 'version' ||
39
- argvMain.includes('--v') ||
40
- argvMain.includes('--version')
41
- ) {
42
- console.log(APP_VERSION)
43
- return
44
- }
37
+ if (await isCorrectNodeVersion(process.version)) {
38
+ const { mainOptions, argv: argvMain } = getMainOption()
39
+ const command =
40
+ mainOptions.command != undefined ? mainOptions.command.toLowerCase() : ''
41
+ if (
42
+ command === 'version' ||
43
+ argvMain.includes('--v') ||
44
+ argvMain.includes('--version')
45
+ ) {
46
+ console.log(APP_VERSION)
47
+ await findLatestCLIVersion()
48
+ return
49
+ }
45
50
 
46
- await findLatestCLIVersion()
51
+ // @ts-ignore
52
+ config.set('numOfRuns', config.get('numOfRuns') + 1)
47
53
 
48
- if (command === 'config') {
49
- return processConfig(argvMain, config)
50
- }
54
+ // @ts-ignore
55
+ if (config.get('numOfRuns') >= 5) {
56
+ // @ts-ignore
57
+ await findLatestCLIVersion()
58
+ config.set('numOfRuns', 0)
59
+ }
51
60
 
52
- if (command === 'auth') {
53
- return await processAuth(config)
54
- }
61
+ if (command === 'config') {
62
+ return processConfig(argvMain, config)
63
+ }
55
64
 
56
- if (command === 'lambda') {
57
- return await processLambda(argvMain)
58
- }
65
+ if (command === 'auth') {
66
+ return await processAuth(argvMain, config)
67
+ }
59
68
 
60
- if (command === 'scan') {
61
- return await processScan(argvMain)
62
- }
69
+ if (command === 'lambda') {
70
+ return await processLambda(argvMain)
71
+ }
63
72
 
64
- if (command === 'audit') {
65
- return await processAudit(argvMain)
66
- }
73
+ if (command === 'scan') {
74
+ return await processScan(argvMain)
75
+ }
76
+
77
+ if (command === 'audit') {
78
+ return await processAudit(argvMain)
79
+ }
67
80
 
68
- if (
69
- command === 'help' ||
70
- argvMain.includes('--help') ||
71
- Object.keys(mainOptions).length === 0
72
- ) {
73
- console.log(mainUsageGuide)
81
+ if (
82
+ command === 'help' ||
83
+ argvMain.includes('--help') ||
84
+ Object.keys(mainOptions).length === 0
85
+ ) {
86
+ console.log(mainUsageGuide)
87
+ } else if (mainOptions._unknown !== undefined) {
88
+ const foundCommand = findCommandOnError(mainOptions._unknown)
89
+
90
+ foundCommand
91
+ ? console.log(
92
+ `Unknown Command: Did you mean "${foundCommand}"? \nUse "${foundCommand} --help" for the full list of options`
93
+ )
94
+ : console.log(
95
+ `Unknown Command: ${command} \nUse --help for the full list`
96
+ )
97
+ } else {
98
+ console.log(`Unknown Command: ${command} \nUse --help for the full list`)
99
+ }
100
+ process.exit(9)
74
101
  } else {
75
102
  console.log(
76
- 'Unknown Command: ' + command + ' \nUse --help for the full list'
103
+ 'Contrast supports Node versions >=16.13.2 <17. Please use one of those versions.'
77
104
  )
78
105
  process.exit(9)
79
106
  }
@@ -0,0 +1,17 @@
1
+ import { getHttpClient } from '../utils/commonApi'
2
+
3
+ export default function generateSbom(config: any) {
4
+ const client = getHttpClient(config)
5
+ return client
6
+ .getSbom(config)
7
+ .then((res: { statusCode: number; body: any }) => {
8
+ if (res.statusCode === 200) {
9
+ return res.body
10
+ } else {
11
+ console.log('Unable to retrieve Software Bill of Materials (SBOM)')
12
+ }
13
+ })
14
+ .catch((err: any) => {
15
+ console.log(err)
16
+ })
17
+ }
package/src/scan/help.js CHANGED
@@ -21,14 +21,16 @@ const scanUsageGuide = commandLineUsage([
21
21
  optionList: constants.commandLineDefinitions.scanOptionDefinitions,
22
22
  hide: [
23
23
  'project-id',
24
- 'language',
25
24
  'organization-id',
26
25
  'api-key',
27
26
  'authorization',
28
27
  'host',
29
28
  'proxy',
29
+ 'help',
30
30
  'ff',
31
- 'ignore-cert-errors'
31
+ 'ignore-cert-errors',
32
+ 'verbose',
33
+ 'debug'
32
34
  ]
33
35
  },
34
36
  {
@@ -23,6 +23,7 @@ const createProjectId = async (config, client) => {
23
23
  }
24
24
  if (res.statusCode === 403) {
25
25
  console.log(i18n.__('permissionsError'))
26
+ process.exit(1)
26
27
  return
27
28
  }
28
29
 
@@ -1,15 +1,14 @@
1
1
  const fs = require('fs')
2
2
 
3
- const writeResultsToFile = (responseBody, name = 'results.sarif') => {
4
- fs.writeFile(name, JSON.stringify(responseBody, null, 2), err => {
5
- if (err) {
6
- console.log('Error writing Scan Results to file')
7
- } else {
8
- console.log(`Scan Results saved to ${name}`)
9
- }
10
- })
3
+ const writeResultsToFile = async (responseBody, name = 'results.sarif') => {
4
+ try {
5
+ fs.writeFileSync(name, JSON.stringify(responseBody, null, 2))
6
+ console.log(`Scan Results saved to ${name}`)
7
+ } catch (err) {
8
+ console.log('Error writing Scan Results to file')
9
+ }
11
10
  }
12
11
 
13
12
  module.exports = {
14
- writeResultsToFile
13
+ writeResultsToFile: writeResultsToFile
15
14
  }
package/src/scan/scan.js CHANGED
@@ -58,6 +58,7 @@ const sendScan = async config => {
58
58
  )
59
59
  if (res.statusCode === 403) {
60
60
  console.log(i18n.__('permissionsError'))
61
+ process.exit(1)
61
62
  }
62
63
  console.log(i18n.__('genericServiceError', res.statusCode))
63
64
  process.exit(1)
@@ -94,6 +95,16 @@ const formatScanOutput = (overview, results) => {
94
95
  console.log(`\t ${count}. ${lineInfo}`)
95
96
  count++
96
97
  })
98
+
99
+ if (entry?.cwe && entry?.cwe.length > 0) {
100
+ formatLinks('cwe', entry.cwe)
101
+ }
102
+ if (entry?.reference && entry?.reference.length > 0) {
103
+ formatLinks('reference', entry.reference)
104
+ }
105
+ if (entry?.owasp && entry?.owasp.length > 0) {
106
+ formatLinks('owasp', entry.owasp)
107
+ }
97
108
  console.log(chalk.bold('How to fix:'))
98
109
  console.log(entry.recommendation)
99
110
  console.log()
@@ -106,7 +117,9 @@ const formatScanOutput = (overview, results) => {
106
117
  overview.low +
107
118
  overview.note
108
119
 
109
- console.log(chalk.bold(`Found ${totalVulnerabilities} vulnerabilities`))
120
+ let vulMessage =
121
+ totalVulnerabilities === 1 ? `vulnerability` : `vulnerabilities`
122
+ console.log(chalk.bold(`Found ${totalVulnerabilities} ${vulMessage}`))
110
123
  console.log(
111
124
  i18n.__(
112
125
  'foundDetailedVulnerabilities',
@@ -120,6 +133,14 @@ const formatScanOutput = (overview, results) => {
120
133
  }
121
134
  }
122
135
 
136
+ const formatLinks = (objName, entry) => {
137
+ console.log(chalk.bold(objName + ':'))
138
+ entry.forEach(link => {
139
+ console.log(link)
140
+ })
141
+ console.log()
142
+ }
143
+
123
144
  const getGroups = content => {
124
145
  const groupTypeSet = new Set(content.map(({ ruleId }) => ruleId))
125
146
  let groupTypeResults = []
@@ -127,12 +148,18 @@ const getGroups = content => {
127
148
  let groupResultsObj = {
128
149
  ruleId: groupName,
129
150
  lineInfoSet: new Set(),
151
+ cwe: '',
152
+ owasp: '',
153
+ reference: '',
130
154
  recommendation: '',
131
155
  severity: ''
132
156
  }
133
157
  content.forEach(resultEntry => {
134
158
  if (resultEntry.ruleId === groupName) {
135
159
  groupResultsObj.severity = resultEntry.severity
160
+ groupResultsObj.cwe = resultEntry.cwe
161
+ groupResultsObj.owasp = resultEntry.owasp
162
+ groupResultsObj.reference = resultEntry.reference
136
163
  groupResultsObj.recommendation = resultEntry.recommendation
137
164
  ? stripMustacheTags(resultEntry.recommendation)
138
165
  : 'No Recommendations Data Found'
@@ -163,5 +190,6 @@ module.exports = {
163
190
  allowedFileTypes: allowedFileTypes,
164
191
  isFileAllowed: isFileAllowed,
165
192
  stripMustacheTags: stripMustacheTags,
166
- formatScanOutput: formatScanOutput
193
+ formatScanOutput: formatScanOutput,
194
+ formatLinks: formatLinks
167
195
  }
@@ -2,14 +2,34 @@ const paramHandler = require('../utils/paramsUtil/paramHandler')
2
2
  const constants = require('../../src/constants.js')
3
3
  const parsedCLIOptions = require('../../src/utils/parsedCLIOptions')
4
4
  const path = require('path')
5
+ const {
6
+ supportedLanguages
7
+ } = require('../audit/languageAnalysisEngine/constants')
8
+ const i18n = require('i18n')
9
+ const { scanUsageGuide } = require('./help')
5
10
 
6
11
  const getScanConfig = argv => {
7
12
  let scanParams = parsedCLIOptions.getCommandLineArgsCustom(
8
13
  argv,
9
14
  constants.commandLineDefinitions.scanOptionDefinitions
10
15
  )
16
+
17
+ if (scanParams.help) {
18
+ printHelpMessage()
19
+ process.exit(0)
20
+ }
21
+
11
22
  const paramsAuth = paramHandler.getAuth(scanParams)
12
23
 
24
+ if (scanParams.language) {
25
+ scanParams.language = scanParams.language.toUpperCase()
26
+ if (!Object.values(supportedLanguages).includes(scanParams.language)) {
27
+ console.log(`Did not recognise --language ${scanParams.language}`)
28
+ console.log(i18n.__('constantsHowToRunDev3'))
29
+ process.exit(0)
30
+ }
31
+ }
32
+
13
33
  // if no name, take the full file path and use it as the project name
14
34
  if (!scanParams.name && scanParams.file) {
15
35
  scanParams.name = getFileName(scanParams.file)
@@ -23,7 +43,12 @@ const getFileName = file => {
23
43
  return file.split(path.sep).pop()
24
44
  }
25
45
 
46
+ const printHelpMessage = () => {
47
+ console.log(scanUsageGuide)
48
+ }
49
+
26
50
  module.exports = {
27
51
  getScanConfig,
28
- getFileName
52
+ getFileName,
53
+ printHelpMessage
29
54
  }
@@ -9,6 +9,7 @@ const scan = require('./scan')
9
9
  const scanResults = require('./scanResults')
10
10
  const autoDetection = require('./autoDetection')
11
11
  const fileFunctions = require('./fileUtils')
12
+ const { performance } = require('perf_hooks')
12
13
 
13
14
  const getTimeout = config => {
14
15
  if (config.timeout) {
@@ -25,7 +26,7 @@ const fileAndLanguageLogic = async configToUse => {
25
26
  if (configToUse.file) {
26
27
  if (!fileFunctions.fileExists(configToUse.file)) {
27
28
  console.log(i18n.__('fileNotExist'))
28
- process.exit(0)
29
+ process.exit(1)
29
30
  }
30
31
  return configToUse
31
32
  } else {
@@ -36,6 +37,7 @@ const fileAndLanguageLogic = async configToUse => {
36
37
  }
37
38
 
38
39
  const startScan = async configToUse => {
40
+ const startTime = performance.now()
39
41
  await fileAndLanguageLogic(configToUse)
40
42
 
41
43
  if (!configToUse.projectId) {
@@ -46,7 +48,7 @@ const startScan = async configToUse => {
46
48
  const codeArtifactId = await scan.sendScan(configToUse)
47
49
 
48
50
  if (!configToUse.ff) {
49
- const startScanSpinner = returnOra('Contrast Scan started')
51
+ const startScanSpinner = returnOra('🚀 Contrast Scan started')
50
52
  startSpinner(startScanSpinner)
51
53
  const scanDetail = await scanResults.returnScanResults(
52
54
  configToUse,
@@ -58,7 +60,12 @@ const startScan = async configToUse => {
58
60
  configToUse,
59
61
  scanDetail.id
60
62
  )
63
+ const endTime = performance.now()
64
+ const scanDurationMs = endTime - startTime
61
65
  succeedSpinner(startScanSpinner, 'Contrast Scan complete')
66
+ console.log(
67
+ `----- Scan completed in ${(scanDurationMs / 1000).toFixed(2)}s -----`
68
+ )
62
69
  const projectOverview = await scanResults.returnScanProjectById(configToUse)
63
70
  return { projectOverview, scanDetail, scanResultsInstances }
64
71
  }
@@ -2,6 +2,7 @@ const commonApi = require('../utils/commonApi')
2
2
  const requestUtils = require('../../src/utils/requestUtils')
3
3
  const oraFunctions = require('../utils/oraWrapper')
4
4
  const _ = require('lodash')
5
+ const i18n = require('i18n')
5
6
 
6
7
  const getScanId = async (config, codeArtifactId, client) => {
7
8
  return client
@@ -48,6 +49,15 @@ const returnScanResults = async (
48
49
  complete = true
49
50
  oraFunctions.failSpinner(startScanSpinner, 'Contrast Scan Failed.')
50
51
  console.log(result.body.errorMessage)
52
+ if (
53
+ result.body.errorMessage ===
54
+ 'Unable to determine language for code artifact'
55
+ ) {
56
+ console.log(
57
+ 'Try scanning again using --language param. ',
58
+ i18n.__('scanOptionsLanguageSummary')
59
+ )
60
+ }
51
61
  process.exit(1)
52
62
  }
53
63
  }
@@ -6,6 +6,7 @@ type ContrastConfOptions = Partial<{
6
6
  apiKey: string
7
7
  orgId: string
8
8
  authHeader: string
9
+ numOfRuns: number
9
10
  }>
10
11
 
11
12
  type ContrastConf = Conf<ContrastConfOptions>
@@ -15,6 +16,7 @@ const localConfig = (name: string, version: string) => {
15
16
  configName: name
16
17
  })
17
18
  config.set('version', version)
19
+
18
20
  if (!config.has('host')) {
19
21
  config.set('host', 'https://ce.contrastsecurity.com/')
20
22
  }
@@ -8,7 +8,7 @@ function sendRequest({ options, method = 'put' }) {
8
8
  }
9
9
 
10
10
  const millisToSeconds = millis => {
11
- return ((millis % 60000) / 1000).toFixed(0)
11
+ return (millis / 1000).toFixed(0)
12
12
  }
13
13
 
14
14
  const sleep = ms => {
@@ -0,0 +1,19 @@
1
+ const { SARIF_FILE } = require('../constants/constants')
2
+ const commonApi = require('./commonApi')
3
+ const saveResults = require('../scan/saveResults')
4
+ const i18n = require('i18n')
5
+
6
+ const saveScanFile = async (config, scanResults) => {
7
+ if (config.save === null || config.save.toUpperCase() === SARIF_FILE) {
8
+ const scanId = scanResults.scanDetail.id
9
+ const client = commonApi.getHttpClient(config)
10
+ const rawResults = await client.getSpecificScanResultSarif(config, scanId)
11
+ await saveResults.writeResultsToFile(rawResults?.body)
12
+ } else {
13
+ console.log(i18n.__('scanNoFiletypeSpecifiedForSave'))
14
+ }
15
+ }
16
+
17
+ module.exports = {
18
+ saveScanFile: saveScanFile
19
+ }
@@ -1,27 +0,0 @@
1
- import latestVersion from 'latest-version'
2
- import { APP_VERSION } from '../constants/constants'
3
- import boxen from 'boxen'
4
- import chalk from 'chalk'
5
- import semver from 'semver'
6
-
7
- export default async function findLatestCLIVersion() {
8
- const latestCLIVersion = await latestVersion('@contrast/contrast')
9
-
10
- if (semver.lt(APP_VERSION, latestCLIVersion)) {
11
- const updateAvailableMessage = `Update available ${chalk.yellow(
12
- APP_VERSION
13
- )} → ${chalk.green(latestCLIVersion)}`
14
-
15
- const updateAvailableCommand = `Run ${chalk.cyan(
16
- 'npm i @contrast/contrast'
17
- )} to update`
18
-
19
- console.log(
20
- boxen(`${updateAvailableMessage}\n${updateAvailableCommand}`, {
21
- margin: 1,
22
- padding: 1,
23
- align: 'center'
24
- })
25
- )
26
- }
27
- }