@contrast/contrast 3.2.1 → 3.2.2

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.
@@ -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 = '3.2.1';
20
+ const APP_VERSION = '3.2.2';
21
21
  export const TIMEOUT = 120000;
22
22
  export const CRITICAL_PRIORITY = 1;
23
23
  export const HIGH_PRIORITY = 2;
@@ -249,6 +249,7 @@ export const en_locales = () => {
249
249
  auditServicesMessageForTS: 'View your vulnerable library list or full dependency tree in Contrast:',
250
250
  auditReportFailureMessage: 'Unable to generate library report',
251
251
  auditSCAAnalysisBegins: 'Contrast SCA audit started',
252
+ sarifAnalysis: 'Sarif analysis started...',
252
253
  auditSCAAnalysisComplete: 'Contrast audit complete',
253
254
  commonHelpHeader: 'Need More Help? NEW users',
254
255
  commonHelpEnterpriseHeader: 'Existing Contrast Licensed user?',
@@ -1,8 +1,14 @@
1
1
  import { getSarifConfig } from './sarifConfig.js';
2
- import { writeSarif } from './sarifWriter.js';
3
- import { logInfo } from '../common/logging.js';
2
+ import { convertSeverityInput, writeSarif } from './sarifHelper.js';
3
+ import { logDebug, logInfo } from '../common/logging.js';
4
4
  import { sarifUsageGuide } from './help.js';
5
- import { sarifVulns } from './sarifClient.js';
5
+ import { createSarifReportId, sarifReport, sarifStatus } from './sarifRequests.js';
6
+ import { got } from 'got';
7
+ import { sleep } from '../utils/requestUtils.js';
8
+ import { handleTimeout } from '../utils/commonApi.js';
9
+ import { performance } from 'perf_hooks';
10
+ import { failSpinner, returnOra, startSpinner, succeedSpinner } from '../utils/oraWrapper.js';
11
+ import i18n from 'i18n';
6
12
  // This filename could be set by a customer
7
13
  // Defaulted to be ingested in a GH workflow
8
14
  const outputFileName = 'contrast.sarif';
@@ -11,15 +17,42 @@ export const processSarif = async (contrastConf, argvMain) => {
11
17
  printHelpMessage();
12
18
  process.exit(0);
13
19
  }
20
+ const startTime = performance.now();
14
21
  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);
22
+ if (!config.applicationId) {
23
+ throw new Error(`Unable to generate sarif file without application ID.`);
24
+ }
25
+ const reportSpinner = returnOra(i18n.__('sarifAnalysis'));
26
+ startSpinner(reportSpinner);
27
+ await generateSarif(config, reportSpinner, startTime);
28
+ };
29
+ export const generateSarif = async (config, reportSpinner, startTime) => {
30
+ let severities = [];
31
+ if (config.severity) {
32
+ severities = convertSeverityInput(config.severity);
33
+ }
34
+ const response = await createSarifReportId(config, severities);
35
+ if (response.body.success === true) {
36
+ const reportId = response.body.uuid;
37
+ logDebug(config, `Async sarif endpoint hit successfully - ${response.body.messages}`);
38
+ let doPoll = true;
39
+ while (doPoll) {
40
+ await sleep(5000);
41
+ const statusRes = await sarifStatus(config, reportId);
42
+ if (statusRes.body.status === 'ACTIVE') {
43
+ doPoll = false;
44
+ let data = await sarifReport(config, reportId);
45
+ writeSarif(outputFileName, data.body);
46
+ succeedSpinner(reportSpinner, 'contrast.sarif file generated successfully');
47
+ }
48
+ // defaulting timeout to 30 minutes
49
+ handleTimeout(startTime, 1800, reportSpinner);
50
+ }
51
+ }
52
+ else {
53
+ logDebug(config, response.body.messages);
54
+ failSpinner(reportSpinner, 'Unable to generate contrast.sarif');
20
55
  }
21
- writeSarif(outputFileName, sarifData);
22
- logInfo('contrast.sarif file generated successfully');
23
56
  };
24
57
  const printHelpMessage = () => {
25
58
  logInfo(sarifUsageGuide);
@@ -45,8 +45,5 @@ export const sarifUsageGuide = commandLineUsage([
45
45
  {
46
46
  header: __('constantsAdvancedOptions'),
47
47
  optionList: commandLineDefinitions.sarifAdvancedOptionDefinitionsForHelp
48
- },
49
- commonHelpLinks()[0],
50
- commonHelpLinks()[1],
51
- commonHelpLinks()[2]
48
+ }
52
49
  ]);
