@contrast/contrast 1.0.1 → 1.0.4

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 (94) hide show
  1. package/.prettierignore +2 -0
  2. package/README.md +103 -133
  3. package/dist/audit/languageAnalysisEngine/{langugageAnalysisFactory.js → languageAnalysisFactory.js} +26 -11
  4. package/dist/audit/languageAnalysisEngine/report/commonReportingFunctions.js +62 -234
  5. package/dist/audit/languageAnalysisEngine/report/models/reportLibraryModel.js +19 -0
  6. package/dist/audit/languageAnalysisEngine/report/models/reportListModel.js +24 -0
  7. package/dist/audit/languageAnalysisEngine/report/models/reportSeverityModel.js +10 -0
  8. package/dist/audit/languageAnalysisEngine/report/reportingFeature.js +24 -129
  9. package/dist/audit/languageAnalysisEngine/report/utils/reportUtils.js +85 -0
  10. package/dist/audit/languageAnalysisEngine/sendSnapshot.js +3 -1
  11. package/dist/commands/audit/auditController.js +6 -3
  12. package/dist/commands/audit/saveFile.js +11 -0
  13. package/dist/commands/auth/auth.js +19 -1
  14. package/dist/commands/config/config.js +19 -8
  15. package/dist/commands/scan/processScan.js +8 -25
  16. package/dist/common/HTTPClient.js +30 -26
  17. package/dist/common/errorHandling.js +17 -1
  18. package/dist/common/versionChecker.js +32 -0
  19. package/dist/constants/constants.js +4 -2
  20. package/dist/constants/lambda.js +3 -1
  21. package/dist/constants/locales.js +41 -18
  22. package/dist/constants.js +39 -3
  23. package/dist/index.js +49 -28
  24. package/dist/lambda/help.js +22 -14
  25. package/dist/lambda/lambda.js +6 -0
  26. package/dist/sbom/generateSbom.js +20 -0
  27. package/dist/scan/help.js +4 -2
  28. package/dist/scan/models/groupedResultsModel.js +10 -0
  29. package/dist/scan/models/resultContentModel.js +2 -0
  30. package/dist/scan/models/scanResultsModel.js +11 -0
  31. package/dist/scan/populateProjectIdAndProjectName.js +1 -0
  32. package/dist/scan/saveResults.js +9 -10
  33. package/dist/scan/scan.js +99 -74
  34. package/dist/scan/scanConfig.js +20 -1
  35. package/dist/scan/scanController.js +7 -2
  36. package/dist/scan/scanResults.js +6 -0
  37. package/dist/utils/getConfig.js +3 -0
  38. package/dist/utils/paramsUtil/commandlineParams.js +1 -1
  39. package/dist/utils/requestUtils.js +1 -1
  40. package/dist/utils/saveFile.js +19 -0
  41. package/package.json +2 -2
  42. package/src/audit/languageAnalysisEngine/{langugageAnalysisFactory.js → languageAnalysisFactory.js} +33 -15
  43. package/src/audit/languageAnalysisEngine/report/commonReportingFunctions.ts +127 -0
  44. package/src/audit/languageAnalysisEngine/report/models/reportLibraryModel.ts +30 -0
  45. package/src/audit/languageAnalysisEngine/report/models/reportListModel.ts +32 -0
  46. package/src/audit/languageAnalysisEngine/report/models/reportSeverityModel.ts +9 -0
  47. package/src/audit/languageAnalysisEngine/report/reportingFeature.ts +56 -0
  48. package/src/audit/languageAnalysisEngine/report/utils/reportUtils.ts +110 -0
  49. package/src/audit/languageAnalysisEngine/sendSnapshot.js +3 -1
  50. package/src/commands/audit/auditController.ts +12 -3
  51. package/src/commands/audit/processAudit.ts +0 -1
  52. package/src/commands/audit/saveFile.ts +6 -0
  53. package/src/commands/auth/auth.js +25 -1
  54. package/src/commands/config/config.js +22 -8
  55. package/src/commands/scan/processScan.js +8 -29
  56. package/src/common/HTTPClient.js +42 -36
  57. package/src/common/errorHandling.ts +29 -2
  58. package/src/common/versionChecker.ts +41 -0
  59. package/src/constants/constants.js +5 -4
  60. package/src/constants/lambda.js +3 -1
  61. package/src/constants/locales.js +51 -19
  62. package/src/constants.js +44 -3
  63. package/src/index.ts +63 -31
  64. package/src/lambda/help.ts +22 -14
  65. package/src/lambda/lambda.ts +8 -0
  66. package/src/sbom/generateSbom.ts +17 -0
  67. package/src/scan/help.js +4 -2
  68. package/src/scan/models/groupedResultsModel.ts +18 -0
  69. package/src/scan/models/resultContentModel.ts +86 -0
  70. package/src/scan/models/scanResultsModel.ts +52 -0
  71. package/src/scan/populateProjectIdAndProjectName.js +1 -0
  72. package/src/scan/saveResults.js +8 -9
  73. package/src/scan/scan.ts +192 -0
  74. package/src/scan/scanConfig.js +26 -1
  75. package/src/scan/scanController.js +11 -2
  76. package/src/scan/scanResults.js +11 -0
  77. package/src/utils/getConfig.ts +12 -0
  78. package/src/utils/paramsUtil/commandlineParams.js +1 -1
  79. package/src/utils/requestUtils.js +1 -1
  80. package/src/utils/saveFile.js +19 -0
  81. package/dist/audit/languageAnalysisEngine/report/checkIgnoreDevDep.js +0 -17
  82. package/dist/audit/languageAnalysisEngine/report/newReportingFeature.js +0 -81
  83. package/dist/common/findLatestCLIVersion.js +0 -23
  84. package/dist/lambda/scanDetail.js +0 -30
  85. package/dist/scan/fileFinder.js +0 -15
  86. package/dist/utils/fileUtils.js +0 -31
  87. package/dist/utils/paramsUtil/genericCommandLineParams.js +0 -12
  88. package/dist/utils/paramsUtil/yamlParams.js +0 -6
  89. package/src/audit/languageAnalysisEngine/report/checkIgnoreDevDep.js +0 -27
  90. package/src/audit/languageAnalysisEngine/report/commonReportingFunctions.js +0 -303
  91. package/src/audit/languageAnalysisEngine/report/newReportingFeature.js +0 -124
  92. package/src/audit/languageAnalysisEngine/report/reportingFeature.js +0 -190
  93. package/src/common/findLatestCLIVersion.ts +0 -27
  94. package/src/scan/scan.js +0 -162
