@contrast/contrast 1.0.3 → 1.0.6

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 (107) hide show
  1. package/.prettierignore +4 -0
  2. package/README.md +20 -14
  3. package/dist/audit/autodetection/autoDetectLanguage.js +32 -0
  4. package/dist/audit/catalogueApplication/catalogueApplication.js +2 -11
  5. package/dist/audit/languageAnalysisEngine/{langugageAnalysisFactory.js → languageAnalysisFactory.js} +6 -14
  6. package/dist/audit/languageAnalysisEngine/reduceIdentifiedLanguages.js +29 -0
  7. package/dist/audit/languageAnalysisEngine/report/commonReportingFunctions.js +101 -234
  8. package/dist/audit/languageAnalysisEngine/report/models/reportLibraryModel.js +19 -0
  9. package/dist/audit/languageAnalysisEngine/report/models/reportListModel.js +24 -0
  10. package/dist/audit/languageAnalysisEngine/report/models/reportOutputModel.js +24 -0
  11. package/dist/audit/languageAnalysisEngine/report/models/reportSeverityModel.js +12 -0
  12. package/dist/audit/languageAnalysisEngine/report/models/severityCountModel.js +13 -0
  13. package/dist/audit/languageAnalysisEngine/report/reportingFeature.js +24 -129
  14. package/dist/audit/languageAnalysisEngine/report/utils/reportUtils.js +99 -0
  15. package/dist/audit/languageAnalysisEngine/sendSnapshot.js +2 -14
  16. package/dist/commands/audit/auditConfig.js +8 -2
  17. package/dist/commands/audit/auditController.js +14 -5
  18. package/dist/commands/scan/processScan.js +10 -6
  19. package/dist/commands/scan/sca/scaAnalysis.js +49 -0
  20. package/dist/common/HTTPClient.js +18 -26
  21. package/dist/common/errorHandling.js +7 -17
  22. package/dist/common/versionChecker.js +14 -12
  23. package/dist/constants/constants.js +24 -2
  24. package/dist/constants/lambda.js +3 -1
  25. package/dist/constants/locales.js +42 -42
  26. package/dist/constants.js +25 -1
  27. package/dist/index.js +2 -2
  28. package/dist/lambda/help.js +22 -14
  29. package/dist/lambda/lambda.js +6 -0
  30. package/dist/scaAnalysis/common/formatMessage.js +19 -0
  31. package/dist/scaAnalysis/common/treeUpload.js +29 -0
  32. package/dist/scaAnalysis/go/goAnalysis.js +17 -0
  33. package/dist/scaAnalysis/go/goParseDeps.js +158 -0
  34. package/dist/scaAnalysis/go/goReadDepFile.js +23 -0
  35. package/dist/scaAnalysis/java/analysis.js +108 -0
  36. package/dist/scaAnalysis/java/index.js +18 -0
  37. package/dist/scaAnalysis/java/javaBuildDepsParser.js +339 -0
  38. package/dist/scan/autoDetection.js +46 -1
  39. package/dist/scan/fileUtils.js +73 -1
  40. package/dist/scan/formatScanOutput.js +215 -0
  41. package/dist/scan/help.js +3 -1
  42. package/dist/scan/models/groupedResultsModel.js +11 -0
  43. package/dist/scan/models/resultContentModel.js +2 -0
  44. package/dist/scan/models/scanResultsModel.js +11 -0
  45. package/dist/scan/scan.js +27 -126
  46. package/dist/scan/scanConfig.js +1 -1
  47. package/dist/scan/scanController.js +11 -5
  48. package/dist/scan/scanResults.js +15 -19
  49. package/dist/utils/getConfig.js +3 -0
  50. package/dist/utils/oraWrapper.js +5 -1
  51. package/package.json +3 -2
  52. package/src/audit/autodetection/autoDetectLanguage.ts +40 -0
  53. package/src/audit/catalogueApplication/catalogueApplication.js +4 -16
  54. package/src/audit/languageAnalysisEngine/{langugageAnalysisFactory.js → languageAnalysisFactory.js} +11 -21
  55. package/src/audit/languageAnalysisEngine/reduceIdentifiedLanguages.js +72 -0
  56. package/src/audit/languageAnalysisEngine/report/commonReportingFunctions.ts +204 -0
  57. package/src/audit/languageAnalysisEngine/report/models/reportLibraryModel.ts +30 -0
  58. package/src/audit/languageAnalysisEngine/report/models/reportListModel.ts +32 -0
  59. package/src/audit/languageAnalysisEngine/report/models/reportOutputModel.ts +29 -0
  60. package/src/audit/languageAnalysisEngine/report/models/reportSeverityModel.ts +13 -0
  61. package/src/audit/languageAnalysisEngine/report/models/severityCountModel.ts +16 -0
  62. package/src/audit/languageAnalysisEngine/report/reportingFeature.ts +56 -0
  63. package/src/audit/languageAnalysisEngine/report/utils/reportUtils.ts +116 -0
  64. package/src/audit/languageAnalysisEngine/sendSnapshot.js +2 -22
  65. package/src/commands/audit/auditConfig.ts +12 -3
  66. package/src/commands/audit/auditController.ts +20 -5
  67. package/src/commands/audit/processAudit.ts +3 -0
  68. package/src/commands/scan/processScan.js +13 -9
  69. package/src/commands/scan/sca/scaAnalysis.js +75 -0
  70. package/src/common/HTTPClient.js +31 -38
  71. package/src/common/errorHandling.ts +7 -25
  72. package/src/common/versionChecker.ts +24 -22
  73. package/src/constants/constants.js +24 -2
  74. package/src/constants/lambda.js +3 -1
  75. package/src/constants/locales.js +47 -56
  76. package/src/constants.js +29 -1
  77. package/src/index.ts +2 -3
  78. package/src/lambda/help.ts +22 -14
  79. package/src/lambda/lambda.ts +8 -0
  80. package/src/scaAnalysis/common/formatMessage.js +20 -0
  81. package/src/scaAnalysis/common/treeUpload.js +30 -0
  82. package/src/scaAnalysis/go/goAnalysis.js +20 -0
  83. package/src/scaAnalysis/go/goParseDeps.js +203 -0
  84. package/src/scaAnalysis/go/goReadDepFile.js +32 -0
  85. package/src/scaAnalysis/java/analysis.js +143 -0
  86. package/src/scaAnalysis/java/index.js +21 -0
  87. package/src/scaAnalysis/java/javaBuildDepsParser.js +404 -0
  88. package/src/scan/autoDetection.js +54 -1
  89. package/src/scan/fileUtils.js +91 -1
  90. package/src/scan/formatScanOutput.ts +250 -0
  91. package/src/scan/help.js +3 -1
  92. package/src/scan/models/groupedResultsModel.ts +20 -0
  93. package/src/scan/models/resultContentModel.ts +86 -0
  94. package/src/scan/models/scanResultsModel.ts +52 -0
  95. package/src/scan/scan.ts +63 -0
  96. package/src/scan/scanConfig.js +1 -1
  97. package/src/scan/scanController.js +15 -7
  98. package/src/scan/scanResults.js +21 -18
  99. package/src/utils/getConfig.ts +10 -0
  100. package/src/utils/oraWrapper.js +6 -1
  101. package/dist/audit/languageAnalysisEngine/report/checkIgnoreDevDep.js +0 -17
  102. package/dist/audit/languageAnalysisEngine/report/newReportingFeature.js +0 -81
  103. package/src/audit/languageAnalysisEngine/report/checkIgnoreDevDep.js +0 -27
  104. package/src/audit/languageAnalysisEngine/report/commonReportingFunctions.js +0 -303
  105. package/src/audit/languageAnalysisEngine/report/newReportingFeature.js +0 -124
  106. package/src/audit/languageAnalysisEngine/report/reportingFeature.js +0 -190
  107. package/src/scan/scan.js +0 -195
