@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
@@ -171,9 +171,9 @@ HTTPClient.prototype.getScanProjectById = function getScanProjectById(config) {
171
171
  return requestUtils.sendRequest({ method: 'get', options })
172
172
  }
173
173
 
174
- HTTPClient.prototype.getGlobalProperties = function getGlobalProperties() {
174
+ HTTPClient.prototype.getGlobalProperties = function getGlobalProperties(host) {
175
175
  const options = _.cloneDeep(this.requestOptions)
176
- let url = createGlobalPropertiesUrl(options.uri)
176
+ let url = createGlobalPropertiesUrl(host)
177
177
  options.url = url
178
178
  return requestUtils.sendRequest({ method: 'get', options })
179
179
  }
@@ -216,6 +216,36 @@ HTTPClient.prototype.sendSnapshot = function sendSnapshot(requestBody, config) {
216
216
  return requestUtils.sendRequest({ method: 'post', options })
217
217
  }
218
218
 
219
+ HTTPClient.prototype.scaServiceIngest = function scaServiceIngest(
220
+ requestBody,
221
+ config
222
+ ) {
223
+ const options = _.cloneDeep(this.requestOptions)
224
+ let url = createScaServiceIngestURL(config)
225
+ options.url = url
226
+ options.body = requestBody
227
+ return requestUtils.sendRequest({ method: 'post', options })
228
+ }
229
+ HTTPClient.prototype.scaServiceReport = function scaServiceReport(
230
+ config,
231
+ reportId
232
+ ) {
233
+ const options = _.cloneDeep(this.requestOptions)
234
+ let url = createScaServiceReportURL(config, reportId)
235
+ options.url = url
236
+ return requestUtils.sendRequest({ method: 'get', options })
237
+ }
238
+
239
+ HTTPClient.prototype.scaServiceReportStatus = function scaServiceReport(
240
+ config,
241
+ reportId
242
+ ) {
243
+ const options = _.cloneDeep(this.requestOptions)
244
+ let url = createScaServiceReportStatusURL(config, reportId)
245
+ options.url = url
246
+ return requestUtils.sendRequest({ method: 'get', options })
247
+ }
248
+
219
249
  HTTPClient.prototype.getReportById = function getReportById(config, reportId) {
220
250
  const options = _.cloneDeep(this.requestOptions)
221
251
  if (config.ignoreDev) {
@@ -416,6 +446,18 @@ function createSnapshotURL(config) {
416
446
  return `${config.host}/Contrast/api/ng/sca/organizations/${config.organizationId}/applications/${config.applicationId}/snapshots`
417
447
  }
418
448
 
449
+ function createScaServiceReportURL(config, reportId) {
450
+ return ``
451
+ }
452
+
453
+ function createScaServiceReportStatusURL(config, reportId) {
454
+ return ``
455
+ }
456
+
457
+ function createScaServiceIngestURL(config) {
458
+ return ``
459
+ }
460
+
419
461
  const createAppCreateURL = config => {
420
462
  return `${config.host}/Contrast/api/ng/sca/organizations/${config.organizationId}/applications/create`
421
463
  }
@@ -38,8 +38,7 @@ const reportFailureError = () => {
38
38
  console.log(i18n.__('auditReportFailureMessage'))
39
39
  }
40
40
 
41
- const genericError = (missingCliOption: string) => {
42
- console.log(missingCliOption)
41
+ const genericError = () => {
43
42
  console.error(i18n.__('genericErrorMessage'))
44
43
  process.exit(1)
45
44
  }
@@ -25,11 +25,15 @@ const isSeverityViolation = (severity, reportResults) => {
25
25
  count += reportResults.high + reportResults.critical
26
26
  break
27
27
  case 'medium':
28
- count += reportResults.medium + reportResults.low + reportResults.critical
28
+ count +=
29
+ reportResults.medium + reportResults.high + reportResults.critical
29
30
  break
30
31
  case 'low':
31
32
  count +=
32
- reportResults.high + reportResults.critical + reportResults.medium
33
+ reportResults.high +
34
+ reportResults.critical +
35
+ reportResults.medium +
36
+ reportResults.low
33
37
  break
34
38
  case 'note':
35
39
  if (reportResults.note == reportResults.total) {
@@ -51,7 +55,7 @@ const failPipeline = (message = '') => {
51
55
  ' *********************************\n' +
52
56
  i18n.__(message)
53
57
  )
54
- process.exit(1)
58
+ process.exit(2)
55
59
  }
56
60
 
57
61
  const parseSeverity = severity => {
@@ -6,7 +6,7 @@ import commonApi from '../utils/commonApi'
6
6
  import { constants } from 'http2'
7
7
  import { ContrastConf } from '../utils/getConfig'
8
8
 
9
- const getLatestVersion = async (config: any) => {
9
+ export const getLatestVersion = async (config: ContrastConf) => {
10
10
  const client = commonApi.getHttpClient(config)
11
11
  try {
12
12
  const res = await client.getLatestVersion()
@@ -14,18 +14,28 @@ const getLatestVersion = async (config: any) => {
14
14
  return res.body
15
15
  }
16
16
  } catch (e) {
17
- return
17
+ return undefined
18
18
  }
19
19
  }
20
20
 
21
21
  export async function findLatestCLIVersion(config: ContrastConf) {
22
22
  const isCI = process.env.CONTRAST_CODESEC_CI
23
- ? JSON.parse(process.env.CONTRAST_CODESEC_CI)
23
+ ? JSON.parse(process.env.CONTRAST_CODESEC_CI.toLowerCase())
24
24
  : false
25
+
25
26
  if (!isCI) {
26
- let latestCLIVersion: string = await getLatestVersion(config)
27
- //strip key
28
- latestCLIVersion = latestCLIVersion.substring(8)
27
+ let latestCLIVersion = await getLatestVersion(config)
28
+
29
+ if (latestCLIVersion === undefined) {
30
+ config.set('numOfRuns', 0)
31
+ console.log(
32
+ 'Failed to retrieve latest version info. Continuing execution.'
33
+ )
34
+ return
35
+ }
36
+
37
+ //strip key and remove new lines
38
+ latestCLIVersion = latestCLIVersion.substring(8).replace('\n', '')
29
39
 
30
40
  if (semver.lt(APP_VERSION, latestCLIVersion)) {
31
41
  const updateAvailableMessage = `Update available ${chalk.yellow(
@@ -14,7 +14,7 @@ const HIGH = 'HIGH'
14
14
  const CRITICAL = 'CRITICAL'
15
15
  // App
16
16
  const APP_NAME = 'contrast'
17
- const APP_VERSION = '1.0.12'
17
+ const APP_VERSION = '1.0.14'
18
18
  const TIMEOUT = 120000
19
19
  const HIGH_COLOUR = '#ff9900'
20
20
  const CRITICAL_COLOUR = '#e35858'
@@ -26,8 +26,7 @@ const en_locales = () => {
26
26
  unauthenticatedErrorMessage:
27
27
  'Please check the following keys are correct:\n--organization-id, --api-key or --authorization',
28
28
  badRequestErrorHeader: '400 error - Bad Request',
29
- badRequestErrorMessage:
30
- 'Please check the following key is correct: \n--application-id',
29
+ badRequestErrorMessage: 'Please check your parameters and try again',
31
30
  badRequestCatalogueErrorMessage:
32
31
  'The application name already exists, please use a unique name',
33
32
  forbiddenRequestErrorHeader: '403 error - Forbidden',
@@ -123,6 +122,8 @@ const en_locales = () => {
123
122
  'Provide this if you want to catalogue an application',
124
123
  failOptionErrorMessage:
125
124
  ' FAIL - CVEs have been detected that match at least the cve_severity option specified.',
125
+ failOptionMessage:
126
+ ' 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.',
126
127
  constantsLanguage:
127
128
  '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',
128
129
  constantsFilePath: `Specify a directory or the file where dependencies are declared. (By default, CodeSec will search for project files in the current directory.)`,
@@ -148,7 +149,7 @@ const en_locales = () => {
148
149
  failSeverityOptionErrorMessage:
149
150
  ' FAIL - Results detected vulnerabilities over accepted severity level',
150
151
  constantsSeverity:
151
- '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.',
152
+ '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.',
152
153
  constantsCount: 'The number of CVEs that must be exceeded to fail a build',
153
154
  constantsHeader: 'CodeSec by Contrast Security',
154
155
  configHeader2: 'Config options',
@@ -171,7 +172,7 @@ const en_locales = () => {
171
172
  constantsConfigUsageContents: 'view / clear the configuration',
172
173
  constantsPrerequisitesContent:
173
174
  'To scan a Java project you will need a .jar or .war file for analysis\n' +
174
- 'To scan a Javascript project you will need a .js or.zip file for analysis\n' +
175
+ 'To scan a Javascript project you will need a single .js or a .zip of multiple .js files\n' +
175
176
  'To scan a .NET c# webforms project you will need a .exe or a .zip file for analysis\n',
176
177
  constantsUsage: 'Usage',
177
178
  constantsUsageCommandExample: 'contrast [command] [options]',
@@ -318,7 +319,17 @@ const en_locales = () => {
318
319
  scanOptionsVerboseSummary: ' Returns extended information to the terminal.',
319
320
  authSuccessMessage: 'Authentication successful',
320
321
  runAuthSuccessMessage:
321
- "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.",
322
+ chalk.bold('CodeSec by Contrast') +
323
+ '\nScan, secure and ship your code in minutes for FREE. \n' +
324
+ chalk.bold('\nRun\n') +
325
+ chalk.bold('\ncontrast scan') +
326
+ " to run Contrast's industry leading SAST scanner. \nSupports Java, JavaScript and .Net \n" +
327
+ chalk.bold('\ncontrast audit') +
328
+ ' to find vulnerabilities in your open source dependencies.\nSupports Java, .NET, Node, Ruby, Python, Go and PHP \n' +
329
+ chalk.bold('\ncontrast lambda') +
330
+ ' to secure your AWS serverless functions. \nSupports Java and Python \n' +
331
+ chalk.bold('\ncontrast help') +
332
+ ' to learn more about the capabilities.',
322
333
  authWaitingMessage: 'Waiting for auth...',
323
334
  authTimedOutMessage: 'Auth Timed out, try again',
324
335
  zipErrorScan:
@@ -326,10 +337,7 @@ const en_locales = () => {
326
337
  unknownFileErrorScan: 'Unsupported file selected for Scan.',
327
338
  foundScanFile: 'Found: %s',
328
339
  foundDetailedVulnerabilities:
329
- chalk.bold('%s Critical') +
330
- ' | ' +
331
- chalk.bold('%s High') +
332
- ' | %s Medium | %s Low | %s Note',
340
+ chalk.bold('%s') + ' | ' + chalk.bold('%s') + ' | %s | %s | %s ',
333
341
  requiredParams: 'All required parameters are not present.',
334
342
  timeoutScan: 'Timeout set to 5 minutes.',
335
343
  searchingScanFileDirectory: 'Searching for file to scan from %s...',
package/src/constants.js CHANGED
@@ -115,7 +115,7 @@ const scanOptionDefinitions = [
115
115
  '{bold ' +
116
116
  i18n.__('constantsOptional') +
117
117
  '}: ' +
118
- i18n.__('failOptionErrorMessage')
118
+ i18n.__('failOptionMessage')
119
119
  },
120
120
  {
121
121
  name: 'severity',
@@ -247,7 +247,7 @@ const auditOptionDefinitions = [
247
247
  '{bold ' +
248
248
  i18n.__('constantsOptional') +
249
249
  '}: ' +
250
- i18n.__('failOptionErrorMessage')
250
+ i18n.__('failOptionMessage')
251
251
  },
252
252
  {
253
253
  name: 'severity',
package/src/index.ts CHANGED
@@ -15,7 +15,6 @@ import {
15
15
  } from './common/versionChecker'
16
16
  import { findCommandOnError } from './common/errorHandling'
17
17
  import { sendTelemetryConfigAsConfObj } from './telemetry/telemetry'
18
-
19
18
  const {
20
19
  commandLineDefinitions: { mainUsageGuide, mainDefinition }
21
20
  } = constants
@@ -58,7 +57,7 @@ const start = async () => {
58
57
  config.set('numOfRuns', config.get('numOfRuns') + 1)
59
58
 
60
59
  // @ts-ignore
61
- if (config.get('numOfRuns') >= 1) {
60
+ if (config.get('numOfRuns') >= 10) {
62
61
  await findLatestCLIVersion(config)
63
62
  config.set('numOfRuns', 0)
64
63
  }
@@ -96,9 +95,8 @@ const start = async () => {
96
95
  ? console.log(
97
96
  `Unknown Command: Did you mean "${foundCommand}"? \nUse "${foundCommand} --help" for the full list of options`
98
97
  )
99
- : console.log(
100
- `Unknown Command: ${command} \nUse --help for the full list`
101
- )
98
+ : console.log(`\nUnknown Command: ${command} \n`)
99
+ console.log(mainUsageGuide)
102
100
  await sendTelemetryConfigAsConfObj(
103
101
  config,
104
102
  command,
@@ -107,9 +105,8 @@ const start = async () => {
107
105
  'undefined'
108
106
  )
109
107
  } else {
110
- console.log(
111
- `Unknown Command: ${command} \nUse --help for the full list`
112
- )
108
+ console.log(`\nUnknown Command: ${command}\n`)
109
+ console.log(mainUsageGuide)
113
110
  await sendTelemetryConfigAsConfObj(
114
111
  config,
115
112
  command,
@@ -17,6 +17,7 @@ import ora from '../utils/oraWrapper'
17
17
  import { postAnalytics } from './analytics'
18
18
  import { LambdaOptions, AnalyticsOption, StatusType, EventType } from './types'
19
19
  import { APP_VERSION } from '../constants/constants'
20
+ import chalk from 'chalk'
20
21
 
21
22
  type ApiParams = {
22
23
  organizationId: string
@@ -114,6 +115,9 @@ const processLambda = async (argv: string[]) => {
114
115
  await postAnalytics(endCommandAnalytics).catch((error: Error) => {
115
116
  /* ignore */
116
117
  })
118
+
119
+ postRunMessage()
120
+
117
121
  if (errorMsg) {
118
122
  process.exit(1)
119
123
  }
@@ -122,7 +126,7 @@ const processLambda = async (argv: string[]) => {
122
126
 
123
127
  const getAvailableFunctions = async (lambdaOptions: LambdaOptions) => {
124
128
  const lambdas = await getAllLambdas(lambdaOptions)
125
- printAvailableLambdas(lambdas, { runtimes: ['python', 'java'] })
129
+ printAvailableLambdas(lambdas, { runtimes: ['python', 'java', 'node'] })
126
130
  }
127
131
 
128
132
  const actualProcessLambda = async (lambdaOptions: LambdaOptions) => {
@@ -221,4 +225,12 @@ const handleLambdaHelp = () => {
221
225
  process.exit(0)
222
226
  }
223
227
 
228
+ const postRunMessage = () => {
229
+ console.log('\n' + chalk.underline.bold('Other Codesec Features:'))
230
+ console.log("'contrast scan' to run CodeSec’s industry leading SAST scanner")
231
+ console.log(
232
+ "'contrast audit' to find vulnerabilities in your open source dependencies\n"
233
+ )
234
+ }
235
+
224
236
  export { processLambda, LambdaOptions, ApiParams, getAvailableFunctions }
@@ -11,7 +11,7 @@ import ora from '../utils/oraWrapper'
11
11
  import { LambdaOptions } from './lambda'
12
12
  import { log, getReadableFileSize } from './logUtils'
13
13
 
14
- type RuntimeLanguage = 'java' | 'python'
14
+ type RuntimeLanguage = 'java' | 'python' | 'node'
15
15
 
16
16
  type FilterLambdas = {
17
17
  runtimes: RuntimeLanguage[]
@@ -0,0 +1,108 @@
1
+ const {
2
+ getSeverityCounts,
3
+ createSummaryMessageTop,
4
+ printVulnInfo,
5
+ getReportTable,
6
+ getIssueRow,
7
+ printNoVulnFoundMsg
8
+ } = require('../../audit/report/commonReportingFunctions')
9
+ const { orderBy } = require('lodash')
10
+ const { assignBySeverity } = require('../../scan/formatScanOutput')
11
+ const chalk = require('chalk')
12
+ const { CE_URL } = require('../../constants/constants')
13
+ const common = require('../../common/fail')
14
+
15
+ const processAuditReport = (config, results) => {
16
+ let severityCounts = {}
17
+ if (results !== undefined) {
18
+ severityCounts = formatScaServicesReport(config, results)
19
+ }
20
+
21
+ if (config.fail) {
22
+ common.processFail(config, severityCounts)
23
+ }
24
+ }
25
+ const formatScaServicesReport = (config, results) => {
26
+ const projectOverviewCount = getSeverityCounts(results)
27
+
28
+ if (projectOverviewCount.total === 0) {
29
+ printNoVulnFoundMsg()
30
+ return projectOverviewCount
31
+ } else {
32
+ let total = 0
33
+ const numberOfCves = results.length
34
+ const table = getReportTable()
35
+ let contrastHeaderNumCounter = 0
36
+ let assignPriorityToResults = results.map(result =>
37
+ assignBySeverity(result, result)
38
+ )
39
+ const numberOfVulns = results
40
+ .map(result => result.vulnerabilities)
41
+ .reduce((a, b) => {
42
+ return (total += b.length)
43
+ }, 0)
44
+ const outputOrderedByLowestSeverityAndLowestNumOfCvesFirst = orderBy(
45
+ assignPriorityToResults,
46
+ [
47
+ reportListItem => {
48
+ return reportListItem.priority
49
+ },
50
+ reportListItem => {
51
+ return reportListItem.vulnerabilities.length
52
+ }
53
+ ],
54
+ ['asc', 'desc']
55
+ )
56
+
57
+ for (const result of outputOrderedByLowestSeverityAndLowestNumOfCvesFirst) {
58
+ contrastHeaderNumCounter++
59
+ const cvesNum = result.vulnerabilities.length
60
+ const grammaticallyCorrectVul =
61
+ result.vulnerabilities.length > 1 ? 'vulnerabilities' : 'vulnerability'
62
+
63
+ const headerColour = chalk.hex(result.colour)
64
+ const headerRow = [
65
+ headerColour(
66
+ `CONTRAST-${contrastHeaderNumCounter.toString().padStart(3, '0')}`
67
+ ),
68
+ headerColour(`-`),
69
+ headerColour(`[${result.severity}] `) +
70
+ headerColour.bold(`${result.artifactName}`) +
71
+ ` introduces ${cvesNum} ${grammaticallyCorrectVul}`
72
+ ]
73
+
74
+ const adviceRow = [
75
+ chalk.bold(`Advice`),
76
+ chalk.bold(`:`),
77
+ `Change to version ${result.remediationAdvice.latestStableVersion}`
78
+ ]
79
+
80
+ let assignPriorityToVulns = result.vulnerabilities.map(result =>
81
+ assignBySeverity(result, result)
82
+ )
83
+ const issueRow = getIssueRow(assignPriorityToVulns)
84
+
85
+ table.push(headerRow, issueRow, adviceRow)
86
+ console.log()
87
+ }
88
+
89
+ console.log()
90
+ createSummaryMessageTop(numberOfCves, numberOfVulns)
91
+ console.log(table.toString() + '\n')
92
+ printVulnInfo(projectOverviewCount)
93
+
94
+ if (config.host !== CE_URL) {
95
+ console.log(
96
+ '\n' + chalk.bold('View your full dependency tree in Contrast:')
97
+ )
98
+ console.log(
99
+ `${config.host}/Contrast/static/ng/index.html#/${config.organizationId}/applications/${config.applicationId}/libs/dependency-tree`
100
+ )
101
+ }
102
+ return projectOverviewCount
103
+ }
104
+ }
105
+ module.exports = {
106
+ formatScaServicesReport,
107
+ processAuditReport
108
+ }
@@ -0,0 +1,56 @@
1
+ const commonApi = require('../../utils/commonApi')
2
+ const { APP_VERSION } = require('../../constants/constants')
3
+ const requestUtils = require('../../utils/requestUtils')
4
+
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
+
17
+ const client = commonApi.getHttpClient(config)
18
+ const reportID = await client
19
+ .scaServiceIngest(requestBody, config)
20
+ .then(res => {
21
+ if (res.statusCode === 201) {
22
+ return res.body.libraryIngestJobId
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
+
32
+ let keepChecking = true
33
+ let res
34
+ while (keepChecking) {
35
+ res = await client.scaServiceReportStatus(config, reportID).then(res => {
36
+ console.log(res.statusCode)
37
+ console.log(res.body)
38
+ if (res.body.status == 'COMPLETED') {
39
+ keepChecking = false
40
+ return client.scaServiceReport(config, reportID).then(res => {
41
+ return res.body
42
+ })
43
+ }
44
+ })
45
+
46
+ if (!keepChecking) {
47
+ return res
48
+ }
49
+ await requestUtils.sleep(5000)
50
+ }
51
+ return res
52
+ }
53
+
54
+ module.exports = {
55
+ scaTreeUpload
56
+ }
@@ -1,6 +1,7 @@
1
1
  const analysis = require('./analysis')
2
2
  const i18n = require('i18n')
3
3
  const formatMessage = require('../common/formatMessage')
4
+ const scaServiceParser = require('./scaServiceParser')
4
5
 
5
6
  const jsAnalysis = async (config, languageFiles) => {
6
7
  checkForCorrectFiles(languageFiles)
@@ -13,6 +14,9 @@ const jsAnalysis = async (config, languageFiles) => {
13
14
  const buildNodeTree = async (config, files) => {
14
15
  let analysis = await readFiles(config, files)
15
16
  const rawNode = await parseFiles(config, files, analysis)
17
+ if (config.experimental) {
18
+ return scaServiceParser.parseJS(rawNode)
19
+ }
16
20
  return formatMessage.createJavaScriptTSMessage(rawNode)
17
21
  }
18
22
 
@@ -0,0 +1,145 @@
1
+ const parseJS = rawNode => {
2
+ let dependencyTree = {}
3
+ let combinedPackageJSONDep = {
4
+ ...rawNode.packageJSON?.dependencies,
5
+ ...rawNode.packageJSON?.devDependencies
6
+ }
7
+ let analyseLock = chooseLockFile(rawNode)
8
+
9
+ if (analyseLock.type === 'yarn') {
10
+ dependencyTree = yarnCreateDepTree(
11
+ dependencyTree,
12
+ combinedPackageJSONDep,
13
+ analyseLock.lockFile,
14
+ rawNode
15
+ )
16
+ }
17
+
18
+ if (analyseLock.type === 'npm') {
19
+ dependencyTree = npmCreateDepTree(
20
+ dependencyTree,
21
+ combinedPackageJSONDep,
22
+ analyseLock.lockFile,
23
+ rawNode
24
+ )
25
+ }
26
+
27
+ return dependencyTree
28
+ }
29
+
30
+ const npmCreateDepTree = (
31
+ dependencyTree,
32
+ combinedPackageJSONDep,
33
+ packageLock,
34
+ rawNode
35
+ ) => {
36
+ for (const [key, value] of Object.entries(packageLock)) {
37
+ dependencyTree[key] = {
38
+ name: key,
39
+ version: getResolvedVersion(key, packageLock),
40
+ group: null,
41
+ isProduction: checkIfInPackageJSON(rawNode.packageJSON.dependencies, key),
42
+ directDependency: checkIfInPackageJSON(combinedPackageJSONDep, key),
43
+ dependencies: createNPMChildDependencies(packageLock, key)
44
+ }
45
+ }
46
+ return dependencyTree
47
+ }
48
+
49
+ const yarnCreateDepTree = (
50
+ dependencyTree,
51
+ combinedPackageJSONDep,
52
+ packageLock,
53
+ rawNode
54
+ ) => {
55
+ for (const [key, value] of Object.entries(packageLock)) {
56
+ let gav = getNameFromGAV(key)
57
+ let nag = getDepNameWithoutVersion(key)
58
+ dependencyTree[key] = {
59
+ name: gav,
60
+ version: getResolvedVersion(key, packageLock),
61
+ group: null,
62
+ isProduction: checkIfInPackageJSON(rawNode.packageJSON.dependencies, nag),
63
+ directDependency: checkIfInPackageJSON(combinedPackageJSONDep, nag),
64
+ dependencies: createChildDependencies(packageLock, key)
65
+ }
66
+ }
67
+ return dependencyTree
68
+ }
69
+
70
+ const chooseLockFile = rawNode => {
71
+ if (rawNode?.yarn?.yarnLockFile !== undefined) {
72
+ return { lockFile: rawNode?.yarn?.yarnLockFile?.object, type: 'yarn' }
73
+ } else if (rawNode.npmLockFile !== undefined) {
74
+ return { lockFile: rawNode?.npmLockFile?.dependencies, type: 'npm' }
75
+ } else {
76
+ return undefined
77
+ }
78
+ }
79
+
80
+ const createKeyName = (dep, version) => {
81
+ return dep + '@' + version
82
+ }
83
+
84
+ const checkIfInPackageJSON = (list, dep) => {
85
+ return Object.keys(list).includes(dep)
86
+ }
87
+
88
+ const createChildDependencies = (lockFileDep, currentDep) => {
89
+ let depArray = []
90
+ if (lockFileDep[currentDep]?.dependencies) {
91
+ for (const [key, value] of Object.entries(
92
+ lockFileDep[currentDep]?.dependencies
93
+ )) {
94
+ depArray.push(createKeyName(key, value))
95
+ }
96
+ }
97
+ return depArray
98
+ }
99
+
100
+ const createNPMChildDependencies = (lockFileDep, currentDep) => {
101
+ let depArray = []
102
+ if (lockFileDep[currentDep]?.requires) {
103
+ for (const [key, value] of Object.entries(
104
+ lockFileDep[currentDep]?.requires
105
+ )) {
106
+ depArray.push(key)
107
+ }
108
+ }
109
+ return depArray
110
+ }
111
+
112
+ const getDepNameWithoutVersion = depKey => {
113
+ let dependency = depKey.split('@')
114
+ if (dependency.length - 1 > 1) {
115
+ return '@' + dependency[1]
116
+ }
117
+ return dependency[0]
118
+ }
119
+
120
+ const getNameFromGAV = depKey => {
121
+ let dependency = depKey.split('/')
122
+ if (dependency.length == 2) {
123
+ dependency = getDepNameWithoutVersion(dependency[1])
124
+ return dependency
125
+ }
126
+ if (dependency.length == 1) {
127
+ dependency = getDepNameWithoutVersion(depKey)
128
+ return dependency
129
+ }
130
+ //what should we do if there's no version? The service will fall over but do we want to throw error for only one wrong version?
131
+ return depKey
132
+ }
133
+
134
+ const getResolvedVersion = (depKey, packageLock) => {
135
+ return packageLock[depKey]?.version
136
+ }
137
+
138
+ module.exports = {
139
+ parseJS,
140
+ checkIfInPackageJSON,
141
+ getNameFromGAV,
142
+ getResolvedVersion,
143
+ chooseLockFile,
144
+ createNPMChildDependencies
145
+ }