mobbdev 0.0.65 → 0.0.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.mjs +235 -137
  2. package/package.json +2 -1
package/dist/index.mjs CHANGED
@@ -163,7 +163,7 @@ import fetch3 from "node-fetch";
163
163
  import open2 from "open";
164
164
  import semver from "semver";
165
165
  import tmp2 from "tmp";
166
- import { z as z8 } from "zod";
166
+ import { z as z7 } from "zod";
167
167
 
168
168
  // src/features/analysis/git.ts
169
169
  import Debug2 from "debug";
@@ -385,16 +385,7 @@ var GET_ANALYSIS = gql2`
385
385
  commitSha
386
386
  pullRequest
387
387
  }
388
- fixes {
389
- id
390
- issueType
391
- vulnerabilityReportIssues {
392
- issueLanguage
393
- state
394
- issueType
395
- vendorIssueId
396
- }
397
- }
388
+ vulnerabilityReportId
398
389
  vulnerabilityReport {
399
390
  projectId
400
391
  project {
@@ -412,12 +403,38 @@ var GET_ANALYSIS = gql2`
412
403
  var GET_FIX = gql2`
413
404
  query getFix($fixId: uuid!) {
414
405
  fix_by_pk(id: $fixId) {
406
+ issueType
407
+ id
415
408
  patchAndQuestions {
416
409
  patch
417
410
  }
418
411
  }
419
412
  }
420
413
  `;
414
+ var GET_VUL_BY_NODES_METADATA = gql2`
415
+ query getVulByNodesMetadata(
416
+ $filters: [vulnerability_report_issue_code_node_bool_exp!]
417
+ $vulnerabilityReportId: uuid!
418
+ ) {
419
+ vulnerabilityReportIssueCodeNodes: vulnerability_report_issue_code_node(
420
+ order_by: { index: desc }
421
+ where: {
422
+ _or: $filters
423
+ vulnerabilityReportIssue: {
424
+ vulnerabilityReportId: { _eq: $vulnerabilityReportId }
425
+ }
426
+ }
427
+ ) {
428
+ vulnerabilityReportIssueId
429
+ path
430
+ startLine
431
+ vulnerabilityReportIssue {
432
+ issueType
433
+ fixId
434
+ }
435
+ }
436
+ }
437
+ `;
421
438
 
422
439
  // src/features/analysis/graphql/subscirbe.ts
423
440
  import { createClient } from "graphql-ws";
@@ -610,20 +627,7 @@ var GetAnalysisQueryZ = z2.object({
610
627
  commitSha: z2.string(),
611
628
  pullRequest: z2.number()
612
629
  }),
