@contrast/contrast 1.0.12 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/report/commonReportingFunctions.js +175 -116
- package/dist/audit/report/models/reportSeverityModel.js +3 -3
- package/dist/audit/report/reportingFeature.js +1 -10
- package/dist/audit/report/utils/reportUtils.js +4 -4
- package/dist/commands/audit/processAudit.js +10 -0
- package/dist/commands/scan/processScan.js +9 -0
- package/dist/commands/scan/sca/scaAnalysis.js +2 -0
- package/dist/common/HTTPClient.js +30 -2
- package/dist/common/errorHandling.js +1 -2
- package/dist/common/fail.js +7 -3
- package/dist/common/versionChecker.js +11 -5
- package/dist/constants/constants.js +1 -1
- package/dist/constants/locales.js +16 -8
- package/dist/constants.js +2 -2
- package/dist/index.js +5 -3
- package/dist/lambda/lambda.js +8 -1
- package/dist/scaAnalysis/common/auditReport.js +78 -0
- package/dist/scaAnalysis/common/scaServicesUpload.js +53 -0
- package/dist/scaAnalysis/javascript/index.js +4 -0
- package/dist/scaAnalysis/javascript/scaServiceParser.js +109 -0
- package/dist/scaAnalysis/ruby/analysis.js +106 -9
- package/dist/scaAnalysis/ruby/index.js +6 -1
- package/dist/scan/formatScanOutput.js +4 -29
- package/dist/scan/scanResults.js +1 -1
- package/dist/{audit/languageAnalysisEngine/util → utils}/capabilities.js +0 -0
- package/dist/{audit/languageAnalysisEngine/util → utils}/generalAPI.js +14 -5
- package/package.json +4 -1
- package/src/audit/report/commonReportingFunctions.js +432 -0
- package/src/audit/report/models/reportSeverityModel.ts +6 -6
- package/src/audit/report/reportingFeature.ts +2 -16
- package/src/audit/report/utils/reportUtils.ts +2 -8
- package/src/commands/audit/processAudit.ts +8 -0
- package/src/commands/scan/processScan.js +14 -0
- package/src/commands/scan/sca/scaAnalysis.js +9 -0
- package/src/common/HTTPClient.js +44 -2
- package/src/common/errorHandling.ts +1 -2
- package/src/common/fail.js +7 -3
- package/src/common/versionChecker.ts +16 -6
- package/src/constants/constants.js +1 -1
- package/src/constants/locales.js +17 -9
- package/src/constants.js +2 -2
- package/src/index.ts +5 -8
- package/src/lambda/lambda.ts +13 -1
- package/src/lambda/lambdaUtils.ts +1 -1
- package/src/scaAnalysis/common/auditReport.js +108 -0
- package/src/scaAnalysis/common/scaServicesUpload.js +56 -0
- package/src/scaAnalysis/javascript/index.js +4 -0
- package/src/scaAnalysis/javascript/scaServiceParser.js +145 -0
- package/src/scaAnalysis/ruby/analysis.js +137 -9
- package/src/scaAnalysis/ruby/index.js +6 -1
- package/src/scan/formatScanOutput.ts +5 -42
- package/src/scan/scanResults.js +1 -1
- package/src/{audit/languageAnalysisEngine/util → utils}/capabilities.js +0 -0
- package/src/{audit/languageAnalysisEngine/util → utils}/generalAPI.js +16 -6
- package/src/audit/report/commonReportingFunctions.ts +0 -355
|
@@ -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.
|
|
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
|
|
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: '
|
|
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
|
|
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:
|
|
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
|
|
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.__('
|
|
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.__('
|
|
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') >=
|
|
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(
|
|
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(
|
|
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);
|
package/dist/lambda/lambda.js
CHANGED
|
@@ -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
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
200
|
-
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
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) {
|
package/dist/scan/scanResults.js
CHANGED
|
@@ -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();
|
|
File without changes
|