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: {
|
|
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: {
|
|
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(
|
|
1322
|
-
switch (
|
|
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
|
|
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
|
|
6865
|
-
|
|
6866
|
-
return
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
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:
|
|
6880
|
-
accessToken:
|
|
6881
|
-
scmLibType: getScmLibTypeFromScmType(
|
|
6882
|
-
scmOrg:
|
|
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/${
|
|
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
|
-
|
|
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
|
|
12224
|
-
|
|
12225
|
-
|
|
12226
|
-
|
|
12227
|
-
|
|
12228
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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", (
|
|
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(
|
|
12303
|
-
this.currentPath.push(
|
|
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(
|
|
12317
|
-
super.onOpenTag(
|
|
12371
|
+
onOpenTag(tag2) {
|
|
12372
|
+
super.onOpenTag(tag2);
|
|
12318
12373
|
switch (this.getPathString()) {
|
|
12319
12374
|
case "Audit > IssueList > Issue":
|
|
12320
|
-
this.suppressedMap[String(
|
|
12321
|
-
|
|
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(
|
|
12341
|
-
super.onOpenTag(
|
|
12395
|
+
onOpenTag(tag2) {
|
|
12396
|
+
super.onOpenTag(tag2);
|
|
12342
12397
|
switch (this.getPathString()) {
|
|
12343
12398
|
case "FVDL > EngineData > RuleInfo > Rule":
|
|
12344
|
-
this.ruleId = String(
|
|
12399
|
+
this.ruleId = String(tag2.attributes["id"] ?? "");
|
|
12345
12400
|
break;
|
|
12346
12401
|
case "FVDL > EngineData > RuleInfo > Rule > MetaInfo > Group":
|
|
12347
|
-
this.groupName = String(
|
|
12402
|
+
this.groupName = String(tag2.attributes["name"] ?? "");
|
|
12348
12403
|
break;
|
|
12349
12404
|
case "FVDL > CreatedTS":
|
|
12350
|
-
this.createdTSDate = String(
|
|
12351
|
-
this.createdTSTime = String(
|
|
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(
|
|
12385
|
-
super.onOpenTag(
|
|
12439
|
+
onOpenTag(tag2) {
|
|
12440
|
+
super.onOpenTag(tag2);
|
|
12386
12441
|
switch (this.getPathString()) {
|
|
12387
12442
|
case "FVDL > UnifiedNodePool > Node":
|
|
12388
|
-
this.nodeId = String(
|
|
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(
|
|
12393
|
-
colStart: String(
|
|
12394
|
-
colEnd: String(
|
|
12395
|
-
line: String(
|
|
12396
|
-
lineEnd: String(
|
|
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(
|
|
12419
|
-
super.onOpenTag(
|
|
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(
|
|
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(
|
|
12435
|
-
colStart: String(
|
|
12436
|
-
colEnd: String(
|
|
12437
|
-
line: String(
|
|
12438
|
-
lineEnd: String(
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
20923
|
-
return { applicableFixes: fixes, skippedRuleIds: [] };
|
|
20924
|
-
}
|
|
21019
|
+
const disabled = isInteractiveRoutingDisabled();
|
|
20925
21020
|
const applicableFixes = [];
|
|
20926
|
-
const
|
|
21021
|
+
const interactiveFixes = [];
|
|
21022
|
+
const droppedInteractive = [];
|
|
20927
21023
|
for (const fix of fixes) {
|
|
20928
21024
|
if (isInteractiveFix(fix)) {
|
|
20929
|
-
|
|
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 (
|
|
20935
|
-
logInfo(
|
|
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
|
-
|
|
20938
|
-
|
|
21046
|
+
interactiveCount: interactiveFixes.length,
|
|
21047
|
+
interactiveByRule: countByRule(interactiveFixes.map(ruleIdFor))
|
|
20939
21048
|
});
|
|
20940
21049
|
}
|
|
20941
|
-
return { applicableFixes,
|
|
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: [],
|
|
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,
|
|
21372
|
+
const { applicableFixes, interactiveFixes } = partitionInteractiveFixes(merged);
|
|
21264
21373
|
return {
|
|
21265
21374
|
applicableFixes: applicableFixes.slice(0, limit),
|
|
21266
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
21565
|
+
interactiveCount: interactiveFixes.length
|
|
21456
21566
|
});
|
|
21457
21567
|
return {
|
|
21458
21568
|
fixes: applicableFixes,
|
|
21459
|
-
|
|
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
|
|
24528
|
-
|
|
24529
|
-
|
|
24530
|
-
|
|
24531
|
-
const
|
|
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
|
-
##
|
|
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
|
-
${
|
|
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
|
-
|
|
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 +
|
|
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
|
-
|
|
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
|
-
|
|
25146
|
+
interactiveFixes = [],
|
|
25147
|
+
repositoryPath
|
|
24880
25148
|
}) => {
|
|
25149
|
+
const interactiveBlock = interactiveFixesPrompt({
|
|
25150
|
+
interactiveFixes,
|
|
25151
|
+
repositoryPath
|
|
25152
|
+
});
|
|
24881
25153
|
if (totalCount === 0) {
|
|
24882
|
-
return noFixesFoundPrompt({ scannedFiles }) +
|
|
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
|
-
${
|
|
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
|
-
|
|
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
|
-
${
|
|
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
|
-
|
|
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
|
-
${
|
|
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((
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
28418
|
-
|
|
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
|
|
28422
|
-
\u2022
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
"
|
|
28450
|
-
|
|
28451
|
-
|
|
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,
|