mobbdev 1.0.207 → 1.0.208

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/dist/index.mjs CHANGED
@@ -9953,13 +9953,8 @@ import fs10 from "fs";
9953
9953
  import chalk8 from "chalk";
9954
9954
 
9955
9955
  // src/commands/index.ts
9956
- import crypto from "crypto";
9957
- import os from "os";
9958
- import chalk6 from "chalk";
9959
9956
  import chalkAnimation from "chalk-animation";
9960
- import Configstore2 from "configstore";
9961
- import Debug19 from "debug";
9962
- import open3 from "open";
9957
+ import Configstore3 from "configstore";
9963
9958
 
9964
9959
  // src/features/analysis/index.ts
9965
9960
  import fs9 from "fs";
@@ -9967,16 +9962,32 @@ import fsPromises2 from "fs/promises";
9967
9962
  import path9 from "path";
9968
9963
  import { env as env2 } from "process";
9969
9964
  import { pipeline } from "stream/promises";
9970
- import chalk5 from "chalk";
9971
- import Configstore from "configstore";
9972
- import Debug18 from "debug";
9965
+ import chalk6 from "chalk";
9966
+ import Configstore2 from "configstore";
9967
+ import Debug19 from "debug";
9973
9968
  import extract from "extract-zip";
9974
9969
  import { createSpinner as createSpinner4 } from "nanospinner";
9975
9970
  import fetch4 from "node-fetch";
9976
- import open2 from "open";
9971
+ import open3 from "open";
9977
9972
  import tmp2 from "tmp";
9978
9973
  import { z as z29 } from "zod";
9979
9974
 
9975
+ // src/commands/handleMobbLogin.ts
9976
+ import crypto from "crypto";
9977
+ import os from "os";
9978
+ import chalk3 from "chalk";
9979
+ import Configstore from "configstore";
9980
+ import Debug7 from "debug";
9981
+ import open from "open";
9982
+
9983
+ // src/features/analysis/graphql/gql.ts
9984
+ import fetchOrig from "cross-fetch";
9985
+ import Debug6 from "debug";
9986
+ import { GraphQLClient } from "graphql-request";
9987
+ import { HttpProxyAgent } from "http-proxy-agent";
9988
+ import { HttpsProxyAgent as HttpsProxyAgent2 } from "https-proxy-agent";
9989
+ import { v4 as uuidv4 } from "uuid";
9990
+
9980
9991
  // src/mcp/core/Errors.ts
