mobbdev 0.0.23 → 0.0.28

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 CHANGED
@@ -1,26 +1,34 @@
1
1
  # Bugsy
2
2
 
3
- Bugsy is a command-line interface (CLI) tool that provides automatic security vulnerability remediation for your code. It is the community edition version of [Mobb](https://www.mobb.dev), the first vendor-agnostic automatic security vulnerability remediation tool. Bugsy is designed to help developers easily identify and fix security vulnerabilities in their code.
3
+ Bugsy is a command-line interface (CLI) tool that provides automatic security vulnerability remediation for your code. It is the community edition version of [Mobb](https://www.mobb.dev), the first vendor-agnostic automated security vulnerability remediation tool. Bugsy is designed to help developers quickly identify and fix security vulnerabilities in their code.
4
4
 
5
- <img width="750" alt="Screenshot 2023-03-27 at 5 23 19 PM" src="https://user-images.githubusercontent.com/96389636/228070025-2a1c3aae-6b40-427f-a1e9-2b10ef97b5ea.png">
5
+ <img width="1888" alt="Bugsy" src="./img/bugsy.png">
6
6
 
7
7
  ## What is [Mobb](https://www.mobb.dev)?
8
8
 
9
- [Mobb](https://www.mobb.dev) is the first vendor-agnostic automatic security vulnerability remediation tool. It ingests SAST results from Checkmarx, GitHub Advanced Security, and Snyk and produces code fixes for developers to review and commit to their code.
9
+ [Mobb](https://www.mobb.dev) is the first vendor-agnostic automatic security vulnerability remediation tool. It ingests SAST results from Checkmarx, CodeQL (GitHub Advanced Security), OpenText Fortify, and Snyk and produces code fixes for developers to review and commit to their code.
10
10
 
11
11
  ## What does Bugsy do?
12
12
 
13
+ Bugsy has two modes - Analyze (the user has a pre-generated SAST report from one of the supported SAST tools) and Scan (no SAST report needed).
14
+
15
+ Scan
16
+
13
17
  - Uses Snyk CLI tool to run a SAST analysis on a given open-source GitHub repo
14
18
  - Analyzes the vulnerability report to identify issues that can be remediated automatically
15
19
  - Produces the code fixes and redirects the user to the fix report page on the Mobb platform
16
20
 
21
+ Analyze
22
+
23
+ - Analyzes the vulnerability report to identify issues that can be remediated automatically
24
+ - Produces the code fixes and redirects the user to the fix report page on the Mobb platform
25
+
17
26
  ## Disclaimer
18
27
 
19
- This is a community edition version that only analyzes public GitHub repositories.
28
+ This is a community edition version that only analyzes public GitHub repositories. Analyzing private repositories is allowed for a limited amount of time.
20
29
  Snyk CLI is used to produce a SAST vulnerability report.
21
30
 
22
- - Only Java projects are supported at the moment.
23
- - Only SQLi, CMDi, XSS, XXE, and Path Traversal are currently supported.
31
+ - Only Java and Node.js projects are supported at the moment.
24
32
 
25
33
  ## Usage
26
34
 
@@ -34,4 +42,4 @@ Bugsy will automatically generate a fix for each supported vulnerability identif
34
42
 
35
43
  ## Getting support
36
44
 
37
- If you need support using Bugsy or just want to share your thoughts and learn more, you are more than welcome to join our [discord server](https://discord.gg/Jmpb5QUa)
45
+ If you need support using Bugsy or just want to share your thoughts and learn more, you are more than welcome to join our [discord server](https://bit.ly/Mobb-discord)
package/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { analyze, scan } from './src/commands/index.mjs';
1
+ import { analyze, scan, CliError } from './src/commands/index.mjs';
2
2
  import { parseArgs } from './src/yargs.mjs';
3
3
  import { hideBin } from 'yargs/helpers';
4
4
 
@@ -20,6 +20,11 @@ async function run() {
20
20
  await run();
21
21
  process.exit(0);
22
22
  } catch (err) {
23
+ if (err instanceof CliError) {
24
+ console.error(err.message);
25
+ process.exit(1);
26
+ }
27
+ // unexpected error - print stack trace
23
28
  console.error(
24
29
  'Something went wrong, please try again or contact support if issue persists.'
25
30
  );
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "mobbdev",
3
- "version": "0.0.23",
3
+ "version": "0.0.28",
4
4
  "description": "Automated secure code remediation tool",
5
+ "repository": "https://github.com/mobb-dev/bugsy",
5
6
  "main": "index.mjs",
6
7
  "scripts": {
7
8
  "lint": "prettier --check . && eslint **/*.mjs",
@@ -15,6 +16,7 @@
15
16
  "author": "",
16
17
  "license": "MIT",
17
18
  "dependencies": {
19
+ "adm-zip": "0.5.10",
18
20
  "chalk": "5.3.0",
19
21
  "chalk-animation": "2.0.3",
20
22
  "colors": "1.4.0",
@@ -23,11 +25,13 @@
23
25
  "dotenv": "16.0.3",
24
26
  "extract-zip": "2.0.1",
25
27
  "inquirer": "9.2.7",
28
+ "globby": "13.2.2",
26
29
  "nanospinner": "1.1.0",
27
30
  "node-fetch": "3.3.1",
28
31
  "open": "8.4.2",
29
32
  "semver": "7.5.0",
30
33
  "snyk": "1.1118.0",
34
+ "simple-git": "3.19.1",
31
35
  "tmp": "0.2.1",
32
36
  "yargs": "17.7.2",
33
37
  "zod": "3.21.4"
@@ -11,6 +11,7 @@ import path from 'path';
11
11
  const GITHUB_REPO_URL_PATTERN = new RegExp(
12
12
  'https://github.com/[\\w-]+/[\\w-]+'
13
13
  );
14
+ export class CliError extends Error {}
14
15
 
15
16
  const UrlZ = z
16
17
  .string({
@@ -20,45 +21,53 @@ const UrlZ = z
20
21
  message: 'is not a valid github URL',
21
22
  });
22
23
 
23
- function printRepoUrlErrorMessage({ error, repoUrl, command }) {
24
+ function throwRepoUrlErrorMessage({ error, repoUrl, command }) {
24
25
  const errorMessage = error.issues[error.issues.length - 1].message;
25
- console.error(`\nError: ${chalk.bold(repoUrl)} is ${errorMessage}`);
26
- console.error(
27
- `Example: \n\tmobbdev ${command} -r ${chalk.bold(
28
- 'https://github.com/WebGoat/WebGoat'
29
- )}`
30
- );
26
+ const formattedErrorMessage = `\nError: ${chalk.bold(
27
+ repoUrl
28
+ )} is ${errorMessage}
29
+ Example: \n\tmobbdev ${command} -r ${chalk.bold(
30
+ 'https://github.com/WebGoat/WebGoat'
31
+ )}`;
32
+ throw new CliError(formattedErrorMessage);
31
33
  }
32
34
 
33
35
  export async function analyze(
34
- { repo, scanFile, branch, apiKey, ci },
36
+ { repo, scanFile, ref, apiKey, ci, commitHash, srcPath },
35
37
  { skipPrompts = false } = {}
36
38
  ) {
37
39
  const { success, error } = UrlZ.safeParse(repo);
38
- if (!success) {
39
- printRepoUrlErrorMessage({
40
+
41
+ if (!success && !srcPath) {
42
+ throwRepoUrlErrorMessage({
40
43
  error,
41
44
  repoUrl: repo,
42
45
  command: 'analyze',
43
46
  });
44
- process.exit(1);
45
- }
46
- if (ci && !apiKey) {
47
- console.error(
48
- '\nError: --ci flag requires --api-key to be provided as well'
49
- );
50
- process.exit(1);
51
47
  }
48
+
52
49
  if (!fs.existsSync(scanFile)) {
53
- console.error(`\nCan't access ${chalk.bold(scanFile)}`);
54
- process.exit(1);
50
+ throw new CliError(`\nCan't access ${chalk.bold(scanFile)}`);
55
51
  }
52
+
56
53
  if (path.extname(scanFile) !== '.json') {
57
- console.error(`\n${chalk.bold(scanFile)} is not a json file`);
58
- process.exit(1);
54
+ throw new CliError(`\n${chalk.bold(scanFile)} is not a json file`);
59
55
  }
56
+
60
57
  !ci && (await showWelcomeMessage(skipPrompts));
61
- await runAnalysis({ repo, scanFile, branch, apiKey, ci }, { skipPrompts });
58
+
59
+ await runAnalysis(
60
+ {
61
+ repo,
62
+ scanFile,
63
+ ref,
64
+ apiKey,
65
+ ci,
66
+ commitHash,
67
+ srcPath,
68
+ },
69
+ { skipPrompts }
70
+ );
62
71
  }
63
72
 
64
73
  export async function scan(
@@ -67,23 +76,20 @@ export async function scan(
67
76
  ) {
68
77
  const { success, error } = UrlZ.safeParse(repo);
69
78
  if (ci && !apiKey) {
70
- console.error(
79
+ throw new CliError(
71
80
  '\nError: --ci flag requires --api-key to be provided as well'
72
81
  );
73
- process.exit(1);
74
82
  }
75
83
 
76
84
  if (!success) {
77
- printRepoUrlErrorMessage({ error, repoUrl: repo, command: 'scan' });
78
- process.exit(1);
85
+ throwRepoUrlErrorMessage({ error, repoUrl: repo, command: 'scan' });
79
86
  }
80
87
  !ci && (await showWelcomeMessage(skipPrompts));
81
88
  scanner ??= scanner || (await choseScanner());
82
89
  if (scanner !== SCANNERS.Snyk) {
83
- console.error(
90
+ throw new CliError(
84
91
  'Vulnerability scanning via Bugsy is available only with Snyk at the moment. Additional scanners will follow soon.'
85
92
  );
86
- process.exit(1);
87
93
  }
88
94
  await runAnalysis({ repo, scanner, branch, apiKey }, { skipPrompts });
89
95
  }
package/src/constants.mjs CHANGED
@@ -20,7 +20,6 @@ const envVariablesSchema = z
20
20
  WEB_LOGIN_URL: z.string(),
21
21
  WEB_APP_URL: z.string(),
22
22
  API_URL: z.string(),
23
- GITHUB_CLIENT_ID: z.string(),
24
23
  })
25
24
  .required();
26
25
 
@@ -59,4 +58,3 @@ export const mobbAscii = `
59
58
  export const WEB_LOGIN_URL = envVariables.WEB_LOGIN_URL;
60
59
  export const WEB_APP_URL = envVariables.WEB_APP_URL;
61
60
  export const API_URL = envVariables.API_URL;
62
- export const GITHUB_CLIENT_ID = envVariables.GITHUB_CLIENT_ID;
@@ -0,0 +1,50 @@
1
+ import { simpleGit } from 'simple-git';
2
+ import Debug from 'debug';
3
+
4
+ const debug = Debug('mobbdev:git');
5
+
6
+ export async function getGitInfo(srcDirPath) {
7
+ debug('getting git info for %s', srcDirPath);
8
+
9
+ const git = simpleGit({
10
+ baseDir: srcDirPath,
11
+ // binary: 'git123',
12
+ maxConcurrentProcesses: 1,
13
+ trimmed: true,
14
+ });
15
+
16
+ let repoUrl = '';
17
+ let hash = '';
18
+ let reference = '';
19
+
20
+ try {
21
+ repoUrl = (await git.getConfig('remote.origin.url')).value || '';
22
+ hash = (await git.revparse(['HEAD'])) || '';
23
+ reference = (await git.revparse(['--abbrev-ref', 'HEAD'])) || '';
24
+ } catch (e) {
25
+ debug('failed to run git %o', e);
26
+ if (e.message && e.message.includes(' spawn ')) {
27
+ debug('git cli not installed');
28
+ } else if (e.message && e.message.includes(' not a git repository ')) {
29
+ debug('folder is not a git repo');
30
+ } else {
31
+ throw e;
32
+ }
33
+ }
34
+
35
+ // Normalize git URL. We may need more generic code here, but it's not very
36
+ // important at the moment.
37
+ if (repoUrl.endsWith('.git')) {
38
+ repoUrl = repoUrl.slice(0, -'.git'.length);
39
+ }
40
+
41
+ if (repoUrl.startsWith('git@github.com:')) {
42
+ repoUrl = repoUrl.replace('git@github.com:', 'https://github.com/');
43
+ }
44
+
45
+ return {
46
+ repoUrl,
47
+ hash,
48
+ reference,
49
+ };
50
+ }
@@ -7,14 +7,9 @@ import fetch from 'node-fetch';
7
7
  import extract from 'extract-zip';
8
8
  import Debug from 'debug';
9
9
  import { Spinner } from '../../utils.mjs';
10
- import { GITHUB_CLIENT_ID, WEB_APP_URL } from '../../constants.mjs';
11
10
  const pipeline = promisify(stream.pipeline);
12
11
  const debug = Debug('mobbdev:github');
13
12
 
14
- const githubTokenUrl = `${WEB_APP_URL}/gh-callback`;
15
-
16
- export const GITHUB_OAUTH_URL = `https://github.com/login/oauth/authorize?client_id=${GITHUB_CLIENT_ID}&scope=repo&redirect_uri=${githubTokenUrl}`;
17
-
18
13
  async function getRepo(slug, { token } = {}) {
19
14
  try {
20
15
  return fetch(`https://api.github.com/repos/${slug}`, {
@@ -49,16 +49,23 @@ mutation uploadS3BucketInfo($fileName: String!) {
49
49
  uploadFieldsJSON
50
50
  uploadKey
51
51
  }
52
+ repoUploadInfo {
53
+ url
54
+ fixReportId
55
+ uploadFieldsJSON
56
+ uploadKey
57
+ }
52
58
  }
53
59
  }
54
60
  `;
55
61
 
56
62
  const SUBMIT_VULNERABILITY_REPORT = `
57
- mutation SubmitVulnerabilityReport($vulnerabilityReportFileName: String!, $fixReportId: String!, $repoUrl: String!, $reference: String!, $projectId: String!) {
63
+ mutation SubmitVulnerabilityReport($vulnerabilityReportFileName: String!, $fixReportId: String!, $repoUrl: String!, $reference: String!, $projectId: String!, $sha: String) {
58
64
  submitVulnerabilityReport(
59
65
  fixReportId: $fixReportId
60
66
  repoUrl: $repoUrl
61
67
  reference: $reference
68
+ sha: $sha
62
69
  vulnerabilityReportFileName: $vulnerabilityReportFileName
63
70
  projectId: $projectId
64
71
  ) {
@@ -162,6 +169,13 @@ export class GQLClient {
162
169
  uploadFields: JSON.parse(
163
170
  data.uploadS3BucketInfo.uploadInfo.uploadFieldsJSON
164
171
  ),
172
+
173
+ repoFixReportId: data.uploadS3BucketInfo.repoUploadInfo.fixReportId,
174
+ repoUploadKey: data.uploadS3BucketInfo.repoUploadInfo.uploadKey,
175
+ repoUrl: data.uploadS3BucketInfo.repoUploadInfo.url,
176
+ repoUploadFields: JSON.parse(
177
+ data.uploadS3BucketInfo.repoUploadInfo.uploadFieldsJSON
178
+ ),
165
179
  };
166
180
  }
167
181
 
@@ -169,7 +183,8 @@ export class GQLClient {
169
183
  fixReportId,
170
184
  repoUrl,
171
185
  reference,
172
- projectId
186
+ projectId,
187
+ sha
173
188
  ) {
174
189
  await this._apiCall(SUBMIT_VULNERABILITY_REPORT, {
175
190
  fixReportId,
@@ -177,6 +192,7 @@ export class GQLClient {
177
192
  reference,
178
193
  vulnerabilityReportFileName: 'report.json',
179
194
  projectId,
195
+ sha: sha || '',
180
196
  });
181
197
  }
182
198
  }
@@ -8,22 +8,21 @@ import open from 'open';
8
8
  import semver from 'semver';
9
9
  import { callbackServer } from './callback-server.mjs';
10
10
  import tmp from 'tmp';
11
+ import { CliError } from '../../commands/index.mjs';
11
12
 
12
13
  import { WEB_APP_URL } from '../../constants.mjs';
13
- import {
14
- canReachRepo,
15
- downloadRepo,
16
- getDefaultBranch,
17
- GITHUB_OAUTH_URL,
18
- } from './github.mjs';
14
+ import { canReachRepo, downloadRepo, getDefaultBranch } from './github.mjs';
19
15
  import { GQLClient } from './gql.mjs';
20
16
  import { githubIntegrationPrompt, mobbAnalysisPrompt } from './prompts.mjs';
21
17
  import { getSnykReport } from './snyk.mjs';
22
18
  import { uploadFile } from './upload-file.mjs';
23
19
  import { keypress, Spinner } from '../../utils.mjs';
20
+ import { pack } from './pack.mjs';
21
+ import { getGitInfo } from './git.mjs';
24
22
 
25
23
  const webLoginUrl = `${WEB_APP_URL}/cli-login`;
26
24
  const githubSubmitUrl = `${WEB_APP_URL}/gh-callback`;
25
+ const githubAuthUrl = `${WEB_APP_URL}/github-auth`;
27
26
 
28
27
  const MOBB_LOGIN_REQUIRED_MSG = `🔓 Login to Mobb is Required, you will be redirected to our login page, once the authorization is complete return to this prompt, ${chalk.bgBlue(
29
28
  'press any key to continue'
@@ -40,17 +39,16 @@ const packageJson = JSON.parse(
40
39
  );
41
40
 
42
41
  if (!semver.satisfies(process.version, packageJson.engines.node)) {
43
- console.error(
42
+ throw new CliError(
44
43
  `${packageJson.name} requires node version ${packageJson.engines.node}, but running ${process.version}.`
45
44
  );
46
- process.exit(1);
47
45
  }
48
46
 
49
47
  const config = new Configstore(packageJson.name, { token: '' });
50
48
  debug('config %o', config);
51
49
 
52
50
  export async function runAnalysis(
53
- { repo, scanner, scanFile, branch, apiKey, ci },
51
+ { repo, scanner, scanFile, apiKey, ci, commitHash, srcPath, ref },
54
52
  options
55
53
  ) {
56
54
  try {
@@ -60,9 +58,11 @@ export async function runAnalysis(
60
58
  repo,
61
59
  scanner,
62
60
  scanFile,
63
- branch,
61
+ ref,
64
62
  apiKey,
65
63
  ci,
64
+ srcPath,
65
+ commitHash,
66
66
  },
67
67
  options
68
68
  );
@@ -72,7 +72,7 @@ export async function runAnalysis(
72
72
  }
73
73
 
74
74
  export async function _scan(
75
- { dirname, repo, scanFile, branch, apiKey, ci },
75
+ { dirname, repo, scanFile, branch, apiKey, ci, srcPath, commitHash, ref },
76
76
  { skipPrompts = false } = {}
77
77
  ) {
78
78
  debug('start %s %s', dirname, repo);
@@ -83,23 +83,34 @@ export async function _scan(
83
83
  apiKey ?? debug('token %s', apiKey);
84
84
  let gqlClient = new GQLClient(apiKey ? { apiKey } : { token });
85
85
  await handleMobbLogin();
86
+ const { projectId, organizationId } = await gqlClient.getOrgAndProjectId();
87
+ const uploadData = await gqlClient.uploadS3BucketInfo();
88
+ let reportPath = scanFile;
89
+
90
+ if (srcPath) {
91
+ return await uploadExistingRepo();
92
+ }
93
+
86
94
  const userInfo = await gqlClient.getUserInfo();
87
95
  let { githubToken } = userInfo;
88
96
  const isRepoAvailable = await canReachRepo(repo, {
89
97
  token: userInfo.githubToken,
90
98
  });
91
99
  if (!isRepoAvailable) {
100
+ if (ci) {
101
+ throw new Error(
102
+ `Cannot access repo ${repo} with the provided token, please visit ${githubAuthUrl} to refresh your Github token`
103
+ );
104
+ }
92
105
  const { token } = await handleGithubIntegration();
93
106
  githubToken = token;
94
107
  }
95
108
 
96
109
  const reference =
97
- branch ?? (await getDefaultBranch(repo, { token: githubToken }));
98
- const { projectId, organizationId } = await gqlClient.getOrgAndProjectId();
110
+ ref ?? (await getDefaultBranch(repo, { token: githubToken }));
99
111
  debug('org id %s', organizationId);
100
112
  debug('project id %s', projectId);
101
113
  debug('default branch %s', reference);
102
- const uploadData = await gqlClient.uploadS3BucketInfo();
103
114
 
104
115
  const repositoryRoot = await downloadRepo(
105
116
  {
@@ -111,7 +122,9 @@ export async function _scan(
111
122
  { token: githubToken }
112
123
  );
113
124
 
114
- const reportPath = scanFile || (await getReportFromSnyk());
125
+ if (!reportPath) {
126
+ reportPath = await getReportFromSnyk();
127
+ }
115
128
 
116
129
  const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
117
130
  await uploadFile(
@@ -188,7 +201,7 @@ export async function _scan(
188
201
  createSpinner().start().error({
189
202
  text: '🔓 Logged in to Mobb failed - check your api-key',
190
203
  });
191
- process.exit(1);
204
+ throw new CliError();
192
205
  }
193
206
  const mobbLoginSpinner = createSpinner().start();
194
207
  if (!skipPrompts) {
@@ -212,7 +225,7 @@ export async function _scan(
212
225
  mobbLoginSpinner.error({
213
226
  text: 'Something went wrong, API token is invalid.',
214
227
  });
215
- process.exit(1);
228
+ throw new CliError();
216
229
  }
217
230
  debug('set token %s', token);
218
231
  config.set('token', token);
@@ -231,10 +244,49 @@ export async function _scan(
231
244
  throw Error('Could not reach github repo');
232
245
  }
233
246
  const result = await callbackServer(
234
- GITHUB_OAUTH_URL,
247
+ githubAuthUrl,
235
248
  `${githubSubmitUrl}?done=true`
236
249
  );
237
250
  githubSpinner.success({ text: '🔗 Github integration successful!' });
238
251
  return result;
239
252
  }
253
+ async function uploadExistingRepo() {
254
+ const gitInfo = await getGitInfo(srcPath);
255
+ const zipBuffer = await pack(srcPath);
256
+
257
+ await uploadFile(
258
+ reportPath,
259
+ uploadData.url,
260
+ uploadData.uploadKey,
261
+ uploadData.uploadFields
262
+ );
263
+ await uploadFile(
264
+ zipBuffer,
265
+ uploadData.repoUrl,
266
+ uploadData.repoUploadKey,
267
+ uploadData.repoUploadFields
268
+ );
269
+
270
+ const mobbSpinner = createSpinner(
271
+ '🕵️‍♂️ Initiating Mobb analysis'
272
+ ).start();
273
+
274
+ try {
275
+ await gqlClient.submitVulnerabilityReport(
276
+ uploadData.fixReportId,
277
+ repo || gitInfo.repoUrl,
278
+ branch || gitInfo.reference,
279
+ projectId,
280
+ commitHash || gitInfo.hash
281
+ );
282
+ } catch (e) {
283
+ mobbSpinner.error({ text: '🕵️‍♂️ Mobb analysis failed' });
284
+ throw e;
285
+ }
286
+
287
+ mobbSpinner.success({
288
+ text: '🕵️‍♂️ Generating fixes...',
289
+ });
290
+ await askToOpenAnalysis();
291
+ }
240
292
  }
@@ -0,0 +1,31 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { globby } from 'globby';
4
+ import AdmZip from 'adm-zip';
5
+ import Debug from 'debug';
6
+
7
+ const debug = Debug('mobbdev:pack');
8
+
9
+ export async function pack(srcDirPath) {
10
+ debug('pack folder %s', srcDirPath);
11
+ const filepaths = await globby('**', {
12
+ gitignore: true,
13
+ onlyFiles: true,
14
+ cwd: srcDirPath,
15
+ followSymbolicLinks: false,
16
+ });
17
+ debug('files found %d', filepaths.length);
18
+
19
+ const zip = new AdmZip();
20
+
21
+ debug('compressing files');
22
+ for (const filepath of filepaths) {
23
+ zip.addFile(
24
+ filepath.toString(),
25
+ fs.readFileSync(path.join(srcDirPath, filepath.toString()))
26
+ );
27
+ }
28
+
29
+ debug('get zip file buffer');
30
+ return zip.toBuffer();
31
+ }
@@ -1,10 +1,11 @@
1
- import fetch, { FormData, fileFrom } from 'node-fetch';
1
+ import fetch, { FormData, fileFrom, File } from 'node-fetch';
2
2
  import Debug from 'debug';
3
3
 
4
4
  const debug = Debug('mobbdev:upload-file');
5
5
 
6
- export async function uploadFile(reportPath, url, uploadKey, uploadFields) {
7
- debug('upload report file start %s %s', reportPath, url);
6
+ // `file` can be string representing absolute path or buffer.
7
+ export async function uploadFile(file, url, uploadKey, uploadFields) {
8
+ debug('upload file start %s', url);
8
9
  debug('upload fields %o', uploadFields);
9
10
  debug('upload key %s', uploadKey);
10
11
 
@@ -15,7 +16,13 @@ export async function uploadFile(reportPath, url, uploadKey, uploadFields) {
15
16
  }
16
17
 
17
18
  form.append('key', uploadKey);
18
- form.append('file', await fileFrom(reportPath));
19
+ if (typeof file === 'string') {
20
+ debug('upload file from path %s', file);
21
+ form.append('file', await fileFrom(file));
22
+ } else {
23
+ debug('upload file from buffer');
24
+ form.append('file', new File([file], 'file'));
25
+ }
19
26
 
20
27
  const response = await fetch(url, {
21
28
  method: 'POST',
@@ -24,7 +31,7 @@ export async function uploadFile(reportPath, url, uploadKey, uploadFields) {
24
31
 
25
32
  if (!response.ok) {
26
33
  debug('error from S3 %s %s', response.body, response.status);
27
- throw new Error(`Failed to upload the report: ${response.status}`);
34
+ throw new Error(`Failed to upload the file: ${response.status}`);
28
35
  }
29
- debug('upload report file done');
36
+ debug('upload file done');
30
37
  }
package/src/yargs.mjs CHANGED
@@ -3,9 +3,20 @@ import chalk from 'chalk';
3
3
 
4
4
  import { SCANNERS } from './constants.mjs';
5
5
 
6
- const branchOption = {
7
- alias: 'branch',
8
- describe: chalk.bold('Branch of the repository'),
6
+ const refOption = {
7
+ describe: chalk.bold('reference of the repository (branch, tag, commit)'),
8
+ type: 'string',
9
+ };
10
+
11
+ const srcPathOption = {
12
+ alias: 'src-path',
13
+ describe: chalk.bold('Path to the repository folder with the source code'),
14
+ type: 'string',
15
+ };
16
+
17
+ const commitHash = {
18
+ alias: 'commit-hash',
19
+ describe: chalk.bold('Hash of the commit'),
9
20
  type: 'string',
10
21
  };
11
22
 
@@ -59,7 +70,7 @@ export const parseArgs = (args) => {
59
70
  builder: (yargs) => {
60
71
  return yargs.options({
61
72
  r: repoOption,
62
- b: branchOption,
73
+ ref: refOption,
63
74
  s: {
64
75
  alias: 'scanner',
65
76
  choices: Object.values(SCANNERS),
@@ -76,20 +87,41 @@ export const parseArgs = (args) => {
76
87
  'Provide a vulnerability report and relevant code repository, get automated fixes right away.'
77
88
  ),
78
89
  builder: (yargs) => {
79
- return yargs.options({
80
- f: {
81
- alias: 'scan-file',
82
- demandOption: true,
83
- describe: chalk.bold(
84
- 'Select the vulnerability report to analyze'
85
- ),
86
- },
87
- r: repoOption,
88
- b: branchOption,
89
- y: yesOption,
90
- ['api-key']: apiKeyOption,
91
- ci: ciOption,
92
- });
90
+ return yargs
91
+ .options({
92
+ f: {
93
+ alias: 'scan-file',
94
+ demandOption: true,
95
+ describe: chalk.bold(
96
+ 'Select the vulnerability report to analyze'
97
+ ),
98
+ },
99
+ r: {
100
+ ...repoOption,
101
+ demandOption: false,
102
+ },
103
+ p: srcPathOption,
104
+ ref: refOption,
105
+ ch: commitHash,
106
+ y: yesOption,
107
+ ['api-key']: apiKeyOption,
108
+ ci: ciOption,
109
+ })
110
+ .check((argv) => {
111
+ if (!argv.srcPath && !argv.repo) {
112
+ throw new Error(
113
+ 'You must supply either --src-path or --repo'
114
+ );
115
+ }
116
+
117
+ if (argv.ci && !argv.apiKey) {
118
+ throw new Error(
119
+ '--ci flag requires --api-key to be provided as well'
120
+ );
121
+ }
122
+
123
+ return true;
124
+ });
93
125
  },
94
126
  })
95
127
  .example('$0 scan -r https://github.com/WebGoat/WebGoat')