@@ -0,0 +1,16 @@
1
+ import fs from 'fs';
2
+ export const writeSarif = (outputFileName, sarifData) => {
3
+ if (outputFileName) {
4
+ fs.writeFileSync(outputFileName, JSON.stringify(sarifData, null, 2), 'utf8');
5
+ }
6
+ else {
7
+ console.log(sarifData);
8
+ }
9
+ };
10
+ export const SEVERITIES = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'NOTE'];
11
+ export const convertSeverityInput = severity => {
12
+ const index = SEVERITIES.indexOf(severity.toUpperCase());
13
+ if (index === -1)
14
+ return SEVERITIES;
15
+ return SEVERITIES.slice(0, index + 1);
16
+ };
@@ -1,11 +1,11 @@
1
1
  import { buildBaseRequestOptions, ErrorType } from '../common/baseRequest.js';
2
2
  import { got } from 'got';
3
- const getSarifUrl = (config) => {
4
- return `${config.host}/Contrast/api/ng/organizations/${config.organizationId}/applications/${config.applicationId}/sarif`;
3
+ export const createSarifReportUrl = (config) => {
4
+ return `${config.host}/Contrast/api/ng/organizations/${config.organizationId}/applications/${config.applicationId}/sarif/async`;
5
5
  };
6
- export function getSarifVulns(config, severities) {
6
+ export const createSarifReportId = (config, severities) => {
7
7
  const options = buildBaseRequestOptions(config, ErrorType.GENERIC);
8
- options.url = getSarifUrl(config);
8
+ options.url = createSarifReportUrl(config);
9
9
  options.json = {
10
10
  metadataFilters: config.metadata ? config.metadata : [],
11
11
  // User can specify minimum severity, if none will include all
@@ -14,4 +14,20 @@ export function getSarifVulns(config, severities) {
14
14
  toolTypes: config.toolType ? [config.toolType] : ['SCA', 'ASSESS']
15
15
  };
16
16
  return got.post(options);
17
- }
17
+ };
18
+ export const sarifStatusUrl = (config, reportUuid) => {
19
+ return `${config.host}/Contrast/api/ng/organizations/${config.organizationId}/reports/${reportUuid}/status`;
20
+ };
21
+ export const sarifStatus = (config, reportId) => {
22
+ const options = buildBaseRequestOptions(config, ErrorType.GENERIC);
23
+ options.url = sarifStatusUrl(config, reportId);
24
+ return got.get(options);
25
+ };
26
+ export const getSarifDataUrl = (config, reportUuid) => {
27
+ return `${config.host}/Contrast/api/ng/${config.organizationId}/reports/${reportUuid}/download`;
28
+ };
29
+ export const sarifReport = async (config, reportId) => {
30
+ const options = buildBaseRequestOptions(config, ErrorType.GENERIC);
31
+ options.url = getSarifDataUrl(config, reportId);
32
+ return await got.post(options);
33
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/contrast",
3
- "version": "3.2.1",
3
+ "version": "3.2.2",
4
4
  "description": "Contrast Security's command line tool",
5
5
  "exports": "./dist/index.js",
6
6
  "type": "module",
@@ -77,7 +77,6 @@
77
77
  "js-yaml": "4.1.0",
78
78
  "lodash-es": "4.17.21",
79
79
  "log-symbols": "4.1.0",
80
- "node-sarif-builder": "^3.1.0",
81
80
  "open": "8.4.2",
82
81
  "ora": "6.3.1",
83
82
  "pkginfo": "0.4.1",
@@ -1,33 +0,0 @@
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
- }
@@ -1,199 +0,0 @@
1
- import { SarifBuilder, SarifRunBuilder, SarifResultBuilder, SarifRuleBuilder } from 'node-sarif-builder';
2
- import fs from 'fs';
3
- export const mapSeverity = contrastSeverity => {
4
- switch (contrastSeverity.toLowerCase()) {
5
- case 'critical':
6
- return 'error';
7
- case 'high':
8
- return 'warning';
9
- case 'medium':
10
- return 'note';
11
- case 'low':
12
- return 'note';
13
- case 'note':
14
- return 'note';
15
- default:
16
- return 'note';
17
- }
18
- };
19
- export const getFileFromTSDescription = description => {
20
- const regex = /\(([^)]+):\d*\)/;
21
- const match = regex.exec(description);
22
- return match ? match[1] : 'NOT AVAILABLE';
23
- };
24
- export const getLineFromTSDescription = description => {
25
- const regex = /\(.*:(\d*)\)/;
26
- const match = regex.exec(description);
27
- return match ? parseInt(match[1]) : 1;
28
- };
29
- export const getLogicalLocationFromTSDescription = description => {
30
- const regex = /(.*)\([^]+:\d*\)/;
31
- const match = regex.exec(description);
32
- return match ? match[1] + '()' : 'NOT AVAILABLE';
33
- };
34
- export const generateIastSarifRun = traces => {
35
- const sarifRunBuilderIast = new SarifRunBuilder().initSimple({
36
- toolDriverName: 'contrast-assess',
37
- toolDriverVersion: '1.0.0',
38
- url: 'https://contrastsecurity.com' // Url of your analyzer tool
39
- });
40
- if (traces.length === 0) {
41
- return sarifRunBuilderIast;
42
- }
43
- for (const trace of traces) {
44
- let extractedString = null;
45
- if (trace.trace.sink && trace.trace.sink.label) {
46
- extractedString = getFileFromTSDescription(trace.trace.sink.label);
47
- }
48
- const sarifResultBuilder = new SarifResultBuilder().initSimple({
49
- ruleId: trace.trace.rule_title,
50
- messageText: trace.trace.title,
51
- level: mapSeverity(trace.trace.severity)
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);
59
- if (extractedString) {
60
- sarifResultBuilder.setLocationArtifactUri({ uri: extractedString });
61
- }
62
- if (trace.trace.request) {
63
- sarifResultBuilder.result.webRequest = {
64
- target: trace.trace.request.uri,
65
- method: trace.trace.request.method,
66
- protocol: trace.trace.request.protocol,
67
- version: trace.trace.request.version
68
- };
69
- }
70
- sarifResultBuilder.result.provenance = {
71
- firstDetectionTimeUtc: new Date(trace.trace.discovered).toISOString(),
72
- lastDetectionTimeUtc: new Date(trace.trace.last_time_seen).toISOString()
73
- };
74
- sarifResultBuilder.result.properties = {
75
- vulnerability_id: trace.trace.uuid,
76
- contrast_severity: trace.trace.severity,
77
- contrast_status: trace.trace.status,
78
- contrast_substatus: trace.trace.sub_status
79
- };
80
- // Add stack trace
81
- sarifResultBuilder.result.codeFlows = [
82
- {
83
- threadFlows: trace.events.map(event => {
84
- return {
85
- locations: [
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
- },
102
- stack: {
103
- frames: event.stacktraces
104
- .filter(stacktrace => getFileFromTSDescription(stacktrace.description) !==
105
- 'NOT AVAILABLE')
106
- .map(stacktrace => {
107
- return {
108
- location: {
109
- physicalLocation: {
110
- artifactLocation: {
111
- uri: getFileFromTSDescription(stacktrace.description)
112
- },
113
- region: {
114
- startLine: getLineFromTSDescription(stacktrace.description)
115
- }
116
- },
117
- logicalLocations: [
118
- {
119
- fullyQualifiedName: getLogicalLocationFromTSDescription(stacktrace.description)
120
- }
121
- ]
122
- }
123
- };
124
- })
125
- }
126
- }
127
- ]
128
- };
129
- }),
130
- properties: {
131
- routeSignature: trace.routes[0] ? trace.routes[0].signature : ''
132
- }
133
- }
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
- });
151
- sarifRunBuilderIast.addResult(sarifResultBuilder);
152
- }
153
- return sarifRunBuilderIast;
154
- };
155
- export const generateScaSarifRun = cveList => {
156
- const sarifRunBuilderSca = new SarifRunBuilder().initSimple({
157
- toolDriverName: 'contrast-sca',
158
- toolDriverVersion: '1.0.0',
159
- url: 'https://contrastsecurity.com' // Url of your analyzer tool
160
- });
161
- if (cveList.length === 0) {
162
- return sarifRunBuilderSca;
163
- }
164
- for (const cve of cveList) {
165
- const sarifResultBuilder = new SarifResultBuilder().initSimple({
166
- ruleId: cve.name,
167
- messageText: cve.description,
168
- level: mapSeverity(cve.severityToUse)
169
- });
170
- sarifResultBuilder.setLocationArtifactUri({ uri: cve.library.file_name });
171
- sarifResultBuilder.result.properties = {
172
- contrast_severity: cve.severityToUse,
173
- library_version: cve.library.version,
174
- cvss_score: cve.cvss_3_severity_value,
175
- vector: cve.cvss_3_vector
176
- };
177
- sarifRunBuilderSca.addResult(sarifResultBuilder);
178
- }
179
- return sarifRunBuilderSca;
180
- };
181
- export const writeCombinedSarif = (traces, cveList, outputFileName) => {
182
- const sarifBuilder = new SarifBuilder();
183
- const sarifRunBuilderIast = generateIastSarifRun(traces);
184
- sarifBuilder.addRun(sarifRunBuilderIast);
185
- const sarifRunBuilderSca = generateScaSarifRun(cveList);
186
- sarifBuilder.addRun(sarifRunBuilderSca);
187
- const sarifJsonString = sarifBuilder.buildSarifJsonString({
188
- indent: true
189
- }); // indent:true if you like
190
- writeSarif(outputFileName, sarifJsonString);
191
- };
192
- export const writeSarif = (outputFileName, sarifData) => {
193
- if (outputFileName) {
194
- fs.writeFileSync(outputFileName, sarifData);
195
- }
196
- else {
197
- console.log(sarifData);
198
- }
199
- };