mobbdev 1.4.11 → 1.4.15

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
@@ -109,6 +109,9 @@ function getSdk(client, withWrapper = defaultWrapper) {
109
109
  autoPrAnalysis(variables, requestHeaders, signal) {
110
110
  return withWrapper((wrappedRequestHeaders) => client.request({ document: AutoPrAnalysisDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "autoPrAnalysis", "mutation", variables);
111
111
  },
112
+ getFixWithAnswers(variables, requestHeaders, signal) {
113
+ return withWrapper((wrappedRequestHeaders) => client.request({ document: GetFixWithAnswersDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getFixWithAnswers", "query", variables);
114
+ },
112
115
  GetFixReportsByRepoUrl(variables, requestHeaders, signal) {
113
116
  return withWrapper((wrappedRequestHeaders) => client.request({ document: GetFixReportsByRepoUrlDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetFixReportsByRepoUrl", "query", variables);
114
117
  },
@@ -138,7 +141,7 @@ function getSdk(client, withWrapper = defaultWrapper) {
138
141
  }
139
142
  };
140
143
  }
141
- 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, GetTracyRawDataUploadUrlDocument, DigestVulnerabilityReportDocument, SubmitVulnerabilityReportDocument, CreateCommunityUserDocument, CreateCliLoginDocument, PerformCliLoginDocument, SetQuarantineEnabledDocument, CreateProjectDocument, ValidateRepoUrlDocument, GitReferenceDocument, AutoPrAnalysisDocument, GetFixReportsByRepoUrlDocument, GetReportFixesDocument, GetLatestReportByRepoUrlDocument, UpdateDownloadedFixDataDocument, GetUserMvsAutoFixDocument, StreamBlameAiAnalysisRequestsDocument, StreamCommitBlameRequestsDocument, ScanSkillDocument, SkillVerdictsByMd5Document, defaultWrapper;
144
+ 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, GetTracyRawDataUploadUrlDocument, DigestVulnerabilityReportDocument, SubmitVulnerabilityReportDocument, CreateCommunityUserDocument, CreateCliLoginDocument, PerformCliLoginDocument, SetQuarantineEnabledDocument, CreateProjectDocument, ValidateRepoUrlDocument, GitReferenceDocument, AutoPrAnalysisDocument, GetFixWithAnswersDocument, GetFixReportsByRepoUrlDocument, GetReportFixesDocument, GetLatestReportByRepoUrlDocument, UpdateDownloadedFixDataDocument, GetUserMvsAutoFixDocument, StreamBlameAiAnalysisRequestsDocument, StreamCommitBlameRequestsDocument, ScanSkillDocument, SkillVerdictsByMd5Document, defaultWrapper;
142
145
  var init_client_generates = __esm({
143
146
  "src/features/analysis/scm/generates/client_generates.ts"() {
144
147
  "use strict";
@@ -312,6 +315,7 @@ var init_client_generates = __esm({
312
315
  IssueType_Enum2["NoReturnInFinally"] = "NO_RETURN_IN_FINALLY";
313
316
  IssueType_Enum2["NoVar"] = "NO_VAR";
314
317
  IssueType_Enum2["NullDereference"] = "NULL_DEREFERENCE";
318
+ IssueType_Enum2["OftenMisusedBooleanGetBoolean"] = "OFTEN_MISUSED_BOOLEAN_GET_BOOLEAN";
315
319
  IssueType_Enum2["OpenRedirect"] = "OPEN_REDIRECT";
316
320
  IssueType_Enum2["OverlyBroadCatch"] = "OVERLY_BROAD_CATCH";
317
321
  IssueType_Enum2["OverlyLargeRange"] = "OVERLY_LARGE_RANGE";
@@ -442,6 +446,7 @@ var init_client_generates = __esm({
442
446
  id
443
447
  confidence
444
448
  safeIssueType
449
+ safeIssueLanguage
445
450
  severityText
446
451
  gitBlameLogin
447
452
  severityValue
@@ -465,7 +470,17 @@ var init_client_generates = __esm({
465
470
  patch
466
471
  patchOriginalEncodingBase64
467
472
  questions {
473
+ key
468
474
  name
475
+ defaultValue
476
+ value
477
+ inputType
478
+ options
479
+ index
480
+ extraContext {
481
+ key
482
+ value
483
+ }
469
484
  }
470
485
  extraContext {
471
486
  extraContext {
@@ -1022,7 +1037,7 @@ var init_client_generates = __esm({
1022
1037
  }
1023
1038
  `;
1024
1039
  DigestVulnerabilityReportDocument = `
1025
- mutation DigestVulnerabilityReport($vulnerabilityReportFileName: String, $fixReportId: String!, $projectId: String!, $scanSource: String!, $repoUrl: String, $reference: String, $sha: String) {
1040
+ mutation DigestVulnerabilityReport($vulnerabilityReportFileName: String, $fixReportId: String!, $projectId: String!, $scanSource: String!, $repoUrl: String, $reference: String, $sha: String, $baselineCommit: String) {
1026
1041
  digestVulnerabilityReport(
1027
1042
  fixReportId: $fixReportId
1028
1043
  vulnerabilityReportFileName: $vulnerabilityReportFileName
@@ -1031,6 +1046,7 @@ var init_client_generates = __esm({
1031
1046
  repoUrl: $repoUrl
1032
1047
  reference: $reference
1033
1048
  sha: $sha
1049
+ baselineCommit: $baselineCommit
1034
1050
  ) {
1035
1051
  __typename
1036
1052
  ... on VulnerabilityReport {
@@ -1182,6 +1198,37 @@ var init_client_generates = __esm({
1182
1198
  error
1183
1199
  }
1184
1200
  }
1201
+ }
1202
+ `;
1203
+ GetFixWithAnswersDocument = `
1204
+ query getFixWithAnswers($fixId: uuid!, $userInput: [QuestionAnswer!]!) {
1205
+ fixData: getFix(fixId: $fixId, userInput: $userInput, loadAnswers: false) {
1206
+ __typename
1207
+ ... on FixData {
1208
+ patch
1209
+ patchOriginalEncodingBase64
1210
+ questions {
1211
+ key
1212
+ name
1213
+ defaultValue
1214
+ value
1215
+ inputType
1216
+ options
1217
+ index
1218
+ extraContext {
1219
+ key
1220
+ value
1221
+ }
1222
+ }
1223
+ extraContext {
1224
+ extraContext {
1225
+ key
1226
+ value
1227
+ }
1228
+ fixDescription
1229
+ }
1230
+ }
1231
+ }
1185
1232
  }
1186
1233
  `;
1187
1234
  GetFixReportsByRepoUrlDocument = `
@@ -1216,14 +1263,14 @@ var init_client_generates = __esm({
1216
1263
  GetLatestReportByRepoUrlDocument = `
1217
1264
  query GetLatestReportByRepoUrl($repoUrl: String!, $filters: fix_bool_exp = {}, $limit: Int!, $offset: Int!, $currentUserEmail: String!) {
1218
1265
  fixReport(
1219
- where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Finished}}, {vulnerabilityReport: {scanSource: {_neq: MCP}}}]}
1266
+ where: {_and: [{repo: {originalUrl: {_ilike: $repoUrl}}}, {state: {_eq: Finished}}, {vulnerabilityReport: {scanSource: {_neq: MCP}}}]}
1220
1267
  order_by: {createdOn: desc}
1221
1268
  limit: 1
1222
1269
  ) {
1223
1270
  ...FixReportSummaryFields
1224
1271
  }
1225
1272
  expiredReport: fixReport(
1226
- where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Expired}}, {vulnerabilityReport: {scanSource: {_neq: MCP}}}]}
1273
+ where: {_and: [{repo: {originalUrl: {_ilike: $repoUrl}}}, {state: {_eq: Expired}}, {vulnerabilityReport: {scanSource: {_neq: MCP}}}]}
1227
1274
  order_by: {createdOn: desc}
1228
1275
  limit: 1
1229
1276
  ) {
@@ -1318,8 +1365,8 @@ var init_client_generates = __esm({
1318
1365
 
1319
1366
  // src/features/analysis/scm/shared/src/getIssueType.ts
1320
1367
  import { z } from "zod";
1321
- function getTagTooltip(tag) {
1322
- switch (tag) {
1368
+ function getTagTooltip(tag2) {
1369
+ switch (tag2) {
1323
1370
  case "FALSE_POSITIVE":
1324
1371
  return "Issue was found to be a false positive";
1325
1372
  case "TEST_CODE":
@@ -1335,7 +1382,7 @@ function getTagTooltip(tag) {
1335
1382
  case "SUPPRESSED":
1336
1383
  return "Suppressed in the scan report";
1337
1384
  default:
1338
- return tag;
1385
+ return tag2;
1339
1386
  }
1340
1387
  }
1341
1388
  function replaceKeysWithValues(fixDescription, extraContext) {
@@ -1502,7 +1549,8 @@ var init_getIssueType = __esm({
1502
1549
  ["MISSING_X_FRAME_OPTIONS" /* MissingXFrameOptions */]: "Missing X-Frame-Options Header",
1503
1550
  ["IMPROPER_VALIDATION_OF_ARRAY_INDEX" /* ImproperValidationOfArrayIndex */]: "Improper Validation of Array Index",
1504
1551
  ["INCORRECT_INTEGER_CONVERSION" /* IncorrectIntegerConversion */]: "Incorrect Integer Conversion",
1505
- ["IMPROPER_CERTIFICATE_VALIDATION" /* ImproperCertificateValidation */]: "Improper Certificate Validation"
1552
+ ["IMPROPER_CERTIFICATE_VALIDATION" /* ImproperCertificateValidation */]: "Improper Certificate Validation",
1553
+ ["OFTEN_MISUSED_BOOLEAN_GET_BOOLEAN" /* OftenMisusedBooleanGetBoolean */]: "Often Misused: Boolean.getBoolean()"
1506
1554
  };
1507
1555
  issueTypeZ = z.nativeEnum(IssueType_Enum);
1508
1556
  getIssueTypeFriendlyString = (issueType) => {
@@ -1752,7 +1800,7 @@ var init_analysis = __esm({
1752
1800
 
1753
1801
  // src/features/analysis/scm/shared/src/types/issue.ts
1754
1802
  import { z as z9 } from "zod";
1755
- var MAX_SOURCE_CODE_FILE_SIZE_IN_BYTES, VulnerabilityReportIssueRatingZ, VulnerabilityReportIssueSharedStateZ, BaseIssuePartsZ, FalsePositivePartsZ, IssuePartsWithFixZ, IssuePartsFpZ, GeneralIssueZ, IssuePartsZ, GetIssueIndexesZ, GetIssueScreenDataZ, IssueBucketZ, mapCategoryToBucket, mapBucketTypeToCategory;
1803
+ var MAX_SOURCE_CODE_FILE_SIZE_IN_BYTES, VulnerabilityReportIssueRatingZ, VulnerabilityReportIssueSharedStateZ, BaseIssuePartsZ, FalsePositivePartsZ, UnfixablePartsZ, IssuePartsWithFixZ, IssuePartsFpZ, GeneralIssueZ, IssuePartsZ, GetIssueIndexesZ, GetIssueScreenDataZ, IssueBucketZ, mapCategoryToBucket, mapBucketTypeToCategory;
1756
1804
  var init_issue = __esm({
1757
1805
  "src/features/analysis/scm/shared/src/types/issue.ts"() {
1758
1806
  "use strict";
@@ -1834,12 +1882,17 @@ var init_issue = __esm({
1834
1882
  return { codeDiff };
1835
1883
  })
1836
1884
  }).nullish(),
1837
- sharedState: VulnerabilityReportIssueSharedStateZ
1885
+ sharedState: VulnerabilityReportIssueSharedStateZ,
1886
+ unfixableId: z9.string().uuid().nullish()
1838
1887
  });
1839
1888
  FalsePositivePartsZ = z9.object({
1840
1889
  extraContext: z9.array(z9.object({ key: z9.string(), value: z9.string() })),
1841
1890
  fixDescription: z9.string()
1842
1891
  });
1892
+ UnfixablePartsZ = z9.object({
1893
+ extraContext: z9.array(z9.object({ key: z9.string(), value: z9.string() })),
1894
+ fixDescription: z9.string()
1895
+ });
1843
1896
  IssuePartsWithFixZ = BaseIssuePartsZ.merge(
1844
1897
  z9.object({
1845
1898
  category: z9.literal("Irrelevant" /* Irrelevant */),
@@ -1861,7 +1914,8 @@ var init_issue = __esm({
1861
1914
  z9.literal("Fixable" /* Fixable */),
1862
1915
  z9.literal("Filtered" /* Filtered */),
1863
1916
  z9.literal("Pending" /* Pending */)
1864
- ])
1917
+ ]),
1918
+ getUnfixable: UnfixablePartsZ.nullish()
1865
1919
  })
1866
1920
  );
1867
1921
  IssuePartsZ = z9.union([
@@ -4753,7 +4807,8 @@ var fixDetailsData = {
4753
4807
  ["MISSING_X_FRAME_OPTIONS" /* MissingXFrameOptions */]: void 0,
4754
4808
  ["IMPROPER_VALIDATION_OF_ARRAY_INDEX" /* ImproperValidationOfArrayIndex */]: void 0,
4755
4809
  ["INCORRECT_INTEGER_CONVERSION" /* IncorrectIntegerConversion */]: void 0,
4756
- ["IMPROPER_CERTIFICATE_VALIDATION" /* ImproperCertificateValidation */]: void 0
4810
+ ["IMPROPER_CERTIFICATE_VALIDATION" /* ImproperCertificateValidation */]: void 0,
4811
+ ["OFTEN_MISUSED_BOOLEAN_GET_BOOLEAN" /* OftenMisusedBooleanGetBoolean */]: void 0
4757
4812
  };
4758
4813
 
4759
4814
  // src/features/analysis/scm/shared/src/commitDescriptionMarkup.ts
@@ -4823,7 +4878,8 @@ var getCommitIssueDescription = ({
4823
4878
  vendor,
4824
4879
  issueType,
4825
4880
  irrelevantIssueWithTags,
4826
- fpDescription
4881
+ fpDescription,
4882
+ unfixableDescription
4827
4883
  }) => {
4828
4884
  const issueTypeString = getIssueTypeFriendlyString(issueType);
4829
4885
  let description = `The following issues reported by ${capitalizeFirstLetter(vendor)} on this PR were found to be irrelevant to your project:
@@ -4839,7 +4895,7 @@ var getCommitIssueDescription = ({
4839
4895
 
4840
4896
 
4841
4897
  ## Justification
4842
- ${fpDescription ?? issueDescription[irrelevantIssueWithTags[0].tag]}
4898
+ ${fpDescription ?? unfixableDescription ?? issueDescription[irrelevantIssueWithTags[0].tag]}
4843
4899
  `;
4844
4900
  }
4845
4901
  const staticData = fixDetailsData[parseIssueTypeRes.data];
@@ -6861,25 +6917,18 @@ function getScmConfig({
6861
6917
  });
6862
6918
  const virtualDomain = filteredBrokerHosts[0]?.virtualDomain;
6863
6919
  const virtualUrl = virtualDomain ? `https://${virtualDomain}${urlObject.pathname}${urlObject.search}` : void 0;
6864
- const scmOrgConfig = filteredScmConfigs.find((scm) => scm.orgId && scm.token);
6865
- if (scmOrgConfig && includeOrgTokens) {
6866
- return {
6867
- id: scmOrgConfig.id,
6868
- accessToken: scmOrgConfig.token || void 0,
6869
- scmLibType: getScmLibTypeFromScmType(scmOrgConfig.scmType),
6870
- scmOrg: scmOrgConfig.scmOrg || void 0,
6871
- virtualUrl
6872
- };
6873
- }
6874
- const scmUserConfig = filteredScmConfigs.find(
6875
- (scm) => scm.userId && scm.token
6876
- );
6877
- if (scmUserConfig) {
6920
+ const matched = filteredScmConfigs.find((scm) => {
6921
+ if (!scm.token) return false;
6922
+ if (scm.userId) return true;
6923
+ if (scm.orgId) return includeOrgTokens;
6924
+ return false;
6925
+ });
6926
+ if (matched) {
6878
6927
  return {
6879
- id: scmUserConfig.id,
6880
- accessToken: scmUserConfig.token || void 0,
6881
- scmLibType: getScmLibTypeFromScmType(scmUserConfig.scmType),
6882
- scmOrg: scmUserConfig.scmOrg || void 0,
6928
+ id: matched.id,
6929
+ accessToken: matched.token || void 0,
6930
+ scmLibType: getScmLibTypeFromScmType(matched.scmType),
6931
+ scmOrg: matched.scmOrg || void 0,
6883
6932
  virtualUrl
6884
6933
  };
6885
6934
  }
@@ -9583,12 +9632,12 @@ function getGithubSdk(params = {}) {
9583
9632
  });
9584
9633
  },
9585
9634
  async getTagDate({
9586
- tag,
9635
+ tag: tag2,
9587
9636
  owner,
9588
9637
  repo
9589
9638
  }) {
9590
9639
  const refResponse = await octokit.rest.git.getRef({
9591
- ref: `tags/${tag}`,
9640
+ ref: `tags/${tag2}`,
9592
9641
  owner,
9593
9642
  repo
9594
9643
  });
@@ -10607,7 +10656,11 @@ var GithubSCMLib = class extends SCMLib {
10607
10656
  }
10608
10657
  const aDate = a.repoUpdatedAt ? Date.parse(a.repoUpdatedAt) : 0;
10609
10658
  const bDate = b.repoUpdatedAt ? Date.parse(b.repoUpdatedAt) : 0;
10610
- return (aDate - bDate) * sortOrder;
10659
+ const dateCmp = (aDate - bDate) * sortOrder;
10660
+ if (dateCmp !== 0) {
10661
+ return dateCmp;
10662
+ }
10663
+ return a.repoName.localeCompare(b.repoName) * sortOrder;
10611
10664
  });
10612
10665
  const limit = params.limit || 10;
10613
10666
  const offset = parseCursorSafe(params.cursor, 0);
@@ -12220,22 +12273,24 @@ if (!semver.satisfies(process.version, packageJson.engines.node)) {
12220
12273
 
12221
12274
  // src/utils/gitUtils.ts
12222
12275
  import simpleGit2 from "simple-git";
12223
- var defaultLogger = {
12224
- info: (data, msg) => {
12225
- if (msg) {
12226
- const sanitizedMsg = String(msg).replace(/\n|\r/g, "");
12227
- console.log(`[GIT] ${sanitizedMsg}`, data);
12228
- } else {
12229
- console.log("[GIT]", data);
12230
- }
12276
+ var tag = (sink) => (data, msg) => {
12277
+ if (msg) {
12278
+ const sanitizedMsg = String(msg).replace(/\n|\r/g, "");
12279
+ sink(`[GIT] ${sanitizedMsg}`, data);
12280
+ } else {
12281
+ sink("[GIT]", data);
12231
12282
  }
12232
12283
  };
12284
+ var defaultLogger = {
12285
+ debug: tag(console.log),
12286
+ warn: tag(console.warn)
12287
+ };
12233
12288
  function createGitWithLogging(dirName, logger3 = defaultLogger) {
12234
12289
  return simpleGit2(dirName, {
12235
12290
  maxConcurrentProcesses: 6
12236
12291
  }).outputHandler((bin, stdout2, stderr2) => {
12237
12292
  const callID = Math.random();
12238
- logger3.info({ callID, bin }, "Start git CLI call");
12293
+ logger3.debug({ callID, bin }, "Start git CLI call");
12239
12294
  let errChunks = [];
12240
12295
  let outChunks = [];
12241
12296
  let isStdoutDone = false;
@@ -12256,7 +12311,7 @@ function createGitWithLogging(dirName, logger3 = defaultLogger) {
12256
12311
  err: `${errChunks.join("").slice(0, 200)}...`,
12257
12312
  out: `${outChunks.join("").slice(0, 200)}...`
12258
12313
  };
12259
- logger3.info(logObj, "git log output");
12314
+ logger3.debug(logObj, "git log output");
12260
12315
  stderr2.removeListener("data", onStderrData);
12261
12316
  stdout2.removeListener("data", onStdoutData);
12262
12317
  errChunks = [];
@@ -12270,11 +12325,11 @@ function createGitWithLogging(dirName, logger3 = defaultLogger) {
12270
12325
  stderr2.on("close", () => markDone("stderr"));
12271
12326
  stdout2.on("close", () => markDone("stdout"));
12272
12327
  stderr2.on("error", (error) => {
12273
- logger3.info({ callID, error: String(error) }, "git stderr stream error");
12328
+ logger3.warn({ callID, error: String(error) }, "git stderr stream error");
12274
12329
  markDone("stderr");
12275
12330
  });
12276
12331
  stdout2.on("error", (error) => {
12277
- logger3.info({ callID, error: String(error) }, "git stdout stream error");
12332
+ logger3.warn({ callID, error: String(error) }, "git stdout stream error");
12278
12333
  markDone("stdout");
12279
12334
  });
12280
12335
  });
@@ -12292,15 +12347,15 @@ import sax from "sax";
12292
12347
  var BaseStreamParser = class {
12293
12348
  constructor(parser) {
12294
12349
  __publicField(this, "currentPath", []);
12295
- parser.on("opentag", (tag) => this.onOpenTag(tag));
12350
+ parser.on("opentag", (tag2) => this.onOpenTag(tag2));
12296
12351
  parser.on("closetag", () => this.onCloseTag());
12297
12352
  parser.on("text", (text) => this.onText(text));
12298
12353
  }
12299
12354
  getPathString() {
12300
12355
  return this.currentPath.join(" > ");
12301
12356
  }
12302
- onOpenTag(tag) {
12303
- this.currentPath.push(tag.name);
12357
+ onOpenTag(tag2) {
12358
+ this.currentPath.push(tag2.name);
12304
12359
  }
12305
12360
  onCloseTag() {
12306
12361
  this.currentPath.pop();
@@ -12313,12 +12368,12 @@ var AuditMetadataParser = class extends BaseStreamParser {
12313
12368
  super(...arguments);
12314
12369
  __publicField(this, "suppressedMap", {});
12315
12370
  }
12316
- onOpenTag(tag) {
12317
- super.onOpenTag(tag);
12371
+ onOpenTag(tag2) {
12372
+ super.onOpenTag(tag2);
12318
12373
  switch (this.getPathString()) {
12319
12374
  case "Audit > IssueList > Issue":
12320
- this.suppressedMap[String(tag.attributes["instanceId"] ?? "")] = String(
12321
- tag.attributes["suppressed"] ?? ""
12375
+ this.suppressedMap[String(tag2.attributes["instanceId"] ?? "")] = String(
12376
+ tag2.attributes["suppressed"] ?? ""
12322
12377
  );
12323
12378
  break;
12324
12379
  }
@@ -12337,18 +12392,18 @@ var ReportMetadataParser = class extends BaseStreamParser {
12337
12392
  __publicField(this, "ruleId", "");
12338
12393
  __publicField(this, "groupName", "");
12339
12394
  }
12340
- onOpenTag(tag) {
12341
- super.onOpenTag(tag);
12395
+ onOpenTag(tag2) {
12396
+ super.onOpenTag(tag2);
12342
12397
  switch (this.getPathString()) {
12343
12398
  case "FVDL > EngineData > RuleInfo > Rule":
12344
- this.ruleId = String(tag.attributes["id"] ?? "");
12399
+ this.ruleId = String(tag2.attributes["id"] ?? "");
12345
12400
  break;
12346
12401
  case "FVDL > EngineData > RuleInfo > Rule > MetaInfo > Group":
12347
- this.groupName = String(tag.attributes["name"] ?? "");
12402
+ this.groupName = String(tag2.attributes["name"] ?? "");
12348
12403
  break;
12349
12404
  case "FVDL > CreatedTS":
12350
- this.createdTSDate = String(tag.attributes["date"] ?? "");
12351
- this.createdTSTime = String(tag.attributes["time"] ?? "");
12405
+ this.createdTSDate = String(tag2.attributes["date"] ?? "");
12406
+ this.createdTSTime = String(tag2.attributes["time"] ?? "");
12352
12407
  break;
12353
12408
  }
12354
12409
  }
@@ -12381,19 +12436,19 @@ var UnifiedNodePoolParser = class extends BaseStreamParser {
12381
12436
  __publicField(this, "codePoints", {});
12382
12437
  __publicField(this, "nodeId", "");
12383
12438
  }
12384
- onOpenTag(tag) {
12385
- super.onOpenTag(tag);
12439
+ onOpenTag(tag2) {
12440
+ super.onOpenTag(tag2);
12386
12441
  switch (this.getPathString()) {
12387
12442
  case "FVDL > UnifiedNodePool > Node":
12388
- this.nodeId = String(tag.attributes["id"] ?? "");
12443
+ this.nodeId = String(tag2.attributes["id"] ?? "");
12389
12444
  break;
12390
12445
  case "FVDL > UnifiedNodePool > Node > SourceLocation":
12391
12446
  this.codePoints[this.nodeId] = {
12392
- path: String(tag.attributes["path"] ?? ""),
12393
- colStart: String(tag.attributes["colStart"] ?? ""),
12394
- colEnd: String(tag.attributes["colEnd"] ?? ""),
12395
- line: String(tag.attributes["line"] ?? ""),
12396
- lineEnd: String(tag.attributes["lineEnd"] ?? "")
12447
+ path: String(tag2.attributes["path"] ?? ""),
12448
+ colStart: String(tag2.attributes["colStart"] ?? ""),
12449
+ colEnd: String(tag2.attributes["colEnd"] ?? ""),
12450
+ line: String(tag2.attributes["line"] ?? ""),
12451
+ lineEnd: String(tag2.attributes["lineEnd"] ?? "")
12397
12452
  };
12398
12453
  break;
12399
12454
  }
@@ -12415,8 +12470,8 @@ var VulnerabilityParser = class extends BaseStreamParser {
12415
12470
  this.tmpStorageFilePath = tmpStorageFilePath;
12416
12471
  this.tmpStorageFileWriter = fs5.createWriteStream(tmpStorageFilePath);
12417
12472
  }
12418
- onOpenTag(tag) {
12419
- super.onOpenTag(tag);
12473
+ onOpenTag(tag2) {
12474
+ super.onOpenTag(tag2);
12420
12475
  switch (this.getPathString()) {
12421
12476
  case "FVDL > Vulnerabilities > Vulnerability":
12422
12477
  this.isInVulnerability = true;
@@ -12425,21 +12480,21 @@ var VulnerabilityParser = class extends BaseStreamParser {
12425
12480
  this.codePoints = [];
12426
12481
  break;
12427
12482
  case "FVDL > Vulnerabilities > Vulnerability > InstanceInfo > MetaInfo > Group":
12428
- this.groupName = String(tag.attributes["name"] ?? "");
12483
+ this.groupName = String(tag2.attributes["name"] ?? "");
12429
12484
  break;
12430
12485
  }
12431
12486
  if (this.isInVulnerability) {
12432
12487
  if (this.getPathString().endsWith(" > Entry > Node > SourceLocation")) {
12433
12488
  this.codePoints.push({
12434
- path: String(tag.attributes["path"] ?? ""),
12435
- colStart: String(tag.attributes["colStart"] ?? ""),
12436
- colEnd: String(tag.attributes["colEnd"] ?? ""),
12437
- line: String(tag.attributes["line"] ?? ""),
12438
- lineEnd: String(tag.attributes["lineEnd"] ?? "")
12489
+ path: String(tag2.attributes["path"] ?? ""),
12490
+ colStart: String(tag2.attributes["colStart"] ?? ""),
12491
+ colEnd: String(tag2.attributes["colEnd"] ?? ""),
12492
+ line: String(tag2.attributes["line"] ?? ""),
12493
+ lineEnd: String(tag2.attributes["lineEnd"] ?? "")
12439
12494
  });
12440
12495
  } else if (this.getPathString().endsWith(" > Entry > NodeRef")) {
12441
12496
  this.codePoints.push({
12442
- id: String(tag.attributes["id"] ?? "")
12497
+ id: String(tag2.attributes["id"] ?? "")
12443
12498
  });
12444
12499
  }
12445
12500
  }
@@ -12916,6 +12971,12 @@ var convertToSarifCodePathPatternsOption = {
12916
12971
  type: "string",
12917
12972
  array: true
12918
12973
  };
12974
+ var baselineCommitOption = {
12975
+ describe: chalk2.bold(
12976
+ "Only report findings introduced since this commit (PR mode). The sha must be reachable from the scanned repository \u2014 unreachable baselines fail the scan loudly. Effective only when no scan file is provided."
12977
+ ),
12978
+ type: "string"
12979
+ };
12919
12980
  var pollingOption = {
12920
12981
  describe: chalk2.bold(
12921
12982
  "Use HTTP polling instead of WebSocket for status updates. Useful for proxy environments or firewalls that block WebSocket connections. Polling interval: 5 seconds, timeout: 30 minutes."
@@ -13567,7 +13628,8 @@ var GQLClient = class {
13567
13628
  repoUrl,
13568
13629
  reference,
13569
13630
  sha,
13570
- shouldScan
13631
+ shouldScan,
13632
+ baselineCommit
13571
13633
  }) {
13572
13634
  const res = await this._clientSdk.DigestVulnerabilityReport({
13573
13635
  fixReportId,
@@ -13576,7 +13638,8 @@ var GQLClient = class {
13576
13638
  scanSource,
13577
13639
  repoUrl,
13578
13640
  reference,
13579
- sha
13641
+ sha,
13642
+ baselineCommit
13580
13643
  });
13581
13644
  if (res.digestVulnerabilityReport.__typename !== "VulnerabilityReport") {
13582
13645
  throw new Error("Digesting vulnerability report failed");
@@ -14106,8 +14169,16 @@ var ADO_PAT_PATTERN = {
14106
14169
  severity: "high",
14107
14170
  validator: (match) => match.length >= 52 && match.length <= 100
14108
14171
  };
14172
+ var DATADOG_APP_KEY_PATTERN = {
14173
+ type: "DATADOG_APP_KEY",
14174
+ regex: /\bddapp_[a-zA-Z0-9]{30,}\b/g,
14175
+ priority: 95,
14176
+ placeholder: "[DATADOG_APP_KEY_{n}]",
14177
+ description: "Datadog Application Key",
14178
+ severity: "high"
14179
+ };
14109
14180
  var openRedaction = new OpenRedaction({
14110
- customPatterns: [ADO_PAT_PATTERN],
14181
+ customPatterns: [ADO_PAT_PATTERN, DATADOG_APP_KEY_PATTERN],
14111
14182
  patterns: [
14112
14183
  // Core Personal Data
14113
14184
  // Removed EMAIL - causes false positives in code/test snippets (e.g. --author="Eve Author <eve@example.com>")
@@ -15982,10 +16053,10 @@ function createFork({ args, processPath, name }, options) {
15982
16053
  });
15983
16054
  return createChildProcess({ childProcess: child, name }, options);
15984
16055
  }
15985
- function createSpawn({ args, processPath, name, cwd }, options) {
16056
+ function createSpawn({ args, processPath, name, cwd, env: env3 }, options) {
15986
16057
  const child = cp.spawn(processPath, args, {
15987
16058
  stdio: ["inherit", "pipe", "pipe", "ipc"],
15988
- env: { ...process2.env },
16059
+ env: { ...process2.env, ...env3 },
15989
16060
  cwd
15990
16061
  });
15991
16062
  return createChildProcess({ childProcess: child, name }, options);
@@ -16440,7 +16511,8 @@ async function _scan(params, { skipPrompts = false } = {}) {
16440
16511
  createOnePr,
16441
16512
  commitDirectly,
16442
16513
  pullRequest,
16443
- polling
16514
+ polling,
16515
+ baselineCommit
16444
16516
  } = params;
16445
16517
  debug21("start %s %s", dirname, repo);
16446
16518
  const { createSpinner: createSpinner5 } = Spinner2({ ci });
@@ -16557,7 +16629,9 @@ async function _scan(params, { skipPrompts = false } = {}) {
16557
16629
  sha,
16558
16630
  reference,
16559
16631
  shouldScan,
16560
- polling
16632
+ polling,
16633
+ // Only meaningful when opengrep is going to run (no user-supplied report).
16634
+ baselineCommit: shouldScan ? baselineCommit : void 0
16561
16635
  });
16562
16636
  uploadReportSpinner.success({ text: "\u{1F4C1} Report uploaded successfully" });
16563
16637
  const mobbSpinner = createSpinner5("\u{1F575}\uFE0F\u200D\u2642\uFE0F Initiating Mobb analysis").start();
@@ -16700,7 +16774,11 @@ async function _scan(params, { skipPrompts = false } = {}) {
16700
16774
  command,
16701
16775
  ci,
16702
16776
  shouldScan: shouldScan2,
16703
- polling
16777
+ polling,
16778
+ // shouldScan is false here (user provided a report); baseline filter
16779
+ // only applies to the opengrep code path. Drop it to keep the contract
16780
+ // honest with the CLI help text.
16781
+ baselineCommit: void 0
16704
16782
  });
16705
16783
  const res = await _zipAndUploadRepo({
16706
16784
  srcPath,
@@ -16724,7 +16802,8 @@ async function _scan(params, { skipPrompts = false } = {}) {
16724
16802
  command,
16725
16803
  ci,
16726
16804
  shouldScan: shouldScan2,
16727
- polling
16805
+ polling,
16806
+ baselineCommit
16728
16807
  });
16729
16808
  }
16730
16809
  const mobbSpinner2 = createSpinner5("\u{1F575}\uFE0F\u200D\u2642\uFE0F Initiating Mobb analysis").start();
@@ -16819,7 +16898,8 @@ async function _digestReport({
16819
16898
  sha,
16820
16899
  reference,
16821
16900
  shouldScan,
16822
- polling
16901
+ polling,
16902
+ baselineCommit
16823
16903
  }) {
16824
16904
  const digestSpinner = createSpinner4(
16825
16905
  progressMassages.processingVulnerabilityReport
@@ -16833,7 +16913,8 @@ async function _digestReport({
16833
16913
  repoUrl,
16834
16914
  sha,
16835
16915
  reference,
16836
- shouldScan
16916
+ shouldScan,
16917
+ baselineCommit
16837
16918
  }
16838
16919
  );
16839
16920
  const callbackStates = [
@@ -17083,7 +17164,8 @@ async function analyze({
17083
17164
  createOnePr,
17084
17165
  commitDirectly,
17085
17166
  pullRequest,
17086
- polling
17167
+ polling,
17168
+ baselineCommit
17087
17169
  }, { skipPrompts = false } = {}) {
17088
17170
  !ci && await showWelcomeMessage(skipPrompts);
17089
17171
  await runAnalysis(
@@ -17102,7 +17184,8 @@ async function analyze({
17102
17184
  commitDirectly,
17103
17185
  pullRequest,
17104
17186
  createOnePr,
17105
- polling
17187
+ polling,
17188
+ baselineCommit
17106
17189
  },
17107
17190
  { skipPrompts }
17108
17191
  );
@@ -17312,9 +17395,12 @@ function analyzeBuilder(yargs2) {
17312
17395
  describe: chalk10.bold("Number of the pull request"),
17313
17396
  type: "number",
17314
17397
  demandOption: false
17315
- }).option("polling", pollingOption).example(
17398
+ }).option("polling", pollingOption).option("baseline-commit", baselineCommitOption).example(
17316
17399
  "npx mobbdev@latest analyze -r https://github.com/WebGoat/WebGoat -f <your_vulnerability_report_path>",
17317
17400
  "analyze an existing repository"
17401
+ ).example(
17402
+ "npx mobbdev@latest analyze -r https://github.com/org/repo --baseline-commit <sha>",
17403
+ "analyze only findings introduced since <sha> (PR-mode scan)"
17318
17404
  ).help();
17319
17405
  }
17320
17406
  function validateAnalyzeOptions(argv) {
@@ -19313,7 +19399,7 @@ function createLogger(config2) {
19313
19399
 
19314
19400
  // src/features/claude_code/hook_logger.ts
19315
19401
  var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
19316
- var CLI_VERSION = true ? "1.4.11" : "unknown";
19402
+ var CLI_VERSION = true ? "1.4.15" : "unknown";
19317
19403
  var NAMESPACE = "mobbdev-claude-code-hook-logs";
19318
19404
  var claudeCodeVersion;
19319
19405
  function buildDdTags() {
@@ -20801,7 +20887,14 @@ var FixExtraContextResponseSchema = z33.object({
20801
20887
  });
20802
20888
  var FixQuestionSchema = z33.object({
20803
20889
  __typename: z33.literal("FixQuestion").optional(),
20804
- name: z33.string()
20890
+ key: z33.string(),
20891
+ name: z33.string(),
20892
+ defaultValue: z33.string(),
20893
+ value: z33.string().nullable().optional(),
20894
+ inputType: z33.nativeEnum(FixQuestionInputType),
20895
+ options: z33.array(z33.string()),
20896
+ index: z33.number(),
20897
+ extraContext: z33.array(UnstructuredFixExtraContextSchema)
20805
20898
  });
20806
20899
  var FixDataSchema = z33.object({
20807
20900
  __typename: z33.literal("FixData"),
@@ -20820,6 +20913,7 @@ var McpFixSchema = z33.object({
20820
20913
  // GraphQL uses `any` type for UUID
20821
20914
  confidence: z33.number(),
20822
20915
  safeIssueType: z33.string().nullable(),
20916
+ safeIssueLanguage: z33.string().nullable().optional(),
20823
20917
  severityText: z33.string().nullable(),
20824
20918
  gitBlameLogin: z33.string().nullable().optional(),
20825
20919
  // Optional in GraphQL
@@ -20900,10 +20994,6 @@ var GetLatestReportByRepoUrlResponseSchema = z33.object({
20900
20994
  });
20901
20995
 
20902
20996
  // src/mcp/services/InteractiveFixFilter.ts
20903
- var isFilterDisabled = () => {
20904
- const raw = process.env["MOBB_MCP_DISABLE_INTERACTIVE_FILTER"];
20905
- return raw === "1" || raw === "true";
20906
- };
20907
20997
  var isInteractiveFix = (fix) => {
20908
20998
  if (fix.patchAndQuestions.__typename !== "FixData") {
20909
20999
  return false;
@@ -20918,27 +21008,46 @@ var countByRule = (ruleIds) => {
20918
21008
  }
20919
21009
  return counts;
20920
21010
  };
21011
+ var MOBB_MCP_DISABLE_INTERACTIVE_FILTER_DEFAULT = false;
21012
+ var isInteractiveRoutingDisabled = () => {
21013
+ const raw = process.env["MOBB_MCP_DISABLE_INTERACTIVE_FILTER"];
21014
+ if (!raw) return MOBB_MCP_DISABLE_INTERACTIVE_FILTER_DEFAULT;
21015
+ const normalized = raw.toLowerCase();
21016
+ return normalized === "1" || normalized === "true";
21017
+ };
20921
21018
  var partitionInteractiveFixes = (fixes) => {
20922
- if (isFilterDisabled()) {
20923
- return { applicableFixes: fixes, skippedRuleIds: [] };
20924
- }
21019
+ const disabled = isInteractiveRoutingDisabled();
20925
21020
  const applicableFixes = [];
20926
- const skippedRuleIds = [];
21021
+ const interactiveFixes = [];
21022
+ const droppedInteractive = [];
20927
21023
  for (const fix of fixes) {
20928
21024
  if (isInteractiveFix(fix)) {
20929
- skippedRuleIds.push(ruleIdFor(fix));
21025
+ if (disabled) {
21026
+ droppedInteractive.push(fix);
21027
+ } else {
21028
+ interactiveFixes.push(fix);
21029
+ }
20930
21030
  } else {
20931
21031
  applicableFixes.push(fix);
20932
21032
  }
20933
21033
  }
20934
- if (skippedRuleIds.length > 0) {
20935
- logInfo("[InteractiveFixFilter] Skipped interactive fixes", {
21034
+ if (disabled && droppedInteractive.length > 0) {
21035
+ logInfo(
21036
+ "[InteractiveFixFilter] Dropping interactive fixes (MOBB_MCP_DISABLE_INTERACTIVE_FILTER=true)",
21037
+ {
21038
+ totalFixes: fixes.length,
21039
+ droppedCount: droppedInteractive.length,
21040
+ droppedByRule: countByRule(droppedInteractive.map(ruleIdFor))
21041
+ }
21042
+ );
21043
+ } else if (interactiveFixes.length > 0) {
21044
+ logInfo("[InteractiveFixFilter] Routing interactive fixes to LLM", {
20936
21045
  totalFixes: fixes.length,
20937
- skippedCount: skippedRuleIds.length,
20938
- skippedByRule: countByRule(skippedRuleIds)
21046
+ interactiveCount: interactiveFixes.length,
21047
+ interactiveByRule: countByRule(interactiveFixes.map(ruleIdFor))
20939
21048
  });
20940
21049
  }
20941
- return { applicableFixes, skippedRuleIds };
21050
+ return { applicableFixes, interactiveFixes };
20942
21051
  };
20943
21052
 
20944
21053
  // src/mcp/services/McpGQLClient.ts
@@ -21223,7 +21332,7 @@ var McpGQLClient = class extends GQLClient {
21223
21332
  reportData,
21224
21333
  limit
21225
21334
  }) {
21226
- if (!reportData) return { applicableFixes: [], skippedRuleIds: [] };
21335
+ if (!reportData) return { applicableFixes: [], interactiveFixes: [] };
21227
21336
  const reportMetadata = {
21228
21337
  id: reportData.id,
21229
21338
  organizationId: reportData.vulnerabilityReport?.project?.organizationId,
@@ -21260,10 +21369,10 @@ var McpGQLClient = class extends GQLClient {
21260
21369
  }
21261
21370
  }
21262
21371
  const merged = Array.from(fixMap.values());
21263
- const { applicableFixes, skippedRuleIds } = partitionInteractiveFixes(merged);
21372
+ const { applicableFixes, interactiveFixes } = partitionInteractiveFixes(merged);
21264
21373
  return {
21265
21374
  applicableFixes: applicableFixes.slice(0, limit),
21266
- skippedRuleIds
21375
+ interactiveFixes: interactiveFixes.slice(0, limit)
21267
21376
  };
21268
21377
  }
21269
21378
  async updateFixesDownloadStatus(fixIds) {
@@ -21356,8 +21465,9 @@ var McpGQLClient = class extends GQLClient {
21356
21465
  codeNodes: { path: { _in: fileFilter } }
21357
21466
  };
21358
21467
  }
21468
+ const escapedRepoUrl = repoUrl.replace(/[%_\\]/g, (c) => `\\${c}`);
21359
21469
  const resp = await this._clientSdk.GetLatestReportByRepoUrl({
21360
- repoUrl,
21470
+ repoUrl: escapedRepoUrl,
21361
21471
  limit,
21362
21472
  offset,
21363
21473
  currentUserEmail,
@@ -21368,7 +21478,7 @@ var McpGQLClient = class extends GQLClient {
21368
21478
  reportCount: resp.fixReport?.length || 0
21369
21479
  });
21370
21480
  const latestReport = resp.fixReport?.[0] && FixReportSummarySchema.parse(resp.fixReport?.[0]);
21371
- const { applicableFixes, skippedRuleIds } = this.mergeUserAndSystemFixes({
21481
+ const { applicableFixes, interactiveFixes } = this.mergeUserAndSystemFixes({
21372
21482
  reportData: latestReport,
21373
21483
  limit
21374
21484
  });
@@ -21376,7 +21486,7 @@ var McpGQLClient = class extends GQLClient {
21376
21486
  fixReport: latestReport ? {
21377
21487
  ...latestReport,
21378
21488
  fixes: applicableFixes,
21379
- skippedRuleIds
21489
+ interactiveFixes
21380
21490
  } : null,
21381
21491
  expiredReport: resp.expiredReport?.[0] || null
21382
21492
  };
@@ -21446,17 +21556,17 @@ var McpGQLClient = class extends GQLClient {
21446
21556
  return null;
21447
21557
  }
21448
21558
  const latestReport = FixReportSummarySchema.parse(res.fixReport?.[0]);
21449
- const { applicableFixes, skippedRuleIds } = this.mergeUserAndSystemFixes({
21559
+ const { applicableFixes, interactiveFixes } = this.mergeUserAndSystemFixes({
21450
21560
  reportData: latestReport,
21451
21561
  limit
21452
21562
  });
21453
21563
  logDebug("[GraphQL] GetReportFixes response parsed", {
21454
21564
  fixes: applicableFixes,
21455
- skippedCount: skippedRuleIds.length
21565
+ interactiveCount: interactiveFixes.length
21456
21566
  });
21457
21567
  return {
21458
21568
  fixes: applicableFixes,
21459
- skippedRuleIds,
21569
+ interactiveFixes,
21460
21570
  totalCount: res.fixReport?.[0]?.filteredFixesCount?.aggregate?.count || 0,
21461
21571
  expiredReport: res.expiredReport?.[0] || null,
21462
21572
  fixReport: res.fixReport?.[0] ? {
@@ -21474,6 +21584,36 @@ var McpGQLClient = class extends GQLClient {
21474
21584
  throw e;
21475
21585
  }
21476
21586
  }
21587
+ /** Root getFix recomputes the patch; fix_by_pk.patchAndQuestions(userInput) does not (stale questions looked like cascading). */
21588
+ async getFixWithAnswers({
21589
+ fixId,
21590
+ answers
21591
+ }) {
21592
+ try {
21593
+ logDebug("[GraphQL] Calling getFixWithAnswers query", {
21594
+ fixId,
21595
+ answerCount: answers.length,
21596
+ userInput: answers
21597
+ });
21598
+ const resp = await this._clientSdk.getFixWithAnswers({
21599
+ fixId,
21600
+ userInput: answers
21601
+ });
21602
+ logDebug("[GraphQL] getFixWithAnswers successful", {
21603
+ fixId,
21604
+ responseTypename: resp.fixData?.__typename,
21605
+ remainingQuestionKeys: resp.fixData?.__typename === "FixData" ? resp.fixData.questions.map((q) => q.key) : void 0
21606
+ });
21607
+ return { fixData: resp.fixData ?? null };
21608
+ } catch (e) {
21609
+ logError("[GraphQL] getFixWithAnswers failed", {
21610
+ error: e,
21611
+ fixId,
21612
+ ...this.getErrorContext()
21613
+ });
21614
+ throw e;
21615
+ }
21616
+ }
21477
21617
  };
21478
21618
  async function createAuthenticatedMcpGQLClient({
21479
21619
  isBackgroundCall = false,
@@ -24516,6 +24656,7 @@ init_client_generates();
24516
24656
  init_configs();
24517
24657
 
24518
24658
  // src/mcp/core/prompts.ts
24659
+ init_client_generates();
24519
24660
  init_configs();
24520
24661
  function friendlyType(s) {
24521
24662
  const withoutUnderscores = s.replace(/_/g, " ");
@@ -24524,17 +24665,133 @@ function friendlyType(s) {
24524
24665
  }
24525
24666
  var noFixesReturnedForParameters = `No fixes returned for the given offset and limit parameters.
24526
24667
  `;
24527
- var skippedInteractiveFixesNotice = (skippedCount) => {
24528
- if (skippedCount <= 0) return "";
24529
- const s = skippedCount === 1 ? "" : "es";
24530
- const verb = skippedCount === 1 ? "requires" : "require";
24531
- const wasWere = skippedCount === 1 ? "was" : "were";
24668
+ var resolveQuestionText = ({
24669
+ fix,
24670
+ question
24671
+ }) => {
24672
+ const language = fix.safeIssueLanguage ?? void 0;
24673
+ const issueType = fix.safeIssueType ?? void 0;
24674
+ if (!language || !issueType) {
24675
+ return { content: question.name, description: "" };
24676
+ }
24677
+ const item = storedQuestionData_default[language]?.[issueType]?.[question.name];
24678
+ if (!item) {
24679
+ return { content: question.name, description: "" };
24680
+ }
24681
+ const args = question.extraContext.reduce(
24682
+ (acc, ctx) => {
24683
+ acc[ctx.key] = ctx.value;
24684
+ return acc;
24685
+ },
24686
+ {}
24687
+ );
24688
+ try {
24689
+ return {
24690
+ content: item.content(args) || question.name,
24691
+ description: item.description(args) || ""
24692
+ };
24693
+ } catch {
24694
+ return { content: question.name, description: "" };
24695
+ }
24696
+ };
24697
+ var formatQuestionInputContract = (question) => {
24698
+ switch (question.inputType) {
24699
+ case "SELECT" /* Select */:
24700
+ return `Pick exactly ONE of: ${question.options.map((o) => `\`${o}\``).join(", ")}`;
24701
+ case "NUMBER" /* Number */:
24702
+ return 'Provide a numeric string (e.g. "60").';
24703
+ case "TEXT" /* Text */:
24704
+ return "Provide a free-form string (or an empty string to accept the default).";
24705
+ }
24706
+ };
24707
+ var renderInteractiveFix = (fix, index) => {
24708
+ if (fix.patchAndQuestions.__typename !== "FixData") return "";
24709
+ const { questions, extraContext } = fix.patchAndQuestions;
24710
+ const vulnerabilityType = friendlyType(fix.safeIssueType ?? "Unknown");
24711
+ const questionsBlock = questions.slice().sort((a, b) => a.index - b.index).map((q, qIdx) => {
24712
+ const { content, description } = resolveQuestionText({ fix, question: q });
24713
+ const desc = description ? `
24714
+ *Why it matters:* ${description}` : "";
24715
+ const defaultLine = q.defaultValue ? `
24716
+ *Default if you don't decide:* \`${q.defaultValue}\`` : "";
24717
+ return `${qIdx + 1}. **\`${q.key}\`** \u2014 ${content}
24718
+ *Input:* ${formatQuestionInputContract(q)}${defaultLine}${desc}`;
24719
+ }).join("\n\n");
24720
+ return `### Interactive fix ${index + 1}: ${vulnerabilityType}
24721
+
24722
+ **Fix id:** \`${fix.id}\`
24723
+ **Description:** ${extraContext?.fixDescription || "Security vulnerability fix that requires answers before it can be tailored."}
24724
+
24725
+ **Questions you must answer before this fix can be applied:**
24726
+
24727
+ ${questionsBlock}
24728
+ `;
24729
+ };
24730
+ var interactiveFixesPrompt = ({
24731
+ interactiveFixes,
24732
+ repositoryPath
24733
+ }) => {
24734
+ if (interactiveFixes.length === 0) return "";
24735
+ const fixesBlock = interactiveFixes.map((fix, idx) => renderInteractiveFix(fix, idx)).join("\n---\n");
24532
24736
  return `
24533
- ## Skipped fixes
24737
+ ## Interactive fixes \u2014 decide from code, then apply or abstain
24738
+
24739
+ The Mobb backend produced ${interactiveFixes.length} fix${interactiveFixes.length === 1 ? "" : "es"} that need context before the patch can be tailored. **Infer answers only from the real code**: touched files, imports, framework, and how untrusted data flows (SSR/XSS/PT/SSRF, etc.). **Do NOT ask the end user** \u2014 use tooling/read-only repo inspection only.
24740
+
24741
+ ### CRITICAL INSTRUCTIONS \u2014 READ CAREFULLY
24742
+
24743
+ 1. **Defaults are hints from Mobb, not autopilot.** The line *Default if you don't decide* shows what the backend would fall back to. Prefer answers **consistent with the codebase**. Do **not** pick an answer **only** because it equals that default \u2014 a mismatched default can produce the wrong tailored patch.
24744
+ 2. **Time budget \u2014 ~5 seconds per interactive fix, as one shared pool.** With ${interactiveFixes.length} interactive fix${interactiveFixes.length === 1 ? "" : "es"} above, spend **at most ~${interactiveFixes.length * 5} seconds total** inspecting the codebase before re-calling \`${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES}\` with \`interactiveAnswers\`. The budget is a pool \u2014 one fix may take 8s if it's genuinely ambiguous while another takes 2s if the call site is obvious, as long as the total stays near the bound. If a fix is still uncertain when its share runs out, **omit it from \`interactiveAnswers\`** (rule 4) rather than over-deliberating.
24745
+ 3. **Confidence required.** Include in \`interactiveAnswers\` **only** fixes where your answers are justified by what you see in code (exact SELECT strings where applicable).
24746
+ 4. **Abstain rather than guess.** If you **cannot** justify any responsible answer after inspecting the code (ambiguous flows, missing callers, isomorphic bundles, unclear SSRF allowlists, etc.), **omit that fix id entirely** from \`interactiveAnswers\`. Tell the user in prose what was skipped and why so they can fix manually or follow up later \u2014 **do not fabricate answers**.
24747
+ 5. **Skipping everything.** If you skip **all** interactive fixes, still call **\`${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES}\`** once with \`"interactiveAnswers": []\` (empty array) together with \`path\`. That acknowledges abstention **without** starting a new scan. Omitting \`interactiveAnswers\` entirely falls back to scan mode instead \u2014 avoid that when you intend purely to abstain.
24748
+ 6. **Use exact option strings for SELECT questions.** Copy them character-for-character from the option list. Do **not** append explanation, rationale, or commentary to the value \u2014 that turns the answer into a non-matching string and the backend silently falls back to its default.
24749
+ 7. **Use the \`key\` verbatim, not the human label.** Each question shows a backtick-quoted key (e.g. \`is_server_side_code\`, \`tainted_term_type\`). That exact string goes into \`answers[].key\`. The display name (e.g. \`isServerSideCode\`, \`taintedTermType\`) is for humans only \u2014 sending it as a key means the backend won't recognise the answer and falls back to the default.
24750
+ 8. **After the tool call**, summarize: fixes applied with reasoning; fixes skipped (confidence/abstention/time-budget); tool failures.
24751
+
24752
+ ### Decision heuristics for common questions
24753
+
24754
+ (Keys shown in snake_case \u2014 copy them verbatim from each fix's question block.)
24755
+
24756
+ - **\`is_server_side_code\` (XSS)** \u2014 \`yes\` when server-render or Node HTTP handlers dominate; \`no\` when clearly browser-only. If bundle/context is genuinely ambiguous after inspection, **omit** this fix from \`interactiveAnswers\`.
24757
+ - **\`tainted_term_type\` (Path Traversal)** \u2014 match how user input is joined/consumed (single filename vs path segments vs absolute). If usage cannot be determined, **omit**.
24758
+ - **\`iframe_restrictions\`** \u2014 strict sandbox (\`""\`) unless embedded content clearly needs listed capabilities.
24759
+
24760
+ ### How to call \`${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES}\`
24761
+
24762
+ Apply fixes you are confident about (subset allowed):
24763
+
24764
+ \`\`\`json
24765
+ {
24766
+ "path": "${repositoryPath}",
24767
+ "interactiveAnswers": [
24768
+ {
24769
+ "fixId": "<fix id from below>",
24770
+ "answers": [
24771
+ { "key": "<question key, exactly as shown>", "value": "<your decided value>" }
24772
+ ]
24773
+ }
24774
+ ]
24775
+ }
24776
+ \`\`\`
24777
+
24778
+ Explicit abstention \u2014 skip **all** interactive fixes without rescanning:
24779
+
24780
+ \`\`\`json
24781
+ {
24782
+ "path": "${repositoryPath}",
24783
+ "interactiveAnswers": []
24784
+ }
24785
+ \`\`\`
24534
24786
 
24535
- ${skippedCount} fix${s} ${verb} user input that is not available over MCP and ${wasWere} skipped. Mention this to the user when summarizing results.
24787
+ ${fixesBlock}
24536
24788
  `;
24537
24789
  };
24790
+ var interactiveAnswersAbstainAllToolResponse = `## Interactive fixes \u2014 none applied
24791
+
24792
+ \`interactiveAnswers\` was an empty array: **no** tailored patches were requested and **no** scan was run.
24793
+
24794
+ State clearly for the user which interactive fixes you **skipped**, why the code did not support a confident answer, and that they can apply those manually or re-run after clarifying the codebase.`;
24538
24795
  var noFixesReturnedForParametersWithGuidance = ({
24539
24796
  offset,
24540
24797
  limit,
@@ -24593,9 +24850,13 @@ var applyFixesPrompt = ({
24593
24850
  currentTool,
24594
24851
  offset,
24595
24852
  limit,
24596
- gqlClient
24853
+ gqlClient,
24854
+ hasInteractiveFixes = false
24597
24855
  }) => {
24598
24856
  if (fixes.length === 0) {
24857
+ if (hasInteractiveFixes) {
24858
+ return "";
24859
+ }
24599
24860
  if (totalCount > 0) {
24600
24861
  return noFixesReturnedForParametersWithGuidance({
24601
24862
  offset,
@@ -24782,11 +25043,16 @@ var fixesFoundPrompt = ({
24782
25043
  offset,
24783
25044
  limit,
24784
25045
  gqlClient,
24785
- skippedInteractiveCount = 0
25046
+ interactiveFixes = [],
25047
+ repositoryPath
24786
25048
  }) => {
24787
25049
  const totalFixes = fixReport.filteredFixesCount.aggregate?.count || 0;
25050
+ const interactiveBlock = interactiveFixesPrompt({
25051
+ interactiveFixes,
25052
+ repositoryPath
25053
+ });
24788
25054
  if (totalFixes === 0) {
24789
- return noFixesAvailablePrompt + skippedInteractiveFixesNotice(skippedInteractiveCount);
25055
+ return noFixesAvailablePrompt + interactiveBlock;
24790
25056
  }
24791
25057
  const criticalFixes = fixReport.CRITICAL?.aggregate?.count || 0;
24792
25058
  const highFixes = fixReport.HIGH?.aggregate?.count || 0;
@@ -24828,8 +25094,9 @@ ${applyFixesPrompt({
24828
25094
  currentTool: MCP_TOOL_FETCH_AVAILABLE_FIXES,
24829
25095
  offset,
24830
25096
  limit,
24831
- gqlClient
24832
- })}${skippedInteractiveFixesNotice(skippedInteractiveCount)}`;
25097
+ gqlClient,
25098
+ hasInteractiveFixes: interactiveFixes.length > 0
25099
+ })}${interactiveBlock}`;
24833
25100
  };
24834
25101
  var nextStepsPrompt = ({ scannedFiles }) => `
24835
25102
  ### \u{1F4C1} Scanned Files
@@ -24876,10 +25143,15 @@ var fixesPrompt = ({
24876
25143
  scannedFiles,
24877
25144
  limit,
24878
25145
  gqlClient,
24879
- skippedInteractiveCount = 0
25146
+ interactiveFixes = [],
25147
+ repositoryPath
24880
25148
  }) => {
25149
+ const interactiveBlock = interactiveFixesPrompt({
25150
+ interactiveFixes,
25151
+ repositoryPath
25152
+ });
24881
25153
  if (totalCount === 0) {
24882
- return noFixesFoundPrompt({ scannedFiles }) + skippedInteractiveFixesNotice(skippedInteractiveCount);
25154
+ return noFixesFoundPrompt({ scannedFiles }) + interactiveBlock;
24883
25155
  }
24884
25156
  const shownCount = fixes.length;
24885
25157
  const nextOffset = offset + shownCount;
@@ -24895,9 +25167,10 @@ ${applyFixesPrompt({
24895
25167
  currentTool: MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES,
24896
25168
  offset,
24897
25169
  limit,
24898
- gqlClient
25170
+ gqlClient,
25171
+ hasInteractiveFixes: interactiveFixes.length > 0
24899
25172
  })}
24900
- ${skippedInteractiveFixesNotice(skippedInteractiveCount)}
25173
+ ${interactiveBlock}
24901
25174
  ${nextStepsPrompt({ scannedFiles })}
24902
25175
  `;
24903
25176
  };
@@ -24971,7 +25244,8 @@ var freshFixesPrompt = ({
24971
25244
  fixes,
24972
25245
  limit,
24973
25246
  gqlClient,
24974
- skippedInteractiveCount = 0
25247
+ interactiveFixes = [],
25248
+ repositoryPath
24975
25249
  }) => {
24976
25250
  return `Here are the fresh fixes to the vulnerabilities discovered by Mobb MCP
24977
25251
 
@@ -24984,9 +25258,10 @@ ${applyFixesPrompt({
24984
25258
  currentTool: MCP_TOOL_FETCH_AVAILABLE_FIXES,
24985
25259
  offset: 0,
24986
25260
  limit,
24987
- gqlClient
25261
+ gqlClient,
25262
+ hasInteractiveFixes: interactiveFixes.length > 0
24988
25263
  })}
24989
- ${skippedInteractiveFixesNotice(skippedInteractiveCount)}
25264
+ ${interactiveFixesPrompt({ interactiveFixes, repositoryPath })}
24990
25265
  `;
24991
25266
  };
24992
25267
  function extractTargetFileFromPatch(patch) {
@@ -25006,7 +25281,8 @@ function formatSeverity(severityText, severityValue) {
25006
25281
  var appliedFixesSummaryPrompt = ({
25007
25282
  fixes,
25008
25283
  gqlClient,
25009
- skippedInteractiveCount = 0
25284
+ interactiveFixes = [],
25285
+ repositoryPath
25010
25286
  }) => {
25011
25287
  const fixIds = fixes.map((fix) => fix.id);
25012
25288
  void gqlClient.updateFixesDownloadStatus(fixIds);
@@ -25041,7 +25317,7 @@ ${fixes.map((fix, index) => {
25041
25317
  ${continuousMonitoringSection}
25042
25318
 
25043
25319
  ${autoFixSettingsSection}
25044
- ${skippedInteractiveFixesNotice(skippedInteractiveCount)}
25320
+ ${interactiveFixesPrompt({ interactiveFixes, repositoryPath })}
25045
25321
  ## \u{1F4CB} Next Steps
25046
25322
 
25047
25323
  1. **Review the changes** - Check the modified files to understand what was fixed
@@ -25675,7 +25951,7 @@ var LocalMobbFolderService = class {
25675
25951
  if (fix.vulnerabilityReportIssues.length > 0) {
25676
25952
  fix.vulnerabilityReportIssues.forEach((issue) => {
25677
25953
  if (issue.vulnerabilityReportIssueTags && issue.vulnerabilityReportIssueTags.length > 0) {
25678
- markdown += `**Tags:** ${issue.vulnerabilityReportIssueTags.map((tag) => tag.vulnerability_report_issue_tag_value).join(", ")}
25954
+ markdown += `**Tags:** ${issue.vulnerabilityReportIssueTags.map((tag2) => tag2.vulnerability_report_issue_tag_value).join(", ")}
25679
25955
 
25680
25956
  `;
25681
25957
  }
@@ -25948,6 +26224,7 @@ var LocalMobbFolderService = class {
25948
26224
  };
25949
26225
 
25950
26226
  // src/mcp/services/PatchApplicationService.ts
26227
+ init_client_generates();
25951
26228
  init_configs();
25952
26229
  import {
25953
26230
  existsSync as existsSync6,
@@ -26513,7 +26790,8 @@ var PatchApplicationService = class {
26513
26790
  repositoryPath,
26514
26791
  scanStartTime,
26515
26792
  gqlClient,
26516
- scanContext
26793
+ scanContext,
26794
+ downloadSource = "AUTO_MVS" /* AutoMvs */
26517
26795
  }) {
26518
26796
  const appliedFixes = [];
26519
26797
  const failedFixes = [];
@@ -26572,20 +26850,26 @@ var PatchApplicationService = class {
26572
26850
  if (appliedFixes.length > 0 && gqlClient) {
26573
26851
  try {
26574
26852
  const appliedFixIds = appliedFixes.map((fix) => fix.id).filter(Boolean);
26575
- await gqlClient.updateAutoAppliedFixesStatus(appliedFixIds);
26853
+ if (downloadSource === "MCP" /* Mcp */) {
26854
+ await gqlClient.updateFixesDownloadStatus(appliedFixIds);
26855
+ } else {
26856
+ await gqlClient.updateAutoAppliedFixesStatus(appliedFixIds);
26857
+ }
26576
26858
  logDebug(
26577
- `[${scanContext}] Successfully updated download status for auto-applied fixes`,
26859
+ `[${scanContext}] Successfully updated download status for applied fixes`,
26578
26860
  {
26579
26861
  appliedFixIds,
26580
- count: appliedFixIds.length
26862
+ count: appliedFixIds.length,
26863
+ downloadSource
26581
26864
  }
26582
26865
  );
26583
26866
  } catch (error) {
26584
26867
  logError(
26585
- `[${scanContext}] Failed to update download status for auto-applied fixes`,
26868
+ `[${scanContext}] Failed to update download status for applied fixes`,
26586
26869
  {
26587
26870
  error: error instanceof Error ? error.message : String(error),
26588
- appliedFixCount: appliedFixes.length
26871
+ appliedFixCount: appliedFixes.length,
26872
+ downloadSource
26589
26873
  }
26590
26874
  );
26591
26875
  }
@@ -27327,6 +27611,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
27327
27611
  __publicField(this, "path", "");
27328
27612
  __publicField(this, "filesLastScanned", {});
27329
27613
  __publicField(this, "freshFixes", []);
27614
+ __publicField(this, "interactiveFixes", []);
27330
27615
  __publicField(this, "reportedFixes", []);
27331
27616
  __publicField(this, "intervalId", null);
27332
27617
  __publicField(this, "isInitialScanComplete", false);
@@ -27348,6 +27633,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
27348
27633
  reset() {
27349
27634
  this.filesLastScanned = {};
27350
27635
  this.freshFixes = [];
27636
+ this.interactiveFixes = [];
27351
27637
  this.reportedFixes = [];
27352
27638
  this.hasAuthenticationFailed = false;
27353
27639
  this.fullScanPathsScanned = configStore.get("fullScanPathsScanned") || [];
@@ -27433,6 +27719,16 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
27433
27719
  const newFixes = fixes?.fixes?.filter(
27434
27720
  (fix) => !this.isFixAlreadyReported(fix)
27435
27721
  );
27722
+ const newInteractiveFixes = fixes?.interactiveFixes?.filter(
27723
+ (fix) => !this.isFixAlreadyReported(fix)
27724
+ ) ?? [];
27725
+ if (newInteractiveFixes.length > 0) {
27726
+ this.interactiveFixes.push(...newInteractiveFixes);
27727
+ logInfo(
27728
+ `[${scanContext}] Buffered ${newInteractiveFixes.length} interactive fixes for next response`,
27729
+ { totalBuffered: this.interactiveFixes.length }
27730
+ );
27731
+ }
27436
27732
  logInfo(
27437
27733
  `[${scanContext}] Security fixes retrieved, total: ${fixes?.fixes?.length || 0}, new: ${newFixes?.length || 0}`
27438
27734
  );
@@ -27862,7 +28158,9 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
27862
28158
  return freshFixesPrompt({
27863
28159
  fixes: freshFixes,
27864
28160
  limit: MCP_DEFAULT_LIMIT,
27865
- gqlClient: this.gqlClient
28161
+ gqlClient: this.gqlClient,
28162
+ interactiveFixes: this.interactiveFixes.splice(0, MCP_DEFAULT_LIMIT),
28163
+ repositoryPath: this.path
27866
28164
  });
27867
28165
  }
27868
28166
  logInfo(`[${scanContext}] No fresh fixes to report`);
@@ -27876,7 +28174,9 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
27876
28174
  );
27877
28175
  return appliedFixesSummaryPrompt({
27878
28176
  fixes: appliedFixesToShow,
27879
- gqlClient: this.gqlClient
28177
+ gqlClient: this.gqlClient,
28178
+ interactiveFixes: this.interactiveFixes.splice(0, MCP_DEFAULT_LIMIT),
28179
+ repositoryPath: this.path
27880
28180
  });
27881
28181
  }
27882
28182
  logInfo(`[${scanContext}] No applied fixes to report`);
@@ -27983,6 +28283,7 @@ var _FetchAvailableFixesService = class _FetchAvailableFixesService {
27983
28283
  }
27984
28284
  async checkForAvailableFixes({
27985
28285
  repoUrl,
28286
+ repositoryPath,
27986
28287
  limit = MCP_DEFAULT_LIMIT,
27987
28288
  offset,
27988
28289
  fileFilter
@@ -28020,7 +28321,8 @@ var _FetchAvailableFixesService = class _FetchAvailableFixesService {
28020
28321
  offset: effectiveOffset,
28021
28322
  limit,
28022
28323
  gqlClient,
28023
- skippedInteractiveCount: fixReport.skippedRuleIds?.length ?? 0
28324
+ interactiveFixes: fixReport.interactiveFixes ?? [],
28325
+ repositoryPath
28024
28326
  });
28025
28327
  this.currentOffset = effectiveOffset + (fixReport.fixes?.length || 0);
28026
28328
  return prompt;
@@ -28162,6 +28464,7 @@ Call this tool instead of ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES} when you only
28162
28464
  }
28163
28465
  const fixResult = await this.availableFixesService.checkForAvailableFixes({
28164
28466
  repoUrl: originUrl,
28467
+ repositoryPath: path37,
28165
28468
  limit: args.limit,
28166
28469
  offset: args.offset,
28167
28470
  fileFilter: actualFileFilter
@@ -28265,6 +28568,7 @@ import z45 from "zod";
28265
28568
  init_configs();
28266
28569
 
28267
28570
  // src/mcp/tools/scanAndFixVulnerabilities/ScanAndFixVulnerabilitiesService.ts
28571
+ init_client_generates();
28268
28572
  init_configs();
28269
28573
  var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService {
28270
28574
  constructor() {
@@ -28356,7 +28660,8 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
28356
28660
  scannedFiles: [...fileList],
28357
28661
  limit: effectiveLimit,
28358
28662
  gqlClient: this.gqlClient,
28359
- skippedInteractiveCount: fixes.skippedRuleIds.length
28663
+ interactiveFixes: fixes.interactiveFixes,
28664
+ repositoryPath
28360
28665
  });
28361
28666
  this.currentOffset = effectiveOffset + (fixes.fixes?.length || 0);
28362
28667
  return prompt;
@@ -28401,12 +28706,164 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
28401
28706
  return {
28402
28707
  fixes: fixes?.fixes || [],
28403
28708
  totalCount: fixes?.totalCount || 0,
28404
- skippedRuleIds: fixes?.skippedRuleIds || []
28709
+ interactiveFixes: fixes?.interactiveFixes || []
28405
28710
  };
28406
28711
  }
28712
+ /** Applies patches from interactiveAnswers only (no scan). */
28713
+ async applyInteractiveAnswers({
28714
+ interactiveAnswers,
28715
+ repositoryPath
28716
+ }) {
28717
+ this.gqlClient = await this.initializeGqlClient();
28718
+ logInfo(
28719
+ `Applying ${interactiveAnswers.length} interactive fix(es) with LLM-supplied answers`,
28720
+ { repositoryPath }
28721
+ );
28722
+ const applied = [];
28723
+ const failed = [];
28724
+ const skipped = [];
28725
+ for (const entry of interactiveAnswers) {
28726
+ try {
28727
+ const { fixData } = await this.gqlClient.getFixWithAnswers({
28728
+ fixId: entry.fixId,
28729
+ answers: entry.answers
28730
+ });
28731
+ if (!fixData) {
28732
+ failed.push({
28733
+ fixId: entry.fixId,
28734
+ reason: "Fix not found on the server (may have expired)"
28735
+ });
28736
+ continue;
28737
+ }
28738
+ if (fixData.__typename !== "FixData") {
28739
+ failed.push({
28740
+ fixId: entry.fixId,
28741
+ reason: `Backend returned ${fixData.__typename} \u2014 could not produce a patch with the supplied answers`
28742
+ });
28743
+ continue;
28744
+ }
28745
+ if (!fixData.patch) {
28746
+ failed.push({
28747
+ fixId: entry.fixId,
28748
+ reason: "Backend returned FixData with no patch \u2014 answers did not yield an applicable fix"
28749
+ });
28750
+ continue;
28751
+ }
28752
+ const sentByKey = new Map(entry.answers.map((a) => [a.key, a.value]));
28753
+ const invalidSelectAnswers = [];
28754
+ for (const q of fixData.questions) {
28755
+ if (q.inputType !== "SELECT" /* Select */) continue;
28756
+ const sentValue = sentByKey.get(q.key);
28757
+ if (sentValue === void 0) continue;
28758
+ if (!q.options.includes(sentValue)) {
28759
+ invalidSelectAnswers.push({
28760
+ key: q.key,
28761
+ sentValue,
28762
+ options: [...q.options]
28763
+ });
28764
+ }
28765
+ }
28766
+ if (invalidSelectAnswers.length > 0) {
28767
+ skipped.push({
28768
+ fixId: entry.fixId,
28769
+ invalidSelectAnswers
28770
+ });
28771
+ continue;
28772
+ }
28773
+ const newPendingKeys = fixData.questions.map((q) => q.key).filter((k) => !sentByKey.has(k));
28774
+ const mcpFix = McpFixSchema.parse({
28775
+ __typename: "fix",
28776
+ id: entry.fixId,
28777
+ confidence: 0,
28778
+ safeIssueType: null,
28779
+ safeIssueLanguage: null,
28780
+ severityText: null,
28781
+ severityValue: null,
28782
+ vulnerabilityReportIssues: [],
28783
+ patchAndQuestions: fixData
28784
+ });
28785
+ const result = await PatchApplicationService.applyFixes({
28786
+ fixes: [mcpFix],
28787
+ repositoryPath,
28788
+ gqlClient: this.gqlClient,
28789
+ scanContext: ScanContext.USER_REQUEST,
28790
+ downloadSource: "MCP" /* Mcp */
28791
+ });
28792
+ if (result.appliedFixes.length > 0) {
28793
+ const targetFile = extractTargetFile(fixData.patch) ?? "unknown file";
28794
+ applied.push({
28795
+ fixId: entry.fixId,
28796
+ targetFile,
28797
+ newPendingKeys
28798
+ });
28799
+ } else {
28800
+ failed.push({
28801
+ fixId: entry.fixId,
28802
+ reason: result.failedFixes[0]?.error ?? "patch application failed"
28803
+ });
28804
+ }
28805
+ } catch (error) {
28806
+ failed.push({
28807
+ fixId: entry.fixId,
28808
+ reason: error.message
28809
+ });
28810
+ }
28811
+ }
28812
+ return formatApplyAnswersSummary({ applied, failed, skipped });
28813
+ }
28407
28814
  };
28408
28815
  __publicField(_ScanAndFixVulnerabilitiesService, "instance");
28409
28816
  var ScanAndFixVulnerabilitiesService = _ScanAndFixVulnerabilitiesService;
28817
+ function extractTargetFile(patch) {
28818
+ const match = patch.match(/^\+\+\+ b\/(.+)$/m);
28819
+ return match?.[1] ?? null;
28820
+ }
28821
+ function formatApplyAnswersSummary({
28822
+ applied,
28823
+ failed,
28824
+ skipped
28825
+ }) {
28826
+ const sections = [];
28827
+ if (applied.length > 0) {
28828
+ sections.push(
28829
+ `## \u2705 Applied ${applied.length} fix${applied.length === 1 ? "" : "es"}
28830
+
28831
+ ` + applied.map((a) => {
28832
+ const hint = a.newPendingKeys.length > 0 ? `
28833
+ \u26A0\uFE0F The backend returned additional question key(s) we didn't send: [${a.newPendingKeys.map((k) => `\`${k}\``).join(
28834
+ ", "
28835
+ )}]. This is either (a) a true cascading question \u2014 re-call \`scan_and_fix_vulnerabilities\` with those added to \`interactiveAnswers\` if you have a confident answer; or (b) the key we sent was wrong (e.g. camelCase vs snake_case) and the backend fell back to its default \u2014 copy the echoed key verbatim and retry. The patch above was already applied using defaults.` : "";
28836
+ return `- **\`${a.fixId}\`** \u2192 \`${a.targetFile}\`${hint}`;
28837
+ }).join("\n")
28838
+ );
28839
+ }
28840
+ if (skipped.length > 0) {
28841
+ sections.push(
28842
+ `## \u23ED\uFE0F Skipped ${skipped.length} fix${skipped.length === 1 ? "" : "es"} \u2014 invalid SELECT answer value(s)
28843
+
28844
+ ` + skipped.map((s) => {
28845
+ const detail = s.invalidSelectAnswers.map(
28846
+ (a) => `\`${a.key}\` got \`"${a.sentValue}"\` \u2014 allowed options: [${a.options.map((o) => `\`"${o}"\``).join(", ")}]`
28847
+ ).join("; ");
28848
+ return `- **\`${s.fixId}\`** \u2014 ${detail}
28849
+ The file was **not modified**. Re-call \`scan_and_fix_vulnerabilities\` with one of the exact allowed option strings (copy character-for-character, no commentary appended) or omit this fix entirely if no option is justified by the code.`;
28850
+ }).join("\n") + `
28851
+
28852
+ This guardrail exists to prevent the backend from silently falling back to its default sanitizer for an answer it didn't recognize \u2014 which could apply a semantically-wrong patch and break legitimate code paths.`
28853
+ );
28854
+ }
28855
+ if (failed.length > 0) {
28856
+ sections.push(
28857
+ `## \u274C Failed
28858
+
28859
+ ` + failed.map((f) => `- **\`${f.fixId}\`** \u2014 ${f.reason}`).join("\n")
28860
+ );
28861
+ }
28862
+ if (sections.length === 0) {
28863
+ return "No fixes were processed.";
28864
+ }
28865
+ return sections.join("\n\n");
28866
+ }
28410
28867
 
28411
28868
  // src/mcp/tools/scanAndFixVulnerabilities/ScanAndFixVulnerabilitiesTool.ts
28412
28869
  var ScanAndFixVulnerabilitiesTool = class extends BaseTool {
@@ -28414,13 +28871,23 @@ var ScanAndFixVulnerabilitiesTool = class extends BaseTool {
28414
28871
  super();
28415
28872
  __publicField(this, "name", MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES);
28416
28873
  __publicField(this, "displayName", "Scan and Fix Vulnerabilities");
28417
- // A detailed description to guide the LLM on when and how to invoke this tool.
28418
- __publicField(this, "description", `Scans a given local repository for security vulnerabilities and returns auto-generated code fixes.
28874
+ __publicField(this, "description", `Scans a given local repository for security vulnerabilities, applies the auto-fixable ones, and surfaces any fix that needs your input as an "Interactive fix". Re-invoke with "interactiveAnswers" to apply those.
28875
+
28876
+ Two modes of operation:
28877
+
28878
+ A) SCAN MODE (default \u2014 interactiveAnswers omitted)
28879
+ - Scans changed/recent files at "path"
28880
+ - Auto-applies fixes that need no input
28881
+ - Returns "Interactive fix" entries for fixes that need decisions; you (the AI) decide answers from the surrounding code
28882
+
28883
+ B) APPLY-WITH-ANSWERS MODE (interactiveAnswers field supplied \u2014 array may be empty or partial)
28884
+ - SKIPS scanning entirely (does NOT fall back to scan mode just because some fixes were skipped)
28885
+ - Include ONLY fixes where answers are justified by code/context; omit fix IDs you are not confident about
28886
+ - Empty array []: abstain from applying ALL interactive fixes (no patches fetched \u2014 summarize skips for the user)
28419
28887
 
28420
28888
  When to invoke:
28421
- \u2022 Use when the user explicitly asks to "scan for vulnerabilities", "run a security check", or "test for security issues" in a local repository.
28422
- \u2022 The repository must exist on disk; supply its absolute path with the required "path" argument.
28423
- \u2022 Ideal after the user makes code changes (added/modified/staged files) but before committing, or whenever they request a full rescan.
28889
+ \u2022 Mode A \u2014 when the user asks to "scan for vulnerabilities", "run a security check", or after they make code changes.
28890
+ \u2022 Mode B \u2014 immediately after Mode A returns interactive fixes; pass confident answers only; use [] only when abstaining from every interactive fix.
28424
28891
 
28425
28892
  How to invoke:
28426
28893
  \u2022 Required argument:
@@ -28430,25 +28897,32 @@ How to invoke:
28430
28897
  \u2013 limit (number): maximum number of fixes to include in the response.
28431
28898
  \u2013 maxFiles (number): maximum number of files to scan (default: ${MCP_DEFAULT_MAX_FILES_TO_SCAN}). Provide this value to increase the scope of the scan.
28432
28899
  \u2013 rescan (boolean): true to force a complete rescan even if cached results exist.
28900
+ \u2013 interactiveAnswers (array): triggers Mode B. Each entry: { fixId, answers: [{ key, value }] }. SELECT values MUST be exact strings from the option list. Omit fixes you cannot answer confidently. Use [] to abstain from all interactive fixes without rescanning.
28433
28901
 
28434
- Behaviour:
28435
- \u2022 If the directory is a valid Git repository, the tool scans the changed files in the repository. If there are no changes, it scans the files included in the las commit.
28902
+ Behaviour (Mode A):
28903
+ \u2022 If the directory is a valid Git repository, the tool scans the changed files in the repository. If there are no changes, it scans the files included in the last commit.
28436
28904
  \u2022 If the directory is not a valid Git repository, the tool falls back to scanning recently changed files in the folder.
28437
28905
  \u2022 If maxFiles is provided, the tool scans the maxFiles most recently changed files in the repository.
28438
- \u2022 By default, only new, modified, or staged files are scanned; if none are found, it checks recently changed files.
28439
- \u2022 The tool NEVER commits or pushes changes; it only returns proposed diffs/fixes as text.
28906
+ \u2022 The tool NEVER commits or pushes changes.
28440
28907
 
28441
28908
  Return value:
28442
- The response is an object with a single "content" array containing one text element. The text is either:
28443
- \u2022 A human-readable summary of the fixes / patches, or
28444
- \u2022 A diagnostic or error message if the scan fails or finds nothing to fix.
28909
+ A "content" array with one text element. Either a human-readable summary of fixes/patches, an interactive-fix prompt, an apply-with-answers result, or an error message.
28445
28910
 
28446
- Example payload:
28911
+ Example payload (Mode A):
28912
+ { "path": "/home/user/my-project", "limit": 20, "maxFiles": 50 }
28913
+
28914
+ Example payload (Mode B \u2014 subset or abstain):
28447
28915
  {
28448
28916
  "path": "/home/user/my-project",
28449
- "limit": 20,
28450
- "maxFiles": 50,
28451
- "rescan": false
28917
+ "interactiveAnswers": [
28918
+ { "fixId": "abc-123", "answers": [{ "key": "isServerSideCode", "value": "yes" }] }
28919
+ ]
28920
+ }
28921
+
28922
+ Example payload (Mode B \u2014 abstain from every interactive fix, no rescan):
28923
+ {
28924
+ "path": "/home/user/my-project",
28925
+ "interactiveAnswers": []
28452
28926
  }`);
28453
28927
  __publicField(this, "hasAuthentication", true);
28454
28928
  __publicField(this, "inputValidationSchema", z45.object({
@@ -28463,6 +28937,21 @@ Example payload:
28463
28937
  rescan: z45.boolean().optional().describe("Optional whether to rescan the repository"),
28464
28938
  scanRecentlyChangedFiles: z45.boolean().optional().describe(
28465
28939
  "Optional whether to automatically scan recently changed files when no changed files are found in git status. If false, the tool will prompt the user instead."
28940
+ ),
28941
+ interactiveAnswers: z45.array(
28942
+ z45.object({
28943
+ fixId: z45.string().min(1).describe('Fix id from a previous "Interactive fix" prompt block.'),
28944
+ answers: z45.array(
28945
+ z45.object({
28946
+ key: z45.string().min(1).describe("FixQuestion key."),
28947
+ value: z45.string().describe(
28948
+ "For SELECT questions MUST be one of the listed options; for TEXT/NUMBER, a free-form value."
28949
+ )
28950
+ })
28951
+ ).min(1)
28952
+ })
28953
+ ).optional().describe(
28954
+ "When supplied (including []), SKIPS scanning. Non-empty: apply each listed interactive fix. Empty []: abstain from all interactive fixes \u2014 no patches applied. Omit entirely for scan mode."
28466
28955
  )
28467
28956
  }));
28468
28957
  __publicField(this, "inputSchema", {
@@ -28491,6 +28980,34 @@ Example payload:
28491
28980
  scanRecentlyChangedFiles: {
28492
28981
  type: "boolean",
28493
28982
  description: "[Optional] whether to automatically scan recently changed files when no changed files are found in git status. If false, the tool will prompt the user instead."
28983
+ },
28984
+ interactiveAnswers: {
28985
+ type: "array",
28986
+ items: {
28987
+ type: "object",
28988
+ properties: {
28989
+ fixId: {
28990
+ type: "string",
28991
+ description: 'Fix id from a previous "Interactive fix" prompt.'
28992
+ },
28993
+ answers: {
28994
+ type: "array",
28995
+ items: {
28996
+ type: "object",
28997
+ properties: {
28998
+ key: { type: "string", description: "FixQuestion key." },
28999
+ value: {
29000
+ type: "string",
29001
+ description: "Decided value (SELECT must match an option exactly)."
29002
+ }
29003
+ },
29004
+ required: ["key", "value"]
29005
+ }
29006
+ }
29007
+ },
29008
+ required: ["fixId", "answers"]
29009
+ },
29010
+ description: "[Optional] When supplied (including []), skips scanning. Non-empty: apply interactive fixes with answers. Empty []: abstain from all interactive fixes without rescanning. Omit for scan mode."
28494
29011
  }
28495
29012
  },
28496
29013
  required: ["path"]
@@ -28501,7 +29018,8 @@ Example payload:
28501
29018
  }
28502
29019
  async executeInternal(args) {
28503
29020
  logDebug(`Executing tool: ${this.name}`, {
28504
- path: args.path
29021
+ path: args.path,
29022
+ mode: args.interactiveAnswers === void 0 ? "scan" : args.interactiveAnswers.length === 0 ? "apply-interactive-abstain-all" : "apply-with-answers"
28505
29023
  });
28506
29024
  if (!args.path) {
28507
29025
  throw new Error("Invalid arguments: Missing required parameter 'path'");
@@ -28513,6 +29031,26 @@ Example payload:
28513
29031
  );
28514
29032
  }
28515
29033
  const path37 = pathValidationResult.path;
29034
+ if (args.interactiveAnswers !== void 0) {
29035
+ if (args.interactiveAnswers.length === 0) {
29036
+ return this.createSuccessResponse(
29037
+ interactiveAnswersAbstainAllToolResponse
29038
+ );
29039
+ }
29040
+ try {
29041
+ const result = await this.vulnerabilityFixService.applyInteractiveAnswers({
29042
+ interactiveAnswers: args.interactiveAnswers,
29043
+ repositoryPath: path37
29044
+ });
29045
+ return this.createSuccessResponse(result);
29046
+ } catch (error) {
29047
+ const message = error.message;
29048
+ logError("Tool execution failed (apply-with-answers)", {
29049
+ error: message
29050
+ });
29051
+ return this.createSuccessResponse(message);
29052
+ }
29053
+ }
28516
29054
  const files = await getLocalFiles({
28517
29055
  path: path37,
28518
29056
  maxFileSize: MCP_MAX_FILE_SIZE,