mobbdev 1.2.33 → 1.2.35

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
@@ -73,6 +73,12 @@ function getSdk(client, withWrapper = defaultWrapper) {
73
73
  FinalizeAIBlameInferencesUpload(variables, requestHeaders, signal) {
74
74
  return withWrapper((wrappedRequestHeaders) => client.request({ document: FinalizeAiBlameInferencesUploadDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "FinalizeAIBlameInferencesUpload", "mutation", variables);
75
75
  },
76
+ UploadTracyRecords(variables, requestHeaders, signal) {
77
+ return withWrapper((wrappedRequestHeaders) => client.request({ document: UploadTracyRecordsDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "UploadTracyRecords", "mutation", variables);
78
+ },
79
+ GetTracyRawDataUploadUrls(variables, requestHeaders, signal) {
80
+ return withWrapper((wrappedRequestHeaders) => client.request({ document: GetTracyRawDataUploadUrlsDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetTracyRawDataUploadUrls", "mutation", variables);
81
+ },
76
82
  DigestVulnerabilityReport(variables, requestHeaders, signal) {
77
83
  return withWrapper((wrappedRequestHeaders) => client.request({ document: DigestVulnerabilityReportDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "DigestVulnerabilityReport", "mutation", variables);
78
84
  },
@@ -126,7 +132,7 @@ function getSdk(client, withWrapper = defaultWrapper) {
126
132
  }
127
133
  };
128
134
  }
129
- var AiBlameInferenceType, FixQuestionInputType, Language, ManifestAction, Effort_To_Apply_Fix_Enum, Fix_Rating_Tag_Enum, Fix_Report_State_Enum, Fix_State_Enum, IssueLanguage_Enum, IssueType_Enum, Pr_Status_Enum, Project_Role_Type_Enum, Vulnerability_Report_Issue_Category_Enum, Vulnerability_Report_Issue_State_Enum, Vulnerability_Report_Issue_Tag_Enum, Vulnerability_Report_Vendor_Enum, Vulnerability_Severity_Enum, FixDetailsFragmentDoc, FixReportSummaryFieldsFragmentDoc, MeDocument, GetLastOrgAndNamedProjectDocument, GetLastOrgDocument, GetEncryptedApiTokenDocument, FixReportStateDocument, GetVulnerabilityReportPathsDocument, GetAnalysisSubscriptionDocument, GetAnalysisDocument, GetFixesDocument, GetVulByNodesMetadataDocument, GetFalsePositiveDocument, UpdateScmTokenDocument, UploadS3BucketInfoDocument, GetTracyDiffUploadUrlDocument, AnalyzeCommitForExtensionAiBlameDocument, GetAiBlameInferenceDocument, GetAiBlameAttributionPromptDocument, GetPromptSummaryDocument, UploadAiBlameInferencesInitDocument, FinalizeAiBlameInferencesUploadDocument, DigestVulnerabilityReportDocument, SubmitVulnerabilityReportDocument, CreateCommunityUserDocument, CreateCliLoginDocument, PerformCliLoginDocument, CreateProjectDocument, ValidateRepoUrlDocument, GitReferenceDocument, AutoPrAnalysisDocument, GetFixReportsByRepoUrlDocument, GetReportFixesDocument, GetLatestReportByRepoUrlDocument, UpdateDownloadedFixDataDocument, GetUserMvsAutoFixDocument, StreamBlameAiAnalysisRequestsDocument, StreamCommitBlameRequestsDocument, ScanSkillDocument, defaultWrapper;
135
+ var AiBlameInferenceType, FixQuestionInputType, Language, ManifestAction, Effort_To_Apply_Fix_Enum, Fix_Rating_Tag_Enum, Fix_Report_State_Enum, Fix_State_Enum, IssueLanguage_Enum, IssueType_Enum, Pr_Status_Enum, Project_Role_Type_Enum, Vulnerability_Report_Issue_Category_Enum, Vulnerability_Report_Issue_State_Enum, Vulnerability_Report_Issue_Tag_Enum, Vulnerability_Report_Vendor_Enum, Vulnerability_Severity_Enum, FixDetailsFragmentDoc, FixReportSummaryFieldsFragmentDoc, MeDocument, GetLastOrgAndNamedProjectDocument, GetLastOrgDocument, GetEncryptedApiTokenDocument, FixReportStateDocument, GetVulnerabilityReportPathsDocument, GetAnalysisSubscriptionDocument, GetAnalysisDocument, GetFixesDocument, GetVulByNodesMetadataDocument, GetFalsePositiveDocument, UpdateScmTokenDocument, UploadS3BucketInfoDocument, GetTracyDiffUploadUrlDocument, AnalyzeCommitForExtensionAiBlameDocument, GetAiBlameInferenceDocument, GetAiBlameAttributionPromptDocument, GetPromptSummaryDocument, UploadAiBlameInferencesInitDocument, FinalizeAiBlameInferencesUploadDocument, UploadTracyRecordsDocument, GetTracyRawDataUploadUrlsDocument, DigestVulnerabilityReportDocument, SubmitVulnerabilityReportDocument, CreateCommunityUserDocument, CreateCliLoginDocument, PerformCliLoginDocument, CreateProjectDocument, ValidateRepoUrlDocument, GitReferenceDocument, AutoPrAnalysisDocument, GetFixReportsByRepoUrlDocument, GetReportFixesDocument, GetLatestReportByRepoUrlDocument, UpdateDownloadedFixDataDocument, GetUserMvsAutoFixDocument, StreamBlameAiAnalysisRequestsDocument, StreamCommitBlameRequestsDocument, ScanSkillDocument, defaultWrapper;
130
136
  var init_client_generates = __esm({
131
137
  "src/features/analysis/scm/generates/client_generates.ts"() {
132
138
  "use strict";
@@ -962,6 +968,28 @@ var init_client_generates = __esm({
962
968
  status
963
969
  error
964
970
  }
971
+ }
972
+ `;
973
+ UploadTracyRecordsDocument = `
974
+ mutation UploadTracyRecords($records: [TracyRecordInput!]!) {
975
+ uploadTracyRecords(records: $records) {
976
+ status
977
+ error
978
+ }
979
+ }
980
+ `;
981
+ GetTracyRawDataUploadUrlsDocument = `
982
+ mutation GetTracyRawDataUploadUrls($recordIds: [String!]!) {
983
+ getTracyRawDataUploadUrls(recordIds: $recordIds) {
984
+ status
985
+ error
986
+ uploads {
987
+ recordId
988
+ url
989
+ uploadFieldsJSON
990
+ uploadKey
991
+ }
992
+ }
965
993
  }
966
994
  `;
967
995
  DigestVulnerabilityReportDocument = `
@@ -2276,20 +2304,20 @@ function computeCanonicalUrl(data) {
2276
2304
  } = data;
2277
2305
  switch (scmType) {
2278
2306
  case "GitHub" /* GitHub */:
2279
- return `https://${hostname}/${organization}/${repoName}`;
2307
+ return `https://${hostname}/${organization.toLowerCase()}/${repoName.toLowerCase()}`;
2280
2308
  case "GitLab" /* GitLab */:
2281
- return `https://${hostname}/${projectPath}`;
2309
+ return `https://${hostname}/${projectPath.toLowerCase()}`;
2282
2310
  case "Bitbucket" /* Bitbucket */:
2283
- return `https://${hostname}/${organization}/${repoName}`;
2311
+ return `https://${hostname}/${organization.toLowerCase()}/${repoName.toLowerCase()}`;
2284
2312
  case "Ado" /* Ado */: {
2285
2313
  const adoHostname = hostname === "ssh.dev.azure.com" ? "dev.azure.com" : hostname;
2286
2314
  if (projectName) {
2287
- return `https://${adoHostname}/${organization}/${projectName}/_git/${repoName}`;
2315
+ return `https://${adoHostname}/${organization.toLowerCase()}/${projectName.toLowerCase()}/_git/${repoName.toLowerCase()}`;
2288
2316
  }
2289
- return `https://${adoHostname}/${organization}/_git/${repoName}`;
2317
+ return `https://${adoHostname}/${organization.toLowerCase()}/_git/${repoName.toLowerCase()}`;
2290
2318
  }
2291
2319
  default:
2292
- return `https://${hostname}/${projectPath}`;
2320
+ return `https://${hostname}/${projectPath.toLowerCase()}`;
2293
2321
  }
2294
2322
  }
2295
2323
  function detectAdoUrl(args) {
@@ -4126,7 +4154,7 @@ ${rootContent}`;
4126
4154
  });
4127
4155
 
4128
4156
  // src/index.ts
4129
- import Debug20 from "debug";
4157
+ import Debug22 from "debug";
4130
4158
  import { hideBin } from "yargs/helpers";
4131
4159
 
4132
4160
  // src/args/yargs.ts
@@ -11700,39 +11728,40 @@ var ScanContext = {
11700
11728
 
11701
11729
  // src/args/commands/analyze.ts
11702
11730
  import fs12 from "fs";
11703
- import chalk9 from "chalk";
11731
+ import chalk10 from "chalk";
11704
11732
 
11705
11733
  // src/commands/index.ts
11706
- import chalk7 from "chalk";
11734
+ import chalk8 from "chalk";
11707
11735
  import chalkAnimation from "chalk-animation";
11708
11736
 
11709
11737
  // src/features/analysis/index.ts
11710
11738
  import fs10 from "fs";
11711
- import fsPromises2 from "fs/promises";
11712
- import path9 from "path";
11739
+ import fsPromises3 from "fs/promises";
11740
+ import path10 from "path";
11713
11741
  import { env as env2 } from "process";
11714
11742
  import { pipeline } from "stream/promises";
11715
- import chalk6 from "chalk";
11716
- import Debug19 from "debug";
11743
+ import chalk7 from "chalk";
11744
+ import Debug21 from "debug";
11717
11745
  import extract from "extract-zip";
11718
11746
  import { createSpinner as createSpinner4 } from "nanospinner";
11719
11747
  import fetch4 from "node-fetch";
11720
11748
  import open3 from "open";
11721
11749
  import tmp2 from "tmp";
11722
- import { z as z30 } from "zod";
11750
+ import { z as z31 } from "zod";
11723
11751
 
11724
11752
  // src/commands/handleMobbLogin.ts
11725
- import chalk3 from "chalk";
11726
- import Debug7 from "debug";
11753
+ import chalk4 from "chalk";
11754
+ import Debug10 from "debug";
11727
11755
 
11728
11756
  // src/commands/AuthManager.ts
11729
11757
  import crypto from "crypto";
11730
- import os from "os";
11758
+ import os3 from "os";
11759
+ import Debug9 from "debug";
11731
11760
  import open from "open";
11732
11761
 
11733
11762
  // src/features/analysis/graphql/gql.ts
11734
11763
  import Debug6 from "debug";
11735
- import { GraphQLClient } from "graphql-request";
11764
+ import { ClientError, GraphQLClient } from "graphql-request";
11736
11765
  import { v4 as uuidv4 } from "uuid";
11737
11766
 
11738
11767
  // src/mcp/core/Errors.ts
@@ -11847,8 +11876,7 @@ function getProxyAgent(url) {
11847
11876
  const isHttps = parsedUrl.protocol === "https:";
11848
11877
  const proxy = isHttps ? getHttpProxy() : isHttp ? getHttpProxyOnly() : null;
11849
11878
  if (proxy) {
11850
- debug6("Using proxy %s", proxy);
11851
- debug6("Proxy agent %o", proxy);
11879
+ debug6("Using proxy %s for %s", proxy, url);
11852
11880
  return new HttpsProxyAgent(proxy);
11853
11881
  }
11854
11882
  } catch (err) {
@@ -12069,6 +12097,19 @@ var GetVulByNodesMetadataZ = z26.object({
12069
12097
 
12070
12098
  // src/features/analysis/graphql/gql.ts
12071
12099
  var debug7 = Debug6("mobbdev:gql");
12100
+ function isAuthError(error) {
12101
+ if (error instanceof ClientError) {
12102
+ const gqlErrors = error.response?.errors;
12103
+ return gqlErrors?.some(
12104
+ (e) => e.extensions?.["code"] === "access-denied" || e.message?.includes("Authentication hook unauthorized")
12105
+ ) ?? false;
12106
+ }
12107
+ return false;
12108
+ }
12109
+ function isNetworkError(error) {
12110
+ const errorString = error?.toString() ?? "";
12111
+ return errorString.includes("FetchError") || errorString.includes("TypeError") || errorString.includes("ECONNREFUSED") || errorString.includes("ENOTFOUND") || errorString.includes("ETIMEDOUT") || errorString.includes("UND_ERR");
12112
+ }
12072
12113
  var API_KEY_HEADER_NAME = "x-mobb-key";
12073
12114
  var REPORT_STATE_CHECK_DELAY = 5 * 1e3;
12074
12115
  var GQLClient = class {
@@ -12128,23 +12169,35 @@ var GQLClient = class {
12128
12169
  try {
12129
12170
  await this.getUserInfo();
12130
12171
  } catch (e) {
12131
- if (e?.toString().startsWith("FetchError")) {
12132
- debug7("verify connection failed %o", e);
12172
+ if (isNetworkError(e)) {
12173
+ debug7("verify connection failed (network error) %o", e);
12133
12174
  return false;
12134
12175
  }
12176
+ debug7("verify connection: endpoint reachable but request failed %o", e);
12135
12177
  }
12136
12178
  return true;
12137
12179
  }
12138
12180
  async validateUserToken() {
12139
- await this.createCommunityUser();
12140
- let info;
12141
12181
  try {
12142
- info = await this.getUserInfo();
12182
+ await this.createCommunityUser();
12183
+ const info = await this.getUserInfo();
12184
+ if (!info) {
12185
+ debug7("verify token failed - no user info returned");
12186
+ return false;
12187
+ }
12188
+ return info.email || true;
12143
12189
  } catch (e) {
12144
- debug7("verify token failed %o", e);
12145
- return false;
12190
+ if (isAuthError(e)) {
12191
+ debug7("verify token failed - auth error %o", e);
12192
+ return false;
12193
+ }
12194
+ if (isNetworkError(e)) {
12195
+ debug7("verify token failed - network error, rethrowing %o", e);
12196
+ throw e;
12197
+ }
12198
+ debug7("verify token failed - unexpected error, rethrowing %o", e);
12199
+ throw e;
12146
12200
  }
12147
- return info?.email || true;
12148
12201
  }
12149
12202
  async getLastOrgAndNamedProject(params) {
12150
12203
  const me = await this.getUserInfo();
@@ -12500,6 +12553,12 @@ var GQLClient = class {
12500
12553
  async finalizeAIBlameInferencesUploadRaw(variables) {
12501
12554
  return await this._clientSdk.FinalizeAIBlameInferencesUpload(variables);
12502
12555
  }
12556
+ async uploadTracyRecords(variables) {
12557
+ return await this._clientSdk.UploadTracyRecords(variables);
12558
+ }
12559
+ async getTracyRawDataUploadUrls(variables) {
12560
+ return await this._clientSdk.GetTracyRawDataUploadUrls(variables);
12561
+ }
12503
12562
  async analyzeCommitForExtensionAIBlame(variables) {
12504
12563
  return await this._clientSdk.AnalyzeCommitForExtensionAIBlame(variables);
12505
12564
  }
@@ -12517,38 +12576,76 @@ var GQLClient = class {
12517
12576
  }
12518
12577
  };
12519
12578
 
12520
- // src/mcp/services/types.ts
12521
- function detectIDE() {
12522
- const env3 = process.env;
12523
- if (env3["CURSOR_TRACE_ID"] || env3["CURSOR_SESSION_ID"]) return "cursor";
12524
- if (env3["WINDSURF_IPC_HOOK"] || env3["WINDSURF_PID"]) return "windsurf";
12525
- if (env3["CLAUDE_DESKTOP"] || env3["ANTHROPIC_CLAUDE"]) return "claude";
12526
- if (env3["WEBSTORM_VM_OPTIONS"] || env3["IDEA_VM_OPTIONS"] || env3["JETBRAINS_IDE"])
12527
- return "webstorm";
12528
- if (env3["VSCODE_IPC_HOOK"] || env3["VSCODE_PID"]) return "vscode";
12529
- const termProgram = env3["TERM_PROGRAM"]?.toLowerCase();
12530
- if (termProgram === "windsurf") return "windsurf";
12531
- if (termProgram === "vscode") return "vscode";
12532
- return void 0;
12533
- }
12534
- function createMcpLoginContext(trigger) {
12535
- return {
12536
- trigger,
12537
- source: "mcp",
12538
- ide: detectIDE()
12539
- };
12540
- }
12541
- function buildLoginUrl(baseUrl, loginId, hostname, context) {
12542
- const url = new URL(`${baseUrl}/${loginId}`);
12543
- url.searchParams.set("hostname", hostname);
12544
- url.searchParams.set("trigger", context.trigger);
12545
- url.searchParams.set("source", context.source);
12546
- if (context.ide) {
12547
- url.searchParams.set("ide", context.ide);
12579
+ // src/features/analysis/graphql/tracy-batch-upload.ts
12580
+ import { promisify } from "util";
12581
+ import { gzip } from "zlib";
12582
+ import Debug8 from "debug";
12583
+
12584
+ // src/args/commands/upload_ai_blame.ts
12585
+ import fsPromises2 from "fs/promises";
12586
+ import * as os2 from "os";
12587
+ import path7 from "path";
12588
+ import chalk3 from "chalk";
12589
+ import { withFile } from "tmp-promise";
12590
+ import z27 from "zod";
12591
+ init_client_generates();
12592
+ init_GitService();
12593
+ init_urlParser2();
12594
+
12595
+ // src/features/analysis/upload-file.ts
12596
+ import Debug7 from "debug";
12597
+ import fetch3, { File, fileFrom, FormData } from "node-fetch";
12598
+ var debug8 = Debug7("mobbdev:upload-file");
12599
+ async function uploadFile({
12600
+ file,
12601
+ url,
12602
+ uploadKey,
12603
+ uploadFields,
12604
+ logger: logger2
12605
+ }) {
12606
+ const logInfo2 = logger2 || ((_message, _data) => {
12607
+ });
12608
+ logInfo2(`FileUpload: upload file start ${url}`);
12609
+ logInfo2(`FileUpload: upload fields`, uploadFields);
12610
+ logInfo2(`FileUpload: upload key ${uploadKey}`);
12611
+ debug8("upload file start %s", url);
12612
+ debug8("upload fields %o", uploadFields);
12613
+ debug8("upload key %s", uploadKey);
12614
+ const form = new FormData();
12615
+ Object.entries(uploadFields).forEach(([key, value]) => {
12616
+ form.append(key, value);
12617
+ });
12618
+ if (!form.has("key")) {
12619
+ form.append("key", uploadKey);
12548
12620
  }
12549
- return url.toString();
12621
+ if (typeof file === "string") {
12622
+ debug8("upload file from path %s", file);
12623
+ logInfo2(`FileUpload: upload file from path ${file}`);
12624
+ form.append("file", await fileFrom(file));
12625
+ } else {
12626
+ debug8("upload file from buffer");
12627
+ logInfo2(`FileUpload: upload file from buffer`);
12628
+ form.append("file", new File([new Uint8Array(file)], "file"));
12629
+ }
12630
+ const agent = getProxyAgent(url);
12631
+ const response = await fetch3(url, {
12632
+ method: "POST",
12633
+ body: form,
12634
+ agent
12635
+ });
12636
+ if (!response.ok) {
12637
+ debug8("error from S3 %s %s", response.body, response.status);
12638
+ logInfo2(`FileUpload: error from S3 ${response.body} ${response.status}`);
12639
+ throw new Error(`Failed to upload the file: ${response.status}`);
12640
+ }
12641
+ debug8("upload file done");
12642
+ logInfo2(`FileUpload: upload file done`);
12550
12643
  }
12551
12644
 
12645
+ // src/utils/computerName.ts
12646
+ import { execSync } from "child_process";
12647
+ import os from "os";
12648
+
12552
12649
  // src/utils/ConfigStoreService.ts
12553
12650
  import Configstore from "configstore";
12554
12651
  function createConfigStore(defaultValues = { apiToken: "" }) {
@@ -12568,339 +12665,941 @@ function getConfigStore() {
12568
12665
  }
12569
12666
  var configStore = getConfigStore();
12570
12667
 
12571
- // src/commands/AuthManager.ts
12572
- var LOGIN_MAX_WAIT = 10 * 60 * 1e3;
12573
- var LOGIN_CHECK_DELAY = 5 * 1e3;
12574
- var AuthManager = class {
12575
- constructor(webAppUrl, apiUrl) {
12576
- __publicField(this, "publicKey");
12577
- __publicField(this, "privateKey");
12578
- __publicField(this, "loginId");
12579
- __publicField(this, "gqlClient");
12580
- __publicField(this, "currentBrowserUrl");
12581
- __publicField(this, "authenticated", null);
12582
- __publicField(this, "resolvedWebAppUrl");
12583
- __publicField(this, "resolvedApiUrl");
12584
- this.resolvedWebAppUrl = webAppUrl || WEB_APP_URL;
12585
- this.resolvedApiUrl = apiUrl || API_URL;
12586
- }
12587
- openUrlInBrowser() {
12588
- if (this.currentBrowserUrl) {
12589
- open(this.currentBrowserUrl);
12590
- return true;
12591
- }
12592
- return false;
12593
- }
12594
- async waitForAuthentication() {
12595
- let newApiToken = null;
12596
- for (let i = 0; i < LOGIN_MAX_WAIT / LOGIN_CHECK_DELAY; i++) {
12597
- newApiToken = await this.getApiToken();
12598
- if (newApiToken) {
12599
- break;
12668
+ // src/utils/computerName.ts
12669
+ var STABLE_COMPUTER_NAME_CONFIG_KEY = "stableComputerName";
12670
+ var HOSTNAME_SUFFIXES = [
12671
+ // Cloud providers (must be first - most specific)
12672
+ ".ec2.internal",
12673
+ ".compute.internal",
12674
+ ".cloudapp.net",
12675
+ // mDNS/Bonjour
12676
+ ".local",
12677
+ ".localhost",
12678
+ ".localdomain",
12679
+ // Home networks
12680
+ ".lan",
12681
+ ".home",
12682
+ ".homelan",
12683
+ // Corporate networks
12684
+ ".corp",
12685
+ ".internal",
12686
+ ".intranet",
12687
+ ".domain",
12688
+ ".work",
12689
+ ".office",
12690
+ // Container environments
12691
+ ".docker",
12692
+ ".kubernetes",
12693
+ ".k8s"
12694
+ ];
12695
+ function getOsSpecificComputerName() {
12696
+ const platform2 = os.platform();
12697
+ try {
12698
+ if (platform2 === "darwin") {
12699
+ const result = execSync("scutil --get LocalHostName", {
12700
+ encoding: "utf-8",
12701
+ timeout: 1e3
12702
+ });
12703
+ return result.trim();
12704
+ } else if (platform2 === "win32") {
12705
+ return process.env["COMPUTERNAME"] || null;
12706
+ } else {
12707
+ try {
12708
+ const result2 = execSync("hostnamectl --static", {
12709
+ encoding: "utf-8",
12710
+ timeout: 1e3
12711
+ });
12712
+ const hostname = result2.trim();
12713
+ if (hostname) return hostname;
12714
+ } catch {
12600
12715
  }
12601
- await sleep(LOGIN_CHECK_DELAY);
12602
- }
12603
- if (!newApiToken) {
12604
- return false;
12605
- }
12606
- this.gqlClient = new GQLClient({
12607
- apiKey: newApiToken,
12608
- type: "apiKey",
12609
- apiUrl: this.resolvedApiUrl
12610
- });
12611
- const loginSuccess = await this.gqlClient.validateUserToken();
12612
- if (loginSuccess) {
12613
- configStore.set("apiToken", newApiToken);
12614
- this.authenticated = true;
12615
- return true;
12716
+ const result = execSync("cat /etc/hostname", {
12717
+ encoding: "utf-8",
12718
+ timeout: 1e3
12719
+ });
12720
+ return result.trim();
12616
12721
  }
12617
- return false;
12722
+ } catch (error) {
12723
+ return null;
12618
12724
  }
12619
- /**
12620
- * Checks if the user is already authenticated
12621
- */
12622
- async isAuthenticated() {
12623
- if (this.authenticated === null) {
12624
- const result = await this.checkAuthentication();
12625
- this.authenticated = result.isAuthenticated;
12725
+ }
12726
+ function stripHostnameSuffixes(hostname) {
12727
+ if (!hostname) return hostname;
12728
+ let normalized = hostname;
12729
+ for (const suffix of HOSTNAME_SUFFIXES) {
12730
+ if (normalized.endsWith(suffix)) {
12731
+ normalized = normalized.slice(0, -suffix.length);
12732
+ break;
12626
12733
  }
12627
- return this.authenticated;
12628
12734
  }
12629
- /**
12630
- * Private function to check if the user is authenticated with the server
12631
- */
12632
- async checkAuthentication(apiKey) {
12633
- try {
12634
- if (!this.gqlClient) {
12635
- this.gqlClient = this.getGQLClient(apiKey);
12636
- }
12637
- const isConnected = await this.gqlClient.verifyApiConnection();
12638
- if (!isConnected) {
12639
- return {
12640
- isAuthenticated: false,
12641
- message: "Failed to connect to Mobb server"
12642
- };
12643
- }
12644
- const userVerify = await this.gqlClient.validateUserToken();
12645
- if (!userVerify) {
12646
- return {
12647
- isAuthenticated: false,
12648
- message: "User token validation failed"
12649
- };
12735
+ return normalized;
12736
+ }
12737
+ function generateStableComputerName() {
12738
+ const osSpecificName = getOsSpecificComputerName();
12739
+ if (osSpecificName) {
12740
+ return osSpecificName;
12741
+ }
12742
+ const currentHostname = os.hostname();
12743
+ return stripHostnameSuffixes(currentHostname);
12744
+ }
12745
+ function getStableComputerName() {
12746
+ const cached = configStore.get(STABLE_COMPUTER_NAME_CONFIG_KEY);
12747
+ if (cached) {
12748
+ return cached;
12749
+ }
12750
+ const currentName = generateStableComputerName();
12751
+ configStore.set(STABLE_COMPUTER_NAME_CONFIG_KEY, currentName);
12752
+ return currentName;
12753
+ }
12754
+
12755
+ // src/utils/sanitize-sensitive-data.ts
12756
+ import { OpenRedaction } from "@openredaction/openredaction";
12757
+ var openRedaction = new OpenRedaction({
12758
+ patterns: [
12759
+ // Core Personal Data
12760
+ // Removed EMAIL - causes false positives in code/test snippets (e.g. --author="Eve Author <eve@example.com>")
12761
+ // Prefer false negatives over false positives for this use case.
12762
+ "SSN",
12763
+ "NATIONAL_INSURANCE_UK",
12764
+ "DATE_OF_BIRTH",
12765
+ // Identity Documents
12766
+ "PASSPORT_UK",
12767
+ "PASSPORT_US",
12768
+ "PASSPORT_MRZ_TD1",
12769
+ "PASSPORT_MRZ_TD3",
12770
+ "DRIVING_LICENSE_UK",
12771
+ "DRIVING_LICENSE_US",
12772
+ "VISA_NUMBER",
12773
+ "VISA_MRZ",
12774
+ "TAX_ID",
12775
+ // Financial Data (removed SWIFT_BIC, CARD_AUTH_CODE - too broad, causing false positives with authentication words)
12776
+ // Removed CREDIT_CARD - causes false positives on zero-filled UUIDs (e.g. '00000000-0000-0000-0000-000000000000')
12777
+ // Prefer false negatives over false positives for this use case.
12778
+ "IBAN",
12779
+ "BANK_ACCOUNT_UK",
12780
+ "ROUTING_NUMBER_US",
12781
+ "CARD_TRACK1_DATA",
12782
+ "CARD_TRACK2_DATA",
12783
+ "CARD_EXPIRY",
12784
+ // Cryptocurrency (removed BITCOIN_ADDRESS - too broad, matches hash-like strings)
12785
+ "ETHEREUM_ADDRESS",
12786
+ "LITECOIN_ADDRESS",
12787
+ "CARDANO_ADDRESS",
12788
+ "SOLANA_ADDRESS",
12789
+ "MONERO_ADDRESS",
12790
+ "RIPPLE_ADDRESS",
12791
+ // Medical Data (removed PRESCRIPTION_NUMBER - too broad, matches words containing "ription")
12792
+ // Removed MEDICAL_RECORD_NUMBER - too broad, "MR" prefix matches "Merge Request" in SCM contexts (e.g. "MR branches" → "MR br****es")
12793
+ "NHS_NUMBER",
12794
+ "AUSTRALIAN_MEDICARE",
12795
+ "HEALTH_PLAN_NUMBER",
12796
+ "PATIENT_ID",
12797
+ // Communications (removed EMERGENCY_CONTACT, ADDRESS_PO_BOX, ZIP_CODE_US, PHONE_US, PHONE_INTERNATIONAL - too broad, causing false positives)
12798
+ "PHONE_UK",
12799
+ "PHONE_UK_MOBILE",
12800
+ "PHONE_LINE_NUMBER",
12801
+ "ADDRESS_STREET",
12802
+ "POSTCODE_UK",
12803
+ // Network & Technical
12804
+ "IPV4",
12805
+ "IPV6",
12806
+ "MAC_ADDRESS",
12807
+ "URL_WITH_AUTH",
12808
+ // Security Keys & Tokens
12809
+ "PRIVATE_KEY",
12810
+ "SSH_PRIVATE_KEY",
12811
+ "AWS_SECRET_KEY",
12812
+ "AWS_ACCESS_KEY",
12813
+ "AZURE_STORAGE_KEY",
12814
+ "GCP_SERVICE_ACCOUNT",
12815
+ "JWT_TOKEN",
12816
+ "OAUTH_TOKEN",
12817
+ "OAUTH_CLIENT_SECRET",
12818
+ "BEARER_TOKEN",
12819
+ "PAYMENT_TOKEN",
12820
+ "GENERIC_SECRET",
12821
+ "GENERIC_API_KEY",
12822
+ // Platform-Specific API Keys
12823
+ "GITHUB_TOKEN",
12824
+ "SLACK_TOKEN",
12825
+ "STRIPE_API_KEY",
12826
+ "GOOGLE_API_KEY",
12827
+ "FIREBASE_API_KEY",
12828
+ "HEROKU_API_KEY",
12829
+ "MAILGUN_API_KEY",
12830
+ "SENDGRID_API_KEY",
12831
+ "TWILIO_API_KEY",
12832
+ "NPM_TOKEN",
12833
+ "PYPI_TOKEN",
12834
+ "DOCKER_AUTH",
12835
+ "KUBERNETES_SECRET",
12836
+ // Government & Legal
12837
+ // Removed CLIENT_ID - too broad, "client" is ubiquitous in code (npm packages like @scope/client-*, class names like ClientSdkOptions)
12838
+ "POLICE_REPORT_NUMBER",
12839
+ "IMMIGRATION_NUMBER",
12840
+ "COURT_REPORTER_LICENSE"
12841
+ ]
12842
+ });
12843
+ function maskString(str, showStart = 2, showEnd = 2) {
12844
+ if (str.length <= showStart + showEnd) {
12845
+ return "*".repeat(str.length);
12846
+ }
12847
+ return str.slice(0, showStart) + "*".repeat(str.length - showStart - showEnd) + str.slice(-showEnd);
12848
+ }
12849
+ async function sanitizeDataWithCounts(obj) {
12850
+ const counts = {
12851
+ detections: { total: 0, high: 0, medium: 0, low: 0 }
12852
+ };
12853
+ const sanitizeString = async (str) => {
12854
+ let result = str;
12855
+ const piiDetections = openRedaction.scan(str);
12856
+ if (piiDetections && piiDetections.total > 0) {
12857
+ const allDetections = [
12858
+ ...piiDetections.high,
12859
+ ...piiDetections.medium,
12860
+ ...piiDetections.low
12861
+ ];
12862
+ for (const detection of allDetections) {
12863
+ counts.detections.total++;
12864
+ if (detection.severity === "high") counts.detections.high++;
12865
+ else if (detection.severity === "medium") counts.detections.medium++;
12866
+ else if (detection.severity === "low") counts.detections.low++;
12867
+ const masked = maskString(detection.value);
12868
+ result = result.replaceAll(detection.value, masked);
12650
12869
  }
12651
- } catch (error) {
12652
- return {
12653
- isAuthenticated: false,
12654
- message: error instanceof Error ? error.message : "Unknown authentication error"
12655
- };
12656
12870
  }
12657
- return { isAuthenticated: true, message: "Successfully authenticated" };
12658
- }
12659
- /**
12660
- * Generates a login URL for manual authentication
12661
- */
12662
- async generateLoginUrl(loginContext) {
12663
- try {
12664
- if (!this.gqlClient) {
12665
- this.gqlClient = this.getGQLClient();
12871
+ return result;
12872
+ };
12873
+ const sanitizeRecursive = async (data) => {
12874
+ if (typeof data === "string") {
12875
+ return sanitizeString(data);
12876
+ } else if (Array.isArray(data)) {
12877
+ return Promise.all(data.map((item) => sanitizeRecursive(item)));
12878
+ } else if (data instanceof Error) {
12879
+ return data;
12880
+ } else if (data instanceof Date) {
12881
+ return data;
12882
+ } else if (typeof data === "object" && data !== null) {
12883
+ const sanitized = {};
12884
+ const record = data;
12885
+ for (const key in record) {
12886
+ if (Object.prototype.hasOwnProperty.call(record, key)) {
12887
+ sanitized[key] = await sanitizeRecursive(record[key]);
12888
+ }
12666
12889
  }
12667
- const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
12668
- modulusLength: 2048
12669
- });
12670
- this.publicKey = publicKey;
12671
- this.privateKey = privateKey;
12672
- this.loginId = await this.gqlClient.createCliLogin({
12673
- publicKey: this.publicKey.export({ format: "pem", type: "pkcs1" }).toString()
12674
- });
12675
- const webLoginUrl = `${this.resolvedWebAppUrl}/cli-login`;
12676
- const browserUrl = loginContext ? buildLoginUrl(webLoginUrl, this.loginId, os.hostname(), loginContext) : `${webLoginUrl}/${this.loginId}?hostname=${os.hostname()}`;
12677
- this.currentBrowserUrl = browserUrl;
12678
- return browserUrl;
12679
- } catch (error) {
12680
- console.error("Failed to generate login URL:", error);
12681
- return null;
12890
+ return sanitized;
12891
+ }
12892
+ return data;
12893
+ };
12894
+ const sanitizedData = await sanitizeRecursive(obj);
12895
+ return { sanitizedData, counts };
12896
+ }
12897
+
12898
+ // src/args/commands/upload_ai_blame.ts
12899
+ var defaultLogger2 = {
12900
+ info: (msg, data) => {
12901
+ if (data !== void 0) {
12902
+ console.log(msg, data);
12903
+ } else {
12904
+ console.log(msg);
12905
+ }
12906
+ },
12907
+ error: (msg, data) => {
12908
+ if (data !== void 0) {
12909
+ console.error(msg, data);
12910
+ } else {
12911
+ console.error(msg);
12682
12912
  }
12683
12913
  }
12684
- /**
12685
- * Retrieves and decrypts the API token after authentication
12686
- */
12687
- async getApiToken() {
12688
- if (!this.gqlClient || !this.loginId || !this.privateKey) {
12914
+ };
12915
+ var PromptItemZ = z27.object({
12916
+ type: z27.enum([
12917
+ "USER_PROMPT",
12918
+ "AI_RESPONSE",
12919
+ "TOOL_EXECUTION",
12920
+ "AI_THINKING",
12921
+ "MCP_TOOL_CALL"
12922
+ // MCP (Model Context Protocol) tool invocation
12923
+ ]),
12924
+ attachedFiles: z27.array(
12925
+ z27.object({
12926
+ relativePath: z27.string(),
12927
+ startLine: z27.number().optional()
12928
+ })
12929
+ ).optional(),
12930
+ tokens: z27.object({
12931
+ inputCount: z27.number(),
12932
+ outputCount: z27.number()
12933
+ }).optional(),
12934
+ text: z27.string().optional(),
12935
+ date: z27.date().optional(),
12936
+ tool: z27.object({
12937
+ name: z27.string(),
12938
+ parameters: z27.string(),
12939
+ result: z27.string(),
12940
+ rawArguments: z27.string().optional(),
12941
+ accepted: z27.boolean().optional(),
12942
+ // MCP-specific fields (only populated for MCP_TOOL_CALL type)
12943
+ mcpServer: z27.string().optional(),
12944
+ // MCP server name (e.g., "datadog", "mobb-mcp")
12945
+ mcpToolName: z27.string().optional()
12946
+ // MCP tool name without prefix (e.g., "scan_and_fix_vulnerabilities")
12947
+ }).optional()
12948
+ });
12949
+ var PromptItemArrayZ = z27.array(PromptItemZ);
12950
+ async function getRepositoryUrl() {
12951
+ try {
12952
+ const gitService = new GitService(process.cwd());
12953
+ const isRepo = await gitService.isGitRepository();
12954
+ if (!isRepo) {
12689
12955
  return null;
12690
12956
  }
12691
- const encryptedApiToken = await this.gqlClient.getEncryptedApiToken({
12692
- loginId: this.loginId
12693
- });
12694
- if (encryptedApiToken) {
12695
- return crypto.privateDecrypt(
12696
- this.privateKey,
12697
- Buffer.from(encryptedApiToken, "base64")
12698
- ).toString("utf-8");
12699
- }
12957
+ const remoteUrl = await gitService.getRemoteUrl();
12958
+ const parsed = parseScmURL(remoteUrl);
12959
+ return parsed?.scmType === "GitHub" /* GitHub */ || parsed?.scmType === "GitLab" /* GitLab */ ? remoteUrl : null;
12960
+ } catch {
12700
12961
  return null;
12701
12962
  }
12702
- /**
12703
- * Gets the current GQL client (if authenticated)
12704
- */
12705
- getGQLClient(inputApiKey) {
12706
- if (this.gqlClient === void 0) {
12707
- this.gqlClient = new GQLClient({
12708
- apiKey: inputApiKey || configStore.get("apiToken") || "",
12709
- type: "apiKey",
12710
- apiUrl: this.resolvedApiUrl
12963
+ }
12964
+ function getSystemInfo() {
12965
+ let userName;
12966
+ try {
12967
+ userName = os2.userInfo().username;
12968
+ } catch {
12969
+ userName = void 0;
12970
+ }
12971
+ return {
12972
+ computerName: getStableComputerName(),
12973
+ userName
12974
+ };
12975
+ }
12976
+ function uploadAiBlameBuilder(args) {
12977
+ return args.option("prompt", {
12978
+ type: "string",
12979
+ array: true,
12980
+ demandOption: true,
12981
+ describe: chalk3.bold("Path(s) to prompt artifact(s) (one per session)")
12982
+ }).option("inference", {
12983
+ type: "string",
12984
+ array: true,
12985
+ demandOption: true,
12986
+ describe: chalk3.bold(
12987
+ "Path(s) to inference artifact(s) (one per session)"
12988
+ )
12989
+ }).option("ai-response-at", {
12990
+ type: "string",
12991
+ array: true,
12992
+ describe: chalk3.bold(
12993
+ "ISO timestamp(s) for AI response (one per session, defaults to now)"
12994
+ )
12995
+ }).option("model", {
12996
+ type: "string",
12997
+ array: true,
12998
+ describe: chalk3.bold("AI model name(s) (optional, one per session)")
12999
+ }).option("tool-name", {
13000
+ type: "string",
13001
+ array: true,
13002
+ describe: chalk3.bold("Tool/IDE name(s) (optional, one per session)")
13003
+ }).option("blame-type", {
13004
+ type: "string",
13005
+ array: true,
13006
+ choices: Object.values(AiBlameInferenceType),
13007
+ describe: chalk3.bold(
13008
+ "Blame type(s) (optional, one per session, defaults to CHAT)"
13009
+ )
13010
+ }).strict();
13011
+ }
13012
+ async function uploadAiBlameHandlerFromExtension(args) {
13013
+ const uploadArgs = {
13014
+ prompt: [],
13015
+ inference: [],
13016
+ model: [],
13017
+ toolName: [],
13018
+ aiResponseAt: [],
13019
+ blameType: [],
13020
+ sessionId: []
13021
+ };
13022
+ let promptsCounts;
13023
+ let inferenceCounts;
13024
+ let promptsUUID;
13025
+ let inferenceUUID;
13026
+ await withFile(async (promptFile) => {
13027
+ const promptsResult = await sanitizeDataWithCounts(args.prompts);
13028
+ promptsCounts = promptsResult.counts;
13029
+ promptsUUID = path7.basename(promptFile.path, path7.extname(promptFile.path));
13030
+ await fsPromises2.writeFile(
13031
+ promptFile.path,
13032
+ JSON.stringify(promptsResult.sanitizedData, null, 2),
13033
+ "utf-8"
13034
+ );
13035
+ uploadArgs.prompt.push(promptFile.path);
13036
+ await withFile(async (inferenceFile) => {
13037
+ const inferenceResult = await sanitizeDataWithCounts(args.inference);
13038
+ inferenceCounts = inferenceResult.counts;
13039
+ inferenceUUID = path7.basename(
13040
+ inferenceFile.path,
13041
+ path7.extname(inferenceFile.path)
13042
+ );
13043
+ await fsPromises2.writeFile(
13044
+ inferenceFile.path,
13045
+ inferenceResult.sanitizedData,
13046
+ "utf-8"
13047
+ );
13048
+ uploadArgs.inference.push(inferenceFile.path);
13049
+ uploadArgs.model.push(args.model);
13050
+ uploadArgs.toolName.push(args.tool);
13051
+ uploadArgs.aiResponseAt.push(args.responseTime);
13052
+ uploadArgs.blameType.push(args.blameType || "CHAT" /* Chat */);
13053
+ if (args.sessionId) {
13054
+ uploadArgs.sessionId.push(args.sessionId);
13055
+ }
13056
+ await uploadAiBlameHandler({
13057
+ args: uploadArgs,
13058
+ exitOnError: false,
13059
+ apiUrl: args.apiUrl,
13060
+ webAppUrl: args.webAppUrl,
13061
+ repositoryUrl: args.repositoryUrl
12711
13062
  });
13063
+ });
13064
+ });
13065
+ return {
13066
+ promptsCounts,
13067
+ inferenceCounts,
13068
+ promptsUUID,
13069
+ inferenceUUID
13070
+ };
13071
+ }
13072
+ async function uploadAiBlameHandler(options) {
13073
+ const {
13074
+ args,
13075
+ exitOnError = true,
13076
+ apiUrl,
13077
+ webAppUrl,
13078
+ logger: logger2 = defaultLogger2
13079
+ } = options;
13080
+ const prompts = args.prompt || [];
13081
+ const inferences = args.inference || [];
13082
+ const models = args.model || [];
13083
+ const tools = args.toolName || args["tool-name"] || [];
13084
+ const responseTimes = args.aiResponseAt || args["ai-response-at"] || [];
13085
+ const blameTypes = args.blameType || args["blame-type"] || [];
13086
+ const sessionIds = args.sessionId || args["session-id"] || [];
13087
+ if (prompts.length !== inferences.length) {
13088
+ const errorMsg = "prompt and inference must have the same number of entries";
13089
+ logger2.error(chalk3.red(errorMsg));
13090
+ if (exitOnError) {
13091
+ process.exit(1);
12712
13092
  }
12713
- return this.gqlClient;
12714
- }
12715
- /**
12716
- * Assigns a GQL client instance to the AuthManager, and resets auth state
12717
- * @param gqlClient The GQL client instance to set
12718
- */
12719
- setGQLClient(gqlClient) {
12720
- this.gqlClient = gqlClient;
12721
- this.cleanup();
13093
+ throw new Error(errorMsg);
12722
13094
  }
12723
- /**
12724
- * Cleans up any active login session
12725
- */
12726
- cleanup() {
12727
- this.publicKey = void 0;
12728
- this.privateKey = void 0;
12729
- this.loginId = void 0;
12730
- this.authenticated = null;
12731
- this.currentBrowserUrl = null;
13095
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
13096
+ const { computerName, userName } = getSystemInfo();
13097
+ const repositoryUrl = options.repositoryUrl !== void 0 ? options.repositoryUrl : await getRepositoryUrl();
13098
+ const sessions = [];
13099
+ for (let i = 0; i < prompts.length; i++) {
13100
+ const promptPath = String(prompts[i]);
13101
+ const inferencePath = String(inferences[i]);
13102
+ try {
13103
+ await Promise.all([
13104
+ fsPromises2.access(promptPath),
13105
+ fsPromises2.access(inferencePath)
13106
+ ]);
13107
+ } catch {
13108
+ const errorMsg = `File not found for session ${i + 1}`;
13109
+ logger2.error(chalk3.red(errorMsg));
13110
+ if (exitOnError) {
13111
+ process.exit(1);
13112
+ }
13113
+ throw new Error(errorMsg);
13114
+ }
13115
+ sessions.push({
13116
+ promptFileName: path7.basename(promptPath),
13117
+ inferenceFileName: path7.basename(inferencePath),
13118
+ aiResponseAt: responseTimes[i] || nowIso,
13119
+ model: models[i],
13120
+ toolName: tools[i],
13121
+ blameType: blameTypes[i] || "CHAT" /* Chat */,
13122
+ computerName,
13123
+ userName,
13124
+ sessionId: sessionIds[i],
13125
+ repositoryUrl
13126
+ });
12732
13127
  }
12733
- };
12734
-
12735
- // src/commands/handleMobbLogin.ts
12736
- var debug8 = Debug7("mobbdev:commands");
12737
- var LOGIN_MAX_WAIT2 = 10 * 60 * 1e3;
12738
- var LOGIN_CHECK_DELAY2 = 5 * 1e3;
12739
- 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(
12740
- "press any key to continue"
12741
- )};`;
12742
- async function getAuthenticatedGQLClient({
12743
- inputApiKey = "",
12744
- isSkipPrompts = true,
12745
- apiUrl,
12746
- webAppUrl
12747
- }) {
12748
- debug8(
12749
- "getAuthenticatedGQLClient called with: apiUrl=%s, webAppUrl=%s",
12750
- apiUrl || "undefined",
12751
- webAppUrl || "undefined"
12752
- );
12753
- const authManager = new AuthManager(webAppUrl, apiUrl);
12754
- let gqlClient = authManager.getGQLClient(inputApiKey);
12755
- gqlClient = await handleMobbLogin({
12756
- inGqlClient: gqlClient,
12757
- skipPrompts: isSkipPrompts,
13128
+ const authenticatedClient = await getAuthenticatedGQLClient({
13129
+ isSkipPrompts: true,
12758
13130
  apiUrl,
12759
13131
  webAppUrl
12760
13132
  });
12761
- return gqlClient;
12762
- }
12763
- async function handleMobbLogin({
12764
- inGqlClient,
12765
- apiKey,
12766
- skipPrompts,
12767
- apiUrl,
12768
- webAppUrl,
12769
- loginContext
12770
- }) {
12771
- debug8(
12772
- "handleMobbLogin: resolved URLs - apiUrl=%s (from param: %s), webAppUrl=%s (from param: %s)",
12773
- apiUrl || "fallback",
12774
- apiUrl || "fallback",
12775
- webAppUrl || "fallback",
12776
- webAppUrl || "fallback"
13133
+ const initSessions = sessions.map(
13134
+ ({ sessionId: _sessionId, ...rest }) => rest
12777
13135
  );
12778
- const { createSpinner: createSpinner5 } = Spinner({ ci: skipPrompts });
12779
- const authManager = new AuthManager(webAppUrl, apiUrl);
12780
- authManager.setGQLClient(inGqlClient);
12781
- try {
12782
- const isAuthenticated = await authManager.isAuthenticated();
12783
- if (isAuthenticated) {
12784
- createSpinner5().start().success({
12785
- text: `\u{1F513} Login to Mobb succeeded. Already authenticated`
12786
- });
12787
- return authManager.getGQLClient();
13136
+ const initRes = await authenticatedClient.uploadAIBlameInferencesInitRaw({
13137
+ sessions: initSessions
13138
+ });
13139
+ const uploadSessions = initRes.uploadAIBlameInferencesInit?.uploadSessions ?? [];
13140
+ if (uploadSessions.length !== sessions.length) {
13141
+ const errorMsg = "Init failed to return expected number of sessions";
13142
+ logger2.error(chalk3.red(errorMsg));
13143
+ if (exitOnError) {
13144
+ process.exit(1);
12788
13145
  }
12789
- } catch (error) {
12790
- debug8("Authentication check failed:", error);
13146
+ throw new Error(errorMsg);
12791
13147
  }
12792
- if (apiKey) {
12793
- createSpinner5().start().error({
12794
- text: "\u{1F513} Login to Mobb failed: The provided API key does not match any configured API key on the system"
12795
- });
12796
- throw new CliError(
12797
- "Login to Mobb failed: The provided API key does not match any configured API key on the system"
12798
- );
13148
+ for (let i = 0; i < uploadSessions.length; i++) {
13149
+ const us = uploadSessions[i];
13150
+ const promptPath = String(prompts[i]);
13151
+ const inferencePath = String(inferences[i]);
13152
+ await Promise.all([
13153
+ // Prompt
13154
+ uploadFile({
13155
+ file: promptPath,
13156
+ url: us.prompt.url,
13157
+ uploadFields: JSON.parse(us.prompt.uploadFieldsJSON),
13158
+ uploadKey: us.prompt.uploadKey
13159
+ }),
13160
+ // Inference
13161
+ uploadFile({
13162
+ file: inferencePath,
13163
+ url: us.inference.url,
13164
+ uploadFields: JSON.parse(us.inference.uploadFieldsJSON),
13165
+ uploadKey: us.inference.uploadKey
13166
+ })
13167
+ ]);
12799
13168
  }
12800
- const loginSpinner = createSpinner5().start();
12801
- if (!skipPrompts) {
12802
- loginSpinner.update({ text: MOBB_LOGIN_REQUIRED_MSG });
12803
- await keypress();
12804
- }
12805
- loginSpinner.update({
12806
- text: "\u{1F513} Waiting for Mobb login..."
13169
+ const finalizeSessions = uploadSessions.map((us, i) => {
13170
+ const s = sessions[i];
13171
+ return {
13172
+ aiBlameInferenceId: us.aiBlameInferenceId,
13173
+ promptKey: us.prompt.uploadKey,
13174
+ inferenceKey: us.inference.uploadKey,
13175
+ aiResponseAt: s.aiResponseAt,
13176
+ model: s.model,
13177
+ toolName: s.toolName,
13178
+ blameType: s.blameType,
13179
+ computerName: s.computerName,
13180
+ userName: s.userName,
13181
+ sessionId: s.sessionId,
13182
+ repositoryUrl: s.repositoryUrl
13183
+ };
12807
13184
  });
12808
13185
  try {
12809
- const loginUrl = await authManager.generateLoginUrl(loginContext);
12810
- if (!loginUrl) {
12811
- loginSpinner.error({
12812
- text: "Failed to generate login URL"
12813
- });
12814
- throw new CliError("Failed to generate login URL");
12815
- }
12816
- !skipPrompts && console.log(
12817
- `If the page does not open automatically, kindly access it through ${loginUrl}.`
13186
+ logger2.info(
13187
+ `[UPLOAD] Calling finalizeAIBlameInferencesUploadRaw with ${finalizeSessions.length} sessions`
12818
13188
  );
12819
- authManager.openUrlInBrowser();
12820
- const authSuccess = await authManager.waitForAuthentication();
12821
- if (!authSuccess) {
12822
- loginSpinner.error({
12823
- text: "Login timeout error"
12824
- });
12825
- throw new CliError("Login timeout error");
13189
+ const finRes = await authenticatedClient.finalizeAIBlameInferencesUploadRaw(
13190
+ {
13191
+ sessions: finalizeSessions
13192
+ }
13193
+ );
13194
+ logger2.info("[UPLOAD] Finalize response:", JSON.stringify(finRes, null, 2));
13195
+ const status = finRes?.finalizeAIBlameInferencesUpload?.status;
13196
+ if (status !== "OK") {
13197
+ const errorMsg = finRes?.finalizeAIBlameInferencesUpload?.error || "unknown error";
13198
+ logger2.error(
13199
+ chalk3.red(
13200
+ `[UPLOAD] Finalize failed with status: ${status}, error: ${errorMsg}`
13201
+ )
13202
+ );
13203
+ if (exitOnError) {
13204
+ process.exit(1);
13205
+ }
13206
+ throw new Error(errorMsg);
12826
13207
  }
12827
- loginSpinner.success({
12828
- text: `\u{1F513} Login to Mobb successful!`
12829
- });
12830
- return authManager.getGQLClient();
12831
- } finally {
12832
- authManager.cleanup();
13208
+ logger2.info(chalk3.green("[UPLOAD] AI Blame uploads finalized successfully"));
13209
+ } catch (error) {
13210
+ logger2.error("[UPLOAD] Finalize threw error:", error);
13211
+ throw error;
12833
13212
  }
12834
13213
  }
13214
+ async function uploadAiBlameCommandHandler(args) {
13215
+ await uploadAiBlameHandler({ args });
13216
+ }
12835
13217
 
12836
- // src/features/analysis/add_fix_comments_for_pr/add_fix_comments_for_pr.ts
12837
- import Debug11 from "debug";
12838
-
12839
- // src/features/analysis/add_fix_comments_for_pr/utils/utils.ts
12840
- import Debug10 from "debug";
12841
- import parseDiff from "parse-diff";
12842
- import { z as z28 } from "zod";
13218
+ // src/features/analysis/graphql/tracy-batch-upload.ts
13219
+ var gzipAsync = promisify(gzip);
13220
+ var debug9 = Debug8("mobbdev:tracy-batch-upload");
13221
+ var MAX_BATCH_PAYLOAD_BYTES = 3 * 1024 * 1024;
12843
13222
 
12844
- // src/features/analysis/utils/by_key.ts
12845
- function keyBy(array, keyBy2) {
12846
- return array.reduce((acc, item) => {
12847
- return { ...acc, [item[keyBy2]]: item };
12848
- }, {});
13223
+ // src/mcp/services/types.ts
13224
+ function detectIDE() {
13225
+ const env3 = process.env;
13226
+ if (env3["CURSOR_TRACE_ID"] || env3["CURSOR_SESSION_ID"]) return "cursor";
13227
+ if (env3["WINDSURF_IPC_HOOK"] || env3["WINDSURF_PID"]) return "windsurf";
13228
+ if (env3["CLAUDE_DESKTOP"] || env3["ANTHROPIC_CLAUDE"]) return "claude";
13229
+ if (env3["WEBSTORM_VM_OPTIONS"] || env3["IDEA_VM_OPTIONS"] || env3["JETBRAINS_IDE"])
13230
+ return "webstorm";
13231
+ if (env3["VSCODE_IPC_HOOK"] || env3["VSCODE_PID"]) return "vscode";
13232
+ const termProgram = env3["TERM_PROGRAM"]?.toLowerCase();
13233
+ if (termProgram === "windsurf") return "windsurf";
13234
+ if (termProgram === "vscode") return "vscode";
13235
+ return void 0;
13236
+ }
13237
+ function createMcpLoginContext(trigger) {
13238
+ return {
13239
+ trigger,
13240
+ source: "mcp",
13241
+ ide: detectIDE()
13242
+ };
13243
+ }
13244
+ function buildLoginUrl(baseUrl, loginId, hostname, context) {
13245
+ const url = new URL(`${baseUrl}/${loginId}`);
13246
+ url.searchParams.set("hostname", hostname);
13247
+ url.searchParams.set("trigger", context.trigger);
13248
+ url.searchParams.set("source", context.source);
13249
+ if (context.ide) {
13250
+ url.searchParams.set("ide", context.ide);
13251
+ }
13252
+ return url.toString();
12849
13253
  }
12850
13254
 
12851
- // src/features/analysis/utils/send_report.ts
12852
- import Debug8 from "debug";
12853
- init_client_generates();
12854
- var debug9 = Debug8("mobbdev:index");
12855
- async function sendReport({
12856
- spinner,
12857
- submitVulnerabilityReportVariables,
12858
- gqlClient,
12859
- polling
12860
- }) {
12861
- try {
12862
- const submitRes = await gqlClient.submitVulnerabilityReport(
12863
- submitVulnerabilityReportVariables
12864
- );
12865
- if (submitRes.submitVulnerabilityReport.__typename !== "VulnerabilityReport") {
12866
- debug9("error submit vul report %s", submitRes);
12867
- throw new Error("\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed");
13255
+ // src/commands/AuthManager.ts
13256
+ var debug10 = Debug9("mobbdev:auth");
13257
+ var LOGIN_MAX_WAIT = 10 * 60 * 1e3;
13258
+ var LOGIN_CHECK_DELAY = 5 * 1e3;
13259
+ var _AuthManager = class _AuthManager {
13260
+ constructor(webAppUrl, apiUrl) {
13261
+ __publicField(this, "publicKey");
13262
+ __publicField(this, "privateKey");
13263
+ __publicField(this, "loginId");
13264
+ __publicField(this, "gqlClient");
13265
+ __publicField(this, "currentBrowserUrl");
13266
+ __publicField(this, "authenticated", null);
13267
+ __publicField(this, "resolvedWebAppUrl");
13268
+ __publicField(this, "resolvedApiUrl");
13269
+ this.resolvedWebAppUrl = webAppUrl || WEB_APP_URL;
13270
+ this.resolvedApiUrl = apiUrl || API_URL;
13271
+ }
13272
+ openUrlInBrowser() {
13273
+ if (this.currentBrowserUrl) {
13274
+ open(this.currentBrowserUrl);
13275
+ return true;
12868
13276
  }
12869
- spinner.update({ text: progressMassages.processingVulnerabilityReport });
12870
- const callback = (_analysisId) => spinner.update({
12871
- text: "\u2699\uFE0F Vulnerability report processed successfully"
13277
+ return false;
13278
+ }
13279
+ async waitForAuthentication() {
13280
+ let newApiToken = null;
13281
+ for (let i = 0; i < _AuthManager.loginMaxWait / LOGIN_CHECK_DELAY; i++) {
13282
+ newApiToken = await this.getApiToken();
13283
+ if (newApiToken) {
13284
+ break;
13285
+ }
13286
+ await sleep(LOGIN_CHECK_DELAY);
13287
+ }
13288
+ if (!newApiToken) {
13289
+ return false;
13290
+ }
13291
+ this.gqlClient = new GQLClient({
13292
+ apiKey: newApiToken,
13293
+ type: "apiKey",
13294
+ apiUrl: this.resolvedApiUrl
12872
13295
  });
12873
- const callbackStates = [
12874
- "Digested" /* Digested */,
12875
- "Finished" /* Finished */
12876
- ];
12877
- if (polling) {
12878
- debug9("[sendReport] Using POLLING mode for analysis state updates");
12879
- await gqlClient.pollForAnalysisState({
12880
- analysisId: submitRes.submitVulnerabilityReport.fixReportId,
12881
- callback,
12882
- callbackStates,
12883
- timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
13296
+ const loginSuccess = await this.gqlClient.validateUserToken();
13297
+ if (loginSuccess) {
13298
+ configStore.set("apiToken", newApiToken);
13299
+ this.authenticated = true;
13300
+ return true;
13301
+ }
13302
+ return false;
13303
+ }
13304
+ /**
13305
+ * Checks if the user is already authenticated
13306
+ */
13307
+ async isAuthenticated() {
13308
+ if (this.authenticated === null) {
13309
+ const result = await this.checkAuthentication();
13310
+ this.authenticated = result.isAuthenticated;
13311
+ if (!result.isAuthenticated) {
13312
+ debug10("isAuthenticated: false \u2014 %s", result.message);
13313
+ }
13314
+ }
13315
+ return this.authenticated;
13316
+ }
13317
+ /**
13318
+ * Private function to check if the user is authenticated with the server
13319
+ */
13320
+ async checkAuthentication(apiKey) {
13321
+ try {
13322
+ if (!this.gqlClient) {
13323
+ this.gqlClient = this.getGQLClient(apiKey);
13324
+ }
13325
+ const isConnected = await this.gqlClient.verifyApiConnection();
13326
+ if (!isConnected) {
13327
+ return {
13328
+ isAuthenticated: false,
13329
+ message: "Failed to connect to Mobb server"
13330
+ };
13331
+ }
13332
+ const userVerify = await this.gqlClient.validateUserToken();
13333
+ if (!userVerify) {
13334
+ return {
13335
+ isAuthenticated: false,
13336
+ message: "User token validation failed"
13337
+ };
13338
+ }
13339
+ } catch (error) {
13340
+ return {
13341
+ isAuthenticated: false,
13342
+ message: error instanceof Error ? error.message : "Unknown authentication error"
13343
+ };
13344
+ }
13345
+ return { isAuthenticated: true, message: "Successfully authenticated" };
13346
+ }
13347
+ /**
13348
+ * Generates a login URL for manual authentication
13349
+ */
13350
+ async generateLoginUrl(loginContext) {
13351
+ try {
13352
+ if (!this.gqlClient) {
13353
+ this.gqlClient = this.getGQLClient();
13354
+ }
13355
+ const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
13356
+ modulusLength: 2048
12884
13357
  });
12885
- } else {
12886
- debug9("[sendReport] Using WEBSOCKET mode for analysis state updates");
12887
- await gqlClient.subscribeToAnalysis({
12888
- subscribeToAnalysisParams: {
12889
- analysisId: submitRes.submitVulnerabilityReport.fixReportId
12890
- },
12891
- callback,
12892
- callbackStates,
12893
- timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
13358
+ this.publicKey = publicKey;
13359
+ this.privateKey = privateKey;
13360
+ this.loginId = await this.gqlClient.createCliLogin({
13361
+ publicKey: this.publicKey.export({ format: "pem", type: "pkcs1" }).toString()
12894
13362
  });
13363
+ const webLoginUrl = `${this.resolvedWebAppUrl}/cli-login`;
13364
+ const browserUrl = loginContext ? buildLoginUrl(webLoginUrl, this.loginId, os3.hostname(), loginContext) : `${webLoginUrl}/${this.loginId}?hostname=${os3.hostname()}`;
13365
+ this.currentBrowserUrl = browserUrl;
13366
+ return browserUrl;
13367
+ } catch (error) {
13368
+ console.error("Failed to generate login URL:", error);
13369
+ return null;
12895
13370
  }
12896
- return submitRes;
12897
- } catch (e) {
12898
- spinner.error({ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed" });
12899
- throw e;
12900
13371
  }
12901
- }
12902
-
12903
- // src/features/analysis/utils/index.ts
13372
+ /**
13373
+ * Retrieves and decrypts the API token after authentication
13374
+ */
13375
+ async getApiToken() {
13376
+ if (!this.gqlClient || !this.loginId || !this.privateKey) {
13377
+ return null;
13378
+ }
13379
+ const encryptedApiToken = await this.gqlClient.getEncryptedApiToken({
13380
+ loginId: this.loginId
13381
+ });
13382
+ if (encryptedApiToken) {
13383
+ return crypto.privateDecrypt(
13384
+ this.privateKey,
13385
+ Buffer.from(encryptedApiToken, "base64")
13386
+ ).toString("utf-8");
13387
+ }
13388
+ return null;
13389
+ }
13390
+ /**
13391
+ * Returns true if a non-empty API token is stored in the configStore.
13392
+ * Used for diagnostics — does NOT validate the token.
13393
+ */
13394
+ hasStoredToken() {
13395
+ const token = configStore.get("apiToken");
13396
+ return typeof token === "string" && token.length > 0;
13397
+ }
13398
+ /**
13399
+ * Gets the current GQL client (if authenticated)
13400
+ */
13401
+ getGQLClient(inputApiKey) {
13402
+ if (this.gqlClient === void 0) {
13403
+ this.gqlClient = new GQLClient({
13404
+ apiKey: inputApiKey || configStore.get("apiToken") || "",
13405
+ type: "apiKey",
13406
+ apiUrl: this.resolvedApiUrl
13407
+ });
13408
+ }
13409
+ return this.gqlClient;
13410
+ }
13411
+ /**
13412
+ * Assigns a GQL client instance to the AuthManager, and resets auth state
13413
+ * @param gqlClient The GQL client instance to set
13414
+ */
13415
+ setGQLClient(gqlClient) {
13416
+ this.gqlClient = gqlClient;
13417
+ this.cleanup();
13418
+ }
13419
+ /**
13420
+ * Cleans up any active login session
13421
+ */
13422
+ cleanup() {
13423
+ this.publicKey = void 0;
13424
+ this.privateKey = void 0;
13425
+ this.loginId = void 0;
13426
+ this.authenticated = null;
13427
+ this.currentBrowserUrl = null;
13428
+ }
13429
+ };
13430
+ /** Maximum time (ms) to wait for login authentication. Override in tests for faster failures. */
13431
+ __publicField(_AuthManager, "loginMaxWait", LOGIN_MAX_WAIT);
13432
+ var AuthManager = _AuthManager;
13433
+
13434
+ // src/commands/handleMobbLogin.ts
13435
+ var debug11 = Debug10("mobbdev:commands");
13436
+ var LOGIN_MAX_WAIT2 = 10 * 60 * 1e3;
13437
+ var LOGIN_CHECK_DELAY2 = 5 * 1e3;
13438
+ 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, ${chalk4.bgBlue(
13439
+ "press any key to continue"
13440
+ )};`;
13441
+ async function getAuthenticatedGQLClient({
13442
+ inputApiKey = "",
13443
+ isSkipPrompts = true,
13444
+ apiUrl,
13445
+ webAppUrl
13446
+ }) {
13447
+ debug11(
13448
+ "getAuthenticatedGQLClient called with: apiUrl=%s, webAppUrl=%s",
13449
+ apiUrl || "undefined",
13450
+ webAppUrl || "undefined"
13451
+ );
13452
+ const authManager = new AuthManager(webAppUrl, apiUrl);
13453
+ let gqlClient = authManager.getGQLClient(inputApiKey);
13454
+ gqlClient = await handleMobbLogin({
13455
+ inGqlClient: gqlClient,
13456
+ skipPrompts: isSkipPrompts,
13457
+ apiUrl,
13458
+ webAppUrl
13459
+ });
13460
+ return gqlClient;
13461
+ }
13462
+ async function handleMobbLogin({
13463
+ inGqlClient,
13464
+ apiKey,
13465
+ skipPrompts,
13466
+ apiUrl,
13467
+ webAppUrl,
13468
+ loginContext
13469
+ }) {
13470
+ debug11(
13471
+ "handleMobbLogin: resolved URLs - apiUrl=%s (from param: %s), webAppUrl=%s (from param: %s)",
13472
+ apiUrl || "fallback",
13473
+ apiUrl || "fallback",
13474
+ webAppUrl || "fallback",
13475
+ webAppUrl || "fallback"
13476
+ );
13477
+ const { createSpinner: createSpinner5 } = Spinner({ ci: skipPrompts });
13478
+ const authManager = new AuthManager(webAppUrl, apiUrl);
13479
+ authManager.setGQLClient(inGqlClient);
13480
+ try {
13481
+ const isAuthenticated = await authManager.isAuthenticated();
13482
+ if (isAuthenticated) {
13483
+ createSpinner5().start().success({
13484
+ text: `\u{1F513} Login to Mobb succeeded. Already authenticated`
13485
+ });
13486
+ return authManager.getGQLClient();
13487
+ }
13488
+ } catch (error) {
13489
+ debug11("Authentication check failed:", error);
13490
+ }
13491
+ if (apiKey) {
13492
+ createSpinner5().start().error({
13493
+ text: "\u{1F513} Login to Mobb failed: The provided API key does not match any configured API key on the system"
13494
+ });
13495
+ throw new CliError(
13496
+ "Login to Mobb failed: The provided API key does not match any configured API key on the system"
13497
+ );
13498
+ }
13499
+ const loginSpinner = createSpinner5().start();
13500
+ if (!skipPrompts) {
13501
+ loginSpinner.update({ text: MOBB_LOGIN_REQUIRED_MSG });
13502
+ await keypress();
13503
+ }
13504
+ loginSpinner.update({
13505
+ text: "\u{1F513} Waiting for Mobb login..."
13506
+ });
13507
+ try {
13508
+ const loginUrl = await authManager.generateLoginUrl(loginContext);
13509
+ if (!loginUrl) {
13510
+ loginSpinner.error({
13511
+ text: "Failed to generate login URL"
13512
+ });
13513
+ throw new CliError("Failed to generate login URL");
13514
+ }
13515
+ !skipPrompts && console.log(
13516
+ `If the page does not open automatically, kindly access it through ${loginUrl}.`
13517
+ );
13518
+ authManager.openUrlInBrowser();
13519
+ const authSuccess = await authManager.waitForAuthentication();
13520
+ if (!authSuccess) {
13521
+ loginSpinner.error({
13522
+ text: "Login timeout error"
13523
+ });
13524
+ throw new CliError("Login timeout error");
13525
+ }
13526
+ loginSpinner.success({
13527
+ text: `\u{1F513} Login to Mobb successful!`
13528
+ });
13529
+ return authManager.getGQLClient();
13530
+ } finally {
13531
+ authManager.cleanup();
13532
+ }
13533
+ }
13534
+
13535
+ // src/features/analysis/add_fix_comments_for_pr/add_fix_comments_for_pr.ts
13536
+ import Debug14 from "debug";
13537
+
13538
+ // src/features/analysis/add_fix_comments_for_pr/utils/utils.ts
13539
+ import Debug13 from "debug";
13540
+ import parseDiff from "parse-diff";
13541
+ import { z as z29 } from "zod";
13542
+
13543
+ // src/features/analysis/utils/by_key.ts
13544
+ function keyBy(array, keyBy2) {
13545
+ return array.reduce((acc, item) => {
13546
+ return { ...acc, [item[keyBy2]]: item };
13547
+ }, {});
13548
+ }
13549
+
13550
+ // src/features/analysis/utils/send_report.ts
13551
+ import Debug11 from "debug";
13552
+ init_client_generates();
13553
+ var debug12 = Debug11("mobbdev:index");
13554
+ async function sendReport({
13555
+ spinner,
13556
+ submitVulnerabilityReportVariables,
13557
+ gqlClient,
13558
+ polling
13559
+ }) {
13560
+ try {
13561
+ const submitRes = await gqlClient.submitVulnerabilityReport(
13562
+ submitVulnerabilityReportVariables
13563
+ );
13564
+ if (submitRes.submitVulnerabilityReport.__typename !== "VulnerabilityReport") {
13565
+ debug12("error submit vul report %s", submitRes);
13566
+ throw new Error("\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed");
13567
+ }
13568
+ spinner.update({ text: progressMassages.processingVulnerabilityReport });
13569
+ const callback = (_analysisId) => spinner.update({
13570
+ text: "\u2699\uFE0F Vulnerability report processed successfully"
13571
+ });
13572
+ const callbackStates = [
13573
+ "Digested" /* Digested */,
13574
+ "Finished" /* Finished */
13575
+ ];
13576
+ if (polling) {
13577
+ debug12("[sendReport] Using POLLING mode for analysis state updates");
13578
+ await gqlClient.pollForAnalysisState({
13579
+ analysisId: submitRes.submitVulnerabilityReport.fixReportId,
13580
+ callback,
13581
+ callbackStates,
13582
+ timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
13583
+ });
13584
+ } else {
13585
+ debug12("[sendReport] Using WEBSOCKET mode for analysis state updates");
13586
+ await gqlClient.subscribeToAnalysis({
13587
+ subscribeToAnalysisParams: {
13588
+ analysisId: submitRes.submitVulnerabilityReport.fixReportId
13589
+ },
13590
+ callback,
13591
+ callbackStates,
13592
+ timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
13593
+ });
13594
+ }
13595
+ return submitRes;
13596
+ } catch (e) {
13597
+ spinner.error({ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed" });
13598
+ throw e;
13599
+ }
13600
+ }
13601
+
13602
+ // src/features/analysis/utils/index.ts
12904
13603
  function getFromArraySafe(array) {
12905
13604
  return array.reduce((acc, nullableItem) => {
12906
13605
  if (nullableItem) {
@@ -12926,10 +13625,10 @@ var scannerToFriendlyString = {
12926
13625
  };
12927
13626
 
12928
13627
  // src/features/analysis/add_fix_comments_for_pr/utils/buildCommentBody.ts
12929
- import Debug9 from "debug";
12930
- import { z as z27 } from "zod";
13628
+ import Debug12 from "debug";
13629
+ import { z as z28 } from "zod";
12931
13630
  init_client_generates();
12932
- var debug10 = Debug9("mobbdev:handle-finished-analysis");
13631
+ var debug13 = Debug12("mobbdev:handle-finished-analysis");
12933
13632
  var getCommitFixButton = (commitUrl) => `<a href="${commitUrl}"><img src=${COMMIT_FIX_SVG}></a>`;
12934
13633
  function buildFixCommentBody({
12935
13634
  fix,
@@ -12981,14 +13680,14 @@ function buildFixCommentBody({
12981
13680
  });
12982
13681
  const issueType = getIssueTypeFriendlyString(fix.safeIssueType);
12983
13682
  const title = `# ${MobbIconMarkdown} ${issueType} fix is ready`;
12984
- const validFixParseRes = z27.object({
13683
+ const validFixParseRes = z28.object({
12985
13684
  patchAndQuestions: PatchAndQuestionsZ,
12986
- safeIssueLanguage: z27.nativeEnum(IssueLanguage_Enum),
12987
- severityText: z27.nativeEnum(Vulnerability_Severity_Enum),
12988
- safeIssueType: z27.nativeEnum(IssueType_Enum)
13685
+ safeIssueLanguage: z28.nativeEnum(IssueLanguage_Enum),
13686
+ severityText: z28.nativeEnum(Vulnerability_Severity_Enum),
13687
+ safeIssueType: z28.nativeEnum(IssueType_Enum)
12989
13688
  }).safeParse(fix);
12990
13689
  if (!validFixParseRes.success) {
12991
- debug10(
13690
+ debug13(
12992
13691
  `fix ${fixId} has custom issue type or language, therefore the commit description will not be added`,
12993
13692
  validFixParseRes.error
12994
13693
  );
@@ -13052,7 +13751,7 @@ ${issuePageLink}`;
13052
13751
  }
13053
13752
 
13054
13753
  // src/features/analysis/add_fix_comments_for_pr/utils/utils.ts
13055
- var debug11 = Debug10("mobbdev:handle-finished-analysis");
13754
+ var debug14 = Debug13("mobbdev:handle-finished-analysis");
13056
13755
  function calculateRanges(integers) {
13057
13756
  if (integers.length === 0) {
13058
13757
  return [];
@@ -13086,7 +13785,7 @@ function deleteAllPreviousComments({
13086
13785
  try {
13087
13786
  return scm.deleteComment({ comment_id: comment.id });
13088
13787
  } catch (e) {
13089
- debug11("delete comment failed %s", e);
13788
+ debug14("delete comment failed %s", e);
13090
13789
  return Promise.resolve();
13091
13790
  }
13092
13791
  });
@@ -13102,7 +13801,7 @@ function deleteAllPreviousGeneralPrComments(params) {
13102
13801
  try {
13103
13802
  return scm.deleteGeneralPrComment({ commentId: comment.id });
13104
13803
  } catch (e) {
13105
- debug11("delete comment failed %s", e);
13804
+ debug14("delete comment failed %s", e);
13106
13805
  return Promise.resolve();
13107
13806
  }
13108
13807
  });
@@ -13219,7 +13918,7 @@ async function getRelevantVulenrabilitiesFromDiff(params) {
13219
13918
  });
13220
13919
  const lineAddedRanges = calculateRanges(fileNumbers);
13221
13920
  const fileFilter = {
13222
- path: z28.string().parse(file.to),
13921
+ path: z29.string().parse(file.to),
13223
13922
  ranges: lineAddedRanges.map(([startLine, endLine]) => ({
13224
13923
  endLine,
13225
13924
  startLine
@@ -13246,7 +13945,7 @@ async function postAnalysisInsightComment(params) {
13246
13945
  fixablePrVuls,
13247
13946
  nonFixablePrVuls
13248
13947
  } = prVulenrabilities;
13249
- debug11({
13948
+ debug14({
13250
13949
  fixablePrVuls,
13251
13950
  nonFixablePrVuls,
13252
13951
  vulnerabilitiesOutsidePr,
@@ -13301,7 +14000,7 @@ ${contactUsMarkdown}`;
13301
14000
  }
13302
14001
 
13303
14002
  // src/features/analysis/add_fix_comments_for_pr/add_fix_comments_for_pr.ts
13304
- var debug12 = Debug11("mobbdev:handle-finished-analysis");
14003
+ var debug15 = Debug14("mobbdev:handle-finished-analysis");
13305
14004
  async function addFixCommentsForPr({
13306
14005
  analysisId,
13307
14006
  scm: _scm,
@@ -13313,7 +14012,7 @@ async function addFixCommentsForPr({
13313
14012
  }
13314
14013
  const scm = _scm;
13315
14014
  const getAnalysisRes = await gqlClient.getAnalysis(analysisId);
13316
- debug12("getAnalysis %o", getAnalysisRes);
14015
+ debug15("getAnalysis %o", getAnalysisRes);
13317
14016
  const {
13318
14017
  vulnerabilityReport: {
13319
14018
  projectId,
@@ -13423,8 +14122,8 @@ ${contextString}` : description;
13423
14122
 
13424
14123
  // src/features/analysis/auto_pr_handler.ts
13425
14124
  init_client_generates();
13426
- import Debug12 from "debug";
13427
- var debug13 = Debug12("mobbdev:handleAutoPr");
14125
+ import Debug15 from "debug";
14126
+ var debug16 = Debug15("mobbdev:handleAutoPr");
13428
14127
  async function handleAutoPr(params) {
13429
14128
  const {
13430
14129
  gqlClient,
@@ -13445,7 +14144,7 @@ async function handleAutoPr(params) {
13445
14144
  prId,
13446
14145
  prStrategy: createOnePr ? "CONDENSE" /* Condense */ : "SPREAD" /* Spread */
13447
14146
  });
13448
- debug13("auto pr analysis res %o", autoPrAnalysisRes);
14147
+ debug16("auto pr analysis res %o", autoPrAnalysisRes);
13449
14148
  if (autoPrAnalysisRes.autoPrAnalysis?.__typename === "AutoPrError") {
13450
14149
  createAutoPrSpinner.error({
13451
14150
  text: `\u{1F504} Automatic pull request failed - ${autoPrAnalysisRes.autoPrAnalysis.error}`
@@ -13467,14 +14166,14 @@ async function handleAutoPr(params) {
13467
14166
  };
13468
14167
  const callbackStates = ["Finished" /* Finished */];
13469
14168
  if (polling) {
13470
- debug13("[handleAutoPr] Using POLLING mode for analysis state updates");
14169
+ debug16("[handleAutoPr] Using POLLING mode for analysis state updates");
13471
14170
  return await gqlClient.pollForAnalysisState({
13472
14171
  analysisId,
13473
14172
  callback,
13474
14173
  callbackStates
13475
14174
  });
13476
14175
  } else {
13477
- debug13("[handleAutoPr] Using WEBSOCKET mode for analysis state updates");
14176
+ debug16("[handleAutoPr] Using WEBSOCKET mode for analysis state updates");
13478
14177
  return await gqlClient.subscribeToAnalysis({
13479
14178
  subscribeToAnalysisParams: {
13480
14179
  analysisId
@@ -13487,15 +14186,15 @@ async function handleAutoPr(params) {
13487
14186
 
13488
14187
  // src/features/analysis/git.ts
13489
14188
  init_GitService();
13490
- import Debug13 from "debug";
13491
- var debug14 = Debug13("mobbdev:git");
14189
+ import Debug16 from "debug";
14190
+ var debug17 = Debug16("mobbdev:git");
13492
14191
  async function getGitInfo(srcDirPath) {
13493
- debug14("getting git info for %s", srcDirPath);
14192
+ debug17("getting git info for %s", srcDirPath);
13494
14193
  const gitService = new GitService(srcDirPath);
13495
14194
  try {
13496
14195
  const validationResult = await gitService.validateRepository();
13497
14196
  if (!validationResult.isValid) {
13498
- debug14("folder is not a git repo");
14197
+ debug17("folder is not a git repo");
13499
14198
  return {
13500
14199
  success: false,
13501
14200
  hash: void 0,
@@ -13510,9 +14209,9 @@ async function getGitInfo(srcDirPath) {
13510
14209
  };
13511
14210
  } catch (e) {
13512
14211
  if (e instanceof Error) {
13513
- debug14("failed to run git %o", e);
14212
+ debug17("failed to run git %o", e);
13514
14213
  if (e.message.includes(" spawn ")) {
13515
- debug14("git cli not installed");
14214
+ debug17("git cli not installed");
13516
14215
  } else {
13517
14216
  throw e;
13518
14217
  }
@@ -13524,22 +14223,22 @@ async function getGitInfo(srcDirPath) {
13524
14223
  // src/features/analysis/pack.ts
13525
14224
  init_configs();
13526
14225
  import fs9 from "fs";
13527
- import path7 from "path";
14226
+ import path8 from "path";
13528
14227
  import AdmZip from "adm-zip";
13529
- import Debug14 from "debug";
14228
+ import Debug17 from "debug";
13530
14229
  import { globby } from "globby";
13531
14230
  import { isBinary as isBinary2 } from "istextorbinary";
13532
14231
  import { simpleGit as simpleGit3 } from "simple-git";
13533
14232
  import { parseStringPromise } from "xml2js";
13534
- import { z as z29 } from "zod";
13535
- var debug15 = Debug14("mobbdev:pack");
13536
- var FPR_SOURCE_CODE_FILE_MAPPING_SCHEMA = z29.object({
13537
- properties: z29.object({
13538
- entry: z29.array(
13539
- z29.object({
13540
- _: z29.string(),
13541
- $: z29.object({
13542
- key: z29.string()
14233
+ import { z as z30 } from "zod";
14234
+ var debug18 = Debug17("mobbdev:pack");
14235
+ var FPR_SOURCE_CODE_FILE_MAPPING_SCHEMA = z30.object({
14236
+ properties: z30.object({
14237
+ entry: z30.array(
14238
+ z30.object({
14239
+ _: z30.string(),
14240
+ $: z30.object({
14241
+ key: z30.string()
13543
14242
  })
13544
14243
  })
13545
14244
  )
@@ -13554,7 +14253,7 @@ function getManifestFilesSuffixes() {
13554
14253
  return ["package.json", "pom.xml"];
13555
14254
  }
13556
14255
  async function pack(srcDirPath, vulnFiles, isIncludeAllFiles = false) {
13557
- debug15("pack folder %s", srcDirPath);
14256
+ debug18("pack folder %s", srcDirPath);
13558
14257
  let git = void 0;
13559
14258
  try {
13560
14259
  git = simpleGit3({
@@ -13564,13 +14263,13 @@ async function pack(srcDirPath, vulnFiles, isIncludeAllFiles = false) {
13564
14263
  });
13565
14264
  await git.status();
13566
14265
  } catch (e) {
13567
- debug15("failed to run git %o", e);
14266
+ debug18("failed to run git %o", e);
13568
14267
  git = void 0;
13569
14268
  if (e instanceof Error) {
13570
14269
  if (e.message.includes(" spawn ")) {
13571
- debug15("git cli not installed");
14270
+ debug18("git cli not installed");
13572
14271
  } else if (e.message.includes("not a git repository")) {
13573
- debug15("folder is not a git repo");
14272
+ debug18("folder is not a git repo");
13574
14273
  } else {
13575
14274
  throw e;
13576
14275
  }
@@ -13585,23 +14284,23 @@ async function pack(srcDirPath, vulnFiles, isIncludeAllFiles = false) {
13585
14284
  followSymbolicLinks: false,
13586
14285
  dot: true
13587
14286
  });
13588
- debug15("files found %d", filepaths.length);
14287
+ debug18("files found %d", filepaths.length);
13589
14288
  const zip = new AdmZip();
13590
- debug15("compressing files");
14289
+ debug18("compressing files");
13591
14290
  for (const filepath of filepaths) {
13592
- const absFilepath = path7.join(srcDirPath, filepath.toString());
14291
+ const absFilepath = path8.join(srcDirPath, filepath.toString());
13593
14292
  if (!isIncludeAllFiles) {
13594
14293
  vulnFiles = vulnFiles.concat(getManifestFilesSuffixes());
13595
14294
  if (!endsWithAny(
13596
- absFilepath.toString().replaceAll(path7.win32.sep, path7.posix.sep),
14295
+ absFilepath.toString().replaceAll(path8.win32.sep, path8.posix.sep),
13597
14296
  vulnFiles
13598
14297
  )) {
13599
- debug15("ignoring %s because it is not a vulnerability file", filepath);
14298
+ debug18("ignoring %s because it is not a vulnerability file", filepath);
13600
14299
  continue;
13601
14300
  }
13602
14301
  }
13603
14302
  if (fs9.lstatSync(absFilepath).size > MCP_MAX_FILE_SIZE) {
13604
- debug15("ignoring %s because the size is > 5MB", filepath);
14303
+ debug18("ignoring %s because the size is > 5MB", filepath);
13605
14304
  continue;
13606
14305
  }
13607
14306
  let data;
@@ -13615,16 +14314,16 @@ async function pack(srcDirPath, vulnFiles, isIncludeAllFiles = false) {
13615
14314
  data = fs9.readFileSync(absFilepath);
13616
14315
  }
13617
14316
  if (isBinary2(null, data)) {
13618
- debug15("ignoring %s because is seems to be a binary file", filepath);
14317
+ debug18("ignoring %s because is seems to be a binary file", filepath);
13619
14318
  continue;
13620
14319
  }
13621
14320
  zip.addFile(filepath.toString(), data);
13622
14321
  }
13623
- debug15("get zip file buffer");
14322
+ debug18("get zip file buffer");
13624
14323
  return zip.toBuffer();
13625
14324
  }
13626
14325
  async function repackFpr(fprPath) {
13627
- debug15("repack fpr file %s", fprPath);
14326
+ debug18("repack fpr file %s", fprPath);
13628
14327
  const zipIn = new AdmZip(fprPath);
13629
14328
  const zipOut = new AdmZip();
13630
14329
  const mappingXML = zipIn.readAsText("src-archive/index.xml", "utf-8");
@@ -13639,7 +14338,7 @@ async function repackFpr(fprPath) {
13639
14338
  zipOut.addFile(realPath, buf);
13640
14339
  }
13641
14340
  }
13642
- debug15("get repacked zip file buffer");
14341
+ debug18("get repacked zip file buffer");
13643
14342
  return zipOut.toBuffer();
13644
14343
  }
13645
14344
 
@@ -13709,12 +14408,12 @@ async function snykArticlePrompt() {
13709
14408
 
13710
14409
  // src/features/analysis/scanners/checkmarx.ts
13711
14410
  import { createRequire } from "module";
13712
- import chalk4 from "chalk";
13713
- import Debug16 from "debug";
14411
+ import chalk5 from "chalk";
14412
+ import Debug19 from "debug";
13714
14413
  import { existsSync } from "fs";
13715
14414
  import { createSpinner as createSpinner2 } from "nanospinner";
13716
14415
  import { type } from "os";
13717
- import path8 from "path";
14416
+ import path9 from "path";
13718
14417
 
13719
14418
  // src/post_install/constants.mjs
13720
14419
  var cxOperatingSystemSupportMessage = `Your operating system does not support checkmarx.
@@ -13722,7 +14421,7 @@ var cxOperatingSystemSupportMessage = `Your operating system does not support ch
13722
14421
 
13723
14422
  // src/utils/child_process.ts
13724
14423
  import cp from "child_process";
13725
- import Debug15 from "debug";
14424
+ import Debug18 from "debug";
13726
14425
  import * as process2 from "process";
13727
14426
  function createFork({ args, processPath, name }, options) {
13728
14427
  const child = cp.fork(processPath, args, {
@@ -13740,16 +14439,16 @@ function createSpawn({ args, processPath, name, cwd }, options) {
13740
14439
  return createChildProcess({ childProcess: child, name }, options);
13741
14440
  }
13742
14441
  function createChildProcess({ childProcess, name }, options) {
13743
- const debug21 = Debug15(`mobbdev:${name}`);
14442
+ const debug23 = Debug18(`mobbdev:${name}`);
13744
14443
  const { display } = options;
13745
14444
  return new Promise((resolve, reject) => {
13746
14445
  let out = "";
13747
14446
  const onData = (chunk) => {
13748
- debug21(`chunk received from ${name} std ${chunk}`);
14447
+ debug23(`chunk received from ${name} std ${chunk}`);
13749
14448
  out += chunk;
13750
14449
  };
13751
14450
  if (!childProcess?.stdout || !childProcess?.stderr) {
13752
- debug21(`unable to fork ${name}`);
14451
+ debug23(`unable to fork ${name}`);
13753
14452
  reject(new Error(`unable to fork ${name}`));
13754
14453
  }
13755
14454
  childProcess.stdout?.on("data", onData);
@@ -13759,18 +14458,18 @@ function createChildProcess({ childProcess, name }, options) {
13759
14458
  childProcess.stderr?.pipe(process2.stderr);
13760
14459
  }
13761
14460
  childProcess.on("exit", (code) => {
13762
- debug21(`${name} exit code ${code}`);
14461
+ debug23(`${name} exit code ${code}`);
13763
14462
  resolve({ message: out, code });
13764
14463
  });
13765
14464
  childProcess.on("error", (err) => {
13766
- debug21(`${name} error %o`, err);
14465
+ debug23(`${name} error %o`, err);
13767
14466
  reject(err);
13768
14467
  });
13769
14468
  });
13770
14469
  }
13771
14470
 
13772
14471
  // src/features/analysis/scanners/checkmarx.ts
13773
- var debug16 = Debug16("mobbdev:checkmarx");
14472
+ var debug19 = Debug19("mobbdev:checkmarx");
13774
14473
  var moduleUrl;
13775
14474
  if (typeof __filename !== "undefined") {
13776
14475
  moduleUrl = __filename;
@@ -13829,14 +14528,14 @@ function validateCheckmarxInstallation() {
13829
14528
  existsSync(getCheckmarxPath());
13830
14529
  }
13831
14530
  async function forkCheckmarx(args, { display }) {
13832
- debug16("fork checkmarx with args %o %s", args.join(" "), display);
14531
+ debug19("fork checkmarx with args %o %s", args.join(" "), display);
13833
14532
  return createSpawn(
13834
14533
  { args, processPath: getCheckmarxPath(), name: "checkmarx" },
13835
14534
  { display }
13836
14535
  );
13837
14536
  }
13838
14537
  async function getCheckmarxReport({ reportPath, repositoryRoot, branch, projectName }, { skipPrompts = false }) {
13839
- debug16("get checkmarx report start %s %s", reportPath, repositoryRoot);
14538
+ debug19("get checkmarx report start %s %s", reportPath, repositoryRoot);
13840
14539
  const { code: loginCode } = await forkCheckmarx(VALIDATE_COMMAND, {
13841
14540
  display: false
13842
14541
  });
@@ -13847,9 +14546,9 @@ async function getCheckmarxReport({ reportPath, repositoryRoot, branch, projectN
13847
14546
  await startCheckmarxConfigationPrompt();
13848
14547
  await validateCheckamxCredentials();
13849
14548
  }
13850
- const extension = path8.extname(reportPath);
13851
- const filePath = path8.dirname(reportPath);
13852
- const fileName = path8.basename(reportPath, extension);
14549
+ const extension = path9.extname(reportPath);
14550
+ const filePath = path9.dirname(reportPath);
14551
+ const fileName = path9.basename(reportPath, extension);
13853
14552
  const checkmarxCommandArgs = getCheckmarxCommandArgs({
13854
14553
  repoPath: repositoryRoot,
13855
14554
  branch,
@@ -13875,7 +14574,7 @@ async function throwCheckmarxConfigError() {
13875
14574
  await createSpinner2("\u{1F513} Checkmarx is not configued correctly").start().error();
13876
14575
  throw new CliError(
13877
14576
  `Checkmarx is not configued correctly
13878
- you can configure it by using the ${chalk4.bold(
14577
+ you can configure it by using the ${chalk5.bold(
13879
14578
  "cx configure"
13880
14579
  )} command`
13881
14580
  );
@@ -13883,8 +14582,8 @@ async function throwCheckmarxConfigError() {
13883
14582
  async function validateCheckamxCredentials() {
13884
14583
  console.log(`
13885
14584
  Here's a suggestion for checkmarx configuation:
13886
- ${chalk4.bold("AST Base URI:")} https://ast.checkmarx.net
13887
- ${chalk4.bold("AST Base Auth URI (IAM):")} https://iam.checkmarx.net
14585
+ ${chalk5.bold("AST Base URI:")} https://ast.checkmarx.net
14586
+ ${chalk5.bold("AST Base Auth URI (IAM):")} https://iam.checkmarx.net
13888
14587
  `);
13889
14588
  await forkCheckmarx(CONFIGURE_COMMAND, { display: true });
13890
14589
  const { code: loginCode } = await forkCheckmarx(VALIDATE_COMMAND, {
@@ -13903,11 +14602,11 @@ async function validateCheckamxCredentials() {
13903
14602
 
13904
14603
  // src/features/analysis/scanners/snyk.ts
13905
14604
  import { createRequire as createRequire2 } from "module";
13906
- import chalk5 from "chalk";
13907
- import Debug17 from "debug";
14605
+ import chalk6 from "chalk";
14606
+ import Debug20 from "debug";
13908
14607
  import { createSpinner as createSpinner3 } from "nanospinner";
13909
14608
  import open2 from "open";
13910
- var debug17 = Debug17("mobbdev:snyk");
14609
+ var debug20 = Debug20("mobbdev:snyk");
13911
14610
  var moduleUrl2;
13912
14611
  if (typeof __filename !== "undefined") {
13913
14612
  moduleUrl2 = __filename;
@@ -13929,13 +14628,13 @@ if (typeof __filename !== "undefined") {
13929
14628
  var costumeRequire2 = createRequire2(moduleUrl2);
13930
14629
  var SNYK_PATH = costumeRequire2.resolve("snyk/bin/snyk");
13931
14630
  var SNYK_ARTICLE_URL = "https://docs.snyk.io/scan-using-snyk/snyk-code/configure-snyk-code#enable-snyk-code";
13932
- debug17("snyk executable path %s", SNYK_PATH);
14631
+ debug20("snyk executable path %s", SNYK_PATH);
13933
14632
  async function forkSnyk(args, { display }) {
13934
- debug17("fork snyk with args %o %s", args, display);
14633
+ debug20("fork snyk with args %o %s", args, display);
13935
14634
  return createFork({ args, processPath: SNYK_PATH, name: "snyk" }, { display });
13936
14635
  }
13937
14636
  async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
13938
- debug17("get snyk report start %s %s", reportPath, repoRoot);
14637
+ debug20("get snyk report start %s %s", reportPath, repoRoot);
13939
14638
  const config2 = await forkSnyk(["config"], { display: false });
13940
14639
  const { message: configMessage } = config2;
13941
14640
  if (!configMessage.includes("api: ")) {
@@ -13949,7 +14648,7 @@ async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
13949
14648
  snykLoginSpinner.update({
13950
14649
  text: "\u{1F513} Waiting for Snyk login to complete"
13951
14650
  });
13952
- debug17("no token in the config %s", config2);
14651
+ debug20("no token in the config %s", config2);
13953
14652
  await forkSnyk(["auth"], { display: true });
13954
14653
  snykLoginSpinner.success({ text: "\u{1F513} Login to Snyk Successful" });
13955
14654
  }
@@ -13959,16 +14658,16 @@ async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
13959
14658
  { display: true }
13960
14659
  );
13961
14660
  if (scanOutput.includes("Snyk Code is not supported for org")) {
13962
- debug17("snyk code is not enabled %s", scanOutput);
14661
+ debug20("snyk code is not enabled %s", scanOutput);
13963
14662
  snykSpinner.error({ text: "\u{1F50D} Snyk configuration needed" });
13964
14663
  const answer = await snykArticlePrompt();
13965
- debug17("answer %s", answer);
14664
+ debug20("answer %s", answer);
13966
14665
  if (answer) {
13967
- debug17("opening the browser");
14666
+ debug20("opening the browser");
13968
14667
  await open2(SNYK_ARTICLE_URL);
13969
14668
  }
13970
14669
  console.log(
13971
- chalk5.bgBlue(
14670
+ chalk6.bgBlue(
13972
14671
  "\nPlease enable Snyk Code in your Snyk account and try again."
13973
14672
  )
13974
14673
  );
@@ -13980,58 +14679,6 @@ async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
13980
14679
 
13981
14680
  // src/features/analysis/index.ts
13982
14681
  init_client_generates();
13983
-
13984
- // src/features/analysis/upload-file.ts
13985
- import Debug18 from "debug";
13986
- import fetch3, { File, fileFrom, FormData } from "node-fetch";
13987
- var debug18 = Debug18("mobbdev:upload-file");
13988
- async function uploadFile({
13989
- file,
13990
- url,
13991
- uploadKey,
13992
- uploadFields,
13993
- logger: logger2
13994
- }) {
13995
- const logInfo2 = logger2 || ((_message, _data) => {
13996
- });
13997
- logInfo2(`FileUpload: upload file start ${url}`);
13998
- logInfo2(`FileUpload: upload fields`, uploadFields);
13999
- logInfo2(`FileUpload: upload key ${uploadKey}`);
14000
- debug18("upload file start %s", url);
14001
- debug18("upload fields %o", uploadFields);
14002
- debug18("upload key %s", uploadKey);
14003
- const form = new FormData();
14004
- Object.entries(uploadFields).forEach(([key, value]) => {
14005
- form.append(key, value);
14006
- });
14007
- if (!form.has("key")) {
14008
- form.append("key", uploadKey);
14009
- }
14010
- if (typeof file === "string") {
14011
- debug18("upload file from path %s", file);
14012
- logInfo2(`FileUpload: upload file from path ${file}`);
14013
- form.append("file", await fileFrom(file));
14014
- } else {
14015
- debug18("upload file from buffer");
14016
- logInfo2(`FileUpload: upload file from buffer`);
14017
- form.append("file", new File([new Uint8Array(file)], "file"));
14018
- }
14019
- const agent = getProxyAgent(url);
14020
- const response = await fetch3(url, {
14021
- method: "POST",
14022
- body: form,
14023
- agent
14024
- });
14025
- if (!response.ok) {
14026
- debug18("error from S3 %s %s", response.body, response.status);
14027
- logInfo2(`FileUpload: error from S3 ${response.body} ${response.status}`);
14028
- throw new Error(`Failed to upload the file: ${response.status}`);
14029
- }
14030
- debug18("upload file done");
14031
- logInfo2(`FileUpload: upload file done`);
14032
- }
14033
-
14034
- // src/features/analysis/index.ts
14035
14682
  var { CliError: CliError2, Spinner: Spinner2 } = utils_exports;
14036
14683
  function _getScanSource(command, ci) {
14037
14684
  if (command === "review") return "AUTO_FIXER" /* AutoFixer */;
@@ -14062,9 +14709,9 @@ async function downloadRepo({
14062
14709
  }) {
14063
14710
  const { createSpinner: createSpinner5 } = Spinner2({ ci });
14064
14711
  const repoSpinner = createSpinner5("\u{1F4BE} Downloading Repo").start();
14065
- debug19("download repo %s %s %s", repoUrl, dirname);
14066
- const zipFilePath = path9.join(dirname, "repo.zip");
14067
- debug19("download URL: %s auth headers: %o", downloadUrl, authHeaders);
14712
+ debug21("download repo %s %s %s", repoUrl, dirname);
14713
+ const zipFilePath = path10.join(dirname, "repo.zip");
14714
+ debug21("download URL: %s auth headers: %o", downloadUrl, authHeaders);
14068
14715
  const response = await fetch4(downloadUrl, {
14069
14716
  method: "GET",
14070
14717
  headers: {
@@ -14072,9 +14719,9 @@ async function downloadRepo({
14072
14719
  }
14073
14720
  });
14074
14721
  if (!response.ok) {
14075
- debug19("SCM zipball request failed %s %s", response.body, response.status);
14722
+ debug21("SCM zipball request failed %s %s", response.body, response.status);
14076
14723
  repoSpinner.error({ text: "\u{1F4BE} Repo download failed" });
14077
- throw new Error(`Can't access ${chalk6.bold(repoUrl)}`);
14724
+ throw new Error(`Can't access ${chalk7.bold(repoUrl)}`);
14078
14725
  }
14079
14726
  const fileWriterStream = fs10.createWriteStream(zipFilePath);
14080
14727
  if (!response.body) {
@@ -14086,16 +14733,16 @@ async function downloadRepo({
14086
14733
  if (!repoRoot) {
14087
14734
  throw new Error("Repo root not found");
14088
14735
  }
14089
- debug19("repo root %s", repoRoot);
14736
+ debug21("repo root %s", repoRoot);
14090
14737
  repoSpinner.success({ text: "\u{1F4BE} Repo downloaded successfully" });
14091
- return path9.join(dirname, repoRoot);
14738
+ return path10.join(dirname, repoRoot);
14092
14739
  }
14093
14740
  var getReportUrl = ({
14094
14741
  organizationId,
14095
14742
  projectId,
14096
14743
  fixReportId
14097
14744
  }) => `${WEB_APP_URL}/organization/${organizationId}/project/${projectId}/report/${fixReportId}`;
14098
- var debug19 = Debug19("mobbdev:index");
14745
+ var debug21 = Debug21("mobbdev:index");
14099
14746
  async function runAnalysis(params, options) {
14100
14747
  const tmpObj = tmp2.dirSync({
14101
14748
  unsafeCleanup: true
@@ -14196,7 +14843,7 @@ async function getReport(params, { skipPrompts }) {
14196
14843
  authHeaders: scm.getAuthHeaders(),
14197
14844
  downloadUrl
14198
14845
  });
14199
- const reportPath = path9.join(dirname, REPORT_DEFAULT_FILE_NAME);
14846
+ const reportPath = path10.join(dirname, REPORT_DEFAULT_FILE_NAME);
14200
14847
  switch (scanner) {
14201
14848
  case "snyk":
14202
14849
  await getSnykReport(reportPath, repositoryRoot, { skipPrompts });
@@ -14241,7 +14888,7 @@ async function _scan(params, { skipPrompts = false } = {}) {
14241
14888
  pullRequest,
14242
14889
  polling
14243
14890
  } = params;
14244
- debug19("start %s %s", dirname, repo);
14891
+ debug21("start %s %s", dirname, repo);
14245
14892
  const { createSpinner: createSpinner5 } = Spinner2({ ci });
14246
14893
  skipPrompts = skipPrompts || ci;
14247
14894
  const gqlClient = await getAuthenticatedGQLClient({
@@ -14310,8 +14957,8 @@ async function _scan(params, { skipPrompts = false } = {}) {
14310
14957
  );
14311
14958
  }
14312
14959
  const { sha } = getReferenceDataRes.gitReference;
14313
- debug19("project id %s", projectId);
14314
- debug19("default branch %s", reference);
14960
+ debug21("project id %s", projectId);
14961
+ debug21("default branch %s", reference);
14315
14962
  if (command === "scan") {
14316
14963
  reportPath = await getReport(
14317
14964
  {
@@ -14361,7 +15008,7 @@ async function _scan(params, { skipPrompts = false } = {}) {
14361
15008
  spinner: mobbSpinner,
14362
15009
  submitVulnerabilityReportVariables: {
14363
15010
  fixReportId: reportUploadInfo.fixReportId,
14364
- repoUrl: z30.string().parse(repo),
15011
+ repoUrl: z31.string().parse(repo),
14365
15012
  reference,
14366
15013
  projectId,
14367
15014
  vulnerabilityReportFileName: shouldScan ? void 0 : REPORT_DEFAULT_FILE_NAME,
@@ -14413,11 +15060,11 @@ async function _scan(params, { skipPrompts = false } = {}) {
14413
15060
  fixReportId: reportUploadInfo.fixReportId
14414
15061
  });
14415
15062
  !ci && console.log("You can access the analysis at: \n");
14416
- console.log(chalk6.bold(reportUrl));
15063
+ console.log(chalk7.bold(reportUrl));
14417
15064
  !skipPrompts && await mobbAnalysisPrompt();
14418
15065
  !ci && open3(reportUrl);
14419
15066
  !ci && console.log(
14420
- chalk6.bgBlue("\n\n My work here is done for now, see you soon! \u{1F575}\uFE0F\u200D\u2642\uFE0F ")
15067
+ chalk7.bgBlue("\n\n My work here is done for now, see you soon! \u{1F575}\uFE0F\u200D\u2642\uFE0F ")
14421
15068
  );
14422
15069
  }
14423
15070
  async function handleScmIntegration(oldToken, scmAuthUrl2, repoUrl) {
@@ -14578,11 +15225,11 @@ async function _zipAndUploadRepo({
14578
15225
  repoUploadInfo,
14579
15226
  isIncludeAllFiles
14580
15227
  }) {
14581
- const srcFileStatus = await fsPromises2.lstat(srcPath);
15228
+ const srcFileStatus = await fsPromises3.lstat(srcPath);
14582
15229
  const zippingSpinner = createSpinner4("\u{1F4E6} Zipping repo").start();
14583
15230
  let zipBuffer;
14584
15231
  let gitInfo2 = { success: false };
14585
- if (srcFileStatus.isFile() && path9.extname(srcPath).toLowerCase() === ".fpr") {
15232
+ if (srcFileStatus.isFile() && path10.extname(srcPath).toLowerCase() === ".fpr") {
14586
15233
  zipBuffer = await repackFpr(srcPath);
14587
15234
  } else {
14588
15235
  gitInfo2 = await getGitInfo(srcPath);
@@ -14639,11 +15286,11 @@ async function _digestReport({
14639
15286
  text: progressMassages.processingVulnerabilityReportSuccess
14640
15287
  });
14641
15288
  if (polling) {
14642
- debug19(
15289
+ debug21(
14643
15290
  "[_digestReport] Using POLLING mode for analysis state updates (--polling flag enabled)"
14644
15291
  );
14645
15292
  console.log(
14646
- chalk6.cyan(
15293
+ chalk7.cyan(
14647
15294
  "\u{1F504} [Polling Mode] Using HTTP polling instead of WebSocket for status updates"
14648
15295
  )
14649
15296
  );
@@ -14654,11 +15301,11 @@ async function _digestReport({
14654
15301
  timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
14655
15302
  });
14656
15303
  } else {
14657
- debug19(
15304
+ debug21(
14658
15305
  "[_digestReport] Using WEBSOCKET mode for analysis state updates (default)"
14659
15306
  );
14660
15307
  console.log(
14661
- chalk6.cyan(
15308
+ chalk7.cyan(
14662
15309
  "\u{1F50C} [WebSocket Mode] Using WebSocket subscription for status updates"
14663
15310
  )
14664
15311
  );
@@ -14694,9 +15341,9 @@ async function waitForAnaysisAndReviewPr({
14694
15341
  gqlClient,
14695
15342
  polling
14696
15343
  }) {
14697
- const params = z30.object({
14698
- repo: z30.string().url(),
14699
- githubActionToken: z30.string()
15344
+ const params = z31.object({
15345
+ repo: z31.string().url(),
15346
+ githubActionToken: z31.string()
14700
15347
  }).parse({ repo, githubActionToken });
14701
15348
  const scm = await createScmLib(
14702
15349
  {
@@ -14714,15 +15361,15 @@ async function waitForAnaysisAndReviewPr({
14714
15361
  analysisId: analysisId2,
14715
15362
  gqlClient,
14716
15363
  scm,
14717
- scanner: z30.nativeEnum(SCANNERS).parse(scanner)
15364
+ scanner: z31.nativeEnum(SCANNERS).parse(scanner)
14718
15365
  });
14719
15366
  };
14720
15367
  if (polling) {
14721
- debug19(
15368
+ debug21(
14722
15369
  "[waitForAnaysisAndReviewPr] Using POLLING mode for analysis state updates"
14723
15370
  );
14724
15371
  console.log(
14725
- chalk6.cyan(
15372
+ chalk7.cyan(
14726
15373
  "\u{1F504} [Polling Mode] Waiting for analysis completion using HTTP polling"
14727
15374
  )
14728
15375
  );
@@ -14732,11 +15379,11 @@ async function waitForAnaysisAndReviewPr({
14732
15379
  callbackStates: ["Finished" /* Finished */]
14733
15380
  });
14734
15381
  } else {
14735
- debug19(
15382
+ debug21(
14736
15383
  "[waitForAnaysisAndReviewPr] Using WEBSOCKET mode for analysis state updates"
14737
15384
  );
14738
15385
  console.log(
14739
- chalk6.cyan(
15386
+ chalk7.cyan(
14740
15387
  "\u{1F50C} [WebSocket Mode] Waiting for analysis completion using WebSocket"
14741
15388
  )
14742
15389
  );
@@ -14752,12 +15399,12 @@ async function waitForAnaysisAndReviewPr({
14752
15399
 
14753
15400
  // src/commands/scan_skill_input.ts
14754
15401
  import fs11 from "fs";
14755
- import path10 from "path";
15402
+ import path11 from "path";
14756
15403
  import AdmZip2 from "adm-zip";
14757
15404
  var LOCAL_SKILL_ZIP_DATA_URL_PREFIX = "data:application/zip;base64,";
14758
15405
  var MAX_LOCAL_SKILL_ARCHIVE_BYTES = 2 * 1024 * 1024;
14759
15406
  async function resolveSkillScanInput(skillInput) {
14760
- const resolvedPath = path10.resolve(skillInput);
15407
+ const resolvedPath = path11.resolve(skillInput);
14761
15408
  if (!fs11.existsSync(resolvedPath)) {
14762
15409
  return skillInput;
14763
15410
  }
@@ -14771,18 +15418,18 @@ async function resolveSkillScanInput(skillInput) {
14771
15418
  }
14772
15419
  function packageLocalSkillDirectory(directoryPath) {
14773
15420
  const zip = new AdmZip2();
14774
- const rootPath = path10.resolve(directoryPath);
15421
+ const rootPath = path11.resolve(directoryPath);
14775
15422
  const filePaths = listDirectoryFiles(rootPath);
14776
15423
  let hasSkillMd = false;
14777
15424
  for (const relativePath of filePaths) {
14778
- const fullPath = path10.join(rootPath, relativePath);
14779
- const normalizedFullPath = path10.resolve(fullPath);
14780
- if (normalizedFullPath !== rootPath && !normalizedFullPath.startsWith(rootPath + path10.sep)) {
15425
+ const fullPath = path11.join(rootPath, relativePath);
15426
+ const normalizedFullPath = path11.resolve(fullPath);
15427
+ if (normalizedFullPath !== rootPath && !normalizedFullPath.startsWith(rootPath + path11.sep)) {
14781
15428
  continue;
14782
15429
  }
14783
15430
  const fileContent = fs11.readFileSync(normalizedFullPath);
14784
15431
  zip.addFile(relativePath, fileContent);
14785
- if (path10.basename(relativePath).toLowerCase() === "skill.md") {
15432
+ if (path11.basename(relativePath).toLowerCase() === "skill.md") {
14786
15433
  hasSkillMd = true;
14787
15434
  }
14788
15435
  }
@@ -14800,17 +15447,17 @@ function packageLocalSkillDirectory(directoryPath) {
14800
15447
  function listDirectoryFiles(rootPath) {
14801
15448
  const files = [];
14802
15449
  function walk(relativeDir) {
14803
- const absoluteDir = path10.join(rootPath, relativeDir);
15450
+ const absoluteDir = path11.join(rootPath, relativeDir);
14804
15451
  const entries = fs11.readdirSync(absoluteDir, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
14805
15452
  for (const entry of entries) {
14806
- const safeEntryName = path10.basename(entry.name).replace(/\0/g, "");
15453
+ const safeEntryName = path11.basename(entry.name).replace(/\0/g, "");
14807
15454
  if (!safeEntryName) {
14808
15455
  continue;
14809
15456
  }
14810
- const relativePath = relativeDir ? path10.posix.join(relativeDir, safeEntryName) : safeEntryName;
14811
- const absolutePath = path10.join(rootPath, relativePath);
14812
- const normalizedAbsolutePath = path10.resolve(absolutePath);
14813
- if (normalizedAbsolutePath !== rootPath && !normalizedAbsolutePath.startsWith(rootPath + path10.sep)) {
15457
+ const relativePath = relativeDir ? path11.posix.join(relativeDir, safeEntryName) : safeEntryName;
15458
+ const absolutePath = path11.join(rootPath, relativePath);
15459
+ const normalizedAbsolutePath = path11.resolve(absolutePath);
15460
+ if (normalizedAbsolutePath !== rootPath && !normalizedAbsolutePath.startsWith(rootPath + path11.sep)) {
14814
15461
  continue;
14815
15462
  }
14816
15463
  if (entry.isSymbolicLink()) {
@@ -14898,831 +15545,266 @@ async function analyze({
14898
15545
  pullRequest,
14899
15546
  createOnePr,
14900
15547
  polling
14901
- },
14902
- { skipPrompts }
14903
- );
14904
- }
14905
- async function addScmToken(addScmTokenOptions) {
14906
- const { apiKey, token, organization, scmType, url, refreshToken, ci } = addScmTokenOptions;
14907
- const gqlClient = await getAuthenticatedGQLClient({
14908
- inputApiKey: apiKey,
14909
- isSkipPrompts: ci
14910
- });
14911
- if (!scmType) {
14912
- throw new CliError(errorMessages.invalidScmType);
14913
- }
14914
- const resp = await gqlClient.updateScmToken({
14915
- scmType,
14916
- url,
14917
- token,
14918
- org: organization,
14919
- refreshToken
14920
- });
14921
- if (resp.updateScmToken?.__typename === "RepoUnreachableError") {
14922
- throw new CliError(
14923
- "Mobb could not reach the repository. Please try again. If Mobb is connected through a broker, please make sure the broker is connected."
14924
- );
14925
- } else if (resp.updateScmToken?.__typename === "BadScmCredentials") {
14926
- throw new CliError("Invalid SCM credentials. Please try again.");
14927
- } else if (resp.updateScmToken?.__typename === "ScmAccessTokenUpdateSuccess") {
14928
- console.log("Token added successfully");
14929
- } else {
14930
- throw new CliError("Unexpected error, failed to add token");
14931
- }
14932
- }
14933
- async function scan(scanOptions, { skipPrompts = false } = {}) {
14934
- const { scanner, ci } = scanOptions;
14935
- !ci && await showWelcomeMessage(skipPrompts);
14936
- const selectedScanner = scanner || await choseScanner();
14937
- if (selectedScanner !== SCANNERS.Checkmarx && selectedScanner !== SCANNERS.Snyk) {
14938
- throw new CliError(
14939
- "Vulnerability scanning via Bugsy is available only with Snyk and Checkmarx at the moment. Additional scanners will follow soon."
14940
- );
14941
- }
14942
- selectedScanner === SCANNERS.Checkmarx && validateCheckmarxInstallation();
14943
- if (selectedScanner === SCANNERS.Checkmarx && !scanOptions.cxProjectName) {
14944
- throw new CliError(errorMessages.missingCxProjectName);
14945
- }
14946
- await runAnalysis(
14947
- { ...scanOptions, scanner: selectedScanner, command: "scan" },
14948
- { skipPrompts }
14949
- );
14950
- }
14951
- async function showWelcomeMessage(skipPrompts = false) {
14952
- console.log(mobbAscii);
14953
- const welcome = chalkAnimation.rainbow("\n Welcome to Bugsy\n");
14954
- skipPrompts ? await sleep(100) : await sleep(2e3);
14955
- welcome.stop();
14956
- }
14957
- var VERDICT_COLORS = {
14958
- BENIGN: "green",
14959
- WARNING: "yellow",
14960
- SUSPICIOUS: "magenta",
14961
- MALICIOUS: "red"
14962
- };
14963
- var SEVERITY_COLORS = {
14964
- CRITICAL: "red",
14965
- HIGH: "magenta",
14966
- MEDIUM: "yellow",
14967
- LOW: "cyan"
14968
- };
14969
- async function scanSkill(options) {
14970
- const { url, apiKey, ci } = options;
14971
- const gqlClient = await getAuthenticatedGQLClient({
14972
- inputApiKey: apiKey,
14973
- isSkipPrompts: ci
14974
- });
14975
- console.log(chalk7.dim(`Scanning skill: ${url}`));
14976
- console.log();
14977
- const skillUrl = await resolveSkillScanInput(url);
14978
- const result = await gqlClient.scanSkill({ skillUrl });
14979
- const scan2 = result.scanSkill;
14980
- const verdictColor = VERDICT_COLORS[scan2.verdict] ?? "white";
14981
- console.log(
14982
- chalk7.bold(`Verdict: `) + chalk7[verdictColor](
14983
- scan2.verdict
14984
- )
14985
- );
14986
- console.log(`Skill: ${scan2.skillName}`);
14987
- if (scan2.skillVersion) {
14988
- console.log(`Version: ${scan2.skillVersion}`);
14989
- }
14990
- console.log(`Hash: ${scan2.skillHash ?? "N/A"}`);
14991
- console.log(`Findings: ${scan2.findingsCount}`);
14992
- console.log(`Duration: ${scan2.scanDurationMs}ms`);
14993
- if (scan2.cached) {
14994
- console.log(chalk7.dim("(cached result)"));
14995
- }
14996
- console.log();
14997
- if (scan2.findings.length > 0) {
14998
- console.log(chalk7.bold("Findings:"));
14999
- console.log();
15000
- for (const f of scan2.findings) {
15001
- const sevColor = SEVERITY_COLORS[f.severity] ?? "white";
15002
- const location = [f.filePath, f.lineNumber].filter(Boolean).join(":");
15003
- console.log(
15004
- ` ${chalk7[sevColor](f.severity)} [${f.layer}] ${f.category}${f.ruleId ? ` (${f.ruleId})` : ""}`
15005
- );
15006
- if (location) {
15007
- console.log(` ${chalk7.dim(location)}`);
15008
- }
15009
- console.log(` ${f.explanation}`);
15010
- if (f.evidence) {
15011
- console.log(
15012
- ` ${String(chalk7.dim("Evidence: " + f.evidence.slice(0, 120))).replace(/\n|\r/g, "")}`
15013
- );
15014
- }
15015
- console.log();
15016
- }
15017
- }
15018
- if (scan2.summary) {
15019
- console.log(chalk7.bold("Analysis:"));
15020
- console.log(` ${scan2.summary}`);
15021
- console.log();
15022
- }
15023
- if (scan2.verdict === "MALICIOUS" || scan2.verdict === "SUSPICIOUS") {
15024
- process.exit(2);
15025
- }
15026
- }
15027
-
15028
- // src/args/validation.ts
15029
- import chalk8 from "chalk";
15030
- import path11 from "path";
15031
- import { z as z31 } from "zod";
15032
- function throwRepoUrlErrorMessage({
15033
- error,
15034
- repoUrl,
15035
- command
15036
- }) {
15037
- const errorMessage = error.issues[error.issues.length - 1]?.message;
15038
- const formattedErrorMessage = `
15039
- Error: ${chalk8.bold(
15040
- repoUrl
15041
- )} is ${errorMessage}
15042
- Example:
15043
- mobbdev ${command} -r ${chalk8.bold(
15044
- "https://github.com/WebGoat/WebGoat"
15045
- )}`;
15046
- throw new CliError(formattedErrorMessage);
15047
- }
15048
- var UrlZ = z31.string({
15049
- invalid_type_error: `is not a valid ${Object.values(ScmType).join("/ ")} URL`
15050
- });
15051
- function validateOrganizationId(organizationId) {
15052
- const orgIdValidation = z31.string().uuid().nullish().safeParse(organizationId);
15053
- if (!orgIdValidation.success) {
15054
- throw new CliError(`organizationId: ${organizationId} is not a valid UUID`);
15055
- }
15056
- }
15057
- function validateRepoUrl(args) {
15058
- const repoSafeParseResult = UrlZ.safeParse(args.repo);
15059
- const { success } = repoSafeParseResult;
15060
- const [command] = args._;
15061
- if (!command) {
15062
- throw new CliError("Command not found");
15063
- }
15064
- if (!success) {
15065
- throwRepoUrlErrorMessage({
15066
- error: repoSafeParseResult.error,
15067
- repoUrl: args.repo,
15068
- command
15069
- });
15070
- }
15071
- }
15072
- var supportExtensions = [".json", ".xml", ".fpr", ".sarif", ".zip"];
15073
- function validateReportFileFormat(reportFile) {
15074
- if (!supportExtensions.includes(path11.extname(reportFile))) {
15075
- throw new CliError(
15076
- `
15077
- ${chalk8.bold(
15078
- reportFile
15079
- )} is not a supported file extension. Supported extensions are: ${chalk8.bold(
15080
- supportExtensions.join(", ")
15081
- )}
15082
- `
15083
- );
15084
- }
15085
- }
15086
-
15087
- // src/args/commands/analyze.ts
15088
- function analyzeBuilder(yargs2) {
15089
- return yargs2.option("f", {
15090
- alias: "scan-file",
15091
- type: "string",
15092
- describe: chalk9.bold(
15093
- "Select the vulnerability report to analyze (Checkmarx, Snyk, Fortify, CodeQL, Sonarqube, Semgrep, Datadog)"
15094
- )
15095
- }).option("repo", repoOption).option("p", {
15096
- alias: "src-path",
15097
- describe: chalk9.bold(
15098
- "Path to the repository folder with the source code; alternatively, you can specify the Fortify FPR file to extract source code out of it"
15099
- ),
15100
- type: "string"
15101
- }).option("ref", refOption).option("ch", {
15102
- alias: "commit-hash",
15103
- describe: chalk9.bold("Hash of the commit"),
15104
- type: "string"
15105
- }).option("mobb-project-name", mobbProjectNameOption).option("y", yesOption).option("ci", ciOption).option("org", organizationIdOptions).option("api-key", apiKeyOption).option("commit-hash", commitHashOption).option("auto-pr", autoPrOption).option("create-one-pr", createOnePrOption).option("commit-directly", commitDirectlyOption).option("pull-request", {
15106
- alias: ["pr", "pr-number", "pr-id"],
15107
- describe: chalk9.bold("Number of the pull request"),
15108
- type: "number",
15109
- demandOption: false
15110
- }).option("polling", pollingOption).example(
15111
- "npx mobbdev@latest analyze -r https://github.com/WebGoat/WebGoat -f <your_vulnerability_report_path>",
15112
- "analyze an existing repository"
15113
- ).help();
15114
- }
15115
- function validateAnalyzeOptions(argv) {
15116
- if (argv.f && !fs12.existsSync(argv.f)) {
15117
- throw new CliError(`
15118
- Can't access ${chalk9.bold(argv.f)}`);
15119
- }
15120
- validateOrganizationId(argv.organizationId);
15121
- if (!argv.srcPath && !argv.repo) {
15122
- throw new CliError("You must supply either --src-path or --repo");
15123
- }
15124
- if (!argv.srcPath && argv.repo) {
15125
- validateRepoUrl(argv);
15126
- }
15127
- if (argv.ci && !argv.apiKey) {
15128
- throw new CliError("--ci flag requires --api-key to be provided as well");
15129
- }
15130
- if (argv.commitDirectly && !argv["auto-pr"]) {
15131
- throw new CliError(
15132
- "--commit-directly flag requires --auto-pr to be provided as well"
15133
- );
15134
- }
15135
- if (argv["create-one-pr"] && !argv["auto-pr"]) {
15136
- throw new CliError(
15137
- "--create-one-pr flag requires --auto-pr to be provided as well"
15138
- );
15139
- }
15140
- if (argv["create-one-pr"] && argv.commitDirectly) {
15141
- throw new CliError(
15142
- "--create-one-pr and --commit-directly cannot be provided at the same time"
15143
- );
15144
- }
15145
- if (argv.pullRequest && !argv.commitDirectly) {
15146
- throw new CliError(
15147
- "--pull-request flag requires --commit-directly to be provided as well"
15148
- );
15149
- }
15150
- if (argv.f) {
15151
- validateReportFileFormat(argv.f);
15152
- }
15153
- }
15154
- async function analyzeHandler(args) {
15155
- validateAnalyzeOptions(args);
15156
- await analyze(args, { skipPrompts: args.yes });
15157
- }
15158
-
15159
- // src/features/claude_code/data_collector.ts
15160
- import { z as z33 } from "zod";
15161
-
15162
- // src/args/commands/upload_ai_blame.ts
15163
- import fsPromises3 from "fs/promises";
15164
- import * as os3 from "os";
15165
- import path12 from "path";
15166
- import chalk10 from "chalk";
15167
- import { withFile } from "tmp-promise";
15168
- import z32 from "zod";
15169
- init_client_generates();
15170
- init_GitService();
15171
- init_urlParser2();
15172
-
15173
- // src/utils/computerName.ts
15174
- import { execSync } from "child_process";
15175
- import os2 from "os";
15176
- var STABLE_COMPUTER_NAME_CONFIG_KEY = "stableComputerName";
15177
- var HOSTNAME_SUFFIXES = [
15178
- // Cloud providers (must be first - most specific)
15179
- ".ec2.internal",
15180
- ".compute.internal",
15181
- ".cloudapp.net",
15182
- // mDNS/Bonjour
15183
- ".local",
15184
- ".localhost",
15185
- ".localdomain",
15186
- // Home networks
15187
- ".lan",
15188
- ".home",
15189
- ".homelan",
15190
- // Corporate networks
15191
- ".corp",
15192
- ".internal",
15193
- ".intranet",
15194
- ".domain",
15195
- ".work",
15196
- ".office",
15197
- // Container environments
15198
- ".docker",
15199
- ".kubernetes",
15200
- ".k8s"
15201
- ];
15202
- function getOsSpecificComputerName() {
15203
- const platform2 = os2.platform();
15204
- try {
15205
- if (platform2 === "darwin") {
15206
- const result = execSync("scutil --get LocalHostName", {
15207
- encoding: "utf-8",
15208
- timeout: 1e3
15209
- });
15210
- return result.trim();
15211
- } else if (platform2 === "win32") {
15212
- return process.env["COMPUTERNAME"] || null;
15213
- } else {
15214
- try {
15215
- const result2 = execSync("hostnamectl --static", {
15216
- encoding: "utf-8",
15217
- timeout: 1e3
15218
- });
15219
- const hostname = result2.trim();
15220
- if (hostname) return hostname;
15221
- } catch {
15222
- }
15223
- const result = execSync("cat /etc/hostname", {
15224
- encoding: "utf-8",
15225
- timeout: 1e3
15226
- });
15227
- return result.trim();
15228
- }
15229
- } catch (error) {
15230
- return null;
15231
- }
15232
- }
15233
- function stripHostnameSuffixes(hostname) {
15234
- if (!hostname) return hostname;
15235
- let normalized = hostname;
15236
- for (const suffix of HOSTNAME_SUFFIXES) {
15237
- if (normalized.endsWith(suffix)) {
15238
- normalized = normalized.slice(0, -suffix.length);
15239
- break;
15240
- }
15241
- }
15242
- return normalized;
15243
- }
15244
- function generateStableComputerName() {
15245
- const osSpecificName = getOsSpecificComputerName();
15246
- if (osSpecificName) {
15247
- return osSpecificName;
15248
- }
15249
- const currentHostname = os2.hostname();
15250
- return stripHostnameSuffixes(currentHostname);
15251
- }
15252
- function getStableComputerName() {
15253
- const cached = configStore.get(STABLE_COMPUTER_NAME_CONFIG_KEY);
15254
- if (cached) {
15255
- return cached;
15256
- }
15257
- const currentName = generateStableComputerName();
15258
- configStore.set(STABLE_COMPUTER_NAME_CONFIG_KEY, currentName);
15259
- return currentName;
15260
- }
15261
-
15262
- // src/utils/sanitize-sensitive-data.ts
15263
- import { OpenRedaction } from "@openredaction/openredaction";
15264
- var openRedaction = new OpenRedaction({
15265
- patterns: [
15266
- // Core Personal Data
15267
- // Removed EMAIL - causes false positives in code/test snippets (e.g. --author="Eve Author <eve@example.com>")
15268
- // Prefer false negatives over false positives for this use case.
15269
- "SSN",
15270
- "NATIONAL_INSURANCE_UK",
15271
- "DATE_OF_BIRTH",
15272
- // Identity Documents
15273
- "PASSPORT_UK",
15274
- "PASSPORT_US",
15275
- "PASSPORT_MRZ_TD1",
15276
- "PASSPORT_MRZ_TD3",
15277
- "DRIVING_LICENSE_UK",
15278
- "DRIVING_LICENSE_US",
15279
- "VISA_NUMBER",
15280
- "VISA_MRZ",
15281
- "TAX_ID",
15282
- // Financial Data (removed SWIFT_BIC, CARD_AUTH_CODE - too broad, causing false positives with authentication words)
15283
- // Removed CREDIT_CARD - causes false positives on zero-filled UUIDs (e.g. '00000000-0000-0000-0000-000000000000')
15284
- // Prefer false negatives over false positives for this use case.
15285
- "IBAN",
15286
- "BANK_ACCOUNT_UK",
15287
- "ROUTING_NUMBER_US",
15288
- "CARD_TRACK1_DATA",
15289
- "CARD_TRACK2_DATA",
15290
- "CARD_EXPIRY",
15291
- // Cryptocurrency (removed BITCOIN_ADDRESS - too broad, matches hash-like strings)
15292
- "ETHEREUM_ADDRESS",
15293
- "LITECOIN_ADDRESS",
15294
- "CARDANO_ADDRESS",
15295
- "SOLANA_ADDRESS",
15296
- "MONERO_ADDRESS",
15297
- "RIPPLE_ADDRESS",
15298
- // Medical Data (removed PRESCRIPTION_NUMBER - too broad, matches words containing "ription")
15299
- // Removed MEDICAL_RECORD_NUMBER - too broad, "MR" prefix matches "Merge Request" in SCM contexts (e.g. "MR branches" → "MR br****es")
15300
- "NHS_NUMBER",
15301
- "AUSTRALIAN_MEDICARE",
15302
- "HEALTH_PLAN_NUMBER",
15303
- "PATIENT_ID",
15304
- // Communications (removed EMERGENCY_CONTACT, ADDRESS_PO_BOX, ZIP_CODE_US, PHONE_US, PHONE_INTERNATIONAL - too broad, causing false positives)
15305
- "PHONE_UK",
15306
- "PHONE_UK_MOBILE",
15307
- "PHONE_LINE_NUMBER",
15308
- "ADDRESS_STREET",
15309
- "POSTCODE_UK",
15310
- // Network & Technical
15311
- "IPV4",
15312
- "IPV6",
15313
- "MAC_ADDRESS",
15314
- "URL_WITH_AUTH",
15315
- // Security Keys & Tokens
15316
- "PRIVATE_KEY",
15317
- "SSH_PRIVATE_KEY",
15318
- "AWS_SECRET_KEY",
15319
- "AWS_ACCESS_KEY",
15320
- "AZURE_STORAGE_KEY",
15321
- "GCP_SERVICE_ACCOUNT",
15322
- "JWT_TOKEN",
15323
- "OAUTH_TOKEN",
15324
- "OAUTH_CLIENT_SECRET",
15325
- "BEARER_TOKEN",
15326
- "PAYMENT_TOKEN",
15327
- "GENERIC_SECRET",
15328
- "GENERIC_API_KEY",
15329
- // Platform-Specific API Keys
15330
- "GITHUB_TOKEN",
15331
- "SLACK_TOKEN",
15332
- "STRIPE_API_KEY",
15333
- "GOOGLE_API_KEY",
15334
- "FIREBASE_API_KEY",
15335
- "HEROKU_API_KEY",
15336
- "MAILGUN_API_KEY",
15337
- "SENDGRID_API_KEY",
15338
- "TWILIO_API_KEY",
15339
- "NPM_TOKEN",
15340
- "PYPI_TOKEN",
15341
- "DOCKER_AUTH",
15342
- "KUBERNETES_SECRET",
15343
- // Government & Legal
15344
- // Removed CLIENT_ID - too broad, "client" is ubiquitous in code (npm packages like @scope/client-*, class names like ClientSdkOptions)
15345
- "POLICE_REPORT_NUMBER",
15346
- "IMMIGRATION_NUMBER",
15347
- "COURT_REPORTER_LICENSE"
15348
- ]
15349
- });
15350
- function maskString(str, showStart = 2, showEnd = 2) {
15351
- if (str.length <= showStart + showEnd) {
15352
- return "*".repeat(str.length);
15353
- }
15354
- return str.slice(0, showStart) + "*".repeat(str.length - showStart - showEnd) + str.slice(-showEnd);
15355
- }
15356
- async function sanitizeDataWithCounts(obj) {
15357
- const counts = {
15358
- detections: { total: 0, high: 0, medium: 0, low: 0 }
15359
- };
15360
- const sanitizeString = async (str) => {
15361
- let result = str;
15362
- const piiDetections = openRedaction.scan(str);
15363
- if (piiDetections && piiDetections.total > 0) {
15364
- const allDetections = [
15365
- ...piiDetections.high,
15366
- ...piiDetections.medium,
15367
- ...piiDetections.low
15368
- ];
15369
- for (const detection of allDetections) {
15370
- counts.detections.total++;
15371
- if (detection.severity === "high") counts.detections.high++;
15372
- else if (detection.severity === "medium") counts.detections.medium++;
15373
- else if (detection.severity === "low") counts.detections.low++;
15374
- const masked = maskString(detection.value);
15375
- result = result.replaceAll(detection.value, masked);
15376
- }
15377
- }
15378
- return result;
15379
- };
15380
- const sanitizeRecursive = async (data) => {
15381
- if (typeof data === "string") {
15382
- return sanitizeString(data);
15383
- } else if (Array.isArray(data)) {
15384
- return Promise.all(data.map((item) => sanitizeRecursive(item)));
15385
- } else if (data instanceof Error) {
15386
- return data;
15387
- } else if (data instanceof Date) {
15388
- return data;
15389
- } else if (typeof data === "object" && data !== null) {
15390
- const sanitized = {};
15391
- const record = data;
15392
- for (const key in record) {
15393
- if (Object.prototype.hasOwnProperty.call(record, key)) {
15394
- sanitized[key] = await sanitizeRecursive(record[key]);
15395
- }
15396
- }
15397
- return sanitized;
15398
- }
15399
- return data;
15400
- };
15401
- const sanitizedData = await sanitizeRecursive(obj);
15402
- return { sanitizedData, counts };
15548
+ },
15549
+ { skipPrompts }
15550
+ );
15403
15551
  }
15404
-
15405
- // src/args/commands/upload_ai_blame.ts
15406
- var defaultLogger2 = {
15407
- info: (msg, data) => {
15408
- if (data !== void 0) {
15409
- console.log(msg, data);
15410
- } else {
15411
- console.log(msg);
15412
- }
15413
- },
15414
- error: (msg, data) => {
15415
- if (data !== void 0) {
15416
- console.error(msg, data);
15417
- } else {
15418
- console.error(msg);
15419
- }
15552
+ async function addScmToken(addScmTokenOptions) {
15553
+ const { apiKey, token, organization, scmType, url, refreshToken, ci } = addScmTokenOptions;
15554
+ const gqlClient = await getAuthenticatedGQLClient({
15555
+ inputApiKey: apiKey,
15556
+ isSkipPrompts: ci
15557
+ });
15558
+ if (!scmType) {
15559
+ throw new CliError(errorMessages.invalidScmType);
15420
15560
  }
15421
- };
15422
- var PromptItemZ = z32.object({
15423
- type: z32.enum([
15424
- "USER_PROMPT",
15425
- "AI_RESPONSE",
15426
- "TOOL_EXECUTION",
15427
- "AI_THINKING",
15428
- "MCP_TOOL_CALL"
15429
- // MCP (Model Context Protocol) tool invocation
15430
- ]),
15431
- attachedFiles: z32.array(
15432
- z32.object({
15433
- relativePath: z32.string(),
15434
- startLine: z32.number().optional()
15435
- })
15436
- ).optional(),
15437
- tokens: z32.object({
15438
- inputCount: z32.number(),
15439
- outputCount: z32.number()
15440
- }).optional(),
15441
- text: z32.string().optional(),
15442
- date: z32.date().optional(),
15443
- tool: z32.object({
15444
- name: z32.string(),
15445
- parameters: z32.string(),
15446
- result: z32.string(),
15447
- rawArguments: z32.string().optional(),
15448
- accepted: z32.boolean().optional(),
15449
- // MCP-specific fields (only populated for MCP_TOOL_CALL type)
15450
- mcpServer: z32.string().optional(),
15451
- // MCP server name (e.g., "datadog", "mobb-mcp")
15452
- mcpToolName: z32.string().optional()
15453
- // MCP tool name without prefix (e.g., "scan_and_fix_vulnerabilities")
15454
- }).optional()
15455
- });
15456
- var PromptItemArrayZ = z32.array(PromptItemZ);
15457
- async function getRepositoryUrl() {
15458
- try {
15459
- const gitService = new GitService(process.cwd());
15460
- const isRepo = await gitService.isGitRepository();
15461
- if (!isRepo) {
15462
- return null;
15463
- }
15464
- const remoteUrl = await gitService.getRemoteUrl();
15465
- const parsed = parseScmURL(remoteUrl);
15466
- return parsed?.scmType === "GitHub" /* GitHub */ || parsed?.scmType === "GitLab" /* GitLab */ ? remoteUrl : null;
15467
- } catch {
15468
- return null;
15561
+ const resp = await gqlClient.updateScmToken({
15562
+ scmType,
15563
+ url,
15564
+ token,
15565
+ org: organization,
15566
+ refreshToken
15567
+ });
15568
+ if (resp.updateScmToken?.__typename === "RepoUnreachableError") {
15569
+ throw new CliError(
15570
+ "Mobb could not reach the repository. Please try again. If Mobb is connected through a broker, please make sure the broker is connected."
15571
+ );
15572
+ } else if (resp.updateScmToken?.__typename === "BadScmCredentials") {
15573
+ throw new CliError("Invalid SCM credentials. Please try again.");
15574
+ } else if (resp.updateScmToken?.__typename === "ScmAccessTokenUpdateSuccess") {
15575
+ console.log("Token added successfully");
15576
+ } else {
15577
+ throw new CliError("Unexpected error, failed to add token");
15469
15578
  }
15470
15579
  }
15471
- function getSystemInfo() {
15472
- let userName;
15473
- try {
15474
- userName = os3.userInfo().username;
15475
- } catch {
15476
- userName = void 0;
15580
+ async function scan(scanOptions, { skipPrompts = false } = {}) {
15581
+ const { scanner, ci } = scanOptions;
15582
+ !ci && await showWelcomeMessage(skipPrompts);
15583
+ const selectedScanner = scanner || await choseScanner();
15584
+ if (selectedScanner !== SCANNERS.Checkmarx && selectedScanner !== SCANNERS.Snyk) {
15585
+ throw new CliError(
15586
+ "Vulnerability scanning via Bugsy is available only with Snyk and Checkmarx at the moment. Additional scanners will follow soon."
15587
+ );
15477
15588
  }
15478
- return {
15479
- computerName: getStableComputerName(),
15480
- userName
15481
- };
15589
+ selectedScanner === SCANNERS.Checkmarx && validateCheckmarxInstallation();
15590
+ if (selectedScanner === SCANNERS.Checkmarx && !scanOptions.cxProjectName) {
15591
+ throw new CliError(errorMessages.missingCxProjectName);
15592
+ }
15593
+ await runAnalysis(
15594
+ { ...scanOptions, scanner: selectedScanner, command: "scan" },
15595
+ { skipPrompts }
15596
+ );
15482
15597
  }
15483
- function uploadAiBlameBuilder(args) {
15484
- return args.option("prompt", {
15485
- type: "string",
15486
- array: true,
15487
- demandOption: true,
15488
- describe: chalk10.bold("Path(s) to prompt artifact(s) (one per session)")
15489
- }).option("inference", {
15490
- type: "string",
15491
- array: true,
15492
- demandOption: true,
15493
- describe: chalk10.bold(
15494
- "Path(s) to inference artifact(s) (one per session)"
15495
- )
15496
- }).option("ai-response-at", {
15497
- type: "string",
15498
- array: true,
15499
- describe: chalk10.bold(
15500
- "ISO timestamp(s) for AI response (one per session, defaults to now)"
15501
- )
15502
- }).option("model", {
15503
- type: "string",
15504
- array: true,
15505
- describe: chalk10.bold("AI model name(s) (optional, one per session)")
15506
- }).option("tool-name", {
15507
- type: "string",
15508
- array: true,
15509
- describe: chalk10.bold("Tool/IDE name(s) (optional, one per session)")
15510
- }).option("blame-type", {
15511
- type: "string",
15512
- array: true,
15513
- choices: Object.values(AiBlameInferenceType),
15514
- describe: chalk10.bold(
15515
- "Blame type(s) (optional, one per session, defaults to CHAT)"
15516
- )
15517
- }).strict();
15598
+ async function showWelcomeMessage(skipPrompts = false) {
15599
+ console.log(mobbAscii);
15600
+ const welcome = chalkAnimation.rainbow("\n Welcome to Bugsy\n");
15601
+ skipPrompts ? await sleep(100) : await sleep(2e3);
15602
+ welcome.stop();
15518
15603
  }
15519
- async function uploadAiBlameHandlerFromExtension(args) {
15520
- const uploadArgs = {
15521
- prompt: [],
15522
- inference: [],
15523
- model: [],
15524
- toolName: [],
15525
- aiResponseAt: [],
15526
- blameType: [],
15527
- sessionId: []
15528
- };
15529
- let promptsCounts;
15530
- let inferenceCounts;
15531
- let promptsUUID;
15532
- let inferenceUUID;
15533
- await withFile(async (promptFile) => {
15534
- const promptsResult = await sanitizeDataWithCounts(args.prompts);
15535
- promptsCounts = promptsResult.counts;
15536
- promptsUUID = path12.basename(promptFile.path, path12.extname(promptFile.path));
15537
- await fsPromises3.writeFile(
15538
- promptFile.path,
15539
- JSON.stringify(promptsResult.sanitizedData, null, 2),
15540
- "utf-8"
15541
- );
15542
- uploadArgs.prompt.push(promptFile.path);
15543
- await withFile(async (inferenceFile) => {
15544
- const inferenceResult = await sanitizeDataWithCounts(args.inference);
15545
- inferenceCounts = inferenceResult.counts;
15546
- inferenceUUID = path12.basename(
15547
- inferenceFile.path,
15548
- path12.extname(inferenceFile.path)
15549
- );
15550
- await fsPromises3.writeFile(
15551
- inferenceFile.path,
15552
- inferenceResult.sanitizedData,
15553
- "utf-8"
15554
- );
15555
- uploadArgs.inference.push(inferenceFile.path);
15556
- uploadArgs.model.push(args.model);
15557
- uploadArgs.toolName.push(args.tool);
15558
- uploadArgs.aiResponseAt.push(args.responseTime);
15559
- uploadArgs.blameType.push(args.blameType || "CHAT" /* Chat */);
15560
- if (args.sessionId) {
15561
- uploadArgs.sessionId.push(args.sessionId);
15562
- }
15563
- await uploadAiBlameHandler({
15564
- args: uploadArgs,
15565
- exitOnError: false,
15566
- apiUrl: args.apiUrl,
15567
- webAppUrl: args.webAppUrl,
15568
- repositoryUrl: args.repositoryUrl
15569
- });
15570
- });
15604
+ var VERDICT_COLORS = {
15605
+ BENIGN: "green",
15606
+ WARNING: "yellow",
15607
+ SUSPICIOUS: "magenta",
15608
+ MALICIOUS: "red"
15609
+ };
15610
+ var SEVERITY_COLORS = {
15611
+ CRITICAL: "red",
15612
+ HIGH: "magenta",
15613
+ MEDIUM: "yellow",
15614
+ LOW: "cyan"
15615
+ };
15616
+ async function scanSkill(options) {
15617
+ const { url, apiKey, ci } = options;
15618
+ const gqlClient = await getAuthenticatedGQLClient({
15619
+ inputApiKey: apiKey,
15620
+ isSkipPrompts: ci
15571
15621
  });
15572
- return {
15573
- promptsCounts,
15574
- inferenceCounts,
15575
- promptsUUID,
15576
- inferenceUUID
15577
- };
15578
- }
15579
- async function uploadAiBlameHandler(options) {
15580
- const {
15581
- args,
15582
- exitOnError = true,
15583
- apiUrl,
15584
- webAppUrl,
15585
- logger: logger2 = defaultLogger2
15586
- } = options;
15587
- const prompts = args.prompt || [];
15588
- const inferences = args.inference || [];
15589
- const models = args.model || [];
15590
- const tools = args.toolName || args["tool-name"] || [];
15591
- const responseTimes = args.aiResponseAt || args["ai-response-at"] || [];
15592
- const blameTypes = args.blameType || args["blame-type"] || [];
15593
- const sessionIds = args.sessionId || args["session-id"] || [];
15594
- if (prompts.length !== inferences.length) {
15595
- const errorMsg = "prompt and inference must have the same number of entries";
15596
- logger2.error(chalk10.red(errorMsg));
15597
- if (exitOnError) {
15598
- process.exit(1);
15599
- }
15600
- throw new Error(errorMsg);
15622
+ console.log(chalk8.dim(`Scanning skill: ${url}`));
15623
+ console.log();
15624
+ const skillUrl = await resolveSkillScanInput(url);
15625
+ const result = await gqlClient.scanSkill({ skillUrl });
15626
+ const scan2 = result.scanSkill;
15627
+ const verdictColor = VERDICT_COLORS[scan2.verdict] ?? "white";
15628
+ console.log(
15629
+ chalk8.bold(`Verdict: `) + chalk8[verdictColor](
15630
+ scan2.verdict
15631
+ )
15632
+ );
15633
+ console.log(`Skill: ${scan2.skillName}`);
15634
+ if (scan2.skillVersion) {
15635
+ console.log(`Version: ${scan2.skillVersion}`);
15601
15636
  }
15602
- const nowIso = (/* @__PURE__ */ new Date()).toISOString();
15603
- const { computerName, userName } = getSystemInfo();
15604
- const repositoryUrl = options.repositoryUrl !== void 0 ? options.repositoryUrl : await getRepositoryUrl();
15605
- const sessions = [];
15606
- for (let i = 0; i < prompts.length; i++) {
15607
- const promptPath = String(prompts[i]);
15608
- const inferencePath = String(inferences[i]);
15609
- try {
15610
- await Promise.all([
15611
- fsPromises3.access(promptPath),
15612
- fsPromises3.access(inferencePath)
15613
- ]);
15614
- } catch {
15615
- const errorMsg = `File not found for session ${i + 1}`;
15616
- logger2.error(chalk10.red(errorMsg));
15617
- if (exitOnError) {
15618
- process.exit(1);
15637
+ console.log(`Hash: ${scan2.skillHash ?? "N/A"}`);
15638
+ console.log(`Findings: ${scan2.findingsCount}`);
15639
+ console.log(`Duration: ${scan2.scanDurationMs}ms`);
15640
+ if (scan2.cached) {
15641
+ console.log(chalk8.dim("(cached result)"));
15642
+ }
15643
+ console.log();
15644
+ if (scan2.findings.length > 0) {
15645
+ console.log(chalk8.bold("Findings:"));
15646
+ console.log();
15647
+ for (const f of scan2.findings) {
15648
+ const sevColor = SEVERITY_COLORS[f.severity] ?? "white";
15649
+ const location = [f.filePath, f.lineNumber].filter(Boolean).join(":");
15650
+ console.log(
15651
+ ` ${chalk8[sevColor](f.severity)} [${f.layer}] ${f.category}${f.ruleId ? ` (${f.ruleId})` : ""}`
15652
+ );
15653
+ if (location) {
15654
+ console.log(` ${chalk8.dim(location)}`);
15619
15655
  }
15620
- throw new Error(errorMsg);
15656
+ console.log(` ${f.explanation}`);
15657
+ if (f.evidence) {
15658
+ console.log(
15659
+ ` ${String(chalk8.dim("Evidence: " + f.evidence.slice(0, 120))).replace(/\n|\r/g, "")}`
15660
+ );
15661
+ }
15662
+ console.log();
15621
15663
  }
15622
- sessions.push({
15623
- promptFileName: path12.basename(promptPath),
15624
- inferenceFileName: path12.basename(inferencePath),
15625
- aiResponseAt: responseTimes[i] || nowIso,
15626
- model: models[i],
15627
- toolName: tools[i],
15628
- blameType: blameTypes[i] || "CHAT" /* Chat */,
15629
- computerName,
15630
- userName,
15631
- sessionId: sessionIds[i],
15632
- repositoryUrl
15664
+ }
15665
+ if (scan2.summary) {
15666
+ console.log(chalk8.bold("Analysis:"));
15667
+ console.log(` ${scan2.summary}`);
15668
+ console.log();
15669
+ }
15670
+ if (scan2.verdict === "MALICIOUS" || scan2.verdict === "SUSPICIOUS") {
15671
+ process.exit(2);
15672
+ }
15673
+ }
15674
+
15675
+ // src/args/validation.ts
15676
+ import chalk9 from "chalk";
15677
+ import path12 from "path";
15678
+ import { z as z32 } from "zod";
15679
+ function throwRepoUrlErrorMessage({
15680
+ error,
15681
+ repoUrl,
15682
+ command
15683
+ }) {
15684
+ const errorMessage = error.issues[error.issues.length - 1]?.message;
15685
+ const formattedErrorMessage = `
15686
+ Error: ${chalk9.bold(
15687
+ repoUrl
15688
+ )} is ${errorMessage}
15689
+ Example:
15690
+ mobbdev ${command} -r ${chalk9.bold(
15691
+ "https://github.com/WebGoat/WebGoat"
15692
+ )}`;
15693
+ throw new CliError(formattedErrorMessage);
15694
+ }
15695
+ var UrlZ = z32.string({
15696
+ invalid_type_error: `is not a valid ${Object.values(ScmType).join("/ ")} URL`
15697
+ });
15698
+ function validateOrganizationId(organizationId) {
15699
+ const orgIdValidation = z32.string().uuid().nullish().safeParse(organizationId);
15700
+ if (!orgIdValidation.success) {
15701
+ throw new CliError(`organizationId: ${organizationId} is not a valid UUID`);
15702
+ }
15703
+ }
15704
+ function validateRepoUrl(args) {
15705
+ const repoSafeParseResult = UrlZ.safeParse(args.repo);
15706
+ const { success } = repoSafeParseResult;
15707
+ const [command] = args._;
15708
+ if (!command) {
15709
+ throw new CliError("Command not found");
15710
+ }
15711
+ if (!success) {
15712
+ throwRepoUrlErrorMessage({
15713
+ error: repoSafeParseResult.error,
15714
+ repoUrl: args.repo,
15715
+ command
15633
15716
  });
15634
15717
  }
15635
- const authenticatedClient = await getAuthenticatedGQLClient({
15636
- isSkipPrompts: true,
15637
- apiUrl,
15638
- webAppUrl
15639
- });
15640
- const initSessions = sessions.map(
15641
- ({ sessionId: _sessionId, ...rest }) => rest
15642
- );
15643
- const initRes = await authenticatedClient.uploadAIBlameInferencesInitRaw({
15644
- sessions: initSessions
15645
- });
15646
- const uploadSessions = initRes.uploadAIBlameInferencesInit?.uploadSessions ?? [];
15647
- if (uploadSessions.length !== sessions.length) {
15648
- const errorMsg = "Init failed to return expected number of sessions";
15649
- logger2.error(chalk10.red(errorMsg));
15650
- if (exitOnError) {
15651
- process.exit(1);
15652
- }
15653
- throw new Error(errorMsg);
15718
+ }
15719
+ var supportExtensions = [".json", ".xml", ".fpr", ".sarif", ".zip"];
15720
+ function validateReportFileFormat(reportFile) {
15721
+ if (!supportExtensions.includes(path12.extname(reportFile))) {
15722
+ throw new CliError(
15723
+ `
15724
+ ${chalk9.bold(
15725
+ reportFile
15726
+ )} is not a supported file extension. Supported extensions are: ${chalk9.bold(
15727
+ supportExtensions.join(", ")
15728
+ )}
15729
+ `
15730
+ );
15654
15731
  }
15655
- for (let i = 0; i < uploadSessions.length; i++) {
15656
- const us = uploadSessions[i];
15657
- const promptPath = String(prompts[i]);
15658
- const inferencePath = String(inferences[i]);
15659
- await Promise.all([
15660
- // Prompt
15661
- uploadFile({
15662
- file: promptPath,
15663
- url: us.prompt.url,
15664
- uploadFields: JSON.parse(us.prompt.uploadFieldsJSON),
15665
- uploadKey: us.prompt.uploadKey
15666
- }),
15667
- // Inference
15668
- uploadFile({
15669
- file: inferencePath,
15670
- url: us.inference.url,
15671
- uploadFields: JSON.parse(us.inference.uploadFieldsJSON),
15672
- uploadKey: us.inference.uploadKey
15673
- })
15674
- ]);
15732
+ }
15733
+
15734
+ // src/args/commands/analyze.ts
15735
+ function analyzeBuilder(yargs2) {
15736
+ return yargs2.option("f", {
15737
+ alias: "scan-file",
15738
+ type: "string",
15739
+ describe: chalk10.bold(
15740
+ "Select the vulnerability report to analyze (Checkmarx, Snyk, Fortify, CodeQL, Sonarqube, Semgrep, Datadog)"
15741
+ )
15742
+ }).option("repo", repoOption).option("p", {
15743
+ alias: "src-path",
15744
+ describe: chalk10.bold(
15745
+ "Path to the repository folder with the source code; alternatively, you can specify the Fortify FPR file to extract source code out of it"
15746
+ ),
15747
+ type: "string"
15748
+ }).option("ref", refOption).option("ch", {
15749
+ alias: "commit-hash",
15750
+ describe: chalk10.bold("Hash of the commit"),
15751
+ type: "string"
15752
+ }).option("mobb-project-name", mobbProjectNameOption).option("y", yesOption).option("ci", ciOption).option("org", organizationIdOptions).option("api-key", apiKeyOption).option("commit-hash", commitHashOption).option("auto-pr", autoPrOption).option("create-one-pr", createOnePrOption).option("commit-directly", commitDirectlyOption).option("pull-request", {
15753
+ alias: ["pr", "pr-number", "pr-id"],
15754
+ describe: chalk10.bold("Number of the pull request"),
15755
+ type: "number",
15756
+ demandOption: false
15757
+ }).option("polling", pollingOption).example(
15758
+ "npx mobbdev@latest analyze -r https://github.com/WebGoat/WebGoat -f <your_vulnerability_report_path>",
15759
+ "analyze an existing repository"
15760
+ ).help();
15761
+ }
15762
+ function validateAnalyzeOptions(argv) {
15763
+ if (argv.f && !fs12.existsSync(argv.f)) {
15764
+ throw new CliError(`
15765
+ Can't access ${chalk10.bold(argv.f)}`);
15675
15766
  }
15676
- const finalizeSessions = uploadSessions.map((us, i) => {
15677
- const s = sessions[i];
15678
- return {
15679
- aiBlameInferenceId: us.aiBlameInferenceId,
15680
- promptKey: us.prompt.uploadKey,
15681
- inferenceKey: us.inference.uploadKey,
15682
- aiResponseAt: s.aiResponseAt,
15683
- model: s.model,
15684
- toolName: s.toolName,
15685
- blameType: s.blameType,
15686
- computerName: s.computerName,
15687
- userName: s.userName,
15688
- sessionId: s.sessionId,
15689
- repositoryUrl: s.repositoryUrl
15690
- };
15691
- });
15692
- try {
15693
- logger2.info(
15694
- `[UPLOAD] Calling finalizeAIBlameInferencesUploadRaw with ${finalizeSessions.length} sessions`
15767
+ validateOrganizationId(argv.organizationId);
15768
+ if (!argv.srcPath && !argv.repo) {
15769
+ throw new CliError("You must supply either --src-path or --repo");
15770
+ }
15771
+ if (!argv.srcPath && argv.repo) {
15772
+ validateRepoUrl(argv);
15773
+ }
15774
+ if (argv.ci && !argv.apiKey) {
15775
+ throw new CliError("--ci flag requires --api-key to be provided as well");
15776
+ }
15777
+ if (argv.commitDirectly && !argv["auto-pr"]) {
15778
+ throw new CliError(
15779
+ "--commit-directly flag requires --auto-pr to be provided as well"
15695
15780
  );
15696
- const finRes = await authenticatedClient.finalizeAIBlameInferencesUploadRaw(
15697
- {
15698
- sessions: finalizeSessions
15699
- }
15781
+ }
15782
+ if (argv["create-one-pr"] && !argv["auto-pr"]) {
15783
+ throw new CliError(
15784
+ "--create-one-pr flag requires --auto-pr to be provided as well"
15700
15785
  );
15701
- logger2.info("[UPLOAD] Finalize response:", JSON.stringify(finRes, null, 2));
15702
- const status = finRes?.finalizeAIBlameInferencesUpload?.status;
15703
- if (status !== "OK") {
15704
- const errorMsg = finRes?.finalizeAIBlameInferencesUpload?.error || "unknown error";
15705
- logger2.error(
15706
- chalk10.red(
15707
- `[UPLOAD] Finalize failed with status: ${status}, error: ${errorMsg}`
15708
- )
15709
- );
15710
- if (exitOnError) {
15711
- process.exit(1);
15712
- }
15713
- throw new Error(errorMsg);
15714
- }
15715
- logger2.info(chalk10.green("[UPLOAD] AI Blame uploads finalized successfully"));
15716
- } catch (error) {
15717
- logger2.error("[UPLOAD] Finalize threw error:", error);
15718
- throw error;
15786
+ }
15787
+ if (argv["create-one-pr"] && argv.commitDirectly) {
15788
+ throw new CliError(
15789
+ "--create-one-pr and --commit-directly cannot be provided at the same time"
15790
+ );
15791
+ }
15792
+ if (argv.pullRequest && !argv.commitDirectly) {
15793
+ throw new CliError(
15794
+ "--pull-request flag requires --commit-directly to be provided as well"
15795
+ );
15796
+ }
15797
+ if (argv.f) {
15798
+ validateReportFileFormat(argv.f);
15719
15799
  }
15720
15800
  }
15721
- async function uploadAiBlameCommandHandler(args) {
15722
- await uploadAiBlameHandler({ args });
15801
+ async function analyzeHandler(args) {
15802
+ validateAnalyzeOptions(args);
15803
+ await analyze(args, { skipPrompts: args.yes });
15723
15804
  }
15724
15805
 
15725
15806
  // src/features/claude_code/data_collector.ts
15807
+ import { z as z33 } from "zod";
15726
15808
  init_client_generates();
15727
15809
  init_GitService();
15728
15810
  init_urlParser2();
@@ -16348,7 +16430,7 @@ var log = logger.log.bind(logger);
16348
16430
 
16349
16431
  // src/mcp/services/McpGQLClient.ts
16350
16432
  import crypto3 from "crypto";
16351
- import { GraphQLClient as GraphQLClient2 } from "graphql-request";
16433
+ import { ClientError as ClientError2, GraphQLClient as GraphQLClient2 } from "graphql-request";
16352
16434
  import { v4 as uuidv42 } from "uuid";
16353
16435
  init_client_generates();
16354
16436
  init_configs();
@@ -16487,12 +16569,15 @@ import crypto2 from "crypto";
16487
16569
  import os5 from "os";
16488
16570
  import open4 from "open";
16489
16571
  init_configs();
16490
- var McpAuthService = class {
16572
+ var _McpAuthService = class _McpAuthService {
16491
16573
  constructor(client) {
16492
16574
  __publicField(this, "client");
16493
- __publicField(this, "lastBrowserOpenTime", 0);
16494
16575
  this.client = client;
16495
16576
  }
16577
+ /** Reset cooldown state. Used by tests to ensure isolation. */
16578
+ static resetCooldown() {
16579
+ _McpAuthService.lastBrowserOpenTime = 0;
16580
+ }
16496
16581
  /**
16497
16582
  * Opens a browser window for authentication
16498
16583
  * @param url URL to open in browser
@@ -16501,14 +16586,15 @@ var McpAuthService = class {
16501
16586
  async openBrowser(url, isBackgoundCall) {
16502
16587
  if (isBackgoundCall) {
16503
16588
  const now = Date.now();
16504
- if (now - this.lastBrowserOpenTime < MCP_TOOLS_BROWSER_COOLDOWN_MS) {
16589
+ if (now - _McpAuthService.lastBrowserOpenTime < MCP_TOOLS_BROWSER_COOLDOWN_MS) {
16505
16590
  logDebug(`browser cooldown active, skipping open for ${url}`);
16506
- return;
16591
+ return false;
16507
16592
  }
16508
16593
  }
16509
16594
  logDebug(`opening browser url ${url}`);
16510
16595
  await open4(url);
16511
- this.lastBrowserOpenTime = Date.now();
16596
+ _McpAuthService.lastBrowserOpenTime = Date.now();
16597
+ return true;
16512
16598
  }
16513
16599
  /**
16514
16600
  * Handles the complete authentication flow
@@ -16530,7 +16616,12 @@ var McpAuthService = class {
16530
16616
  logDebug(`cli login created ${loginId}`);
16531
16617
  const webLoginUrl = `${WEB_APP_URL}/mvs-login`;
16532
16618
  const browserUrl = loginContext ? buildLoginUrl(webLoginUrl, loginId, os5.hostname(), loginContext) : `${webLoginUrl}/${loginId}?hostname=${os5.hostname()}`;
16533
- await this.openBrowser(browserUrl, isBackgoundCall);
16619
+ const browserOpened = await this.openBrowser(browserUrl, isBackgoundCall);
16620
+ if (!browserOpened) {
16621
+ throw new AuthenticationError(
16622
+ "Authentication required but browser cooldown is active"
16623
+ );
16624
+ }
16534
16625
  logDebug(`waiting for login to complete`);
16535
16626
  let newApiToken = null;
16536
16627
  for (let i = 0; i < MCP_LOGIN_MAX_WAIT / MCP_LOGIN_CHECK_DELAY; i++) {
@@ -16561,8 +16652,24 @@ var McpAuthService = class {
16561
16652
  return newApiToken;
16562
16653
  }
16563
16654
  };
16655
+ // Static so cooldown persists across McpAuthService instances
16656
+ __publicField(_McpAuthService, "lastBrowserOpenTime", 0);
16657
+ var McpAuthService = _McpAuthService;
16564
16658
 
16565
16659
  // src/mcp/services/McpGQLClient.ts
16660
+ function isAuthError2(error) {
16661
+ if (error instanceof ClientError2) {
16662
+ const gqlErrors = error.response?.errors;
16663
+ return gqlErrors?.some(
16664
+ (e) => e.extensions?.["code"] === "access-denied" || e.message?.includes("Authentication hook unauthorized")
16665
+ ) ?? false;
16666
+ }
16667
+ return false;
16668
+ }
16669
+ function isNetworkError2(error) {
16670
+ const errorString = error?.toString() ?? "";
16671
+ return errorString.includes("FetchError") || errorString.includes("TypeError") || errorString.includes("ECONNREFUSED") || errorString.includes("ENOTFOUND") || errorString.includes("ETIMEDOUT") || errorString.includes("UND_ERR");
16672
+ }
16566
16673
  var McpGQLClient = class {
16567
16674
  constructor(args) {
16568
16675
  __publicField(this, "client");
@@ -16616,14 +16723,16 @@ var McpGQLClient = class {
16616
16723
  logDebug("[GraphQL] Me query successful", { result });
16617
16724
  return true;
16618
16725
  } catch (e) {
16619
- const error = e;
16620
- logDebug(`[GraphQL] API connection verification failed`, { error });
16621
- if (error?.toString().includes("FetchError")) {
16622
- logError("[GraphQL] API connection verification failed", { error });
16726
+ logDebug("[GraphQL] API connection verification failed", { error: e });
16727
+ if (isNetworkError2(e)) {
16728
+ logError("[GraphQL] API endpoint unreachable (network error)", {
16729
+ error: e
16730
+ });
16623
16731
  return false;
16624
16732
  }
16733
+ logDebug("[GraphQL] API endpoint is reachable (non-network error)");
16734
+ return true;
16625
16735
  }
16626
- return true;
16627
16736
  }
16628
16737
  /**
16629
16738
  * Verifies both API endpoint reachability and user authentication
@@ -16894,11 +17003,27 @@ var McpGQLClient = class {
16894
17003
  try {
16895
17004
  await this.clientSdk.CreateCommunityUser();
16896
17005
  const info = await this.getUserInfo();
17006
+ if (!info) {
17007
+ logDebug("[GraphQL] User token is invalid (no user info returned)");
17008
+ return false;
17009
+ }
16897
17010
  logDebug("[GraphQL] User token validated successfully");
16898
- return info?.email || true;
17011
+ return info.email || true;
16899
17012
  } catch (e) {
16900
- logError("[GraphQL] User token validation failed");
16901
- return false;
17013
+ if (isAuthError2(e)) {
17014
+ logDebug("[GraphQL] User token is invalid (auth error from server)");
17015
+ return false;
17016
+ }
17017
+ if (isNetworkError2(e)) {
17018
+ logError("[GraphQL] Token validation failed due to network error", {
17019
+ error: e
17020
+ });
17021
+ throw e;
17022
+ }
17023
+ logError("[GraphQL] Token validation failed with unexpected error", {
17024
+ error: e
17025
+ });
17026
+ throw e;
16902
17027
  }
16903
17028
  }
16904
17029
  async createCliLogin(variables) {
@@ -17206,7 +17331,15 @@ async function createAuthenticatedMcpGQLClient({
17206
17331
  throw new ApiConnectionError("Error: failed to reach Mobb GraphQL endpoint");
17207
17332
  }
17208
17333
  logDebug("[GraphQL] Validating user token");
17209
- const userVerify = await initialClient.validateUserToken();
17334
+ let userVerify;
17335
+ try {
17336
+ userVerify = await initialClient.validateUserToken();
17337
+ } catch (e) {
17338
+ logError("[GraphQL] Token validation failed due to transient error", {
17339
+ error: e
17340
+ });
17341
+ throw e;
17342
+ }
17210
17343
  if (userVerify) {
17211
17344
  return initialClient;
17212
17345
  }
@@ -20157,13 +20290,7 @@ var BaseTool = class {
20157
20290
  }
20158
20291
  async execute(args) {
20159
20292
  if (this.hasAuthentication) {
20160
- logDebug(`Authenticating tool: ${this.name}`, { args });
20161
- const loginContext = createMcpLoginContext(this.name);
20162
- const mcpGqlClient = await createAuthenticatedMcpGQLClient({
20163
- loginContext
20164
- });
20165
- const userInfo2 = await mcpGqlClient.getUserInfo();
20166
- logDebug("User authenticated successfully", { userInfo: userInfo2 });
20293
+ logDebug(`Tool ${this.name} requires authentication (handled by service)`);
20167
20294
  }
20168
20295
  const validatedArgs = this.validateInput(args);
20169
20296
  logDebug(`Tool ${this.name} input validation successful`, {
@@ -24963,13 +25090,13 @@ var parseArgs = async (args) => {
24963
25090
  };
24964
25091
 
24965
25092
  // src/index.ts
24966
- var debug20 = Debug20("mobbdev:index");
25093
+ var debug22 = Debug22("mobbdev:index");
24967
25094
  async function run() {
24968
25095
  return parseArgs(hideBin(process.argv));
24969
25096
  }
24970
25097
  (async () => {
24971
25098
  try {
24972
- debug20("Bugsy CLI v%s running...", packageJson.version);
25099
+ debug22("Bugsy CLI v%s running...", packageJson.version);
24973
25100
  await run();
24974
25101
  process.exit(0);
24975
25102
  } catch (err) {