mobbdev 1.0.125 → 1.0.126

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.mjs +280 -217
  2. package/package.json +5 -4
package/dist/index.mjs CHANGED
@@ -32,7 +32,7 @@ var init_env = __esm({
32
32
  });
33
33
 
34
34
  // src/mcp/core/configs.ts
35
- var MCP_DEFAULT_API_URL, MCP_API_KEY_HEADER_NAME, MCP_LOGIN_MAX_WAIT, MCP_LOGIN_CHECK_DELAY, MCP_VUL_REPORT_DIGEST_TIMEOUT_MS, MCP_MAX_FILE_SIZE, MCP_PERIODIC_CHECK_INTERVAL, MCP_DEFAULT_MAX_FILES_TO_SCAN, MCP_REPORT_ID_EXPIRATION_MS, MCP_TOOLS_BROWSER_COOLDOWN_MS, MCP_DEFAULT_LIMIT;
35
+ var MCP_DEFAULT_API_URL, MCP_API_KEY_HEADER_NAME, MCP_LOGIN_MAX_WAIT, MCP_LOGIN_CHECK_DELAY, MCP_VUL_REPORT_DIGEST_TIMEOUT_MS, MCP_MAX_FILE_SIZE, MCP_PERIODIC_CHECK_INTERVAL, MCP_DEFAULT_MAX_FILES_TO_SCAN, MCP_REPORT_ID_EXPIRATION_MS, MCP_TOOLS_BROWSER_COOLDOWN_MS, MCP_DEFAULT_LIMIT, isAutoScan;
36
36
  var init_configs = __esm({
37
37
  "src/mcp/core/configs.ts"() {
38
38
  "use strict";
@@ -48,6 +48,7 @@ var init_configs = __esm({
48
48
  MCP_REPORT_ID_EXPIRATION_MS = 2 * 60 * 60 * 1e3;
49
49
  MCP_TOOLS_BROWSER_COOLDOWN_MS = 24 * 60 * 60 * 1e3;
50
50
  MCP_DEFAULT_LIMIT = 3;
51
+ isAutoScan = process.env["AUTO_SCAN"] !== "false";
51
52
  }
52
53
  });
53
54
 
@@ -2259,14 +2260,14 @@ var GetReportFixesDocument = `
2259
2260
  var GetLatestReportByRepoUrlDocument = `
2260
2261
  query GetLatestReportByRepoUrl($repoUrl: String!, $filters: fix_bool_exp = {}, $limit: Int!, $offset: Int!, $currentUserEmail: String!) {
2261
2262
  fixReport(
2262
- where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Finished}}]}
2263
+ where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Finished}}, {vulnerabilityReport: {_or: [{vendor: {_is_null: true}}, {vendor: {_nin: [semgrep, opengrep]}}]}}]}
2263
2264
  order_by: {createdOn: desc}
2264
2265
  limit: 1
2265
2266
  ) {
2266
2267
  ...FixReportSummaryFields
2267
2268
  }
2268
2269
  expiredReport: fixReport(
2269
- where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Expired}}]}
2270
+ where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Expired}}, {_or: [{vulnerabilityReport: {vendor: {_is_null: true}}}, {vulnerabilityReport: {vendor: {_nin: [semgrep, opengrep]}}}]}]}
2270
2271
  order_by: {createdOn: desc}
2271
2272
  limit: 1
