@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.
Files changed (55) hide show
  1. package/dist/audit/report/commonReportingFunctions.js +175 -116
  2. package/dist/audit/report/models/reportSeverityModel.js +3 -3
  3. package/dist/audit/report/reportingFeature.js +1 -10
  4. package/dist/audit/report/utils/reportUtils.js +4 -4
  5. package/dist/commands/audit/processAudit.js +10 -0
  6. package/dist/commands/scan/processScan.js +9 -0
  7. package/dist/commands/scan/sca/scaAnalysis.js +2 -0
  8. package/dist/common/HTTPClient.js +30 -2
  9. package/dist/common/errorHandling.js +1 -2
  10. package/dist/common/fail.js +7 -3
  11. package/dist/common/versionChecker.js +11 -5
  12. package/dist/constants/constants.js +1 -1
  13. package/dist/constants/locales.js +16 -8
  14. package/dist/constants.js +2 -2
  15. package/dist/index.js +5 -3
  16. package/dist/lambda/lambda.js +8 -1
  17. package/dist/scaAnalysis/common/auditReport.js +78 -0
  18. package/dist/scaAnalysis/common/scaServicesUpload.js +53 -0
  19. package/dist/scaAnalysis/javascript/index.js +4 -0
  20. package/dist/scaAnalysis/javascript/scaServiceParser.js +109 -0
  21. package/dist/scaAnalysis/ruby/analysis.js +106 -9
  22. package/dist/scaAnalysis/ruby/index.js +6 -1
  23. package/dist/scan/formatScanOutput.js +4 -29
  24. package/dist/scan/scanResults.js +1 -1
  25. package/dist/{audit/languageAnalysisEngine/util → utils}/capabilities.js +0 -0
  26. package/dist/{audit/languageAnalysisEngine/util → utils}/generalAPI.js +14 -5
  27. package/package.json +4 -1
  28. package/src/audit/report/commonReportingFunctions.js +432 -0
  29. package/src/audit/report/models/reportSeverityModel.ts +6 -6
  30. package/src/audit/report/reportingFeature.ts +2 -16
  31. package/src/audit/report/utils/reportUtils.ts +2 -8
  32. package/src/commands/audit/processAudit.ts +8 -0
  33. package/src/commands/scan/processScan.js +14 -0
  34. package/src/commands/scan/sca/scaAnalysis.js +9 -0
  35. package/src/common/HTTPClient.js +44 -2
  36. package/src/common/errorHandling.ts +1 -2
  37. package/src/common/fail.js +7 -3
  38. package/src/common/versionChecker.ts +16 -6
  39. package/src/constants/constants.js +1 -1
  40. package/src/constants/locales.js +17 -9
  41. package/src/constants.js +2 -2
  42. package/src/index.ts +5 -8
  43. package/src/lambda/lambda.ts +13 -1
  44. package/src/lambda/lambdaUtils.ts +1 -1
  45. package/src/scaAnalysis/common/auditReport.js +108 -0
  46. package/src/scaAnalysis/common/scaServicesUpload.js +56 -0
  47. package/src/scaAnalysis/javascript/index.js +4 -0
  48. package/src/scaAnalysis/javascript/scaServiceParser.js +145 -0
  49. package/src/scaAnalysis/ruby/analysis.js +137 -9
  50. package/src/scaAnalysis/ruby/index.js +6 -1
  51. package/src/scan/formatScanOutput.ts +5 -42
  52. package/src/scan/scanResults.js +1 -1
  53. package/src/{audit/languageAnalysisEngine/util → utils}/capabilities.js +0 -0
  54. package/src/{audit/languageAnalysisEngine/util → utils}/generalAPI.js +16 -6
  55. package/src/audit/report/commonReportingFunctions.ts +0 -355
@@ -12,7 +12,7 @@ const MEDIUM = 'MEDIUM';
12
12
  const HIGH = 'HIGH';
13
13
  const CRITICAL = 'CRITICAL';
14
14
  const APP_NAME = 'contrast';
15
- const APP_VERSION = '1.0.12';
15
+ const APP_VERSION = '1.0.14';
16
16
  const TIMEOUT = 120000;
17
17
  const HIGH_COLOUR = '#ff9900';
18
18
  const CRITICAL_COLOUR = '#e35858';
