@contrast/contrast 1.0.0 → 1.0.1
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/.prettierignore +2 -0
- package/README.md +120 -47
- package/dist/audit/AnalysisEngine.js +37 -0
- package/dist/audit/catalogueApplication/catalogueApplication.js +36 -0
- package/dist/audit/dotnetAnalysisEngine/index.js +25 -0
- package/dist/audit/dotnetAnalysisEngine/parseLockFileContents.js +35 -0
- package/dist/audit/dotnetAnalysisEngine/parseProjectFileContents.js +15 -0
- package/dist/audit/dotnetAnalysisEngine/readLockFileContents.js +18 -0
- package/dist/audit/dotnetAnalysisEngine/readProjectFileContents.js +14 -0
- package/dist/audit/dotnetAnalysisEngine/sanitizer.js +9 -0
- package/dist/audit/goAnalysisEngine/index.js +17 -0
- package/dist/audit/goAnalysisEngine/parseProjectFileContents.js +164 -0
- package/dist/audit/goAnalysisEngine/readProjectFileContents.js +21 -0
- package/dist/audit/goAnalysisEngine/sanitizer.js +5 -0
- package/dist/audit/javaAnalysisEngine/index.js +34 -0
- package/dist/audit/javaAnalysisEngine/parseMavenProjectFileContents.js +153 -0
- package/dist/audit/javaAnalysisEngine/parseProjectFileContents.js +353 -0
- package/dist/audit/javaAnalysisEngine/readProjectFileContents.js +98 -0
- package/dist/audit/javaAnalysisEngine/sanitizer.js +5 -0
- package/dist/audit/languageAnalysisEngine/checkForMultipleIdentifiedLanguages.js +24 -0
- package/dist/audit/languageAnalysisEngine/checkForMultipleIdentifiedProjectFiles.js +24 -0
- package/dist/audit/languageAnalysisEngine/checkIdentifiedLanguageHasLockFile.js +35 -0
- package/dist/audit/languageAnalysisEngine/checkIdentifiedLanguageHasProjectFile.js +23 -0
- package/dist/audit/languageAnalysisEngine/commonApi.js +18 -0
- package/dist/audit/languageAnalysisEngine/constants.js +20 -0
- package/dist/audit/languageAnalysisEngine/filterProjectPath.js +20 -0
- package/dist/audit/languageAnalysisEngine/getIdentifiedLanguageInfo.js +25 -0
- package/dist/audit/languageAnalysisEngine/getProjectRootFilenames.js +39 -0
- package/dist/audit/languageAnalysisEngine/index.js +39 -0
- package/dist/audit/languageAnalysisEngine/langugageAnalysisFactory.js +70 -0
- package/dist/audit/languageAnalysisEngine/reduceIdentifiedLanguages.js +121 -0
- package/dist/audit/languageAnalysisEngine/report/checkIgnoreDevDep.js +17 -0
- package/dist/audit/languageAnalysisEngine/report/commonReportingFunctions.js +257 -0
- package/dist/audit/languageAnalysisEngine/report/newReportingFeature.js +81 -0
- package/dist/audit/languageAnalysisEngine/report/reportingFeature.js +133 -0
- package/dist/audit/languageAnalysisEngine/sendSnapshot.js +41 -0
- package/dist/audit/languageAnalysisEngine/util/capabilities.js +11 -0
- package/dist/audit/languageAnalysisEngine/util/generalAPI.js +39 -0
- package/dist/audit/languageAnalysisEngine/util/requestUtils.js +14 -0
- package/dist/audit/nodeAnalysisEngine/handleNPMLockFileV2.js +40 -0
- package/dist/audit/nodeAnalysisEngine/index.js +31 -0
- package/dist/audit/nodeAnalysisEngine/parseNPMLockFileContents.js +18 -0
- package/dist/audit/nodeAnalysisEngine/parseYarn2LockFileContents.js +51 -0
- package/dist/audit/nodeAnalysisEngine/parseYarnLockFileContents.js +18 -0
- package/dist/audit/nodeAnalysisEngine/readNPMLockFileContents.js +17 -0
- package/dist/audit/nodeAnalysisEngine/readProjectFileContents.js +14 -0
- package/dist/audit/nodeAnalysisEngine/readYarnLockFileContents.js +24 -0
- package/dist/audit/nodeAnalysisEngine/sanitizer.js +9 -0
- package/dist/audit/phpAnalysisEngine/index.js +23 -0
- package/dist/audit/phpAnalysisEngine/parseLockFileContents.js +52 -0
- package/dist/audit/phpAnalysisEngine/readLockFileContents.js +13 -0
- package/dist/audit/phpAnalysisEngine/readProjectFileContents.js +16 -0
- package/dist/audit/phpAnalysisEngine/sanitizer.js +5 -0
- package/dist/audit/pythonAnalysisEngine/index.js +25 -0
- package/dist/audit/pythonAnalysisEngine/parsePipfileLockContents.js +17 -0
- package/dist/audit/pythonAnalysisEngine/parseProjectFileContents.js +21 -0
- package/dist/audit/pythonAnalysisEngine/readPipfileLockFileContents.js +13 -0
- package/dist/audit/pythonAnalysisEngine/readPythonProjectFileContents.js +14 -0
- package/dist/audit/pythonAnalysisEngine/sanitizer.js +7 -0
- package/dist/audit/rubyAnalysisEngine/index.js +25 -0
- package/dist/audit/rubyAnalysisEngine/parseGemfileLockContents.js +176 -0
- package/dist/audit/rubyAnalysisEngine/parsedGemfile.js +22 -0
- package/dist/audit/rubyAnalysisEngine/readGemfileContents.js +14 -0
- package/dist/audit/rubyAnalysisEngine/readGemfileLockContents.js +14 -0
- package/dist/audit/rubyAnalysisEngine/sanitizer.js +6 -0
- package/dist/commands/audit/auditConfig.js +25 -0
- package/dist/commands/audit/auditController.js +31 -0
- package/dist/commands/audit/help.js +52 -0
- package/dist/commands/audit/processAudit.js +18 -0
- package/dist/commands/auth/auth.js +1 -1
- package/dist/commands/scan/processScan.js +19 -5
- package/dist/common/HTTPClient.js +101 -13
- package/dist/common/errorHandling.js +49 -1
- package/dist/common/findLatestCLIVersion.js +23 -0
- package/dist/constants/constants.js +1 -1
- package/dist/constants/lambda.js +32 -4
- package/dist/constants/locales.js +39 -16
- package/dist/constants.js +148 -20
- package/dist/index.js +7 -1
- package/dist/lambda/aws.js +14 -11
- package/dist/lambda/help.js +4 -0
- package/dist/lambda/lambda.js +50 -27
- package/dist/lambda/lambdaUtils.js +72 -0
- package/dist/lambda/logUtils.js +11 -1
- package/dist/lambda/scanDetailCompletion.js +4 -4
- package/dist/lambda/scanRequest.js +11 -5
- package/dist/lambda/utils.js +110 -53
- package/dist/scan/autoDetection.js +0 -32
- package/dist/scan/fileUtils.js +1 -1
- package/dist/scan/help.js +12 -40
- package/dist/scan/populateProjectIdAndProjectName.js +4 -0
- package/dist/scan/saveResults.js +15 -0
- package/dist/scan/scan.js +77 -42
- package/dist/scan/scanConfig.js +20 -0
- package/dist/scan/scanController.js +13 -15
- package/dist/scan/scanResults.js +18 -16
- package/dist/utils/commonApi.js +3 -3
- package/dist/utils/fileUtils.js +31 -0
- package/dist/utils/paramsUtil/commandlineParams.js +1 -20
- package/dist/utils/paramsUtil/genericCommandLineParams.js +12 -0
- package/dist/utils/paramsUtil/paramHandler.js +3 -6
- package/dist/utils/parsedCLIOptions.js +14 -8
- package/package.json +26 -21
- package/src/audit/AnalysisEngine.js +103 -0
- package/src/audit/catalogueApplication/catalogueApplication.js +42 -0
- package/src/audit/dotnetAnalysisEngine/index.js +26 -0
- package/src/audit/dotnetAnalysisEngine/parseLockFileContents.js +47 -0
- package/src/audit/dotnetAnalysisEngine/parseProjectFileContents.js +29 -0
- package/src/audit/dotnetAnalysisEngine/readLockFileContents.js +30 -0
- package/src/audit/dotnetAnalysisEngine/readProjectFileContents.js +26 -0
- package/src/audit/dotnetAnalysisEngine/sanitizer.js +11 -0
- package/src/audit/goAnalysisEngine/index.js +18 -0
- package/src/audit/goAnalysisEngine/parseProjectFileContents.js +209 -0
- package/src/audit/goAnalysisEngine/readProjectFileContents.js +31 -0
- package/src/audit/goAnalysisEngine/sanitizer.js +7 -0
- package/src/audit/javaAnalysisEngine/index.js +41 -0
- package/src/audit/javaAnalysisEngine/parseMavenProjectFileContents.js +222 -0
- package/src/audit/javaAnalysisEngine/parseProjectFileContents.js +420 -0
- package/src/audit/javaAnalysisEngine/readProjectFileContents.js +141 -0
- package/src/audit/javaAnalysisEngine/sanitizer.js +6 -0
- package/src/audit/languageAnalysisEngine/checkForMultipleIdentifiedLanguages.js +35 -0
- package/src/audit/languageAnalysisEngine/checkForMultipleIdentifiedProjectFiles.js +41 -0
- package/src/audit/languageAnalysisEngine/checkIdentifiedLanguageHasLockFile.js +54 -0
- package/src/audit/languageAnalysisEngine/checkIdentifiedLanguageHasProjectFile.js +32 -0
- package/src/audit/languageAnalysisEngine/commonApi.js +20 -0
- package/src/audit/languageAnalysisEngine/constants.js +23 -0
- package/src/audit/languageAnalysisEngine/filterProjectPath.js +21 -0
- package/src/audit/languageAnalysisEngine/getIdentifiedLanguageInfo.js +41 -0
- package/src/audit/languageAnalysisEngine/getProjectRootFilenames.js +72 -0
- package/src/audit/languageAnalysisEngine/index.js +45 -0
- package/src/audit/languageAnalysisEngine/langugageAnalysisFactory.js +94 -0
- package/src/audit/languageAnalysisEngine/reduceIdentifiedLanguages.js +177 -0
- package/src/audit/languageAnalysisEngine/report/checkIgnoreDevDep.js +27 -0
- package/src/audit/languageAnalysisEngine/report/commonReportingFunctions.js +303 -0
- package/src/audit/languageAnalysisEngine/report/newReportingFeature.js +124 -0
- package/src/audit/languageAnalysisEngine/report/reportingFeature.js +190 -0
- package/src/audit/languageAnalysisEngine/sendSnapshot.js +51 -0
- package/src/audit/languageAnalysisEngine/util/capabilities.js +12 -0
- package/src/audit/languageAnalysisEngine/util/generalAPI.js +43 -0
- package/src/audit/languageAnalysisEngine/util/requestUtils.js +17 -0
- package/src/audit/nodeAnalysisEngine/handleNPMLockFileV2.js +49 -0
- package/src/audit/nodeAnalysisEngine/index.js +35 -0
- package/src/audit/nodeAnalysisEngine/parseNPMLockFileContents.js +20 -0
- package/src/audit/nodeAnalysisEngine/parseYarn2LockFileContents.js +63 -0
- package/src/audit/nodeAnalysisEngine/parseYarnLockFileContents.js +26 -0
- package/src/audit/nodeAnalysisEngine/readNPMLockFileContents.js +23 -0
- package/src/audit/nodeAnalysisEngine/readProjectFileContents.js +27 -0
- package/src/audit/nodeAnalysisEngine/readYarnLockFileContents.js +36 -0
- package/src/audit/nodeAnalysisEngine/sanitizer.js +11 -0
- package/src/audit/phpAnalysisEngine/index.js +27 -0
- package/src/audit/phpAnalysisEngine/parseLockFileContents.js +60 -0
- package/src/audit/phpAnalysisEngine/readLockFileContents.js +14 -0
- package/src/audit/phpAnalysisEngine/readProjectFileContents.js +25 -0
- package/src/audit/phpAnalysisEngine/sanitizer.js +4 -0
- package/src/audit/pythonAnalysisEngine/index.js +55 -0
- package/src/audit/pythonAnalysisEngine/parsePipfileLockContents.js +23 -0
- package/src/audit/pythonAnalysisEngine/parseProjectFileContents.js +33 -0
- package/src/audit/pythonAnalysisEngine/readPipfileLockFileContents.js +16 -0
- package/src/audit/pythonAnalysisEngine/readPythonProjectFileContents.js +22 -0
- package/src/audit/pythonAnalysisEngine/sanitizer.js +9 -0
- package/src/audit/rubyAnalysisEngine/index.js +30 -0
- package/src/audit/rubyAnalysisEngine/parseGemfileLockContents.js +215 -0
- package/src/audit/rubyAnalysisEngine/parsedGemfile.js +39 -0
- package/src/audit/rubyAnalysisEngine/readGemfileContents.js +18 -0
- package/src/audit/rubyAnalysisEngine/readGemfileLockContents.js +17 -0
- package/src/audit/rubyAnalysisEngine/sanitizer.js +8 -0
- package/src/commands/audit/auditConfig.ts +30 -0
- package/src/commands/audit/auditController.ts +31 -0
- package/src/commands/audit/help.ts +48 -0
- package/src/commands/audit/processAudit.ts +19 -0
- package/src/commands/auth/auth.js +1 -1
- package/src/commands/scan/processScan.js +20 -5
- package/src/common/HTTPClient.js +136 -14
- package/src/common/errorHandling.ts +56 -1
- package/src/common/findLatestCLIVersion.ts +27 -0
- package/src/constants/constants.js +1 -1
- package/src/constants/lambda.js +45 -4
- package/src/constants/locales.js +48 -20
- package/src/constants.js +168 -22
- package/src/index.ts +9 -2
- package/src/lambda/aws.ts +13 -12
- package/src/lambda/help.ts +4 -0
- package/src/lambda/lambda.ts +53 -34
- package/src/lambda/lambdaUtils.ts +111 -0
- package/src/lambda/logUtils.ts +19 -1
- package/src/lambda/scanDetailCompletion.ts +4 -4
- package/src/lambda/scanRequest.ts +13 -11
- package/src/lambda/utils.ts +149 -81
- package/src/scan/autoDetection.js +0 -29
- package/src/scan/fileUtils.js +1 -1
- package/src/scan/help.js +12 -45
- package/src/scan/populateProjectIdAndProjectName.js +4 -0
- package/src/scan/saveResults.js +15 -0
- package/src/scan/scan.js +95 -59
- package/src/scan/scanConfig.js +29 -0
- package/src/scan/scanController.js +13 -13
- package/src/scan/scanResults.js +21 -19
- package/src/utils/commonApi.js +2 -3
- package/src/utils/paramsUtil/commandlineParams.js +1 -26
- package/src/utils/paramsUtil/paramHandler.js +3 -7
- package/src/utils/parsedCLIOptions.js +11 -9
|
@@ -24,8 +24,7 @@ const sendScanPostRequest = async (
|
|
|
24
24
|
const client = getHttpClient(config)
|
|
25
25
|
|
|
26
26
|
if (showProgress) {
|
|
27
|
-
|
|
28
|
-
log(`${logSymbols.success} Sending Lambda Function scan request to Contrast`)
|
|
27
|
+
log(i18n.__('sendingScanRequest', { icon: logSymbols.success }))
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
const res = await client.postFunctionScan(config, params, functionsEvent)
|
|
@@ -33,7 +32,7 @@ const sendScanPostRequest = async (
|
|
|
33
32
|
|
|
34
33
|
if (statusCode === 201) {
|
|
35
34
|
if (showProgress) {
|
|
36
|
-
log(
|
|
35
|
+
log(i18n.__('scanRequestedSuccessfully', { icon: logSymbols.success }))
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
return body?.data?.scanId
|
|
@@ -45,11 +44,10 @@ const sendScanPostRequest = async (
|
|
|
45
44
|
let description = ''
|
|
46
45
|
switch (errorCode) {
|
|
47
46
|
case 'not_supported_runtime':
|
|
48
|
-
description = i18n.__(
|
|
49
|
-
|
|
50
|
-
data?.
|
|
51
|
-
|
|
52
|
-
)
|
|
47
|
+
description = i18n.__(errorCode, {
|
|
48
|
+
runtime: data?.runtime,
|
|
49
|
+
supportedRuntimes: data?.supportedRuntimes.sort().join(' | ')
|
|
50
|
+
})
|
|
53
51
|
errorCode = false
|
|
54
52
|
break
|
|
55
53
|
}
|
|
@@ -88,8 +86,12 @@ const requestScanFunctionPost = async (
|
|
|
88
86
|
const lambdaClient = getLambdaClient(lambdaOptions)
|
|
89
87
|
|
|
90
88
|
if (!jsonOutput) {
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
log(
|
|
90
|
+
i18n.__('fetchingConfiguration', {
|
|
91
|
+
icon: logSymbols.success,
|
|
92
|
+
functionName: chalk.bold(functionName)
|
|
93
|
+
})
|
|
94
|
+
)
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
const lambdaConfig = await getLambdaFunctionConfiguration(
|
|
@@ -125,7 +127,7 @@ const requestScanFunctionPost = async (
|
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
if (verbose) {
|
|
128
|
-
log(
|
|
130
|
+
log(i18n.__('fetchedConfiguration', { icon: logSymbols.success }))
|
|
129
131
|
prettyPrintJson(functionEvent)
|
|
130
132
|
}
|
|
131
133
|
|
package/src/lambda/utils.ts
CHANGED
|
@@ -1,112 +1,182 @@
|
|
|
1
1
|
import chalk from 'chalk'
|
|
2
|
-
import {
|
|
2
|
+
import { groupBy, sortBy, capitalize, minBy } from 'lodash'
|
|
3
|
+
import i18n from 'i18n'
|
|
3
4
|
import { log } from './logUtils'
|
|
4
5
|
|
|
6
|
+
// fix for using `plural`
|
|
7
|
+
// https://github.com/mashpie/i18n-node/issues/429
|
|
8
|
+
i18n.setLocale('en')
|
|
9
|
+
|
|
10
|
+
class PrintVulnerability {
|
|
11
|
+
index: number
|
|
12
|
+
vulnerability: any
|
|
13
|
+
group?: any[]
|
|
14
|
+
title: string
|
|
15
|
+
severity: string
|
|
16
|
+
remediation: string
|
|
17
|
+
description: string
|
|
18
|
+
recommendation: string
|
|
19
|
+
whatHappened: string
|
|
20
|
+
|
|
21
|
+
constructor(index: number, vulnerability: any, group?: any[]) {
|
|
22
|
+
const {
|
|
23
|
+
severityText,
|
|
24
|
+
title,
|
|
25
|
+
description,
|
|
26
|
+
remediation,
|
|
27
|
+
categoryText
|
|
28
|
+
} = vulnerability
|
|
29
|
+
|
|
30
|
+
this.group = group
|
|
31
|
+
this.vulnerability = vulnerability
|
|
32
|
+
this.index = index
|
|
33
|
+
this.title = title
|
|
34
|
+
this.severity = capitalize(severityText)
|
|
35
|
+
this.description = underlineLinks(description)
|
|
36
|
+
this.remediation = remediation?.description
|
|
37
|
+
this.recommendation = ''
|
|
38
|
+
this.whatHappened = ''
|
|
39
|
+
|
|
40
|
+
if (categoryText === 'PERMISSIONS') {
|
|
41
|
+
this.formatPermissions()
|
|
42
|
+
} else if (categoryText === 'DEPENDENCIES') {
|
|
43
|
+
this.formatDependencies()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
formatPermissions() {
|
|
48
|
+
const { leastPrivilege, comment } = this.vulnerability.evidence
|
|
49
|
+
const violatingPolicies = leastPrivilege?.violatingPolicies || []
|
|
50
|
+
|
|
51
|
+
const filteredPolicies = violatingPolicies
|
|
52
|
+
.filter((vp: any) => vp?.suggestedPolicy?.suggestedPolicyCode?.length)
|
|
53
|
+
.map((vp: any) => vp?.suggestedPolicy)
|
|
54
|
+
|
|
55
|
+
const shouldNumerate = filteredPolicies.length > 1
|
|
56
|
+
filteredPolicies.forEach((policies: any, i: number) => {
|
|
57
|
+
const { suggestedPolicyCode, description } = policies
|
|
58
|
+
|
|
59
|
+
suggestedPolicyCode.forEach((policy: any) => {
|
|
60
|
+
const { snippet, title } = policy
|
|
61
|
+
this.recommendation += shouldNumerate
|
|
62
|
+
? ` ${i + 1}. ${description}\n`
|
|
63
|
+
: `${description}\n`
|
|
64
|
+
|
|
65
|
+
if (title !== 'DELETE POLICY') {
|
|
66
|
+
this.recommendation += `${snippet}\n`
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
if (comment?.length) {
|
|
72
|
+
const splitComment = (comment: string) => {
|
|
73
|
+
const [policy, description] = comment.split(':').map(c => c.trim())
|
|
74
|
+
return { policy, description }
|
|
75
|
+
}
|
|
76
|
+
const groupByPolicy = groupBy(comment, c => splitComment(c).policy)
|
|
77
|
+
|
|
78
|
+
Object.entries(groupByPolicy).forEach(([policy, commentArr]) => {
|
|
79
|
+
const comments = commentArr
|
|
80
|
+
.map(splitComment)
|
|
81
|
+
.map(({ description }) => ` - ${description}`)
|
|
82
|
+
.join('\n')
|
|
83
|
+
this.whatHappened += i18n.__('whatHappenedItem', { policy, comments })
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
formatDependencies() {
|
|
89
|
+
if (!this.group?.length) {
|
|
90
|
+
this.recommendation = this.vulnerability?.remediation?.description
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const maxSeverity = minBy(this.group, 'severity')
|
|
95
|
+
this.title = i18n.__('vulnerableDependency')
|
|
96
|
+
this.severity = capitalize(maxSeverity.severityText)
|
|
97
|
+
this.recommendation = maxSeverity.remediation?.description
|
|
98
|
+
|
|
99
|
+
const library = groupByDependency({ title: this.vulnerability.title })
|
|
100
|
+
const [packageName, version] = library.split(':')
|
|
101
|
+
const allCves = this.group.map(groupByCVE)
|
|
102
|
+
|
|
103
|
+
this.description = i18n.__mf('vulnerableDependencyDescriptions', {
|
|
104
|
+
NUM: this.group.length,
|
|
105
|
+
packageName,
|
|
106
|
+
version,
|
|
107
|
+
cves: allCves.join(' | ')
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
print() {
|
|
112
|
+
log(`${this.index}.`)
|
|
113
|
+
// prettier-ignore
|
|
114
|
+
log(`${chalk.bold(this.severity)} | ${chalk.bold(this.title)} ${this.description}`)
|
|
115
|
+
|
|
116
|
+
if (this.whatHappened) {
|
|
117
|
+
log(`\n${chalk.bold(i18n.__('whatHappenedTitle'))}\n${this.whatHappened}`)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (this.recommendation) {
|
|
121
|
+
log(`${chalk.bold(i18n.__('recommendation'))}\n${this.recommendation}`)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
log('')
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
5
128
|
const groupByCVE = ({ title }: any) =>
|
|
6
129
|
title.substring(0, title.indexOf('[') - 1)
|
|
7
130
|
|
|
8
131
|
const groupByDependency = ({ title }: any) =>
|
|
9
132
|
title.substring(title.indexOf('[') + 1, title.indexOf(']'))
|
|
10
133
|
|
|
11
|
-
const
|
|
12
|
-
log('')
|
|
13
|
-
|
|
134
|
+
const printResults = (results: any[]) => {
|
|
14
135
|
//filter out any vulnerabs which is not least privilege or dependencies- cli does not handle other vulnerabs yet
|
|
15
136
|
const vulnerabs = results.filter(r => r.category === 1 || r.category === 4)
|
|
16
137
|
const sortBySeverity = sortBy(vulnerabs, ['severity', 'title'])
|
|
17
138
|
const notDependencies = sortBySeverity.filter(r => r.category !== 1)
|
|
18
139
|
const dependencies = sortBySeverity.filter(r => r.category === 1)
|
|
19
140
|
const dependenciesByLibrary = groupBy(dependencies, groupByDependency)
|
|
20
|
-
const dependenciesCount = Object.keys(dependenciesByLibrary).length
|
|
21
141
|
|
|
22
|
-
|
|
142
|
+
log('')
|
|
23
143
|
|
|
144
|
+
notDependencies.forEach((vulnerability: any, index: number) => {
|
|
145
|
+
const printVulnerab = new PrintVulnerability(index + 1, vulnerability)
|
|
146
|
+
printVulnerab.print()
|
|
147
|
+
})
|
|
24
148
|
const prevIndex = notDependencies.length + 1
|
|
25
|
-
Object.entries(dependenciesByLibrary).forEach(([
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
log(prevIndex + i)
|
|
30
|
-
log(
|
|
31
|
-
`${chalk.bold(capitalize(maxSeverity.severityText))} | ${chalk.bold(
|
|
32
|
-
'Vulnerable dependency'
|
|
33
|
-
)} ${library} has ${group.length} known CVEs`
|
|
34
|
-
)
|
|
35
|
-
log(allCves.join(', '))
|
|
36
|
-
if (maxSeverity.remediation?.description) {
|
|
37
|
-
log(
|
|
38
|
-
`${chalk.bold('Recommendation:')} ${
|
|
39
|
-
maxSeverity.remediation.description
|
|
40
|
-
}`
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
log('')
|
|
149
|
+
Object.entries(dependenciesByLibrary).forEach(([, group], i) => {
|
|
150
|
+
const printVulnerab = new PrintVulnerability(prevIndex + i, group[0], group)
|
|
151
|
+
printVulnerab.print()
|
|
44
152
|
})
|
|
45
153
|
|
|
154
|
+
const dependenciesCount = Object.keys(dependenciesByLibrary).length
|
|
46
155
|
const resultCount = notDependencies.length + dependenciesCount
|
|
156
|
+
log(i18n.__n('foundVulnerabilities', resultCount), { bold: true })
|
|
157
|
+
|
|
158
|
+
const counters = getNotDependenciesCounters(notDependencies)
|
|
159
|
+
if (dependenciesCount) {
|
|
160
|
+
counters.push(i18n.__n('dependenciesCount', dependenciesCount))
|
|
161
|
+
}
|
|
162
|
+
log(counters.join(' | '), { bold: true })
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const getNotDependenciesCounters = (notDependencies: any[]) => {
|
|
47
166
|
const groupByType = groupBy(notDependencies, ['categoryText'])
|
|
48
|
-
|
|
167
|
+
return Object.values(groupByType).map(
|
|
49
168
|
group => `${group.length} ${capitalize(group[0].categoryText)}`
|
|
50
169
|
)
|
|
51
|
-
log(`Found ${resultCount} vulnerabilities`, { bold: true })
|
|
52
|
-
summary.push(`${dependenciesCount} Dependencies`)
|
|
53
|
-
|
|
54
|
-
log(chalk.bold(summary.join(' | ')))
|
|
55
170
|
}
|
|
56
171
|
|
|
57
172
|
const underlineLinks = (text: string) => {
|
|
58
173
|
if (!text) {
|
|
59
174
|
return text
|
|
60
175
|
}
|
|
61
|
-
|
|
62
176
|
const urlRegex = /(https?:\/\/[^\s]+)/g
|
|
63
177
|
return text.replace(urlRegex, chalk.underline('$1'))
|
|
64
178
|
}
|
|
65
179
|
|
|
66
|
-
const printVulnerability = (vulnerability: any, index: number) => {
|
|
67
|
-
log(index + 1)
|
|
68
|
-
const descriptionWithLinks = underlineLinks(vulnerability.description)
|
|
69
|
-
log(
|
|
70
|
-
`${chalk.bold(capitalize(vulnerability.severityText))} | ${chalk.bold(
|
|
71
|
-
vulnerability.title
|
|
72
|
-
)} ${descriptionWithLinks}`
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
const category = vulnerability?.categoryText
|
|
76
|
-
switch (category) {
|
|
77
|
-
case 'PERMISSIONS':
|
|
78
|
-
printLeastPrivilegeRemediation(vulnerability)
|
|
79
|
-
break
|
|
80
|
-
default:
|
|
81
|
-
printRemediation(vulnerability)
|
|
82
|
-
}
|
|
83
|
-
log('')
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const printLeastPrivilegeRemediation = (vulnerability: any) => {
|
|
87
|
-
log(
|
|
88
|
-
`${chalk.bold(
|
|
89
|
-
'Recommendation:'
|
|
90
|
-
)} Replace the existing policies with the following`
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
const violatingPolicies =
|
|
94
|
-
vulnerability?.evidence?.leastPrivilege?.violatingPolicies || []
|
|
95
|
-
|
|
96
|
-
violatingPolicies
|
|
97
|
-
.filter((vp: any) => vp?.suggestedPolicy?.suggestedPolicyCode?.length)
|
|
98
|
-
.map((vp: any) => vp?.suggestedPolicy?.suggestedPolicyCode)
|
|
99
|
-
.forEach((policies: any) => {
|
|
100
|
-
policies.forEach((policy: any) => {
|
|
101
|
-
console.log(policy.snippet)
|
|
102
|
-
})
|
|
103
|
-
})
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const printRemediation = (vulnerability: any) => {
|
|
107
|
-
log(`Remediation - ${vulnerability?.remediation?.description || 'Unknown'}`)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
180
|
function toLowerKeys(obj: Record<string, unknown>) {
|
|
111
181
|
return Object.keys(obj).reduce((accumulator, key) => {
|
|
112
182
|
const new_key = `${key[0].toLowerCase()}${key.slice(1)}`
|
|
@@ -115,11 +185,9 @@ function toLowerKeys(obj: Record<string, unknown>) {
|
|
|
115
185
|
}, {} as Record<string, unknown>)
|
|
116
186
|
}
|
|
117
187
|
|
|
118
|
-
export { toLowerKeys,
|
|
119
|
-
|
|
188
|
+
export { toLowerKeys, printResults }
|
|
120
189
|
export const exportedForTesting = {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
underlineLinks
|
|
190
|
+
underlineLinks,
|
|
191
|
+
printResults,
|
|
192
|
+
PrintVulnerability
|
|
125
193
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
const i18n = require('i18n')
|
|
2
|
-
const { zipValidator } = require('./scan')
|
|
3
2
|
const fileFinder = require('./fileUtils')
|
|
4
|
-
const { supportedLanguages } = require('../constants/constants')
|
|
5
3
|
|
|
6
4
|
const autoDetectFileAndLanguage = async configToUse => {
|
|
7
5
|
const entries = await fileFinder.findFile()
|
|
@@ -18,8 +16,6 @@ const autoDetectFileAndLanguage = async configToUse => {
|
|
|
18
16
|
if (configToUse.name === undefined) {
|
|
19
17
|
configToUse.name = entries[0]
|
|
20
18
|
}
|
|
21
|
-
zipValidator(configToUse)
|
|
22
|
-
assignLanguage(entries, configToUse)
|
|
23
19
|
} else {
|
|
24
20
|
errorOnFileDetection(entries)
|
|
25
21
|
}
|
|
@@ -46,32 +42,7 @@ const errorOnFileDetection = entries => {
|
|
|
46
42
|
process.exit(1)
|
|
47
43
|
}
|
|
48
44
|
|
|
49
|
-
const assignLanguage = (entries, configToUse) => {
|
|
50
|
-
let split = entries[0].split('.')
|
|
51
|
-
const fileType = split[split.length - 1]
|
|
52
|
-
if (fileType === 'war' || fileType === 'jar') {
|
|
53
|
-
console.log('Language is Java')
|
|
54
|
-
configToUse.language = 'JAVA'
|
|
55
|
-
} else if (fileType === 'dll') {
|
|
56
|
-
console.log('Language is Dotnet')
|
|
57
|
-
configToUse.language = 'DOTNET'
|
|
58
|
-
} else if (fileType === 'js') {
|
|
59
|
-
console.log('Language is Javascript')
|
|
60
|
-
configToUse.language = supportedLanguages.JAVASCRIPT
|
|
61
|
-
} else if (fileType === 'zip') {
|
|
62
|
-
if (configToUse.language !== supportedLanguages.JAVASCRIPT) {
|
|
63
|
-
console.log(i18n.__('zipErrorScan'))
|
|
64
|
-
process.exit(1)
|
|
65
|
-
}
|
|
66
|
-
console.log('Language is Javascript within zip file')
|
|
67
|
-
} else {
|
|
68
|
-
console.log(i18n.__('unknownFileErrorScan'))
|
|
69
|
-
process.exit(1)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
45
|
module.exports = {
|
|
74
46
|
autoDetectFileAndLanguage,
|
|
75
|
-
assignLanguage,
|
|
76
47
|
errorOnFileDetection
|
|
77
48
|
}
|
package/src/scan/fileUtils.js
CHANGED
|
@@ -4,7 +4,7 @@ const i18n = require('i18n')
|
|
|
4
4
|
|
|
5
5
|
const findFile = async () => {
|
|
6
6
|
console.log(i18n.__('searchingScanFileDirectory', process.cwd()))
|
|
7
|
-
return fg(['**/*.jar', '**/*.war', '**/*.zip', '**/*.dll'], {
|
|
7
|
+
return fg(['**/*.jar', '**/*.war', '**/*.zip', '**/*.dll', '**/*.exe'], {
|
|
8
8
|
dot: false,
|
|
9
9
|
deep: 3,
|
|
10
10
|
onlyFiles: true
|
package/src/scan/help.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const commandLineUsage = require('command-line-usage')
|
|
2
2
|
const i18n = require('i18n')
|
|
3
|
+
const constants = require('../constants')
|
|
3
4
|
|
|
4
5
|
const scanUsageGuide = commandLineUsage([
|
|
5
6
|
{
|
|
@@ -17,51 +18,17 @@ const scanUsageGuide = commandLineUsage([
|
|
|
17
18
|
},
|
|
18
19
|
{
|
|
19
20
|
header: i18n.__('constantsScanOptions'),
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
summary:
|
|
32
|
-
'{italic ' +
|
|
33
|
-
i18n.__('constantsOptional') +
|
|
34
|
-
'}: ' +
|
|
35
|
-
i18n.__('scanOptionsLanguageSummaryOptional') +
|
|
36
|
-
'{italic ' +
|
|
37
|
-
i18n.__('constantsRequired') +
|
|
38
|
-
'}: ' +
|
|
39
|
-
i18n.__('scanOptionsLanguageSummaryRequired')
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
name: i18n.__('scanOptionsName'),
|
|
43
|
-
summary:
|
|
44
|
-
'{italic ' +
|
|
45
|
-
i18n.__('constantsOptional') +
|
|
46
|
-
'}: ' +
|
|
47
|
-
i18n.__('scanOptionsNameSummary')
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
name: i18n.__('scanOptionsTimeout'),
|
|
51
|
-
summary:
|
|
52
|
-
'{italic ' +
|
|
53
|
-
i18n.__('constantsOptional') +
|
|
54
|
-
'}: ' +
|
|
55
|
-
i18n.__('scanOptionsTimeoutSummary')
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
name: i18n.__('scanOptionsVerbose'),
|
|
59
|
-
summary:
|
|
60
|
-
'{italic ' +
|
|
61
|
-
i18n.__('constantsOptional') +
|
|
62
|
-
'}: ' +
|
|
63
|
-
i18n.__('scanOptionsVerboseSummary')
|
|
64
|
-
}
|
|
21
|
+
optionList: constants.commandLineDefinitions.scanOptionDefinitions,
|
|
22
|
+
hide: [
|
|
23
|
+
'project-id',
|
|
24
|
+
'language',
|
|
25
|
+
'organization-id',
|
|
26
|
+
'api-key',
|
|
27
|
+
'authorization',
|
|
28
|
+
'host',
|
|
29
|
+
'proxy',
|
|
30
|
+
'ff',
|
|
31
|
+
'ignore-cert-errors'
|
|
65
32
|
]
|
|
66
33
|
},
|
|
67
34
|
{
|
|
@@ -21,6 +21,10 @@ const createProjectId = async (config, client) => {
|
|
|
21
21
|
console.log(i18n.__('foundExistingProjectScan'))
|
|
22
22
|
return
|
|
23
23
|
}
|
|
24
|
+
if (res.statusCode === 403) {
|
|
25
|
+
console.log(i18n.__('permissionsError'))
|
|
26
|
+
return
|
|
27
|
+
}
|
|
24
28
|
|
|
25
29
|
if (res.statusCode === 201) {
|
|
26
30
|
console.log(i18n.__('projectCreatedScan'))
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
|
|
3
|
+
const writeResultsToFile = (responseBody, name = 'results.sarif') => {
|
|
4
|
+
fs.writeFile(name, JSON.stringify(responseBody, null, 2), err => {
|
|
5
|
+
if (err) {
|
|
6
|
+
console.log('Error writing Scan Results to file')
|
|
7
|
+
} else {
|
|
8
|
+
console.log(`Scan Results saved to ${name}`)
|
|
9
|
+
}
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
writeResultsToFile
|
|
15
|
+
}
|
package/src/scan/scan.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
const commonApi = require('../utils/commonApi.js')
|
|
2
2
|
const fileUtils = require('../scan/fileUtils')
|
|
3
|
-
const allowedFileTypes = ['.jar', '.war', '.js', '.zip']
|
|
3
|
+
const allowedFileTypes = ['.jar', '.war', '.js', '.zip', '.exe']
|
|
4
4
|
const i18n = require('i18n')
|
|
5
|
-
const AdmZip = require('adm-zip')
|
|
6
5
|
const oraWrapper = require('../utils/oraWrapper')
|
|
7
|
-
const
|
|
6
|
+
const chalk = require('chalk')
|
|
8
7
|
|
|
9
8
|
const isFileAllowed = scanOption => {
|
|
10
9
|
let valid = false
|
|
@@ -16,6 +15,14 @@ const isFileAllowed = scanOption => {
|
|
|
16
15
|
return valid
|
|
17
16
|
}
|
|
18
17
|
|
|
18
|
+
const stripMustacheTags = oldString => {
|
|
19
|
+
return oldString
|
|
20
|
+
.replace(/\n/g, ' ')
|
|
21
|
+
.replace(/{{.*?}}/g, '\n')
|
|
22
|
+
.replace(/\s+/g, ' ')
|
|
23
|
+
.trim()
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
const sendScan = async config => {
|
|
20
27
|
if (!isFileAllowed(config.file)) {
|
|
21
28
|
console.log(i18n.__('scanErrorFileMessage'))
|
|
@@ -40,10 +47,17 @@ const sendScan = async config => {
|
|
|
40
47
|
}
|
|
41
48
|
return res.body.id
|
|
42
49
|
} else {
|
|
50
|
+
if (config.debug) {
|
|
51
|
+
console.log(res.statusCode)
|
|
52
|
+
console.log(config)
|
|
53
|
+
}
|
|
43
54
|
oraWrapper.failSpinner(
|
|
44
55
|
startUploadSpinner,
|
|
45
56
|
i18n.__('uploadingScanFail')
|
|
46
57
|
)
|
|
58
|
+
if (res.statusCode === 403) {
|
|
59
|
+
console.log(i18n.__('permissionsError'))
|
|
60
|
+
}
|
|
47
61
|
process.exit(1)
|
|
48
62
|
}
|
|
49
63
|
})
|
|
@@ -53,74 +67,96 @@ const sendScan = async config => {
|
|
|
53
67
|
}
|
|
54
68
|
}
|
|
55
69
|
|
|
56
|
-
const zipValidator = configToUse => {
|
|
57
|
-
if (configToUse.file.endsWith('.zip')) {
|
|
58
|
-
let zipFileName = configToUse.file.split('/').pop()
|
|
59
|
-
try {
|
|
60
|
-
let zip = new AdmZip(configToUse.file)
|
|
61
|
-
let zipEntries = zip.getEntries()
|
|
62
|
-
zipEntries.forEach(function(zipEntry) {
|
|
63
|
-
if (
|
|
64
|
-
!zipEntry.entryName.includes('._') &&
|
|
65
|
-
!zipEntry.entryName.includes('/.')
|
|
66
|
-
) {
|
|
67
|
-
if (!zipEntry.isDirectory) {
|
|
68
|
-
if (!zipEntry.entryName.endsWith('.js')) {
|
|
69
|
-
console.log(i18n.__('scanZipError', zipFileName))
|
|
70
|
-
process.exit(1)
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
})
|
|
75
|
-
configToUse.language = supportedLanguages.JAVASCRIPT
|
|
76
|
-
} catch {
|
|
77
|
-
console.log(i18n.__('zipFileException'))
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
70
|
const formatScanOutput = (overview, results) => {
|
|
83
71
|
console.log()
|
|
84
|
-
|
|
85
|
-
console.log()
|
|
72
|
+
//check for no vulnerabilities and show a different message
|
|
86
73
|
|
|
87
|
-
results.content.
|
|
88
|
-
console.log(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
'in',
|
|
92
|
-
entry.locations[0]?.physicalLocation.artifactLocation.uri,
|
|
93
|
-
'@',
|
|
94
|
-
entry.codeFlows[0]?.threadFlows[0]?.locations[0]?.location
|
|
95
|
-
?.physicalLocation?.region?.startLine
|
|
96
|
-
)
|
|
74
|
+
if (results.content.length === 0) {
|
|
75
|
+
console.log(i18n.__('scanNoVulnerabilitiesFound'))
|
|
76
|
+
} else {
|
|
77
|
+
console.log(chalk.bold('Here are your top priorities to fix'))
|
|
97
78
|
console.log()
|
|
98
|
-
})
|
|
99
79
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
80
|
+
const groups = getGroups(results.content)
|
|
81
|
+
groups.forEach(entry => {
|
|
82
|
+
console.log(
|
|
83
|
+
chalk.bold(
|
|
84
|
+
`${entry.severity} | ${entry.ruleId} (${entry.lineInfoSet.size})`
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
let count = 1
|
|
88
|
+
entry.lineInfoSet.forEach(lineInfo => {
|
|
89
|
+
console.log(`\t ${count}. ${lineInfo}`)
|
|
90
|
+
count++
|
|
91
|
+
})
|
|
92
|
+
console.log(chalk.bold('How to fix:'))
|
|
93
|
+
console.log(entry.recommendation)
|
|
94
|
+
console.log()
|
|
95
|
+
})
|
|
106
96
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
overview.
|
|
112
|
-
overview.high,
|
|
113
|
-
overview.medium,
|
|
114
|
-
overview.low,
|
|
97
|
+
const totalVulnerabilities =
|
|
98
|
+
overview.critical +
|
|
99
|
+
overview.high +
|
|
100
|
+
overview.medium +
|
|
101
|
+
overview.low +
|
|
115
102
|
overview.note
|
|
103
|
+
|
|
104
|
+
console.log(chalk.bold(`Found ${totalVulnerabilities} vulnerabilities`))
|
|
105
|
+
console.log(
|
|
106
|
+
i18n.__(
|
|
107
|
+
'foundDetailedVulnerabilities',
|
|
108
|
+
overview.critical,
|
|
109
|
+
overview.high,
|
|
110
|
+
overview.medium,
|
|
111
|
+
overview.low,
|
|
112
|
+
overview.note
|
|
113
|
+
)
|
|
116
114
|
)
|
|
117
|
-
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const getGroups = content => {
|
|
119
|
+
const groupTypeSet = new Set(content.map(({ ruleId }) => ruleId))
|
|
120
|
+
let groupTypeResults = []
|
|
121
|
+
groupTypeSet.forEach(groupName => {
|
|
122
|
+
let groupResultsObj = {
|
|
123
|
+
ruleId: groupName,
|
|
124
|
+
lineInfoSet: new Set(),
|
|
125
|
+
recommendation: '',
|
|
126
|
+
severity: ''
|
|
127
|
+
}
|
|
128
|
+
content.forEach(resultEntry => {
|
|
129
|
+
if (resultEntry.ruleId === groupName) {
|
|
130
|
+
groupResultsObj.severity = resultEntry.severity
|
|
131
|
+
groupResultsObj.recommendation = resultEntry.recommendation
|
|
132
|
+
? stripMustacheTags(resultEntry.recommendation)
|
|
133
|
+
: ''
|
|
134
|
+
groupResultsObj.lineInfoSet.add(formattedCodeLine(resultEntry))
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
groupTypeResults.push(groupResultsObj)
|
|
138
|
+
})
|
|
139
|
+
return groupTypeResults
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const formattedCodeLine = resultEntry => {
|
|
143
|
+
let lineUri = resultEntry.locations[0]?.physicalLocation.artifactLocation.uri
|
|
144
|
+
return lineUri + ' @ ' + setLineNumber(resultEntry)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const setLineNumber = resultEntry => {
|
|
148
|
+
return resultEntry.codeFlows?.[0]?.threadFlows[0]?.locations[0]?.location
|
|
149
|
+
?.physicalLocation?.region?.startLine
|
|
150
|
+
? resultEntry.codeFlows[0]?.threadFlows[0]?.locations[0]?.location
|
|
151
|
+
?.physicalLocation?.region?.startLine
|
|
152
|
+
: resultEntry.locations[0]?.physicalLocation?.region?.startLine
|
|
118
153
|
}
|
|
119
154
|
|
|
120
155
|
module.exports = {
|
|
121
156
|
sendScan: sendScan,
|
|
157
|
+
getGroups: getGroups,
|
|
122
158
|
allowedFileTypes: allowedFileTypes,
|
|
123
159
|
isFileAllowed: isFileAllowed,
|
|
124
|
-
|
|
125
|
-
|
|
160
|
+
stripMustacheTags: stripMustacheTags,
|
|
161
|
+
formatScanOutput: formatScanOutput
|
|
126
162
|
}
|