@@ -4,6 +4,7 @@ const requestUtils = require('../../src/utils/requestUtils');
4
4
  const oraFunctions = require('../utils/oraWrapper');
5
5
  const _ = require('lodash');
6
6
  const i18n = require('i18n');
7
+ const oraWrapper = require('../utils/oraWrapper');
7
8
  const getScanId = async (config, codeArtifactId, client) => {
8
9
  return client
9
10
  .getScanId(config, codeArtifactId)
@@ -40,12 +41,16 @@ const returnScanResults = async (config, codeArtifactId, timeout, startScanSpinn
40
41
  }
41
42
  if (result.body.status === 'FAILED') {
42
43
  complete = true;
43
- oraFunctions.failSpinner(startScanSpinner, 'Contrast Scan Failed.');
44
- console.log(result.body.errorMessage);
45
- if (result.body.errorMessage ===
44
+ if (config.debug) {
45
+ oraFunctions.failSpinner(startScanSpinner, i18n.__('scanNotCompleted', 'https://docs.contrastsecurity.com/en/binary-package-preparation.html'));
46
+ }
47
+ if (result?.body?.errorMessage ===
46
48
  'Unable to determine language for code artifact') {
49
+ console.log(result.body.errorMessage);
47
50
  console.log('Try scanning again using --language param. ', i18n.__('scanOptionsLanguageSummary'));
48
51
  }
52
+ oraWrapper.stopSpinner(startScanSpinner);
53
+ console.log('Contrast Scan Finished');
49
54
  process.exit(1);
50
55
  }
51
56
  }
@@ -64,30 +69,21 @@ const returnScanResultsInstances = async (config, scanId) => {
64
69
  try {
65
70
  result = await client.getScanResultsInstances(config, scanId);
66
71
  if (JSON.stringify(result.statusCode) == 200) {
67
- return result.body;
72
+ return { body: result.body, statusCode: result.statusCode };
68
73
  }
69
- }
70
- catch (e) {
71
- console.log(e.message.toString());
72
- }
73
- };
74
- const returnScanProjectById = async (config) => {
75
- const client = commonApi.getHttpClient(config);
76
- let result;
77
- try {
78
- result = await client.getScanProjectById(config);
79
- if (JSON.stringify(result.statusCode) == 200) {
80
- return result.body;
74
+ if (JSON.stringify(result.statusCode) == 503) {
75
+ return { statusCode: result.statusCode };
81
76
  }
82
77
  }
83
78
  catch (e) {
84
- console.log(e.message.toString());
79
+ if (config.debug) {
80
+ console.log(e.message.toString());
81
+ }
85
82
  }
86
83
  };
87
84
  module.exports = {
88
85
  getScanId: getScanId,
89
86
  returnScanResults: returnScanResults,
90
87
  pollScanResults: pollScanResults,
91
- returnScanResultsInstances: returnScanResultsInstances,
92
- returnScanProjectById: returnScanProjectById
88
+ returnScanResultsInstances: returnScanResultsInstances
93
89
  };
@@ -10,6 +10,9 @@ const localConfig = (name, version) => {
10
10
  configName: name
11
11
  });
12
12
  config.set('version', version);
13
+ if (process.env.CONTRAST_CODSEC_DISABLE_UPDATE_MESSAGE) {
14
+ config.set('updateMessageHidden', JSON.parse(process.env.CONTRAST_CODSEC_DISABLE_UPDATE_MESSAGE.toLowerCase()));
15
+ }
13
16
  if (!config.has('host')) {
14
17
  config.set('host', 'https://ce.contrastsecurity.com/');
15
18
  }
@@ -6,6 +6,9 @@ const returnOra = text => {
6
6
  const startSpinner = spinner => {
7
7
  spinner.start();
8
8
  };
9
+ const stopSpinner = spinner => {
10
+ spinner.stop();
11
+ };
9
12
  const succeedSpinner = (spinner, text) => {
10
13
  spinner.succeed(text);
11
14
  };
@@ -16,5 +19,6 @@ module.exports = {
16
19
  returnOra,
17
20
  startSpinner,
18
21
  succeedSpinner,
19
- failSpinner
22
+ failSpinner,
23
+ stopSpinner
20
24
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/contrast",
3
- "version": "1.0.3",
3
+ "version": "1.0.6",
4
4
  "description": "Contrast Security's command line tool",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -35,7 +35,7 @@
35
35
  "dev": "npx ts-node src/index.ts"
36
36
  },
37
37
  "engines": {
38
- "node": ">=16.13.2 <17"
38
+ "node": ">=16"
39
39
  },
40
40
  "dependencies": {
41
41
  "@aws-sdk/client-iam": "^3.78.0",
@@ -45,6 +45,7 @@
45
45
  "bluebird": "^3.7.2",
46
46
  "boxen": "5.1.2",
47
47
  "chalk": "4.1.2",
48
+ "cli-table3": "^0.6.2",
48
49
  "command-line-args": "^5.2.1",
49
50
  "command-line-usage": "^6.1.3",
50
51
  "conf": "^10.1.2",
@@ -0,0 +1,40 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import i18n from 'i18n'
3
+ import {
4
+ reduceIdentifiedLanguages,
5
+ deduceLanguage
6
+ } from '../languageAnalysisEngine/reduceIdentifiedLanguages'
7
+
8
+ import { getProjectRootFilenames } from '../languageAnalysisEngine/getProjectRootFilenames'
9
+
10
+ export function identifyLanguages(config: any) {
11
+ const { projectPath } = config
12
+ const projectRootFilenames = getProjectRootFilenames(projectPath)
13
+
14
+ const identifiedLanguages = projectRootFilenames.reduce(
15
+ (accumulator: any, filename: string) => {
16
+ const deducedLanguages = deduceLanguage(filename)
17
+ return [...accumulator, ...deducedLanguages]
18
+ },
19
+ []
20
+ )
21
+
22
+ if (Object.keys(identifiedLanguages).length === 0) {
23
+ throw new Error(i18n.__('languageAnalysisNoLanguage', projectPath))
24
+ }
25
+
26
+ return reduceIdentifiedLanguages(identifiedLanguages)
27
+ }
28
+
29
+ export function determineProjectLanguage(
30
+ reducedLanguages: Record<string, string>
31
+ ) {
32
+ const reducedLanguagesKeys = Object.keys(reducedLanguages)
33
+ if (reducedLanguagesKeys.length === 1) {
34
+ return reducedLanguagesKeys[0]
35
+ } else {
36
+ throw new Error(
37
+ 'Detected multiple languages. Please specify a single language using --language'
38
+ )
39
+ }
40
+ }
@@ -1,21 +1,8 @@
1
1
  const i18n = require('i18n')
2
2
  const { getHttpClient, handleResponseErrors } = require('../../utils/commonApi')
3
3
 
4
- const locationOfApp = (config, appId) => {
5
- return `${config.host}/Contrast/static/ng/index.html#/${config.organizationId}/applications/${appId}`
6
- }
7
-
8
- const displaySuccessMessage = (config, appId) => {
9
- console.log(
10
- '\n **************************' +
11
- i18n.__('successHeader') +
12
- '************************** \n'
13
- )
14
- console.log('\n' + i18n.__('catalogueSuccessCommand') + appId + '\n')
15
- console.log(locationOfApp(config, appId))
16
- console.log(
17
- '\n *********************************************************** \n'
18
- )
4
+ const displaySuccessMessage = () => {
5
+ console.log(i18n.__('catalogueSuccessCommand'))
19
6
  }
20
7
 
21
8
  const catalogueApplication = async config => {
@@ -25,9 +12,10 @@ const catalogueApplication = async config => {
25
12
  .catalogueCommand(config)
26
13
  .then(res => {
27
14
  if (res.statusCode === 201) {
28
- displaySuccessMessage(config, res.body.application.app_id)
15
+ //displaySuccessMessage(config, res.body.application.app_id)
29
16
  appId = res.body.application.app_id
30
17
  } else {
18
+ // console.log(res.statusCode)
31
19
  handleResponseErrors(res, 'catalogue')
32
20
  }
33
21
  })
@@ -10,13 +10,17 @@ const pythonAE = require('../pythonAnalysisEngine')
10
10
  const phpAE = require('../phpAnalysisEngine')
11
11
  const goAE = require('../goAnalysisEngine')
12
12
  const { vulnerabilityReport } = require('./report/reportingFeature')
13
- const { vulnReportWithoutDevDep } = require('./report/newReportingFeature')
14
- const { checkDevDeps } = require('./report/checkIgnoreDevDep')
15
13
  const { newSendSnapShot } = require('../languageAnalysisEngine/sendSnapshot')
16
14
  const fs = require('fs')
17
15
  const chalk = require('chalk')
18
16
  const saveFile = require('../../commands/audit/saveFile').default
19
17
  const generateSbom = require('../../sbom/generateSbom').default
18
+ const {
19
+ failSpinner,
20
+ returnOra,
21
+ startSpinner,
22
+ succeedSpinner
23
+ } = require('../../utils/oraWrapper')
20
24
 
21
25
  module.exports = exports = (err, analysis) => {
22
26
  const { identifiedLanguageInfo } = analysis.languageAnalysis
@@ -47,29 +51,15 @@ module.exports = exports = (err, analysis) => {
47
51
  return process.exit(5)
48
52
  }
49
53
 
50
- console.log('\n **************CONTRAST OSS ANALYSIS BEGINS**************')
54
+ const reportSpinner = returnOra(i18n.__('auditSCAAnalysisBegins'))
55
+ startSpinner(reportSpinner)
51
56
  const snapshotResponse = await newSendSnapShot(analysis, catalogueAppId)
57
+ succeedSpinner(reportSpinner, 'Contrast SCA analysis complete')
52
58
 
53
- if (config.report) {
54
- const ignoreDevUrl = await checkDevDeps(config)
55
- if (ignoreDevUrl) {
56
- await vulnReportWithoutDevDep(
57
- analysis,
58
- catalogueAppId,
59
- snapshotResponse.id,
60
- config
61
- )
62
- } else {
63
- await vulnerabilityReport(analysis, catalogueAppId, config)
64
- }
65
- }
59
+ await vulnerabilityReport(analysis, catalogueAppId, snapshotResponse.id)
66
60
 
67
61
  //should be moved to processAudit.ts once promises implemented
68
62
  await auditSave(config)
69
-
70
- console.log(
71
- '\n ***************CONTRAST OSS ANALYSIS COMPLETE************** \n'
72
- )
73
63
  }
74
64
 
75
65
  if (identifiedLanguageInfo.language === DOTNET) {
@@ -120,7 +110,7 @@ async function auditSave(config) {
120
110
  } else {
121
111
  console.log(i18n.__('auditBadFiletypeSpecifiedForSave'))
122
112
  }
123
- } else {
113
+ } else if (config.save === null) {
124
114
  console.log(i18n.__('auditNoFiletypeSpecifiedForSave'))
125
115
  }
126
116
  }
@@ -28,6 +28,77 @@ const isRubyLockFilename = filename => filename === 'Gemfile.lock'
28
28
  const isPipfileLockLockFilename = filename => filename === 'Pipfile.lock'
29
29
  const isGoProjectFilename = filename => filename === 'go.mod'
30
30
 
31
+ const deduceLanguageScaAnalysis = filenames => {
32
+ const deducedLanguages = []
33
+ let language = ''
34
+
35
+ filenames.forEach(filename => {
36
+ // Check for project filenames...
37
+ if (isJavaMavenProjectFilename(filename)) {
38
+ deducedLanguages.push(filename)
39
+ language = JAVA
40
+ }
41
+
42
+ if (isJavaGradleProjectFilename(filename)) {
43
+ deducedLanguages.push(filename)
44
+ language = JAVA
45
+ }
46
+
47
+ if (isNodeProjectFilename(filename)) {
48
+ deducedLanguages.push(filename)
49
+ language = NODE
50
+ }
51
+ //
52
+ // if (isDotNetProjectFilename(filename)) {
53
+ // deducedLanguages.push({language: DOTNET, projectFilename: filename})
54
+ // }
55
+ //
56
+ // if (isRubyProjectFilename(filename)) {
57
+ // deducedLanguages.push({language: RUBY, projectFilename: filename})
58
+ // }
59
+ //
60
+ // if (isPythonProjectFilename(filename)) {
61
+ // deducedLanguages.push({language: PYTHON, projectFilename: filename})
62
+ // }
63
+ //
64
+ // if (isPhpProjectFilename(filename)) {
65
+ // deducedLanguages.push({language: PHP, projectFilename: filename})
66
+ // }
67
+ //
68
+ // // Check for lock filenames...
69
+ // if (isDotNetLockFilename(filename)) {
70
+ // deducedLanguages.push({language: DOTNET, lockFilename: filename})
71
+ // }
72
+ //
73
+ if (isNodeLockFilename(filename)) {
74
+ deducedLanguages.push(filename)
75
+ language = NODE
76
+ }
77
+ //
78
+ // if (isRubyLockFilename(filename)) {
79
+ // deducedLanguages.push({language: RUBY, lockFilename: filename})
80
+ // }
81
+ //
82
+ // // this is pipfileLock rather than python lock as there can be different python locks
83
+ // if (isPipfileLockLockFilename(filename)) {
84
+ // deducedLanguages.push({language: PYTHON, lockFilename: filename})
85
+ // }
86
+ //
87
+ // if (isPhpLockFilename(filename)) {
88
+ // deducedLanguages.push({language: PHP, lockFilename: filename})
89
+ // }
90
+ //
91
+ // go does not have a lockfile, it should have a go.mod file containing the modules
92
+ if (isGoProjectFilename(filename)) {
93
+ deducedLanguages.push({ language: GO, projectFilename: filename })
94
+ language = GO
95
+ }
96
+ })
97
+ let identifiedLanguages = { [language]: deducedLanguages }
98
+
99
+ return identifiedLanguages
100
+ }
101
+
31
102
  const deduceLanguage = filename => {
32
103
  const deducedLanguages = []
33
104
 
@@ -175,3 +246,4 @@ exports.isPhpProjectFilename = isPhpProjectFilename
175
246
  exports.isPhpLockFilename = isPhpLockFilename
176
247
  exports.deduceLanguage = deduceLanguage
177
248
  exports.reduceIdentifiedLanguages = reduceIdentifiedLanguages
249
+ exports.deduceLanguageScaAnalysis = deduceLanguageScaAnalysis
@@ -0,0 +1,204 @@
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
+ findCVESeveritiesAndOrderByHighestPriority,
13
+ findHighestSeverityCVE,
14
+ findNameAndVersion,
15
+ severityCountAllCVEs
16
+ } from './utils/reportUtils'
17
+ import {SeverityCountModel} from "./models/severityCountModel";
18
+ import {
19
+ ReportOutputBodyModel,
20
+ ReportOutputHeaderModel,
21
+ ReportOutputModel
22
+ } from "./models/reportOutputModel";
23
+
24
+ export const createLibraryHeader = (
25
+ id: string,
26
+ numberOfVulnerableLibraries: number,
27
+ numberOfCves: number
28
+ ) => {
29
+ numberOfVulnerableLibraries === 1
30
+ ? console.log(
31
+ ` Found 1 vulnerable library containing ${numberOfCves} CVE's`
32
+ )
33
+ : console.log(
34
+ ` Found ${numberOfVulnerableLibraries} vulnerable libraries containing ${numberOfCves} CVE's `
35
+ )
36
+ }
37
+
38
+ export const getReport = async (config: any, reportId: string) => {
39
+ const client = getHttpClient(config)
40
+ return client
41
+ .getReportById(config, reportId)
42
+ .then((res: { statusCode: number; body: any }) => {
43
+ if (res.statusCode === 200) {
44
+ return res.body
45
+ } else {
46
+ console.log('config-------------------')
47
+ console.log(config)
48
+ console.log('reportId----------------')
49
+ console.log(reportId)
50
+ console.log(JSON.stringify(res))
51
+ handleResponseErrors(res, 'report')
52
+ }
53
+ })
54
+ .catch((err: any) => {
55
+ console.log(err)
56
+ })
57
+ }
58
+
59
+ export const printVulnerabilityResponse = (
60
+ vulnerabilities: ReportLibraryModel[],
61
+ config: any
62
+ ) => {
63
+ let hasSomeVulnerabilitiesReported = false
64
+ printFormattedOutput(vulnerabilities, config)
65
+ if (Object.keys(vulnerabilities).length > 0) {
66
+ hasSomeVulnerabilitiesReported = true
67
+ }
68
+ return hasSomeVulnerabilitiesReported
69
+ }
70
+
71
+ export const printFormattedOutput = (
72
+ libraries: ReportLibraryModel[],
73
+ config: any
74
+ ) => {
75
+ const report = new ReportList()
76
+
77
+ for (const library of libraries) {
78
+ const { name, version } = findNameAndVersion(library, config)
79
+
80
+ const newOutputModel = new ReportModelStructure(
81
+ new ReportCompositeKey(
82
+ name,
83
+ version,
84
+ findHighestSeverityCVE(library.cveArray) as ReportSeverityModel
85
+ ),
86
+ library.cveArray
87
+ )
88
+
89
+ report.reportOutputList.push(newOutputModel)
90
+ }
91
+
92
+ const orderedOutputListLowestFirst = orderBy(
93
+ report.reportOutputList,
94
+ reportListItem => reportListItem.compositeKey.highestSeverity.priority,
95
+ ['desc']
96
+ )
97
+
98
+ let contrastHeaderNumCounter = 0
99
+ for (const reportModel of orderedOutputListLowestFirst) {
100
+ contrastHeaderNumCounter++
101
+ const {libraryName, libraryVersion, highestSeverity} = reportModel.compositeKey
102
+ const numOfCVEs = reportModel.cveArray.length
103
+
104
+ const header = buildHeader(
105
+ highestSeverity,
106
+ contrastHeaderNumCounter,
107
+ libraryName,
108
+ libraryVersion,
109
+ numOfCVEs
110
+ )
111
+
112
+ const body = buildBody(
113
+ reportModel.cveArray
114
+ )
115
+
116
+ const reportOutputModel = new ReportOutputModel(header, body)
117
+ console.log(reportOutputModel.header.vulnMessage, reportOutputModel.header.introducesMessage)
118
+ console.log(reportOutputModel.body.issueMessage)
119
+ console.log(reportOutputModel.body.adviceMessage)
120
+ }
121
+ }
122
+
123
+ export function buildHeader(
124
+ highestSeverity: ReportSeverityModel,
125
+ contrastHeaderNum: number,
126
+ libraryName: string,
127
+ version: string,
128
+ numOfCVEs: number,
129
+ ) {
130
+ const vulnerabilityPluralised = numOfCVEs > 1 ? 'Vulnerabilities' : 'Vulnerability'
131
+ const formattedHeaderNum = buildFormattedHeaderNum(contrastHeaderNum)
132
+
133
+ const vulnMessage = chalk
134
+ .hex(highestSeverity.outputColour)
135
+ .bold(`${formattedHeaderNum} - [${highestSeverity.severity}] ${libraryName}-${version}`)
136
+
137
+ const introducesMessage = chalk.bold(
138
+ `introduces ${numOfCVEs} ${vulnerabilityPluralised}`
139
+ )
140
+
141
+ return new ReportOutputHeaderModel(vulnMessage, introducesMessage)
142
+ }
143
+
144
+ export function buildBody(cveArray: ReportCVEModel[]) {
145
+ const cveMessages: string[] = []
146
+
147
+ findCVESeveritiesAndOrderByHighestPriority(cveArray).forEach(reportSeverityModel => {
148
+ // @ts-ignore
149
+ const {outputColour, severity, cveName} = reportSeverityModel
150
+
151
+ const severityShorthand = chalk
152
+ .hex(outputColour)
153
+ .bold(`[${severity.charAt(0).toUpperCase()}]`)
154
+
155
+ const builtMessage = `${severityShorthand} ${cveName}`
156
+ cveMessages.push(builtMessage)
157
+ }
158
+ )
159
+
160
+ const numAndSeverityType = getNumOfAndSeverityType(cveArray)
161
+
162
+ const issueMessage =
163
+ ` ${chalk.bold('Issue')} : ${numAndSeverityType} ${cveMessages.join(', ')}.`
164
+
165
+ const adviceMessage =
166
+ ` ${chalk.bold('Advice')} : ${chalk.bold('Update to latest version')}.`
167
+
168
+ return new ReportOutputBodyModel(issueMessage, adviceMessage)
169
+ }
170
+
171
+ export function buildFormattedHeaderNum(contrastHeaderNum: number) {
172
+ let formattedHeaderNum
173
+
174
+ if (contrastHeaderNum < 10) {
175
+ formattedHeaderNum = `00${contrastHeaderNum}`
176
+ } else if (contrastHeaderNum >= 10 && contrastHeaderNum < 100) {
177
+ formattedHeaderNum = `0${contrastHeaderNum}`
178
+ } else if (contrastHeaderNum >= 100) {
179
+ formattedHeaderNum = contrastHeaderNum
180
+ }
181
+
182
+ return `CONTRAST-${formattedHeaderNum}`
183
+ }
184
+
185
+ export function getNumOfAndSeverityType(cveArray: ReportCVEModel[]) {
186
+ const {
187
+ critical,
188
+ high,
189
+ medium,
190
+ low,
191
+ note
192
+ } = severityCountAllCVEs(cveArray, new SeverityCountModel())
193
+
194
+ const criticalMessage = critical > 0 ? `${critical} Critical` : ''
195
+ const highMessage = high > 0 ? `${high} High` : ''
196
+ const mediumMessage = medium > 0 ? `${medium} Medium` : ''
197
+ const lowMessage = low > 0 ? `${low} Low` : ''
198
+ const noteMessage = note > 0 ? `${note} Note` : ''
199
+
200
+ //removes/trims whitespace to single spaces
201
+ return `${criticalMessage} ${highMessage} ${mediumMessage} ${lowMessage} ${noteMessage}`
202
+ .replace(/\s+/g, ' ')
203
+ .trim();
204
+ }
@@ -0,0 +1,30 @@
1
+ export class ReportLibraryModel {
2
+ name: string
3
+ cveArray: ReportCVEModel[]
4
+
5
+ constructor (name: string, cveArray: ReportCVEModel[]){
6
+ this.name = name
7
+ this.cveArray = cveArray
8
+ }
9
+ }
10
+
11
+ export class ReportCVEModel {
12
+ name?: string
13
+ description?: string
14
+ authentication?: string
15
+ references?: []
16
+ severityCode?: string
17
+ cvss3SeverityCode?: string
18
+
19
+ constructor (
20
+ name: string,
21
+ description: string,
22
+ severityCode: string,
23
+ cvss3SeverityCode: string
24
+ ){
25
+ this.name = name
26
+ this.description = description
27
+ this.severityCode = severityCode
28
+ this.cvss3SeverityCode = cvss3SeverityCode
29
+ }
30
+ }
@@ -0,0 +1,32 @@
1
+ import {ReportSeverityModel} from "./reportSeverityModel";
2
+ import {ReportCVEModel} from "./reportLibraryModel";
3
+
4
+ export class ReportList {
5
+ reportOutputList: ReportModelStructure[]
6
+
7
+ constructor (){
8
+ this.reportOutputList = []
9
+ }
10
+ }
11
+
12
+ export class ReportModelStructure {
13
+ compositeKey: ReportCompositeKey;
14
+ cveArray: ReportCVEModel[];
15
+
16
+ constructor (compositeKey: ReportCompositeKey, cveArray: ReportCVEModel[]){
17
+ this.compositeKey = compositeKey
18
+ this.cveArray = cveArray
19
+ }
20
+ }
21
+
22
+ export class ReportCompositeKey {
23
+ libraryName!: string;
24
+ libraryVersion!: string;
25
+ highestSeverity!: ReportSeverityModel;
26
+
27
+ constructor (libraryName: string, libraryVersion: string, highestSeverity: ReportSeverityModel){
28
+ this.libraryName = libraryName
29
+ this.libraryVersion = libraryVersion
30
+ this.highestSeverity = highestSeverity
31
+ }
32
+ }
@@ -0,0 +1,29 @@
1
+ export class ReportOutputModel {
2
+ header: ReportOutputHeaderModel
3
+ body: ReportOutputBodyModel
4
+
5
+ constructor(header: ReportOutputHeaderModel, body: ReportOutputBodyModel) {
6
+ this.header = header
7
+ this.body = body
8
+ }
9
+ }
10
+
11
+ export class ReportOutputHeaderModel {
12
+ vulnMessage: string
13
+ introducesMessage: string
14
+
15
+ constructor(vulnMessage: string, introducesMessage: string) {
16
+ this.vulnMessage = vulnMessage
17
+ this.introducesMessage = introducesMessage
18
+ }
19
+ }
20
+
21
+ export class ReportOutputBodyModel {
22
+ issueMessage: string
23
+ adviceMessage: string
24
+
25
+ constructor(bodyIssueMessage: string, bodyAdviceMessage: string) {
26
+ this.issueMessage = bodyIssueMessage
27
+ this.adviceMessage = bodyAdviceMessage
28
+ }
29
+ }
@@ -0,0 +1,13 @@
1
+ export class ReportSeverityModel {
2
+ severity: string
3
+ priority: number
4
+ outputColour: string
5
+ cveName: string
6
+
7
+ constructor(severity: string, priority: number, outputColour: string, cveName: string) {
8
+ this.severity = severity
9
+ this.priority = priority
10
+ this.outputColour = outputColour
11
+ this.cveName = cveName
12
+ }
13
+ }
@@ -0,0 +1,16 @@
1
+ export class SeverityCountModel {
2
+ critical!: number
3
+ high!: number
4
+ medium!: number
5
+ low!: number
6
+ note!: number
7
+
8
+ //needed as default to stop NaN when new object constructed
9
+ constructor() {
10
+ this.critical = 0
11
+ this.high = 0
12
+ this.medium = 0
13
+ this.low = 0
14
+ this.note = 0
15
+ }
16
+ }