@@ -20,7 +20,7 @@ const en_locales = () => {
20
20
  unauthenticatedErrorHeader: '401 error - Unauthenticated',
21
21
  unauthenticatedErrorMessage: 'Please check the following keys are correct:\n--organization-id, --api-key or --authorization',
22
22
  badRequestErrorHeader: '400 error - Bad Request',
23
- badRequestErrorMessage: 'Please check the following key is correct: \n--application-id',
23
+ badRequestErrorMessage: 'Please check your parameters and try again',
24
24
  badRequestCatalogueErrorMessage: 'The application name already exists, please use a unique name',
25
25
  forbiddenRequestErrorHeader: '403 error - Forbidden',
26
26
  forbiddenRequestErrorMessage: 'You do not have permission to access this server.',
@@ -80,6 +80,7 @@ const en_locales = () => {
80
80
  constantsApplicationName: 'The name of the application cataloged by Contrast UI',
81
81
  constantsCatalogueApplication: 'Provide this if you want to catalogue an application',
82
82
  failOptionErrorMessage: ' FAIL - CVEs have been detected that match at least the cve_severity option specified.',
83
+ failOptionMessage: ' 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.',
83
84
  constantsLanguage: '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',
84
85
  constantsFilePath: `Specify a directory or the file where dependencies are declared. (By default, CodeSec will search for project files in the current directory.)`,
85
86
  constantsSilent: 'Silences JSON output.',
@@ -96,7 +97,7 @@ const en_locales = () => {
96
97
  constantsFail: 'Set the process to fail if this option is set in combination with --cve_severity.',
97
98
  failThresholdOptionErrorMessage: 'More than 0 vulnerabilities found',
98
99
  failSeverityOptionErrorMessage: ' FAIL - Results detected vulnerabilities over accepted severity level',
99
- constantsSeverity: '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.',
100
+ constantsSeverity: '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.',
100
101
  constantsCount: 'The number of CVEs that must be exceeded to fail a build',
101
102
  constantsHeader: 'CodeSec by Contrast Security',
102
103
  configHeader2: 'Config options',
@@ -113,7 +114,7 @@ const en_locales = () => {
113
114
  configHeader: 'Config',
114
115
  constantsConfigUsageContents: 'view / clear the configuration',
115
116
  constantsPrerequisitesContent: 'To scan a Java project you will need a .jar or .war file for analysis\n' +
116
- 'To scan a Javascript project you will need a .js or.zip file for analysis\n' +
117
+ 'To scan a Javascript project you will need a single .js or a .zip of multiple .js files\n' +
117
118
  'To scan a .NET c# webforms project you will need a .exe or a .zip file for analysis\n',
118
119
  constantsUsage: 'Usage',
119
120
  constantsUsageCommandExample: 'contrast [command] [options]',
@@ -213,16 +214,23 @@ const en_locales = () => {
213
214
  scanOptionsFileNameSummary: '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.',
214
215
  scanOptionsVerboseSummary: ' Returns extended information to the terminal.',
215
216
  authSuccessMessage: 'Authentication successful',
216
- runAuthSuccessMessage: "Now you can use CodeSec by Contrast \nRun: \n'contrast scan' on your file \n'contrast audit' on a file or directory,\n'contrast lambda' on an AWS function.\nor 'contrast help' to learn more about the capabilities.",
217
+ runAuthSuccessMessage: chalk.bold('CodeSec by Contrast') +
218
+ '\nScan, secure and ship your code in minutes for FREE. \n' +
219
+ chalk.bold('\nRun\n') +
220
+ chalk.bold('\ncontrast scan') +
221
+ " to run Contrast's industry leading SAST scanner. \nSupports Java, JavaScript and .Net \n" +
222
+ chalk.bold('\ncontrast audit') +
223
+ ' to find vulnerabilities in your open source dependencies.\nSupports Java, .NET, Node, Ruby, Python, Go and PHP \n' +
224
+ chalk.bold('\ncontrast lambda') +
225
+ ' to secure your AWS serverless functions. \nSupports Java and Python \n' +
226
+ chalk.bold('\ncontrast help') +
227
+ ' to learn more about the capabilities.',
217
228
  authWaitingMessage: 'Waiting for auth...',
218
229
  authTimedOutMessage: 'Auth Timed out, try again',
219
230
  zipErrorScan: 'We only support zip files for JAVASCRIPT language, please set the flag --language JAVASCRIPT',
220
231
  unknownFileErrorScan: 'Unsupported file selected for Scan.',
221
232
  foundScanFile: 'Found: %s',
222
- foundDetailedVulnerabilities: chalk.bold('%s Critical') +
223
- ' | ' +
224
- chalk.bold('%s High') +
225
- ' | %s Medium | %s Low | %s Note',
233
+ foundDetailedVulnerabilities: chalk.bold('%s') + ' | ' + chalk.bold('%s') + ' | %s | %s | %s ',
226
234
  requiredParams: 'All required parameters are not present.',
227
235
  timeoutScan: 'Timeout set to 5 minutes.',
228
236
  searchingScanFileDirectory: 'Searching for file to scan from %s...',
package/dist/constants.js CHANGED
@@ -101,7 +101,7 @@ const scanOptionDefinitions = [
101
101
  description: '{bold ' +
102
102
  i18n.__('constantsOptional') +
103
103
  '}: ' +
104
- i18n.__('failOptionErrorMessage')
104
+ i18n.__('failOptionMessage')
105
105
  },
106
106
  {
107
107
  name: 'severity',
@@ -219,7 +219,7 @@ const auditOptionDefinitions = [
219
219
  description: '{bold ' +
220
220
  i18n.__('constantsOptional') +
221
221
  '}: ' +
222
- i18n.__('failOptionErrorMessage')
222
+ i18n.__('failOptionMessage')
223
223
  },
224
224
  {
225
225
  name: 'severity',
package/dist/index.js CHANGED
@@ -45,7 +45,7 @@ const start = async () => {
45
45
  return;
46
46
  }
47
47
  config.set('numOfRuns', config.get('numOfRuns') + 1);
48
- if (config.get('numOfRuns') >= 1) {
48
+ if (config.get('numOfRuns') >= 10) {
49
49
  await (0, versionChecker_1.findLatestCLIVersion)(config);
50
50
  config.set('numOfRuns', 0);
51
51
  }
@@ -73,11 +73,13 @@ const start = async () => {
73
73
  const foundCommand = (0, errorHandling_1.findCommandOnError)(mainOptions._unknown);
74
74
  foundCommand
75
75
  ? console.log(`Unknown Command: Did you mean "${foundCommand}"? \nUse "${foundCommand} --help" for the full list of options`)
76
- : console.log(`Unknown Command: ${command} \nUse --help for the full list`);
76
+ : console.log(`\nUnknown Command: ${command} \n`);
77
+ console.log(mainUsageGuide);
77
78
  await (0, telemetry_1.sendTelemetryConfigAsConfObj)(config, command, argvMain, 'FAILURE', 'undefined');
78
79
  }
79
80
  else {
80
- console.log(`Unknown Command: ${command} \nUse --help for the full list`);
81
+ console.log(`\nUnknown Command: ${command}\n`);
82
+ console.log(mainUsageGuide);
81
83
  await (0, telemetry_1.sendTelemetryConfigAsConfObj)(config, command, argvMain, 'FAILURE', 'undefined');
82
84
  }
83
85
  process.exit(9);
@@ -23,6 +23,7 @@ const oraWrapper_1 = __importDefault(require("../utils/oraWrapper"));
23
23
  const analytics_1 = require("./analytics");
24
24
  const types_1 = require("./types");
25
25
  const constants_2 = require("../constants/constants");
26
+ const chalk_1 = __importDefault(require("chalk"));
26
27
  const failedStates = [
27
28
  'UNSUPPORTED',
28
29
  'EXCLUDED',
@@ -109,6 +110,7 @@ const processLambda = async (argv) => {
109
110
  }
110
111
  await (0, analytics_1.postAnalytics)(endCommandAnalytics).catch((error) => {
111
112
  });
113
+ postRunMessage();
112
114
  if (errorMsg) {
113
115
  process.exit(1);
114
116
  }
@@ -117,7 +119,7 @@ const processLambda = async (argv) => {
117
119
  exports.processLambda = processLambda;
118
120
  const getAvailableFunctions = async (lambdaOptions) => {
119
121
  const lambdas = await (0, lambdaUtils_1.getAllLambdas)(lambdaOptions);
120
- (0, lambdaUtils_1.printAvailableLambdas)(lambdas, { runtimes: ['python', 'java'] });
122
+ (0, lambdaUtils_1.printAvailableLambdas)(lambdas, { runtimes: ['python', 'java', 'node'] });
121
123
  };
122
124
  exports.getAvailableFunctions = getAvailableFunctions;
123
125
  const actualProcessLambda = async (lambdaOptions) => {
@@ -191,3 +193,8 @@ const handleLambdaHelp = () => {
191
193
  printHelpMessage();
192
194
  process.exit(0);
193
195
  };
196
+ const postRunMessage = () => {
197
+ console.log('\n' + chalk_1.default.underline.bold('Other Codesec Features:'));
198
+ console.log("'contrast scan' to run CodeSec’s industry leading SAST scanner");
199
+ console.log("'contrast audit' to find vulnerabilities in your open source dependencies\n");
200
+ };
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ const { getSeverityCounts, createSummaryMessageTop, printVulnInfo, getReportTable, getIssueRow, printNoVulnFoundMsg } = require('../../audit/report/commonReportingFunctions');
3
+ const { orderBy } = require('lodash');
4
+ const { assignBySeverity } = require('../../scan/formatScanOutput');
5
+ const chalk = require('chalk');
6
+ const { CE_URL } = require('../../constants/constants');
7
+ const common = require('../../common/fail');
8
+ const processAuditReport = (config, results) => {
9
+ let severityCounts = {};
10
+ if (results !== undefined) {
11
+ severityCounts = formatScaServicesReport(config, results);
12
+ }
13
+ if (config.fail) {
14
+ common.processFail(config, severityCounts);
15
+ }
16
+ };
17
+ const formatScaServicesReport = (config, results) => {
18
+ const projectOverviewCount = getSeverityCounts(results);
19
+ if (projectOverviewCount.total === 0) {
20
+ printNoVulnFoundMsg();
21
+ return projectOverviewCount;
22
+ }
23
+ else {
24
+ let total = 0;
25
+ const numberOfCves = results.length;
26
+ const table = getReportTable();
27
+ let contrastHeaderNumCounter = 0;
28
+ let assignPriorityToResults = results.map(result => assignBySeverity(result, result));
29
+ const numberOfVulns = results
30
+ .map(result => result.vulnerabilities)
31
+ .reduce((a, b) => {
32
+ return (total += b.length);
33
+ }, 0);
34
+ const outputOrderedByLowestSeverityAndLowestNumOfCvesFirst = orderBy(assignPriorityToResults, [
35
+ reportListItem => {
36
+ return reportListItem.priority;
37
+ },
38
+ reportListItem => {
39
+ return reportListItem.vulnerabilities.length;
40
+ }
41
+ ], ['asc', 'desc']);
42
+ for (const result of outputOrderedByLowestSeverityAndLowestNumOfCvesFirst) {
43
+ contrastHeaderNumCounter++;
44
+ const cvesNum = result.vulnerabilities.length;
45
+ const grammaticallyCorrectVul = result.vulnerabilities.length > 1 ? 'vulnerabilities' : 'vulnerability';
46
+ const headerColour = chalk.hex(result.colour);
47
+ const headerRow = [
48
+ headerColour(`CONTRAST-${contrastHeaderNumCounter.toString().padStart(3, '0')}`),
49
+ headerColour(`-`),
50
+ headerColour(`[${result.severity}] `) +
51
+ headerColour.bold(`${result.artifactName}`) +
52
+ ` introduces ${cvesNum} ${grammaticallyCorrectVul}`
53
+ ];
54
+ const adviceRow = [
55
+ chalk.bold(`Advice`),
56
+ chalk.bold(`:`),
57
+ `Change to version ${result.remediationAdvice.latestStableVersion}`
58
+ ];
59
+ let assignPriorityToVulns = result.vulnerabilities.map(result => assignBySeverity(result, result));
60
+ const issueRow = getIssueRow(assignPriorityToVulns);
61
+ table.push(headerRow, issueRow, adviceRow);
62
+ console.log();
63
+ }
64
+ console.log();
65
+ createSummaryMessageTop(numberOfCves, numberOfVulns);
66
+ console.log(table.toString() + '\n');
67
+ printVulnInfo(projectOverviewCount);
68
+ if (config.host !== CE_URL) {
69
+ console.log('\n' + chalk.bold('View your full dependency tree in Contrast:'));
70
+ console.log(`${config.host}/Contrast/static/ng/index.html#/${config.organizationId}/applications/${config.applicationId}/libs/dependency-tree`);
71
+ }
72
+ return projectOverviewCount;
73
+ }
74
+ };
75
+ module.exports = {
76
+ formatScaServicesReport,
77
+ processAuditReport
78
+ };
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ const commonApi = require('../../utils/commonApi');
3
+ const { APP_VERSION } = require('../../constants/constants');
4
+ const requestUtils = require('../../utils/requestUtils');
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
+ const client = commonApi.getHttpClient(config);
17
+ const reportID = await client
18
+ .scaServiceIngest(requestBody, config)
19
+ .then(res => {
20
+ if (res.statusCode === 201) {
21
+ return res.body.libraryIngestJobId;
22
+ }
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
+ let keepChecking = true;
32
+ let res;
33
+ while (keepChecking) {
34
+ res = await client.scaServiceReportStatus(config, reportID).then(res => {
35
+ console.log(res.statusCode);
36
+ console.log(res.body);
37
+ if (res.body.status == 'COMPLETED') {
38
+ keepChecking = false;
39
+ return client.scaServiceReport(config, reportID).then(res => {
40
+ return res.body;
41
+ });
42
+ }
43
+ });
44
+ if (!keepChecking) {
45
+ return res;
46
+ }
47
+ await requestUtils.sleep(5000);
48
+ }
49
+ return res;
50
+ };
51
+ module.exports = {
52
+ scaTreeUpload
53
+ };
@@ -2,6 +2,7 @@
2
2
  const analysis = require('./analysis');
3
3
  const i18n = require('i18n');
4
4
  const formatMessage = require('../common/formatMessage');
5
+ const scaServiceParser = require('./scaServiceParser');
5
6
  const jsAnalysis = async (config, languageFiles) => {
6
7
  checkForCorrectFiles(languageFiles);
7
8
  if (!config.file.endsWith('/')) {
@@ -12,6 +13,9 @@ const jsAnalysis = async (config, languageFiles) => {
12
13
  const buildNodeTree = async (config, files) => {
13
14
  let analysis = await readFiles(config, files);
14
15
  const rawNode = await parseFiles(config, files, analysis);
16
+ if (config.experimental) {
17
+ return scaServiceParser.parseJS(rawNode);
18
+ }
15
19
  return formatMessage.createJavaScriptTSMessage(rawNode);
16
20
  };
17
21
  const readFiles = async (config, files) => {
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ const parseJS = rawNode => {
3
+ let dependencyTree = {};
4
+ let combinedPackageJSONDep = {
5
+ ...rawNode.packageJSON?.dependencies,
6
+ ...rawNode.packageJSON?.devDependencies
7
+ };
8
+ let analyseLock = chooseLockFile(rawNode);
9
+ if (analyseLock.type === 'yarn') {
10
+ dependencyTree = yarnCreateDepTree(dependencyTree, combinedPackageJSONDep, analyseLock.lockFile, rawNode);
11
+ }
12
+ if (analyseLock.type === 'npm') {
13
+ dependencyTree = npmCreateDepTree(dependencyTree, combinedPackageJSONDep, analyseLock.lockFile, rawNode);
14
+ }
15
+ return dependencyTree;
16
+ };
17
+ const npmCreateDepTree = (dependencyTree, combinedPackageJSONDep, packageLock, rawNode) => {
18
+ for (const [key, value] of Object.entries(packageLock)) {
19
+ dependencyTree[key] = {
20
+ name: key,
21
+ version: getResolvedVersion(key, packageLock),
22
+ group: null,
23
+ isProduction: checkIfInPackageJSON(rawNode.packageJSON.dependencies, key),
24
+ directDependency: checkIfInPackageJSON(combinedPackageJSONDep, key),
25
+ dependencies: createNPMChildDependencies(packageLock, key)
26
+ };
27
+ }
28
+ return dependencyTree;
29
+ };
30
+ const yarnCreateDepTree = (dependencyTree, combinedPackageJSONDep, packageLock, rawNode) => {
31
+ for (const [key, value] of Object.entries(packageLock)) {
32
+ let gav = getNameFromGAV(key);
33
+ let nag = getDepNameWithoutVersion(key);
34
+ dependencyTree[key] = {
35
+ name: gav,
36
+ version: getResolvedVersion(key, packageLock),
37
+ group: null,
38
+ isProduction: checkIfInPackageJSON(rawNode.packageJSON.dependencies, nag),
39
+ directDependency: checkIfInPackageJSON(combinedPackageJSONDep, nag),
40
+ dependencies: createChildDependencies(packageLock, key)
41
+ };
42
+ }
43
+ return dependencyTree;
44
+ };
45
+ const chooseLockFile = rawNode => {
46
+ if (rawNode?.yarn?.yarnLockFile !== undefined) {
47
+ return { lockFile: rawNode?.yarn?.yarnLockFile?.object, type: 'yarn' };
48
+ }
49
+ else if (rawNode.npmLockFile !== undefined) {
50
+ return { lockFile: rawNode?.npmLockFile?.dependencies, type: 'npm' };
51
+ }
52
+ else {
53
+ return undefined;
54
+ }
55
+ };
56
+ const createKeyName = (dep, version) => {
57
+ return dep + '@' + version;
58
+ };
59
+ const checkIfInPackageJSON = (list, dep) => {
60
+ return Object.keys(list).includes(dep);
61
+ };
62
+ const createChildDependencies = (lockFileDep, currentDep) => {
63
+ let depArray = [];
64
+ if (lockFileDep[currentDep]?.dependencies) {
65
+ for (const [key, value] of Object.entries(lockFileDep[currentDep]?.dependencies)) {
66
+ depArray.push(createKeyName(key, value));
67
+ }
68
+ }
69
+ return depArray;
70
+ };
71
+ const createNPMChildDependencies = (lockFileDep, currentDep) => {
72
+ let depArray = [];
73
+ if (lockFileDep[currentDep]?.requires) {
74
+ for (const [key, value] of Object.entries(lockFileDep[currentDep]?.requires)) {
75
+ depArray.push(key);
76
+ }
77
+ }
78
+ return depArray;
79
+ };
80
+ const getDepNameWithoutVersion = depKey => {
81
+ let dependency = depKey.split('@');
82
+ if (dependency.length - 1 > 1) {
83
+ return '@' + dependency[1];
84
+ }
85
+ return dependency[0];
86
+ };
87
+ const getNameFromGAV = depKey => {
88
+ let dependency = depKey.split('/');
89
+ if (dependency.length == 2) {
90
+ dependency = getDepNameWithoutVersion(dependency[1]);
91
+ return dependency;
92
+ }
93
+ if (dependency.length == 1) {
94
+ dependency = getDepNameWithoutVersion(depKey);
95
+ return dependency;
96
+ }
97
+ return depKey;
98
+ };
99
+ const getResolvedVersion = (depKey, packageLock) => {
100
+ return packageLock[depKey]?.version;
101
+ };
102
+ module.exports = {
103
+ parseJS,
104
+ checkIfInPackageJSON,
105
+ getNameFromGAV,
106
+ getResolvedVersion,
107
+ chooseLockFile,
108
+ createNPMChildDependencies
109
+ };
@@ -1,6 +1,26 @@
1
1
  "use strict";
2
2
  const fs = require('fs');
3
3
  const i18n = require('i18n');
4
+ const getRubyDeps = (config, languageFiles) => {
5
+ try {
6
+ checkForCorrectFiles(languageFiles);
7
+ const parsedGem = readAndParseGemfile(config.file);
8
+ const parsedLock = readAndParseGemLockFile(config.file);
9
+ if (config.experimental) {
10
+ const rubyArray = removeRedundantAndPopulateDefinedElements(parsedLock.sources);
11
+ let rubyTree = createRubyTree(rubyArray);
12
+ findChildrenDependencies(rubyTree);
13
+ processRootDependencies(parsedLock.dependencies, rubyTree);
14
+ return rubyTree;
15
+ }
16
+ else {
17
+ return { gemfilesDependanceies: parsedGem, gemfileLock: parsedLock };
18
+ }
19
+ }
20
+ catch (err) {
21
+ throw err;
22
+ }
23
+ };
4
24
  const readAndParseGemfile = file => {
5
25
  const gemFile = fs.readFileSync(file + '/Gemfile', 'utf8');
6
26
  const rubyArray = gemFile.split('\n');
@@ -189,16 +209,89 @@ const buildSourceDependencyWithVersion = (whitespaceRegx, dependencyRegEx, line,
189
209
  }
190
210
  return dependencies;
191
211
  };
192
- const getRubyDeps = (config, languageFiles) => {
193
- try {
194
- checkForCorrectFiles(languageFiles);
195
- const parsedGem = readAndParseGemfile(config.file);
196
- const parsedLock = readAndParseGemLockFile(config.file);
197
- return { gemfilesDependanceies: parsedGem, gemfileLock: parsedLock };
212
+ const processRootDependencies = (rootDependencies, rubyTree) => {
213
+ const getParentObjectByName = queryToken => Object.values(rubyTree).filter(({ name }) => name === queryToken);
214
+ for (let parent in rootDependencies) {
215
+ let parentObject = getParentObjectByName(parent);
216
+ if (parentObject[0]) {
217
+ let gav = parentObject[0].group +
218
+ '/' +
219
+ parentObject[0].name +
220
+ '@' +
221
+ parentObject[0].version;
222
+ rubyTree[gav] = parentObject[0];
223
+ rubyTree[gav].directDependency = true;
224
+ }
198
225
  }
199
- catch (err) {
200
- throw err;
226
+ return rubyTree;
227
+ };
228
+ const createRubyTree = rubyArray => {
229
+ let rubyTree = {};
230
+ for (let x in rubyArray) {
231
+ let version = rubyArray[x].resolved;
232
+ let gav = rubyArray[x].group + '/' + rubyArray[x].name + '@' + version;
233
+ rubyTree[gav] = rubyArray[x];
234
+ rubyTree[gav].directDependency = false;
235
+ rubyTree[gav].version = version;
236
+ if (!rubyTree[gav].dependencies) {
237
+ rubyTree[gav].dependencies = [];
238
+ }
239
+ delete rubyTree[gav].resolved;
201
240
  }
241
+ return rubyTree;
242
+ };
243
+ const findChildrenDependencies = rubyTree => {
244
+ for (let dep in rubyTree) {
245
+ let unResolvedChildDepsKey = Object.keys(rubyTree[dep].dependencies);
246
+ rubyTree[dep].dependencies = resolveVersionOfChildDependencies(unResolvedChildDepsKey, rubyTree);
247
+ }
248
+ };
249
+ const resolveVersionOfChildDependencies = (unResolvedChildDepsKey, rubyObject) => {
250
+ const getParentObjectByName = queryToken => Object.values(rubyObject).filter(({ name }) => name === queryToken);
251
+ let resolvedChildrenDependencies = [];
252
+ for (let childDep in unResolvedChildDepsKey) {
253
+ let childDependencyName = unResolvedChildDepsKey[childDep];
254
+ let parent = getParentObjectByName(childDependencyName);
255
+ resolvedChildrenDependencies.push('null/' + childDependencyName + '@' + parent[0].version);
256
+ }
257
+ return resolvedChildrenDependencies;
258
+ };
259
+ const removeRedundantAndPopulateDefinedElements = deps => {
260
+ return deps.map(element => {
261
+ if (element.sourceType === 'GIT') {
262
+ delete element.sourceType;
263
+ delete element.remote;
264
+ delete element.platform;
265
+ element.group = null;
266
+ element.isProduction = true;
267
+ }
268
+ if (element.sourceType === 'GEM') {
269
+ element.group = null;
270
+ element.isProduction = true;
271
+ delete element.sourceType;
272
+ delete element.remote;
273
+ delete element.platform;
274
+ }
275
+ if (element.sourceType === 'PATH') {
276
+ element.group = null;
277
+ element.isProduction = true;
278
+ delete element.platform;
279
+ delete element.sourceType;
280
+ delete element.remote;
281
+ }
282
+ if (element.sourceType === 'BUNDLED WITH') {
283
+ element.group = null;
284
+ element.isProduction = true;
285
+ delete element.sourceType;
286
+ delete element.remote;
287
+ delete element.branch;
288
+ delete element.revision;
289
+ delete element.depthLevel;
290
+ delete element.specs;
291
+ delete element.platform;
292
+ }
293
+ return element;
294
+ });
202
295
  };
203
296
  const checkForCorrectFiles = languageFiles => {
204
297
  if (!languageFiles.includes('Gemfile.lock')) {
@@ -224,5 +317,9 @@ module.exports = {
224
317
  getPatchLevel,
225
318
  formatSourceArr,
226
319
  getSourceArray,
227
- checkForCorrectFiles
320
+ checkForCorrectFiles,
321
+ removeRedundantAndPopulateDefinedElements,
322
+ createRubyTree,
323
+ findChildrenDependencies,
324
+ processRootDependencies
228
325
  };
@@ -3,7 +3,12 @@ const analysis = require('./analysis');
3
3
  const { createRubyTSMessage } = require('../common/formatMessage');
4
4
  const rubyAnalysis = (config, languageFiles) => {
5
5
  const rubyDeps = analysis.getRubyDeps(config, languageFiles.RUBY);
6
- return createRubyTSMessage(rubyDeps);
6
+ if (config.experimental) {
7
+ return rubyDeps;
8
+ }
9
+ else {
10
+ return createRubyTSMessage(rubyDeps);
11
+ }
7
12
  };
8
13
  module.exports = {
9
14
  rubyAnalysis
@@ -3,16 +3,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.assignBySeverity = exports.stripTags = exports.getCodeFlowInfo = exports.getSourceLineNumber = exports.getLocationsSyncInfo = exports.editVulName = exports.getDefaultView = exports.formatLinks = exports.getProjectOverview = exports.formatScanOutput = void 0;
6
+ exports.assignBySeverity = exports.stripTags = exports.getCodeFlowInfo = exports.getSourceLineNumber = exports.getLocationsSyncInfo = exports.editVulName = exports.getDefaultView = exports.formatLinks = exports.formatScanOutput = void 0;
7
7
  const i18n_1 = __importDefault(require("i18n"));
8
8
  const chalk_1 = __importDefault(require("chalk"));
9
9
  const groupedResultsModel_1 = require("./models/groupedResultsModel");
10
10
  const lodash_1 = require("lodash");
11
11
  const cli_table3_1 = __importDefault(require("cli-table3"));
12
12
  const constants_1 = require("../constants/constants");
13
+ const commonReportingFunctions_1 = require("../audit/report/commonReportingFunctions");
13
14
  function formatScanOutput(scanResults) {
14
15
  const { scanResultsInstances } = scanResults;
15
- const projectOverview = getProjectOverview(scanResultsInstances);
16
+ const projectOverview = (0, commonReportingFunctions_1.getSeverityCounts)(scanResultsInstances.content);
16
17
  if (scanResultsInstances.content.length === 0) {
17
18
  console.log(i18n_1.default.__('scanNoVulnerabilitiesFound'));
18
19
  console.log(i18n_1.default.__('scanNoVulnerabilitiesFoundSecureCode'));
@@ -89,36 +90,10 @@ function formatScanOutput(scanResults) {
89
90
  console.log();
90
91
  });
91
92
  }
92
- printVulnInfo(projectOverview);
93
+ (0, commonReportingFunctions_1.printVulnInfo)(projectOverview);
93
94
  return projectOverview;
94
95
  }
95
96
  exports.formatScanOutput = formatScanOutput;
96
- function printVulnInfo(projectOverview) {
97
- const totalVulnerabilities = projectOverview.total;
98
- const vulMessage = totalVulnerabilities === 1 ? `vulnerability` : `vulnerabilities`;
99
- console.log(chalk_1.default.bold(`Found ${totalVulnerabilities} ${vulMessage}`));
100
- console.log(i18n_1.default.__('foundDetailedVulnerabilities', String(projectOverview.critical), String(projectOverview.high), String(projectOverview.medium), String(projectOverview.low), String(projectOverview.note)));
101
- }
102
- function getProjectOverview(scanResultsInstances) {
103
- const acc = {
104
- critical: 0,
105
- high: 0,
106
- medium: 0,
107
- low: 0,
108
- note: 0,
109
- total: 0
110
- };
111
- if (scanResultsInstances?.content &&
112
- scanResultsInstances.content.length > 0) {
113
- scanResultsInstances.content.forEach((i) => {
114
- acc[i.severity.toLowerCase()] += 1;
115
- acc.total += 1;
116
- return acc;
117
- });
118
- }
119
- return acc;
120
- }
121
- exports.getProjectOverview = getProjectOverview;
122
97
  function formatLinks(objName, entry) {
123
98
  const line = chalk_1.default.bold(objName + ' : ');
124
99
  if (entry.length === 1) {
@@ -66,7 +66,7 @@ const returnScanResults = async (config, codeArtifactId, newProject, timeout, st
66
66
  if (requestUtils.millisToSeconds(endTime) > timeout) {
67
67
  oraFunctions.failSpinner(startScanSpinner, 'Contrast Scan timed out at the specified ' + timeout + ' seconds.');
68
68
  const isCI = process.env.CONTRAST_CODESEC_CI
69
- ? JSON.parse(process.env.CONTRAST_CODESEC_CI)
69
+ ? JSON.parse(process.env.CONTRAST_CODESEC_CI.toLowerCase())
70
70
  : false;
71
71
  if (!isCI) {
72
72
  const retry = await retryScanPrompt();