package/dist/scan/scan.js CHANGED
@@ -1,43 +1,43 @@
1
1
  "use strict";
2
- const commonApi = require('../utils/commonApi.js');
3
- const fileUtils = require('../scan/fileUtils');
4
- const allowedFileTypes = ['.jar', '.war', '.js', '.zip', '.exe'];
5
- const i18n = require('i18n');
6
- const oraWrapper = require('../utils/oraWrapper');
7
- const chalk = require('chalk');
8
- const isFileAllowed = scanOption => {
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.stripMustacheTags = exports.getMessage = exports.getGroups = exports.formatLinks = exports.formatScanOutput = exports.sendScan = exports.isFileAllowed = exports.allowedFileTypes = void 0;
7
+ const commonApi_js_1 = __importDefault(require("../utils/commonApi.js"));
8
+ const fileUtils_1 = __importDefault(require("../scan/fileUtils"));
9
+ const i18n_1 = __importDefault(require("i18n"));
10
+ const oraWrapper_1 = __importDefault(require("../utils/oraWrapper"));
11
+ const chalk_1 = __importDefault(require("chalk"));
12
+ const groupedResultsModel_1 = require("./models/groupedResultsModel");
13
+ exports.allowedFileTypes = ['.jar', '.war', '.js', '.zip', '.exe'];
14
+ const isFileAllowed = (scanOption) => {
9
15
  let valid = false;
10
- allowedFileTypes.forEach(fileType => {
16
+ exports.allowedFileTypes.forEach(fileType => {
11
17
  if (scanOption.endsWith(fileType)) {
12
18
  valid = true;
13
19
  }
14
20
  });
15
21
  return valid;
16
22
  };
17
- const stripMustacheTags = oldString => {
18
- return oldString
19
- .replace(/\n/g, ' ')
20
- .replace(/{{.*?}}/g, '\n')
21
- .replace(/\s+/g, ' ')
22
- .trim();
23
- };
23
+ exports.isFileAllowed = isFileAllowed;
24
24
  const sendScan = async (config) => {
25
- if (!isFileAllowed(config.file)) {
26
- console.log(i18n.__('scanErrorFileMessage'));
25
+ if (!(0, exports.isFileAllowed)(config.file)) {
26
+ console.log(i18n_1.default.__('scanErrorFileMessage'));
27
27
  process.exit(9);
28
28
  }
29
29
  else {
30
- fileUtils.checkFilePermissions(config.file);
31
- const client = commonApi.getHttpClient(config);
32
- const startUploadSpinner = oraWrapper.returnOra(i18n.__('uploadingScan'));
33
- oraWrapper.startSpinner(startUploadSpinner);
30
+ fileUtils_1.default.checkFilePermissions(config.file);
31
+ const client = commonApi_js_1.default.getHttpClient(config);
32
+ const startUploadSpinner = oraWrapper_1.default.returnOra(i18n_1.default.__('uploadingScan'));
33
+ oraWrapper_1.default.startSpinner(startUploadSpinner);
34
34
  return await client
35
35
  .sendArtifact(config)
36
36
  .then(res => {
37
37
  if (res.statusCode === 201) {
38
- oraWrapper.succeedSpinner(startUploadSpinner, i18n.__('uploadingScanSuccessful'));
38
+ oraWrapper_1.default.succeedSpinner(startUploadSpinner, i18n_1.default.__('uploadingScanSuccessful'));
39
39
  if (config.verbose) {
40
- console.log(i18n.__('responseMessage', res.body));
40
+ console.log(i18n_1.default.__('responseMessage', res.body));
41
41
  }
42
42
  return res.body.id;
43
43
  }
@@ -46,10 +46,12 @@ const sendScan = async (config) => {
46
46
  console.log(res.statusCode);
47
47
  console.log(config);
48
48
  }
49
- oraWrapper.failSpinner(startUploadSpinner, i18n.__('uploadingScanFail'));
49
+ oraWrapper_1.default.failSpinner(startUploadSpinner, i18n_1.default.__('uploadingScanFail'));
50
50
  if (res.statusCode === 403) {
51
- console.log(i18n.__('permissionsError'));
51
+ console.log(i18n_1.default.__('permissionsError'));
52
+ process.exit(1);
52
53
  }
54
+ console.log(i18n_1.default.__('genericServiceError', res.statusCode));
53
55
  process.exit(1);
54
56
  }
55
57
  })
@@ -58,74 +60,97 @@ const sendScan = async (config) => {
58
60
  });
59
61
  }
60
62
  };
61
- const formatScanOutput = (overview, results) => {
62
- console.log();
63
- if (results.content.length === 0) {
64
- console.log(i18n.__('scanNoVulnerabilitiesFound'));
63
+ exports.sendScan = sendScan;
64
+ function formatScanOutput(scanResults) {
65
+ const { projectOverview, scanResultsInstances } = scanResults;
66
+ if (scanResultsInstances.content.length === 0) {
67
+ console.log(i18n_1.default.__('scanNoVulnerabilitiesFound'));
65
68
  }
66
69
  else {
67
- console.log(chalk.bold('Here are your top priorities to fix'));
70
+ const message = projectOverview.critical || projectOverview.high
71
+ ? 'Here are your top priorities to fix'
72
+ : "No major issues, here's what we found";
73
+ console.log(chalk_1.default.bold(message));
68
74
  console.log();
69
- const groups = getGroups(results.content);
75
+ const groups = getGroups(scanResultsInstances.content);
70
76
  groups.forEach(entry => {
71
- console.log(chalk.bold(`${entry.severity} | ${entry.ruleId} (${entry.lineInfoSet.size})`));
77
+ console.log(chalk_1.default.bold(`[ ${entry.severity} ] | ${entry.ruleId} (${entry.lineInfoSet.size}) - ` +
78
+ `${entry.message}`));
72
79
  let count = 1;
73
80
  entry.lineInfoSet.forEach(lineInfo => {
74
81
  console.log(`\t ${count}. ${lineInfo}`);
75
82
  count++;
76
83
  });
77
- console.log(chalk.bold('How to fix:'));
78
- console.log(entry.recommendation);
84
+ if (entry?.issue) {
85
+ console.log(chalk_1.default.bold('Issue' + ': ') + entry.issue);
86
+ }
87
+ if (entry?.advice) {
88
+ console.log(chalk_1.default.bold('Advice' + ': ') + entry.advice);
89
+ }
90
+ if (entry?.learn && entry?.learn.length > 0) {
91
+ formatLinks('Learn', entry.learn);
92
+ }
79
93
  console.log();
80
94
  });
81
- const totalVulnerabilities = overview.critical +
82
- overview.high +
83
- overview.medium +
84
- overview.low +
85
- overview.note;
86
- console.log(chalk.bold(`Found ${totalVulnerabilities} vulnerabilities`));
87
- console.log(i18n.__('foundDetailedVulnerabilities', overview.critical, overview.high, overview.medium, overview.low, overview.note));
95
+ printVulnInfo(projectOverview);
88
96
  }
89
- };
90
- const getGroups = content => {
97
+ }
98
+ exports.formatScanOutput = formatScanOutput;
99
+ function printVulnInfo(projectOverview) {
100
+ const totalVulnerabilities = getTotalVulns(projectOverview);
101
+ const vulMessage = totalVulnerabilities === 1 ? `vulnerability` : `vulnerabilities`;
102
+ console.log(chalk_1.default.bold(`Found ${totalVulnerabilities} ${vulMessage}`));
103
+ console.log(i18n_1.default.__('foundDetailedVulnerabilities', String(projectOverview.critical), String(projectOverview.high), String(projectOverview.medium), String(projectOverview.low), String(projectOverview.note)));
104
+ }
105
+ function getTotalVulns(projectOverview) {
106
+ return (projectOverview.critical +
107
+ projectOverview.high +
108
+ projectOverview.medium +
109
+ projectOverview.low +
110
+ projectOverview.note);
111
+ }
112
+ function formatLinks(objName, entry) {
113
+ console.log(chalk_1.default.bold(objName + ':'));
114
+ entry.forEach(link => {
115
+ console.log(link);
116
+ });
117
+ }
118
+ exports.formatLinks = formatLinks;
119
+ function getGroups(content) {
91
120
  const groupTypeSet = new Set(content.map(({ ruleId }) => ruleId));
92
- let groupTypeResults = [];
121
+ const groupTypeResults = [];
93
122
  groupTypeSet.forEach(groupName => {
94
- let groupResultsObj = {
95
- ruleId: groupName,
96
- lineInfoSet: new Set(),
97
- recommendation: '',
98
- severity: ''
99
- };
123
+ const groupResultsObj = new groupedResultsModel_1.GroupedResultsModel(groupName);
100
124
  content.forEach(resultEntry => {
101
125
  if (resultEntry.ruleId === groupName) {
102
126
  groupResultsObj.severity = resultEntry.severity;
103
- groupResultsObj.recommendation = resultEntry.recommendation
104
- ? stripMustacheTags(resultEntry.recommendation)
105
- : '';
106
- groupResultsObj.lineInfoSet.add(formattedCodeLine(resultEntry));
127
+ groupResultsObj.issue = stripMustacheTags(resultEntry.issue);
128
+ groupResultsObj.advice = resultEntry.advice;
129
+ groupResultsObj.learn = resultEntry.learn;
130
+ groupResultsObj.message = resultEntry.message?.text;
131
+ groupResultsObj.lineInfoSet.add(getMessage(resultEntry.locations));
107
132
  }
108
133
  });
109
134
  groupTypeResults.push(groupResultsObj);
110
135
  });
111
136
  return groupTypeResults;
112
- };
113
- const formattedCodeLine = resultEntry => {
114
- let lineUri = resultEntry.locations[0]?.physicalLocation.artifactLocation.uri;
115
- return lineUri + ' @ ' + setLineNumber(resultEntry);
116
- };
117
- const setLineNumber = resultEntry => {
118
- return resultEntry.codeFlows?.[0]?.threadFlows[0]?.locations[0]?.location
119
- ?.physicalLocation?.region?.startLine
120
- ? resultEntry.codeFlows[0]?.threadFlows[0]?.locations[0]?.location
121
- ?.physicalLocation?.region?.startLine
122
- : resultEntry.locations[0]?.physicalLocation?.region?.startLine;
123
- };
124
- module.exports = {
125
- sendScan: sendScan,
126
- getGroups: getGroups,
127
- allowedFileTypes: allowedFileTypes,
128
- isFileAllowed: isFileAllowed,
129
- stripMustacheTags: stripMustacheTags,
130
- formatScanOutput: formatScanOutput
131
- };
137
+ }
138
+ exports.getGroups = getGroups;
139
+ function getMessage(locations) {
140
+ const message = locations[0]?.physicalLocation?.artifactLocation?.uri || '';
141
+ const lineNumber = locations[0]?.physicalLocation?.region?.startLine || '';
142
+ if (!lineNumber) {
143
+ return '@' + message;
144
+ }
145
+ return '@' + message + ':' + lineNumber;
146
+ }
147
+ exports.getMessage = getMessage;
148
+ function stripMustacheTags(oldString) {
149
+ return oldString
150
+ .replace(/\n/g, ' ')
151
+ .replace(/{{.*?}}/g, '\n')
152
+ .replace(/\$\$LINK_DELIM\$\$/g, '\n')
153
+ .replace(/\s+/g, ' ')
154
+ .trim();
155
+ }
156
+ exports.stripMustacheTags = stripMustacheTags;
@@ -3,9 +3,24 @@ const paramHandler = require('../utils/paramsUtil/paramHandler');
3
3
  const constants = require('../../src/constants.js');
4
4
  const parsedCLIOptions = require('../../src/utils/parsedCLIOptions');
5
5
  const path = require('path');
6
+ const { supportedLanguages } = require('../audit/languageAnalysisEngine/constants');
7
+ const i18n = require('i18n');
8
+ const { scanUsageGuide } = require('./help');
6
9
  const getScanConfig = argv => {
7
10
  let scanParams = parsedCLIOptions.getCommandLineArgsCustom(argv, constants.commandLineDefinitions.scanOptionDefinitions);
11
+ if (scanParams.help) {
12
+ printHelpMessage();
13
+ process.exit(0);
14
+ }
8
15
  const paramsAuth = paramHandler.getAuth(scanParams);
16
+ if (scanParams.language) {
17
+ scanParams.language = scanParams.language.toUpperCase();
18
+ if (!Object.values(supportedLanguages).includes(scanParams.language)) {
19
+ console.log(`Did not recognise --language ${scanParams.language}`);
20
+ console.log(i18n.__('constantsHowToRunDev3'));
21
+ process.exit(1);
22
+ }
23
+ }
9
24
  if (!scanParams.name && scanParams.file) {
10
25
  scanParams.name = getFileName(scanParams.file);
11
26
  }
@@ -14,7 +29,11 @@ const getScanConfig = argv => {
14
29
  const getFileName = file => {
15
30
  return file.split(path.sep).pop();
16
31
  };
32
+ const printHelpMessage = () => {
33
+ console.log(scanUsageGuide);
34
+ };
17
35
  module.exports = {
18
36
  getScanConfig,
19
- getFileName
37
+ getFileName,
38
+ printHelpMessage
20
39
  };
@@ -6,6 +6,7 @@ const scan = require('./scan');
6
6
  const scanResults = require('./scanResults');
7
7
  const autoDetection = require('./autoDetection');
8
8
  const fileFunctions = require('./fileUtils');
9
+ const { performance } = require('perf_hooks');
9
10
  const getTimeout = config => {
10
11
  if (config.timeout) {
11
12
  return config.timeout;
@@ -21,7 +22,7 @@ const fileAndLanguageLogic = async (configToUse) => {
21
22
  if (configToUse.file) {
22
23
  if (!fileFunctions.fileExists(configToUse.file)) {
23
24
  console.log(i18n.__('fileNotExist'));
24
- process.exit(0);
25
+ process.exit(1);
25
26
  }
26
27
  return configToUse;
27
28
  }
@@ -32,17 +33,21 @@ const fileAndLanguageLogic = async (configToUse) => {
32
33
  }
33
34
  };
34
35
  const startScan = async (configToUse) => {
36
+ const startTime = performance.now();
35
37
  await fileAndLanguageLogic(configToUse);
36
38
  if (!configToUse.projectId) {
37
39
  configToUse.projectId = await populateProjectIdAndProjectName.populateProjectId(configToUse);
38
40
  }
39
41
  const codeArtifactId = await scan.sendScan(configToUse);
40
42
  if (!configToUse.ff) {
41
- const startScanSpinner = returnOra('Contrast Scan started');
43
+ const startScanSpinner = returnOra('🚀 Contrast Scan started');
42
44
  startSpinner(startScanSpinner);
43
45
  const scanDetail = await scanResults.returnScanResults(configToUse, codeArtifactId, getTimeout(configToUse), startScanSpinner);
44
46
  const scanResultsInstances = await scanResults.returnScanResultsInstances(configToUse, scanDetail.id);
47
+ const endTime = performance.now();
48
+ const scanDurationMs = endTime - startTime;
45
49
  succeedSpinner(startScanSpinner, 'Contrast Scan complete');
50
+ console.log(`----- Scan completed in ${(scanDurationMs / 1000).toFixed(2)}s -----`);
46
51
  const projectOverview = await scanResults.returnScanProjectById(configToUse);
47
52
  return { projectOverview, scanDetail, scanResultsInstances };
48
53
  }
@@ -3,6 +3,7 @@ const commonApi = require('../utils/commonApi');
3
3
  const requestUtils = require('../../src/utils/requestUtils');
4
4
  const oraFunctions = require('../utils/oraWrapper');
5
5
  const _ = require('lodash');
6
+ const i18n = require('i18n');
6
7
  const getScanId = async (config, codeArtifactId, client) => {
7
8
  return client
8
9
  .getScanId(config, codeArtifactId)
@@ -40,6 +41,11 @@ const returnScanResults = async (config, codeArtifactId, timeout, startScanSpinn
40
41
  if (result.body.status === 'FAILED') {
41
42
  complete = true;
42
43
  oraFunctions.failSpinner(startScanSpinner, 'Contrast Scan Failed.');
44
+ console.log(result.body.errorMessage);
45
+ if (result.body.errorMessage ===
46
+ 'Unable to determine language for code artifact') {
47
+ console.log('Try scanning again using --language param. ', i18n.__('scanOptionsLanguageSummary'));
48
+ }
43
49
  process.exit(1);
44
50
  }
45
51
  }
@@ -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
  }
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const getAuth = parsedCLIOptions => {
2
+ const getAuth = (parsedCLIOptions = {}) => {
3
3
  let params = {};
4
4
  params.apiKey = parsedCLIOptions['apiKey'];
5
5
  params.authorization = parsedCLIOptions['authorization'];
@@ -6,7 +6,7 @@ function sendRequest({ options, method = 'put' }) {
6
6
  return request[`${method}Async`](options.url, options);
7
7
  }
8
8
  const millisToSeconds = millis => {
9
- return ((millis % 60000) / 1000).toFixed(0);
9
+ return (millis / 1000).toFixed(0);
10
10
  };
11
11
  const sleep = ms => {
12
12
  return new Promise(resolve => setTimeout(resolve, ms));
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ const { SARIF_FILE } = require('../constants/constants');
3
+ const commonApi = require('./commonApi');
4
+ const saveResults = require('../scan/saveResults');
5
+ const i18n = require('i18n');
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
+ }
13
+ else {
14
+ console.log(i18n.__('scanNoFiletypeSpecifiedForSave'));
15
+ }
16
+ };
17
+ module.exports = {
18
+ saveScanFile: saveScanFile
19
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/contrast",
3
- "version": "1.0.1",
3
+ "version": "1.0.4",
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",
@@ -10,9 +10,11 @@ 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')
14
+ const fs = require('fs')
15
+ const chalk = require('chalk')
16
+ const saveFile = require('../../commands/audit/saveFile').default
17
+ const generateSbom = require('../../sbom/generateSbom').default
16
18
 
17
19
  module.exports = exports = (err, analysis) => {
18
20
  const { identifiedLanguageInfo } = analysis.languageAnalysis
@@ -46,19 +48,11 @@ module.exports = exports = (err, analysis) => {
46
48
  console.log('\n **************CONTRAST OSS ANALYSIS BEGINS**************')
47
49
  const snapshotResponse = await newSendSnapShot(analysis, catalogueAppId)
48
50
 
49
- if (config.report) {
50
- const ignoreDevUrl = await checkDevDeps(config)
51
- if (ignoreDevUrl) {
52
- await vulnReportWithoutDevDep(
53
- analysis,
54
- catalogueAppId,
55
- snapshotResponse.id,
56
- config
57
- )
58
- } else {
59
- await vulnerabilityReport(analysis, catalogueAppId, config)
60
- }
61
- }
51
+ await vulnerabilityReport(analysis, catalogueAppId, snapshotResponse.id)
52
+
53
+ //should be moved to processAudit.ts once promises implemented
54
+ await auditSave(config)
55
+
62
56
  console.log(
63
57
  '\n ***************CONTRAST OSS ANALYSIS COMPLETE************** \n'
64
58
  )
@@ -92,3 +86,27 @@ module.exports = exports = (err, analysis) => {
92
86
  goAE(identifiedLanguageInfo, analysis.config, langCallback)
93
87
  }
94
88
  }
89
+
90
+ async function auditSave(config) {
91
+ //should be moved to processAudit.ts once promises implemented
92
+ if (config.save) {
93
+ if (config.save.toLowerCase() === 'sbom') {
94
+ saveFile(config, await generateSbom(config))
95
+
96
+ const filename = `${config.applicationId}-sbom-cyclonedx.json`
97
+ if (fs.existsSync(filename)) {
98
+ console.log(i18n.__('auditSBOMSaveSuccess') + ` - ${filename}`)
99
+ } else {
100
+ console.log(
101
+ chalk.yellow.bold(
102
+ `\n Unable to save ${filename} Software Bill of Materials (SBOM)`
103
+ )
104
+ )
105
+ }
106
+ } else {
107
+ console.log(i18n.__('auditBadFiletypeSpecifiedForSave'))
108
+ }
109
+ } else if (config.save === null) {
110
+ console.log(i18n.__('auditNoFiletypeSpecifiedForSave'))
111
+ }
112
+ }
@@ -0,0 +1,127 @@
1
+ import i18n from 'i18n'
2
+ import { getHttpClient, handleResponseErrors } from '../../../utils/commonApi'
3
+ import {
4
+ ReportCompositeKey,
5
+ ReportList,
6
+ ReportModelStructure
7
+ } from './models/reportListModel'
8
+ import { ReportSeverityModel } from './models/reportSeverityModel'
9
+ import { orderBy } from 'lodash'
10
+ import chalk from 'chalk'
11
+ import { ReportLibraryModel } from './models/reportLibraryModel'
12
+ import { findHighestSeverityCVE, findNameAndVersion } from './utils/reportUtils'
13
+ import {
14
+ failSpinner,
15
+ returnOra,
16
+ startSpinner,
17
+ succeedSpinner
18
+ } from '../../../utils/oraWrapper'
19
+
20
+ export const createLibraryHeader = (
21
+ id: string,
22
+ numberOfVulnerableLibraries: number,
23
+ numberOfCves: number,
24
+ name: string
25
+ ) => {
26
+ name
27
+ ? console.log(`\n Application Name: ${name} | Application ID: ${id}`)
28
+ : console.log(` Application ID: ${id}`)
29
+
30
+ numberOfVulnerableLibraries === 1
31
+ ? console.log(
32
+ '\n **************************' +
33
+ ` Found 1 vulnerable library containing ${numberOfCves} CVE's` +
34
+ '************************** '
35
+ )
36
+ : console.log(
37
+ '\n **************************' +
38
+ ` Found ${numberOfVulnerableLibraries} vulnerable libraries containing ${numberOfCves} CVE's ` +
39
+ '************************** '
40
+ )
41
+ }
42
+
43
+ export const getReport = async (config: any, reportId: string) => {
44
+ const client = getHttpClient(config)
45
+
46
+ const reportSpinner = returnOra(i18n.__('auditReportWaiting'))
47
+ reportSpinner.indent = 1
48
+ startSpinner(reportSpinner)
49
+ return client
50
+ .getReportById(config, reportId)
51
+ .then((res: { statusCode: number; body: any }) => {
52
+ if (res.statusCode === 200) {
53
+ succeedSpinner(reportSpinner, i18n.__('auditReportSuccessMessage'))
54
+ return res.body
55
+ } else {
56
+ failSpinner(reportSpinner, i18n.__('auditReportFail'))
57
+ console.log('config-------------------')
58
+ console.log(config)
59
+ console.log('reportId----------------')
60
+ console.log(reportId)
61
+ console.log(JSON.stringify(res))
62
+ handleResponseErrors(res, 'report')
63
+ }
64
+ })
65
+ .catch((err: any) => {
66
+ console.log(err)
67
+ })
68
+ }
69
+
70
+ export const printVulnerabilityResponse = (
71
+ vulnerabilities: ReportLibraryModel[],
72
+ config: any
73
+ ) => {
74
+ let hasSomeVulnerabilitiesReported = false
75
+ printFormattedOutput(vulnerabilities, config)
76
+ if (Object.keys(vulnerabilities).length > 0) {
77
+ hasSomeVulnerabilitiesReported = true
78
+ }
79
+ return hasSomeVulnerabilitiesReported
80
+ }
81
+
82
+ export const printFormattedOutput = (
83
+ libraries: ReportLibraryModel[],
84
+ config: any
85
+ ) => {
86
+ const report = new ReportList()
87
+
88
+ for (const library of libraries) {
89
+ const { name, version } = findNameAndVersion(library, config)
90
+
91
+ const newOutputModel = new ReportModelStructure(
92
+ new ReportCompositeKey(
93
+ name,
94
+ version,
95
+ findHighestSeverityCVE(library.cveArray) as ReportSeverityModel
96
+ ),
97
+ library.cveArray
98
+ )
99
+
100
+ report.reportOutputList.push(newOutputModel)
101
+ }
102
+
103
+ const orderedOutputList = orderBy(
104
+ report.reportOutputList,
105
+ reportListItem => reportListItem.compositeKey.highestSeverity.priority
106
+ )
107
+
108
+ for (const reportModel of orderedOutputList) {
109
+ const name = reportModel.compositeKey.libraryName
110
+ const version = reportModel.compositeKey.libraryVersion
111
+ const highestSeverity = reportModel.compositeKey.highestSeverity.severity
112
+
113
+ const numOfCVEs = reportModel.cveArray.length
114
+
115
+ const cveNames: string[] = []
116
+
117
+ reportModel.cveArray.forEach(cve => cveNames.push(cve.name as string))
118
+
119
+ const boldHeader = chalk.bold(`${highestSeverity} | Vulnerable Library`)
120
+ const cvePluralised = numOfCVEs > 1 ? 'CVEs' : 'CVE'
121
+ console.log(
122
+ `\n ${boldHeader} ${name} (${version}) has ${numOfCVEs} known ${cvePluralised}`
123
+ )
124
+ console.log(` ${cveNames.join(', ')}`)
125
+ console.log(chalk.bold(' How to fix: Update to latest version'))
126
+ }
127
+ }
@@ -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,9 @@
1
+ export class ReportSeverityModel {
2
+ severity!: string
3
+ priority!: number
4
+
5
+ constructor(severity: string, priority: number) {
6
+ this.severity = severity
7
+ this.priority = priority
8
+ }
9
+ }