9981
9992
  var ApiConnectionError = class extends Error {
9982
9993
  constructor(message = "Failed to connect to the API") {
@@ -10049,1218 +10060,1313 @@ var _ReportDigestError = class _ReportDigestError extends Error {
10049
10060
  __publicField(_ReportDigestError, "defaultMessage", "\u{1F575}\uFE0F\u200D\u2642\uFE0F Digesting report failed. Please verify that the file provided is of a valid supported report format.");
10050
10061
  var ReportDigestError = _ReportDigestError;
10051
10062
 
10052
- // src/features/analysis/add_fix_comments_for_pr/add_fix_comments_for_pr.ts
10053
- import Debug8 from "debug";
10054
-
10055
- // src/features/analysis/add_fix_comments_for_pr/utils/utils.ts
10056
- import Debug7 from "debug";
10057
- import parseDiff from "parse-diff";
10058
- import { z as z26 } from "zod";
10059
-
10060
- // src/features/analysis/utils/by_key.ts
10061
- function keyBy(array, keyBy2) {
10062
- return array.reduce((acc, item) => {
10063
- return { ...acc, [item[keyBy2]]: item };
10064
- }, {});
10065
- }
10066
-
10067
- // src/features/analysis/utils/send_report.ts
10063
+ // src/features/analysis/graphql/subscribe.ts
10068
10064
  import Debug5 from "debug";
10069
- var debug6 = Debug5("mobbdev:index");
10070
- async function sendReport({
10071
- spinner,
10072
- submitVulnerabilityReportVariables,
10073
- gqlClient
10074
- }) {
10075
- try {
10076
- const submitRes = await gqlClient.submitVulnerabilityReport(
10077
- submitVulnerabilityReportVariables
10078
- );
10079
- if (submitRes.submitVulnerabilityReport.__typename !== "VulnerabilityReport") {
10080
- debug6("error submit vul report %s", submitRes);
10081
- throw new Error("\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed");
10065
+ import { createClient } from "graphql-ws";
10066
+ import { HttpsProxyAgent } from "https-proxy-agent";
10067
+ import WebSocket from "ws";
10068
+ var DEFAULT_API_URL = "https://api.mobb.ai/v1/graphql";
10069
+ var debug6 = Debug5("mobbdev:subscribe");
10070
+ var SUBSCRIPTION_TIMEOUT_MS = 30 * 60 * 1e3;
10071
+ function createWSClient(options) {
10072
+ const proxy = options.url.startsWith("wss://") && process.env["HTTPS_PROXY"] ? new HttpsProxyAgent(process.env["HTTPS_PROXY"]) : options.url.startsWith("ws://") && process.env["HTTP_PROXY"] ? new HttpsProxyAgent(process.env["HTTP_PROXY"]) : null;
10073
+ debug6(
10074
+ `Using proxy: ${proxy ? "yes" : "no"} with url: ${options.url} and with proxy: ${process.env["HTTP_PROXY"]} for the websocket connection`
10075
+ );
10076
+ const CustomWebSocket = class extends WebSocket {
10077
+ constructor(address, protocols) {
10078
+ super(
10079
+ address,
10080
+ protocols,
10081
+ proxy ? { agent: proxy } : void 0
10082
+ );
10082
10083
  }
10083
- spinner.update({ text: progressMassages.processingVulnerabilityReport });
10084
- await gqlClient.subscribeToAnalysis({
10085
- subscribeToAnalysisParams: {
10086
- analysisId: submitRes.submitVulnerabilityReport.fixReportId
10087
- },
10088
- callback: () => spinner.update({
10089
- text: "\u2699\uFE0F Vulnerability report processed successfully"
10090
- }),
10091
- callbackStates: [
10092
- "Digested" /* Digested */,
10093
- "Finished" /* Finished */
10094
- ],
10095
- timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
10084
+ };
10085
+ return createClient({
10086
+ //this is needed to prevent AWS from killing the connection
10087
+ //currently our load balancer has a 29s idle timeout
10088
+ keepAlive: 1e4,
10089
+ url: options.url,
10090
+ webSocketImpl: proxy ? CustomWebSocket : options.websocket || WebSocket,
10091
+ connectionParams: () => {
10092
+ return {
10093
+ headers: options.type === "apiKey" ? {
10094
+ [API_KEY_HEADER_NAME]: options.apiKey
10095
+ } : { authorization: `Bearer ${options.token}` }
10096
+ };
10097
+ }
10098
+ });
10099
+ }
10100
+ function subscribe(query, variables, callback, wsClientOptions) {
10101
+ return new Promise((resolve, reject) => {
10102
+ let timer = null;
10103
+ const { timeoutInMs = SUBSCRIPTION_TIMEOUT_MS } = wsClientOptions;
10104
+ const API_URL2 = process.env["API_URL"] || DEFAULT_API_URL;
10105
+ const client = createWSClient({
10106
+ ...wsClientOptions,
10107
+ websocket: WebSocket,
10108
+ url: API_URL2.replace("http", "ws")
10096
10109
  });
10097
- return submitRes;
10098
- } catch (e) {
10099
- spinner.error({ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed" });
10100
- throw e;
10101
- }
10110
+ const unsubscribe = client.subscribe(
10111
+ { query, variables },
10112
+ {
10113
+ next: (data) => {
10114
+ function callbackResolve(data2) {
10115
+ unsubscribe();
10116
+ if (timer) {
10117
+ clearTimeout(timer);
10118
+ }
10119
+ resolve(data2);
10120
+ }
10121
+ function callbackReject(data2) {
10122
+ unsubscribe();
10123
+ if (timer) {
10124
+ clearTimeout(timer);
10125
+ }
10126
+ reject(data2);
10127
+ }
10128
+ if (!data.data) {
10129
+ reject(
10130
+ new Error(
10131
+ `Broken data object from graphQL subscribe: ${JSON.stringify(
10132
+ data
10133
+ )} for query: ${query}`
10134
+ )
10135
+ );
10136
+ } else {
10137
+ callback(callbackResolve, callbackReject, data.data);
10138
+ }
10139
+ },
10140
+ error: (error) => {
10141
+ if (timer) {
10142
+ clearTimeout(timer);
10143
+ }
10144
+ reject(error);
10145
+ },
10146
+ complete: () => {
10147
+ return;
10148
+ }
10149
+ }
10150
+ );
10151
+ if (typeof timeoutInMs === "number") {
10152
+ timer = setTimeout(() => {
10153
+ unsubscribe();
10154
+ reject(
10155
+ new Error(
10156
+ `Timeout expired for graphQL subscribe query: ${query} with timeout: ${timeoutInMs}`
10157
+ )
10158
+ );
10159
+ }, timeoutInMs);
10160
+ }
10161
+ });
10102
10162
  }
10103
10163
 
10104
- // src/features/analysis/utils/index.ts
10105
- function getFromArraySafe(array) {
10106
- return array.reduce((acc, nullableItem) => {
10107
- if (nullableItem) {
10108
- acc.push(nullableItem);
10164
+ // src/features/analysis/graphql/types.ts
10165
+ import { z as z25 } from "zod";
10166
+ var VulnerabilityReportIssueCodeNodeZ = z25.object({
10167
+ vulnerabilityReportIssueId: z25.string(),
10168
+ path: z25.string(),
10169
+ startLine: z25.number(),
10170
+ vulnerabilityReportIssue: z25.object({
10171
+ fixId: z25.string(),
10172
+ category: z25.nativeEnum(Vulnerability_Report_Issue_Category_Enum),
10173
+ safeIssueType: z25.string(),
10174
+ vulnerabilityReportIssueTags: z25.array(
10175
+ z25.object({
10176
+ tag: z25.nativeEnum(Vulnerability_Report_Issue_Tag_Enum)
10177
+ })
10178
+ )
10179
+ })
10180
+ });
10181
+ var VulnerabilityReportIssueNoFixCodeNodeZ = z25.object({
10182
+ vulnerabilityReportIssues: z25.array(
10183
+ z25.object({
10184
+ id: z25.string(),
10185
+ fixId: z25.string().nullable(),
10186
+ category: z25.nativeEnum(Vulnerability_Report_Issue_Category_Enum),
10187
+ safeIssueType: z25.string(),
10188
+ fpId: z25.string().uuid().nullable(),
10189
+ codeNodes: z25.array(
10190
+ z25.object({
10191
+ path: z25.string(),
10192
+ startLine: z25.number()
10193
+ })
10194
+ ),
10195
+ vulnerabilityReportIssueTags: z25.array(
10196
+ z25.object({
10197
+ tag: z25.nativeEnum(Vulnerability_Report_Issue_Tag_Enum)
10198
+ })
10199
+ )
10200
+ })
10201
+ )
10202
+ });
10203
+ var GetVulByNodesMetadataZ = z25.object({
10204
+ vulnerabilityReportIssueCodeNodes: z25.array(VulnerabilityReportIssueCodeNodeZ),
10205
+ nonFixablePrVuls: z25.object({
10206
+ aggregate: z25.object({
10207
+ count: z25.number()
10208
+ })
10209
+ }),
10210
+ fixablePrVuls: z25.object({
10211
+ aggregate: z25.object({
10212
+ count: z25.number()
10213
+ })
10214
+ }),
10215
+ totalScanVulnerabilities: z25.object({
10216
+ aggregate: z25.object({
10217
+ count: z25.number()
10218
+ })
10219
+ }),
10220
+ irrelevantVulnerabilityReportIssue: z25.array(
10221
+ VulnerabilityReportIssueNoFixCodeNodeZ
10222
+ )
10223
+ });
10224
+
10225
+ // src/features/analysis/graphql/gql.ts
10226
+ var debug7 = Debug6("mobbdev:gql");
10227
+ var API_KEY_HEADER_NAME = "x-mobb-key";
10228
+ var REPORT_STATE_CHECK_DELAY = 5 * 1e3;
10229
+ function getProxyAgent(url) {
10230
+ try {
10231
+ const parsedUrl = new URL(url);
10232
+ const isHttp = parsedUrl.protocol === "http:";
10233
+ const isHttps = parsedUrl.protocol === "https:";
10234
+ const proxy = isHttps ? HTTPS_PROXY : isHttp ? HTTP_PROXY : null;
10235
+ if (proxy) {
10236
+ debug7("Using proxy %s", proxy);
10237
+ debug7("Proxy agent %o", proxy);
10238
+ return isHttps ? new HttpsProxyAgent2(proxy) : new HttpProxyAgent(proxy);
10109
10239
  }
10110
- return acc;
10111
- }, []);
10240
+ } catch (err) {
10241
+ debug7(`Skipping proxy for ${url}. Reason: ${err.message}`);
10242
+ }
10243
+ return void 0;
10112
10244
  }
10113
-
10114
- // src/features/analysis/add_fix_comments_for_pr/constants.ts
10115
- var contactUsMarkdown = `For specific requests [contact us](https://content.mobb.ai/contact) and we'll do the most to answer your need quickly.`;
10116
- var MobbIconMarkdown = `![image](${MOBB_ICON_IMG})`;
10117
- var noVulnerabilitiesFoundTitle = `# ${MobbIconMarkdown} No security issues were found \u2705`;
10118
- var COMMIT_FIX_SVG = `https://app.mobb.ai/gh-action/commit-button.svg`;
10119
- var scannerToFriendlyString = {
10120
- checkmarx: "Checkmarx",
10121
- codeql: "CodeQL",
10122
- fortify: "Fortify",
10123
- snyk: "Snyk",
10124
- sonarqube: "Sonarqube",
10125
- semgrep: "Semgrep",
10126
- datadog: "Datadog"
10245
+ var fetchWithProxy = (url, options = {}) => {
10246
+ try {
10247
+ const agent = getProxyAgent(url.toString());
10248
+ if (agent) {
10249
+ return fetchOrig(url, {
10250
+ ...options,
10251
+ // @ts-expect-error Node-fetch doesn't type 'agent', but it's valid
10252
+ agent
10253
+ });
10254
+ }
10255
+ } catch (err) {
10256
+ debug7(`Skipping proxy for ${url}. Reason: ${err.message}`);
10257
+ }
10258
+ return fetchOrig(url, options);
10127
10259
  };
10128
-
10129
- // src/features/analysis/add_fix_comments_for_pr/utils/buildCommentBody.ts
10130
- import Debug6 from "debug";
10131
- import { z as z25 } from "zod";
10132
- var debug7 = Debug6("mobbdev:handle-finished-analysis");
10133
- var getCommitFixButton = (commitUrl) => `<a href="${commitUrl}"><img src=${COMMIT_FIX_SVG}></a>`;
10134
- function buildFixCommentBody({
10135
- fix,
10136
- issueId,
10137
- commentId,
10138
- commentUrl,
10139
- scanner,
10140
- fixId,
10141
- projectId,
10142
- analysisId,
10143
- organizationId,
10144
- patch,
10145
- irrelevantIssueWithTags
10146
- }) {
10147
- const isIrrelevantIssueWithTags = irrelevantIssueWithTags?.[0]?.tag;
10148
- const commitUrl = isIrrelevantIssueWithTags ? getCommitIssueUrl({
10149
- appBaseUrl: WEB_APP_URL,
10150
- issueId,
10151
- projectId,
10152
- analysisId,
10153
- organizationId,
10154
- redirectUrl: commentUrl,
10155
- commentId
10156
- }) : getCommitUrl({
10157
- appBaseUrl: WEB_APP_URL,
10158
- fixId,
10159
- projectId,
10160
- analysisId,
10161
- organizationId,
10162
- redirectUrl: commentUrl,
10163
- commentId
10164
- });
10165
- const fixUrl = isIrrelevantIssueWithTags ? getIssueUrlWithRedirect({
10166
- appBaseUrl: WEB_APP_URL,
10167
- issueId,
10168
- projectId,
10169
- analysisId,
10170
- organizationId,
10171
- redirectUrl: commentUrl,
10172
- commentId
10173
- }) : getFixUrlWithRedirect({
10174
- appBaseUrl: WEB_APP_URL,
10175
- fixId,
10176
- projectId,
10177
- analysisId,
10178
- organizationId,
10179
- redirectUrl: commentUrl,
10180
- commentId
10181
- });
10182
- const issueType = getIssueTypeFriendlyString(fix.safeIssueType);
10183
- const title = `# ${MobbIconMarkdown} ${issueType} fix is ready`;
10184
- const validFixParseRes = z25.object({
10185
- patchAndQuestions: PatchAndQuestionsZ,
10186
- safeIssueLanguage: z25.nativeEnum(IssueLanguage_Enum),
10187
- severityText: z25.nativeEnum(Vulnerability_Severity_Enum),
10188
- safeIssueType: z25.nativeEnum(IssueType_Enum)
10189
- }).safeParse(fix);
10190
- if (!validFixParseRes.success) {
10191
- debug7(
10192
- `fix ${fixId} has custom issue type or language, therefore the commit description will not be added`,
10193
- validFixParseRes.error
10194
- );
10260
+ var GQLClient = class {
10261
+ constructor(args) {
10262
+ __publicField(this, "_client");
10263
+ __publicField(this, "_clientSdk");
10264
+ __publicField(this, "_auth");
10265
+ debug7(`init with ${args}`);
10266
+ this._auth = args;
10267
+ this._client = new GraphQLClient(API_URL, {
10268
+ headers: args.type === "apiKey" ? { [API_KEY_HEADER_NAME]: args.apiKey || "" } : {
10269
+ Authorization: `Bearer ${args.token}`
10270
+ },
10271
+ fetch: fetchWithProxy,
10272
+ requestMiddleware: (request) => {
10273
+ const requestId = uuidv4();
10274
+ debug7(
10275
+ `sending API request with id: ${requestId} and with request: ${request.body}`
10276
+ );
10277
+ return {
10278
+ ...request,
10279
+ headers: {
10280
+ ...request.headers,
10281
+ "x-hasura-request-id": requestId
10282
+ }
10283
+ };
10284
+ }
10285
+ });
10286
+ this._clientSdk = getSdk(this._client);
10195
10287
  }
10196
- const subTitle = validFixParseRes.success ? getCommitDescription({
10197
- issueType: validFixParseRes.data.safeIssueType,
10198
- vendor: scannerToVulnerabilityReportVendorEnum[scanner],
10199
- severity: validFixParseRes.data.severityText,
10200
- guidances: getGuidances({
10201
- questions: validFixParseRes.data.patchAndQuestions.questions.map(toQuestion),
10202
- issueType: validFixParseRes.data.safeIssueType,
10203
- issueLanguage: validFixParseRes.data.safeIssueLanguage,
10204
- fixExtraContext: validFixParseRes.data.patchAndQuestions.extraContext
10205
- }),
10206
- irrelevantIssueWithTags
10207
- }) : "";
10208
- const diff = `\`\`\`diff
10209
- ${patch}
10210
- \`\`\``;
10211
- const fixPageLink = `[Learn more and fine tune the fix](${fixUrl})`;
10212
- return `${title}
10213
- ${subTitle}
10214
- ${diff}
10215
- ${getCommitFixButton(
10216
- commitUrl
10217
- )}
10218
- ${fixPageLink}`;
10219
- }
10220
- function buildIssueCommentBody({
10221
- issueId,
10222
- commentId,
10223
- commentUrl,
10224
- scanner,
10225
- issueType,
10226
- projectId,
10227
- analysisId,
10228
- organizationId,
10229
- irrelevantIssueWithTags,
10230
- fpDescription
10231
- }) {
10232
- const issueUrl = getIssueUrlWithRedirect({
10233
- appBaseUrl: WEB_APP_URL,
10234
- issueId,
10235
- projectId,
10236
- analysisId,
10237
- organizationId,
10238
- redirectUrl: commentUrl,
10239
- commentId
10240
- });
10241
- const title = `# ${MobbIconMarkdown} Irrelevant issues were spotted - no action required \u{1F9F9}`;
10242
- const subTitle = getCommitIssueDescription({
10243
- issueType,
10244
- vendor: scannerToVulnerabilityReportVendorEnum[scanner],
10245
- irrelevantIssueWithTags,
10246
- fpDescription
10247
- });
10248
- const issuePageLink = `[Learn more about this issue](${issueUrl})`;
10249
- return `${title}
10250
- ${subTitle}
10251
- ${issuePageLink}`;
10252
- }
10253
-
10254
- // src/features/analysis/add_fix_comments_for_pr/utils/utils.ts
10255
- var debug8 = Debug7("mobbdev:handle-finished-analysis");
10256
- function calculateRanges(integers) {
10257
- if (integers.length === 0) {
10258
- return [];
10288
+ async getUserInfo() {
10289
+ const { me } = await this._clientSdk.Me();
10290
+ return me;
10259
10291
  }
10260
- integers.sort((a, b) => a - b);
10261
- const ranges = integers.reduce(
10262
- (result, current, index) => {
10263
- if (index === 0) {
10264
- return [...result, [current, current]];
10265
- }
10266
- const currentRange = result[result.length - 1];
10267
- const [_start, end] = currentRange;
10268
- if (current === end + 1) {
10269
- currentRange[1] = current;
10270
- } else {
10271
- result.push([current, current]);
10272
- }
10273
- return result;
10274
- },
10275
- []
10276
- );
10277
- return ranges;
10278
- }
10279
- function deleteAllPreviousComments({
10280
- comments,
10281
- scm
10282
- }) {
10283
- return comments.data.filter((comment) => {
10284
- return comment.body.includes(MOBB_ICON_IMG);
10285
- }).map((comment) => {
10292
+ async createCliLogin(variables) {
10293
+ const res = await this._clientSdk.CreateCliLogin(variables, {
10294
+ // We may have outdated API key in the config storage. Avoid using it for the login request.
10295
+ [API_KEY_HEADER_NAME]: ""
10296
+ });
10297
+ return res.insert_cli_login_one?.id || "";
10298
+ }
10299
+ async verifyApiConnection() {
10286
10300
  try {
10287
- return scm.deleteComment({ comment_id: comment.id });
10301
+ await this.getUserInfo();
10288
10302
  } catch (e) {
10289
- debug8("delete comment failed %s", e);
10290
- return Promise.resolve();
10303
+ if (e?.toString().startsWith("FetchError")) {
10304
+ debug7("verify connection failed %o", e);
10305
+ return false;
10306
+ }
10291
10307
  }
10292
- });
10293
- }
10294
- function deleteAllPreviousGeneralPrComments(params) {
10295
- const { generalPrComments, scm } = params;
10296
- return generalPrComments.data.filter((comment) => {
10297
- if (!comment.body) {
10308
+ return true;
10309
+ }
10310
+ async validateUserToken() {
10311
+ await this.createCommunityUser();
10312
+ let info;
10313
+ try {
10314
+ info = await this.getUserInfo();
10315
+ } catch (e) {
10316
+ debug7("verify token failed %o", e);
10298
10317
  return false;
10299
10318
  }
10300
- return comment.body.includes(MOBB_ICON_IMG);
10301
- }).map((comment) => {
10319
+ return info?.email || true;
10320
+ }
10321
+ async getLastOrgAndNamedProject(params) {
10322
+ const me = await this.getUserInfo();
10323
+ const email = me?.email;
10324
+ if (!email) {
10325
+ throw new Error("User email not found");
10326
+ }
10327
+ const { projectName, userDefinedOrganizationId } = params;
10328
+ if (!projectName) {
10329
+ throw new Error("Project name is required");
10330
+ }
10331
+ const orgAndProjectRes = await this._clientSdk.getLastOrgAndNamedProject({
10332
+ email,
10333
+ projectName
10334
+ });
10335
+ if (!orgAndProjectRes.user?.[0]?.userOrganizationsAndUserOrganizationRoles?.[0]?.organization?.id) {
10336
+ throw new Error(
10337
+ `The user with email:${email} is not associated with any organization`
10338
+ );
10339
+ }
10340
+ const organization = orgAndProjectRes.user?.at(0)?.userOrganizationsAndUserOrganizationRoles.map(
10341
+ (org) => org.organization
10342
+ ).filter(
10343
+ (org) => userDefinedOrganizationId ? org.id === userDefinedOrganizationId : true
10344
+ )?.at(0);
10345
+ if (!organization) {
10346
+ throw new Error(
10347
+ `Organization with id:${userDefinedOrganizationId} not found`
10348
+ );
10349
+ }
10350
+ let projectId = organization?.projects?.[0]?.id;
10351
+ if (!projectId) {
10352
+ const createdProject = await this._clientSdk.CreateProject({
10353
+ organizationId: organization.id,
10354
+ projectName: projectName || "My project"
10355
+ });
10356
+ projectId = createdProject.createProject.projectId;
10357
+ }
10358
+ if (!projectId) {
10359
+ throw new Error("Project not found");
10360
+ }
10361
+ return {
10362
+ organizationId: organization.id,
10363
+ projectId
10364
+ };
10365
+ }
10366
+ async getEncryptedApiToken(variables) {
10367
+ const res = await this._clientSdk.GetEncryptedApiToken(variables, {
10368
+ // We may have outdated API key in the config storage. Avoid using it for the login request.
10369
+ [API_KEY_HEADER_NAME]: ""
10370
+ });
10371
+ return res?.cli_login_by_pk?.encryptedApiToken || null;
10372
+ }
10373
+ async createCommunityUser() {
10302
10374
  try {
10303
- return scm.deleteGeneralPrComment({ commentId: comment.id });
10375
+ await this._clientSdk.CreateCommunityUser();
10304
10376
  } catch (e) {
10305
- debug8("delete comment failed %s", e);
10306
- return Promise.resolve();
10377
+ debug7("create community user failed %o", e);
10307
10378
  }
10308
- });
10309
- }
10310
- async function postIssueComment(params) {
10311
- const {
10312
- vulnerabilityReportIssueCodeNode,
10313
- projectId,
10314
- analysisId,
10315
- organizationId,
10316
- scm,
10317
- commitSha,
10318
- pullRequest,
10319
- scanner,
10320
- fpDescription
10321
- } = params;
10322
- const {
10323
- path: path17,
10324
- startLine,
10325
- vulnerabilityReportIssue: {
10326
- vulnerabilityReportIssueTags,
10327
- category,
10328
- safeIssueType
10329
- },
10330
- vulnerabilityReportIssueId
10331
- } = vulnerabilityReportIssueCodeNode;
10332
- const irrelevantIssueWithTags = mapCategoryToBucket[category] === "irrelevant" && vulnerabilityReportIssueTags?.length > 0 ? vulnerabilityReportIssueTags : [];
10333
- const commentRes = await scm.postPrComment({
10334
- body: `# ${MobbIconMarkdown} Your fix is ready!
10335
- Refresh the page in order to see the changes.`,
10336
- pull_number: pullRequest,
10337
- commit_id: commitSha,
10338
- path: path17,
10339
- line: startLine
10340
- });
10341
- const commentId = commentRes.data.id;
10342
- const commentBody = buildIssueCommentBody({
10343
- issueId: vulnerabilityReportIssueId,
10344
- issueType: safeIssueType,
10345
- irrelevantIssueWithTags,
10346
- commentId,
10347
- commentUrl: commentRes.data.html_url,
10348
- scanner,
10349
- projectId,
10350
- analysisId,
10351
- organizationId,
10352
- fpDescription
10353
- });
10354
- return await scm.updatePrComment({
10355
- body: commentBody,
10356
- comment_id: commentId
10357
- });
10358
- }
10359
- async function postFixComment(params) {
10360
- const {
10361
- vulnerabilityReportIssueCodeNode,
10362
- projectId,
10363
- analysisId,
10364
- organizationId,
10365
- fixesById,
10366
- scm,
10367
- commitSha,
10368
- pullRequest,
10369
- scanner
10370
- } = params;
10371
- const {
10372
- path: path17,
10373
- startLine,
10374
- vulnerabilityReportIssue: { fixId, vulnerabilityReportIssueTags, category },
10375
- vulnerabilityReportIssueId
10376
- } = vulnerabilityReportIssueCodeNode;
10377
- const irrelevantIssueWithTags = mapCategoryToBucket[category] === "irrelevant" && vulnerabilityReportIssueTags?.length > 0 ? vulnerabilityReportIssueTags : [];
10378
- const fix = fixesById[fixId];
10379
- if (!fix || fix.patchAndQuestions.__typename !== "FixData") {
10380
- throw new Error(`fix ${fixId} not found`);
10381
- }
10382
- const {
10383
- patchAndQuestions: { patch }
10384
- } = fix;
10385
- const commentRes = await scm.postPrComment({
10386
- body: `# ${MobbIconMarkdown} Your fix is ready!
10387
- Refresh the page in order to see the changes.`,
10388
- pull_number: pullRequest,
10389
- commit_id: commitSha,
10390
- path: path17,
10391
- line: startLine
10392
- });
10393
- const commentId = commentRes.data.id;
10394
- const commentBody = buildFixCommentBody({
10395
- fix,
10396
- issueId: vulnerabilityReportIssueId,
10397
- irrelevantIssueWithTags,
10398
- commentId,
10399
- commentUrl: commentRes.data.html_url,
10400
- scanner,
10401
- fixId,
10402
- projectId,
10403
- analysisId,
10404
- organizationId,
10405
- patch
10406
- });
10407
- return await scm.updatePrComment({
10408
- body: commentBody,
10409
- comment_id: commentId
10410
- });
10411
- }
10412
- async function getRelevantVulenrabilitiesFromDiff(params) {
10413
- const { gqlClient, diff, vulnerabilityReportId } = params;
10414
- const parsedDiff = parseDiff(diff);
10415
- const fileHunks = parsedDiff.map((file) => {
10416
- const fileNumbers = file.chunks.flatMap((chunk) => chunk.changes).filter((change) => change.type === "add").map((_change) => {
10417
- const change = _change;
10418
- return change.ln;
10419
- });
10420
- const lineAddedRanges = calculateRanges(fileNumbers);
10421
- const fileFilter = {
10422
- path: z26.string().parse(file.to),
10423
- ranges: lineAddedRanges.map(([startLine, endLine]) => ({
10424
- endLine,
10425
- startLine
10426
- }))
10427
- };
10428
- return fileFilter;
10429
- });
10430
- return gqlClient.getVulByNodesMetadata({
10431
- hunks: fileHunks,
10432
- vulnerabilityReportId
10433
- });
10434
- }
10435
- async function getFixesData(params) {
10436
- const { gqlClient, fixesId } = params;
10437
- const { fixes } = await gqlClient.getFixes(fixesId);
10438
- return keyBy(fixes, "id");
10439
- }
10440
- async function postAnalysisInsightComment(params) {
10441
- const { prVulenrabilities, pullRequest, scanner, scm } = params;
10442
- const scanerString = scannerToFriendlyString[scanner];
10443
- const {
10444
- totalPrVulnerabilities,
10445
- vulnerabilitiesOutsidePr,
10446
- fixablePrVuls,
10447
- nonFixablePrVuls
10448
- } = prVulenrabilities;
10449
- debug8({
10450
- fixablePrVuls,
10451
- nonFixablePrVuls,
10452
- vulnerabilitiesOutsidePr,
10453
- totalPrVulnerabilities
10454
- });
10455
- if (totalPrVulnerabilities === 0 && vulnerabilitiesOutsidePr === 0) {
10456
- const body = `Awesome! No vulnerabilities were found by **${scanerString}**`;
10457
- const noVulsFoundComment = `${noVulnerabilitiesFoundTitle}
10458
- ${body}`;
10459
- await scm.postGeneralPrComment({
10460
- body: noVulsFoundComment,
10461
- prNumber: pullRequest
10462
- });
10463
- return;
10464
- }
10465
- if (totalPrVulnerabilities === 0 && vulnerabilitiesOutsidePr > 0) {
10466
- const body = `Awesome! No vulnerabilities were found by **${scanerString}** in the changes made as part of this PR.`;
10467
- const body2 = `Please notice there are issues in this repo that are unrelated to this PR.`;
10468
- const noVulsFoundComment = `${noVulnerabilitiesFoundTitle}
10469
- ${body}
10470
- ${body2}`;
10471
- await scm.postGeneralPrComment({
10472
- body: noVulsFoundComment,
10473
- prNumber: pullRequest
10474
- });
10475
- return;
10476
- }
10477
- if (fixablePrVuls === 0 && nonFixablePrVuls > 0) {
10478
- const title = `# ${MobbIconMarkdown} We couldn't fix the issues detected by **${scanerString}**`;
10479
- const body = `Mobb Fixer gets better and better every day, but unfortunately your current issues aren't supported yet.`;
10480
- const noFixableVulsComment = `${title}
10481
- ${body}
10482
- ${contactUsMarkdown}`;
10483
- await scm.postGeneralPrComment({
10484
- body: noFixableVulsComment,
10485
- prNumber: pullRequest
10486
- });
10487
- return;
10488
- }
10489
- if (fixablePrVuls < nonFixablePrVuls && fixablePrVuls > 0) {
10490
- const title = `# ${MobbIconMarkdown} We couldn't fix some of the issues detected by **${scanerString}**`;
10491
- const body = "Mobb Fixer gets better and better every day, but unfortunately your current issues aren't supported yet.";
10492
- const fixableVulsComment = `${title}
10493
- ${body}
10494
- ${contactUsMarkdown}`;
10495
- await scm.postGeneralPrComment({
10496
- body: fixableVulsComment,
10497
- prNumber: pullRequest
10498
- });
10499
- return;
10500
- }
10501
- }
10502
-
10503
- // src/features/analysis/add_fix_comments_for_pr/add_fix_comments_for_pr.ts
10504
- var debug9 = Debug8("mobbdev:handle-finished-analysis");
10505
- async function addFixCommentsForPr({
10506
- analysisId,
10507
- scm: _scm,
10508
- gqlClient,
10509
- scanner
10510
- }) {
10511
- if (!(_scm instanceof GithubSCMLib)) {
10512
- return;
10513
- }
10514
- const scm = _scm;
10515
- const getAnalysisRes = await gqlClient.getAnalysis(analysisId);
10516
- debug9("getAnalysis %o", getAnalysisRes);
10517
- const {
10518
- vulnerabilityReport: {
10519
- projectId,
10520
- project: {
10521
- organizationId,
10522
- organization: { ghFixerNoFixComments }
10523
- }
10524
- }
10525
- } = getAnalysisRes;
10526
- if (!getAnalysisRes.repo?.commitSha || !getAnalysisRes.repo.pullRequest) {
10527
- throw new Error("repo not found");
10528
- }
10529
- const { commitSha, pullRequest } = getAnalysisRes.repo;
10530
- const diff = await scm.getPrDiff({ pull_number: pullRequest });
10531
- const prVulenrabilities = await getRelevantVulenrabilitiesFromDiff({
10532
- diff,
10533
- gqlClient,
10534
- vulnerabilityReportId: getAnalysisRes.vulnerabilityReportId
10535
- });
10536
- const {
10537
- vulnerabilityReportIssueCodeNodes,
10538
- irrelevantVulnerabilityReportIssues
10539
- } = prVulenrabilities;
10540
- const fixesId = vulnerabilityReportIssueCodeNodes.map(
10541
- ({ vulnerabilityReportIssue: { fixId } }) => fixId
10542
- );
10543
- const fixesById = await getFixesData({ fixesId, gqlClient });
10544
- const [comments, generalPrComments] = await Promise.all([
10545
- scm.getPrComments({ pull_number: pullRequest }),
10546
- scm.getGeneralPrComments({ prNumber: pullRequest })
10547
- ]);
10548
- await Promise.all([
10549
- ...deleteAllPreviousComments({ comments, scm }),
10550
- ...deleteAllPreviousGeneralPrComments({ generalPrComments, scm })
10551
- ]);
10552
- await Promise.all(
10553
- [
10554
- ...prVulenrabilities.vulnerabilityReportIssueCodeNodes.map(
10555
- (vulnerabilityReportIssueCodeNode) => {
10556
- return postFixComment({
10557
- vulnerabilityReportIssueCodeNode,
10558
- projectId,
10559
- analysisId,
10560
- organizationId,
10561
- fixesById,
10562
- scm,
10563
- pullRequest,
10564
- scanner,
10565
- commitSha
10566
- });
10567
- }
10568
- ),
10569
- ...irrelevantVulnerabilityReportIssues.map(
10570
- async (vulnerabilityReportIssue) => {
10571
- let fpDescription = null;
10572
- if (vulnerabilityReportIssue.fpId) {
10573
- const fpRes = await gqlClient.getFalsePositive({
10574
- fpId: vulnerabilityReportIssue.fpId
10575
- });
10576
- const parsedFpRes = await FalsePositivePartsZ.parseAsync(
10577
- fpRes?.getFalsePositive
10578
- );
10579
- const { description, contextString } = getParsedFalsePositiveMessage(parsedFpRes);
10580
- fpDescription = contextString ? `${description}
10581
-
10582
- ${contextString}` : description;
10583
- }
10584
- return await Promise.all(
10585
- vulnerabilityReportIssue.codeNodes.map(
10586
- async (vulnerabilityReportIssueCodeNode) => {
10587
- return await postIssueComment({
10588
- vulnerabilityReportIssueCodeNode: {
10589
- path: vulnerabilityReportIssueCodeNode.path,
10590
- startLine: vulnerabilityReportIssueCodeNode.startLine,
10591
- vulnerabilityReportIssue: {
10592
- fixId: "",
10593
- safeIssueType: vulnerabilityReportIssue.safeIssueType,
10594
- vulnerabilityReportIssueTags: vulnerabilityReportIssue.vulnerabilityReportIssueTags,
10595
- category: vulnerabilityReportIssue.category
10596
- },
10597
- vulnerabilityReportIssueId: vulnerabilityReportIssue.id
10598
- },
10599
- projectId,
10600
- analysisId,
10601
- organizationId,
10602
- fixesById,
10603
- scm,
10604
- pullRequest,
10605
- scanner,
10606
- commitSha,
10607
- fpDescription
10608
- });
10609
- }
10610
- )
10611
- );
10612
- }
10613
- ),
10614
- !ghFixerNoFixComments && postAnalysisInsightComment({
10615
- prVulenrabilities,
10616
- pullRequest,
10617
- scanner,
10618
- scm
10619
- })
10620
- ].filter(Boolean)
10621
- );
10622
- }
10623
-
10624
- // src/features/analysis/auto_pr_handler.ts
10625
- import Debug9 from "debug";
10626
- var debug10 = Debug9("mobbdev:handleAutoPr");
10627
- async function handleAutoPr(params) {
10628
- const {
10629
- gqlClient,
10630
- analysisId,
10631
- commitDirectly,
10632
- prId,
10633
- createSpinner: createSpinner5,
10634
- createOnePr
10635
- } = params;
10636
- const createAutoPrSpinner = createSpinner5(
10637
- "\u{1F504} Waiting for the analysis to finish before initiating automatic pull request creation"
10638
- ).start();
10639
- return await gqlClient.subscribeToAnalysis({
10640
- subscribeToAnalysisParams: {
10641
- analysisId
10642
- },
10643
- callback: async (analysisId2) => {
10644
- const autoPrAnalysisRes = await gqlClient.autoPrAnalysis({
10645
- analysisId: analysisId2,
10646
- commitDirectly,
10647
- prId,
10648
- prStrategy: createOnePr ? "CONDENSE" /* Condense */ : "SPREAD" /* Spread */
10649
- });
10650
- debug10("auto pr analysis res %o", autoPrAnalysisRes);
10651
- if (autoPrAnalysisRes.autoPrAnalysis?.__typename === "AutoPrError") {
10652
- createAutoPrSpinner.error({
10653
- text: `\u{1F504} Automatic pull request failed - ${autoPrAnalysisRes.autoPrAnalysis.error}`
10654
- });
10655
- return;
10656
- }
10657
- if (autoPrAnalysisRes.autoPrAnalysis?.__typename === "AutoPrSuccess") {
10658
- const { appliedAutoPrIssueTypes } = autoPrAnalysisRes.autoPrAnalysis;
10659
- if (appliedAutoPrIssueTypes.length === 0) {
10660
- createAutoPrSpinner.success({
10661
- text: "\u{1F504} Automatic pull request did not find any new fixes to open a pull request for"
10662
- });
10663
- return;
10664
- }
10665
- createAutoPrSpinner.success({
10666
- text: `\u{1F504} Automatic pull request creation initiated successfully for the following issue types: ${appliedAutoPrIssueTypes}`
10667
- });
10379
+ }
10380
+ async updateScmToken(args) {
10381
+ const { scmType, url, token, org, refreshToken } = args;
10382
+ const updateScmTokenResult = await this._clientSdk.updateScmToken({
10383
+ scmType,
10384
+ url,
10385
+ token,
10386
+ org,
10387
+ refreshToken
10388
+ });
10389
+ return updateScmTokenResult;
10390
+ }
10391
+ async uploadS3BucketInfo() {
10392
+ const uploadS3BucketInfoResult = await this._clientSdk.uploadS3BucketInfo({
10393
+ fileName: REPORT_DEFAULT_FILE_NAME
10394
+ });
10395
+ return uploadS3BucketInfoResult;
10396
+ }
10397
+ async getVulByNodesMetadata({
10398
+ hunks,
10399
+ vulnerabilityReportId
10400
+ }) {
10401
+ const filters = hunks.map((hunk) => {
10402
+ const filter = {
10403
+ path: { _eq: hunk.path },
10404
+ _or: hunk.ranges.flatMap(({ endLine, startLine }) => {
10405
+ return [
10406
+ { startLine: { _gte: startLine, _lte: endLine } },
10407
+ { endLine: { _gte: startLine, _lte: endLine } }
10408
+ ];
10409
+ })
10410
+ };
10411
+ return filter;
10412
+ });
10413
+ const getVulByNodesMetadataRes = await this._clientSdk.getVulByNodesMetadata({
10414
+ filters: { _or: filters },
10415
+ vulnerabilityReportId
10416
+ });
10417
+ const parsedGetVulByNodesMetadataRes = GetVulByNodesMetadataZ.parse(
10418
+ getVulByNodesMetadataRes
10419
+ );
10420
+ const uniqueVulByNodesMetadata = parsedGetVulByNodesMetadataRes.vulnerabilityReportIssueCodeNodes.reduce((acc, vulnerabilityReportIssueCodeNode) => {
10421
+ if (acc[vulnerabilityReportIssueCodeNode.vulnerabilityReportIssueId]) {
10422
+ return acc;
10668
10423
  }
10669
- },
10670
- callbackStates: ["Finished" /* Finished */]
10671
- });
10672
- }
10673
-
10674
- // src/features/analysis/git.ts
10675
- init_GitService();
10676
- import Debug10 from "debug";
10677
- var debug11 = Debug10("mobbdev:git");
10678
- async function getGitInfo(srcDirPath) {
10679
- debug11("getting git info for %s", srcDirPath);
10680
- const gitService = new GitService(srcDirPath);
10681
- try {
10682
- const validationResult = await gitService.validateRepository();
10683
- if (!validationResult.isValid) {
10684
- debug11("folder is not a git repo");
10685
10424
  return {
10686
- success: false,
10687
- hash: void 0,
10688
- reference: void 0,
10689
- repoUrl: void 0
10425
+ ...acc,
10426
+ [vulnerabilityReportIssueCodeNode.vulnerabilityReportIssueId]: vulnerabilityReportIssueCodeNode
10690
10427
  };
10691
- }
10692
- const gitInfo2 = await gitService.getGitInfo();
10428
+ }, {});
10429
+ const nonFixablePrVuls = parsedGetVulByNodesMetadataRes.nonFixablePrVuls.aggregate.count;
10430
+ const fixablePrVuls = parsedGetVulByNodesMetadataRes.fixablePrVuls.aggregate.count;
10431
+ const totalScanVulnerabilities = parsedGetVulByNodesMetadataRes.totalScanVulnerabilities.aggregate.count;
10432
+ const vulnerabilitiesOutsidePr = totalScanVulnerabilities - nonFixablePrVuls - fixablePrVuls;
10433
+ const totalPrVulnerabilities = nonFixablePrVuls + fixablePrVuls;
10434
+ const irrelevantVulnerabilityReportIssues = parsedGetVulByNodesMetadataRes.irrelevantVulnerabilityReportIssue?.[0]?.vulnerabilityReportIssues ?? [];
10693
10435
  return {
10694
- success: true,
10695
- ...gitInfo2
10436
+ vulnerabilityReportIssueCodeNodes: Object.values(
10437
+ uniqueVulByNodesMetadata
10438
+ ),
10439
+ nonFixablePrVuls,
10440
+ fixablePrVuls,
10441
+ totalScanVulnerabilities,
10442
+ vulnerabilitiesOutsidePr,
10443
+ totalPrVulnerabilities,
10444
+ irrelevantVulnerabilityReportIssues
10696
10445
  };
10697
- } catch (e) {
10698
- if (e instanceof Error) {
10699
- debug11("failed to run git %o", e);
10700
- if (e.message.includes(" spawn ")) {
10701
- debug11("git cli not installed");
10702
- } else {
10703
- throw e;
10704
- }
10705
- }
10706
- throw e;
10707
10446
  }
10708
- }
10709
-
10710
- // src/features/analysis/graphql/gql.ts
10711
- import fetchOrig from "cross-fetch";
10712
- import Debug12 from "debug";
10713
- import { GraphQLClient } from "graphql-request";
10714
- import { HttpProxyAgent } from "http-proxy-agent";
10715
- import { HttpsProxyAgent as HttpsProxyAgent2 } from "https-proxy-agent";
10716
- import { v4 as uuidv4 } from "uuid";
10717
-
10718
- // src/features/analysis/graphql/subscribe.ts
10719
- import Debug11 from "debug";
10720
- import { createClient } from "graphql-ws";
10721
- import { HttpsProxyAgent } from "https-proxy-agent";
10722
- import WebSocket from "ws";
10723
- var DEFAULT_API_URL = "https://api.mobb.ai/v1/graphql";
10724
- var debug12 = Debug11("mobbdev:subscribe");
10725
- var SUBSCRIPTION_TIMEOUT_MS = 30 * 60 * 1e3;
10726
- function createWSClient(options) {
10727
- const proxy = options.url.startsWith("wss://") && process.env["HTTPS_PROXY"] ? new HttpsProxyAgent(process.env["HTTPS_PROXY"]) : options.url.startsWith("ws://") && process.env["HTTP_PROXY"] ? new HttpsProxyAgent(process.env["HTTP_PROXY"]) : null;
10728
- debug12(
10729
- `Using proxy: ${proxy ? "yes" : "no"} with url: ${options.url} and with proxy: ${process.env["HTTP_PROXY"]} for the websocket connection`
10730
- );
10731
- const CustomWebSocket = class extends WebSocket {
10732
- constructor(address, protocols) {
10733
- super(
10734
- address,
10735
- protocols,
10736
- proxy ? { agent: proxy } : void 0
10737
- );
10447
+ async digestVulnerabilityReport({
10448
+ fixReportId,
10449
+ projectId,
10450
+ scanSource,
10451
+ repoUrl,
10452
+ reference,
10453
+ sha,
10454
+ shouldScan
10455
+ }) {
10456
+ const res = await this._clientSdk.DigestVulnerabilityReport({
10457
+ fixReportId,
10458
+ vulnerabilityReportFileName: shouldScan ? void 0 : REPORT_DEFAULT_FILE_NAME,
10459
+ projectId,
10460
+ scanSource,
10461
+ repoUrl,
10462
+ reference,
10463
+ sha
10464
+ });
10465
+ if (res.digestVulnerabilityReport.__typename !== "VulnerabilityReport") {
10466
+ throw new Error("Digesting vulnerability report failed");
10738
10467
  }
10739
- };
10740
- return createClient({
10741
- //this is needed to prevent AWS from killing the connection
10742
- //currently our load balancer has a 29s idle timeout
10743
- keepAlive: 1e4,
10744
- url: options.url,
10745
- webSocketImpl: proxy ? CustomWebSocket : options.websocket || WebSocket,
10746
- connectionParams: () => {
10747
- return {
10748
- headers: options.type === "apiKey" ? {
10749
- [API_KEY_HEADER_NAME]: options.apiKey
10750
- } : { authorization: `Bearer ${options.token}` }
10751
- };
10468
+ return res.digestVulnerabilityReport;
10469
+ }
10470
+ async submitVulnerabilityReport(params) {
10471
+ const {
10472
+ fixReportId,
10473
+ repoUrl,
10474
+ reference,
10475
+ projectId,
10476
+ sha,
10477
+ experimentalEnabled,
10478
+ vulnerabilityReportFileName,
10479
+ pullRequest
10480
+ } = params;
10481
+ return await this._clientSdk.SubmitVulnerabilityReport({
10482
+ fixReportId,
10483
+ repoUrl,
10484
+ reference,
10485
+ vulnerabilityReportFileName,
10486
+ projectId,
10487
+ pullRequest,
10488
+ sha: sha || "",
10489
+ experimentalEnabled: !!experimentalEnabled,
10490
+ scanSource: params.scanSource,
10491
+ scanContext: ScanContext.BUGSY
10492
+ });
10493
+ }
10494
+ async getFixReportState(fixReportId) {
10495
+ const res = await this._clientSdk.FixReportState({ id: fixReportId });
10496
+ return res?.fixReport_by_pk?.state || "Created" /* Created */;
10497
+ }
10498
+ async waitFixReportInit(fixReportId, includeDigested = false) {
10499
+ const FINAL_STATES = [
10500
+ "Finished" /* Finished */,
10501
+ "Failed" /* Failed */
10502
+ ];
10503
+ let lastState = "Created" /* Created */;
10504
+ let attempts = 100;
10505
+ if (includeDigested) {
10506
+ FINAL_STATES.push("Digested" /* Digested */);
10752
10507
  }
10753
- });
10754
- }
10755
- function subscribe(query, variables, callback, wsClientOptions) {
10756
- return new Promise((resolve, reject) => {
10757
- let timer = null;
10758
- const { timeoutInMs = SUBSCRIPTION_TIMEOUT_MS } = wsClientOptions;
10759
- const API_URL2 = process.env["API_URL"] || DEFAULT_API_URL;
10760
- const client = createWSClient({
10761
- ...wsClientOptions,
10762
- websocket: WebSocket,
10763
- url: API_URL2.replace("http", "ws")
10508
+ do {
10509
+ await sleep(REPORT_STATE_CHECK_DELAY);
10510
+ lastState = await this.getFixReportState(fixReportId);
10511
+ } while (!FINAL_STATES.includes(
10512
+ lastState
10513
+ // wait for final the state of the fix report
10514
+ ) && attempts-- > 0);
10515
+ return lastState;
10516
+ }
10517
+ async getVulnerabilityReportPaths(vulnerabilityReportId) {
10518
+ const res = await this._clientSdk.GetVulnerabilityReportPaths({
10519
+ vulnerabilityReportId
10764
10520
  });
10765
- const unsubscribe = client.subscribe(
10766
- { query, variables },
10767
- {
10768
- next: (data) => {
10769
- function callbackResolve(data2) {
10770
- unsubscribe();
10771
- if (timer) {
10772
- clearTimeout(timer);
10773
- }
10774
- resolve(data2);
10775
- }
10776
- function callbackReject(data2) {
10777
- unsubscribe();
10778
- if (timer) {
10779
- clearTimeout(timer);
10780
- }
10781
- reject(data2);
10782
- }
10783
- if (!data.data) {
10784
- reject(
10785
- new Error(
10786
- `Broken data object from graphQL subscribe: ${JSON.stringify(
10787
- data
10788
- )} for query: ${query}`
10789
- )
10790
- );
10791
- } else {
10792
- callback(callbackResolve, callbackReject, data.data);
10793
- }
10794
- },
10795
- error: (error) => {
10796
- if (timer) {
10797
- clearTimeout(timer);
10798
- }
10799
- reject(error);
10800
- },
10801
- complete: () => {
10521
+ return res.vulnerability_report_path.map((p) => p.path);
10522
+ }
10523
+ async subscribeToAnalysis(params) {
10524
+ const { callbackStates } = params;
10525
+ return subscribe(
10526
+ GetAnalysisSubscriptionDocument,
10527
+ params.subscribeToAnalysisParams,
10528
+ async (resolve, reject, data) => {
10529
+ if (!data.analysis?.state || data.analysis?.state === "Failed" /* Failed */) {
10530
+ const errorMessage = data.analysis?.failReason || `Analysis failed with id: ${data.analysis?.id}`;
10531
+ reject(
10532
+ new ReportDigestError(errorMessage, data.analysis?.failReason ?? "")
10533
+ );
10802
10534
  return;
10803
10535
  }
10536
+ if (callbackStates.includes(data.analysis?.state)) {
10537
+ await params.callback(data.analysis.id);
10538
+ resolve(data);
10539
+ }
10540
+ },
10541
+ this._auth.type === "apiKey" ? {
10542
+ apiKey: this._auth.apiKey,
10543
+ type: "apiKey",
10544
+ timeoutInMs: params.timeoutInMs
10545
+ } : {
10546
+ token: this._auth.token,
10547
+ type: "token",
10548
+ timeoutInMs: params.timeoutInMs
10804
10549
  }
10805
10550
  );
10806
- if (typeof timeoutInMs === "number") {
10807
- timer = setTimeout(() => {
10808
- unsubscribe();
10809
- reject(
10810
- new Error(
10811
- `Timeout expired for graphQL subscribe query: ${query} with timeout: ${timeoutInMs}`
10812
- )
10813
- );
10814
- }, timeoutInMs);
10551
+ }
10552
+ async getFixReportsByRepoUrl({ repoUrl }) {
10553
+ const res = await this._clientSdk.GetFixReportsByRepoUrl({
10554
+ repoUrl
10555
+ });
10556
+ return res;
10557
+ }
10558
+ async getAnalysis(analysisId) {
10559
+ const res = await this._clientSdk.getAnalysis({
10560
+ analysisId
10561
+ });
10562
+ if (!res.analysis) {
10563
+ throw new Error(`Analysis not found: ${analysisId}`);
10815
10564
  }
10565
+ return res.analysis;
10566
+ }
10567
+ async autoPrAnalysis({
10568
+ analysisId,
10569
+ commitDirectly,
10570
+ prId,
10571
+ prStrategy
10572
+ }) {
10573
+ return this._clientSdk.autoPrAnalysis({
10574
+ analysisId,
10575
+ commitDirectly,
10576
+ prId,
10577
+ prStrategy
10578
+ });
10579
+ }
10580
+ async getFixes(fixIds) {
10581
+ const res = await this._clientSdk.getFixes({
10582
+ filters: { id: { _in: fixIds } }
10583
+ });
10584
+ return res;
10585
+ }
10586
+ async validateRepoUrl(args) {
10587
+ return this._clientSdk.validateRepoUrl(args);
10588
+ }
10589
+ async getReferenceData(args) {
10590
+ return this._clientSdk.gitReference(args);
10591
+ }
10592
+ async getFalsePositive(args) {
10593
+ return this._clientSdk.getFalsePositive(args);
10594
+ }
10595
+ async uploadAIBlameInferencesInitRaw(variables) {
10596
+ return await this._clientSdk.UploadAIBlameInferencesInit(variables);
10597
+ }
10598
+ async finalizeAIBlameInferencesUploadRaw(variables) {
10599
+ return await this._clientSdk.FinalizeAIBlameInferencesUpload(variables);
10600
+ }
10601
+ };
10602
+
10603
+ // src/commands/handleMobbLogin.ts
10604
+ var debug8 = Debug7("mobbdev:commands");
10605
+ var LOGIN_MAX_WAIT = 10 * 60 * 1e3;
10606
+ var LOGIN_CHECK_DELAY = 5 * 1e3;
10607
+ var webLoginUrl = `${WEB_APP_URL}/cli-login`;
10608
+ var MOBB_LOGIN_REQUIRED_MSG = `\u{1F513} Login to Mobb is Required, you will be redirected to our login page, once the authorization is complete return to this prompt, ${chalk3.bgBlue(
10609
+ "press any key to continue"
10610
+ )};`;
10611
+ var config2 = new Configstore(packageJson.name, { apiToken: "" });
10612
+ async function handleMobbLogin({
10613
+ inGqlClient,
10614
+ apiKey,
10615
+ skipPrompts
10616
+ }) {
10617
+ const { createSpinner: createSpinner5 } = Spinner({ ci: skipPrompts });
10618
+ const isConnected = await inGqlClient.verifyApiConnection();
10619
+ if (!isConnected) {
10620
+ createSpinner5().start().error({
10621
+ text: "\u{1F513} Connection to Mobb: failed to connect to the Mobb server"
10622
+ });
10623
+ throw new CliError(
10624
+ "Connection to Mobb: failed to connect to the Mobb server"
10625
+ );
10626
+ }
10627
+ createSpinner5().start().success({
10628
+ text: `\u{1F513} Connection to Mobb: succeeded`
10629
+ });
10630
+ const userVerify = await inGqlClient.validateUserToken();
10631
+ if (userVerify) {
10632
+ createSpinner5().start().success({
10633
+ text: `\u{1F513} Login to Mobb succeeded. ${typeof userVerify === "string" ? `Logged in as ${userVerify}` : ""}`
10634
+ });
10635
+ return inGqlClient;
10636
+ } else if (apiKey) {
10637
+ createSpinner5().start().error({
10638
+ text: "\u{1F513} Login to Mobb failed: The provided API key does not match any configured API key on the system"
10639
+ });
10640
+ throw new CliError(
10641
+ "Login to Mobb failed: The provided API key does not match any configured API key on the system"
10642
+ );
10643
+ }
10644
+ const loginSpinner = createSpinner5().start();
10645
+ if (!skipPrompts) {
10646
+ loginSpinner.update({ text: MOBB_LOGIN_REQUIRED_MSG });
10647
+ await keypress();
10648
+ }
10649
+ loginSpinner.update({
10650
+ text: "\u{1F513} Waiting for Mobb login..."
10651
+ });
10652
+ const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
10653
+ modulusLength: 2048
10816
10654
  });
10655
+ const loginId = await inGqlClient.createCliLogin({
10656
+ publicKey: publicKey.export({ format: "pem", type: "pkcs1" }).toString()
10657
+ });
10658
+ const browserUrl = `${webLoginUrl}/${loginId}?hostname=${os.hostname()}`;
10659
+ !skipPrompts && console.log(
10660
+ `If the page does not open automatically, kindly access it through ${browserUrl}.`
10661
+ );
10662
+ await open(browserUrl);
10663
+ let newApiToken = null;
10664
+ for (let i = 0; i < LOGIN_MAX_WAIT / LOGIN_CHECK_DELAY; i++) {
10665
+ const encryptedApiToken = await inGqlClient.getEncryptedApiToken({
10666
+ loginId
10667
+ });
10668
+ loginSpinner.spin();
10669
+ if (encryptedApiToken) {
10670
+ debug8("encrypted API token received %s", encryptedApiToken);
10671
+ newApiToken = crypto.privateDecrypt(privateKey, Buffer.from(encryptedApiToken, "base64")).toString("utf-8");
10672
+ debug8("API token decrypted");
10673
+ break;
10674
+ }
10675
+ await sleep(LOGIN_CHECK_DELAY);
10676
+ }
10677
+ if (!newApiToken) {
10678
+ loginSpinner.error({
10679
+ text: "Login timeout error"
10680
+ });
10681
+ throw new CliError();
10682
+ }
10683
+ const newGqlClient = new GQLClient({ apiKey: newApiToken, type: "apiKey" });
10684
+ const loginSuccess = await newGqlClient.validateUserToken();
10685
+ if (loginSuccess) {
10686
+ debug8(`set api token ${newApiToken}`);
10687
+ config2.set("apiToken", newApiToken);
10688
+ loginSpinner.success({
10689
+ text: `\u{1F513} Login to Mobb successful! ${typeof loginSpinner === "string" ? `Logged in as ${loginSuccess}` : ""}`
10690
+ });
10691
+ } else {
10692
+ loginSpinner.error({
10693
+ text: "Something went wrong, API token is invalid."
10694
+ });
10695
+ throw new CliError();
10696
+ }
10697
+ return newGqlClient;
10817
10698
  }
10818
10699
 
10819
- // src/features/analysis/graphql/types.ts
10700
+ // src/features/analysis/add_fix_comments_for_pr/add_fix_comments_for_pr.ts
10701
+ import Debug11 from "debug";
10702
+
10703
+ // src/features/analysis/add_fix_comments_for_pr/utils/utils.ts
10704
+ import Debug10 from "debug";
10705
+ import parseDiff from "parse-diff";
10820
10706
  import { z as z27 } from "zod";
10821
- var VulnerabilityReportIssueCodeNodeZ = z27.object({
10822
- vulnerabilityReportIssueId: z27.string(),
10823
- path: z27.string(),
10824
- startLine: z27.number(),
10825
- vulnerabilityReportIssue: z27.object({
10826
- fixId: z27.string(),
10827
- category: z27.nativeEnum(Vulnerability_Report_Issue_Category_Enum),
10828
- safeIssueType: z27.string(),
10829
- vulnerabilityReportIssueTags: z27.array(
10830
- z27.object({
10831
- tag: z27.nativeEnum(Vulnerability_Report_Issue_Tag_Enum)
10832
- })
10833
- )
10834
- })
10835
- });
10836
- var VulnerabilityReportIssueNoFixCodeNodeZ = z27.object({
10837
- vulnerabilityReportIssues: z27.array(
10838
- z27.object({
10839
- id: z27.string(),
10840
- fixId: z27.string().nullable(),
10841
- category: z27.nativeEnum(Vulnerability_Report_Issue_Category_Enum),
10842
- safeIssueType: z27.string(),
10843
- fpId: z27.string().uuid().nullable(),
10844
- codeNodes: z27.array(
10845
- z27.object({
10846
- path: z27.string(),
10847
- startLine: z27.number()
10848
- })
10849
- ),
10850
- vulnerabilityReportIssueTags: z27.array(
10851
- z27.object({
10852
- tag: z27.nativeEnum(Vulnerability_Report_Issue_Tag_Enum)
10853
- })
10854
- )
10855
- })
10856
- )
10857
- });
10858
- var GetVulByNodesMetadataZ = z27.object({
10859
- vulnerabilityReportIssueCodeNodes: z27.array(VulnerabilityReportIssueCodeNodeZ),
10860
- nonFixablePrVuls: z27.object({
10861
- aggregate: z27.object({
10862
- count: z27.number()
10863
- })
10864
- }),
10865
- fixablePrVuls: z27.object({
10866
- aggregate: z27.object({
10867
- count: z27.number()
10868
- })
10869
- }),
10870
- totalScanVulnerabilities: z27.object({
10871
- aggregate: z27.object({
10872
- count: z27.number()
10873
- })
10874
- }),
10875
- irrelevantVulnerabilityReportIssue: z27.array(
10876
- VulnerabilityReportIssueNoFixCodeNodeZ
10877
- )
10878
- });
10879
10707
 
10880
- // src/features/analysis/graphql/gql.ts
10881
- var debug13 = Debug12("mobbdev:gql");
10882
- var API_KEY_HEADER_NAME = "x-mobb-key";
10883
- var REPORT_STATE_CHECK_DELAY = 5 * 1e3;
10884
- function getProxyAgent(url) {
10885
- try {
10886
- const parsedUrl = new URL(url);
10887
- const isHttp = parsedUrl.protocol === "http:";
10888
- const isHttps = parsedUrl.protocol === "https:";
10889
- const proxy = isHttps ? HTTPS_PROXY : isHttp ? HTTP_PROXY : null;
10890
- if (proxy) {
10891
- debug13("Using proxy %s", proxy);
10892
- debug13("Proxy agent %o", proxy);
10893
- return isHttps ? new HttpsProxyAgent2(proxy) : new HttpProxyAgent(proxy);
10894
- }
10895
- } catch (err) {
10896
- debug13(`Skipping proxy for ${url}. Reason: ${err.message}`);
10897
- }
10898
- return void 0;
10708
+ // src/features/analysis/utils/by_key.ts
10709
+ function keyBy(array, keyBy2) {
10710
+ return array.reduce((acc, item) => {
10711
+ return { ...acc, [item[keyBy2]]: item };
10712
+ }, {});
10899
10713
  }
10900
- var fetchWithProxy = (url, options = {}) => {
10714
+
10715
+ // src/features/analysis/utils/send_report.ts
10716
+ import Debug8 from "debug";
10717
+ var debug9 = Debug8("mobbdev:index");
10718
+ async function sendReport({
10719
+ spinner,
10720
+ submitVulnerabilityReportVariables,
10721
+ gqlClient
10722
+ }) {
10901
10723
  try {
10902
- const agent = getProxyAgent(url.toString());
10903
- if (agent) {
10904
- return fetchOrig(url, {
10905
- ...options,
10906
- // @ts-expect-error Node-fetch doesn't type 'agent', but it's valid
10907
- agent
10908
- });
10724
+ const submitRes = await gqlClient.submitVulnerabilityReport(
10725
+ submitVulnerabilityReportVariables
10726
+ );
10727
+ if (submitRes.submitVulnerabilityReport.__typename !== "VulnerabilityReport") {
10728
+ debug9("error submit vul report %s", submitRes);
10729
+ throw new Error("\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed");
10909
10730
  }
10910
- } catch (err) {
10911
- debug13(`Skipping proxy for ${url}. Reason: ${err.message}`);
10912
- }
10913
- return fetchOrig(url, options);
10914
- };
10915
- var GQLClient = class {
10916
- constructor(args) {
10917
- __publicField(this, "_client");
10918
- __publicField(this, "_clientSdk");
10919
- __publicField(this, "_auth");
10920
- debug13(`init with ${args}`);
10921
- this._auth = args;
10922
- this._client = new GraphQLClient(API_URL, {
10923
- headers: args.type === "apiKey" ? { [API_KEY_HEADER_NAME]: args.apiKey || "" } : {
10924
- Authorization: `Bearer ${args.token}`
10731
+ spinner.update({ text: progressMassages.processingVulnerabilityReport });
10732
+ await gqlClient.subscribeToAnalysis({
10733
+ subscribeToAnalysisParams: {
10734
+ analysisId: submitRes.submitVulnerabilityReport.fixReportId
10925
10735
  },
10926
- fetch: fetchWithProxy,
10927
- requestMiddleware: (request) => {
10928
- const requestId = uuidv4();
10929
- debug13(
10930
- `sending API request with id: ${requestId} and with request: ${request.body}`
10931
- );
10932
- return {
10933
- ...request,
10934
- headers: {
10935
- ...request.headers,
10936
- "x-hasura-request-id": requestId
10937
- }
10938
- };
10939
- }
10736
+ callback: () => spinner.update({
10737
+ text: "\u2699\uFE0F Vulnerability report processed successfully"
10738
+ }),
10739
+ callbackStates: [
10740
+ "Digested" /* Digested */,
10741
+ "Finished" /* Finished */
10742
+ ],
10743
+ timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
10940
10744
  });
10941
- this._clientSdk = getSdk(this._client);
10745
+ return submitRes;
10746
+ } catch (e) {
10747
+ spinner.error({ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed" });
10748
+ throw e;
10942
10749
  }
10943
- async getUserInfo() {
10944
- const { me } = await this._clientSdk.Me();
10945
- return me;
10750
+ }
10751
+
10752
+ // src/features/analysis/utils/index.ts
10753
+ function getFromArraySafe(array) {
10754
+ return array.reduce((acc, nullableItem) => {
10755
+ if (nullableItem) {
10756
+ acc.push(nullableItem);
10757
+ }
10758
+ return acc;
10759
+ }, []);
10760
+ }
10761
+
10762
+ // src/features/analysis/add_fix_comments_for_pr/constants.ts
10763
+ var contactUsMarkdown = `For specific requests [contact us](https://content.mobb.ai/contact) and we'll do the most to answer your need quickly.`;
10764
+ var MobbIconMarkdown = `![image](${MOBB_ICON_IMG})`;
10765
+ var noVulnerabilitiesFoundTitle = `# ${MobbIconMarkdown} No security issues were found \u2705`;
10766
+ var COMMIT_FIX_SVG = `https://app.mobb.ai/gh-action/commit-button.svg`;
10767
+ var scannerToFriendlyString = {
10768
+ checkmarx: "Checkmarx",
10769
+ codeql: "CodeQL",
10770
+ fortify: "Fortify",
10771
+ snyk: "Snyk",
10772
+ sonarqube: "Sonarqube",
10773
+ semgrep: "Semgrep",
10774
+ datadog: "Datadog"
10775
+ };
10776
+
10777
+ // src/features/analysis/add_fix_comments_for_pr/utils/buildCommentBody.ts
10778
+ import Debug9 from "debug";
10779
+ import { z as z26 } from "zod";
10780
+ var debug10 = Debug9("mobbdev:handle-finished-analysis");
10781
+ var getCommitFixButton = (commitUrl) => `<a href="${commitUrl}"><img src=${COMMIT_FIX_SVG}></a>`;
10782
+ function buildFixCommentBody({
10783
+ fix,
10784
+ issueId,
10785
+ commentId,
10786
+ commentUrl,
10787
+ scanner,
10788
+ fixId,
10789
+ projectId,
10790
+ analysisId,
10791
+ organizationId,
10792
+ patch,
10793
+ irrelevantIssueWithTags
10794
+ }) {
10795
+ const isIrrelevantIssueWithTags = irrelevantIssueWithTags?.[0]?.tag;
10796
+ const commitUrl = isIrrelevantIssueWithTags ? getCommitIssueUrl({
10797
+ appBaseUrl: WEB_APP_URL,
10798
+ issueId,
10799
+ projectId,
10800
+ analysisId,
10801
+ organizationId,
10802
+ redirectUrl: commentUrl,
10803
+ commentId
10804
+ }) : getCommitUrl({
10805
+ appBaseUrl: WEB_APP_URL,
10806
+ fixId,
10807
+ projectId,
10808
+ analysisId,
10809
+ organizationId,
10810
+ redirectUrl: commentUrl,
10811
+ commentId
10812
+ });
10813
+ const fixUrl = isIrrelevantIssueWithTags ? getIssueUrlWithRedirect({
10814
+ appBaseUrl: WEB_APP_URL,
10815
+ issueId,
10816
+ projectId,
10817
+ analysisId,
10818
+ organizationId,
10819
+ redirectUrl: commentUrl,
10820
+ commentId
10821
+ }) : getFixUrlWithRedirect({
10822
+ appBaseUrl: WEB_APP_URL,
10823
+ fixId,
10824
+ projectId,
10825
+ analysisId,
10826
+ organizationId,
10827
+ redirectUrl: commentUrl,
10828
+ commentId
10829
+ });
10830
+ const issueType = getIssueTypeFriendlyString(fix.safeIssueType);
10831
+ const title = `# ${MobbIconMarkdown} ${issueType} fix is ready`;
10832
+ const validFixParseRes = z26.object({
10833
+ patchAndQuestions: PatchAndQuestionsZ,
10834
+ safeIssueLanguage: z26.nativeEnum(IssueLanguage_Enum),
10835
+ severityText: z26.nativeEnum(Vulnerability_Severity_Enum),
10836
+ safeIssueType: z26.nativeEnum(IssueType_Enum)
10837
+ }).safeParse(fix);
10838
+ if (!validFixParseRes.success) {
10839
+ debug10(
10840
+ `fix ${fixId} has custom issue type or language, therefore the commit description will not be added`,
10841
+ validFixParseRes.error
10842
+ );
10946
10843
  }
10947
- async createCliLogin(variables) {
10948
- const res = await this._clientSdk.CreateCliLogin(variables, {
10949
- // We may have outdated API key in the config storage. Avoid using it for the login request.
10950
- [API_KEY_HEADER_NAME]: ""
10951
- });
10952
- return res.insert_cli_login_one?.id || "";
10844
+ const subTitle = validFixParseRes.success ? getCommitDescription({
10845
+ issueType: validFixParseRes.data.safeIssueType,
10846
+ vendor: scannerToVulnerabilityReportVendorEnum[scanner],
10847
+ severity: validFixParseRes.data.severityText,
10848
+ guidances: getGuidances({
10849
+ questions: validFixParseRes.data.patchAndQuestions.questions.map(toQuestion),
10850
+ issueType: validFixParseRes.data.safeIssueType,
10851
+ issueLanguage: validFixParseRes.data.safeIssueLanguage,
10852
+ fixExtraContext: validFixParseRes.data.patchAndQuestions.extraContext
10853
+ }),
10854
+ irrelevantIssueWithTags
10855
+ }) : "";
10856
+ const diff = `\`\`\`diff
10857
+ ${patch}
10858
+ \`\`\``;
10859
+ const fixPageLink = `[Learn more and fine tune the fix](${fixUrl})`;
10860
+ return `${title}
10861
+ ${subTitle}
10862
+ ${diff}
10863
+ ${getCommitFixButton(
10864
+ commitUrl
10865
+ )}
10866
+ ${fixPageLink}`;
10867
+ }
10868
+ function buildIssueCommentBody({
10869
+ issueId,
10870
+ commentId,
10871
+ commentUrl,
10872
+ scanner,
10873
+ issueType,
10874
+ projectId,
10875
+ analysisId,
10876
+ organizationId,
10877
+ irrelevantIssueWithTags,
10878
+ fpDescription
10879
+ }) {
10880
+ const issueUrl = getIssueUrlWithRedirect({
10881
+ appBaseUrl: WEB_APP_URL,
10882
+ issueId,
10883
+ projectId,
10884
+ analysisId,
10885
+ organizationId,
10886
+ redirectUrl: commentUrl,
10887
+ commentId
10888
+ });
10889
+ const title = `# ${MobbIconMarkdown} Irrelevant issues were spotted - no action required \u{1F9F9}`;
10890
+ const subTitle = getCommitIssueDescription({
10891
+ issueType,
10892
+ vendor: scannerToVulnerabilityReportVendorEnum[scanner],
10893
+ irrelevantIssueWithTags,
10894
+ fpDescription
10895
+ });
10896
+ const issuePageLink = `[Learn more about this issue](${issueUrl})`;
10897
+ return `${title}
10898
+ ${subTitle}
10899
+ ${issuePageLink}`;
10900
+ }
10901
+
10902
+ // src/features/analysis/add_fix_comments_for_pr/utils/utils.ts
10903
+ var debug11 = Debug10("mobbdev:handle-finished-analysis");
10904
+ function calculateRanges(integers) {
10905
+ if (integers.length === 0) {
10906
+ return [];
10953
10907
  }
10954
- async verifyApiConnection() {
10955
- try {
10956
- await this.getUserInfo();
10957
- } catch (e) {
10958
- if (e?.toString().startsWith("FetchError")) {
10959
- debug13("verify connection failed %o", e);
10960
- return false;
10908
+ integers.sort((a, b) => a - b);
10909
+ const ranges = integers.reduce(
10910
+ (result, current, index) => {
10911
+ if (index === 0) {
10912
+ return [...result, [current, current]];
10961
10913
  }
10962
- }
10963
- return true;
10964
- }
10965
- async validateUserToken() {
10966
- await this.createCommunityUser();
10967
- let info;
10914
+ const currentRange = result[result.length - 1];
10915
+ const [_start, end] = currentRange;
10916
+ if (current === end + 1) {
10917
+ currentRange[1] = current;
10918
+ } else {
10919
+ result.push([current, current]);
10920
+ }
10921
+ return result;
10922
+ },
10923
+ []
10924
+ );
10925
+ return ranges;
10926
+ }
10927
+ function deleteAllPreviousComments({
10928
+ comments,
10929
+ scm
10930
+ }) {
10931
+ return comments.data.filter((comment) => {
10932
+ return comment.body.includes(MOBB_ICON_IMG);
10933
+ }).map((comment) => {
10968
10934
  try {
10969
- info = await this.getUserInfo();
10935
+ return scm.deleteComment({ comment_id: comment.id });
10970
10936
  } catch (e) {
10971
- debug13("verify token failed %o", e);
10972
- return false;
10973
- }
10974
- return info?.email || true;
10975
- }
10976
- async getLastOrgAndNamedProject(params) {
10977
- const me = await this.getUserInfo();
10978
- const email = me?.email;
10979
- if (!email) {
10980
- throw new Error("User email not found");
10981
- }
10982
- const { projectName, userDefinedOrganizationId } = params;
10983
- if (!projectName) {
10984
- throw new Error("Project name is required");
10985
- }
10986
- const orgAndProjectRes = await this._clientSdk.getLastOrgAndNamedProject({
10987
- email,
10988
- projectName
10989
- });
10990
- if (!orgAndProjectRes.user?.[0]?.userOrganizationsAndUserOrganizationRoles?.[0]?.organization?.id) {
10991
- throw new Error(
10992
- `The user with email:${email} is not associated with any organization`
10993
- );
10994
- }
10995
- const organization = orgAndProjectRes.user?.at(0)?.userOrganizationsAndUserOrganizationRoles.map(
10996
- (org) => org.organization
10997
- ).filter(
10998
- (org) => userDefinedOrganizationId ? org.id === userDefinedOrganizationId : true
10999
- )?.at(0);
11000
- if (!organization) {
11001
- throw new Error(
11002
- `Organization with id:${userDefinedOrganizationId} not found`
11003
- );
11004
- }
11005
- let projectId = organization?.projects?.[0]?.id;
11006
- if (!projectId) {
11007
- const createdProject = await this._clientSdk.CreateProject({
11008
- organizationId: organization.id,
11009
- projectName: projectName || "My project"
11010
- });
11011
- projectId = createdProject.createProject.projectId;
10937
+ debug11("delete comment failed %s", e);
10938
+ return Promise.resolve();
11012
10939
  }
11013
- if (!projectId) {
11014
- throw new Error("Project not found");
10940
+ });
10941
+ }
10942
+ function deleteAllPreviousGeneralPrComments(params) {
10943
+ const { generalPrComments, scm } = params;
10944
+ return generalPrComments.data.filter((comment) => {
10945
+ if (!comment.body) {
10946
+ return false;
11015
10947
  }
11016
- return {
11017
- organizationId: organization.id,
11018
- projectId
11019
- };
11020
- }
11021
- async getEncryptedApiToken(variables) {
11022
- const res = await this._clientSdk.GetEncryptedApiToken(variables, {
11023
- // We may have outdated API key in the config storage. Avoid using it for the login request.
11024
- [API_KEY_HEADER_NAME]: ""
11025
- });
11026
- return res?.cli_login_by_pk?.encryptedApiToken || null;
11027
- }
11028
- async createCommunityUser() {
10948
+ return comment.body.includes(MOBB_ICON_IMG);
10949
+ }).map((comment) => {
11029
10950
  try {
11030
- await this._clientSdk.CreateCommunityUser();
10951
+ return scm.deleteGeneralPrComment({ commentId: comment.id });
11031
10952
  } catch (e) {
11032
- debug13("create community user failed %o", e);
10953
+ debug11("delete comment failed %s", e);
10954
+ return Promise.resolve();
11033
10955
  }
10956
+ });
10957
+ }
10958
+ async function postIssueComment(params) {
10959
+ const {
10960
+ vulnerabilityReportIssueCodeNode,
10961
+ projectId,
10962
+ analysisId,
10963
+ organizationId,
10964
+ scm,
10965
+ commitSha,
10966
+ pullRequest,
10967
+ scanner,
10968
+ fpDescription
10969
+ } = params;
10970
+ const {
10971
+ path: path17,
10972
+ startLine,
10973
+ vulnerabilityReportIssue: {
10974
+ vulnerabilityReportIssueTags,
10975
+ category,
10976
+ safeIssueType
10977
+ },
10978
+ vulnerabilityReportIssueId
10979
+ } = vulnerabilityReportIssueCodeNode;
10980
+ const irrelevantIssueWithTags = mapCategoryToBucket[category] === "irrelevant" && vulnerabilityReportIssueTags?.length > 0 ? vulnerabilityReportIssueTags : [];
10981
+ const commentRes = await scm.postPrComment({
10982
+ body: `# ${MobbIconMarkdown} Your fix is ready!
10983
+ Refresh the page in order to see the changes.`,
10984
+ pull_number: pullRequest,
10985
+ commit_id: commitSha,
10986
+ path: path17,
10987
+ line: startLine
10988
+ });
10989
+ const commentId = commentRes.data.id;
10990
+ const commentBody = buildIssueCommentBody({
10991
+ issueId: vulnerabilityReportIssueId,
10992
+ issueType: safeIssueType,
10993
+ irrelevantIssueWithTags,
10994
+ commentId,
10995
+ commentUrl: commentRes.data.html_url,
10996
+ scanner,
10997
+ projectId,
10998
+ analysisId,
10999
+ organizationId,
11000
+ fpDescription
11001
+ });
11002
+ return await scm.updatePrComment({
11003
+ body: commentBody,
11004
+ comment_id: commentId
11005
+ });
11006
+ }
11007
+ async function postFixComment(params) {
11008
+ const {
11009
+ vulnerabilityReportIssueCodeNode,
11010
+ projectId,
11011
+ analysisId,
11012
+ organizationId,
11013
+ fixesById,
11014
+ scm,
11015
+ commitSha,
11016
+ pullRequest,
11017
+ scanner
11018
+ } = params;
11019
+ const {
11020
+ path: path17,
11021
+ startLine,
11022
+ vulnerabilityReportIssue: { fixId, vulnerabilityReportIssueTags, category },
11023
+ vulnerabilityReportIssueId
11024
+ } = vulnerabilityReportIssueCodeNode;
11025
+ const irrelevantIssueWithTags = mapCategoryToBucket[category] === "irrelevant" && vulnerabilityReportIssueTags?.length > 0 ? vulnerabilityReportIssueTags : [];
11026
+ const fix = fixesById[fixId];
11027
+ if (!fix || fix.patchAndQuestions.__typename !== "FixData") {
11028
+ throw new Error(`fix ${fixId} not found`);
11034
11029
  }
11035
- async updateScmToken(args) {
11036
- const { scmType, url, token, org, refreshToken } = args;
11037
- const updateScmTokenResult = await this._clientSdk.updateScmToken({
11038
- scmType,
11039
- url,
11040
- token,
11041
- org,
11042
- refreshToken
11043
- });
11044
- return updateScmTokenResult;
11045
- }
11046
- async uploadS3BucketInfo() {
11047
- const uploadS3BucketInfoResult = await this._clientSdk.uploadS3BucketInfo({
11048
- fileName: REPORT_DEFAULT_FILE_NAME
11030
+ const {
11031
+ patchAndQuestions: { patch }
11032
+ } = fix;
11033
+ const commentRes = await scm.postPrComment({
11034
+ body: `# ${MobbIconMarkdown} Your fix is ready!
11035
+ Refresh the page in order to see the changes.`,
11036
+ pull_number: pullRequest,
11037
+ commit_id: commitSha,
11038
+ path: path17,
11039
+ line: startLine
11040
+ });
11041
+ const commentId = commentRes.data.id;
11042
+ const commentBody = buildFixCommentBody({
11043
+ fix,
11044
+ issueId: vulnerabilityReportIssueId,
11045
+ irrelevantIssueWithTags,
11046
+ commentId,
11047
+ commentUrl: commentRes.data.html_url,
11048
+ scanner,
11049
+ fixId,
11050
+ projectId,
11051
+ analysisId,
11052
+ organizationId,
11053
+ patch
11054
+ });
11055
+ return await scm.updatePrComment({
11056
+ body: commentBody,
11057
+ comment_id: commentId
11058
+ });
11059
+ }
11060
+ async function getRelevantVulenrabilitiesFromDiff(params) {
11061
+ const { gqlClient, diff, vulnerabilityReportId } = params;
11062
+ const parsedDiff = parseDiff(diff);
11063
+ const fileHunks = parsedDiff.map((file) => {
11064
+ const fileNumbers = file.chunks.flatMap((chunk) => chunk.changes).filter((change) => change.type === "add").map((_change) => {
11065
+ const change = _change;
11066
+ return change.ln;
11049
11067
  });
11050
- return uploadS3BucketInfoResult;
11051
- }
11052
- async getVulByNodesMetadata({
11053
- hunks,
11068
+ const lineAddedRanges = calculateRanges(fileNumbers);
11069
+ const fileFilter = {
11070
+ path: z27.string().parse(file.to),
11071
+ ranges: lineAddedRanges.map(([startLine, endLine]) => ({
11072
+ endLine,
11073
+ startLine
11074
+ }))
11075
+ };
11076
+ return fileFilter;
11077
+ });
11078
+ return gqlClient.getVulByNodesMetadata({
11079
+ hunks: fileHunks,
11054
11080
  vulnerabilityReportId
11055
- }) {
11056
- const filters = hunks.map((hunk) => {
11057
- const filter = {
11058
- path: { _eq: hunk.path },
11059
- _or: hunk.ranges.flatMap(({ endLine, startLine }) => {
11060
- return [
11061
- { startLine: { _gte: startLine, _lte: endLine } },
11062
- { endLine: { _gte: startLine, _lte: endLine } }
11063
- ];
11064
- })
11065
- };
11066
- return filter;
11081
+ });
11082
+ }
11083
+ async function getFixesData(params) {
11084
+ const { gqlClient, fixesId } = params;
11085
+ const { fixes } = await gqlClient.getFixes(fixesId);
11086
+ return keyBy(fixes, "id");
11087
+ }
11088
+ async function postAnalysisInsightComment(params) {
11089
+ const { prVulenrabilities, pullRequest, scanner, scm } = params;
11090
+ const scanerString = scannerToFriendlyString[scanner];
11091
+ const {
11092
+ totalPrVulnerabilities,
11093
+ vulnerabilitiesOutsidePr,
11094
+ fixablePrVuls,
11095
+ nonFixablePrVuls
11096
+ } = prVulenrabilities;
11097
+ debug11({
11098
+ fixablePrVuls,
11099
+ nonFixablePrVuls,
11100
+ vulnerabilitiesOutsidePr,
11101
+ totalPrVulnerabilities
11102
+ });
11103
+ if (totalPrVulnerabilities === 0 && vulnerabilitiesOutsidePr === 0) {
11104
+ const body = `Awesome! No vulnerabilities were found by **${scanerString}**`;
11105
+ const noVulsFoundComment = `${noVulnerabilitiesFoundTitle}
11106
+ ${body}`;
11107
+ await scm.postGeneralPrComment({
11108
+ body: noVulsFoundComment,
11109
+ prNumber: pullRequest
11067
11110
  });
11068
- const getVulByNodesMetadataRes = await this._clientSdk.getVulByNodesMetadata({
11069
- filters: { _or: filters },
11070
- vulnerabilityReportId
11111
+ return;
11112
+ }
11113
+ if (totalPrVulnerabilities === 0 && vulnerabilitiesOutsidePr > 0) {
11114
+ const body = `Awesome! No vulnerabilities were found by **${scanerString}** in the changes made as part of this PR.`;
11115
+ const body2 = `Please notice there are issues in this repo that are unrelated to this PR.`;
11116
+ const noVulsFoundComment = `${noVulnerabilitiesFoundTitle}
11117
+ ${body}
11118
+ ${body2}`;
11119
+ await scm.postGeneralPrComment({
11120
+ body: noVulsFoundComment,
11121
+ prNumber: pullRequest
11071
11122
  });
11072
- const parsedGetVulByNodesMetadataRes = GetVulByNodesMetadataZ.parse(
11073
- getVulByNodesMetadataRes
11074
- );
11075
- const uniqueVulByNodesMetadata = parsedGetVulByNodesMetadataRes.vulnerabilityReportIssueCodeNodes.reduce((acc, vulnerabilityReportIssueCodeNode) => {
11076
- if (acc[vulnerabilityReportIssueCodeNode.vulnerabilityReportIssueId]) {
11077
- return acc;
11078
- }
11079
- return {
11080
- ...acc,
11081
- [vulnerabilityReportIssueCodeNode.vulnerabilityReportIssueId]: vulnerabilityReportIssueCodeNode
11082
- };
11083
- }, {});
11084
- const nonFixablePrVuls = parsedGetVulByNodesMetadataRes.nonFixablePrVuls.aggregate.count;
11085
- const fixablePrVuls = parsedGetVulByNodesMetadataRes.fixablePrVuls.aggregate.count;
11086
- const totalScanVulnerabilities = parsedGetVulByNodesMetadataRes.totalScanVulnerabilities.aggregate.count;
11087
- const vulnerabilitiesOutsidePr = totalScanVulnerabilities - nonFixablePrVuls - fixablePrVuls;
11088
- const totalPrVulnerabilities = nonFixablePrVuls + fixablePrVuls;
11089
- const irrelevantVulnerabilityReportIssues = parsedGetVulByNodesMetadataRes.irrelevantVulnerabilityReportIssue?.[0]?.vulnerabilityReportIssues ?? [];
11090
- return {
11091
- vulnerabilityReportIssueCodeNodes: Object.values(
11092
- uniqueVulByNodesMetadata
11093
- ),
11094
- nonFixablePrVuls,
11095
- fixablePrVuls,
11096
- totalScanVulnerabilities,
11097
- vulnerabilitiesOutsidePr,
11098
- totalPrVulnerabilities,
11099
- irrelevantVulnerabilityReportIssues
11100
- };
11123
+ return;
11101
11124
  }
11102
- async digestVulnerabilityReport({
11103
- fixReportId,
11104
- projectId,
11105
- scanSource,
11106
- repoUrl,
11107
- reference,
11108
- sha,
11109
- shouldScan
11110
- }) {
11111
- const res = await this._clientSdk.DigestVulnerabilityReport({
11112
- fixReportId,
11113
- vulnerabilityReportFileName: shouldScan ? void 0 : REPORT_DEFAULT_FILE_NAME,
11114
- projectId,
11115
- scanSource,
11116
- repoUrl,
11117
- reference,
11118
- sha
11125
+ if (fixablePrVuls === 0 && nonFixablePrVuls > 0) {
11126
+ const title = `# ${MobbIconMarkdown} We couldn't fix the issues detected by **${scanerString}**`;
11127
+ const body = `Mobb Fixer gets better and better every day, but unfortunately your current issues aren't supported yet.`;
11128
+ const noFixableVulsComment = `${title}
11129
+ ${body}
11130
+ ${contactUsMarkdown}`;
11131
+ await scm.postGeneralPrComment({
11132
+ body: noFixableVulsComment,
11133
+ prNumber: pullRequest
11119
11134
  });
11120
- if (res.digestVulnerabilityReport.__typename !== "VulnerabilityReport") {
11121
- throw new Error("Digesting vulnerability report failed");
11122
- }
11123
- return res.digestVulnerabilityReport;
11135
+ return;
11124
11136
  }
11125
- async submitVulnerabilityReport(params) {
11126
- const {
11127
- fixReportId,
11128
- repoUrl,
11129
- reference,
11130
- projectId,
11131
- sha,
11132
- experimentalEnabled,
11133
- vulnerabilityReportFileName,
11134
- pullRequest
11135
- } = params;
11136
- return await this._clientSdk.SubmitVulnerabilityReport({
11137
- fixReportId,
11138
- repoUrl,
11139
- reference,
11140
- vulnerabilityReportFileName,
11141
- projectId,
11142
- pullRequest,
11143
- sha: sha || "",
11144
- experimentalEnabled: !!experimentalEnabled,
11145
- scanSource: params.scanSource,
11146
- scanContext: ScanContext.BUGSY
11137
+ if (fixablePrVuls < nonFixablePrVuls && fixablePrVuls > 0) {
11138
+ const title = `# ${MobbIconMarkdown} We couldn't fix some of the issues detected by **${scanerString}**`;
11139
+ const body = "Mobb Fixer gets better and better every day, but unfortunately your current issues aren't supported yet.";
11140
+ const fixableVulsComment = `${title}
11141
+ ${body}
11142
+ ${contactUsMarkdown}`;
11143
+ await scm.postGeneralPrComment({
11144
+ body: fixableVulsComment,
11145
+ prNumber: pullRequest
11147
11146
  });
11147
+ return;
11148
11148
  }
11149
- async getFixReportState(fixReportId) {
11150
- const res = await this._clientSdk.FixReportState({ id: fixReportId });
11151
- return res?.fixReport_by_pk?.state || "Created" /* Created */;
11149
+ }
11150
+
11151
+ // src/features/analysis/add_fix_comments_for_pr/add_fix_comments_for_pr.ts
11152
+ var debug12 = Debug11("mobbdev:handle-finished-analysis");
11153
+ async function addFixCommentsForPr({
11154
+ analysisId,
11155
+ scm: _scm,
11156
+ gqlClient,
11157
+ scanner
11158
+ }) {
11159
+ if (!(_scm instanceof GithubSCMLib)) {
11160
+ return;
11152
11161
  }
11153
- async waitFixReportInit(fixReportId, includeDigested = false) {
11154
- const FINAL_STATES = [
11155
- "Finished" /* Finished */,
11156
- "Failed" /* Failed */
11157
- ];
11158
- let lastState = "Created" /* Created */;
11159
- let attempts = 100;
11160
- if (includeDigested) {
11161
- FINAL_STATES.push("Digested" /* Digested */);
11162
+ const scm = _scm;
11163
+ const getAnalysisRes = await gqlClient.getAnalysis(analysisId);
11164
+ debug12("getAnalysis %o", getAnalysisRes);
11165
+ const {
11166
+ vulnerabilityReport: {
11167
+ projectId,
11168
+ project: {
11169
+ organizationId,
11170
+ organization: { ghFixerNoFixComments }
11171
+ }
11162
11172
  }
11163
- do {
11164
- await sleep(REPORT_STATE_CHECK_DELAY);
11165
- lastState = await this.getFixReportState(fixReportId);
11166
- } while (!FINAL_STATES.includes(
11167
- lastState
11168
- // wait for final the state of the fix report
11169
- ) && attempts-- > 0);
11170
- return lastState;
11171
- }
11172
- async getVulnerabilityReportPaths(vulnerabilityReportId) {
11173
- const res = await this._clientSdk.GetVulnerabilityReportPaths({
11174
- vulnerabilityReportId
11175
- });
11176
- return res.vulnerability_report_path.map((p) => p.path);
11173
+ } = getAnalysisRes;
11174
+ if (!getAnalysisRes.repo?.commitSha || !getAnalysisRes.repo.pullRequest) {
11175
+ throw new Error("repo not found");
11177
11176
  }
11178
- async subscribeToAnalysis(params) {
11179
- const { callbackStates } = params;
11180
- return subscribe(
11181
- GetAnalysisSubscriptionDocument,
11182
- params.subscribeToAnalysisParams,
11183
- async (resolve, reject, data) => {
11184
- if (!data.analysis?.state || data.analysis?.state === "Failed" /* Failed */) {
11185
- const errorMessage = data.analysis?.failReason || `Analysis failed with id: ${data.analysis?.id}`;
11186
- reject(
11187
- new ReportDigestError(errorMessage, data.analysis?.failReason ?? "")
11177
+ const { commitSha, pullRequest } = getAnalysisRes.repo;
11178
+ const diff = await scm.getPrDiff({ pull_number: pullRequest });
11179
+ const prVulenrabilities = await getRelevantVulenrabilitiesFromDiff({
11180
+ diff,
11181
+ gqlClient,
11182
+ vulnerabilityReportId: getAnalysisRes.vulnerabilityReportId
11183
+ });
11184
+ const {
11185
+ vulnerabilityReportIssueCodeNodes,
11186
+ irrelevantVulnerabilityReportIssues
11187
+ } = prVulenrabilities;
11188
+ const fixesId = vulnerabilityReportIssueCodeNodes.map(
11189
+ ({ vulnerabilityReportIssue: { fixId } }) => fixId
11190
+ );
11191
+ const fixesById = await getFixesData({ fixesId, gqlClient });
11192
+ const [comments, generalPrComments] = await Promise.all([
11193
+ scm.getPrComments({ pull_number: pullRequest }),
11194
+ scm.getGeneralPrComments({ prNumber: pullRequest })
11195
+ ]);
11196
+ await Promise.all([
11197
+ ...deleteAllPreviousComments({ comments, scm }),
11198
+ ...deleteAllPreviousGeneralPrComments({ generalPrComments, scm })
11199
+ ]);
11200
+ await Promise.all(
11201
+ [
11202
+ ...prVulenrabilities.vulnerabilityReportIssueCodeNodes.map(
11203
+ (vulnerabilityReportIssueCodeNode) => {
11204
+ return postFixComment({
11205
+ vulnerabilityReportIssueCodeNode,
11206
+ projectId,
11207
+ analysisId,
11208
+ organizationId,
11209
+ fixesById,
11210
+ scm,
11211
+ pullRequest,
11212
+ scanner,
11213
+ commitSha
11214
+ });
11215
+ }
11216
+ ),
11217
+ ...irrelevantVulnerabilityReportIssues.map(
11218
+ async (vulnerabilityReportIssue) => {
11219
+ let fpDescription = null;
11220
+ if (vulnerabilityReportIssue.fpId) {
11221
+ const fpRes = await gqlClient.getFalsePositive({
11222
+ fpId: vulnerabilityReportIssue.fpId
11223
+ });
11224
+ const parsedFpRes = await FalsePositivePartsZ.parseAsync(
11225
+ fpRes?.getFalsePositive
11226
+ );
11227
+ const { description, contextString } = getParsedFalsePositiveMessage(parsedFpRes);
11228
+ fpDescription = contextString ? `${description}
11229
+
11230
+ ${contextString}` : description;
11231
+ }
11232
+ return await Promise.all(
11233
+ vulnerabilityReportIssue.codeNodes.map(
11234
+ async (vulnerabilityReportIssueCodeNode) => {
11235
+ return await postIssueComment({
11236
+ vulnerabilityReportIssueCodeNode: {
11237
+ path: vulnerabilityReportIssueCodeNode.path,
11238
+ startLine: vulnerabilityReportIssueCodeNode.startLine,
11239
+ vulnerabilityReportIssue: {
11240
+ fixId: "",
11241
+ safeIssueType: vulnerabilityReportIssue.safeIssueType,
11242
+ vulnerabilityReportIssueTags: vulnerabilityReportIssue.vulnerabilityReportIssueTags,
11243
+ category: vulnerabilityReportIssue.category
11244
+ },
11245
+ vulnerabilityReportIssueId: vulnerabilityReportIssue.id
11246
+ },
11247
+ projectId,
11248
+ analysisId,
11249
+ organizationId,
11250
+ fixesById,
11251
+ scm,
11252
+ pullRequest,
11253
+ scanner,
11254
+ commitSha,
11255
+ fpDescription
11256
+ });
11257
+ }
11258
+ )
11188
11259
  );
11260
+ }
11261
+ ),
11262
+ !ghFixerNoFixComments && postAnalysisInsightComment({
11263
+ prVulenrabilities,
11264
+ pullRequest,
11265
+ scanner,
11266
+ scm
11267
+ })
11268
+ ].filter(Boolean)
11269
+ );
11270
+ }
11271
+
11272
+ // src/features/analysis/auto_pr_handler.ts
11273
+ import Debug12 from "debug";
11274
+ var debug13 = Debug12("mobbdev:handleAutoPr");
11275
+ async function handleAutoPr(params) {
11276
+ const {
11277
+ gqlClient,
11278
+ analysisId,
11279
+ commitDirectly,
11280
+ prId,
11281
+ createSpinner: createSpinner5,
11282
+ createOnePr
11283
+ } = params;
11284
+ const createAutoPrSpinner = createSpinner5(
11285
+ "\u{1F504} Waiting for the analysis to finish before initiating automatic pull request creation"
11286
+ ).start();
11287
+ return await gqlClient.subscribeToAnalysis({
11288
+ subscribeToAnalysisParams: {
11289
+ analysisId
11290
+ },
11291
+ callback: async (analysisId2) => {
11292
+ const autoPrAnalysisRes = await gqlClient.autoPrAnalysis({
11293
+ analysisId: analysisId2,
11294
+ commitDirectly,
11295
+ prId,
11296
+ prStrategy: createOnePr ? "CONDENSE" /* Condense */ : "SPREAD" /* Spread */
11297
+ });
11298
+ debug13("auto pr analysis res %o", autoPrAnalysisRes);
11299
+ if (autoPrAnalysisRes.autoPrAnalysis?.__typename === "AutoPrError") {
11300
+ createAutoPrSpinner.error({
11301
+ text: `\u{1F504} Automatic pull request failed - ${autoPrAnalysisRes.autoPrAnalysis.error}`
11302
+ });
11303
+ return;
11304
+ }
11305
+ if (autoPrAnalysisRes.autoPrAnalysis?.__typename === "AutoPrSuccess") {
11306
+ const { appliedAutoPrIssueTypes } = autoPrAnalysisRes.autoPrAnalysis;
11307
+ if (appliedAutoPrIssueTypes.length === 0) {
11308
+ createAutoPrSpinner.success({
11309
+ text: "\u{1F504} Automatic pull request did not find any new fixes to open a pull request for"
11310
+ });
11189
11311
  return;
11190
11312
  }
11191
- if (callbackStates.includes(data.analysis?.state)) {
11192
- await params.callback(data.analysis.id);
11193
- resolve(data);
11194
- }
11195
- },
11196
- this._auth.type === "apiKey" ? {
11197
- apiKey: this._auth.apiKey,
11198
- type: "apiKey",
11199
- timeoutInMs: params.timeoutInMs
11200
- } : {
11201
- token: this._auth.token,
11202
- type: "token",
11203
- timeoutInMs: params.timeoutInMs
11313
+ createAutoPrSpinner.success({
11314
+ text: `\u{1F504} Automatic pull request creation initiated successfully for the following issue types: ${appliedAutoPrIssueTypes}`
11315
+ });
11204
11316
  }
11205
- );
11206
- }
11207
- async getFixReportsByRepoUrl({ repoUrl }) {
11208
- const res = await this._clientSdk.GetFixReportsByRepoUrl({
11209
- repoUrl
11210
- });
11211
- return res;
11212
- }
11213
- async getAnalysis(analysisId) {
11214
- const res = await this._clientSdk.getAnalysis({
11215
- analysisId
11216
- });
11217
- if (!res.analysis) {
11218
- throw new Error(`Analysis not found: ${analysisId}`);
11317
+ },
11318
+ callbackStates: ["Finished" /* Finished */]
11319
+ });
11320
+ }
11321
+
11322
+ // src/features/analysis/git.ts
11323
+ init_GitService();
11324
+ import Debug13 from "debug";
11325
+ var debug14 = Debug13("mobbdev:git");
11326
+ async function getGitInfo(srcDirPath) {
11327
+ debug14("getting git info for %s", srcDirPath);
11328
+ const gitService = new GitService(srcDirPath);
11329
+ try {
11330
+ const validationResult = await gitService.validateRepository();
11331
+ if (!validationResult.isValid) {
11332
+ debug14("folder is not a git repo");
11333
+ return {
11334
+ success: false,
11335
+ hash: void 0,
11336
+ reference: void 0,
11337
+ repoUrl: void 0
11338
+ };
11219
11339
  }
11220
- return res.analysis;
11221
- }
11222
- async autoPrAnalysis({
11223
- analysisId,
11224
- commitDirectly,
11225
- prId,
11226
- prStrategy
11227
- }) {
11228
- return this._clientSdk.autoPrAnalysis({
11229
- analysisId,
11230
- commitDirectly,
11231
- prId,
11232
- prStrategy
11233
- });
11234
- }
11235
- async getFixes(fixIds) {
11236
- const res = await this._clientSdk.getFixes({
11237
- filters: { id: { _in: fixIds } }
11238
- });
11239
- return res;
11240
- }
11241
- async validateRepoUrl(args) {
11242
- return this._clientSdk.validateRepoUrl(args);
11243
- }
11244
- async getReferenceData(args) {
11245
- return this._clientSdk.gitReference(args);
11246
- }
11247
- async getFalsePositive(args) {
11248
- return this._clientSdk.getFalsePositive(args);
11340
+ const gitInfo2 = await gitService.getGitInfo();
11341
+ return {
11342
+ success: true,
11343
+ ...gitInfo2
11344
+ };
11345
+ } catch (e) {
11346
+ if (e instanceof Error) {
11347
+ debug14("failed to run git %o", e);
11348
+ if (e.message.includes(" spawn ")) {
11349
+ debug14("git cli not installed");
11350
+ } else {
11351
+ throw e;
11352
+ }
11353
+ }
11354
+ throw e;
11249
11355
  }
11250
- };
11356
+ }
11251
11357
 
11252
11358
  // src/features/analysis/pack.ts
11253
11359
  init_configs();
11254
11360
  import fs8 from "fs";
11255
11361
  import path7 from "path";
11256
11362
  import AdmZip from "adm-zip";
11257
- import Debug13 from "debug";
11363
+ import Debug14 from "debug";
11258
11364
  import { globby } from "globby";
11259
11365
  import { isBinary as isBinary2 } from "istextorbinary";
11260
11366
  import { simpleGit as simpleGit2 } from "simple-git";
11261
11367
  import { parseStringPromise } from "xml2js";
11262
11368
  import { z as z28 } from "zod";
11263
- var debug14 = Debug13("mobbdev:pack");
11369
+ var debug15 = Debug14("mobbdev:pack");
11264
11370
  var FPR_SOURCE_CODE_FILE_MAPPING_SCHEMA = z28.object({
11265
11371
  properties: z28.object({
11266
11372
  entry: z28.array(
@@ -11282,7 +11388,7 @@ function getManifestFilesSuffixes() {
11282
11388
  return ["package.json", "pom.xml"];
11283
11389
  }
11284
11390
  async function pack(srcDirPath, vulnFiles, isIncludeAllFiles = false) {
11285
- debug14("pack folder %s", srcDirPath);
11391
+ debug15("pack folder %s", srcDirPath);
11286
11392
  let git = void 0;
11287
11393
  try {
11288
11394
  git = simpleGit2({
@@ -11292,13 +11398,13 @@ async function pack(srcDirPath, vulnFiles, isIncludeAllFiles = false) {
11292
11398
  });
11293
11399
  await git.status();
11294
11400
  } catch (e) {
11295
- debug14("failed to run git %o", e);
11401
+ debug15("failed to run git %o", e);
11296
11402
  git = void 0;
11297
11403
  if (e instanceof Error) {
11298
11404
  if (e.message.includes(" spawn ")) {
11299
- debug14("git cli not installed");
11405
+ debug15("git cli not installed");
11300
11406
  } else if (e.message.includes("not a git repository")) {
11301
- debug14("folder is not a git repo");
11407
+ debug15("folder is not a git repo");
11302
11408
  } else {
11303
11409
  throw e;
11304
11410
  }
@@ -11313,9 +11419,9 @@ async function pack(srcDirPath, vulnFiles, isIncludeAllFiles = false) {
11313
11419
  followSymbolicLinks: false,
11314
11420
  dot: true
11315
11421
  });
11316
- debug14("files found %d", filepaths.length);
11422
+ debug15("files found %d", filepaths.length);
11317
11423
  const zip = new AdmZip();
11318
- debug14("compressing files");
11424
+ debug15("compressing files");
11319
11425
  for (const filepath of filepaths) {
11320
11426
  const absFilepath = path7.join(srcDirPath, filepath.toString());
11321
11427
  if (!isIncludeAllFiles) {
@@ -11324,26 +11430,26 @@ async function pack(srcDirPath, vulnFiles, isIncludeAllFiles = false) {
11324
11430
  absFilepath.toString().replaceAll(path7.win32.sep, path7.posix.sep),
11325
11431
  vulnFiles
11326
11432
  )) {
11327
- debug14("ignoring %s because it is not a vulnerability file", filepath);
11433
+ debug15("ignoring %s because it is not a vulnerability file", filepath);
11328
11434
  continue;
11329
11435
  }
11330
11436
  }
11331
11437
  if (fs8.lstatSync(absFilepath).size > MCP_MAX_FILE_SIZE) {
11332
- debug14("ignoring %s because the size is > 5MB", filepath);
11438
+ debug15("ignoring %s because the size is > 5MB", filepath);
11333
11439
  continue;
11334
11440
  }
11335
11441
  const data = git ? await git.showBuffer([`HEAD:./${filepath}`]) : fs8.readFileSync(absFilepath);
11336
11442
  if (isBinary2(null, data)) {
11337
- debug14("ignoring %s because is seems to be a binary file", filepath);
11443
+ debug15("ignoring %s because is seems to be a binary file", filepath);
11338
11444
  continue;
11339
11445
  }
11340
11446
  zip.addFile(filepath.toString(), data);
11341
11447
  }
11342
- debug14("get zip file buffer");
11448
+ debug15("get zip file buffer");
11343
11449
  return zip.toBuffer();
11344
11450
  }
11345
11451
  async function repackFpr(fprPath) {
11346
- debug14("repack fpr file %s", fprPath);
11452
+ debug15("repack fpr file %s", fprPath);
11347
11453
  const zipIn = new AdmZip(fprPath);
11348
11454
  const zipOut = new AdmZip();
11349
11455
  const mappingXML = zipIn.readAsText("src-archive/index.xml", "utf-8");
@@ -11358,7 +11464,7 @@ async function repackFpr(fprPath) {
11358
11464
  zipOut.addFile(realPath, buf);
11359
11465
  }
11360
11466
  }
11361
- debug14("get repacked zip file buffer");
11467
+ debug15("get repacked zip file buffer");
11362
11468
  return zipOut.toBuffer();
11363
11469
  }
11364
11470
 
@@ -11428,8 +11534,8 @@ async function snykArticlePrompt() {
11428
11534
 
11429
11535
  // src/features/analysis/scanners/checkmarx.ts
11430
11536
  import { createRequire } from "module";
11431
- import chalk3 from "chalk";
11432
- import Debug15 from "debug";
11537
+ import chalk4 from "chalk";
11538
+ import Debug16 from "debug";
11433
11539
  import { existsSync } from "fs";
11434
11540
  import { createSpinner as createSpinner2 } from "nanospinner";
11435
11541
  import { type } from "os";
@@ -11441,7 +11547,7 @@ var cxOperatingSystemSupportMessage = `Your operating system does not support ch
11441
11547
 
11442
11548
  // src/utils/child_process.ts
11443
11549
  import cp from "child_process";
11444
- import Debug14 from "debug";
11550
+ import Debug15 from "debug";
11445
11551
  import * as process2 from "process";
11446
11552
  function createFork({ args, processPath, name }, options) {
11447
11553
  const child = cp.fork(processPath, args, {
@@ -11459,7 +11565,7 @@ function createSpawn({ args, processPath, name, cwd }, options) {
11459
11565
  return createChildProcess({ childProcess: child, name }, options);
11460
11566
  }
11461
11567
  function createChildProcess({ childProcess, name }, options) {
11462
- const debug21 = Debug14(`mobbdev:${name}`);
11568
+ const debug21 = Debug15(`mobbdev:${name}`);
11463
11569
  const { display } = options;
11464
11570
  return new Promise((resolve, reject) => {
11465
11571
  let out = "";
@@ -11489,7 +11595,7 @@ function createChildProcess({ childProcess, name }, options) {
11489
11595
  }
11490
11596
 
11491
11597
  // src/features/analysis/scanners/checkmarx.ts
11492
- var debug15 = Debug15("mobbdev:checkmarx");
11598
+ var debug16 = Debug16("mobbdev:checkmarx");
11493
11599
  var moduleUrl;
11494
11600
  if (typeof __filename !== "undefined") {
11495
11601
  moduleUrl = __filename;
@@ -11548,14 +11654,14 @@ function validateCheckmarxInstallation() {
11548
11654
  existsSync(getCheckmarxPath());
11549
11655
  }
11550
11656
  async function forkCheckmarx(args, { display }) {
11551
- debug15("fork checkmarx with args %o %s", args.join(" "), display);
11657
+ debug16("fork checkmarx with args %o %s", args.join(" "), display);
11552
11658
  return createSpawn(
11553
11659
  { args, processPath: getCheckmarxPath(), name: "checkmarx" },
11554
11660
  { display }
11555
11661
  );
11556
11662
  }
11557
11663
  async function getCheckmarxReport({ reportPath, repositoryRoot, branch, projectName }, { skipPrompts = false }) {
11558
- debug15("get checkmarx report start %s %s", reportPath, repositoryRoot);
11664
+ debug16("get checkmarx report start %s %s", reportPath, repositoryRoot);
11559
11665
  const { code: loginCode } = await forkCheckmarx(VALIDATE_COMMAND, {
11560
11666
  display: false
11561
11667
  });
@@ -11594,7 +11700,7 @@ async function throwCheckmarxConfigError() {
11594
11700
  await createSpinner2("\u{1F513} Checkmarx is not configued correctly").start().error();
11595
11701
  throw new CliError(
11596
11702
  `Checkmarx is not configued correctly
11597
- you can configure it by using the ${chalk3.bold(
11703
+ you can configure it by using the ${chalk4.bold(
11598
11704
  "cx configure"
11599
11705
  )} command`
11600
11706
  );
@@ -11602,8 +11708,8 @@ async function throwCheckmarxConfigError() {
11602
11708
  async function validateCheckamxCredentials() {
11603
11709
  console.log(`
11604
11710
  Here's a suggestion for checkmarx configuation:
11605
- ${chalk3.bold("AST Base URI:")} https://ast.checkmarx.net
11606
- ${chalk3.bold("AST Base Auth URI (IAM):")} https://iam.checkmarx.net
11711
+ ${chalk4.bold("AST Base URI:")} https://ast.checkmarx.net
11712
+ ${chalk4.bold("AST Base Auth URI (IAM):")} https://iam.checkmarx.net
11607
11713
  `);
11608
11714
  await forkCheckmarx(CONFIGURE_COMMAND, { display: true });
11609
11715
  const { code: loginCode } = await forkCheckmarx(VALIDATE_COMMAND, {
@@ -11622,11 +11728,11 @@ async function validateCheckamxCredentials() {
11622
11728
 
11623
11729
  // src/features/analysis/scanners/snyk.ts
11624
11730
  import { createRequire as createRequire2 } from "module";
11625
- import chalk4 from "chalk";
11626
- import Debug16 from "debug";
11731
+ import chalk5 from "chalk";
11732
+ import Debug17 from "debug";
11627
11733
  import { createSpinner as createSpinner3 } from "nanospinner";
11628
- import open from "open";
11629
- var debug16 = Debug16("mobbdev:snyk");
11734
+ import open2 from "open";
11735
+ var debug17 = Debug17("mobbdev:snyk");
11630
11736
  var moduleUrl2;
11631
11737
  if (typeof __filename !== "undefined") {
11632
11738
  moduleUrl2 = __filename;
@@ -11648,15 +11754,15 @@ if (typeof __filename !== "undefined") {
11648
11754
  var costumeRequire2 = createRequire2(moduleUrl2);
11649
11755
  var SNYK_PATH = costumeRequire2.resolve("snyk/bin/snyk");
11650
11756
  var SNYK_ARTICLE_URL = "https://docs.snyk.io/scan-using-snyk/snyk-code/configure-snyk-code#enable-snyk-code";
11651
- debug16("snyk executable path %s", SNYK_PATH);
11757
+ debug17("snyk executable path %s", SNYK_PATH);
11652
11758
  async function forkSnyk(args, { display }) {
11653
- debug16("fork snyk with args %o %s", args, display);
11759
+ debug17("fork snyk with args %o %s", args, display);
11654
11760
  return createFork({ args, processPath: SNYK_PATH, name: "snyk" }, { display });
11655
11761
  }
11656
11762
  async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
11657
- debug16("get snyk report start %s %s", reportPath, repoRoot);
11658
- const config4 = await forkSnyk(["config"], { display: false });
11659
- const { message: configMessage } = config4;
11763
+ debug17("get snyk report start %s %s", reportPath, repoRoot);
11764
+ const config6 = await forkSnyk(["config"], { display: false });
11765
+ const { message: configMessage } = config6;
11660
11766
  if (!configMessage.includes("api: ")) {
11661
11767
  const snykLoginSpinner = createSpinner3().start();
11662
11768
  if (!skipPrompts) {
@@ -11668,7 +11774,7 @@ async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
11668
11774
  snykLoginSpinner.update({
11669
11775
  text: "\u{1F513} Waiting for Snyk login to complete"
11670
11776
  });
11671
- debug16("no token in the config %s", config4);
11777
+ debug17("no token in the config %s", config6);
11672
11778
  await forkSnyk(["auth"], { display: true });
11673
11779
  snykLoginSpinner.success({ text: "\u{1F513} Login to Snyk Successful" });
11674
11780
  }
@@ -11678,16 +11784,16 @@ async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
11678
11784
  { display: true }
11679
11785
  );
11680
11786
  if (scanOutput.includes("Snyk Code is not supported for org")) {
11681
- debug16("snyk code is not enabled %s", scanOutput);
11787
+ debug17("snyk code is not enabled %s", scanOutput);
11682
11788
  snykSpinner.error({ text: "\u{1F50D} Snyk configuration needed" });
11683
11789
  const answer = await snykArticlePrompt();
11684
- debug16("answer %s", answer);
11790
+ debug17("answer %s", answer);
11685
11791
  if (answer) {
11686
- debug16("opening the browser");
11687
- await open(SNYK_ARTICLE_URL);
11792
+ debug17("opening the browser");
11793
+ await open2(SNYK_ARTICLE_URL);
11688
11794
  }
11689
11795
  console.log(
11690
- chalk4.bgBlue(
11796
+ chalk5.bgBlue(
11691
11797
  "\nPlease enable Snyk Code in your Snyk account and try again."
11692
11798
  )
11693
11799
  );
@@ -11698,9 +11804,9 @@ async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
11698
11804
  }
11699
11805
 
11700
11806
  // src/features/analysis/upload-file.ts
11701
- import Debug17 from "debug";
11807
+ import Debug18 from "debug";
11702
11808
  import fetch3, { File, fileFrom, FormData } from "node-fetch";
11703
- var debug17 = Debug17("mobbdev:upload-file");
11809
+ var debug18 = Debug18("mobbdev:upload-file");
11704
11810
  async function uploadFile({
11705
11811
  file,
11706
11812
  url,
@@ -11713,9 +11819,9 @@ async function uploadFile({
11713
11819
  logInfo2(`FileUpload: upload file start ${url}`);
11714
11820
  logInfo2(`FileUpload: upload fields`, uploadFields);
11715
11821
  logInfo2(`FileUpload: upload key ${uploadKey}`);
11716
- debug17("upload file start %s", url);
11717
- debug17("upload fields %o", uploadFields);
11718
- debug17("upload key %s", uploadKey);
11822
+ debug18("upload file start %s", url);
11823
+ debug18("upload fields %o", uploadFields);
11824
+ debug18("upload key %s", uploadKey);
11719
11825
  const form = new FormData();
11720
11826
  Object.entries(uploadFields).forEach(([key, value]) => {
11721
11827
  form.append(key, value);
@@ -11724,11 +11830,11 @@ async function uploadFile({
11724
11830
  form.append("key", uploadKey);
11725
11831
  }
11726
11832
  if (typeof file === "string") {
11727
- debug17("upload file from path %s", file);
11833
+ debug18("upload file from path %s", file);
11728
11834
  logInfo2(`FileUpload: upload file from path ${file}`);
11729
11835
  form.append("file", await fileFrom(file));
11730
11836
  } else {
11731
- debug17("upload file from buffer");
11837
+ debug18("upload file from buffer");
11732
11838
  logInfo2(`FileUpload: upload file from buffer`);
11733
11839
  form.append("file", new File([new Uint8Array(file)], "file"));
11734
11840
  }
@@ -11739,11 +11845,11 @@ async function uploadFile({
11739
11845
  agent
11740
11846
  });
11741
11847
  if (!response.ok) {
11742
- debug17("error from S3 %s %s", response.body, response.status);
11848
+ debug18("error from S3 %s %s", response.body, response.status);
11743
11849
  logInfo2(`FileUpload: error from S3 ${response.body} ${response.status}`);
11744
11850
  throw new Error(`Failed to upload the file: ${response.status}`);
11745
11851
  }
11746
- debug17("upload file done");
11852
+ debug18("upload file done");
11747
11853
  logInfo2(`FileUpload: upload file done`);
11748
11854
  }
11749
11855
 
@@ -11778,9 +11884,9 @@ async function downloadRepo({
11778
11884
  }) {
11779
11885
  const { createSpinner: createSpinner5 } = Spinner2({ ci });
11780
11886
  const repoSpinner = createSpinner5("\u{1F4BE} Downloading Repo").start();
11781
- debug18("download repo %s %s %s", repoUrl, dirname);
11887
+ debug19("download repo %s %s %s", repoUrl, dirname);
11782
11888
  const zipFilePath = path9.join(dirname, "repo.zip");
11783
- debug18("download URL: %s auth headers: %o", downloadUrl, authHeaders);
11889
+ debug19("download URL: %s auth headers: %o", downloadUrl, authHeaders);
11784
11890
  const response = await fetch4(downloadUrl, {
11785
11891
  method: "GET",
11786
11892
  headers: {
@@ -11788,9 +11894,9 @@ async function downloadRepo({
11788
11894
  }
11789
11895
  });
11790
11896
  if (!response.ok) {
11791
- debug18("SCM zipball request failed %s %s", response.body, response.status);
11897
+ debug19("SCM zipball request failed %s %s", response.body, response.status);
11792
11898
  repoSpinner.error({ text: "\u{1F4BE} Repo download failed" });
11793
- throw new Error(`Can't access ${chalk5.bold(repoUrl)}`);
11899
+ throw new Error(`Can't access ${chalk6.bold(repoUrl)}`);
11794
11900
  }
11795
11901
  const fileWriterStream = fs9.createWriteStream(zipFilePath);
11796
11902
  if (!response.body) {
@@ -11802,7 +11908,7 @@ async function downloadRepo({
11802
11908
  if (!repoRoot) {
11803
11909
  throw new Error("Repo root not found");
11804
11910
  }
11805
- debug18("repo root %s", repoRoot);
11911
+ debug19("repo root %s", repoRoot);
11806
11912
  repoSpinner.success({ text: "\u{1F4BE} Repo downloaded successfully" });
11807
11913
  return path9.join(dirname, repoRoot);
11808
11914
  }
@@ -11811,9 +11917,9 @@ var getReportUrl = ({
11811
11917
  projectId,
11812
11918
  fixReportId
11813
11919
  }) => `${WEB_APP_URL}/organization/${organizationId}/project/${projectId}/report/${fixReportId}`;
11814
- var debug18 = Debug18("mobbdev:index");
11815
- var config2 = new Configstore(packageJson.name, { apiToken: "" });
11816
- debug18("config %o", config2);
11920
+ var debug19 = Debug19("mobbdev:index");
11921
+ var config3 = new Configstore2(packageJson.name, { apiToken: "" });
11922
+ debug19("config %o", config3);
11817
11923
  async function runAnalysis(params, options) {
11818
11924
  const tmpObj = tmp2.dirSync({
11819
11925
  unsafeCleanup: true
@@ -11958,11 +12064,11 @@ async function _scan(params, { skipPrompts = false } = {}) {
11958
12064
  commitDirectly,
11959
12065
  pullRequest
11960
12066
  } = params;
11961
- debug18("start %s %s", dirname, repo);
12067
+ debug19("start %s %s", dirname, repo);
11962
12068
  const { createSpinner: createSpinner5 } = Spinner2({ ci });
11963
12069
  skipPrompts = skipPrompts || ci;
11964
12070
  let gqlClient = new GQLClient({
11965
- apiKey: apiKey ?? config2.get("apiToken") ?? "",
12071
+ apiKey: apiKey ?? config3.get("apiToken") ?? "",
11966
12072
  type: "apiKey"
11967
12073
  });
11968
12074
  gqlClient = await handleMobbLogin({
@@ -12032,8 +12138,8 @@ async function _scan(params, { skipPrompts = false } = {}) {
12032
12138
  );
12033
12139
  }
12034
12140
  const { sha } = getReferenceDataRes.gitReference;
12035
- debug18("project id %s", projectId);
12036
- debug18("default branch %s", reference);
12141
+ debug19("project id %s", projectId);
12142
+ debug19("default branch %s", reference);
12037
12143
  if (command === "scan") {
12038
12144
  reportPath = await getReport(
12039
12145
  {
@@ -12131,11 +12237,11 @@ async function _scan(params, { skipPrompts = false } = {}) {
12131
12237
  fixReportId: reportUploadInfo.fixReportId
12132
12238
  });
12133
12239
  !ci && console.log("You can access the analysis at: \n");
12134
- console.log(chalk5.bold(reportUrl));
12240
+ console.log(chalk6.bold(reportUrl));
12135
12241
  !skipPrompts && await mobbAnalysisPrompt();
12136
- !ci && open2(reportUrl);
12242
+ !ci && open3(reportUrl);
12137
12243
  !ci && console.log(
12138
- chalk5.bgBlue("\n\n My work here is done for now, see you soon! \u{1F575}\uFE0F\u200D\u2642\uFE0F ")
12244
+ chalk6.bgBlue("\n\n My work here is done for now, see you soon! \u{1F575}\uFE0F\u200D\u2642\uFE0F ")
12139
12245
  );
12140
12246
  }
12141
12247
  async function handleScmIntegration(oldToken, scmAuthUrl2, repoUrl) {
@@ -12152,7 +12258,7 @@ async function _scan(params, { skipPrompts = false } = {}) {
12152
12258
  console.log(
12153
12259
  `If the page does not open automatically, kindly access it through ${scmAuthUrl2}.`
12154
12260
  );
12155
- await open2(scmAuthUrl2);
12261
+ await open3(scmAuthUrl2);
12156
12262
  for (let i = 0; i < LOGIN_MAX_WAIT / LOGIN_CHECK_DELAY; i++) {
12157
12263
  const userInfo = await gqlClient.getUserInfo();
12158
12264
  if (!userInfo) {
@@ -12410,7 +12516,6 @@ async function waitForAnaysisAndReviewPr({
12410
12516
  }
12411
12517
 
12412
12518
  // src/commands/index.ts
12413
- var debug19 = Debug19("mobbdev:commands");
12414
12519
  async function review(params, { skipPrompts = true } = {}) {
12415
12520
  const {
12416
12521
  repo,
@@ -12479,11 +12584,11 @@ async function analyze({
12479
12584
  { skipPrompts }
12480
12585
  );
12481
12586
  }
12482
- var config3 = new Configstore2(packageJson.name, { apiToken: "" });
12587
+ var config4 = new Configstore3(packageJson.name, { apiToken: "" });
12483
12588
  async function addScmToken(addScmTokenOptions) {
12484
12589
  const { apiKey, token, organization, scmType, url, refreshToken, ci } = addScmTokenOptions;
12485
12590
  let gqlClient = new GQLClient({
12486
- apiKey: apiKey ?? config3.get("apiToken") ?? "",
12591
+ apiKey: apiKey ?? config4.get("apiToken") ?? "",
12487
12592
  type: "apiKey"
12488
12593
  });
12489
12594
  gqlClient = await handleMobbLogin({
@@ -12537,99 +12642,6 @@ async function showWelcomeMessage(skipPrompts = false) {
12537
12642
  skipPrompts ? await sleep(100) : await sleep(2e3);
12538
12643
  welcome.stop();
12539
12644
  }
12540
- var LOGIN_MAX_WAIT = 10 * 60 * 1e3;
12541
- var LOGIN_CHECK_DELAY = 5 * 1e3;
12542
- var webLoginUrl = `${WEB_APP_URL}/cli-login`;
12543
- var MOBB_LOGIN_REQUIRED_MSG = `\u{1F513} Login to Mobb is Required, you will be redirected to our login page, once the authorization is complete return to this prompt, ${chalk6.bgBlue(
12544
- "press any key to continue"
12545
- )};`;
12546
- async function handleMobbLogin({
12547
- inGqlClient,
12548
- apiKey,
12549
- skipPrompts
12550
- }) {
12551
- const { createSpinner: createSpinner5 } = Spinner({ ci: skipPrompts });
12552
- const isConnected = await inGqlClient.verifyApiConnection();
12553
- if (!isConnected) {
12554
- createSpinner5().start().error({
12555
- text: "\u{1F513} Connection to Mobb: failed to connect to the Mobb server"
12556
- });
12557
- throw new CliError(
12558
- "Connection to Mobb: failed to connect to the Mobb server"
12559
- );
12560
- }
12561
- createSpinner5().start().success({
12562
- text: `\u{1F513} Connection to Mobb: succeeded`
12563
- });
12564
- const userVerify = await inGqlClient.validateUserToken();
12565
- if (userVerify) {
12566
- createSpinner5().start().success({
12567
- text: `\u{1F513} Login to Mobb succeeded. ${typeof userVerify === "string" ? `Logged in as ${userVerify}` : ""}`
12568
- });
12569
- return inGqlClient;
12570
- } else if (apiKey) {
12571
- createSpinner5().start().error({
12572
- text: "\u{1F513} Login to Mobb failed: The provided API key does not match any configured API key on the system"
12573
- });
12574
- throw new CliError(
12575
- "Login to Mobb failed: The provided API key does not match any configured API key on the system"
12576
- );
12577
- }
12578
- const loginSpinner = createSpinner5().start();
12579
- if (!skipPrompts) {
12580
- loginSpinner.update({ text: MOBB_LOGIN_REQUIRED_MSG });
12581
- await keypress();
12582
- }
12583
- loginSpinner.update({
12584
- text: "\u{1F513} Waiting for Mobb login..."
12585
- });
12586
- const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
12587
- modulusLength: 2048
12588
- });
12589
- const loginId = await inGqlClient.createCliLogin({
12590
- publicKey: publicKey.export({ format: "pem", type: "pkcs1" }).toString()
12591
- });
12592
- const browserUrl = `${webLoginUrl}/${loginId}?hostname=${os.hostname()}`;
12593
- !skipPrompts && console.log(
12594
- `If the page does not open automatically, kindly access it through ${browserUrl}.`
12595
- );
12596
- await open3(browserUrl);
12597
- let newApiToken = null;
12598
- for (let i = 0; i < LOGIN_MAX_WAIT / LOGIN_CHECK_DELAY; i++) {
12599
- const encryptedApiToken = await inGqlClient.getEncryptedApiToken({
12600
- loginId
12601
- });
12602
- loginSpinner.spin();
12603
- if (encryptedApiToken) {
12604
- debug19("encrypted API token received %s", encryptedApiToken);
12605
- newApiToken = crypto.privateDecrypt(privateKey, Buffer.from(encryptedApiToken, "base64")).toString("utf-8");
12606
- debug19("API token decrypted");
12607
- break;
12608
- }
12609
- await sleep(LOGIN_CHECK_DELAY);
12610
- }
12611
- if (!newApiToken) {
12612
- loginSpinner.error({
12613
- text: "Login timeout error"
12614
- });
12615
- throw new CliError();
12616
- }
12617
- const newGqlClient = new GQLClient({ apiKey: newApiToken, type: "apiKey" });
12618
- const loginSuccess = await newGqlClient.validateUserToken();
12619
- if (loginSuccess) {
12620
- debug19(`set api token ${newApiToken}`);
12621
- config3.set("apiToken", newApiToken);
12622
- loginSpinner.success({
12623
- text: `\u{1F513} Login to Mobb successful! ${typeof loginSpinner === "string" ? `Logged in as ${loginSuccess}` : ""}`
12624
- });
12625
- } else {
12626
- loginSpinner.error({
12627
- text: "Something went wrong, API token is invalid."
12628
- });
12629
- throw new CliError();
12630
- }
12631
- return newGqlClient;
12632
- }
12633
12645
 
12634
12646
  // src/args/validation.ts
12635
12647
  import chalk7 from "chalk";
@@ -12771,7 +12783,7 @@ import {
12771
12783
  } from "@modelcontextprotocol/sdk/types.js";
12772
12784
 
12773
12785
  // src/mcp/Logger.ts
12774
- import Configstore3 from "configstore";
12786
+ import Configstore4 from "configstore";
12775
12787
 
12776
12788
  // src/mcp/services/WorkspaceService.ts
12777
12789
  var WorkspaceService = class {
@@ -12857,7 +12869,7 @@ var Logger = class {
12857
12869
  __publicField(this, "lastKnownPath", null);
12858
12870
  this.host = WorkspaceService.getHost();
12859
12871
  this.unknownPathSuffix = Math.floor(1e3 + Math.random() * 9e3).toString();
12860
- this.mobbConfigStore = new Configstore3("mobb-logs", {});
12872
+ this.mobbConfigStore = new Configstore4("mobb-logs", {});
12861
12873
  this.mobbConfigStore.set("version", packageJson.version);
12862
12874
  }
12863
12875
  /**
@@ -13055,7 +13067,7 @@ var GetLatestReportByRepoUrlResponseSchema = z31.object({
13055
13067
  });
13056
13068
 
13057
13069
  // src/mcp/services/ConfigStoreService.ts
13058
- import Configstore4 from "configstore";
13070
+ import Configstore5 from "configstore";
13059
13071
  init_configs();
13060
13072
  function createConfigStore(defaultValues = { apiToken: "" }) {
13061
13073
  const API_URL2 = process.env["API_URL"] || MCP_DEFAULT_API_URL;
@@ -13067,7 +13079,7 @@ function createConfigStore(defaultValues = { apiToken: "" }) {
13067
13079
  domain = API_URL2.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/:\d+$/, "");
13068
13080
  }
13069
13081
  const sanitizedDomain = domain.replace(/\./g, "_");
13070
- return new Configstore4(
13082
+ return new Configstore5(
13071
13083
  `${packageJson.name}-${sanitizedDomain}`,
13072
13084
  defaultValues
13073
13085
  );
@@ -13884,34 +13896,34 @@ var readConfigFile = (filePath) => {
13884
13896
  return null;
13885
13897
  }
13886
13898
  };
13887
- var mergeConfigIntoResult = (config4, mergedConfig) => {
13888
- if (config4?.projects) {
13899
+ var mergeConfigIntoResult = (config6, mergedConfig) => {
13900
+ if (config6?.projects) {
13889
13901
  const allMcpServers = {};
13890
- for (const projectPath in config4.projects) {
13891
- const project = config4.projects[projectPath];
13902
+ for (const projectPath in config6.projects) {
13903
+ const project = config6.projects[projectPath];
13892
13904
  if (project?.mcpServers) {
13893
13905
  Object.assign(allMcpServers, project.mcpServers);
13894
13906
  }
13895
13907
  }
13896
13908
  mergedConfig.mcpServers = { ...mergedConfig.mcpServers, ...allMcpServers };
13897
13909
  }
13898
- if (config4?.mcpServers) {
13910
+ if (config6?.mcpServers) {
13899
13911
  mergedConfig.mcpServers = {
13900
13912
  ...mergedConfig.mcpServers,
13901
- ...config4.mcpServers
13913
+ ...config6.mcpServers
13902
13914
  };
13903
13915
  }
13904
- if (config4?.servers) {
13905
- mergedConfig.servers = { ...mergedConfig.servers, ...config4.servers };
13916
+ if (config6?.servers) {
13917
+ mergedConfig.servers = { ...mergedConfig.servers, ...config6.servers };
13906
13918
  }
13907
13919
  };
13908
13920
  var readMCPConfig = (hostName) => {
13909
13921
  const configPaths = getMCPConfigPaths(hostName);
13910
13922
  const mergedConfig = {};
13911
13923
  for (const configPath of configPaths) {
13912
- const config4 = readConfigFile(configPath);
13913
- if (config4) {
13914
- mergeConfigIntoResult(config4, mergedConfig);
13924
+ const config6 = readConfigFile(configPath);
13925
+ if (config6) {
13926
+ mergeConfigIntoResult(config6, mergedConfig);
13915
13927
  }
13916
13928
  }
13917
13929
  return Object.keys(mergedConfig).length > 0 ? mergedConfig : null;
@@ -14038,10 +14050,10 @@ var getHostInfo = (additionalMcpList) => {
14038
14050
  if (cfg) allConfigs[ide] = cfg;
14039
14051
  }
14040
14052
  for (const additionalPath of uniqueAdditionalPaths) {
14041
- const config4 = readConfigFile(additionalPath);
14042
- if (!config4) continue;
14053
+ const config6 = readConfigFile(additionalPath);
14054
+ if (!config6) continue;
14043
14055
  const mergedConfig = {};
14044
- mergeConfigIntoResult(config4, mergedConfig);
14056
+ mergeConfigIntoResult(config6, mergedConfig);
14045
14057
  if (Object.keys(mergedConfig).length > 0) {
14046
14058
  allConfigs["system"] = mergedConfig;
14047
14059
  }
@@ -14109,7 +14121,7 @@ var getHostInfo = (additionalMcpList) => {
14109
14121
  }
14110
14122
  }
14111
14123
  for (const { ide, name, command, isRunning } of servers) {
14112
- const config4 = allConfigs[ide] || null;
14124
+ const config6 = allConfigs[ide] || null;
14113
14125
  const ideName = ide.charAt(0).toUpperCase() + ide.slice(1) || "Unknown";
14114
14126
  let ideVersion = "Unknown";
14115
14127
  const platform = os3.platform();
@@ -14126,8 +14138,8 @@ var getHostInfo = (additionalMcpList) => {
14126
14138
  }
14127
14139
  }
14128
14140
  let mcpConfigObj = {};
14129
- if (config4) {
14130
- const allServers = config4.mcpServers || config4.servers || {};
14141
+ if (config6) {
14142
+ const allServers = config6.mcpServers || config6.servers || {};
14131
14143
  if (name in allServers && allServers[name]) {
14132
14144
  mcpConfigObj = allServers[name];
14133
14145
  }
@@ -14436,7 +14448,7 @@ var ToolRegistry = class {
14436
14448
 
14437
14449
  // src/mcp/core/McpServer.ts
14438
14450
  var McpServer = class {
14439
- constructor(config4, govOrgId = "") {
14451
+ constructor(config6, govOrgId = "") {
14440
14452
  __publicField(this, "server");
14441
14453
  __publicField(this, "toolRegistry");
14442
14454
  __publicField(this, "isEventHandlersSetup", false);
@@ -14449,8 +14461,8 @@ var McpServer = class {
14449
14461
  this.mcpUsageService = govOrgId ? new McpUsageService(govOrgId) : null;
14450
14462
  this.server = new Server(
14451
14463
  {
14452
- name: config4.name,
14453
- version: config4.version
14464
+ name: config6.name,
14465
+ version: config6.version
14454
14466
  },
14455
14467
  {
14456
14468
  capabilities: {
@@ -14464,7 +14476,7 @@ var McpServer = class {
14464
14476
  this.setupParentProcessMonitoring();
14465
14477
  logInfo("MCP server instance created");
14466
14478
  logDebug("MCP server instance config", {
14467
- config: config4,
14479
+ config: config6,
14468
14480
  parentPid: this.parentPid
14469
14481
  });
14470
14482
  }
@@ -19169,6 +19181,7 @@ async function addScmTokenHandler(args) {
19169
19181
  import fsPromises3 from "fs/promises";
19170
19182
  import path16 from "path";
19171
19183
  import chalk10 from "chalk";
19184
+ import Configstore6 from "configstore";
19172
19185
  import { withFile } from "tmp-promise";
19173
19186
  import z38 from "zod";
19174
19187
  var PromptItemZ = z38.object({
@@ -19223,6 +19236,18 @@ function uploadAiBlameBuilder(args) {
19223
19236
  describe: chalk10.bold("Tool/IDE name(s) (optional, one per session)")
19224
19237
  }).strict();
19225
19238
  }
19239
+ var config5 = new Configstore6(packageJson.name, { apiToken: "" });
19240
+ async function getAuthenticatedGQLClientForIdeExtension() {
19241
+ let gqlClient = new GQLClient({
19242
+ apiKey: config5.get("apiToken") ?? "",
19243
+ type: "apiKey"
19244
+ });
19245
+ gqlClient = await handleMobbLogin({
19246
+ inGqlClient: gqlClient,
19247
+ skipPrompts: true
19248
+ });
19249
+ return gqlClient;
19250
+ }
19226
19251
  async function uploadAiBlameHandler(args, exitOnError = true) {
19227
19252
  const prompts = args.prompt || [];
19228
19253
  const inferences = args.inference || [];
@@ -19263,8 +19288,10 @@ async function uploadAiBlameHandler(args, exitOnError = true) {
19263
19288
  toolName: tools[i]
19264
19289
  });
19265
19290
  }
19266
- const gqlClient = await createAuthenticatedMcpGQLClient();
19267
- const initRes = await gqlClient.uploadAIBlameInferencesInitRaw({ sessions });
19291
+ const authenticatedClient = await getAuthenticatedGQLClientForIdeExtension();
19292
+ const initRes = await authenticatedClient.uploadAIBlameInferencesInitRaw({
19293
+ sessions
19294
+ });
19268
19295
  const uploadSessions = initRes.uploadAIBlameInferencesInit?.uploadSessions ?? [];
19269
19296
  if (uploadSessions.length !== sessions.length) {
19270
19297
  const errorMsg = "Init failed to return expected number of sessions";
@@ -19302,7 +19329,7 @@ async function uploadAiBlameHandler(args, exitOnError = true) {
19302
19329
  toolName: s.toolName
19303
19330
  };
19304
19331
  });
19305
- const finRes = await gqlClient.finalizeAIBlameInferencesUploadRaw({
19332
+ const finRes = await authenticatedClient.finalizeAIBlameInferencesUploadRaw({
19306
19333
  sessions: finalizeSessions
19307
19334
  });
19308
19335
  const status = finRes?.finalizeAIBlameInferencesUpload?.status;