2272
2273
  ) {
@@ -11953,104 +11954,34 @@ import {
11953
11954
  } from "@modelcontextprotocol/sdk/types.js";
11954
11955
 
11955
11956
  // src/mcp/Logger.ts
11956
- var loggerUrl = "http://localhost:4444/log";
11957
- var isTestEnvironment = process.env["VITEST"] || process.env["TEST"];
11958
- var CIRCUIT_BREAKER_TIME = 5e3;
11959
- var URL_CHECK_TIMEOUT = 200;
11960
- var MAX_QUEUE_SIZE = 100;
11957
+ import Configstore3 from "configstore";
11958
+ var MAX_LOGS_SIZE = 1e3;
11961
11959
  var Logger = class {
11962
11960
  constructor() {
11963
- __publicField(this, "queue", []);
11964
- __publicField(this, "isProcessing", false);
11965
- __publicField(this, "isCircuitBroken", false);
11966
- __publicField(this, "circuitBreakerTimer", null);
11961
+ __publicField(this, "mobbConfigStore");
11962
+ __publicField(this, "path");
11963
+ this.path = process.env["WORKSPACE_FOLDER_PATHS"] || "unknown";
11964
+ this.mobbConfigStore = new Configstore3("mobb-logs", {});
11965
+ this.mobbConfigStore.set("version", packageJson.version);
11967
11966
  }
11967
+ /**
11968
+ * Log a message to the console.
11969
+ * @param message - The message to log.
11970
+ * @param level - The level of the message.
11971
+ * @param data - The data to log.
11972
+ */
11968
11973
  log(message, level = "info", data) {
11969
- if (isTestEnvironment) return;
11970
- if (this.queue.length >= MAX_QUEUE_SIZE) {
11971
- this.queue.shift();
11972
- }
11973
- this.queue.push({ message, level, data });
11974
- if (!this.isProcessing && !this.isCircuitBroken) {
11975
- this.processQueue();
11976
- }
11977
- }
11978
- async isUrlReachable(url) {
11979
- try {
11980
- const controller = new AbortController();
11981
- const timeoutId = setTimeout(() => controller.abort(), URL_CHECK_TIMEOUT);
11982
- await fetch(url, {
11983
- method: "HEAD",
11984
- signal: controller.signal
11985
- });
11986
- clearTimeout(timeoutId);
11987
- return true;
11988
- } catch (error) {
11989
- return false;
11990
- }
11991
- }
11992
- async processQueue() {
11993
- if (this.queue.length === 0 || this.isCircuitBroken) {
11994
- this.isProcessing = false;
11995
- return;
11996
- }
11997
- this.isProcessing = true;
11998
- const logEntry = this.queue[0];
11999
- if (!logEntry) {
12000
- this.isProcessing = false;
12001
- return;
12002
- }
12003
- const isReachable = await this.isUrlReachable(loggerUrl);
12004
- if (!isReachable) {
12005
- this.triggerCircuitBreaker();
12006
- return;
12007
- }
12008
- await this.sendLogEntry(logEntry);
12009
- }
12010
- async sendLogEntry(logEntry) {
12011
11974
  const logMessage = {
12012
11975
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12013
- level: logEntry.level,
12014
- message: logEntry.message,
12015
- data: logEntry.data,
12016
- version: packageJson.version
11976
+ level,
11977
+ message,
11978
+ data
12017
11979
  };
12018
- const controller = new AbortController();
12019
- const timeoutId = setTimeout(() => {
12020
- controller.abort();
12021
- }, 500);
12022
- try {
12023
- await fetch(loggerUrl, {
12024
- method: "POST",
12025
- headers: { "Content-Type": "application/json" },
12026
- body: JSON.stringify(logMessage),
12027
- redirect: "error",
12028
- // do not follow redirects
12029
- signal: controller.signal
12030
- });
12031
- this.queue.shift();
12032
- setTimeout(() => this.processQueue(), 0);
12033
- } catch (error) {
12034
- this.triggerCircuitBreaker();
12035
- logError("Failed to send log entry", error);
12036
- } finally {
12037
- clearTimeout(timeoutId);
12038
- }
12039
- }
12040
- triggerCircuitBreaker() {
12041
- this.isCircuitBroken = true;
12042
- this.queue = [];
12043
- this.isProcessing = false;
12044
- if (this.circuitBreakerTimer) {
12045
- clearTimeout(this.circuitBreakerTimer);
11980
+ const logs = this.mobbConfigStore.get(this.path) || [];
11981
+ if (logs.length >= MAX_LOGS_SIZE) {
11982
+ logs.shift();
12046
11983
  }
12047
- this.circuitBreakerTimer = setTimeout(() => {
12048
- this.isCircuitBroken = false;
12049
- this.circuitBreakerTimer = null;
12050
- if (this.queue.length > 0 && !this.isProcessing) {
12051
- this.processQueue();
12052
- }
12053
- }, CIRCUIT_BREAKER_TIME);
11984
+ this.mobbConfigStore.set(this.path, [...logs, logMessage]);
12054
11985
  }
12055
11986
  };
12056
11987
  var logger = new Logger();
@@ -12061,12 +11992,34 @@ var logDebug = (message, data) => logger.log(message, "debug", data);
12061
11992
  var log = logger.log.bind(logger);
12062
11993
 
12063
11994
  // src/mcp/services/McpGQLClient.ts
12064
- import Configstore3 from "configstore";
12065
11995
  import crypto3 from "crypto";
12066
11996
  import { GraphQLClient as GraphQLClient2 } from "graphql-request";
12067
11997
  import { v4 as uuidv42 } from "uuid";
12068
11998
  init_configs();
12069
11999
 
12000
+ // src/mcp/services/ConfigStoreService.ts
12001
+ init_configs();
12002
+ import Configstore4 from "configstore";
12003
+ function createConfigStore(defaultValues = { apiToken: "" }) {
12004
+ const API_URL2 = process.env["API_URL"] || MCP_DEFAULT_API_URL;
12005
+ let domain = "";
12006
+ try {
12007
+ const url = new URL(API_URL2);
12008
+ domain = url.hostname;
12009
+ } catch (e) {
12010
+ domain = API_URL2.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/:\d+$/, "");
12011
+ }
12012
+ const sanitizedDomain = domain.replace(/\./g, "_");
12013
+ return new Configstore4(
12014
+ `${packageJson.name}-${sanitizedDomain}`,
12015
+ defaultValues
12016
+ );
12017
+ }
12018
+ function getConfigStore() {
12019
+ return createConfigStore();
12020
+ }
12021
+ var configStore = getConfigStore();
12022
+
12070
12023
  // src/mcp/services/McpAuthService.ts
12071
12024
  import crypto2 from "crypto";
12072
12025
  import os2 from "os";
@@ -12147,16 +12100,17 @@ var McpAuthService = class {
12147
12100
  };
12148
12101
 
12149
12102
  // src/mcp/services/McpGQLClient.ts
12150
- var mobbConfigStore = new Configstore3(packageJson.name, { apiToken: "" });
12151
12103
  var McpGQLClient = class {
12152
12104
  constructor(args) {
12153
12105
  __publicField(this, "client");
12154
12106
  __publicField(this, "clientSdk");
12155
12107
  __publicField(this, "_auth");
12108
+ __publicField(this, "currentUser", null);
12109
+ __publicField(this, "apiUrl");
12156
12110
  this._auth = args;
12157
- const API_URL2 = process.env["API_URL"] || MCP_DEFAULT_API_URL;
12158
- logDebug("creating graphql client", { API_URL: API_URL2, args });
12159
- this.client = new GraphQLClient2(API_URL2, {
12111
+ this.apiUrl = process.env["API_URL"] || MCP_DEFAULT_API_URL;
12112
+ logDebug(`creating graphql client with api url ${this.apiUrl}`, { args });
12113
+ this.client = new GraphQLClient2(this.apiUrl, {
12160
12114
  headers: args.type === "apiKey" ? { [MCP_API_KEY_HEADER_NAME]: args.apiKey || "" } : {
12161
12115
  Authorization: `Bearer ${args.token}`
12162
12116
  },
@@ -12175,7 +12129,7 @@ var McpGQLClient = class {
12175
12129
  }
12176
12130
  getErrorContext() {
12177
12131
  return {
12178
- endpoint: process.env["API_URL"] || MCP_DEFAULT_API_URL,
12132
+ endpoint: this.apiUrl,
12179
12133
  apiKey: this._auth.type === "apiKey" ? this._auth.apiKey : "",
12180
12134
  headers: {
12181
12135
  [MCP_API_KEY_HEADER_NAME]: this._auth.type === "apiKey" ? "[REDACTED]" : "undefined",
@@ -12183,10 +12137,10 @@ var McpGQLClient = class {
12183
12137
  }
12184
12138
  };
12185
12139
  }
12186
- async verifyApiConnection() {
12140
+ async isApiEndpointReachable() {
12187
12141
  try {
12188
12142
  logDebug("GraphQL: Calling Me query for API connection verification");
12189
- const result = await this.clientSdk.Me();
12143
+ const result = await this.getUserInfo();
12190
12144
  logDebug("GraphQL: Me query successful", { result });
12191
12145
  return true;
12192
12146
  } catch (e) {
@@ -12199,6 +12153,23 @@ var McpGQLClient = class {
12199
12153
  }
12200
12154
  return true;
12201
12155
  }
12156
+ /**
12157
+ * Verifies both API endpoint reachability and user authentication
12158
+ * @returns true if both API is reachable and user is authenticated
12159
+ */
12160
+ async verifyApiConnection() {
12161
+ const isReachable = await this.isApiEndpointReachable();
12162
+ if (!isReachable) {
12163
+ return false;
12164
+ }
12165
+ try {
12166
+ await this.validateUserToken();
12167
+ return true;
12168
+ } catch (e) {
12169
+ logError("User token validation failed", { error: e });
12170
+ return false;
12171
+ }
12172
+ }
12202
12173
  async uploadS3BucketInfo() {
12203
12174
  try {
12204
12175
  logDebug("GraphQL: Calling uploadS3BucketInfo mutation");
@@ -12253,8 +12224,9 @@ var McpGQLClient = class {
12253
12224
  }
12254
12225
  }
12255
12226
  async subscribeToGetAnalysis(params) {
12227
+ const { scanContext } = params;
12256
12228
  try {
12257
- logDebug("GraphQL: Starting GetAnalysis subscription", {
12229
+ logDebug(`[${scanContext}] GraphQL: Starting GetAnalysis subscription`, {
12258
12230
  params: params.subscribeToAnalysisParams
12259
12231
  });
12260
12232
  const { callbackStates } = params;
@@ -12262,10 +12234,13 @@ var McpGQLClient = class {
12262
12234
  GetAnalysisSubscriptionDocument,
12263
12235
  params.subscribeToAnalysisParams,
12264
12236
  async (resolve, reject, data) => {
12265
- logDebug("GraphQL: GetAnalysis subscription data received", { data });
12237
+ logDebug(
12238
+ `[${scanContext}] GraphQL: GetAnalysis subscription data received ${data.analysis?.state}`,
12239
+ { data }
12240
+ );
12266
12241
  if (!data.analysis?.state || data.analysis?.state === "Failed" /* Failed */) {
12267
12242
  const errorMessage = data.analysis?.failReason || `Analysis failed with id: ${data.analysis?.id}`;
12268
- logError("GraphQL: Analysis failed", {
12243
+ logError(`[${scanContext}] GraphQL: Analysis failed`, {
12269
12244
  analysisId: data.analysis?.id,
12270
12245
  state: data.analysis?.state,
12271
12246
  failReason: data.analysis?.failReason,
@@ -12275,11 +12250,14 @@ var McpGQLClient = class {
12275
12250
  return;
12276
12251
  }
12277
12252
  if (callbackStates.includes(data.analysis?.state)) {
12278
- logDebug("GraphQL: Analysis state matches callback states", {
12279
- analysisId: data.analysis.id,
12280
- state: data.analysis.state,
12281
- callbackStates
12282
- });
12253
+ logDebug(
12254
+ `[${scanContext}] GraphQL: Analysis state matches callback states: ${data.analysis.state}`,
12255
+ {
12256
+ analysisId: data.analysis.id,
12257
+ state: data.analysis.state,
12258
+ callbackStates
12259
+ }
12260
+ );
12283
12261
  await params.callback(data.analysis.id);
12284
12262
  resolve(data);
12285
12263
  }
@@ -12294,10 +12272,12 @@ var McpGQLClient = class {
12294
12272
  timeoutInMs: params.timeoutInMs
12295
12273
  }
12296
12274
  );
12297
- logDebug("GraphQL: GetAnalysis subscription completed", { result });
12275
+ logDebug(`[${scanContext}] GraphQL: GetAnalysis subscription completed`, {
12276
+ result
12277
+ });
12298
12278
  return result;
12299
12279
  } catch (e) {
12300
- logError("GraphQL: GetAnalysis subscription failed", {
12280
+ logError(`[${scanContext}] GraphQL: GetAnalysis subscription failed`, {
12301
12281
  error: e,
12302
12282
  params: params.subscribeToAnalysisParams,
12303
12283
  ...this.getErrorContext()
@@ -12361,8 +12341,12 @@ var McpGQLClient = class {
12361
12341
  }
12362
12342
  async getUserInfo() {
12363
12343
  const { me } = await this.clientSdk.Me();
12344
+ this.currentUser = me;
12364
12345
  return me;
12365
12346
  }
12347
+ getCurrentUser() {
12348
+ return this.currentUser;
12349
+ }
12366
12350
  async validateUserToken() {
12367
12351
  logDebug("validating user token");
12368
12352
  try {
@@ -12555,16 +12539,16 @@ var McpGQLClient = class {
12555
12539
  async function createAuthenticatedMcpGQLClient({
12556
12540
  isBackgoundCall = false
12557
12541
  } = {}) {
12558
- logDebug("getting config", { apiToken: mobbConfigStore.get("apiToken") });
12542
+ logDebug("getting config", { apiToken: configStore.get("apiToken") });
12559
12543
  const initialClient = new McpGQLClient({
12560
12544
  apiKey: process.env["MOBB_API_KEY"] || process.env["API_KEY"] || // fallback for backward compatibility
12561
- mobbConfigStore.get("apiToken") || "",
12545
+ configStore.get("apiToken") || "",
12562
12546
  type: "apiKey"
12563
12547
  });
12564
- const isConnected = await initialClient.verifyApiConnection();
12565
- logDebug("API connection status", { isConnected });
12566
- if (!isConnected) {
12567
- throw new ApiConnectionError("Error: failed to connect to Mobb API");
12548
+ const isApiEndpointReachable = await initialClient.isApiEndpointReachable();
12549
+ logDebug("API connection status", { isApiEndpointReachable });
12550
+ if (!isApiEndpointReachable) {
12551
+ throw new ApiConnectionError("Error: failed to reach Mobb GraphQL endpoint");
12568
12552
  }
12569
12553
  logDebug("validating user token");
12570
12554
  const userVerify = await initialClient.validateUserToken();
@@ -12573,7 +12557,7 @@ async function createAuthenticatedMcpGQLClient({
12573
12557
  }
12574
12558
  const authService = new McpAuthService(initialClient);
12575
12559
  const newApiToken = await authService.authenticate(isBackgoundCall);
12576
- mobbConfigStore.set("apiToken", newApiToken);
12560
+ configStore.set("apiToken", newApiToken);
12577
12561
  return new McpGQLClient({ apiKey: newApiToken, type: "apiKey" });
12578
12562
  }
12579
12563
 
@@ -12582,6 +12566,9 @@ var MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES = "check_for_new_available_fixes";
12582
12566
  var MCP_TOOL_FETCH_AVAILABLE_FIXES = "fetch_available_fixes";
12583
12567
  var MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES = "scan_and_fix_vulnerabilities";
12584
12568
 
12569
+ // src/mcp/core/McpServer.ts
12570
+ init_configs();
12571
+
12585
12572
  // src/mcp/core/ToolRegistry.ts
12586
12573
  var ToolRegistry = class {
12587
12574
  constructor() {
@@ -12656,6 +12643,18 @@ var McpServer = class {
12656
12643
  } else {
12657
12644
  logWarn(`${message} (exit code: ${exitCode})`, { signal, exitCode });
12658
12645
  }
12646
+ } else if (signal === "unhandledRejection") {
12647
+ const errorDetails = {
12648
+ signal,
12649
+ errorType: error?.constructor?.name || "Unknown",
12650
+ errorMessage: error instanceof Error ? error.message : String(error),
12651
+ errorStack: error instanceof Error ? error.stack : void 0,
12652
+ errorString: error?.toString(),
12653
+ errorJson: JSON.stringify(error, null, 2),
12654
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
12655
+ };
12656
+ logError(`${message} - Enhanced error details`, errorDetails);
12657
+ logError(`${message} - Raw error object`, { error, signal });
12659
12658
  } else if (error) {
12660
12659
  logError(`${message}`, { error, signal });
12661
12660
  } else {
@@ -12700,30 +12699,43 @@ var McpServer = class {
12700
12699
  });
12701
12700
  }
12702
12701
  async triggerScanForNewAvailableFixes() {
12703
- const gqlClient = await createAuthenticatedMcpGQLClient({
12704
- isBackgoundCall: true
12705
- });
12706
- const isConnected = await gqlClient.verifyApiConnection();
12707
- if (!isConnected) {
12708
- logError("Failed to connect to the API, skipping scan");
12709
- return;
12710
- }
12711
- if (process.env["WORKSPACE_FOLDER_PATHS"]) {
12712
- logDebug("WORKSPACE_FOLDER_PATHS is set", {
12713
- WORKSPACE_FOLDER_PATHS: process.env["WORKSPACE_FOLDER_PATHS"]
12702
+ try {
12703
+ const gqlClient = await createAuthenticatedMcpGQLClient({
12704
+ isBackgoundCall: true
12714
12705
  });
12715
- try {
12716
- const checkForNewAvailableFixesTool = this.toolRegistry.getTool(
12717
- MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES
12718
- );
12719
- logInfo("Triggering periodic scan for new available fixes");
12720
- checkForNewAvailableFixesTool.triggerScan({
12721
- path: process.env["WORKSPACE_FOLDER_PATHS"],
12722
- gqlClient
12706
+ const isConnected = await gqlClient.verifyApiConnection();
12707
+ if (!isConnected) {
12708
+ logError("Failed to connect to the API, skipping background scan");
12709
+ return;
12710
+ }
12711
+ if (process.env["WORKSPACE_FOLDER_PATHS"]) {
12712
+ logDebug("WORKSPACE_FOLDER_PATHS is set", {
12713
+ WORKSPACE_FOLDER_PATHS: process.env["WORKSPACE_FOLDER_PATHS"]
12723
12714
  });
12724
- } catch (error) {
12725
- logError("Error getting workspace folder path tool", { error });
12715
+ try {
12716
+ const checkForNewAvailableFixesTool = this.toolRegistry.getTool(
12717
+ MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES
12718
+ );
12719
+ logInfo("Triggering periodic scan for new available fixes");
12720
+ checkForNewAvailableFixesTool.triggerScan({
12721
+ path: process.env["WORKSPACE_FOLDER_PATHS"],
12722
+ gqlClient
12723
+ });
12724
+ } catch (error) {
12725
+ logError("Error getting workspace folder path tool", { error });
12726
+ }
12727
+ }
12728
+ } catch (error) {
12729
+ if (error instanceof Error && (error.message.includes("Authentication") || error.message.includes("failed to connect to Mobb API"))) {
12730
+ logError(
12731
+ "Background scan skipped due to authentication failure. Please re-authenticate by running a manual scan.",
12732
+ {
12733
+ error: error.message
12734
+ }
12735
+ );
12736
+ return;
12726
12737
  }
12738
+ logError("Unexpected error during background scan", { error });
12727
12739
  }
12728
12740
  }
12729
12741
  async handleListToolsRequest(request) {
@@ -12737,7 +12749,11 @@ var McpServer = class {
12737
12749
  logDebug("env", {
12738
12750
  env: process.env
12739
12751
  });
12740
- void this.triggerScanForNewAvailableFixes();
12752
+ if (isAutoScan) {
12753
+ void this.triggerScanForNewAvailableFixes();
12754
+ } else {
12755
+ logDebug("Auto scan disabled, skipping triggerScanForNewAvailableFixes");
12756
+ }
12741
12757
  const toolsDefinitions = this.toolRegistry.getAllTools();
12742
12758
  const response = {
12743
12759
  tools: toolsDefinitions.map((tool) => ({
@@ -12947,7 +12963,6 @@ var BaseTool = class {
12947
12963
 
12948
12964
  // src/mcp/tools/checkForNewAvailableFixes/CheckForNewAvailableFixesService.ts
12949
12965
  init_configs();
12950
- import Configstore4 from "configstore";
12951
12966
 
12952
12967
  // src/mcp/core/prompts.ts
12953
12968
  init_configs();
@@ -13566,6 +13581,18 @@ var initializeSecurityReport = async (gqlClient, scanContext) => {
13566
13581
  return repoUploadInfo;
13567
13582
  } catch (error) {
13568
13583
  const message = error.message;
13584
+ if (message.includes("Authentication hook unauthorized") || message.includes("access-denied")) {
13585
+ logError(
13586
+ "Authentication failed during security report initialization. Please re-authenticate.",
13587
+ {
13588
+ error: message
13589
+ }
13590
+ );
13591
+ throw new ReportInitializationError(
13592
+ "Authentication failed. Please re-authenticate and try again."
13593
+ );
13594
+ }
13595
+ logError("Error initializing security report", { error: message });
13569
13596
  throw new ReportInitializationError(
13570
13597
  `Error initializing security report: ${message}`
13571
13598
  );
@@ -13646,7 +13673,8 @@ var executeSecurityScan = async ({
13646
13673
  });
13647
13674
  },
13648
13675
  callbackStates: ["Finished" /* Finished */],
13649
- timeoutInMs: MCP_VUL_REPORT_DIGEST_TIMEOUT_MS
13676
+ timeoutInMs: MCP_VUL_REPORT_DIGEST_TIMEOUT_MS,
13677
+ scanContext
13650
13678
  });
13651
13679
  } catch (error) {
13652
13680
  logError(`[${scanContext}] Security analysis failed or timed out`, {
@@ -13680,6 +13708,8 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
13680
13708
  __publicField(this, "intervalId", null);
13681
13709
  __publicField(this, "isInitialScanComplete", false);
13682
13710
  __publicField(this, "gqlClient", null);
13711
+ __publicField(this, "fullScanPathsScanned", []);
13712
+ this.fullScanPathsScanned = configStore.get("fullScanPathsScanned") || [];
13683
13713
  }
13684
13714
  static getInstance() {
13685
13715
  if (!_CheckForNewAvailableFixesService.instance) {
@@ -13695,6 +13725,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
13695
13725
  this.filesLastScanned = {};
13696
13726
  this.freshFixes = [];
13697
13727
  this.reportedFixes = [];
13728
+ this.fullScanPathsScanned = configStore.get("fullScanPathsScanned") || [];
13698
13729
  if (this.intervalId) {
13699
13730
  clearInterval(this.intervalId);
13700
13731
  this.intervalId = null;
@@ -13715,61 +13746,84 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
13715
13746
  });
13716
13747
  if (!this.gqlClient) {
13717
13748
  logInfo(`[${scanContext}] No GQL client found, skipping scan`);
13718
- return;
13719
- }
13720
- const isConnected = await this.gqlClient.verifyApiConnection();
13721
- if (!isConnected) {
13722
- logError(`[${scanContext}] Failed to connect to the API, scan aborted`);
13723
- return;
13749
+ throw new Error("No GQL client found");
13724
13750
  }
13725
- logDebug(
13726
- `[${scanContext}] Connected to the API, assembling list of files to scan`,
13727
- { path: path13 }
13728
- );
13729
- const files = await getLocalFiles({
13730
- path: path13,
13731
- isAllFilesScan
13732
- });
13733
- logDebug(`[${scanContext}] Active files`, { files });
13734
- const filesToScan = files.filter((file) => {
13735
- const lastScannedEditTime = this.filesLastScanned[file.fullPath];
13736
- if (!lastScannedEditTime) {
13737
- return true;
13751
+ try {
13752
+ const isConnected = await this.gqlClient.verifyApiConnection();
13753
+ if (!isConnected) {
13754
+ logError(`[${scanContext}] Failed to connect to the API, scan aborted`);
13755
+ throw new ApiConnectionError();
13738
13756
  }
13739
- return file.lastEdited > lastScannedEditTime;
13740
- });
13741
- if (filesToScan.length === 0) {
13742
- logInfo(`[${scanContext}] No files require scanning`);
13743
- return;
13744
- }
13745
- logDebug(`[${scanContext}] Files requiring security scan`, { filesToScan });
13746
- const { fixReportId, projectId } = await scanFiles({
13747
- fileList: filesToScan.map((file) => file.relativePath),
13748
- repositoryPath: path13,
13749
- gqlClient: this.gqlClient,
13750
- isAllDetectionRulesScan,
13751
- scanContext
13752
- });
13753
- logInfo(
13754
- `[${scanContext}] Security scan completed for ${path13} reportId: ${fixReportId} projectId: ${projectId}`
13755
- );
13756
- if (isAllFilesScan) {
13757
- return;
13757
+ logDebug(
13758
+ `[${scanContext}] Connected to the API, assembling list of files to scan`,
13759
+ { path: path13 }
13760
+ );
13761
+ const files = await getLocalFiles({
13762
+ path: path13,
13763
+ isAllFilesScan
13764
+ });
13765
+ logDebug(`[${scanContext}] Active files`, { files });
13766
+ const filesToScan = files.filter((file) => {
13767
+ const lastScannedEditTime = this.filesLastScanned[file.fullPath];
13768
+ if (!lastScannedEditTime) {
13769
+ return true;
13770
+ }
13771
+ return file.lastEdited > lastScannedEditTime;
13772
+ });
13773
+ if (filesToScan.length === 0) {
13774
+ logInfo(`[${scanContext}] No files require scanning`);
13775
+ return;
13776
+ }
13777
+ logDebug(`[${scanContext}] Files requiring security scan`, {
13778
+ filesToScan
13779
+ });
13780
+ const { fixReportId, projectId } = await scanFiles({
13781
+ fileList: filesToScan.map((file) => file.relativePath),
13782
+ repositoryPath: path13,
13783
+ gqlClient: this.gqlClient,
13784
+ isAllDetectionRulesScan,
13785
+ scanContext
13786
+ });
13787
+ logInfo(
13788
+ `[${scanContext}] Security scan completed for ${path13} reportId: ${fixReportId} projectId: ${projectId}`
13789
+ );
13790
+ if (isAllFilesScan) {
13791
+ return;
13792
+ }
13793
+ const fixes = await this.gqlClient.getReportFixesPaginated({
13794
+ reportId: fixReportId,
13795
+ offset: 0,
13796
+ limit: 1e3
13797
+ });
13798
+ const newFixes = fixes?.fixes?.filter(
13799
+ (fix) => !this.isFixAlreadyReported(fix)
13800
+ );
13801
+ logInfo(
13802
+ `[${scanContext}] Security fixes retrieved, total: ${fixes?.fixes?.length || 0}, new: ${newFixes?.length || 0}`
13803
+ );
13804
+ this.updateFreshFixesCache(newFixes || [], filesToScan);
13805
+ this.updateFilesScanTimestamps(filesToScan);
13806
+ this.isInitialScanComplete = true;
13807
+ } catch (error) {
13808
+ const errorMessage = error.message;
13809
+ if (errorMessage.includes("Authentication failed") || errorMessage.includes("access-denied") || errorMessage.includes("Authentication hook unauthorized")) {
13810
+ logError(
13811
+ "Periodic scan skipped due to authentication failure. Please re-authenticate by running a manual scan.",
13812
+ {
13813
+ error: errorMessage
13814
+ }
13815
+ );
13816
+ return;
13817
+ }
13818
+ if (errorMessage.includes("ReportInitializationError")) {
13819
+ logError("Periodic scan failed during report initialization", {
13820
+ error: errorMessage
13821
+ });
13822
+ return;
13823
+ }
13824
+ logError("Unexpected error during periodic security scan", { error });
13825
+ throw error;
13758
13826
  }
13759
- const fixes = await this.gqlClient.getReportFixesPaginated({
13760
- reportId: fixReportId,
13761
- offset: 0,
13762
- limit: 1e3
13763
- });
13764
- const newFixes = fixes?.fixes?.filter(
13765
- (fix) => !this.isFixAlreadyReported(fix)
13766
- );
13767
- logInfo(
13768
- `[${scanContext}] Security fixes retrieved, total: ${fixes?.fixes?.length || 0}, new: ${newFixes?.length || 0}`
13769
- );
13770
- this.updateFreshFixesCache(newFixes || [], filesToScan);
13771
- this.updateFilesScanTimestamps(filesToScan);
13772
- this.isInitialScanComplete = true;
13773
13827
  }
13774
13828
  updateFreshFixesCache(newFixes, filesToScan) {
13775
13829
  this.freshFixes = this.freshFixes.filter((fix) => !this.isFixFromOldScan(fix, filesToScan)).concat(newFixes).sort((a, b) => {
@@ -13823,7 +13877,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
13823
13877
  if (!this.intervalId) {
13824
13878
  this.startPeriodicScanning(path13);
13825
13879
  this.executeInitialScan(path13);
13826
- this.executeInitialFullScan(path13);
13880
+ void this.executeInitialFullScan(path13);
13827
13881
  }
13828
13882
  }
13829
13883
  startPeriodicScanning(path13) {
@@ -13840,37 +13894,46 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
13840
13894
  });
13841
13895
  }, MCP_PERIODIC_CHECK_INTERVAL);
13842
13896
  }
13843
- executeInitialFullScan(path13) {
13844
- logDebug("Triggering initial full security scan", { path: path13 });
13845
- const mobbConfigStore2 = new Configstore4(packageJson.name, { apiToken: "" });
13846
- const fullScanPathsScanned = mobbConfigStore2.get("fullScanPathsScanned") || [];
13847
- logDebug("Full scan paths scanned", { fullScanPathsScanned });
13848
- if (fullScanPathsScanned.includes(path13)) {
13849
- logDebug("Full scan already executed for this path", { path: path13 });
13897
+ async executeInitialFullScan(path13) {
13898
+ const scanContext = "FULL_SCAN";
13899
+ logDebug(`[${scanContext}] Triggering initial full security scan`, { path: path13 });
13900
+ logDebug(`[${scanContext}] Full scan paths scanned`, {
13901
+ fullScanPathsScanned: this.fullScanPathsScanned
13902
+ });
13903
+ if (this.fullScanPathsScanned.includes(path13)) {
13904
+ logDebug(`[${scanContext}] Full scan already executed for this path`, {
13905
+ path: path13
13906
+ });
13850
13907
  return;
13851
13908
  }
13852
- mobbConfigStore2.set("fullScanPathsScanned", [...fullScanPathsScanned, path13]);
13853
- this.scanForSecurityVulnerabilities({
13854
- path: path13,
13855
- isAllFilesScan: true,
13856
- isAllDetectionRulesScan: true,
13857
- scanContext: "FULL_SCAN"
13858
- }).catch((error) => {
13909
+ configStore.set("fullScanPathsScanned", [
13910
+ ...this.fullScanPathsScanned,
13911
+ path13
13912
+ ]);
13913
+ try {
13914
+ await this.scanForSecurityVulnerabilities({
13915
+ path: path13,
13916
+ isAllFilesScan: true,
13917
+ isAllDetectionRulesScan: true,
13918
+ scanContext: "FULL_SCAN"
13919
+ });
13920
+ if (!this.fullScanPathsScanned.includes(path13)) {
13921
+ this.fullScanPathsScanned.push(path13);
13922
+ configStore.set("fullScanPathsScanned", this.fullScanPathsScanned);
13923
+ }
13924
+ logInfo(`[${scanContext}] Full scan completed`, { path: path13 });
13925
+ } catch (error) {
13859
13926
  logError("Error during initial full security scan", { error });
13860
- }).then(() => {
13861
- const fullScanPathsScanned2 = mobbConfigStore2.get("fullScanPathsScanned") || [];
13862
- fullScanPathsScanned2.push(path13);
13863
- mobbConfigStore2.set("fullScanPathsScanned", fullScanPathsScanned2);
13864
- logDebug("Full scan completed", { path: path13 });
13865
- });
13927
+ }
13866
13928
  }
13867
13929
  executeInitialScan(path13) {
13868
- logDebug("Triggering initial security scan", { path: path13 });
13930
+ const scanContext = "BACKGROUND_INITIAL";
13931
+ logDebug(`[${scanContext}] Triggering initial security scan`, { path: path13 });
13869
13932
  this.scanForSecurityVulnerabilities({
13870
13933
  path: path13,
13871
13934
  scanContext: "BACKGROUND_INITIAL"
13872
13935
  }).catch((error) => {
13873
- logError("Error during initial security scan", { error });
13936
+ logError(`[${scanContext}] Error during initial security scan`, { error });
13874
13937
  });
13875
13938
  }
13876
13939
  generateFreshFixesResponse() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobbdev",
3
- "version": "1.0.125",
3
+ "version": "1.0.126",
4
4
  "description": "Automated secure code remediation tool",
5
5
  "repository": "git+https://github.com/mobb-dev/bugsy.git",
6
6
  "main": "dist/index.js",
@@ -11,13 +11,14 @@
11
11
  "postinstall": "node ./src/post_install/cx_install.mjs",
12
12
  "build": "tsc && tsup-node --env.NODE_ENV production",
13
13
  "build:dev": "tsup-node --env.NODE_ENV development",
14
+ "increment-version": "./src/scripts/increment-version.sh",
14
15
  "test:mcp": "pnpm run build && vitest run __tests__/mcp/",
15
16
  "test:mcp:watch": "vitest watch __tests__/mcp/",
16
17
  "test:mcp:verbose": "pnpm run build && NODE_ENV=test VERBOSE=true vitest run __tests__/mcp/",
17
- "test:mcp:integration": "pnpm run build && GIT_PROXY_HOST=http://tinyproxy:8888 API_URL=http://app-api:8080/v1/graphql TOKEN=$(../../scripts/login_auth0.sh) vitest run __tests__/integration.test.ts -t 'mcp|MCP'",
18
+ "test:mcp:integration": "pnpm run build && GIT_PROXY_HOST=http://tinyproxy:8888 API_URL=http://app-api:8080/v1/graphql TOKEN=$(../../scripts/login_auth0.sh) vitest run --sequence.concurrent=false false __tests__/integration.test.ts -t 'mcp|MCP'",
18
19
  "test:mcp:all": "pnpm run test:mcp && pnpm run test:mcp:integration && cd ./__e2e__ && npm i && npm run test:mcp",
19
20
  "test:unit": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest run --exclude='**/__tests__/integration.test.ts' --exclude='**/__tests__/mcp/**'",
20
- "test:integration": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest run __tests__/integration.test.ts",
21
+ "test:integration": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest run --sequence.concurrent=false false __tests__/integration.test.ts",
21
22
  "test:integration:watch": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest watch run __tests__/integration.test.ts",
22
23
  "test": "pnpm run test:unit && pnpm run test:mcp && pnpm run test:integration",
23
24
  "test:ado": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest run ado.test",
@@ -27,7 +28,7 @@
27
28
  "test:cli:main": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest run cli-main.test",
28
29
  "test:coverage": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest run --coverage",
29
30
  "test:watch": "TOKEN=$(../../scripts/login_auth0.sh) vitest",
30
- "test:integration:proxy": "GIT_PROXY_HOST=http://tinyproxy:8888 HTTP_PROXY=http://localhost:8888 API_URL=http://app-api:8080/v1/graphql TOKEN=$(../../scripts/login_auth0.sh) vitest run integration.test.ts",
31
+ "test:integration:proxy": "GIT_PROXY_HOST=http://tinyproxy:8888 HTTP_PROXY=http://localhost:8888 API_URL=http://app-api:8080/v1/graphql TOKEN=$(../../scripts/login_auth0.sh) vitest run --sequence.concurrent=false false integration.test.ts",
31
32
  "lint": "eslint --cache --max-warnings 0 --ignore-path .eslintignore --ext .ts,.tsx,.jsx,.graphql .",
32
33
  "lint:fix": "eslint --fix --cache --max-warnings 0 --ignore-path .eslintignore --ext .js,.ts,.tsx,.jsx,.graphql . && prettier --write \"src/**/*.graphql\"",
33
34
  "lint:fix:files": "eslint --fix --cache --max-warnings 0 --ignore-path .eslintignore --ext .js,.ts,.tsx,.jsx,.graphql",