@contrast/contrast 2.1.7 → 2.3.0
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/cliConstants.js +50 -1
- package/dist/constants/constants.js +1 -1
- package/dist/constants/locales.js +7 -0
- package/dist/index.js +1 -1
- package/dist/sarif/generateSarif.js +22 -13
- package/dist/sarif/help.js +52 -0
- package/dist/sarif/sarifClient.js +33 -0
- package/dist/sarif/sarifConfig.js +8 -0
- package/dist/sarif/sarifRequests.js +17 -0
- package/dist/sarif/sarifWriter.js +52 -9
- package/dist/scaAnalysis/common/commonReportingFunctionsSca.js +4 -4
- package/dist/scaAnalysis/common/utils/reportUtilsSca.js +9 -4
- package/dist/scaAnalysis/javascript/analysis.js +9 -2
- package/dist/scaAnalysis/javascript/index.js +1 -6
- package/dist/scaAnalysis/javascript/v3LockFileParser.js +107 -0
- package/package.json +12 -12
- package/dist/sarif/sarifAssessRequests.js +0 -39
- package/dist/sarif/sarifIastClient.js +0 -65
- package/dist/sarif/sarifSCARequests.js +0 -25
- package/dist/sarif/sarifScaClient.js +0 -35
package/dist/cliConstants.js
CHANGED
|
@@ -343,6 +343,52 @@ const auditAdvancedOptionDefinitionsForHelp = [
|
|
|
343
343
|
i18n.__('constantsMavenSettingsPath')
|
|
344
344
|
}
|
|
345
345
|
];
|
|
346
|
+
const sarifAdvancedOptionDefinitionsForHelp = [
|
|
347
|
+
...sharedConnectionOptionDefinitions,
|
|
348
|
+
...sharedCertOptionDefinitions
|
|
349
|
+
];
|
|
350
|
+
const sarifOptionDefinitions = [
|
|
351
|
+
...sarifAdvancedOptionDefinitionsForHelp,
|
|
352
|
+
{
|
|
353
|
+
name: 'help',
|
|
354
|
+
alias: 'h',
|
|
355
|
+
type: Boolean
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
name: 'application-id',
|
|
359
|
+
description: '{bold ' +
|
|
360
|
+
i18n.__('constantsRequired') +
|
|
361
|
+
'}: ' +
|
|
362
|
+
i18n.__('constantsApplicationId')
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
name: 'metadata',
|
|
366
|
+
description: '{bold ' +
|
|
367
|
+
i18n.__('constantsOptional') +
|
|
368
|
+
'}: ' +
|
|
369
|
+
i18n.__('constantsMetadata')
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
name: 'severity',
|
|
373
|
+
type: severity => parseSeverity(severity),
|
|
374
|
+
description: '{bold ' +
|
|
375
|
+
i18n.__('constantsOptional') +
|
|
376
|
+
'}: ' +
|
|
377
|
+
i18n.__('constantsSarifSeverity')
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
name: 'tool-type',
|
|
381
|
+
description: '{bold ' +
|
|
382
|
+
i18n.__('constantsOptional') +
|
|
383
|
+
'}: ' +
|
|
384
|
+
i18n.__('constantsToolType')
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
name: 'debug',
|
|
388
|
+
alias: 'd',
|
|
389
|
+
type: Boolean
|
|
390
|
+
}
|
|
391
|
+
];
|
|
346
392
|
const auditOptionDefinitions = [
|
|
347
393
|
...auditAdvancedOptionDefinitionsForHelp,
|
|
348
394
|
{
|
|
@@ -509,12 +555,13 @@ const mainUsageGuide = commandLineUsage([
|
|
|
509
555
|
{ name: i18n.__('configName'), summary: i18n.__('helpConfigSummary') },
|
|
510
556
|
{ name: i18n.__('versionName'), summary: i18n.__('helpVersionSummary') },
|
|
511
557
|
{ name: i18n.__('auditName'), summary: i18n.__('helpAuditSummary') },
|
|
558
|
+
{ name: i18n.__('sarifName'), summary: i18n.__('helpSarifSummary') },
|
|
512
559
|
{ name: i18n.__('scanName'), summary: i18n.__('helpScanSummary') },
|
|
513
560
|
{ name: i18n.__('assessName'), summary: i18n.__('assessSummary') },
|
|
514
561
|
{ name: i18n.__('lambdaName'), summary: i18n.__('helpLambdaSummary') },
|
|
515
562
|
{ name: i18n.__('helpName'), summary: i18n.__('helpSummary') },
|
|
516
563
|
{ name: i18n.__('learnName'), summary: i18n.__('helpLearnSummary') },
|
|
517
|
-
|
|
564
|
+
{ name: i18n.__('sarifName'), summary: i18n.__('sarifSummary') },
|
|
518
565
|
{
|
|
519
566
|
name: i18n.__('configGenerate'),
|
|
520
567
|
summary: i18n.__('configGenerateSummary')
|
|
@@ -544,6 +591,8 @@ export const commandLineDefinitions = {
|
|
|
544
591
|
auditOptionDefinitions,
|
|
545
592
|
authOptionDefinitions,
|
|
546
593
|
configOptionDefinitions,
|
|
594
|
+
sarifOptionDefinitions,
|
|
595
|
+
sarifAdvancedOptionDefinitionsForHelp,
|
|
547
596
|
scanAdvancedOptionDefinitionsForHelp,
|
|
548
597
|
auditAdvancedOptionDefinitionsForHelp,
|
|
549
598
|
assessOptionDefinitions,
|
|
@@ -17,7 +17,7 @@ export const HIGH = 'HIGH';
|
|
|
17
17
|
export const CRITICAL = 'CRITICAL';
|
|
18
18
|
// App
|
|
19
19
|
export const APP_NAME = 'contrast';
|
|
20
|
-
const APP_VERSION = '2.
|
|
20
|
+
const APP_VERSION = '2.3.0';
|
|
21
21
|
export const TIMEOUT = 120000;
|
|
22
22
|
export const CRITICAL_PRIORITY = 1;
|
|
23
23
|
export const HIGH_PRIORITY = 2;
|
|
@@ -52,6 +52,7 @@ export const en_locales = () => {
|
|
|
52
52
|
failThresholdOptionErrorMessage: 'More than 0 vulnerabilities found',
|
|
53
53
|
failSeverityOptionErrorMessage: ' FAIL - Results detected vulnerabilities over accepted severity level',
|
|
54
54
|
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.',
|
|
55
|
+
constantsSarifSeverity: 'Set the severity level to filter the vulnerabilities included in the SARIF output. Severity levels are critical, high, medium, low or note.',
|
|
55
56
|
constantsHeader: `Contrast CLI @ v${getAppVersion()}`,
|
|
56
57
|
configHeader2: 'Config options',
|
|
57
58
|
clearHeader: '-c, --clear',
|
|
@@ -67,6 +68,8 @@ export const en_locales = () => {
|
|
|
67
68
|
constantsConfigUsageContents: 'view / clear the configuration',
|
|
68
69
|
constantsPrerequisitesContent: 'To scan a Java binary project you will need a .jar, .war or a zip of multiple .jar or .war files for analysis\n' +
|
|
69
70
|
'To scan source code you will need a .zip file containing the code for analysis',
|
|
71
|
+
constantsSarifPreRequisitesContent: 'To generate a SARIF file you will need the application-id of the application you want to generate the SARIF file for.\n' +
|
|
72
|
+
'To get the application-id, go to the Contrast UI, select the application you want to generate the SARIF file for, and copy the application-id from the URL.',
|
|
70
73
|
constantsUsage: 'Usage',
|
|
71
74
|
constantsUsageCommandExample: 'contrast [command] [options]',
|
|
72
75
|
constantsUsageCommandInfo: 'The file argument is optional. If no file is given, Contrast will search for a .jar, .war, .exe or .zip file in the working directory.\n',
|
|
@@ -90,6 +93,7 @@ export const en_locales = () => {
|
|
|
90
93
|
scanLabel: "adds a label to the scan - defaults to 'Started by CLI tool at current date'",
|
|
91
94
|
constantsIgnoreDev: 'Excludes developer dependencies from the results. All dependencies are included by default.',
|
|
92
95
|
constantsCommands: 'Commands',
|
|
96
|
+
constantsSarifOptions: 'SARIF Options',
|
|
93
97
|
constantsScanOptions: 'Scan Options',
|
|
94
98
|
constantsAssessOptions: 'Assess Options',
|
|
95
99
|
generatorConfigOptions: 'Generate Config Options',
|
|
@@ -114,6 +118,7 @@ export const en_locales = () => {
|
|
|
114
118
|
helpScanSummary: 'Searches for a .jar, .war, .js, or .zip file in the working directory, uploads files for analysis, and returns the results. \n[scan --help for options] Java, .NET, .NET Core, JavaScript are supported. ',
|
|
115
119
|
assessSummary: 'Reports vulnerabilities found at run-time on a server or microservice using a Contrast agent. \n [assess --help for options] Java, .NET, Node, Ruby, Python, Go, PHP are supported.',
|
|
116
120
|
helpLambdaSummary: 'Performs a static security scan on an AWS lambda function. [lambda --help for options] AWS Lambda - Java & Python are supported. ',
|
|
121
|
+
helpSarifSummary: 'Generates a SARIF file (contrast.sarif) for Contrast Assess and Contrast SCA results for the specified application-id.',
|
|
117
122
|
helpVersionSummary: 'Displays version of Contrast CLI',
|
|
118
123
|
helpConfigSummary: 'Displays stored credentials',
|
|
119
124
|
helpSummary: 'Displays usage guide',
|
|
@@ -171,6 +176,8 @@ export const en_locales = () => {
|
|
|
171
176
|
foundDetailedVulnerabilities: chalk.bold('%s') + ' | ' + chalk.bold('%s') + ' | %s | %s | %s ',
|
|
172
177
|
searchingScanFileDirectory: 'Searching for file to scan from %s...',
|
|
173
178
|
searchingAuditFileDirectory: 'Searching for package manager files from %s...',
|
|
179
|
+
sarifHeader: 'Contrast SARIF CLI',
|
|
180
|
+
sarifHeaderMessage: "Use 'contrast sarif' to generate a SARIF file for the given application-id.",
|
|
174
181
|
scanHeader: `Contrast Scan CLI`,
|
|
175
182
|
assessHeader: `Contrast Assess CLI`,
|
|
176
183
|
generateConfigHeader: `Contrast Generate Config`,
|
package/dist/index.js
CHANGED
|
@@ -78,7 +78,7 @@ const start = async () => {
|
|
|
78
78
|
return await processFingerprint(config, argvMain);
|
|
79
79
|
}
|
|
80
80
|
if (command === 'sarif') {
|
|
81
|
-
return processSarif(config, argvMain);
|
|
81
|
+
return await processSarif(config, argvMain);
|
|
82
82
|
}
|
|
83
83
|
if (command === 'learn') {
|
|
84
84
|
return processLearn();
|
|
@@ -1,17 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { getSarifConfig } from './sarifConfig.js';
|
|
2
|
+
import { writeSarif } from './sarifWriter.js';
|
|
3
|
+
import { logInfo } from '../common/logging.js';
|
|
4
|
+
import { sarifUsageGuide } from './help.js';
|
|
5
|
+
import { sarifVulns } from './sarifClient.js';
|
|
5
6
|
// This filename could be set by a customer
|
|
6
7
|
// Defaulted to be ingested in a GH workflow
|
|
7
|
-
const outputFileName = 'contrast.sarif
|
|
8
|
+
const outputFileName = 'contrast.sarif';
|
|
8
9
|
export const processSarif = async (contrastConf, argvMain) => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
if (argvMain.indexOf('--help') !== -1 || argvMain.indexOf('help') !== -1) {
|
|
11
|
+
printHelpMessage();
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
let config = await getSarifConfig(contrastConf, 'sarif', argvMain);
|
|
15
|
+
logInfo('Generating SARIF file');
|
|
16
|
+
const sarifData = await sarifVulns(config);
|
|
17
|
+
if (sarifData === '') {
|
|
18
|
+
logInfo('No SARIF data found');
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
writeSarif(outputFileName, sarifData);
|
|
22
|
+
logInfo('contrast.sarif file generated successfully');
|
|
23
|
+
};
|
|
24
|
+
const printHelpMessage = () => {
|
|
25
|
+
logInfo(sarifUsageGuide);
|
|
17
26
|
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import commandLineUsage from 'command-line-usage';
|
|
2
|
+
import i18n from 'i18n';
|
|
3
|
+
import { commandLineDefinitions } from '../cliConstants.js';
|
|
4
|
+
import { commonHelpLinks } from '../common/commonHelp.js';
|
|
5
|
+
const { __ } = i18n;
|
|
6
|
+
export const sarifUsageGuide = commandLineUsage([
|
|
7
|
+
{
|
|
8
|
+
header: __('constantsHeader')
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
header: __('sarifHeader'),
|
|
12
|
+
content: [__('sarifHeaderMessage')]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
header: __('constantsPrerequisitesHeader'),
|
|
16
|
+
content: [__('constantsSarifPreRequisitesContent')]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
header: __('constantsSarifOptions'),
|
|
20
|
+
optionList: commandLineDefinitions.sarifOptionDefinitions,
|
|
21
|
+
hide: [
|
|
22
|
+
'organization-id',
|
|
23
|
+
'api-key',
|
|
24
|
+
'authorization',
|
|
25
|
+
'host',
|
|
26
|
+
'proxy',
|
|
27
|
+
'cert',
|
|
28
|
+
'cacert',
|
|
29
|
+
'key',
|
|
30
|
+
'help',
|
|
31
|
+
'ff',
|
|
32
|
+
'cert-self-signed',
|
|
33
|
+
'debug',
|
|
34
|
+
'experimental',
|
|
35
|
+
'tags',
|
|
36
|
+
'sub-project',
|
|
37
|
+
'code',
|
|
38
|
+
'maven-settings-path',
|
|
39
|
+
'language',
|
|
40
|
+
'app-groups',
|
|
41
|
+
'branch',
|
|
42
|
+
'repo'
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
header: __('constantsAdvancedOptions'),
|
|
47
|
+
optionList: commandLineDefinitions.sarifAdvancedOptionDefinitionsForHelp
|
|
48
|
+
},
|
|
49
|
+
commonHelpLinks()[0],
|
|
50
|
+
commonHelpLinks()[1],
|
|
51
|
+
commonHelpLinks()[2]
|
|
52
|
+
]);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getSarifVulns } from './sarifRequests.js';
|
|
2
|
+
export const sarifVulns = async (config) => {
|
|
3
|
+
let severities = [];
|
|
4
|
+
if (config.severity) {
|
|
5
|
+
severities = convertSeverityInput(config.severity);
|
|
6
|
+
}
|
|
7
|
+
const response = await getSarifVulns(config, severities);
|
|
8
|
+
if (response.statusCode === 200) {
|
|
9
|
+
console.log('Retrieved SARIF data');
|
|
10
|
+
return JSON.stringify(response.body, null, 2);
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
console.log(`Error retrieving SARIF data`);
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
export function convertSeverityInput(severity) {
|
|
18
|
+
const severities = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'NOTE'];
|
|
19
|
+
switch (severity.toUpperCase()) {
|
|
20
|
+
case 'CRITICAL':
|
|
21
|
+
return severities.slice(0, 1);
|
|
22
|
+
case 'HIGH':
|
|
23
|
+
return severities.slice(0, 2);
|
|
24
|
+
case 'MEDIUM':
|
|
25
|
+
return severities.slice(0, 3);
|
|
26
|
+
case 'LOW':
|
|
27
|
+
return severities.slice(0, 4);
|
|
28
|
+
case 'NOTE':
|
|
29
|
+
return severities;
|
|
30
|
+
default:
|
|
31
|
+
return severities;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { getCommandLineArgsCustom } from '../utils/parsedCLIOptions.js';
|
|
2
|
+
import { commandLineDefinitions } from '../cliConstants.js';
|
|
3
|
+
import { getAuth } from '../utils/paramsUtil/paramHandler.js';
|
|
4
|
+
export const getSarifConfig = async (contrastConf, command, argv) => {
|
|
5
|
+
const sarifParameters = await getCommandLineArgsCustom(contrastConf, command, argv, commandLineDefinitions.sarifOptionDefinitions);
|
|
6
|
+
const paramsAuth = getAuth(sarifParameters);
|
|
7
|
+
return { ...paramsAuth, ...sarifParameters };
|
|
8
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { buildBaseRequestOptions, ErrorType } from '../common/baseRequest.js';
|
|
2
|
+
import { got } from 'got';
|
|
3
|
+
const getSarifUrl = (config) => {
|
|
4
|
+
return `${config.host}/Contrast/api/ng/organizations/${config.organizationId}/applications/${config.applicationId}/sarif`;
|
|
5
|
+
};
|
|
6
|
+
export function getSarifVulns(config, severities) {
|
|
7
|
+
const options = buildBaseRequestOptions(config, ErrorType.GENERIC);
|
|
8
|
+
options.url = getSarifUrl(config);
|
|
9
|
+
options.json = {
|
|
10
|
+
metadataFilters: config.metadata ? config.metadata : [],
|
|
11
|
+
// User can specify minimum severity, if none will include all
|
|
12
|
+
severities: severities,
|
|
13
|
+
// Allow user to specify toolTypes and default as both
|
|
14
|
+
toolTypes: config.toolType ? [config.toolType] : ['SCA', 'ASSESS']
|
|
15
|
+
};
|
|
16
|
+
return got.post(options);
|
|
17
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SarifBuilder, SarifRunBuilder, SarifResultBuilder } from 'node-sarif-builder';
|
|
1
|
+
import { SarifBuilder, SarifRunBuilder, SarifResultBuilder, SarifRuleBuilder } from 'node-sarif-builder';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
export const mapSeverity = contrastSeverity => {
|
|
4
4
|
switch (contrastSeverity.toLowerCase()) {
|
|
@@ -24,7 +24,7 @@ export const getFileFromTSDescription = description => {
|
|
|
24
24
|
export const getLineFromTSDescription = description => {
|
|
25
25
|
const regex = /\(.*:(\d*)\)/;
|
|
26
26
|
const match = regex.exec(description);
|
|
27
|
-
return match ? parseInt(match[1]) :
|
|
27
|
+
return match ? parseInt(match[1]) : 1;
|
|
28
28
|
};
|
|
29
29
|
export const getLogicalLocationFromTSDescription = description => {
|
|
30
30
|
const regex = /(.*)\([^]+:\d*\)/;
|
|
@@ -42,14 +42,20 @@ export const generateIastSarifRun = traces => {
|
|
|
42
42
|
}
|
|
43
43
|
for (const trace of traces) {
|
|
44
44
|
let extractedString = null;
|
|
45
|
-
if (trace.sink && trace.sink.label) {
|
|
46
|
-
extractedString = getFileFromTSDescription(trace.sink.label);
|
|
45
|
+
if (trace.trace.sink && trace.trace.sink.label) {
|
|
46
|
+
extractedString = getFileFromTSDescription(trace.trace.sink.label);
|
|
47
47
|
}
|
|
48
48
|
const sarifResultBuilder = new SarifResultBuilder().initSimple({
|
|
49
49
|
ruleId: trace.trace.rule_title,
|
|
50
50
|
messageText: trace.trace.title,
|
|
51
51
|
level: mapSeverity(trace.trace.severity)
|
|
52
52
|
});
|
|
53
|
+
const sarifRuleBuilder = new SarifRuleBuilder().initSimple({
|
|
54
|
+
ruleId: trace.trace.rule_title,
|
|
55
|
+
shortDescriptionText: trace.trace.title,
|
|
56
|
+
fullDescriptionText: trace.trace.title
|
|
57
|
+
});
|
|
58
|
+
sarifRunBuilderIast.addRule(sarifRuleBuilder);
|
|
53
59
|
if (extractedString) {
|
|
54
60
|
sarifResultBuilder.setLocationArtifactUri({ uri: extractedString });
|
|
55
61
|
}
|
|
@@ -78,8 +84,26 @@ export const generateIastSarifRun = traces => {
|
|
|
78
84
|
return {
|
|
79
85
|
locations: [
|
|
80
86
|
{
|
|
87
|
+
location: {
|
|
88
|
+
physicalLocation: {
|
|
89
|
+
artifactLocation: {
|
|
90
|
+
uri: getFileFromTSDescription(event.stacktraces[0].description)
|
|
91
|
+
},
|
|
92
|
+
region: {
|
|
93
|
+
startLine: getLineFromTSDescription(event.stacktraces[0].description)
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
logicalLocations: [
|
|
97
|
+
{
|
|
98
|
+
fullyQualifiedName: getLogicalLocationFromTSDescription(event.stacktraces[0].description)
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
},
|
|
81
102
|
stack: {
|
|
82
|
-
frames: event.stacktraces
|
|
103
|
+
frames: event.stacktraces
|
|
104
|
+
.filter(stacktrace => getFileFromTSDescription(stacktrace.description) !==
|
|
105
|
+
'NOT AVAILABLE')
|
|
106
|
+
.map(stacktrace => {
|
|
83
107
|
return {
|
|
84
108
|
location: {
|
|
85
109
|
physicalLocation: {
|
|
@@ -108,6 +132,22 @@ export const generateIastSarifRun = traces => {
|
|
|
108
132
|
}
|
|
109
133
|
}
|
|
110
134
|
];
|
|
135
|
+
sarifResultBuilder.result.locations = [];
|
|
136
|
+
sarifResultBuilder.result.locations.push({
|
|
137
|
+
physicalLocation: {
|
|
138
|
+
artifactLocation: {
|
|
139
|
+
uri: getFileFromTSDescription(trace.trace.sink.label)
|
|
140
|
+
},
|
|
141
|
+
region: {
|
|
142
|
+
startLine: getLineFromTSDescription(trace.trace.sink.label)
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
logicalLocations: [
|
|
146
|
+
{
|
|
147
|
+
fullyQualifiedName: getLogicalLocationFromTSDescription(trace.trace.sink.label)
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
});
|
|
111
151
|
sarifRunBuilderIast.addResult(sarifResultBuilder);
|
|
112
152
|
}
|
|
113
153
|
return sarifRunBuilderIast;
|
|
@@ -138,7 +178,7 @@ export const generateScaSarifRun = cveList => {
|
|
|
138
178
|
}
|
|
139
179
|
return sarifRunBuilderSca;
|
|
140
180
|
};
|
|
141
|
-
export const writeCombinedSarif = (traces, cveList,
|
|
181
|
+
export const writeCombinedSarif = (traces, cveList, outputFileName) => {
|
|
142
182
|
const sarifBuilder = new SarifBuilder();
|
|
143
183
|
const sarifRunBuilderIast = generateIastSarifRun(traces);
|
|
144
184
|
sarifBuilder.addRun(sarifRunBuilderIast);
|
|
@@ -147,10 +187,13 @@ export const writeCombinedSarif = (traces, cveList, output) => {
|
|
|
147
187
|
const sarifJsonString = sarifBuilder.buildSarifJsonString({
|
|
148
188
|
indent: true
|
|
149
189
|
}); // indent:true if you like
|
|
150
|
-
|
|
151
|
-
|
|
190
|
+
writeSarif(outputFileName, sarifJsonString);
|
|
191
|
+
};
|
|
192
|
+
export const writeSarif = (outputFileName, sarifData) => {
|
|
193
|
+
if (outputFileName) {
|
|
194
|
+
fs.writeFileSync(outputFileName, sarifData);
|
|
152
195
|
}
|
|
153
196
|
else {
|
|
154
|
-
console.log(
|
|
197
|
+
console.log(sarifData);
|
|
155
198
|
}
|
|
156
199
|
};
|
|
@@ -25,7 +25,7 @@ export const printFormattedOutputSca = (config, reportModelList, numberOfVulnera
|
|
|
25
25
|
const report = new ReportList();
|
|
26
26
|
for (const library of reportModelList) {
|
|
27
27
|
const { artifactName, version, vulnerabilities, remediationAdvice } = library;
|
|
28
|
-
const newOutputModel = new ReportModelStructure(new ReportCompositeKey(artifactName, version, findHighestSeverityCVESca(vulnerabilities), severityCountAllCVEsSca(vulnerabilities, new SeverityCountModel()).getTotal), vulnerabilities, remediationAdvice);
|
|
28
|
+
const newOutputModel = new ReportModelStructure(new ReportCompositeKey(artifactName, version, findHighestSeverityCVESca(vulnerabilities, config), severityCountAllCVEsSca(vulnerabilities, new SeverityCountModel()).getTotal), vulnerabilities, remediationAdvice);
|
|
29
29
|
report.reportOutputList.push(newOutputModel);
|
|
30
30
|
}
|
|
31
31
|
const outputOrderedByLowestSeverityAndLowestNumOfCvesFirst = orderBy(report.reportOutputList, [
|
|
@@ -44,7 +44,7 @@ export const printFormattedOutputSca = (config, reportModelList, numberOfVulnera
|
|
|
44
44
|
const numOfCVEs = reportModel.cveArray.length;
|
|
45
45
|
const table = getReportTable();
|
|
46
46
|
const header = buildHeader(highestSeverity, contrastHeaderNumCounter, libraryName, libraryVersion, numOfCVEs);
|
|
47
|
-
const body = buildBody(cveArray, remediationAdvice);
|
|
47
|
+
const body = buildBody(cveArray, remediationAdvice, config);
|
|
48
48
|
const reportOutputModel = new ReportOutputModel(header, body);
|
|
49
49
|
table.push(reportOutputModel.body.issueMessage, reportOutputModel.body.adviceMessage);
|
|
50
50
|
logInfo(`${reportOutputModel.header.vulnMessage} ${reportOutputModel.header.introducesMessage}`);
|
|
@@ -98,8 +98,8 @@ export function buildHeader(highestSeverity, contrastHeaderNum, libraryName, ver
|
|
|
98
98
|
const introducesMessage = `introduces ${numOfCVEs} ${vulnerabilityPluralised}`;
|
|
99
99
|
return new ReportOutputHeaderModel(vulnMessage, introducesMessage);
|
|
100
100
|
}
|
|
101
|
-
export function buildBody(cveArray, advice) {
|
|
102
|
-
const orderedCvesWithSeverityAssigned = orderByHighestPrioritySca(cveArray.map(cve => findCVESeveritySca(cve)));
|
|
101
|
+
export function buildBody(cveArray, advice, config) {
|
|
102
|
+
const orderedCvesWithSeverityAssigned = orderByHighestPrioritySca(cveArray.map(cve => findCVESeveritySca(cve, config)));
|
|
103
103
|
const issueMessage = getIssueRow(orderedCvesWithSeverityAssigned);
|
|
104
104
|
const adviceMessage = getAdviceRow(advice);
|
|
105
105
|
return new ReportOutputBodyModel(issueMessage, adviceMessage);
|
|
@@ -2,15 +2,16 @@ import { orderBy } from 'lodash-es';
|
|
|
2
2
|
import { CRITICAL_COLOUR, CRITICAL_PRIORITY, HIGH_COLOUR, HIGH_PRIORITY, LOW_COLOUR, LOW_PRIORITY, MEDIUM_COLOUR, MEDIUM_PRIORITY, NOTE_COLOUR, NOTE_PRIORITY } from '../../../constants/constants.js';
|
|
3
3
|
import { ReportSeverityModel } from '../../../audit/report/models/reportSeverityModel.js';
|
|
4
4
|
import { ScaReportModel } from '../models/ScaReportModel.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import { logDebug } from '../../../common/logging.js';
|
|
6
|
+
export function findHighestSeverityCVESca(cveArray, config) {
|
|
7
|
+
const mappedToReportSeverityModels = cveArray.map(cve => findCVESeveritySca(cve, config));
|
|
8
|
+
// order and get first
|
|
8
9
|
return orderBy(mappedToReportSeverityModels, cve => cve?.priority)[0];
|
|
9
10
|
}
|
|
10
11
|
export function orderByHighestPrioritySca(reportSeverityModel) {
|
|
11
12
|
return orderBy(reportSeverityModel, ['priority'], ['asc']);
|
|
12
13
|
}
|
|
13
|
-
export function findCVESeveritySca(vulnerabilityModel) {
|
|
14
|
+
export function findCVESeveritySca(vulnerabilityModel, config) {
|
|
14
15
|
const { name } = vulnerabilityModel;
|
|
15
16
|
if (vulnerabilityModel.cvss3Severity === 'CRITICAL' ||
|
|
16
17
|
vulnerabilityModel.severity === 'CRITICAL') {
|
|
@@ -32,6 +33,10 @@ export function findCVESeveritySca(vulnerabilityModel) {
|
|
|
32
33
|
vulnerabilityModel.severity === 'NOTE') {
|
|
33
34
|
return new ReportSeverityModel('NOTE', NOTE_PRIORITY, NOTE_COLOUR, name);
|
|
34
35
|
}
|
|
36
|
+
else {
|
|
37
|
+
logDebug(config, `Unknown severity for vulnerability ${name} with cvss3severity of ${vulnerabilityModel.cvss3Severity} and and cvss severity of: ${vulnerabilityModel.severity}`);
|
|
38
|
+
return new ReportSeverityModel('NOTE', NOTE_PRIORITY, NOTE_COLOUR, name);
|
|
39
|
+
}
|
|
35
40
|
}
|
|
36
41
|
export function convertGenericToTypedReportModelSca(reportArray) {
|
|
37
42
|
return reportArray.map((library) => {
|
|
@@ -3,6 +3,7 @@ import { load } from 'js-yaml';
|
|
|
3
3
|
import i18n from 'i18n';
|
|
4
4
|
import { formatKey } from '../../audit/nodeAnalysisEngine/parseYarn2LockFileContents.js';
|
|
5
5
|
import yarnpkg from '@yarnpkg/lockfile';
|
|
6
|
+
import { buildDependencyTreeForV3LockFile } from './v3LockFileParser.js';
|
|
6
7
|
export const readFile = async (config, languageFiles, nameOfFile) => {
|
|
7
8
|
const index = languageFiles.findIndex(v => v.includes(nameOfFile));
|
|
8
9
|
if (config.file) {
|
|
@@ -31,7 +32,7 @@ export const readYarn = async (config, languageFiles, nameOfFile) => {
|
|
|
31
32
|
throw new Error(i18n.__('nodeReadYarnLockFileError') + `${err.message}`);
|
|
32
33
|
}
|
|
33
34
|
};
|
|
34
|
-
export const parseNpmLockFile = async (npmLockFile) => {
|
|
35
|
+
export const parseNpmLockFile = async (npmLockFile, lockFileVersion) => {
|
|
35
36
|
try {
|
|
36
37
|
if (!npmLockFile.parsedPackages) {
|
|
37
38
|
npmLockFile.parsedPackages = {};
|
|
@@ -42,9 +43,15 @@ export const parseNpmLockFile = async (npmLockFile) => {
|
|
|
42
43
|
//e.g: node_modules/@aws-amplify/datastore/node_modules/uuid --> @aws-amplify/datastore/uuid
|
|
43
44
|
packageKey = packageKey.replace(/(node_modules\/)+/g, '');
|
|
44
45
|
}
|
|
46
|
+
if (lockFileVersion === 3) {
|
|
47
|
+
delete packageValue.license;
|
|
48
|
+
}
|
|
45
49
|
npmLockFile.parsedPackages[packageKey] = packageValue;
|
|
46
50
|
});
|
|
47
|
-
|
|
51
|
+
if (lockFileVersion === 3) {
|
|
52
|
+
buildDependencyTreeForV3LockFile(npmLockFile);
|
|
53
|
+
}
|
|
54
|
+
// remove base project package - unneeded
|
|
48
55
|
delete npmLockFile.parsedPackages[''];
|
|
49
56
|
return npmLockFile;
|
|
50
57
|
}
|
|
@@ -47,12 +47,7 @@ const parseFiles = async (config, files, js) => {
|
|
|
47
47
|
logDebug(config, message);
|
|
48
48
|
throw new Error(message);
|
|
49
49
|
}
|
|
50
|
-
|
|
51
|
-
const message = `NPM lockfileVersion 3 is not support with --legacy`;
|
|
52
|
-
logDebug(config, message);
|
|
53
|
-
throw new Error(message);
|
|
54
|
-
}
|
|
55
|
-
js.npmLockFile = await parseNpmLockFile(npmLockFile);
|
|
50
|
+
js.npmLockFile = await parseNpmLockFile(npmLockFile, currentLockFileVersion);
|
|
56
51
|
}
|
|
57
52
|
if (files.includes('yarn.lock')) {
|
|
58
53
|
js = await parseYarnLockFile(js);
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export const buildDependencyTreeForV3LockFile = npmLockFile => {
|
|
2
|
+
let npmLockFileWithDepsObject = createDepTreeObject(npmLockFile);
|
|
3
|
+
const distinguishedDeps = distinguishParentAndTransitiveDependencies(npmLockFileWithDepsObject.dependencies);
|
|
4
|
+
assignChildrenToParents(distinguishedDeps.transitive, distinguishedDeps.parents, npmLockFile);
|
|
5
|
+
};
|
|
6
|
+
export const createDepTreeObject = npmLockFile => {
|
|
7
|
+
const parsedPackages = npmLockFile.parsedPackages;
|
|
8
|
+
let dependencies = {};
|
|
9
|
+
for (let dep in parsedPackages) {
|
|
10
|
+
dependencies[dep] = {
|
|
11
|
+
version: parsedPackages[dep].version,
|
|
12
|
+
resolved: parsedPackages[dep].resolved,
|
|
13
|
+
integrity: parsedPackages[dep].integrity,
|
|
14
|
+
...(parsedPackages[dep].peer != null
|
|
15
|
+
? { peer: parsedPackages[dep].peer }
|
|
16
|
+
: {}),
|
|
17
|
+
...(parsedPackages[dep].optional != null
|
|
18
|
+
? { optional: parsedPackages[dep].optional }
|
|
19
|
+
: {}),
|
|
20
|
+
...(parsedPackages[dep].dev != null
|
|
21
|
+
? { dev: parsedPackages[dep].dev }
|
|
22
|
+
: {}),
|
|
23
|
+
...(parsedPackages[dep].dependencies != null
|
|
24
|
+
? { requires: parsedPackages[dep].dependencies }
|
|
25
|
+
: {})
|
|
26
|
+
};
|
|
27
|
+
npmLockFile.dependencies = dependencies;
|
|
28
|
+
delete npmLockFile.dependencies[''];
|
|
29
|
+
}
|
|
30
|
+
return npmLockFile;
|
|
31
|
+
};
|
|
32
|
+
export const distinguishParentAndTransitiveDependencies = dependencies => {
|
|
33
|
+
let listOfParentObjects = {};
|
|
34
|
+
let listOfChildObjects = {};
|
|
35
|
+
for (let library in dependencies) {
|
|
36
|
+
let library_name = filterDependency(library.split('/'));
|
|
37
|
+
if (library_name.length === 1) {
|
|
38
|
+
Object.assign(listOfParentObjects, {
|
|
39
|
+
[library_name]: dependencies[library]
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
if (library_name.join('/').includes('@') && library_name.length === 2) {
|
|
44
|
+
Object.assign(listOfParentObjects, {
|
|
45
|
+
[library_name.join('/')]: dependencies[library]
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
Object.assign(listOfChildObjects, {
|
|
50
|
+
[library_name.join('/')]: dependencies[library]
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { parents: listOfParentObjects, transitive: listOfChildObjects };
|
|
56
|
+
};
|
|
57
|
+
export const assignChildrenToParents = (transitiveDeps, parentDeps, npmLockFile) => {
|
|
58
|
+
for (let child in transitiveDeps) {
|
|
59
|
+
let group = child.split('/');
|
|
60
|
+
let name = group.pop();
|
|
61
|
+
if (parentDeps[group.join('/')] === undefined) {
|
|
62
|
+
let additionalName = group.pop();
|
|
63
|
+
if (parentDeps[group.join('/')] !== undefined) {
|
|
64
|
+
let newName = additionalName + '/' + name;
|
|
65
|
+
addDependencyToNode(parentDeps[group.join('/')], transitiveDeps[child], newName);
|
|
66
|
+
delete transitiveDeps[child];
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.log('WARNING: Unable to find parent dependency for ', child);
|
|
70
|
+
console.log('Adding as a parent dependency');
|
|
71
|
+
Object.assign(parentDeps, { [child]: transitiveDeps[child] });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
addDependencyToNode(parentDeps[group.join('/')], transitiveDeps[child], name);
|
|
76
|
+
delete transitiveDeps[child];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
delete npmLockFile.dependencies;
|
|
80
|
+
npmLockFile.dependencies = parentDeps;
|
|
81
|
+
return npmLockFile;
|
|
82
|
+
};
|
|
83
|
+
export const filterDependency = inputString => {
|
|
84
|
+
let foundFirstInstance = false;
|
|
85
|
+
// Filter the parts to remove any item containing '@' except for the first instance
|
|
86
|
+
const filteredParts = inputString.filter(part => {
|
|
87
|
+
if (part.includes('@')) {
|
|
88
|
+
if (!foundFirstInstance) {
|
|
89
|
+
foundFirstInstance = true;
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
});
|
|
96
|
+
return filteredParts;
|
|
97
|
+
};
|
|
98
|
+
export function addDependencyToNode(baseObject, newDependency, potentialChildDeps) {
|
|
99
|
+
// Ensure the dependencies node exists
|
|
100
|
+
if (!baseObject.dependencies) {
|
|
101
|
+
baseObject.dependencies = {};
|
|
102
|
+
}
|
|
103
|
+
// Add the new dependency
|
|
104
|
+
Object.assign(baseObject.dependencies, {
|
|
105
|
+
[potentialChildDeps]: newDependency
|
|
106
|
+
});
|
|
107
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/contrast",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Contrast Security's command line tool",
|
|
5
5
|
"exports": "./dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -30,15 +30,15 @@
|
|
|
30
30
|
"build-package": "yarn build && yarn build-binary && yarn package-binary",
|
|
31
31
|
"test": "export VITEST_MAX_THREADS=4 && export VITEST_MIN_THREADS=1 && vitest --dir ./tests/unit-tests/",
|
|
32
32
|
"test-debug": "export VITEST_MAX_THREADS=4 && export VITEST_MIN_THREADS=1 && vitest --dir ./tests/unit-tests/ --inspect-brk",
|
|
33
|
-
"test-int": "vitest --dir ./tests/integration-tests/ --
|
|
34
|
-
"test-int-scan": "vitest --dir ./tests/integration-tests/scan --
|
|
35
|
-
"test-int-audit": "vitest --dir ./tests/integration-tests/audit --
|
|
36
|
-
"test-int-scan-errors": "vitest ./tests/integration-tests/scan/scanLocalErrors.spec.js --
|
|
37
|
-
"test-int-scan-reports": "vitest ./tests/integration-tests/scan/scanReport.spec.js --
|
|
38
|
-
"test-int-audit-features": "vitest --dir ./tests/integration-tests/audit/auditFeatures/ --
|
|
39
|
-
"test-int-audit-projects": "vitest ./tests/integration-tests/audit/audit-projects.spec.js --
|
|
40
|
-
"test-int-fingerprint": "vitest ./tests/integration-tests/fingerprint/fingerprint.spec.js --
|
|
41
|
-
"test-int-repository": "vitest ./tests/integration-tests/fingerprint/repository.spec.js --
|
|
33
|
+
"test-int": "vitest --dir ./tests/integration-tests/ --pool=forks",
|
|
34
|
+
"test-int-scan": "vitest --dir ./tests/integration-tests/scan --pool=forks",
|
|
35
|
+
"test-int-audit": "vitest --dir ./tests/integration-tests/audit --pool=forks",
|
|
36
|
+
"test-int-scan-errors": "vitest ./tests/integration-tests/scan/scanLocalErrors.spec.js --pool=forks",
|
|
37
|
+
"test-int-scan-reports": "vitest ./tests/integration-tests/scan/scanReport.spec.js --pool=forks",
|
|
38
|
+
"test-int-audit-features": "vitest --dir ./tests/integration-tests/audit/auditFeatures/ --pool=forks",
|
|
39
|
+
"test-int-audit-projects": "vitest ./tests/integration-tests/audit/audit-projects.spec.js --pool=forks",
|
|
40
|
+
"test-int-fingerprint": "vitest ./tests/integration-tests/fingerprint/fingerprint.spec.js --pool=forks",
|
|
41
|
+
"test-int-repository": "vitest ./tests/integration-tests/fingerprint/repository.spec.js --pool=forks",
|
|
42
42
|
"format": "prettier --write \"**/*.{ts,tsx,js,json,md,yml}\" .eslintrc.*",
|
|
43
43
|
"check-format": "prettier --check \"**/*.{ts,tsx,js,json,md,yml,mjs,cjs}\" .eslintrc.*",
|
|
44
44
|
"coverage-local": "c8 --reporter=text mocha './tests/unit-tests/**/*.spec.js'",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"extract-licenses": "node scripts/extract-licenses",
|
|
48
48
|
"lambda-dev": "npx ts-node src/index.ts lambda",
|
|
49
49
|
"dev": "npx ts-node src/index.ts",
|
|
50
|
-
"proxy-tests": "vitest ./tests/integration-tests/proxy/proxy-coverage.spec.js --
|
|
50
|
+
"proxy-tests": "vitest ./tests/integration-tests/proxy/proxy-coverage.spec.js --pool=forks"
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|
|
53
53
|
"node": ">=18.16.0"
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
"ts-node": "^10.9.2",
|
|
117
117
|
"typescript": "5.1.6",
|
|
118
118
|
"uuid": "9.0.0",
|
|
119
|
-
"vitest": "
|
|
119
|
+
"vitest": "1.4.0"
|
|
120
120
|
},
|
|
121
121
|
"resolutions": {
|
|
122
122
|
"faker": "5.5.3",
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { buildBaseRequestOptions, ErrorType } from '../common/baseRequest.js';
|
|
2
|
-
import { got } from 'got';
|
|
3
|
-
const getAssessTraceIdsUrl = (config, offset, resultsLimit) => {
|
|
4
|
-
return `${config.host}/Contrast/api/ng/${config.organizationId}/orgtraces/filter/?offset=${offset}&limit=${resultsLimit}`;
|
|
5
|
-
};
|
|
6
|
-
export function getAssessTraceIds(config, offset, resultsLimit) {
|
|
7
|
-
const options = buildBaseRequestOptions(config, ErrorType.GENERIC);
|
|
8
|
-
options.url = getAssessTraceIdsUrl(config, offset, resultsLimit);
|
|
9
|
-
options.json = {
|
|
10
|
-
applicationID: config.applicationId,
|
|
11
|
-
// metadataFilters: sessionMetadata,
|
|
12
|
-
severities: [config.severity]
|
|
13
|
-
};
|
|
14
|
-
return got.post(options);
|
|
15
|
-
}
|
|
16
|
-
const getAssessTraceDetailsUrl = (config, traceId) => {
|
|
17
|
-
return `${config.host}/Contrast/api/ng/${config.organizationId}/traces/${config.applicationId}/filter/${traceId}?expand=events,request,sink,${traceId},skip_links`;
|
|
18
|
-
};
|
|
19
|
-
export function getAssessTraceDetails(config, traceId) {
|
|
20
|
-
const options = buildBaseRequestOptions(config, ErrorType.GENERIC);
|
|
21
|
-
options.url = getAssessTraceDetailsUrl(config, traceId);
|
|
22
|
-
return got.get(options);
|
|
23
|
-
}
|
|
24
|
-
const getAssessTraceEventsUrl = (config, traceId, eventId) => {
|
|
25
|
-
return `${config.host}/Contrast/api/ng/${config.organizationId}/traces/${traceId}/events/${eventId}/details`;
|
|
26
|
-
};
|
|
27
|
-
export function getAssessTraceEvents(config, traceId, eventId) {
|
|
28
|
-
const options = buildBaseRequestOptions(config, ErrorType.GENERIC);
|
|
29
|
-
options.url = getAssessTraceEventsUrl(config, traceId, eventId);
|
|
30
|
-
return got.get(options);
|
|
31
|
-
}
|
|
32
|
-
const getAssessTraceRoutesUrl = (config, traceId) => {
|
|
33
|
-
return `${config.host}/Contrast/api/ng/${config.organizationId}/traces/${config.applicationId}/trace/${traceId}/routes`;
|
|
34
|
-
};
|
|
35
|
-
export function getAssessTraceRoutes(config, traceId) {
|
|
36
|
-
const options = buildBaseRequestOptions(config, ErrorType.GENERIC);
|
|
37
|
-
options.url = getAssessTraceRoutesUrl(config, traceId);
|
|
38
|
-
return got.get(options);
|
|
39
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { getAssessTraceDetails, getAssessTraceEvents, getAssessTraceIds, getAssessTraceRoutes } from './sarifAssessRequests.js';
|
|
2
|
-
const resultsLimit = 50;
|
|
3
|
-
let offset = 0;
|
|
4
|
-
export const iastData = async (config) => {
|
|
5
|
-
const filteredTraceIds = await getTraceIds(config, offset, resultsLimit);
|
|
6
|
-
console.log(`Found ${filteredTraceIds.length} traces`);
|
|
7
|
-
let completeTraces = [];
|
|
8
|
-
for (const traceId of filteredTraceIds) {
|
|
9
|
-
const traceDetails = await getTraceDetails(config, traceId);
|
|
10
|
-
const traceRoutes = await getTraceRoutes(config, traceId);
|
|
11
|
-
let eventsWithFrames = [];
|
|
12
|
-
if (traceDetails.events) {
|
|
13
|
-
for (const eventSummary of traceDetails.events) {
|
|
14
|
-
const eventDetails = await getTraceEvents(config, traceId, eventSummary.eventId);
|
|
15
|
-
eventsWithFrames.push(eventDetails);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
completeTraces.push({
|
|
19
|
-
trace: traceDetails,
|
|
20
|
-
events: eventsWithFrames,
|
|
21
|
-
routes: traceRoutes
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
return completeTraces;
|
|
25
|
-
};
|
|
26
|
-
export async function getTraceIds(config, offset, resultsLimit) {
|
|
27
|
-
let hasResults = true;
|
|
28
|
-
let pivotedData = [];
|
|
29
|
-
while (hasResults) {
|
|
30
|
-
const response = getAssessTraceIds(config, offset, resultsLimit);
|
|
31
|
-
const responseBody = await response.json();
|
|
32
|
-
if (responseBody.traces == null) {
|
|
33
|
-
console.log('No libraries found');
|
|
34
|
-
return pivotedData;
|
|
35
|
-
}
|
|
36
|
-
const result = responseBody.traces;
|
|
37
|
-
const traceIds = result.map(trace => {
|
|
38
|
-
return trace.uuid;
|
|
39
|
-
});
|
|
40
|
-
pivotedData = pivotedData.concat(traceIds);
|
|
41
|
-
if (result.length < resultsLimit) {
|
|
42
|
-
hasResults = false;
|
|
43
|
-
}
|
|
44
|
-
offset += resultsLimit;
|
|
45
|
-
}
|
|
46
|
-
return pivotedData;
|
|
47
|
-
}
|
|
48
|
-
export async function getTraceDetails(config, traceId) {
|
|
49
|
-
console.log(`Getting trace details for ${traceId} of application ${config.applicationId}`);
|
|
50
|
-
const response = getAssessTraceDetails(config, traceId);
|
|
51
|
-
const responseBody = await response.json();
|
|
52
|
-
return responseBody.trace;
|
|
53
|
-
}
|
|
54
|
-
export async function getTraceEvents(config, traceId, eventId) {
|
|
55
|
-
const response = getAssessTraceEvents(config, traceId, eventId);
|
|
56
|
-
console.log(`Getting trace events for ${eventId} of trace ${traceId}`);
|
|
57
|
-
const responseBody = await response.json();
|
|
58
|
-
return responseBody.event;
|
|
59
|
-
}
|
|
60
|
-
export async function getTraceRoutes(config, traceId) {
|
|
61
|
-
console.log(`Getting trace route for ${traceId} of application ${config.applicationId}`);
|
|
62
|
-
const response = getAssessTraceRoutes(config, traceId);
|
|
63
|
-
const responseBody = await response.json();
|
|
64
|
-
return responseBody.routes;
|
|
65
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { buildBaseRequestOptions, ErrorType } from '../common/baseRequest.js';
|
|
2
|
-
import { got } from 'got';
|
|
3
|
-
const getSCAVulnsUrl = (config, offset, resultsLimit) => {
|
|
4
|
-
return `${config.host}/Contrast/api/ng/${config.organizationId}/libraries/filter?expand=skip_links,apps,quickFilters,vulns,status,usage_counts&offset=${offset}&limit=${resultsLimit}&sort=score`;
|
|
5
|
-
};
|
|
6
|
-
export function getSCAVulns(config, offset, resultsLimit) {
|
|
7
|
-
const options = buildBaseRequestOptions(config, ErrorType.GENERIC);
|
|
8
|
-
options.url = getSCAVulnsUrl(config, offset, resultsLimit);
|
|
9
|
-
options.json = {
|
|
10
|
-
q: '',
|
|
11
|
-
quickFilter: 'VULNERABLE',
|
|
12
|
-
apps: [config.applicationId],
|
|
13
|
-
servers: [],
|
|
14
|
-
environments: [],
|
|
15
|
-
grades: [],
|
|
16
|
-
languages: [],
|
|
17
|
-
licenses: [],
|
|
18
|
-
status: [],
|
|
19
|
-
severities: [config.severity],
|
|
20
|
-
tags: [],
|
|
21
|
-
includeUnused: false,
|
|
22
|
-
includeUsed: false
|
|
23
|
-
};
|
|
24
|
-
return got.post(options);
|
|
25
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { getSCAVulns } from './sarifSCARequests.js';
|
|
2
|
-
const resultsLimit = 50;
|
|
3
|
-
let offset = 0;
|
|
4
|
-
export const scaCVES = async (config) => {
|
|
5
|
-
let hasResults = true;
|
|
6
|
-
let pivotedData = [];
|
|
7
|
-
while (hasResults) {
|
|
8
|
-
const response = getSCAVulns(config, offset, resultsLimit);
|
|
9
|
-
const responseBody = await response.json();
|
|
10
|
-
if (responseBody.libraries == null) {
|
|
11
|
-
console.log('No libraries found');
|
|
12
|
-
return pivotedData;
|
|
13
|
-
}
|
|
14
|
-
// Finds all libraries with a vulnerability of specified severity
|
|
15
|
-
const result = responseBody.libraries.flatMap(item => {
|
|
16
|
-
const { vulns } = item;
|
|
17
|
-
return vulns.flatMap(vuln => {
|
|
18
|
-
vuln.library = item;
|
|
19
|
-
return vuln;
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
// All vulns associated with the library will be returned,
|
|
23
|
-
// so we must filter by severity again
|
|
24
|
-
for (const vuln of result) {
|
|
25
|
-
if (vuln.severityToUse.toLowerCase() === config.severity) {
|
|
26
|
-
pivotedData.push(vuln);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
if (result.length < resultsLimit) {
|
|
30
|
-
hasResults = false;
|
|
31
|
-
}
|
|
32
|
-
offset += resultsLimit;
|
|
33
|
-
}
|
|
34
|
-
return pivotedData;
|
|
35
|
-
};
|