mobbdev 1.4.10 → 1.4.12
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/args/commands/upload_ai_blame.d.mts +28 -9
- package/dist/args/commands/upload_ai_blame.mjs +114 -15
- package/dist/index.mjs +814 -146
- package/package.json +1 -1
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
|
|
@@ -464,6 +469,19 @@ var init_client_generates = __esm({
|
|
|
464
469
|
... on FixData {
|
|
465
470
|
patch
|
|
466
471
|
patchOriginalEncodingBase64
|
|
472
|
+
questions {
|
|
473
|
+
key
|
|
474
|
+
name
|
|
475
|
+
defaultValue
|
|
476
|
+
value
|
|
477
|
+
inputType
|
|
478
|
+
options
|
|
479
|
+
index
|
|
480
|
+
extraContext {
|
|
481
|
+
key
|
|
482
|
+
value
|
|
483
|
+
}
|
|
484
|
+
}
|
|
467
485
|
extraContext {
|
|
468
486
|
extraContext {
|
|
469
487
|
key
|
|
@@ -1179,6 +1197,37 @@ var init_client_generates = __esm({
|
|
|
1179
1197
|
error
|
|
1180
1198
|
}
|
|
1181
1199
|
}
|
|
1200
|
+
}
|
|
1201
|
+
`;
|
|
1202
|
+
GetFixWithAnswersDocument = `
|
|
1203
|
+
query getFixWithAnswers($fixId: uuid!, $userInput: [QuestionAnswer!]!) {
|
|
1204
|
+
fixData: getFix(fixId: $fixId, userInput: $userInput, loadAnswers: false) {
|
|
1205
|
+
__typename
|
|
1206
|
+
... on FixData {
|
|
1207
|
+
patch
|
|
1208
|
+
patchOriginalEncodingBase64
|
|
1209
|
+
questions {
|
|
1210
|
+
key
|
|
1211
|
+
name
|
|
1212
|
+
defaultValue
|
|
1213
|
+
value
|
|
1214
|
+
inputType
|
|
1215
|
+
options
|
|
1216
|
+
index
|
|
1217
|
+
extraContext {
|
|
1218
|
+
key
|
|
1219
|
+
value
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
extraContext {
|
|
1223
|
+
extraContext {
|
|
1224
|
+
key
|
|
1225
|
+
value
|
|
1226
|
+
}
|
|
1227
|
+
fixDescription
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1182
1231
|
}
|
|
1183
1232
|
`;
|
|
1184
1233
|
GetFixReportsByRepoUrlDocument = `
|
|
@@ -1499,7 +1548,8 @@ var init_getIssueType = __esm({
|
|
|
1499
1548
|
["MISSING_X_FRAME_OPTIONS" /* MissingXFrameOptions */]: "Missing X-Frame-Options Header",
|
|
1500
1549
|
["IMPROPER_VALIDATION_OF_ARRAY_INDEX" /* ImproperValidationOfArrayIndex */]: "Improper Validation of Array Index",
|
|
1501
1550
|
["INCORRECT_INTEGER_CONVERSION" /* IncorrectIntegerConversion */]: "Incorrect Integer Conversion",
|
|
1502
|
-
["IMPROPER_CERTIFICATE_VALIDATION" /* ImproperCertificateValidation */]: "Improper Certificate Validation"
|
|
1551
|
+
["IMPROPER_CERTIFICATE_VALIDATION" /* ImproperCertificateValidation */]: "Improper Certificate Validation",
|
|
1552
|
+
["OFTEN_MISUSED_BOOLEAN_GET_BOOLEAN" /* OftenMisusedBooleanGetBoolean */]: "Often Misused: Boolean.getBoolean()"
|
|
1503
1553
|
};
|
|
1504
1554
|
issueTypeZ = z.nativeEnum(IssueType_Enum);
|
|
1505
1555
|
getIssueTypeFriendlyString = (issueType) => {
|
|
@@ -3917,6 +3967,31 @@ var init_GitService = __esm({
|
|
|
3917
3967
|
throw new Error(errorMessage);
|
|
3918
3968
|
}
|
|
3919
3969
|
}
|
|
3970
|
+
/**
|
|
3971
|
+
* Reads `{ branch, commitSha }` for tracy event attribution. Detached-HEAD
|
|
3972
|
+
* (rebase, bisect, "open this commit") returns `branch: null` rather than
|
|
3973
|
+
* the literal string `"HEAD"` that `getCurrentBranch()` produces — that
|
|
3974
|
+
* literal would silently corrupt downstream branch dashboards.
|
|
3975
|
+
*
|
|
3976
|
+
* The two reads run in parallel so the wall-time cost is one `git`
|
|
3977
|
+
* round-trip rather than two. Never throws — failures resolve to nulls so
|
|
3978
|
+
* the daemon hot path can rely on a value, not an exception.
|
|
3979
|
+
*/
|
|
3980
|
+
async getCurrentRepoState() {
|
|
3981
|
+
const branchPromise = this.git.raw(["symbolic-ref", "--short", "-q", "HEAD"]).then((s) => {
|
|
3982
|
+
const trimmed = s.trim();
|
|
3983
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
3984
|
+
}).catch(() => null);
|
|
3985
|
+
const commitShaPromise = this.git.raw(["rev-parse", "HEAD"]).then((s) => {
|
|
3986
|
+
const trimmed = s.trim().toLowerCase();
|
|
3987
|
+
return /^[0-9a-f]{40}$/.test(trimmed) ? trimmed : null;
|
|
3988
|
+
}).catch(() => null);
|
|
3989
|
+
const [branch, commitSha] = await Promise.all([
|
|
3990
|
+
branchPromise,
|
|
3991
|
+
commitShaPromise
|
|
3992
|
+
]);
|
|
3993
|
+
return { branch, commitSha };
|
|
3994
|
+
}
|
|
3920
3995
|
/**
|
|
3921
3996
|
* Gets both the current commit hash and current branch name
|
|
3922
3997
|
*/
|
|
@@ -4725,7 +4800,8 @@ var fixDetailsData = {
|
|
|
4725
4800
|
["MISSING_X_FRAME_OPTIONS" /* MissingXFrameOptions */]: void 0,
|
|
4726
4801
|
["IMPROPER_VALIDATION_OF_ARRAY_INDEX" /* ImproperValidationOfArrayIndex */]: void 0,
|
|
4727
4802
|
["INCORRECT_INTEGER_CONVERSION" /* IncorrectIntegerConversion */]: void 0,
|
|
4728
|
-
["IMPROPER_CERTIFICATE_VALIDATION" /* ImproperCertificateValidation */]: void 0
|
|
4803
|
+
["IMPROPER_CERTIFICATE_VALIDATION" /* ImproperCertificateValidation */]: void 0,
|
|
4804
|
+
["OFTEN_MISUSED_BOOLEAN_GET_BOOLEAN" /* OftenMisusedBooleanGetBoolean */]: void 0
|
|
4729
4805
|
};
|
|
4730
4806
|
|
|
4731
4807
|
// src/features/analysis/scm/shared/src/commitDescriptionMarkup.ts
|
|
@@ -9280,6 +9356,52 @@ async function executeBatchGraphQL(octokit, owner, repo, config2) {
|
|
|
9280
9356
|
}
|
|
9281
9357
|
function getGithubSdk(params = {}) {
|
|
9282
9358
|
const octokit = getOctoKit(params);
|
|
9359
|
+
async function openPrWithFiles(params2) {
|
|
9360
|
+
const { owner, repo } = parseGithubOwnerAndRepo(params2.userRepoUrl);
|
|
9361
|
+
const { data: repository } = await octokit.rest.repos.get({ owner, repo });
|
|
9362
|
+
const defaultBranch = repository.default_branch;
|
|
9363
|
+
const baseSha = await octokit.rest.git.getRef({ owner, repo, ref: `heads/${defaultBranch}` }).then((r) => r.data.object.sha);
|
|
9364
|
+
await octokit.rest.git.createRef({
|
|
9365
|
+
owner,
|
|
9366
|
+
repo,
|
|
9367
|
+
ref: `refs/heads/${params2.branch}`,
|
|
9368
|
+
sha: baseSha
|
|
9369
|
+
});
|
|
9370
|
+
const tree = await octokit.rest.git.createTree({
|
|
9371
|
+
owner,
|
|
9372
|
+
repo,
|
|
9373
|
+
base_tree: baseSha,
|
|
9374
|
+
tree: params2.files.map((f) => ({
|
|
9375
|
+
path: f.path,
|
|
9376
|
+
mode: "100644",
|
|
9377
|
+
type: "blob",
|
|
9378
|
+
content: f.content
|
|
9379
|
+
}))
|
|
9380
|
+
});
|
|
9381
|
+
const commit = await octokit.rest.git.createCommit({
|
|
9382
|
+
owner,
|
|
9383
|
+
repo,
|
|
9384
|
+
message: params2.commitMessage ?? params2.title,
|
|
9385
|
+
tree: tree.data.sha,
|
|
9386
|
+
parents: [baseSha]
|
|
9387
|
+
});
|
|
9388
|
+
await octokit.rest.git.updateRef({
|
|
9389
|
+
owner,
|
|
9390
|
+
repo,
|
|
9391
|
+
ref: `heads/${params2.branch}`,
|
|
9392
|
+
sha: commit.data.sha
|
|
9393
|
+
});
|
|
9394
|
+
const pr = await octokit.rest.pulls.create({
|
|
9395
|
+
owner,
|
|
9396
|
+
repo,
|
|
9397
|
+
title: params2.title,
|
|
9398
|
+
head: params2.branch,
|
|
9399
|
+
...params2.headRepo ? { head_repo: params2.headRepo } : {},
|
|
9400
|
+
body: safeBody(params2.body, MAX_GH_PR_BODY_LENGTH),
|
|
9401
|
+
base: defaultBranch
|
|
9402
|
+
});
|
|
9403
|
+
return { pull_request_url: pr.data.html_url };
|
|
9404
|
+
}
|
|
9283
9405
|
return {
|
|
9284
9406
|
async postPrComment(params2) {
|
|
9285
9407
|
return octokit.request(POST_COMMENT_PATH, params2);
|
|
@@ -9576,86 +9698,26 @@ function getGithubSdk(params = {}) {
|
|
|
9576
9698
|
async createPr(params2) {
|
|
9577
9699
|
const { sourceRepoUrl, filesPaths, userRepoUrl, title, body } = params2;
|
|
9578
9700
|
const { owner: sourceOwner, repo: sourceRepo } = parseGithubOwnerAndRepo(sourceRepoUrl);
|
|
9579
|
-
const
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
9590
|
-
|
|
9591
|
-
|
|
9592
|
-
|
|
9593
|
-
|
|
9594
|
-
});
|
|
9595
|
-
const decodedContent = Buffer.from(
|
|
9596
|
-
// Check if file content exists and handle different response types
|
|
9597
|
-
typeof sourceFileContentResponse.data === "object" && !Array.isArray(sourceFileContentResponse.data) && "content" in sourceFileContentResponse.data && typeof sourceFileContentResponse.data.content === "string" ? sourceFileContentResponse.data.content : "",
|
|
9598
|
-
"base64"
|
|
9599
|
-
).toString("utf-8");
|
|
9600
|
-
const tree = [
|
|
9601
|
-
{
|
|
9602
|
-
path: sourceFilePath,
|
|
9603
|
-
mode: "100644",
|
|
9604
|
-
type: "blob",
|
|
9605
|
-
content: decodedContent
|
|
9606
|
-
}
|
|
9607
|
-
];
|
|
9608
|
-
if (secondFilePath) {
|
|
9609
|
-
const secondFileContentResponse = await octokit.rest.repos.getContent({
|
|
9610
|
-
owner: sourceOwner,
|
|
9611
|
-
repo: sourceRepo,
|
|
9612
|
-
path: `/${secondFilePath}`
|
|
9613
|
-
});
|
|
9614
|
-
const secondDecodedContent = Buffer.from(
|
|
9615
|
-
// Check if file content exists and handle different response types
|
|
9616
|
-
typeof secondFileContentResponse.data === "object" && !Array.isArray(secondFileContentResponse.data) && "content" in secondFileContentResponse.data && typeof secondFileContentResponse.data.content === "string" ? secondFileContentResponse.data.content : "",
|
|
9617
|
-
"base64"
|
|
9618
|
-
).toString("utf-8");
|
|
9619
|
-
tree.push({
|
|
9620
|
-
path: secondFilePath,
|
|
9621
|
-
mode: "100644",
|
|
9622
|
-
type: "blob",
|
|
9623
|
-
content: secondDecodedContent
|
|
9624
|
-
});
|
|
9625
|
-
}
|
|
9626
|
-
const createTreeResponse = await octokit.rest.git.createTree({
|
|
9627
|
-
owner,
|
|
9628
|
-
repo,
|
|
9629
|
-
base_tree: await octokit.rest.git.getRef({ owner, repo, ref: `heads/${defaultBranch}` }).then((response) => response.data.object.sha),
|
|
9630
|
-
tree
|
|
9631
|
-
});
|
|
9632
|
-
const createCommitResponse = await octokit.rest.git.createCommit({
|
|
9633
|
-
owner,
|
|
9634
|
-
repo,
|
|
9635
|
-
message: "Add new yaml file",
|
|
9636
|
-
tree: createTreeResponse.data.sha,
|
|
9637
|
-
parents: [
|
|
9638
|
-
await octokit.rest.git.getRef({ owner, repo, ref: `heads/${defaultBranch}` }).then((response) => response.data.object.sha)
|
|
9639
|
-
]
|
|
9640
|
-
});
|
|
9641
|
-
await octokit.rest.git.updateRef({
|
|
9642
|
-
owner,
|
|
9643
|
-
repo,
|
|
9644
|
-
ref: `heads/${newBranchName}`,
|
|
9645
|
-
sha: createCommitResponse.data.sha
|
|
9646
|
-
});
|
|
9647
|
-
const createPRResponse = await octokit.rest.pulls.create({
|
|
9648
|
-
owner,
|
|
9649
|
-
repo,
|
|
9701
|
+
const files = await Promise.all(
|
|
9702
|
+
filesPaths.map(async (filePath) => {
|
|
9703
|
+
const response = await octokit.rest.repos.getContent({
|
|
9704
|
+
owner: sourceOwner,
|
|
9705
|
+
repo: sourceRepo,
|
|
9706
|
+
path: `/${filePath}`
|
|
9707
|
+
});
|
|
9708
|
+
const content = typeof response.data === "object" && !Array.isArray(response.data) && "content" in response.data && typeof response.data.content === "string" ? Buffer.from(response.data.content, "base64").toString("utf-8") : "";
|
|
9709
|
+
return { path: filePath, content };
|
|
9710
|
+
})
|
|
9711
|
+
);
|
|
9712
|
+
return openPrWithFiles({
|
|
9713
|
+
userRepoUrl,
|
|
9714
|
+
files,
|
|
9715
|
+
branch: `mobb/workflow-${Date.now()}`,
|
|
9650
9716
|
title,
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
base: defaultBranch
|
|
9717
|
+
body,
|
|
9718
|
+
commitMessage: "Add new yaml file",
|
|
9719
|
+
headRepo: sourceRepo
|
|
9655
9720
|
});
|
|
9656
|
-
return {
|
|
9657
|
-
pull_request_url: createPRResponse.data.html_url
|
|
9658
|
-
};
|
|
9659
9721
|
},
|
|
9660
9722
|
async getGithubBranchList(repoUrl) {
|
|
9661
9723
|
const { owner, repo } = parseGithubOwnerAndRepo(repoUrl);
|
|
@@ -9666,6 +9728,35 @@ function getGithubSdk(params = {}) {
|
|
|
9666
9728
|
page: 1
|
|
9667
9729
|
});
|
|
9668
9730
|
},
|
|
9731
|
+
// T-500 — open a PR adding a single inline file. Used by the
|
|
9732
|
+
// openSecuritySkillPR resolver to deliver `.claude/skills/<slug>/SKILL.md`.
|
|
9733
|
+
async createPrWithContent(params2) {
|
|
9734
|
+
return openPrWithFiles({
|
|
9735
|
+
userRepoUrl: params2.userRepoUrl,
|
|
9736
|
+
files: [{ path: params2.filePath, content: params2.content }],
|
|
9737
|
+
branch: params2.branch,
|
|
9738
|
+
title: params2.title,
|
|
9739
|
+
body: params2.body
|
|
9740
|
+
});
|
|
9741
|
+
},
|
|
9742
|
+
// T-500 — best-effort branch cleanup for openSecuritySkillPR retry.
|
|
9743
|
+
// Swallows 422/404 so callers can call it unconditionally before
|
|
9744
|
+
// a fresh PR-creation attempt.
|
|
9745
|
+
async deleteBranchIfExists(params2) {
|
|
9746
|
+
const { owner, repo } = parseGithubOwnerAndRepo(params2.userRepoUrl);
|
|
9747
|
+
try {
|
|
9748
|
+
await octokit.rest.git.deleteRef({
|
|
9749
|
+
owner,
|
|
9750
|
+
repo,
|
|
9751
|
+
ref: `heads/${params2.branch}`
|
|
9752
|
+
});
|
|
9753
|
+
} catch (err) {
|
|
9754
|
+
if (err instanceof RequestError && (err.status === 422 || err.status === 404)) {
|
|
9755
|
+
return;
|
|
9756
|
+
}
|
|
9757
|
+
throw err;
|
|
9758
|
+
}
|
|
9759
|
+
},
|
|
9669
9760
|
async createPullRequest(options) {
|
|
9670
9761
|
const { owner, repo } = parseGithubOwnerAndRepo(options.repoUrl);
|
|
9671
9762
|
return octokit.rest.pulls.create({
|
|
@@ -10036,6 +10127,20 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
10036
10127
|
});
|
|
10037
10128
|
return { pull_request_url };
|
|
10038
10129
|
}
|
|
10130
|
+
// T-500 — sibling of `createPullRequestWithNewFile` that takes inline
|
|
10131
|
+
// content rather than reading from a source repo. Used by
|
|
10132
|
+
// openSecuritySkillPR to deliver `.claude/skills/<slug>/SKILL.md`.
|
|
10133
|
+
async createPullRequestWithInlineFile(params) {
|
|
10134
|
+
const { pull_request_url } = await this.githubSdk.createPrWithContent(params);
|
|
10135
|
+
return { pull_request_url };
|
|
10136
|
+
}
|
|
10137
|
+
// T-500 — used by the openSecuritySkillPR resolver to clean up a
|
|
10138
|
+
// branch left behind by a prior failed PR-creation attempt before
|
|
10139
|
+
// retrying. Swallows missing-branch responses; only real network
|
|
10140
|
+
// errors propagate.
|
|
10141
|
+
async deleteBranchIfExists(params) {
|
|
10142
|
+
return this.githubSdk.deleteBranchIfExists(params);
|
|
10143
|
+
}
|
|
10039
10144
|
async validateParams() {
|
|
10040
10145
|
return await githubValidateParams(this.url, this.accessToken);
|
|
10041
10146
|
}
|
|
@@ -14049,8 +14154,16 @@ var ADO_PAT_PATTERN = {
|
|
|
14049
14154
|
severity: "high",
|
|
14050
14155
|
validator: (match) => match.length >= 52 && match.length <= 100
|
|
14051
14156
|
};
|
|
14157
|
+
var DATADOG_APP_KEY_PATTERN = {
|
|
14158
|
+
type: "DATADOG_APP_KEY",
|
|
14159
|
+
regex: /\bddapp_[a-zA-Z0-9]{30,}\b/g,
|
|
14160
|
+
priority: 95,
|
|
14161
|
+
placeholder: "[DATADOG_APP_KEY_{n}]",
|
|
14162
|
+
description: "Datadog Application Key",
|
|
14163
|
+
severity: "high"
|
|
14164
|
+
};
|
|
14052
14165
|
var openRedaction = new OpenRedaction({
|
|
14053
|
-
customPatterns: [ADO_PAT_PATTERN],
|
|
14166
|
+
customPatterns: [ADO_PAT_PATTERN, DATADOG_APP_KEY_PATTERN],
|
|
14054
14167
|
patterns: [
|
|
14055
14168
|
// Core Personal Data
|
|
14056
14169
|
// Removed EMAIL - causes false positives in code/test snippets (e.g. --author="Eve Author <eve@example.com>")
|
|
@@ -14257,19 +14370,33 @@ var PromptItemZ = z27.object({
|
|
|
14257
14370
|
}).optional()
|
|
14258
14371
|
});
|
|
14259
14372
|
var PromptItemArrayZ = z27.array(PromptItemZ);
|
|
14260
|
-
|
|
14373
|
+
var NULL_REPO_STATE = {
|
|
14374
|
+
repositoryUrl: null,
|
|
14375
|
+
branch: null,
|
|
14376
|
+
commitSha: null
|
|
14377
|
+
};
|
|
14378
|
+
async function readRepoState(workingDir) {
|
|
14379
|
+
const dir = workingDir ?? process.cwd();
|
|
14380
|
+
let gitService;
|
|
14261
14381
|
try {
|
|
14262
|
-
|
|
14263
|
-
const isRepo = await gitService.isGitRepository();
|
|
14264
|
-
if (!isRepo) {
|
|
14265
|
-
return null;
|
|
14266
|
-
}
|
|
14267
|
-
const remoteUrl = await gitService.getRemoteUrl();
|
|
14268
|
-
const parsed = parseScmURL(remoteUrl);
|
|
14269
|
-
return parsed?.scmType && parsed.scmType !== "Unknown" ? remoteUrl : null;
|
|
14382
|
+
gitService = new GitService(dir);
|
|
14270
14383
|
} catch {
|
|
14271
|
-
return
|
|
14272
|
-
}
|
|
14384
|
+
return NULL_REPO_STATE;
|
|
14385
|
+
}
|
|
14386
|
+
const repoStatePromise = gitService.getCurrentRepoState().catch(() => ({ branch: null, commitSha: null }));
|
|
14387
|
+
const repositoryUrlPromise = gitService.getRemoteUrl().then((url) => {
|
|
14388
|
+
if (!url) return null;
|
|
14389
|
+
const parsed = parseScmURL(url);
|
|
14390
|
+
return parsed?.scmType && parsed.scmType !== "Unknown" ? url : null;
|
|
14391
|
+
}).catch(() => null);
|
|
14392
|
+
const [{ branch, commitSha }, repositoryUrl] = await Promise.all([
|
|
14393
|
+
repoStatePromise,
|
|
14394
|
+
repositoryUrlPromise
|
|
14395
|
+
]);
|
|
14396
|
+
return { repositoryUrl, branch, commitSha };
|
|
14397
|
+
}
|
|
14398
|
+
async function getRepositoryUrl(workingDir) {
|
|
14399
|
+
return (await readRepoState(workingDir)).repositoryUrl;
|
|
14273
14400
|
}
|
|
14274
14401
|
function getSystemInfo() {
|
|
14275
14402
|
let userName;
|
|
@@ -14602,7 +14729,7 @@ async function prepareAndSendTracyRecords(client, rawRecords, workingDir, option
|
|
|
14602
14729
|
const { computerName, userName } = getSystemInfo();
|
|
14603
14730
|
const defaultClientVersion = packageJson.version;
|
|
14604
14731
|
const shouldSanitize = options?.sanitize ?? true;
|
|
14605
|
-
const
|
|
14732
|
+
const defaults = workingDir != null ? await readRepoState(workingDir) : { repositoryUrl: null, branch: null, commitSha: null };
|
|
14606
14733
|
debug10(
|
|
14607
14734
|
"[step:sanitize] %s %d records",
|
|
14608
14735
|
shouldSanitize ? "Sanitizing" : "Serializing",
|
|
@@ -14622,7 +14749,9 @@ async function prepareAndSendTracyRecords(client, rawRecords, workingDir, option
|
|
|
14622
14749
|
const { rawData: _rawData, ...rest } = record;
|
|
14623
14750
|
results.push({
|
|
14624
14751
|
...rest,
|
|
14625
|
-
repositoryUrl: record.repositoryUrl ??
|
|
14752
|
+
repositoryUrl: record.repositoryUrl ?? defaults.repositoryUrl ?? void 0,
|
|
14753
|
+
branch: record.branch ?? defaults.branch ?? void 0,
|
|
14754
|
+
commitSha: record.commitSha ?? defaults.commitSha ?? void 0,
|
|
14626
14755
|
computerName,
|
|
14627
14756
|
userName,
|
|
14628
14757
|
clientVersion: record.clientVersion ?? defaultClientVersion
|
|
@@ -18760,6 +18889,8 @@ async function uploadContextRecords(opts) {
|
|
|
18760
18889
|
now,
|
|
18761
18890
|
platform: platform2,
|
|
18762
18891
|
repositoryUrl,
|
|
18892
|
+
branch,
|
|
18893
|
+
commitSha,
|
|
18763
18894
|
clientVersion,
|
|
18764
18895
|
onFileError,
|
|
18765
18896
|
onSkillError
|
|
@@ -18770,6 +18901,8 @@ async function uploadContextRecords(opts) {
|
|
|
18770
18901
|
const limit = pLimit7(UPLOAD_CONCURRENCY);
|
|
18771
18902
|
const extraFields = {
|
|
18772
18903
|
...repositoryUrl !== void 0 && { repositoryUrl },
|
|
18904
|
+
...branch !== void 0 && { branch },
|
|
18905
|
+
...commitSha !== void 0 && { commitSha },
|
|
18773
18906
|
...clientVersion !== void 0 && { clientVersion }
|
|
18774
18907
|
};
|
|
18775
18908
|
const tasks = [
|
|
@@ -18855,6 +18988,8 @@ async function runContextFileUploadPipeline(opts) {
|
|
|
18855
18988
|
uploadFieldsJSON,
|
|
18856
18989
|
keyPrefix,
|
|
18857
18990
|
repositoryUrl,
|
|
18991
|
+
branch,
|
|
18992
|
+
commitSha,
|
|
18858
18993
|
clientVersion,
|
|
18859
18994
|
submitRecords,
|
|
18860
18995
|
onFileError,
|
|
@@ -18877,6 +19012,8 @@ async function runContextFileUploadPipeline(opts) {
|
|
|
18877
19012
|
now,
|
|
18878
19013
|
platform: platform2,
|
|
18879
19014
|
repositoryUrl,
|
|
19015
|
+
branch,
|
|
19016
|
+
commitSha,
|
|
18880
19017
|
clientVersion,
|
|
18881
19018
|
onFileError,
|
|
18882
19019
|
onSkillError
|
|
@@ -19232,7 +19369,7 @@ function createLogger(config2) {
|
|
|
19232
19369
|
|
|
19233
19370
|
// src/features/claude_code/hook_logger.ts
|
|
19234
19371
|
var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
|
|
19235
|
-
var CLI_VERSION = true ? "1.4.
|
|
19372
|
+
var CLI_VERSION = true ? "1.4.12" : "unknown";
|
|
19236
19373
|
var NAMESPACE = "mobbdev-claude-code-hook-logs";
|
|
19237
19374
|
var claudeCodeVersion;
|
|
19238
19375
|
function buildDdTags() {
|
|
@@ -19699,6 +19836,7 @@ async function processTranscript(input, sessionStore, log2, maxEntries = DAEMON_
|
|
|
19699
19836
|
}
|
|
19700
19837
|
const cursorForModel = sessionStore.get(cursorKey);
|
|
19701
19838
|
let lastSeenModel = cursorForModel?.lastModel ?? null;
|
|
19839
|
+
const sampledRepoState = await readRepoState(input.cwd);
|
|
19702
19840
|
const records = entries.map((entry) => {
|
|
19703
19841
|
const { _recordId, ...rawEntry } = entry;
|
|
19704
19842
|
const message = rawEntry["message"];
|
|
@@ -19720,7 +19858,10 @@ async function processTranscript(input, sessionStore, log2, maxEntries = DAEMON_
|
|
|
19720
19858
|
recordId: _recordId,
|
|
19721
19859
|
recordTimestamp: entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
19722
19860
|
blameType: "CHAT" /* Chat */,
|
|
19723
|
-
rawData: rawEntry
|
|
19861
|
+
rawData: rawEntry,
|
|
19862
|
+
repositoryUrl: sampledRepoState.repositoryUrl ?? void 0,
|
|
19863
|
+
branch: sampledRepoState.branch,
|
|
19864
|
+
commitSha: sampledRepoState.commitSha
|
|
19724
19865
|
};
|
|
19725
19866
|
});
|
|
19726
19867
|
let totalRawDataBytes = 0;
|
|
@@ -20714,10 +20855,22 @@ var FixExtraContextResponseSchema = z33.object({
|
|
|
20714
20855
|
extraContext: z33.array(UnstructuredFixExtraContextSchema),
|
|
20715
20856
|
fixDescription: z33.string()
|
|
20716
20857
|
});
|
|
20858
|
+
var FixQuestionSchema = z33.object({
|
|
20859
|
+
__typename: z33.literal("FixQuestion").optional(),
|
|
20860
|
+
key: z33.string(),
|
|
20861
|
+
name: z33.string(),
|
|
20862
|
+
defaultValue: z33.string(),
|
|
20863
|
+
value: z33.string().nullable().optional(),
|
|
20864
|
+
inputType: z33.nativeEnum(FixQuestionInputType),
|
|
20865
|
+
options: z33.array(z33.string()),
|
|
20866
|
+
index: z33.number(),
|
|
20867
|
+
extraContext: z33.array(UnstructuredFixExtraContextSchema)
|
|
20868
|
+
});
|
|
20717
20869
|
var FixDataSchema = z33.object({
|
|
20718
20870
|
__typename: z33.literal("FixData"),
|
|
20719
20871
|
patch: z33.string(),
|
|
20720
20872
|
patchOriginalEncodingBase64: z33.string(),
|
|
20873
|
+
questions: z33.array(FixQuestionSchema),
|
|
20721
20874
|
extraContext: FixExtraContextResponseSchema
|
|
20722
20875
|
});
|
|
20723
20876
|
var GetFixNoFixErrorSchema = z33.object({
|
|
@@ -20730,6 +20883,7 @@ var McpFixSchema = z33.object({
|
|
|
20730
20883
|
// GraphQL uses `any` type for UUID
|
|
20731
20884
|
confidence: z33.number(),
|
|
20732
20885
|
safeIssueType: z33.string().nullable(),
|
|
20886
|
+
safeIssueLanguage: z33.string().nullable().optional(),
|
|
20733
20887
|
severityText: z33.string().nullable(),
|
|
20734
20888
|
gitBlameLogin: z33.string().nullable().optional(),
|
|
20735
20889
|
// Optional in GraphQL
|
|
@@ -20809,6 +20963,63 @@ var GetLatestReportByRepoUrlResponseSchema = z33.object({
|
|
|
20809
20963
|
expiredReport: z33.array(ExpiredReportSchema)
|
|
20810
20964
|
});
|
|
20811
20965
|
|
|
20966
|
+
// src/mcp/services/InteractiveFixFilter.ts
|
|
20967
|
+
var isInteractiveFix = (fix) => {
|
|
20968
|
+
if (fix.patchAndQuestions.__typename !== "FixData") {
|
|
20969
|
+
return false;
|
|
20970
|
+
}
|
|
20971
|
+
return fix.patchAndQuestions.questions.length > 0;
|
|
20972
|
+
};
|
|
20973
|
+
var ruleIdFor = (fix) => fix.safeIssueType ?? "UNKNOWN";
|
|
20974
|
+
var countByRule = (ruleIds) => {
|
|
20975
|
+
const counts = {};
|
|
20976
|
+
for (const ruleId of ruleIds) {
|
|
20977
|
+
counts[ruleId] = (counts[ruleId] ?? 0) + 1;
|
|
20978
|
+
}
|
|
20979
|
+
return counts;
|
|
20980
|
+
};
|
|
20981
|
+
var MOBB_MCP_DISABLE_INTERACTIVE_FILTER_DEFAULT = false;
|
|
20982
|
+
var isInteractiveRoutingDisabled = () => {
|
|
20983
|
+
const raw = process.env["MOBB_MCP_DISABLE_INTERACTIVE_FILTER"];
|
|
20984
|
+
if (!raw) return MOBB_MCP_DISABLE_INTERACTIVE_FILTER_DEFAULT;
|
|
20985
|
+
const normalized = raw.toLowerCase();
|
|
20986
|
+
return normalized === "1" || normalized === "true";
|
|
20987
|
+
};
|
|
20988
|
+
var partitionInteractiveFixes = (fixes) => {
|
|
20989
|
+
const disabled = isInteractiveRoutingDisabled();
|
|
20990
|
+
const applicableFixes = [];
|
|
20991
|
+
const interactiveFixes = [];
|
|
20992
|
+
const droppedInteractive = [];
|
|
20993
|
+
for (const fix of fixes) {
|
|
20994
|
+
if (isInteractiveFix(fix)) {
|
|
20995
|
+
if (disabled) {
|
|
20996
|
+
droppedInteractive.push(fix);
|
|
20997
|
+
} else {
|
|
20998
|
+
interactiveFixes.push(fix);
|
|
20999
|
+
}
|
|
21000
|
+
} else {
|
|
21001
|
+
applicableFixes.push(fix);
|
|
21002
|
+
}
|
|
21003
|
+
}
|
|
21004
|
+
if (disabled && droppedInteractive.length > 0) {
|
|
21005
|
+
logInfo(
|
|
21006
|
+
"[InteractiveFixFilter] Dropping interactive fixes (MOBB_MCP_DISABLE_INTERACTIVE_FILTER=true)",
|
|
21007
|
+
{
|
|
21008
|
+
totalFixes: fixes.length,
|
|
21009
|
+
droppedCount: droppedInteractive.length,
|
|
21010
|
+
droppedByRule: countByRule(droppedInteractive.map(ruleIdFor))
|
|
21011
|
+
}
|
|
21012
|
+
);
|
|
21013
|
+
} else if (interactiveFixes.length > 0) {
|
|
21014
|
+
logInfo("[InteractiveFixFilter] Routing interactive fixes to LLM", {
|
|
21015
|
+
totalFixes: fixes.length,
|
|
21016
|
+
interactiveCount: interactiveFixes.length,
|
|
21017
|
+
interactiveByRule: countByRule(interactiveFixes.map(ruleIdFor))
|
|
21018
|
+
});
|
|
21019
|
+
}
|
|
21020
|
+
return { applicableFixes, interactiveFixes };
|
|
21021
|
+
};
|
|
21022
|
+
|
|
20812
21023
|
// src/mcp/services/McpGQLClient.ts
|
|
20813
21024
|
var McpGQLClient = class extends GQLClient {
|
|
20814
21025
|
constructor(args) {
|
|
@@ -21091,7 +21302,7 @@ var McpGQLClient = class extends GQLClient {
|
|
|
21091
21302
|
reportData,
|
|
21092
21303
|
limit
|
|
21093
21304
|
}) {
|
|
21094
|
-
if (!reportData) return [];
|
|
21305
|
+
if (!reportData) return { applicableFixes: [], interactiveFixes: [] };
|
|
21095
21306
|
const reportMetadata = {
|
|
21096
21307
|
id: reportData.id,
|
|
21097
21308
|
organizationId: reportData.vulnerabilityReport?.project?.organizationId,
|
|
@@ -21127,7 +21338,12 @@ var McpGQLClient = class extends GQLClient {
|
|
|
21127
21338
|
fixMap.set(fix.id, fixWithUrl);
|
|
21128
21339
|
}
|
|
21129
21340
|
}
|
|
21130
|
-
|
|
21341
|
+
const merged = Array.from(fixMap.values());
|
|
21342
|
+
const { applicableFixes, interactiveFixes } = partitionInteractiveFixes(merged);
|
|
21343
|
+
return {
|
|
21344
|
+
applicableFixes: applicableFixes.slice(0, limit),
|
|
21345
|
+
interactiveFixes: interactiveFixes.slice(0, limit)
|
|
21346
|
+
};
|
|
21131
21347
|
}
|
|
21132
21348
|
async updateFixesDownloadStatus(fixIds) {
|
|
21133
21349
|
if (fixIds.length > 0) {
|
|
@@ -21231,14 +21447,15 @@ var McpGQLClient = class extends GQLClient {
|
|
|
21231
21447
|
reportCount: resp.fixReport?.length || 0
|
|
21232
21448
|
});
|
|
21233
21449
|
const latestReport = resp.fixReport?.[0] && FixReportSummarySchema.parse(resp.fixReport?.[0]);
|
|
21234
|
-
const
|
|
21450
|
+
const { applicableFixes, interactiveFixes } = this.mergeUserAndSystemFixes({
|
|
21235
21451
|
reportData: latestReport,
|
|
21236
21452
|
limit
|
|
21237
21453
|
});
|
|
21238
21454
|
return {
|
|
21239
21455
|
fixReport: latestReport ? {
|
|
21240
21456
|
...latestReport,
|
|
21241
|
-
fixes
|
|
21457
|
+
fixes: applicableFixes,
|
|
21458
|
+
interactiveFixes
|
|
21242
21459
|
} : null,
|
|
21243
21460
|
expiredReport: resp.expiredReport?.[0] || null
|
|
21244
21461
|
};
|
|
@@ -21308,13 +21525,17 @@ var McpGQLClient = class extends GQLClient {
|
|
|
21308
21525
|
return null;
|
|
21309
21526
|
}
|
|
21310
21527
|
const latestReport = FixReportSummarySchema.parse(res.fixReport?.[0]);
|
|
21311
|
-
const
|
|
21528
|
+
const { applicableFixes, interactiveFixes } = this.mergeUserAndSystemFixes({
|
|
21312
21529
|
reportData: latestReport,
|
|
21313
21530
|
limit
|
|
21314
21531
|
});
|
|
21315
|
-
logDebug("[GraphQL] GetReportFixes response parsed", {
|
|
21532
|
+
logDebug("[GraphQL] GetReportFixes response parsed", {
|
|
21533
|
+
fixes: applicableFixes,
|
|
21534
|
+
interactiveCount: interactiveFixes.length
|
|
21535
|
+
});
|
|
21316
21536
|
return {
|
|
21317
|
-
fixes,
|
|
21537
|
+
fixes: applicableFixes,
|
|
21538
|
+
interactiveFixes,
|
|
21318
21539
|
totalCount: res.fixReport?.[0]?.filteredFixesCount?.aggregate?.count || 0,
|
|
21319
21540
|
expiredReport: res.expiredReport?.[0] || null,
|
|
21320
21541
|
fixReport: res.fixReport?.[0] ? {
|
|
@@ -21332,6 +21553,36 @@ var McpGQLClient = class extends GQLClient {
|
|
|
21332
21553
|
throw e;
|
|
21333
21554
|
}
|
|
21334
21555
|
}
|
|
21556
|
+
/** Root getFix recomputes the patch; fix_by_pk.patchAndQuestions(userInput) does not (stale questions looked like cascading). */
|
|
21557
|
+
async getFixWithAnswers({
|
|
21558
|
+
fixId,
|
|
21559
|
+
answers
|
|
21560
|
+
}) {
|
|
21561
|
+
try {
|
|
21562
|
+
logDebug("[GraphQL] Calling getFixWithAnswers query", {
|
|
21563
|
+
fixId,
|
|
21564
|
+
answerCount: answers.length,
|
|
21565
|
+
userInput: answers
|
|
21566
|
+
});
|
|
21567
|
+
const resp = await this._clientSdk.getFixWithAnswers({
|
|
21568
|
+
fixId,
|
|
21569
|
+
userInput: answers
|
|
21570
|
+
});
|
|
21571
|
+
logDebug("[GraphQL] getFixWithAnswers successful", {
|
|
21572
|
+
fixId,
|
|
21573
|
+
responseTypename: resp.fixData?.__typename,
|
|
21574
|
+
remainingQuestionKeys: resp.fixData?.__typename === "FixData" ? resp.fixData.questions.map((q) => q.key) : void 0
|
|
21575
|
+
});
|
|
21576
|
+
return { fixData: resp.fixData ?? null };
|
|
21577
|
+
} catch (e) {
|
|
21578
|
+
logError("[GraphQL] getFixWithAnswers failed", {
|
|
21579
|
+
error: e,
|
|
21580
|
+
fixId,
|
|
21581
|
+
...this.getErrorContext()
|
|
21582
|
+
});
|
|
21583
|
+
throw e;
|
|
21584
|
+
}
|
|
21585
|
+
}
|
|
21335
21586
|
};
|
|
21336
21587
|
async function createAuthenticatedMcpGQLClient({
|
|
21337
21588
|
isBackgroundCall = false,
|
|
@@ -24374,6 +24625,7 @@ init_client_generates();
|
|
|
24374
24625
|
init_configs();
|
|
24375
24626
|
|
|
24376
24627
|
// src/mcp/core/prompts.ts
|
|
24628
|
+
init_client_generates();
|
|
24377
24629
|
init_configs();
|
|
24378
24630
|
function friendlyType(s) {
|
|
24379
24631
|
const withoutUnderscores = s.replace(/_/g, " ");
|
|
@@ -24382,6 +24634,133 @@ function friendlyType(s) {
|
|
|
24382
24634
|
}
|
|
24383
24635
|
var noFixesReturnedForParameters = `No fixes returned for the given offset and limit parameters.
|
|
24384
24636
|
`;
|
|
24637
|
+
var resolveQuestionText = ({
|
|
24638
|
+
fix,
|
|
24639
|
+
question
|
|
24640
|
+
}) => {
|
|
24641
|
+
const language = fix.safeIssueLanguage ?? void 0;
|
|
24642
|
+
const issueType = fix.safeIssueType ?? void 0;
|
|
24643
|
+
if (!language || !issueType) {
|
|
24644
|
+
return { content: question.name, description: "" };
|
|
24645
|
+
}
|
|
24646
|
+
const item = storedQuestionData_default[language]?.[issueType]?.[question.name];
|
|
24647
|
+
if (!item) {
|
|
24648
|
+
return { content: question.name, description: "" };
|
|
24649
|
+
}
|
|
24650
|
+
const args = question.extraContext.reduce(
|
|
24651
|
+
(acc, ctx) => {
|
|
24652
|
+
acc[ctx.key] = ctx.value;
|
|
24653
|
+
return acc;
|
|
24654
|
+
},
|
|
24655
|
+
{}
|
|
24656
|
+
);
|
|
24657
|
+
try {
|
|
24658
|
+
return {
|
|
24659
|
+
content: item.content(args) || question.name,
|
|
24660
|
+
description: item.description(args) || ""
|
|
24661
|
+
};
|
|
24662
|
+
} catch {
|
|
24663
|
+
return { content: question.name, description: "" };
|
|
24664
|
+
}
|
|
24665
|
+
};
|
|
24666
|
+
var formatQuestionInputContract = (question) => {
|
|
24667
|
+
switch (question.inputType) {
|
|
24668
|
+
case "SELECT" /* Select */:
|
|
24669
|
+
return `Pick exactly ONE of: ${question.options.map((o) => `\`${o}\``).join(", ")}`;
|
|
24670
|
+
case "NUMBER" /* Number */:
|
|
24671
|
+
return 'Provide a numeric string (e.g. "60").';
|
|
24672
|
+
case "TEXT" /* Text */:
|
|
24673
|
+
return "Provide a free-form string (or an empty string to accept the default).";
|
|
24674
|
+
}
|
|
24675
|
+
};
|
|
24676
|
+
var renderInteractiveFix = (fix, index) => {
|
|
24677
|
+
if (fix.patchAndQuestions.__typename !== "FixData") return "";
|
|
24678
|
+
const { questions, extraContext } = fix.patchAndQuestions;
|
|
24679
|
+
const vulnerabilityType = friendlyType(fix.safeIssueType ?? "Unknown");
|
|
24680
|
+
const questionsBlock = questions.slice().sort((a, b) => a.index - b.index).map((q, qIdx) => {
|
|
24681
|
+
const { content, description } = resolveQuestionText({ fix, question: q });
|
|
24682
|
+
const desc = description ? `
|
|
24683
|
+
*Why it matters:* ${description}` : "";
|
|
24684
|
+
const defaultLine = q.defaultValue ? `
|
|
24685
|
+
*Default if you don't decide:* \`${q.defaultValue}\`` : "";
|
|
24686
|
+
return `${qIdx + 1}. **\`${q.key}\`** \u2014 ${content}
|
|
24687
|
+
*Input:* ${formatQuestionInputContract(q)}${defaultLine}${desc}`;
|
|
24688
|
+
}).join("\n\n");
|
|
24689
|
+
return `### Interactive fix ${index + 1}: ${vulnerabilityType}
|
|
24690
|
+
|
|
24691
|
+
**Fix id:** \`${fix.id}\`
|
|
24692
|
+
**Description:** ${extraContext?.fixDescription || "Security vulnerability fix that requires answers before it can be tailored."}
|
|
24693
|
+
|
|
24694
|
+
**Questions you must answer before this fix can be applied:**
|
|
24695
|
+
|
|
24696
|
+
${questionsBlock}
|
|
24697
|
+
`;
|
|
24698
|
+
};
|
|
24699
|
+
var interactiveFixesPrompt = ({
|
|
24700
|
+
interactiveFixes,
|
|
24701
|
+
repositoryPath
|
|
24702
|
+
}) => {
|
|
24703
|
+
if (interactiveFixes.length === 0) return "";
|
|
24704
|
+
const fixesBlock = interactiveFixes.map((fix, idx) => renderInteractiveFix(fix, idx)).join("\n---\n");
|
|
24705
|
+
return `
|
|
24706
|
+
## Interactive fixes \u2014 decide from code, then apply or abstain
|
|
24707
|
+
|
|
24708
|
+
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.
|
|
24709
|
+
|
|
24710
|
+
### CRITICAL INSTRUCTIONS \u2014 READ CAREFULLY
|
|
24711
|
+
|
|
24712
|
+
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.
|
|
24713
|
+
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.
|
|
24714
|
+
3. **Confidence required.** Include in \`interactiveAnswers\` **only** fixes where your answers are justified by what you see in code (exact SELECT strings where applicable).
|
|
24715
|
+
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**.
|
|
24716
|
+
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.
|
|
24717
|
+
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.
|
|
24718
|
+
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.
|
|
24719
|
+
8. **After the tool call**, summarize: fixes applied with reasoning; fixes skipped (confidence/abstention/time-budget); tool failures.
|
|
24720
|
+
|
|
24721
|
+
### Decision heuristics for common questions
|
|
24722
|
+
|
|
24723
|
+
(Keys shown in snake_case \u2014 copy them verbatim from each fix's question block.)
|
|
24724
|
+
|
|
24725
|
+
- **\`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\`.
|
|
24726
|
+
- **\`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**.
|
|
24727
|
+
- **\`iframe_restrictions\`** \u2014 strict sandbox (\`""\`) unless embedded content clearly needs listed capabilities.
|
|
24728
|
+
|
|
24729
|
+
### How to call \`${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES}\`
|
|
24730
|
+
|
|
24731
|
+
Apply fixes you are confident about (subset allowed):
|
|
24732
|
+
|
|
24733
|
+
\`\`\`json
|
|
24734
|
+
{
|
|
24735
|
+
"path": "${repositoryPath}",
|
|
24736
|
+
"interactiveAnswers": [
|
|
24737
|
+
{
|
|
24738
|
+
"fixId": "<fix id from below>",
|
|
24739
|
+
"answers": [
|
|
24740
|
+
{ "key": "<question key, exactly as shown>", "value": "<your decided value>" }
|
|
24741
|
+
]
|
|
24742
|
+
}
|
|
24743
|
+
]
|
|
24744
|
+
}
|
|
24745
|
+
\`\`\`
|
|
24746
|
+
|
|
24747
|
+
Explicit abstention \u2014 skip **all** interactive fixes without rescanning:
|
|
24748
|
+
|
|
24749
|
+
\`\`\`json
|
|
24750
|
+
{
|
|
24751
|
+
"path": "${repositoryPath}",
|
|
24752
|
+
"interactiveAnswers": []
|
|
24753
|
+
}
|
|
24754
|
+
\`\`\`
|
|
24755
|
+
|
|
24756
|
+
${fixesBlock}
|
|
24757
|
+
`;
|
|
24758
|
+
};
|
|
24759
|
+
var interactiveAnswersAbstainAllToolResponse = `## Interactive fixes \u2014 none applied
|
|
24760
|
+
|
|
24761
|
+
\`interactiveAnswers\` was an empty array: **no** tailored patches were requested and **no** scan was run.
|
|
24762
|
+
|
|
24763
|
+
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.`;
|
|
24385
24764
|
var noFixesReturnedForParametersWithGuidance = ({
|
|
24386
24765
|
offset,
|
|
24387
24766
|
limit,
|
|
@@ -24440,9 +24819,13 @@ var applyFixesPrompt = ({
|
|
|
24440
24819
|
currentTool,
|
|
24441
24820
|
offset,
|
|
24442
24821
|
limit,
|
|
24443
|
-
gqlClient
|
|
24822
|
+
gqlClient,
|
|
24823
|
+
hasInteractiveFixes = false
|
|
24444
24824
|
}) => {
|
|
24445
24825
|
if (fixes.length === 0) {
|
|
24826
|
+
if (hasInteractiveFixes) {
|
|
24827
|
+
return "";
|
|
24828
|
+
}
|
|
24446
24829
|
if (totalCount > 0) {
|
|
24447
24830
|
return noFixesReturnedForParametersWithGuidance({
|
|
24448
24831
|
offset,
|
|
@@ -24628,11 +25011,17 @@ var fixesFoundPrompt = ({
|
|
|
24628
25011
|
fixReport,
|
|
24629
25012
|
offset,
|
|
24630
25013
|
limit,
|
|
24631
|
-
gqlClient
|
|
25014
|
+
gqlClient,
|
|
25015
|
+
interactiveFixes = [],
|
|
25016
|
+
repositoryPath
|
|
24632
25017
|
}) => {
|
|
24633
25018
|
const totalFixes = fixReport.filteredFixesCount.aggregate?.count || 0;
|
|
25019
|
+
const interactiveBlock = interactiveFixesPrompt({
|
|
25020
|
+
interactiveFixes,
|
|
25021
|
+
repositoryPath
|
|
25022
|
+
});
|
|
24634
25023
|
if (totalFixes === 0) {
|
|
24635
|
-
return noFixesAvailablePrompt;
|
|
25024
|
+
return noFixesAvailablePrompt + interactiveBlock;
|
|
24636
25025
|
}
|
|
24637
25026
|
const criticalFixes = fixReport.CRITICAL?.aggregate?.count || 0;
|
|
24638
25027
|
const highFixes = fixReport.HIGH?.aggregate?.count || 0;
|
|
@@ -24674,8 +25063,9 @@ ${applyFixesPrompt({
|
|
|
24674
25063
|
currentTool: MCP_TOOL_FETCH_AVAILABLE_FIXES,
|
|
24675
25064
|
offset,
|
|
24676
25065
|
limit,
|
|
24677
|
-
gqlClient
|
|
24678
|
-
|
|
25066
|
+
gqlClient,
|
|
25067
|
+
hasInteractiveFixes: interactiveFixes.length > 0
|
|
25068
|
+
})}${interactiveBlock}`;
|
|
24679
25069
|
};
|
|
24680
25070
|
var nextStepsPrompt = ({ scannedFiles }) => `
|
|
24681
25071
|
### \u{1F4C1} Scanned Files
|
|
@@ -24721,10 +25111,16 @@ var fixesPrompt = ({
|
|
|
24721
25111
|
offset,
|
|
24722
25112
|
scannedFiles,
|
|
24723
25113
|
limit,
|
|
24724
|
-
gqlClient
|
|
25114
|
+
gqlClient,
|
|
25115
|
+
interactiveFixes = [],
|
|
25116
|
+
repositoryPath
|
|
24725
25117
|
}) => {
|
|
25118
|
+
const interactiveBlock = interactiveFixesPrompt({
|
|
25119
|
+
interactiveFixes,
|
|
25120
|
+
repositoryPath
|
|
25121
|
+
});
|
|
24726
25122
|
if (totalCount === 0) {
|
|
24727
|
-
return noFixesFoundPrompt({ scannedFiles });
|
|
25123
|
+
return noFixesFoundPrompt({ scannedFiles }) + interactiveBlock;
|
|
24728
25124
|
}
|
|
24729
25125
|
const shownCount = fixes.length;
|
|
24730
25126
|
const nextOffset = offset + shownCount;
|
|
@@ -24740,9 +25136,10 @@ ${applyFixesPrompt({
|
|
|
24740
25136
|
currentTool: MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES,
|
|
24741
25137
|
offset,
|
|
24742
25138
|
limit,
|
|
24743
|
-
gqlClient
|
|
25139
|
+
gqlClient,
|
|
25140
|
+
hasInteractiveFixes: interactiveFixes.length > 0
|
|
24744
25141
|
})}
|
|
24745
|
-
|
|
25142
|
+
${interactiveBlock}
|
|
24746
25143
|
${nextStepsPrompt({ scannedFiles })}
|
|
24747
25144
|
`;
|
|
24748
25145
|
};
|
|
@@ -24815,7 +25212,9 @@ For assistance:
|
|
|
24815
25212
|
var freshFixesPrompt = ({
|
|
24816
25213
|
fixes,
|
|
24817
25214
|
limit,
|
|
24818
|
-
gqlClient
|
|
25215
|
+
gqlClient,
|
|
25216
|
+
interactiveFixes = [],
|
|
25217
|
+
repositoryPath
|
|
24819
25218
|
}) => {
|
|
24820
25219
|
return `Here are the fresh fixes to the vulnerabilities discovered by Mobb MCP
|
|
24821
25220
|
|
|
@@ -24828,8 +25227,10 @@ ${applyFixesPrompt({
|
|
|
24828
25227
|
currentTool: MCP_TOOL_FETCH_AVAILABLE_FIXES,
|
|
24829
25228
|
offset: 0,
|
|
24830
25229
|
limit,
|
|
24831
|
-
gqlClient
|
|
25230
|
+
gqlClient,
|
|
25231
|
+
hasInteractiveFixes: interactiveFixes.length > 0
|
|
24832
25232
|
})}
|
|
25233
|
+
${interactiveFixesPrompt({ interactiveFixes, repositoryPath })}
|
|
24833
25234
|
`;
|
|
24834
25235
|
};
|
|
24835
25236
|
function extractTargetFileFromPatch(patch) {
|
|
@@ -24848,7 +25249,9 @@ function formatSeverity(severityText, severityValue) {
|
|
|
24848
25249
|
}
|
|
24849
25250
|
var appliedFixesSummaryPrompt = ({
|
|
24850
25251
|
fixes,
|
|
24851
|
-
gqlClient
|
|
25252
|
+
gqlClient,
|
|
25253
|
+
interactiveFixes = [],
|
|
25254
|
+
repositoryPath
|
|
24852
25255
|
}) => {
|
|
24853
25256
|
const fixIds = fixes.map((fix) => fix.id);
|
|
24854
25257
|
void gqlClient.updateFixesDownloadStatus(fixIds);
|
|
@@ -24883,11 +25286,11 @@ ${fixes.map((fix, index) => {
|
|
|
24883
25286
|
${continuousMonitoringSection}
|
|
24884
25287
|
|
|
24885
25288
|
${autoFixSettingsSection}
|
|
24886
|
-
|
|
25289
|
+
${interactiveFixesPrompt({ interactiveFixes, repositoryPath })}
|
|
24887
25290
|
## \u{1F4CB} Next Steps
|
|
24888
25291
|
|
|
24889
25292
|
1. **Review the changes** - Check the modified files to understand what was fixed
|
|
24890
|
-
2. **Test your application** - Ensure the fixes don't break existing functionality
|
|
25293
|
+
2. **Test your application** - Ensure the fixes don't break existing functionality
|
|
24891
25294
|
3. **Commit the changes** - Add and commit the security fixes to your repository
|
|
24892
25295
|
4. **Continue coding** - Mobb will keep protecting your code automatically
|
|
24893
25296
|
|
|
@@ -25790,6 +26193,7 @@ var LocalMobbFolderService = class {
|
|
|
25790
26193
|
};
|
|
25791
26194
|
|
|
25792
26195
|
// src/mcp/services/PatchApplicationService.ts
|
|
26196
|
+
init_client_generates();
|
|
25793
26197
|
init_configs();
|
|
25794
26198
|
import {
|
|
25795
26199
|
existsSync as existsSync6,
|
|
@@ -26355,7 +26759,8 @@ var PatchApplicationService = class {
|
|
|
26355
26759
|
repositoryPath,
|
|
26356
26760
|
scanStartTime,
|
|
26357
26761
|
gqlClient,
|
|
26358
|
-
scanContext
|
|
26762
|
+
scanContext,
|
|
26763
|
+
downloadSource = "AUTO_MVS" /* AutoMvs */
|
|
26359
26764
|
}) {
|
|
26360
26765
|
const appliedFixes = [];
|
|
26361
26766
|
const failedFixes = [];
|
|
@@ -26414,20 +26819,26 @@ var PatchApplicationService = class {
|
|
|
26414
26819
|
if (appliedFixes.length > 0 && gqlClient) {
|
|
26415
26820
|
try {
|
|
26416
26821
|
const appliedFixIds = appliedFixes.map((fix) => fix.id).filter(Boolean);
|
|
26417
|
-
|
|
26822
|
+
if (downloadSource === "MCP" /* Mcp */) {
|
|
26823
|
+
await gqlClient.updateFixesDownloadStatus(appliedFixIds);
|
|
26824
|
+
} else {
|
|
26825
|
+
await gqlClient.updateAutoAppliedFixesStatus(appliedFixIds);
|
|
26826
|
+
}
|
|
26418
26827
|
logDebug(
|
|
26419
|
-
`[${scanContext}] Successfully updated download status for
|
|
26828
|
+
`[${scanContext}] Successfully updated download status for applied fixes`,
|
|
26420
26829
|
{
|
|
26421
26830
|
appliedFixIds,
|
|
26422
|
-
count: appliedFixIds.length
|
|
26831
|
+
count: appliedFixIds.length,
|
|
26832
|
+
downloadSource
|
|
26423
26833
|
}
|
|
26424
26834
|
);
|
|
26425
26835
|
} catch (error) {
|
|
26426
26836
|
logError(
|
|
26427
|
-
`[${scanContext}] Failed to update download status for
|
|
26837
|
+
`[${scanContext}] Failed to update download status for applied fixes`,
|
|
26428
26838
|
{
|
|
26429
26839
|
error: error instanceof Error ? error.message : String(error),
|
|
26430
|
-
appliedFixCount: appliedFixes.length
|
|
26840
|
+
appliedFixCount: appliedFixes.length,
|
|
26841
|
+
downloadSource
|
|
26431
26842
|
}
|
|
26432
26843
|
);
|
|
26433
26844
|
}
|
|
@@ -27169,6 +27580,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
27169
27580
|
__publicField(this, "path", "");
|
|
27170
27581
|
__publicField(this, "filesLastScanned", {});
|
|
27171
27582
|
__publicField(this, "freshFixes", []);
|
|
27583
|
+
__publicField(this, "interactiveFixes", []);
|
|
27172
27584
|
__publicField(this, "reportedFixes", []);
|
|
27173
27585
|
__publicField(this, "intervalId", null);
|
|
27174
27586
|
__publicField(this, "isInitialScanComplete", false);
|
|
@@ -27190,6 +27602,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
27190
27602
|
reset() {
|
|
27191
27603
|
this.filesLastScanned = {};
|
|
27192
27604
|
this.freshFixes = [];
|
|
27605
|
+
this.interactiveFixes = [];
|
|
27193
27606
|
this.reportedFixes = [];
|
|
27194
27607
|
this.hasAuthenticationFailed = false;
|
|
27195
27608
|
this.fullScanPathsScanned = configStore.get("fullScanPathsScanned") || [];
|
|
@@ -27275,6 +27688,16 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
27275
27688
|
const newFixes = fixes?.fixes?.filter(
|
|
27276
27689
|
(fix) => !this.isFixAlreadyReported(fix)
|
|
27277
27690
|
);
|
|
27691
|
+
const newInteractiveFixes = fixes?.interactiveFixes?.filter(
|
|
27692
|
+
(fix) => !this.isFixAlreadyReported(fix)
|
|
27693
|
+
) ?? [];
|
|
27694
|
+
if (newInteractiveFixes.length > 0) {
|
|
27695
|
+
this.interactiveFixes.push(...newInteractiveFixes);
|
|
27696
|
+
logInfo(
|
|
27697
|
+
`[${scanContext}] Buffered ${newInteractiveFixes.length} interactive fixes for next response`,
|
|
27698
|
+
{ totalBuffered: this.interactiveFixes.length }
|
|
27699
|
+
);
|
|
27700
|
+
}
|
|
27278
27701
|
logInfo(
|
|
27279
27702
|
`[${scanContext}] Security fixes retrieved, total: ${fixes?.fixes?.length || 0}, new: ${newFixes?.length || 0}`
|
|
27280
27703
|
);
|
|
@@ -27704,7 +28127,9 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
27704
28127
|
return freshFixesPrompt({
|
|
27705
28128
|
fixes: freshFixes,
|
|
27706
28129
|
limit: MCP_DEFAULT_LIMIT,
|
|
27707
|
-
gqlClient: this.gqlClient
|
|
28130
|
+
gqlClient: this.gqlClient,
|
|
28131
|
+
interactiveFixes: this.interactiveFixes.splice(0, MCP_DEFAULT_LIMIT),
|
|
28132
|
+
repositoryPath: this.path
|
|
27708
28133
|
});
|
|
27709
28134
|
}
|
|
27710
28135
|
logInfo(`[${scanContext}] No fresh fixes to report`);
|
|
@@ -27718,7 +28143,9 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
27718
28143
|
);
|
|
27719
28144
|
return appliedFixesSummaryPrompt({
|
|
27720
28145
|
fixes: appliedFixesToShow,
|
|
27721
|
-
gqlClient: this.gqlClient
|
|
28146
|
+
gqlClient: this.gqlClient,
|
|
28147
|
+
interactiveFixes: this.interactiveFixes.splice(0, MCP_DEFAULT_LIMIT),
|
|
28148
|
+
repositoryPath: this.path
|
|
27722
28149
|
});
|
|
27723
28150
|
}
|
|
27724
28151
|
logInfo(`[${scanContext}] No applied fixes to report`);
|
|
@@ -27825,6 +28252,7 @@ var _FetchAvailableFixesService = class _FetchAvailableFixesService {
|
|
|
27825
28252
|
}
|
|
27826
28253
|
async checkForAvailableFixes({
|
|
27827
28254
|
repoUrl,
|
|
28255
|
+
repositoryPath,
|
|
27828
28256
|
limit = MCP_DEFAULT_LIMIT,
|
|
27829
28257
|
offset,
|
|
27830
28258
|
fileFilter
|
|
@@ -27861,7 +28289,9 @@ var _FetchAvailableFixesService = class _FetchAvailableFixesService {
|
|
|
27861
28289
|
fixReport,
|
|
27862
28290
|
offset: effectiveOffset,
|
|
27863
28291
|
limit,
|
|
27864
|
-
gqlClient
|
|
28292
|
+
gqlClient,
|
|
28293
|
+
interactiveFixes: fixReport.interactiveFixes ?? [],
|
|
28294
|
+
repositoryPath
|
|
27865
28295
|
});
|
|
27866
28296
|
this.currentOffset = effectiveOffset + (fixReport.fixes?.length || 0);
|
|
27867
28297
|
return prompt;
|
|
@@ -28003,6 +28433,7 @@ Call this tool instead of ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES} when you only
|
|
|
28003
28433
|
}
|
|
28004
28434
|
const fixResult = await this.availableFixesService.checkForAvailableFixes({
|
|
28005
28435
|
repoUrl: originUrl,
|
|
28436
|
+
repositoryPath: path37,
|
|
28006
28437
|
limit: args.limit,
|
|
28007
28438
|
offset: args.offset,
|
|
28008
28439
|
fileFilter: actualFileFilter
|
|
@@ -28106,6 +28537,7 @@ import z45 from "zod";
|
|
|
28106
28537
|
init_configs();
|
|
28107
28538
|
|
|
28108
28539
|
// src/mcp/tools/scanAndFixVulnerabilities/ScanAndFixVulnerabilitiesService.ts
|
|
28540
|
+
init_client_generates();
|
|
28109
28541
|
init_configs();
|
|
28110
28542
|
var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService {
|
|
28111
28543
|
constructor() {
|
|
@@ -28196,7 +28628,9 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
|
|
|
28196
28628
|
offset: effectiveOffset,
|
|
28197
28629
|
scannedFiles: [...fileList],
|
|
28198
28630
|
limit: effectiveLimit,
|
|
28199
|
-
gqlClient: this.gqlClient
|
|
28631
|
+
gqlClient: this.gqlClient,
|
|
28632
|
+
interactiveFixes: fixes.interactiveFixes,
|
|
28633
|
+
repositoryPath
|
|
28200
28634
|
});
|
|
28201
28635
|
this.currentOffset = effectiveOffset + (fixes.fixes?.length || 0);
|
|
28202
28636
|
return prompt;
|
|
@@ -28240,12 +28674,165 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
|
|
|
28240
28674
|
logDebug(`${fixes?.fixes?.length} fixes retrieved`);
|
|
28241
28675
|
return {
|
|
28242
28676
|
fixes: fixes?.fixes || [],
|
|
28243
|
-
totalCount: fixes?.totalCount || 0
|
|
28677
|
+
totalCount: fixes?.totalCount || 0,
|
|
28678
|
+
interactiveFixes: fixes?.interactiveFixes || []
|
|
28244
28679
|
};
|
|
28245
28680
|
}
|
|
28681
|
+
/** Applies patches from interactiveAnswers only (no scan). */
|
|
28682
|
+
async applyInteractiveAnswers({
|
|
28683
|
+
interactiveAnswers,
|
|
28684
|
+
repositoryPath
|
|
28685
|
+
}) {
|
|
28686
|
+
this.gqlClient = await this.initializeGqlClient();
|
|
28687
|
+
logInfo(
|
|
28688
|
+
`Applying ${interactiveAnswers.length} interactive fix(es) with LLM-supplied answers`,
|
|
28689
|
+
{ repositoryPath }
|
|
28690
|
+
);
|
|
28691
|
+
const applied = [];
|
|
28692
|
+
const failed = [];
|
|
28693
|
+
const skipped = [];
|
|
28694
|
+
for (const entry of interactiveAnswers) {
|
|
28695
|
+
try {
|
|
28696
|
+
const { fixData } = await this.gqlClient.getFixWithAnswers({
|
|
28697
|
+
fixId: entry.fixId,
|
|
28698
|
+
answers: entry.answers
|
|
28699
|
+
});
|
|
28700
|
+
if (!fixData) {
|
|
28701
|
+
failed.push({
|
|
28702
|
+
fixId: entry.fixId,
|
|
28703
|
+
reason: "Fix not found on the server (may have expired)"
|
|
28704
|
+
});
|
|
28705
|
+
continue;
|
|
28706
|
+
}
|
|
28707
|
+
if (fixData.__typename !== "FixData") {
|
|
28708
|
+
failed.push({
|
|
28709
|
+
fixId: entry.fixId,
|
|
28710
|
+
reason: `Backend returned ${fixData.__typename} \u2014 could not produce a patch with the supplied answers`
|
|
28711
|
+
});
|
|
28712
|
+
continue;
|
|
28713
|
+
}
|
|
28714
|
+
if (!fixData.patch) {
|
|
28715
|
+
failed.push({
|
|
28716
|
+
fixId: entry.fixId,
|
|
28717
|
+
reason: "Backend returned FixData with no patch \u2014 answers did not yield an applicable fix"
|
|
28718
|
+
});
|
|
28719
|
+
continue;
|
|
28720
|
+
}
|
|
28721
|
+
const sentByKey = new Map(entry.answers.map((a) => [a.key, a.value]));
|
|
28722
|
+
const invalidSelectAnswers = [];
|
|
28723
|
+
for (const q of fixData.questions) {
|
|
28724
|
+
if (q.inputType !== "SELECT" /* Select */) continue;
|
|
28725
|
+
const sentValue = sentByKey.get(q.key);
|
|
28726
|
+
if (sentValue === void 0) continue;
|
|
28727
|
+
if (!q.options.includes(sentValue)) {
|
|
28728
|
+
invalidSelectAnswers.push({
|
|
28729
|
+
key: q.key,
|
|
28730
|
+
sentValue,
|
|
28731
|
+
options: [...q.options]
|
|
28732
|
+
});
|
|
28733
|
+
}
|
|
28734
|
+
}
|
|
28735
|
+
if (invalidSelectAnswers.length > 0) {
|
|
28736
|
+
skipped.push({
|
|
28737
|
+
fixId: entry.fixId,
|
|
28738
|
+
invalidSelectAnswers
|
|
28739
|
+
});
|
|
28740
|
+
continue;
|
|
28741
|
+
}
|
|
28742
|
+
const newPendingKeys = fixData.questions.map((q) => q.key).filter((k) => !sentByKey.has(k));
|
|
28743
|
+
const mcpFix = McpFixSchema.parse({
|
|
28744
|
+
__typename: "fix",
|
|
28745
|
+
id: entry.fixId,
|
|
28746
|
+
confidence: 0,
|
|
28747
|
+
safeIssueType: null,
|
|
28748
|
+
safeIssueLanguage: null,
|
|
28749
|
+
severityText: null,
|
|
28750
|
+
severityValue: null,
|
|
28751
|
+
vulnerabilityReportIssues: [],
|
|
28752
|
+
patchAndQuestions: fixData
|
|
28753
|
+
});
|
|
28754
|
+
const result = await PatchApplicationService.applyFixes({
|
|
28755
|
+
fixes: [mcpFix],
|
|
28756
|
+
repositoryPath,
|
|
28757
|
+
gqlClient: this.gqlClient,
|
|
28758
|
+
scanContext: ScanContext.USER_REQUEST,
|
|
28759
|
+
downloadSource: "MCP" /* Mcp */
|
|
28760
|
+
});
|
|
28761
|
+
if (result.appliedFixes.length > 0) {
|
|
28762
|
+
const targetFile = extractTargetFile(fixData.patch) ?? "unknown file";
|
|
28763
|
+
applied.push({
|
|
28764
|
+
fixId: entry.fixId,
|
|
28765
|
+
targetFile,
|
|
28766
|
+
newPendingKeys
|
|
28767
|
+
});
|
|
28768
|
+
} else {
|
|
28769
|
+
failed.push({
|
|
28770
|
+
fixId: entry.fixId,
|
|
28771
|
+
reason: result.failedFixes[0]?.error ?? "patch application failed"
|
|
28772
|
+
});
|
|
28773
|
+
}
|
|
28774
|
+
} catch (error) {
|
|
28775
|
+
failed.push({
|
|
28776
|
+
fixId: entry.fixId,
|
|
28777
|
+
reason: error.message
|
|
28778
|
+
});
|
|
28779
|
+
}
|
|
28780
|
+
}
|
|
28781
|
+
return formatApplyAnswersSummary({ applied, failed, skipped });
|
|
28782
|
+
}
|
|
28246
28783
|
};
|
|
28247
28784
|
__publicField(_ScanAndFixVulnerabilitiesService, "instance");
|
|
28248
28785
|
var ScanAndFixVulnerabilitiesService = _ScanAndFixVulnerabilitiesService;
|
|
28786
|
+
function extractTargetFile(patch) {
|
|
28787
|
+
const match = patch.match(/^\+\+\+ b\/(.+)$/m);
|
|
28788
|
+
return match?.[1] ?? null;
|
|
28789
|
+
}
|
|
28790
|
+
function formatApplyAnswersSummary({
|
|
28791
|
+
applied,
|
|
28792
|
+
failed,
|
|
28793
|
+
skipped
|
|
28794
|
+
}) {
|
|
28795
|
+
const sections = [];
|
|
28796
|
+
if (applied.length > 0) {
|
|
28797
|
+
sections.push(
|
|
28798
|
+
`## \u2705 Applied ${applied.length} fix${applied.length === 1 ? "" : "es"}
|
|
28799
|
+
|
|
28800
|
+
` + applied.map((a) => {
|
|
28801
|
+
const hint = a.newPendingKeys.length > 0 ? `
|
|
28802
|
+
\u26A0\uFE0F The backend returned additional question key(s) we didn't send: [${a.newPendingKeys.map((k) => `\`${k}\``).join(
|
|
28803
|
+
", "
|
|
28804
|
+
)}]. 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.` : "";
|
|
28805
|
+
return `- **\`${a.fixId}\`** \u2192 \`${a.targetFile}\`${hint}`;
|
|
28806
|
+
}).join("\n")
|
|
28807
|
+
);
|
|
28808
|
+
}
|
|
28809
|
+
if (skipped.length > 0) {
|
|
28810
|
+
sections.push(
|
|
28811
|
+
`## \u23ED\uFE0F Skipped ${skipped.length} fix${skipped.length === 1 ? "" : "es"} \u2014 invalid SELECT answer value(s)
|
|
28812
|
+
|
|
28813
|
+
` + skipped.map((s) => {
|
|
28814
|
+
const detail = s.invalidSelectAnswers.map(
|
|
28815
|
+
(a) => `\`${a.key}\` got \`"${a.sentValue}"\` \u2014 allowed options: [${a.options.map((o) => `\`"${o}"\``).join(", ")}]`
|
|
28816
|
+
).join("; ");
|
|
28817
|
+
return `- **\`${s.fixId}\`** \u2014 ${detail}
|
|
28818
|
+
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.`;
|
|
28819
|
+
}).join("\n") + `
|
|
28820
|
+
|
|
28821
|
+
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.`
|
|
28822
|
+
);
|
|
28823
|
+
}
|
|
28824
|
+
if (failed.length > 0) {
|
|
28825
|
+
sections.push(
|
|
28826
|
+
`## \u274C Failed
|
|
28827
|
+
|
|
28828
|
+
` + failed.map((f) => `- **\`${f.fixId}\`** \u2014 ${f.reason}`).join("\n")
|
|
28829
|
+
);
|
|
28830
|
+
}
|
|
28831
|
+
if (sections.length === 0) {
|
|
28832
|
+
return "No fixes were processed.";
|
|
28833
|
+
}
|
|
28834
|
+
return sections.join("\n\n");
|
|
28835
|
+
}
|
|
28249
28836
|
|
|
28250
28837
|
// src/mcp/tools/scanAndFixVulnerabilities/ScanAndFixVulnerabilitiesTool.ts
|
|
28251
28838
|
var ScanAndFixVulnerabilitiesTool = class extends BaseTool {
|
|
@@ -28253,13 +28840,23 @@ var ScanAndFixVulnerabilitiesTool = class extends BaseTool {
|
|
|
28253
28840
|
super();
|
|
28254
28841
|
__publicField(this, "name", MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES);
|
|
28255
28842
|
__publicField(this, "displayName", "Scan and Fix Vulnerabilities");
|
|
28256
|
-
|
|
28257
|
-
|
|
28843
|
+
__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.
|
|
28844
|
+
|
|
28845
|
+
Two modes of operation:
|
|
28846
|
+
|
|
28847
|
+
A) SCAN MODE (default \u2014 interactiveAnswers omitted)
|
|
28848
|
+
- Scans changed/recent files at "path"
|
|
28849
|
+
- Auto-applies fixes that need no input
|
|
28850
|
+
- Returns "Interactive fix" entries for fixes that need decisions; you (the AI) decide answers from the surrounding code
|
|
28851
|
+
|
|
28852
|
+
B) APPLY-WITH-ANSWERS MODE (interactiveAnswers field supplied \u2014 array may be empty or partial)
|
|
28853
|
+
- SKIPS scanning entirely (does NOT fall back to scan mode just because some fixes were skipped)
|
|
28854
|
+
- Include ONLY fixes where answers are justified by code/context; omit fix IDs you are not confident about
|
|
28855
|
+
- Empty array []: abstain from applying ALL interactive fixes (no patches fetched \u2014 summarize skips for the user)
|
|
28258
28856
|
|
|
28259
28857
|
When to invoke:
|
|
28260
|
-
\u2022
|
|
28261
|
-
\u2022
|
|
28262
|
-
\u2022 Ideal after the user makes code changes (added/modified/staged files) but before committing, or whenever they request a full rescan.
|
|
28858
|
+
\u2022 Mode A \u2014 when the user asks to "scan for vulnerabilities", "run a security check", or after they make code changes.
|
|
28859
|
+
\u2022 Mode B \u2014 immediately after Mode A returns interactive fixes; pass confident answers only; use [] only when abstaining from every interactive fix.
|
|
28263
28860
|
|
|
28264
28861
|
How to invoke:
|
|
28265
28862
|
\u2022 Required argument:
|
|
@@ -28269,25 +28866,32 @@ How to invoke:
|
|
|
28269
28866
|
\u2013 limit (number): maximum number of fixes to include in the response.
|
|
28270
28867
|
\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.
|
|
28271
28868
|
\u2013 rescan (boolean): true to force a complete rescan even if cached results exist.
|
|
28869
|
+
\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.
|
|
28272
28870
|
|
|
28273
|
-
Behaviour:
|
|
28274
|
-
\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
|
|
28871
|
+
Behaviour (Mode A):
|
|
28872
|
+
\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.
|
|
28275
28873
|
\u2022 If the directory is not a valid Git repository, the tool falls back to scanning recently changed files in the folder.
|
|
28276
28874
|
\u2022 If maxFiles is provided, the tool scans the maxFiles most recently changed files in the repository.
|
|
28277
|
-
\u2022
|
|
28278
|
-
\u2022 The tool NEVER commits or pushes changes; it only returns proposed diffs/fixes as text.
|
|
28875
|
+
\u2022 The tool NEVER commits or pushes changes.
|
|
28279
28876
|
|
|
28280
28877
|
Return value:
|
|
28281
|
-
|
|
28282
|
-
\u2022 A human-readable summary of the fixes / patches, or
|
|
28283
|
-
\u2022 A diagnostic or error message if the scan fails or finds nothing to fix.
|
|
28878
|
+
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.
|
|
28284
28879
|
|
|
28285
|
-
Example payload:
|
|
28880
|
+
Example payload (Mode A):
|
|
28881
|
+
{ "path": "/home/user/my-project", "limit": 20, "maxFiles": 50 }
|
|
28882
|
+
|
|
28883
|
+
Example payload (Mode B \u2014 subset or abstain):
|
|
28286
28884
|
{
|
|
28287
28885
|
"path": "/home/user/my-project",
|
|
28288
|
-
"
|
|
28289
|
-
|
|
28290
|
-
|
|
28886
|
+
"interactiveAnswers": [
|
|
28887
|
+
{ "fixId": "abc-123", "answers": [{ "key": "isServerSideCode", "value": "yes" }] }
|
|
28888
|
+
]
|
|
28889
|
+
}
|
|
28890
|
+
|
|
28891
|
+
Example payload (Mode B \u2014 abstain from every interactive fix, no rescan):
|
|
28892
|
+
{
|
|
28893
|
+
"path": "/home/user/my-project",
|
|
28894
|
+
"interactiveAnswers": []
|
|
28291
28895
|
}`);
|
|
28292
28896
|
__publicField(this, "hasAuthentication", true);
|
|
28293
28897
|
__publicField(this, "inputValidationSchema", z45.object({
|
|
@@ -28302,6 +28906,21 @@ Example payload:
|
|
|
28302
28906
|
rescan: z45.boolean().optional().describe("Optional whether to rescan the repository"),
|
|
28303
28907
|
scanRecentlyChangedFiles: z45.boolean().optional().describe(
|
|
28304
28908
|
"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."
|
|
28909
|
+
),
|
|
28910
|
+
interactiveAnswers: z45.array(
|
|
28911
|
+
z45.object({
|
|
28912
|
+
fixId: z45.string().min(1).describe('Fix id from a previous "Interactive fix" prompt block.'),
|
|
28913
|
+
answers: z45.array(
|
|
28914
|
+
z45.object({
|
|
28915
|
+
key: z45.string().min(1).describe("FixQuestion key."),
|
|
28916
|
+
value: z45.string().describe(
|
|
28917
|
+
"For SELECT questions MUST be one of the listed options; for TEXT/NUMBER, a free-form value."
|
|
28918
|
+
)
|
|
28919
|
+
})
|
|
28920
|
+
).min(1)
|
|
28921
|
+
})
|
|
28922
|
+
).optional().describe(
|
|
28923
|
+
"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."
|
|
28305
28924
|
)
|
|
28306
28925
|
}));
|
|
28307
28926
|
__publicField(this, "inputSchema", {
|
|
@@ -28330,6 +28949,34 @@ Example payload:
|
|
|
28330
28949
|
scanRecentlyChangedFiles: {
|
|
28331
28950
|
type: "boolean",
|
|
28332
28951
|
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."
|
|
28952
|
+
},
|
|
28953
|
+
interactiveAnswers: {
|
|
28954
|
+
type: "array",
|
|
28955
|
+
items: {
|
|
28956
|
+
type: "object",
|
|
28957
|
+
properties: {
|
|
28958
|
+
fixId: {
|
|
28959
|
+
type: "string",
|
|
28960
|
+
description: 'Fix id from a previous "Interactive fix" prompt.'
|
|
28961
|
+
},
|
|
28962
|
+
answers: {
|
|
28963
|
+
type: "array",
|
|
28964
|
+
items: {
|
|
28965
|
+
type: "object",
|
|
28966
|
+
properties: {
|
|
28967
|
+
key: { type: "string", description: "FixQuestion key." },
|
|
28968
|
+
value: {
|
|
28969
|
+
type: "string",
|
|
28970
|
+
description: "Decided value (SELECT must match an option exactly)."
|
|
28971
|
+
}
|
|
28972
|
+
},
|
|
28973
|
+
required: ["key", "value"]
|
|
28974
|
+
}
|
|
28975
|
+
}
|
|
28976
|
+
},
|
|
28977
|
+
required: ["fixId", "answers"]
|
|
28978
|
+
},
|
|
28979
|
+
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."
|
|
28333
28980
|
}
|
|
28334
28981
|
},
|
|
28335
28982
|
required: ["path"]
|
|
@@ -28340,7 +28987,8 @@ Example payload:
|
|
|
28340
28987
|
}
|
|
28341
28988
|
async executeInternal(args) {
|
|
28342
28989
|
logDebug(`Executing tool: ${this.name}`, {
|
|
28343
|
-
path: args.path
|
|
28990
|
+
path: args.path,
|
|
28991
|
+
mode: args.interactiveAnswers === void 0 ? "scan" : args.interactiveAnswers.length === 0 ? "apply-interactive-abstain-all" : "apply-with-answers"
|
|
28344
28992
|
});
|
|
28345
28993
|
if (!args.path) {
|
|
28346
28994
|
throw new Error("Invalid arguments: Missing required parameter 'path'");
|
|
@@ -28352,6 +29000,26 @@ Example payload:
|
|
|
28352
29000
|
);
|
|
28353
29001
|
}
|
|
28354
29002
|
const path37 = pathValidationResult.path;
|
|
29003
|
+
if (args.interactiveAnswers !== void 0) {
|
|
29004
|
+
if (args.interactiveAnswers.length === 0) {
|
|
29005
|
+
return this.createSuccessResponse(
|
|
29006
|
+
interactiveAnswersAbstainAllToolResponse
|
|
29007
|
+
);
|
|
29008
|
+
}
|
|
29009
|
+
try {
|
|
29010
|
+
const result = await this.vulnerabilityFixService.applyInteractiveAnswers({
|
|
29011
|
+
interactiveAnswers: args.interactiveAnswers,
|
|
29012
|
+
repositoryPath: path37
|
|
29013
|
+
});
|
|
29014
|
+
return this.createSuccessResponse(result);
|
|
29015
|
+
} catch (error) {
|
|
29016
|
+
const message = error.message;
|
|
29017
|
+
logError("Tool execution failed (apply-with-answers)", {
|
|
29018
|
+
error: message
|
|
29019
|
+
});
|
|
29020
|
+
return this.createSuccessResponse(message);
|
|
29021
|
+
}
|
|
29022
|
+
}
|
|
28355
29023
|
const files = await getLocalFiles({
|
|
28356
29024
|
path: path37,
|
|
28357
29025
|
maxFileSize: MCP_MAX_FILE_SIZE,
|