613
- fixes: z2.array(
614
- z2.object({
615
- id: z2.string(),
616
- issueType: z2.string(),
617
- vulnerabilityReportIssues: z2.array(
618
- z2.object({
619
- issueLanguage: z2.string(),
620
- state: z2.string(),
621
- issueType: z2.string(),
622
- vendorIssueId: z2.string()
623
- })
624
- )
625
- })
626
- ),
630
+ vulnerabilityReportId: z2.string(),
627
631
  vulnerabilityReport: z2.object({
628
632
  projectId: z2.string(),
629
633
  project: z2.object({
@@ -639,11 +643,24 @@ var GetAnalysisQueryZ = z2.object({
639
643
  });
640
644
  var GetFixQueryZ = z2.object({
641
645
  fix_by_pk: z2.object({
646
+ issueType: z2.string(),
647
+ id: z2.string(),
642
648
  patchAndQuestions: z2.object({
643
649
  patch: z2.string()
644
650
  })
645
651
  })
646
652
  });
653
+ var VulnerabilityReportIssueCodeNodeZ = z2.object({
654
+ vulnerabilityReportIssueId: z2.string(),
655
+ path: z2.string(),
656
+ startLine: z2.number(),
657
+ vulnerabilityReportIssue: z2.object({
658
+ fixId: z2.string()
659
+ })
660
+ });
661
+ var GetVulByNodesMetadataZ = z2.object({
662
+ vulnerabilityReportIssueCodeNodes: z2.array(VulnerabilityReportIssueCodeNodeZ)
663
+ });
647
664
 
648
665
  // src/features/analysis/graphql/gql.ts
649
666
  var debug3 = Debug3("mobbdev:gql");
@@ -738,6 +755,42 @@ var GQLClient = class {
738
755
  });
739
756
  return UploadS3BucketInfoZ.parse(uploadS3BucketInfoResult);
740
757
  }
758
+ async getVulByNodesMetadata({
759
+ hunks,
760
+ vulnerabilityReportId
761
+ }) {
762
+ const filters = hunks.map((hunk) => {
763
+ const filter = {
764
+ path: { _eq: hunk.path },
765
+ _or: hunk.ranges.map(({ endLine, startLine }) => ({
766
+ startLine: { _gte: startLine, _lte: endLine },
767
+ endLine: { _gte: startLine, _lte: endLine }
768
+ }))
769
+ };
770
+ return filter;
771
+ });
772
+ const getVulByNodesMetadataRes = await this._client.request(GET_VUL_BY_NODES_METADATA, {
773
+ filters: { _or: filters },
774
+ vulnerabilityReportId
775
+ });
776
+ const parsedGetVulByNodesMetadataRes = GetVulByNodesMetadataZ.parse(
777
+ getVulByNodesMetadataRes
778
+ );
779
+ const uniqueVulByNodesMetadata = parsedGetVulByNodesMetadataRes.vulnerabilityReportIssueCodeNodes.reduce((acc, vulnerabilityReportIssueCodeNode) => {
780
+ if (acc[vulnerabilityReportIssueCodeNode.vulnerabilityReportIssueId]) {
781
+ return acc;
782
+ }
783
+ return {
784
+ ...acc,
785
+ [vulnerabilityReportIssueCodeNode.vulnerabilityReportIssueId]: vulnerabilityReportIssueCodeNode
786
+ };
787
+ }, {});
788
+ return {
789
+ vulnerabilityReportIssueCodeNodes: Object.values(
790
+ uniqueVulByNodesMetadata
791
+ )
792
+ };
793
+ }
741
794
  async digestVulnerabilityReport({
742
795
  fixReportId,
743
796
  projectId
@@ -831,16 +884,20 @@ var GQLClient = class {
831
884
  return GetAnalysisQueryZ.parse(res);
832
885
  }
833
886
  async getFix(fixId) {
834
- const res = await this._client.request(GET_FIX, {
835
- fixId
836
- });
887
+ const res = await this._client.request(
888
+ GET_FIX,
889
+ {
890
+ fixId
891
+ }
892
+ );
837
893
  return GetFixQueryZ.parse(res);
838
894
  }
839
895
  };
840
896
 
841
897
  // src/features/analysis/handle_finished_analysis.ts
842
898
  import Debug4 from "debug";
843
- import { z as z7 } from "zod";
899
+ import parseDiff from "parse-diff";
900
+ import { z as z6 } from "zod";
844
901
 
845
902
  // src/features/analysis/scm/gitlab.ts
846
903
  import querystring from "node:querystring";
@@ -1234,6 +1291,7 @@ var POST_COMMENT_PATH = "POST /repos/{owner}/{repo}/pulls/{pull_number}/comments
1234
1291
  var DELETE_COMMENT_PATH = "DELETE /repos/{owner}/{repo}/pulls/comments/{comment_id}";
1235
1292
  var UPDATE_COMMENT_PATH = "PATCH /repos/{owner}/{repo}/pulls/comments/{comment_id}";
1236
1293
  var GET_PR_COMMENTS_PATH = "GET /repos/{owner}/{repo}/pulls/comments";
1294
+ var GET_PR = "GET /repos/{owner}/{repo}/pulls/{pull_number}";
1237
1295
 
1238
1296
  // src/features/analysis/scm/github/github-v2.ts
1239
1297
  function postPrComment(client, params) {
@@ -1248,6 +1306,9 @@ function getPrComments(client, params) {
1248
1306
  function deleteComment(client, params) {
1249
1307
  return client.request(DELETE_COMMENT_PATH, params);
1250
1308
  }
1309
+ function getPr(client, params) {
1310
+ return client.request(GET_PR, params);
1311
+ }
1251
1312
 
1252
1313
  // src/features/analysis/scm/scmSubmit.ts
1253
1314
  import fs from "node:fs/promises";
@@ -1286,7 +1347,9 @@ var submitToScmMessageType = {
1286
1347
  var CommitToSameBranchParamsZ = BaseSubmitToScmMessageZ.merge(
1287
1348
  z4.object({
1288
1349
  type: z4.literal(submitToScmMessageType.commitToSameBranch),
1289
- branch: z4.string()
1350
+ branch: z4.string(),
1351
+ commitMessage: z4.string(),
1352
+ commitDescription: z4.string().nullish()
1290
1353
  })
1291
1354
  );
1292
1355
  var SubmitFixesToDifferentBranchParamsZ = z4.object({
@@ -1671,6 +1734,19 @@ var GithubSCMLib = class extends SCMLib {
1671
1734
  repo
1672
1735
  });
1673
1736
  }
1737
+ async getPrDiff(params) {
1738
+ if (!this.accessToken || !this.url) {
1739
+ throw new Error("cannot get Pr Comments without access token or url");
1740
+ }
1741
+ const { owner, repo } = parseOwnerAndRepo(this.url);
1742
+ const prRes = await getPr(this.oktokit, {
1743
+ ...params,
1744
+ owner,
1745
+ repo
1746
+ });
1747
+ const diffUrl = prRes.data.diff_url;
1748
+ return this.oktokit.request("GET " + diffUrl);
1749
+ }
1674
1750
  async getRepoList() {
1675
1751
  if (!this.accessToken) {
1676
1752
  console.error("no access token");
@@ -2076,22 +2152,30 @@ var GitlabAuthResultZ = z5.object({
2076
2152
  refresh_token: z5.string()
2077
2153
  });
2078
2154
 
2079
- // src/features/analysis/types.ts
2080
- import { z as z6 } from "zod";
2081
- var VulReportLocationZ = z6.object({
2082
- physicalLocation: z6.object({
2083
- artifactLocation: z6.object({
2084
- uri: z6.string(),
2085
- uriBaseId: z6.string(),
2086
- index: z6.number()
2087
- }),
2088
- region: z6.object({
2089
- startLine: z6.number(),
2090
- startColumn: z6.number(),
2091
- endColumn: z6.number()
2092
- })
2093
- })
2094
- });
2155
+ // src/features/analysis/utils/calculate_ranges.ts
2156
+ function calculateRanges(integers) {
2157
+ if (integers.length === 0) {
2158
+ return [];
2159
+ }
2160
+ integers.sort((a, b) => a - b);
2161
+ const ranges = integers.reduce(
2162
+ (result, current, index) => {
2163
+ if (index === 0) {
2164
+ return [...result, [current, current]];
2165
+ }
2166
+ const currentRange = result[result.length - 1];
2167
+ const [_start, end] = currentRange;
2168
+ if (current === end + 1) {
2169
+ currentRange[1] = current;
2170
+ } else {
2171
+ result.push([current, current]);
2172
+ }
2173
+ return result;
2174
+ },
2175
+ []
2176
+ );
2177
+ return ranges;
2178
+ }
2095
2179
 
2096
2180
  // src/features/analysis/utils/get_issue_type.ts
2097
2181
  var getIssueType = (issueType) => {
@@ -2151,6 +2235,20 @@ var getIssueType = (issueType) => {
2151
2235
  };
2152
2236
 
2153
2237
  // src/features/analysis/utils/index.ts
2238
+ function getFixUrlWithRedirect({
2239
+ fixId,
2240
+ projectId,
2241
+ organizationId,
2242
+ analysisId,
2243
+ redirectUrl
2244
+ }) {
2245
+ return `${getFixUrl({
2246
+ fixId,
2247
+ projectId,
2248
+ organizationId,
2249
+ analysisId
2250
+ })}?commit_redirect_url=${encodeURIComponent(redirectUrl)}`;
2251
+ }
2154
2252
  function getFixUrl({
2155
2253
  fixId,
2156
2254
  projectId,
@@ -2191,6 +2289,29 @@ function scannerToFriendlyString(scanner) {
2191
2289
  return "Snyk";
2192
2290
  }
2193
2291
  }
2292
+ async function getFixesFromDiff(params) {
2293
+ const { gqlClient, diff, vulnerabilityReportId } = params;
2294
+ const parsedDiff = parseDiff(diff);
2295
+ const fileHunks = parsedDiff.map((file) => {
2296
+ const fileNumbers = file.chunks.flatMap((chunk) => chunk.changes).filter((change) => change.type === "add").map((_change) => {
2297
+ const change = _change;
2298
+ return change.ln;
2299
+ });
2300
+ const lineAddedRanges = calculateRanges(fileNumbers);
2301
+ const fileFilter = {
2302
+ path: z6.string().parse(file.to),
2303
+ ranges: lineAddedRanges.map(([startLine, endLine]) => ({
2304
+ endLine,
2305
+ startLine
2306
+ }))
2307
+ };
2308
+ return fileFilter;
2309
+ });
2310
+ return gqlClient.getVulByNodesMetadata({
2311
+ hunks: fileHunks,
2312
+ vulnerabilityReportId
2313
+ });
2314
+ }
2194
2315
  async function handleFinishedAnalysis({
2195
2316
  analysisId,
2196
2317
  scm: _scm,
@@ -2202,7 +2323,20 @@ async function handleFinishedAnalysis({
2202
2323
  return;
2203
2324
  }
2204
2325
  const scm = _scm;
2205
- const res = await gqlClient.getAnalysis(analysisId);
2326
+ const getAnalysis = await gqlClient.getAnalysis(analysisId);
2327
+ const {
2328
+ vulnerabilityReport: {
2329
+ projectId,
2330
+ project: { organizationId }
2331
+ }
2332
+ } = getAnalysis.analysis;
2333
+ const { commitSha, pullRequest } = getAnalysis.analysis.repo;
2334
+ const getPrDiff = await scm.getPrDiff({ pull_number: pullRequest });
2335
+ const { vulnerabilityReportIssueCodeNodes } = await getFixesFromDiff({
2336
+ diff: getPrDiff.data,
2337
+ gqlClient,
2338
+ vulnerabilityReportId: getAnalysis.analysis.vulnerabilityReportId
2339
+ });
2206
2340
  const comments = await scm.getPrComments({}, githubActionOctokit);
2207
2341
  await Promise.all(
2208
2342
  comments.data.filter((comment) => {
@@ -2219,97 +2353,62 @@ async function handleFinishedAnalysis({
2219
2353
  }
2220
2354
  })
2221
2355
  );
2222
- const {
2223
- vulnerabilityReport: {
2224
- file: {
2225
- signedFile: { url: vulReportUrl }
2226
- }
2227
- },
2228
- repo: { commitSha, pullRequest }
2229
- } = res.analysis;
2230
- const {
2231
- projectId,
2232
- project: { organizationId }
2233
- } = res.analysis.vulnerabilityReport;
2234
- const vulReportRes = await fetch(vulReportUrl);
2235
- const vulReport = await vulReportRes.json();
2236
2356
  return Promise.all(
2237
- res.analysis.fixes.map((fix) => {
2238
- const [vulnerabilityReportIssue] = fix.vulnerabilityReportIssues;
2239
- const issueIndex = parseInt(
2240
- z7.string().parse(vulnerabilityReportIssue?.vendorIssueId)
2241
- );
2242
- const results = vulReport.runs[0]?.results || [];
2243
- const ruleId = results[issueIndex]?.ruleId;
2244
- const location = VulReportLocationZ.parse(
2245
- results[issueIndex]?.locations[0]
2246
- );
2247
- const { uri: filePath } = location.physicalLocation.artifactLocation;
2248
- const { startLine, startColumn, endColumn } = location.physicalLocation.region;
2249
- const fixLocation = {
2250
- filePath,
2251
- startLine,
2252
- startColumn,
2253
- endColumn,
2254
- ruleId
2255
- };
2256
- return {
2257
- fix,
2258
- fixLocation
2259
- };
2260
- }).map(async ({ fix, fixLocation }) => {
2261
- const { filePath, startLine } = fixLocation;
2262
- const getFixContent = await gqlClient.getFix(fix.id);
2263
- const {
2264
- fix_by_pk: {
2357
+ vulnerabilityReportIssueCodeNodes.map(
2358
+ async (vulnerabilityReportIssueCodeNodes2) => {
2359
+ const { path: path8, startLine, vulnerabilityReportIssue } = vulnerabilityReportIssueCodeNodes2;
2360
+ const { fixId } = vulnerabilityReportIssue;
2361
+ const { fix_by_pk } = await gqlClient.getFix(fixId);
2362
+ const {
2265
2363
  patchAndQuestions: { patch }
2266
- }
2267
- } = getFixContent;
2268
- const commentRes = await scm.postPrComment(
2269
- {
2270
- body: "empty",
2271
- pull_number: pullRequest,
2272
- commit_id: commitSha,
2273
- path: filePath,
2274
- line: startLine
2275
- },
2276
- githubActionOctokit
2277
- );
2278
- const commitUrl = getCommitUrl({
2279
- fixId: fix.id,
2280
- projectId,
2281
- analysisId,
2282
- organizationId,
2283
- redirectUrl: commentRes.data.html_url
2284
- });
2285
- const fixUrl = getFixUrl({
2286
- fixId: fix.id,
2287
- projectId,
2288
- analysisId,
2289
- organizationId
2290
- });
2291
- const scanerString = scannerToFriendlyString(scanner);
2292
- const issueType = getIssueType(fix.issueType);
2293
- const title = `# ${MOBB_ICON_IMG} ${issueType} fix by Mobb is ready`;
2294
- const subTitle = `### Apply the following code change to fix ${issueType} issue detected by ${scanerString}:`;
2295
- const diff = `\`\`\`diff
2364
+ } = fix_by_pk;
2365
+ const commentRes = await scm.postPrComment(
2366
+ {
2367
+ body: "empty",
2368
+ pull_number: pullRequest,
2369
+ commit_id: commitSha,
2370
+ path: path8,
2371
+ line: startLine
2372
+ },
2373
+ githubActionOctokit
2374
+ );
2375
+ const commitUrl = getCommitUrl({
2376
+ fixId: fix_by_pk.id,
2377
+ projectId,
2378
+ analysisId,
2379
+ organizationId,
2380
+ redirectUrl: commentRes.data.html_url
2381
+ });
2382
+ const fixUrl = getFixUrlWithRedirect({
2383
+ fixId: fix_by_pk.id,
2384
+ projectId,
2385
+ analysisId,
2386
+ organizationId,
2387
+ redirectUrl: commentRes.data.html_url
2388
+ });
2389
+ const scanerString = scannerToFriendlyString(scanner);
2390
+ const issueType = getIssueType(fix_by_pk.issueType);
2391
+ const title = `# ${MOBB_ICON_IMG} ${issueType} fix by Mobb is ready`;
2392
+ const subTitle = `### Apply the following code change to fix ${issueType} issue detected by ${scanerString}:`;
2393
+ const diff = `\`\`\`diff
2296
2394
  ${patch}
2297
2395
  \`\`\``;
2298
- const fixPageLink = `[Learn more and fine tune the fix](${fixUrl})`;
2299
- await scm.updatePrComment(
2300
- {
2301
- body: `${title}
2396
+ const fixPageLink = `[Learn more and fine tune the fix](${fixUrl})`;
2397
+ await scm.updatePrComment(
2398
+ {
2399
+ body: `${title}
2302
2400
  ${subTitle}
2303
2401
  ${diff}
2304
2402
  ${commitFixButton(
2305
- commitUrl
2306
- )}
2403
+ commitUrl
2404
+ )}
2307
2405
  ${fixPageLink}`,
2308
- comment_id: commentRes.data.id
2309
- },
2310
- githubActionOctokit
2311
- );
2312
- })
2406
+ comment_id: commentRes.data.id
2407
+ },
2408
+ githubActionOctokit
2409
+ );
2410
+ }
2411
+ )
2313
2412
  );
2314
2413
  }
2315
2414
 
@@ -2595,9 +2694,8 @@ async function validateCheckamxCredentials() {
2595
2694
  if (!tryAgain) {
2596
2695
  await throwCheckmarxConfigError();
2597
2696
  }
2598
- if (await tryCheckmarxConfiguarationAgain()) {
2599
- validateCheckamxCredentials();
2600
- }
2697
+ await validateCheckamxCredentials();
2698
+ return;
2601
2699
  }
2602
2700
  await createSpinner2("\u{1F513} Checkmarx configured successfully!").start().success();
2603
2701
  }
@@ -2893,7 +2991,7 @@ async function _scan(params, { skipPrompts = false } = {}) {
2893
2991
  gqlClient,
2894
2992
  scm,
2895
2993
  githubActionOctokit: new Octokit3({ auth: githubActionToken }),
2896
- scanner: z8.nativeEnum(SCANNERS).parse(scanner)
2994
+ scanner: z7.nativeEnum(SCANNERS).parse(scanner)
2897
2995
  })
2898
2996
  );
2899
2997
  }
@@ -2905,7 +3003,7 @@ async function _scan(params, { skipPrompts = false } = {}) {
2905
3003
  try {
2906
3004
  const sumbitRes = await gqlClient.submitVulnerabilityReport({
2907
3005
  fixReportId: reportUploadInfo.fixReportId,
2908
- repoUrl: z8.string().parse(repo),
3006
+ repoUrl: z7.string().parse(repo),
2909
3007
  reference,
2910
3008
  projectId,
2911
3009
  vulnerabilityReportFileName: "report.json",
@@ -3270,7 +3368,7 @@ var commitHashOption = {
3270
3368
  // src/args/validation.ts
3271
3369
  import chalk6 from "chalk";
3272
3370
  import path7 from "path";
3273
- import { z as z9 } from "zod";
3371
+ import { z as z8 } from "zod";
3274
3372
  function throwRepoUrlErrorMessage({
3275
3373
  error,
3276
3374
  repoUrl,
@@ -3287,7 +3385,7 @@ Example:
3287
3385
  )}`;
3288
3386
  throw new CliError(formattedErrorMessage);
3289
3387
  }
3290
- var UrlZ = z9.string({
3388
+ var UrlZ = z8.string({
3291
3389
  invalid_type_error: "is not a valid GitHub / GitLab URL"
3292
3390
  }).refine((data) => !!parseScmURL(data), {
3293
3391
  message: "is not a valid GitHub / GitLab URL"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobbdev",
3
- "version": "0.0.65",
3
+ "version": "0.0.66",
4
4
  "description": "Automated secure code remediation tool",
5
5
  "repository": "https://github.com/mobb-dev/bugsy",
6
6
  "main": "dist/index.js",
@@ -46,6 +46,7 @@
46
46
  "node-fetch": "3.3.1",
47
47
  "octokit": "2.0.14",
48
48
  "open": "8.4.2",
49
+ "parse-diff": "0.11.1",
49
50
  "semver": "7.5.0",
50
51
  "simple-git": "3.19.1",
51
52
  "snyk": "1.1118.0",