@contrast/contrast 1.0.1 → 1.0.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.
- package/README.md +96 -134
- package/dist/common/errorHandling.js +31 -1
- package/dist/constants/constants.js +1 -1
- package/dist/constants/locales.js +8 -2
- package/dist/index.js +4 -1
- package/dist/scan/scan.js +7 -2
- package/dist/scan/scanResults.js +1 -0
- package/dist/utils/paramsUtil/commandlineParams.js +1 -1
- package/package.json +1 -1
- package/src/common/errorHandling.ts +43 -2
- package/src/constants/constants.js +1 -1
- package/src/constants/locales.js +9 -2
- package/src/index.ts +7 -1
- package/src/scan/scan.js +8 -3
- package/src/scan/scanResults.js +1 -0
- package/src/utils/paramsUtil/commandlineParams.js +1 -1
- package/dist/lambda/scanDetail.js +0 -30
- package/dist/scan/fileFinder.js +0 -15
- package/dist/utils/fileUtils.js +0 -31
- package/dist/utils/paramsUtil/genericCommandLineParams.js +0 -12
- package/dist/utils/paramsUtil/yamlParams.js +0 -6
package/README.md
CHANGED
|
@@ -1,33 +1,43 @@
|
|
|
1
|
-
# Contrast
|
|
1
|
+
# Contrast Free
|
|
2
2
|
|
|
3
|
-
-
|
|
4
|
-
-
|
|
5
|
-
-
|
|
6
|
-
|
|
7
|
-
- [Install NPM / YARN](#install-npm--yarn)
|
|
8
|
-
- [Install with binaries](#install-with-binaries)
|
|
9
|
-
- [Step 2 – Authenticate](#step-2--authenticate)
|
|
10
|
-
- [Step 3 – Scan](#step-3--scan)
|
|
11
|
-
- [Usage](#usage)
|
|
12
|
-
- [Commands](#commands)
|
|
13
|
-
- [Examples of usage](#examples-of-usage)
|
|
14
|
-
- [Example of Results](#example-of-results)
|
|
15
|
-
- [Dependencies Vulnerabilities](#dependencies-vulnerabilities)
|
|
16
|
-
- [Least Privilege](#least-privilege)
|
|
3
|
+
- The fastest and most accurate SAST scanner.
|
|
4
|
+
- Immediate and actionable results — scan code and serverless environments.
|
|
5
|
+
- A frictionless and seamless sign-in process with GitHub or Google Account. From start to finish in minutes.
|
|
6
|
+
- By running a scan on your lambda functions, you can find: Least privilege identity and access management (IAM) vulnerabilities (over permissive policies) and remediation.
|
|
17
7
|
|
|
18
|
-
##
|
|
8
|
+
## Install
|
|
19
9
|
|
|
20
|
-
|
|
21
|
-
|
|
10
|
+
```
|
|
11
|
+
npm install -g @contrast/contrast
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Authenticate
|
|
15
|
+
|
|
16
|
+
Authenticate by entering contrast auth in the terminal.
|
|
17
|
+
|
|
18
|
+
In the resulting browser window, log in and authenticate with your GitHub or Google credentials.
|
|
19
|
+
|
|
20
|
+
## Run a scan
|
|
21
|
+
|
|
22
|
+
### SAST scan
|
|
23
|
+
|
|
24
|
+
####Requirements
|
|
25
|
+
Make sure you have the correct file types to scan.
|
|
22
26
|
|
|
23
|
-
-
|
|
24
|
-
-
|
|
27
|
+
- Upload a .jar or .war file to scan a Java project for analysis
|
|
28
|
+
- Upload a .js or .zip file to scan a JavaScript project for analysis
|
|
29
|
+
- Upload a .exe. or .zip file to scan a .NET c# web forms project
|
|
25
30
|
|
|
26
|
-
|
|
31
|
+
Start scanning
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
Use the Contrast scan command `contrast scan`
|
|
34
|
+
|
|
35
|
+
### Lambda function scan
|
|
36
|
+
|
|
37
|
+
####Requirements
|
|
38
|
+
|
|
39
|
+
- Currently supports Java and Python functions on AWS.
|
|
40
|
+
Configure AWS credentials on your local environment by running the commands with your credentials:
|
|
31
41
|
|
|
32
42
|
```shell
|
|
33
43
|
export AWS_DEFAULT_REGION=<YOUR_AWS_REGION>
|
|
@@ -35,148 +45,100 @@ export AWS_ACCESS_KEY_ID=<YOUR_ACCESS_KEY_ID>
|
|
|
35
45
|
export AWS_SECRET_ACCESS_KEY=<YOUR_SECRET_ACCESS_KEY>
|
|
36
46
|
```
|
|
37
47
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
- Lambda: [GetFunction](https://docs.aws.amazon.com/lambda/latest/dg/API_GetFunction.html) | [GetLayerVersion](https://docs.aws.amazon.com/lambda/latest/dg/API_GetLayerVersion.html)
|
|
41
|
-
- IAM: [GetRolePolicy](https://docs.aws.amazon.com/IAM/latest/APIReference/API_GetRolePolicy.html) | [GetPolicy](https://docs.aws.amazon.com/IAM/latest/APIReference/API_GetPolicy.html) | [GetPolicyVersion](https://docs.aws.amazon.com/IAM/latest/APIReference/API_GetPolicyVersion.html) | [ListRolePolicies](https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListRolePolicies.html) | [ListAttachedRolePolicies](https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListAttachedRolePolicies.html)
|
|
42
|
-
|
|
43
|
-
Policy example:
|
|
44
|
-
|
|
45
|
-
```json
|
|
46
|
-
{
|
|
47
|
-
"Version": "2012-10-17",
|
|
48
|
-
"Statement": [
|
|
49
|
-
{
|
|
50
|
-
"Sid": "VisualEditor0",
|
|
51
|
-
"Effect": "Allow",
|
|
52
|
-
"Action": [
|
|
53
|
-
"iam:GetPolicyVersion",
|
|
54
|
-
"iam:GetPolicy",
|
|
55
|
-
"lambda:GetLayerVersion",
|
|
56
|
-
"lambda:GetFunction",
|
|
57
|
-
"iam:ListAttachedRolePolicies",
|
|
58
|
-
"iam:ListRolePolicies",
|
|
59
|
-
"iam:GetRolePolicy"
|
|
60
|
-
],
|
|
61
|
-
"Resource": [
|
|
62
|
-
"arn:aws:lambda:*:YOUR_ACCOUNT:layer:*:*",
|
|
63
|
-
"arn:aws:lambda:*:YOUR_ACCOUNT:function:*",
|
|
64
|
-
"arn:aws:iam::YOUR_ACCOUNT:role/*",
|
|
65
|
-
"arn:aws:iam::YOUR_ACCOUNT:policy/*"
|
|
66
|
-
]
|
|
67
|
-
}
|
|
68
|
-
]
|
|
69
|
-
}
|
|
70
|
-
```
|
|
48
|
+
- AWS credentials should be available on your local configure (usually **~/.aws/credentials**). You have an option to run a lambda scan with your aws-profile to pass --profile. You also can export different credentials.
|
|
71
49
|
|
|
72
|
-
|
|
50
|
+
- These permissions are required to gather all required information on an AWS Lambda to use the `contrast lambda` command:
|
|
73
51
|
|
|
74
|
-
|
|
52
|
+
- Lambda: [GetFunction](https://docs.aws.amazon.com/lambda/latest/dg/API_GetFunction.html) | [GetLayerVersion](https://docs.aws.amazon.com/lambda/latest/dg/API_GetLayerVersion.html)
|
|
53
|
+
- IAM: [GetRolePolicy](https://docs.aws.amazon.com/IAM/latest/APIReference/API_GetRolePolicy.html) | [GetPolicy](https://docs.aws.amazon.com/IAM/latest/APIReference/API_GetPolicy.html) | [GetPolicyVersion](https://docs.aws.amazon.com/IAM/latest/APIReference/API_GetPolicyVersion.html) | [ListRolePolicies](https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListRolePolicies.html) | [ListAttachedRolePolicies](https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListAttachedRolePolicies.html)
|
|
75
54
|
|
|
76
|
-
|
|
77
|
-
- `brew install contrast`
|
|
55
|
+
### Start scanning
|
|
78
56
|
|
|
79
|
-
|
|
57
|
+
Use contrast lambda to scan your AWS Lambda functions.
|
|
58
|
+
`contrast lambda --function-name MyFunctionName --region my-aws-region`
|
|
80
59
|
|
|
81
|
-
|
|
82
|
-
- `yarn global add @contrast/contrast`
|
|
60
|
+
## Contrast Free commands
|
|
83
61
|
|
|
84
|
-
###
|
|
62
|
+
### auth
|
|
85
63
|
|
|
86
|
-
|
|
87
|
-
- Select your operating system and download the package
|
|
88
|
-
- You must allow **execute permissions** on the file depending on your OS
|
|
64
|
+
Authenticate Contrast using your GitHub or Google account. A new browser window will open for login.
|
|
89
65
|
|
|
90
|
-
|
|
91
|
-
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
92
|
-
| macOS | [https://pkg.contrastsecurity.com/ui/repos/tree/General/cli/1.0.0/mac/contrast](https://pkg.contrastsecurity.com/ui/repos/tree/General/cli/1.0.0/mac/contrast) |
|
|
93
|
-
| Linux | [https://pkg.contrastsecurity.com/ui/repos/tree/General/cli/1.0.0/linux/contrast](https://pkg.contrastsecurity.com/ui/repos/tree/General/cli/1.0.0/linux/contrast) |
|
|
94
|
-
| Windows | [https://pkg.contrastsecurity.com/ui/repos/tree/General/cli/1.0.0/windows/contrast.exe](https://pkg.contrastsecurity.com/ui/repos/tree/General/cli/1.0.0/windows/contrast.exe) |
|
|
66
|
+
**Usage:** `contrast auth`
|
|
95
67
|
|
|
96
|
-
|
|
68
|
+
### config
|
|
97
69
|
|
|
98
|
-
|
|
70
|
+
Displays stored credentials.
|
|
99
71
|
|
|
100
|
-
|
|
101
|
-
contrast auth
|
|
102
|
-
```
|
|
72
|
+
**Usage:** `contrast config`
|
|
103
73
|
|
|
104
|
-
|
|
74
|
+
**Options:**
|
|
105
75
|
|
|
106
|
-
|
|
76
|
+
- **-c, --clear** - Removes stored credentials.
|
|
107
77
|
|
|
108
|
-
|
|
109
|
-
contrast lambda --function-name MyFunctionName --region my-aws-region
|
|
110
|
-
```
|
|
78
|
+
### scan
|
|
111
79
|
|
|
112
|
-
|
|
80
|
+
Performs a security SAST scan.
|
|
113
81
|
|
|
114
|
-
|
|
115
|
-
- `contrast lambda --list-functions`
|
|
116
|
-
- `contrast lambda --function-name <function> [options]`
|
|
117
|
-
- `contrast lambda --help`
|
|
82
|
+
**Usage:** `contrast scan [option]`
|
|
118
83
|
|
|
119
|
-
|
|
84
|
+
**Options:**
|
|
120
85
|
|
|
121
|
-
-
|
|
122
|
-
|
|
123
|
-
- `--function-name` - Name of AWS lambda function to scan
|
|
124
|
-
- `--list-functions` - List all available lambda functions to scan
|
|
125
|
-
- `--endpoint-url` (optional) - AWS Endpoint override, works like in AWS CLI
|
|
126
|
-
- `--region` (optional) - Region override, default to AWS_DEAFAULT_REGION env var, works like in AWS CLI
|
|
127
|
-
- `--profile` (optional) - AWS configuration profile override, works like in AWS CLI
|
|
128
|
-
- `--json-output` (optional) - Return response in JSON (versus default human readable format)
|
|
129
|
-
- `--verbose` (optional) - Returns extended information to the terminal
|
|
130
|
-
- `--help` Displays usage guide
|
|
131
|
-
- `version` - Displays version of Contrast CLI
|
|
132
|
-
- `config` - Displays stored credentials
|
|
133
|
-
- `config --clear` - Removes stored credentials
|
|
134
|
-
- `help` - Displays usage guide
|
|
86
|
+
- **contrast scan --file** Path of the file you want to scan. Contrast searches for a .jar, .war, .js. or .zip file in the working directory if a file is not specified.
|
|
87
|
+
Alias: **--f**
|
|
135
88
|
|
|
136
|
-
|
|
89
|
+
- **contrast scan --name**
|
|
90
|
+
Contrast project name. If not specified, Contrast uses contrast.settings to identify the project or creates a project.
|
|
91
|
+
Alias: **–n**
|
|
92
|
+
- **contrast scan --save**
|
|
93
|
+
Download the results to a Static Analysis Results Interchange Format (SARIF) file.
|
|
94
|
+
Alias: **-s**
|
|
137
95
|
|
|
138
|
-
|
|
96
|
+
- **contrast scan --timeout**
|
|
97
|
+
Time in seconds to wait for the scan to complete. Default value is 300 seconds.
|
|
98
|
+
Alias: **-t**
|
|
139
99
|
|
|
140
|
-
|
|
141
|
-
contrast lambda --help
|
|
142
|
-
```
|
|
100
|
+
### lambda
|
|
143
101
|
|
|
144
|
-
|
|
102
|
+
Name of AWS lambda function to scan.
|
|
145
103
|
|
|
146
|
-
|
|
147
|
-
contrast lambda --list-functions
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
scan lambda function in specific region
|
|
104
|
+
**Usage:** `contrast lambda --function-name`
|
|
151
105
|
|
|
152
|
-
|
|
153
|
-
contrast lambda -f myFunctionName --region eu-cental-1
|
|
154
|
-
```
|
|
106
|
+
**Options:**
|
|
155
107
|
|
|
156
|
-
|
|
108
|
+
- **contrast lambda --function-name --endpoint-url**
|
|
109
|
+
AWS Endpoint override. Similar to AWS CLI.
|
|
110
|
+
Alias: **-e**
|
|
157
111
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
112
|
+
- **contrast lambda --function-name --region**
|
|
113
|
+
Region override. Defaults to AWS_DEFAULT_REGION. Similar to AWS CLI.
|
|
114
|
+
Alias: **-r**
|
|
161
115
|
|
|
162
|
-
|
|
116
|
+
- **contrast lambda --function-name --profile**
|
|
117
|
+
AWS configuration profile override. Similar to AWS CLI.
|
|
118
|
+
Alias: **-p**
|
|
163
119
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
120
|
+
- **contrast lambda --function-name --json**
|
|
121
|
+
Return response in JSON (versus default human-readable format).
|
|
122
|
+
Alias: **-j**
|
|
167
123
|
|
|
168
|
-
|
|
124
|
+
- **contrast lambda -–function-name -–verbose**
|
|
125
|
+
Returns extended information to the terminal.
|
|
126
|
+
Alias: **-v**
|
|
169
127
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
```
|
|
128
|
+
- **contrast lambda -–function-name --list-functions**
|
|
129
|
+
Lists all available lambda functions to scan.
|
|
173
130
|
|
|
174
|
-
|
|
131
|
+
- **contrast lambda --function-name -–help**
|
|
132
|
+
Displays usage guide.
|
|
133
|
+
Alias: **-h**
|
|
175
134
|
|
|
176
|
-
###
|
|
135
|
+
### help
|
|
177
136
|
|
|
178
|
-
|
|
137
|
+
Displays usage guide. To list detailed help for any CLI command, add the -h or --help flag to the command.
|
|
138
|
+
**Usage:** `contrast scan --help`
|
|
139
|
+
Alias: **-h**
|
|
179
140
|
|
|
180
|
-
###
|
|
141
|
+
### version
|
|
181
142
|
|
|
182
|
-
|
|
143
|
+
Displays version of Contrast CLI.
|
|
144
|
+
**Usage:** `contrast version` Alias: **-v**, **--version**
|
|
@@ -3,8 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.libraryAnalysisError = exports.handleResponseErrors = exports.getErrorMessage = exports.generalError = exports.hostWarningError = exports.failOptionError = exports.proxyError = exports.forbiddenError = exports.badRequestError = exports.unauthenticatedError = exports.genericError = void 0;
|
|
6
|
+
exports.approximateCommandOnError = exports.libraryAnalysisError = exports.handleResponseErrors = exports.getErrorMessage = exports.generalError = exports.hostWarningError = exports.failOptionError = exports.proxyError = exports.forbiddenError = exports.badRequestError = exports.unauthenticatedError = exports.genericError = void 0;
|
|
7
7
|
const i18n_1 = __importDefault(require("i18n"));
|
|
8
|
+
const lodash_1 = require("lodash");
|
|
8
9
|
const handleResponseErrors = (res, api) => {
|
|
9
10
|
if (res.statusCode === 400) {
|
|
10
11
|
api === 'catalogue' ? badRequestError(true) : badRequestError(false);
|
|
@@ -106,3 +107,32 @@ const generalError = (header, message) => {
|
|
|
106
107
|
console.log(finalMessage);
|
|
107
108
|
};
|
|
108
109
|
exports.generalError = generalError;
|
|
110
|
+
const approximateCommandOnError = (unknownOptions) => {
|
|
111
|
+
const commandKeywords = {
|
|
112
|
+
auth: 'auth',
|
|
113
|
+
audit: 'audit',
|
|
114
|
+
scan: 'scan',
|
|
115
|
+
lambda: 'lambda',
|
|
116
|
+
config: 'config'
|
|
117
|
+
};
|
|
118
|
+
const sortedUnknownOptions = (0, lodash_1.sortBy)(unknownOptions, param => param === 'auth' ||
|
|
119
|
+
param === 'audit' ||
|
|
120
|
+
param === 'scan' ||
|
|
121
|
+
param === 'lambda' ||
|
|
122
|
+
param === 'config'
|
|
123
|
+
? 0
|
|
124
|
+
: 1);
|
|
125
|
+
const foundCommands = sortedUnknownOptions.filter(command => commandKeywords[command]);
|
|
126
|
+
const parsedUnknownOptions = sortedUnknownOptions
|
|
127
|
+
.toString()
|
|
128
|
+
.replace(/,/g, ' ');
|
|
129
|
+
const approximateParams = parsedUnknownOptions
|
|
130
|
+
.replace(new RegExp(foundCommands.join('|'), 'g'), '')
|
|
131
|
+
.trim();
|
|
132
|
+
const approximateCommand = `${foundCommands[0]} ${approximateParams}`;
|
|
133
|
+
return {
|
|
134
|
+
approximateCommand,
|
|
135
|
+
approximateCommandKeyword: foundCommands[0]
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
exports.approximateCommandOnError = approximateCommandOnError;
|
|
@@ -12,7 +12,7 @@ const MEDIUM = 'MEDIUM';
|
|
|
12
12
|
const HIGH = 'HIGH';
|
|
13
13
|
const CRITICAL = 'CRITICAL';
|
|
14
14
|
const APP_NAME = 'contrast';
|
|
15
|
-
const APP_VERSION = '1.0.
|
|
15
|
+
const APP_VERSION = '1.0.2';
|
|
16
16
|
const TIMEOUT = 120000;
|
|
17
17
|
const AUTH_UI_URL = 'https://cli-auth.contrastsecurity.com';
|
|
18
18
|
const AUTH_CALLBACK_URL = 'https://cli-auth-api.contrastsecurity.com';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const { lambda } = require('./lambda');
|
|
3
|
+
const chalk = require('chalk');
|
|
3
4
|
const en_locales = () => {
|
|
4
5
|
return {
|
|
5
6
|
successHeader: 'SUCCESS',
|
|
@@ -187,6 +188,7 @@ const en_locales = () => {
|
|
|
187
188
|
noFileFoundScan: "We could't find a suitable file in your directories (we go 3 deep)",
|
|
188
189
|
specifyFileScanError: 'Java Scan requires a .war or .jar file. Javascript Scan requires a .js or .zip file.\nTo start a Scan enter "contrast scan -f <path-to-file>"',
|
|
189
190
|
populateProjectIdMessage: 'project ID is %s',
|
|
191
|
+
genericServiceError: 'returned with status code %s',
|
|
190
192
|
permissionsError: 'You do not have the correct permissions here. \n Contact support@contrastsecurity.com to get this fixed.',
|
|
191
193
|
scanErrorFileMessage: 'We only accept the following file types: \nJava - .jar, .war \nJavaScript - .js or .zip files',
|
|
192
194
|
helpAuthSummary: 'Authenticate Contrast using your Github or Google account',
|
|
@@ -207,13 +209,17 @@ const en_locales = () => {
|
|
|
207
209
|
scanOptionsFileNameSummary: 'Path of the file you want to scan. If no file is specified, Contrast searches for a .jar, .war, .js, .exe or .zip file in the working directory.',
|
|
208
210
|
scanOptionsVerboseSummary: ' Returns extended information to the terminal.',
|
|
209
211
|
authSuccessMessage: 'Authentication successful',
|
|
210
|
-
runAuthSuccessMessage:
|
|
212
|
+
runAuthSuccessMessage: "Now you can use Contrast CLI \nRun 'contrast scan' on your file \n" +
|
|
213
|
+
"or 'contrast help' to learn more about the capabilities.",
|
|
211
214
|
authWaitingMessage: 'Waiting for auth...',
|
|
212
215
|
authTimedOutMessage: 'Auth Timed out, try again',
|
|
213
216
|
zipErrorScan: 'We only support zip files for JAVASCRIPT language, please set the flag --language JAVASCRIPT',
|
|
214
217
|
unknownFileErrorScan: 'Unsupported file selected for Scan.',
|
|
215
218
|
foundScanFile: 'found: %s',
|
|
216
|
-
foundDetailedVulnerabilities: '%s Critical
|
|
219
|
+
foundDetailedVulnerabilities: chalk.bold('%s Critical') +
|
|
220
|
+
' | ' +
|
|
221
|
+
chalk.bold('%s High') +
|
|
222
|
+
' | %s Medium | %s Low | %s Note',
|
|
217
223
|
requiredParams: 'All required parameters are not present.',
|
|
218
224
|
timeoutScan: 'Timeout set to 5 minutes.',
|
|
219
225
|
searchingScanFileDirectory: 'Searching for file to scan from %s...',
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,9 @@ const getMainOption = () => {
|
|
|
30
30
|
const start = async () => {
|
|
31
31
|
const { mainOptions, argv: argvMain } = getMainOption();
|
|
32
32
|
const command = mainOptions.command != undefined ? mainOptions.command.toLowerCase() : '';
|
|
33
|
-
if (command === 'version'
|
|
33
|
+
if (command === 'version' ||
|
|
34
|
+
argvMain.includes('--v') ||
|
|
35
|
+
argvMain.includes('--version')) {
|
|
34
36
|
console.log(constants_2.APP_VERSION);
|
|
35
37
|
return;
|
|
36
38
|
}
|
|
@@ -57,6 +59,7 @@ const start = async () => {
|
|
|
57
59
|
}
|
|
58
60
|
else {
|
|
59
61
|
console.log('Unknown Command: ' + command + ' \nUse --help for the full list');
|
|
62
|
+
process.exit(9);
|
|
60
63
|
}
|
|
61
64
|
};
|
|
62
65
|
start();
|
package/dist/scan/scan.js
CHANGED
|
@@ -18,6 +18,7 @@ const stripMustacheTags = oldString => {
|
|
|
18
18
|
return oldString
|
|
19
19
|
.replace(/\n/g, ' ')
|
|
20
20
|
.replace(/{{.*?}}/g, '\n')
|
|
21
|
+
.replace(/\$\$LINK_DELIM\$\$/g, '\n')
|
|
21
22
|
.replace(/\s+/g, ' ')
|
|
22
23
|
.trim();
|
|
23
24
|
};
|
|
@@ -50,6 +51,7 @@ const sendScan = async (config) => {
|
|
|
50
51
|
if (res.statusCode === 403) {
|
|
51
52
|
console.log(i18n.__('permissionsError'));
|
|
52
53
|
}
|
|
54
|
+
console.log(i18n.__('genericServiceError', res.statusCode));
|
|
53
55
|
process.exit(1);
|
|
54
56
|
}
|
|
55
57
|
})
|
|
@@ -64,7 +66,10 @@ const formatScanOutput = (overview, results) => {
|
|
|
64
66
|
console.log(i18n.__('scanNoVulnerabilitiesFound'));
|
|
65
67
|
}
|
|
66
68
|
else {
|
|
67
|
-
|
|
69
|
+
let message = overview.critical || overview.high
|
|
70
|
+
? 'Here are your top priorities to fix'
|
|
71
|
+
: "No major issues, here's what we found";
|
|
72
|
+
console.log(chalk.bold(message));
|
|
68
73
|
console.log();
|
|
69
74
|
const groups = getGroups(results.content);
|
|
70
75
|
groups.forEach(entry => {
|
|
@@ -102,7 +107,7 @@ const getGroups = content => {
|
|
|
102
107
|
groupResultsObj.severity = resultEntry.severity;
|
|
103
108
|
groupResultsObj.recommendation = resultEntry.recommendation
|
|
104
109
|
? stripMustacheTags(resultEntry.recommendation)
|
|
105
|
-
: '';
|
|
110
|
+
: 'No Recommendations Data Found';
|
|
106
111
|
groupResultsObj.lineInfoSet.add(formattedCodeLine(resultEntry));
|
|
107
112
|
}
|
|
108
113
|
});
|
package/dist/scan/scanResults.js
CHANGED
|
@@ -40,6 +40,7 @@ const returnScanResults = async (config, codeArtifactId, timeout, startScanSpinn
|
|
|
40
40
|
if (result.body.status === 'FAILED') {
|
|
41
41
|
complete = true;
|
|
42
42
|
oraFunctions.failSpinner(startScanSpinner, 'Contrast Scan Failed.');
|
|
43
|
+
console.log(result.body.errorMessage);
|
|
43
44
|
process.exit(1);
|
|
44
45
|
}
|
|
45
46
|
}
|
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import i18n from 'i18n'
|
|
2
|
+
import { sortBy } from 'lodash'
|
|
2
3
|
|
|
3
4
|
const handleResponseErrors = (res: any, api: string) => {
|
|
4
5
|
if (res.statusCode === 400) {
|
|
@@ -104,7 +105,7 @@ const getErrorMessage = (header: string, message?: string) => {
|
|
|
104
105
|
const multiLine = message?.includes('\n')
|
|
105
106
|
let finalMessage = ''
|
|
106
107
|
|
|
107
|
-
// i18n split the line if it includes
|
|
108
|
+
// i18n split the line if it includes '\n'
|
|
108
109
|
if (multiLine) {
|
|
109
110
|
finalMessage = `\n${message}`
|
|
110
111
|
} else if (message) {
|
|
@@ -119,6 +120,45 @@ const generalError = (header: string, message?: string) => {
|
|
|
119
120
|
console.log(finalMessage)
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
const approximateCommandOnError = (unknownOptions: string[]) => {
|
|
124
|
+
const commandKeywords = {
|
|
125
|
+
auth: 'auth',
|
|
126
|
+
audit: 'audit',
|
|
127
|
+
scan: 'scan',
|
|
128
|
+
lambda: 'lambda',
|
|
129
|
+
config: 'config'
|
|
130
|
+
}
|
|
131
|
+
const sortedUnknownOptions = sortBy(unknownOptions, param =>
|
|
132
|
+
param === 'auth' ||
|
|
133
|
+
param === 'audit' ||
|
|
134
|
+
param === 'scan' ||
|
|
135
|
+
param === 'lambda' ||
|
|
136
|
+
param === 'config'
|
|
137
|
+
? 0
|
|
138
|
+
: 1
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
const foundCommands = sortedUnknownOptions.filter(
|
|
142
|
+
// @ts-ignore
|
|
143
|
+
command => commandKeywords[command]
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
const parsedUnknownOptions = sortedUnknownOptions
|
|
147
|
+
.toString()
|
|
148
|
+
.replace(/,/g, ' ')
|
|
149
|
+
|
|
150
|
+
const approximateParams = parsedUnknownOptions
|
|
151
|
+
.replace(new RegExp(foundCommands.join('|'), 'g'), '')
|
|
152
|
+
.trim()
|
|
153
|
+
|
|
154
|
+
const approximateCommand = `${foundCommands[0]} ${approximateParams}`
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
approximateCommand,
|
|
158
|
+
approximateCommandKeyword: foundCommands[0]
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
122
162
|
export {
|
|
123
163
|
genericError,
|
|
124
164
|
unauthenticatedError,
|
|
@@ -130,5 +170,6 @@ export {
|
|
|
130
170
|
generalError,
|
|
131
171
|
getErrorMessage,
|
|
132
172
|
handleResponseErrors,
|
|
133
|
-
libraryAnalysisError
|
|
173
|
+
libraryAnalysisError,
|
|
174
|
+
approximateCommandOnError
|
|
134
175
|
}
|
|
@@ -15,7 +15,7 @@ const HIGH = 'HIGH'
|
|
|
15
15
|
const CRITICAL = 'CRITICAL'
|
|
16
16
|
|
|
17
17
|
const APP_NAME = 'contrast'
|
|
18
|
-
const APP_VERSION = '1.0.
|
|
18
|
+
const APP_VERSION = '1.0.2'
|
|
19
19
|
const TIMEOUT = 120000
|
|
20
20
|
const AUTH_UI_URL = 'https://cli-auth.contrastsecurity.com'
|
|
21
21
|
const AUTH_CALLBACK_URL = 'https://cli-auth-api.contrastsecurity.com'
|
package/src/constants/locales.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { lambda } = require('./lambda')
|
|
2
|
+
const chalk = require('chalk')
|
|
2
3
|
|
|
3
4
|
const en_locales = () => {
|
|
4
5
|
return {
|
|
@@ -288,6 +289,7 @@ const en_locales = () => {
|
|
|
288
289
|
specifyFileScanError:
|
|
289
290
|
'Java Scan requires a .war or .jar file. Javascript Scan requires a .js or .zip file.\nTo start a Scan enter "contrast scan -f <path-to-file>"',
|
|
290
291
|
populateProjectIdMessage: 'project ID is %s',
|
|
292
|
+
genericServiceError: 'returned with status code %s',
|
|
291
293
|
permissionsError:
|
|
292
294
|
'You do not have the correct permissions here. \n Contact support@contrastsecurity.com to get this fixed.',
|
|
293
295
|
scanErrorFileMessage:
|
|
@@ -316,7 +318,9 @@ const en_locales = () => {
|
|
|
316
318
|
'Path of the file you want to scan. If no file is specified, Contrast searches for a .jar, .war, .js, .exe or .zip file in the working directory.',
|
|
317
319
|
scanOptionsVerboseSummary: ' Returns extended information to the terminal.',
|
|
318
320
|
authSuccessMessage: 'Authentication successful',
|
|
319
|
-
runAuthSuccessMessage:
|
|
321
|
+
runAuthSuccessMessage:
|
|
322
|
+
"Now you can use Contrast CLI \nRun 'contrast scan' on your file \n" +
|
|
323
|
+
"or 'contrast help' to learn more about the capabilities.",
|
|
320
324
|
authWaitingMessage: 'Waiting for auth...',
|
|
321
325
|
authTimedOutMessage: 'Auth Timed out, try again',
|
|
322
326
|
zipErrorScan:
|
|
@@ -324,7 +328,10 @@ const en_locales = () => {
|
|
|
324
328
|
unknownFileErrorScan: 'Unsupported file selected for Scan.',
|
|
325
329
|
foundScanFile: 'found: %s',
|
|
326
330
|
foundDetailedVulnerabilities:
|
|
327
|
-
'%s Critical
|
|
331
|
+
chalk.bold('%s Critical') +
|
|
332
|
+
' | ' +
|
|
333
|
+
chalk.bold('%s High') +
|
|
334
|
+
' | %s Medium | %s Low | %s Note',
|
|
328
335
|
requiredParams: 'All required parameters are not present.',
|
|
329
336
|
timeoutScan: 'Timeout set to 5 minutes.',
|
|
330
337
|
searchingScanFileDirectory: 'Searching for file to scan from %s...',
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { APP_NAME, APP_VERSION } from './constants/constants'
|
|
|
8
8
|
import { processLambda } from './lambda/lambda'
|
|
9
9
|
import { localConfig } from './utils/getConfig'
|
|
10
10
|
import findLatestCLIVersion from './common/findLatestCLIVersion'
|
|
11
|
+
import { approximateCommandOnError } from './common/errorHandling'
|
|
11
12
|
|
|
12
13
|
const {
|
|
13
14
|
commandLineDefinitions: { mainUsageGuide, mainDefinition }
|
|
@@ -33,7 +34,11 @@ const start = async () => {
|
|
|
33
34
|
const { mainOptions, argv: argvMain } = getMainOption()
|
|
34
35
|
const command =
|
|
35
36
|
mainOptions.command != undefined ? mainOptions.command.toLowerCase() : ''
|
|
36
|
-
if (
|
|
37
|
+
if (
|
|
38
|
+
command === 'version' ||
|
|
39
|
+
argvMain.includes('--v') ||
|
|
40
|
+
argvMain.includes('--version')
|
|
41
|
+
) {
|
|
37
42
|
console.log(APP_VERSION)
|
|
38
43
|
return
|
|
39
44
|
}
|
|
@@ -70,6 +75,7 @@ const start = async () => {
|
|
|
70
75
|
console.log(
|
|
71
76
|
'Unknown Command: ' + command + ' \nUse --help for the full list'
|
|
72
77
|
)
|
|
78
|
+
process.exit(9)
|
|
73
79
|
}
|
|
74
80
|
}
|
|
75
81
|
|
package/src/scan/scan.js
CHANGED
|
@@ -19,6 +19,7 @@ const stripMustacheTags = oldString => {
|
|
|
19
19
|
return oldString
|
|
20
20
|
.replace(/\n/g, ' ')
|
|
21
21
|
.replace(/{{.*?}}/g, '\n')
|
|
22
|
+
.replace(/\$\$LINK_DELIM\$\$/g, '\n')
|
|
22
23
|
.replace(/\s+/g, ' ')
|
|
23
24
|
.trim()
|
|
24
25
|
}
|
|
@@ -58,6 +59,7 @@ const sendScan = async config => {
|
|
|
58
59
|
if (res.statusCode === 403) {
|
|
59
60
|
console.log(i18n.__('permissionsError'))
|
|
60
61
|
}
|
|
62
|
+
console.log(i18n.__('genericServiceError', res.statusCode))
|
|
61
63
|
process.exit(1)
|
|
62
64
|
}
|
|
63
65
|
})
|
|
@@ -69,12 +71,15 @@ const sendScan = async config => {
|
|
|
69
71
|
|
|
70
72
|
const formatScanOutput = (overview, results) => {
|
|
71
73
|
console.log()
|
|
72
|
-
//check for no vulnerabilities and show a different message
|
|
73
74
|
|
|
74
75
|
if (results.content.length === 0) {
|
|
75
76
|
console.log(i18n.__('scanNoVulnerabilitiesFound'))
|
|
76
77
|
} else {
|
|
77
|
-
|
|
78
|
+
let message =
|
|
79
|
+
overview.critical || overview.high
|
|
80
|
+
? 'Here are your top priorities to fix'
|
|
81
|
+
: "No major issues, here's what we found"
|
|
82
|
+
console.log(chalk.bold(message))
|
|
78
83
|
console.log()
|
|
79
84
|
|
|
80
85
|
const groups = getGroups(results.content)
|
|
@@ -130,7 +135,7 @@ const getGroups = content => {
|
|
|
130
135
|
groupResultsObj.severity = resultEntry.severity
|
|
131
136
|
groupResultsObj.recommendation = resultEntry.recommendation
|
|
132
137
|
? stripMustacheTags(resultEntry.recommendation)
|
|
133
|
-
: ''
|
|
138
|
+
: 'No Recommendations Data Found'
|
|
134
139
|
groupResultsObj.lineInfoSet.add(formattedCodeLine(resultEntry))
|
|
135
140
|
}
|
|
136
141
|
})
|
package/src/scan/scanResults.js
CHANGED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
Object.defineProperty(exports, '__esModule', { value: true })
|
|
3
|
-
exports.pollScanDetail = void 0
|
|
4
|
-
const requestUtils_1 = require('../utils/requestUtils')
|
|
5
|
-
const pollScanDetail = async (
|
|
6
|
-
config,
|
|
7
|
-
params,
|
|
8
|
-
scanId,
|
|
9
|
-
httpClient,
|
|
10
|
-
pollCount,
|
|
11
|
-
showProgress = false
|
|
12
|
-
) => {
|
|
13
|
-
await (0, requestUtils_1.sleep)(5000)
|
|
14
|
-
return httpClient.getFunctionScan(config, params, scanId).then(res => {
|
|
15
|
-
const { resultsCount = 0 } = res?.body?.data?.scan || {}
|
|
16
|
-
if (showProgress) {
|
|
17
|
-
process.stdout.write(
|
|
18
|
-
`\rScanning (${resultsCount} results found so far)${'.'.repeat(
|
|
19
|
-
pollCount
|
|
20
|
-
)}`
|
|
21
|
-
)
|
|
22
|
-
}
|
|
23
|
-
if (res.statusCode === 200) {
|
|
24
|
-
return res
|
|
25
|
-
} else {
|
|
26
|
-
throw Error(`Failed to get scan detail: ${res.statusCode} ${res.body}`)
|
|
27
|
-
}
|
|
28
|
-
})
|
|
29
|
-
}
|
|
30
|
-
exports.pollScanDetail = pollScanDetail
|
package/dist/scan/fileFinder.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
const fg = require('fast-glob')
|
|
3
|
-
const i18n = require('i18n')
|
|
4
|
-
const findFile = async () => {
|
|
5
|
-
console.log(i18n.__('searchingScanFileDirectory', process.cwd()))
|
|
6
|
-
const entries = fg(['**/*.jar', '**/*.war', '**/*.zip', '**/*.dll'], {
|
|
7
|
-
dot: false,
|
|
8
|
-
deep: 3,
|
|
9
|
-
onlyFiles: true
|
|
10
|
-
})
|
|
11
|
-
return entries
|
|
12
|
-
}
|
|
13
|
-
module.exports = {
|
|
14
|
-
findFile
|
|
15
|
-
}
|
package/dist/utils/fileUtils.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
const fg = require('fast-glob');
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const i18n = require('i18n');
|
|
5
|
-
const findFile = async () => {
|
|
6
|
-
console.log(i18n.__('searchingScanFileDirectory', process.cwd()));
|
|
7
|
-
return fg(['**/*.jar', '**/*.war', '**/*.zip', '**/*.dll'], {
|
|
8
|
-
dot: false,
|
|
9
|
-
deep: 3,
|
|
10
|
-
onlyFiles: true
|
|
11
|
-
});
|
|
12
|
-
};
|
|
13
|
-
const checkFilePermissions = file => {
|
|
14
|
-
let readableFile = false;
|
|
15
|
-
try {
|
|
16
|
-
fs.accessSync(file, fs.constants.R_OK);
|
|
17
|
-
return (readableFile = true);
|
|
18
|
-
}
|
|
19
|
-
catch (err) {
|
|
20
|
-
console.log('Invalid permissions found on ', file);
|
|
21
|
-
process.exit(0);
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
const fileExists = path => {
|
|
25
|
-
return fs.existsSync(path);
|
|
26
|
-
};
|
|
27
|
-
module.exports = {
|
|
28
|
-
findFile,
|
|
29
|
-
fileExists,
|
|
30
|
-
checkFilePermissions
|
|
31
|
-
};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
const cliOptions = require('../parsedCLIOptions');
|
|
3
|
-
const getGenericCommandLineParams = () => {
|
|
4
|
-
return cliOptions.getCommandLineArgsGeneric();
|
|
5
|
-
};
|
|
6
|
-
const getSpecificCommandLineParams = (parameterList, optionDefinition) => {
|
|
7
|
-
return cliOptions.getCommandLineArgsCustom(parameterList, optionDefinition);
|
|
8
|
-
};
|
|
9
|
-
module.exports = {
|
|
10
|
-
getSpecificCommandLineParams,
|
|
11
|
-
getGenericCommandLineParams
|
|
12
|
-
};
|