mobbdev 1.0.113 → 1.0.116
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 +683 -425
- package/package.json +25 -22
package/dist/index.mjs
CHANGED
|
@@ -364,6 +364,7 @@ var IssueType_Enum = /* @__PURE__ */ ((IssueType_Enum2) => {
|
|
|
364
364
|
IssueType_Enum2["AutoEscapeFalse"] = "AUTO_ESCAPE_FALSE";
|
|
365
365
|
IssueType_Enum2["AvoidBuiltinShadowing"] = "AVOID_BUILTIN_SHADOWING";
|
|
366
366
|
IssueType_Enum2["AvoidIdentityComparisonCachedTypes"] = "AVOID_IDENTITY_COMPARISON_CACHED_TYPES";
|
|
367
|
+
IssueType_Enum2["BufferOverflow"] = "BUFFER_OVERFLOW";
|
|
367
368
|
IssueType_Enum2["ClientDomStoredCodeInjection"] = "CLIENT_DOM_STORED_CODE_INJECTION";
|
|
368
369
|
IssueType_Enum2["CmDi"] = "CMDi";
|
|
369
370
|
IssueType_Enum2["CmDiRelativePathCommand"] = "CMDi_relative_path_command";
|
|
@@ -444,6 +445,7 @@ var IssueType_Enum = /* @__PURE__ */ ((IssueType_Enum2) => {
|
|
|
444
445
|
IssueType_Enum2["SqlInjection"] = "SQL_Injection";
|
|
445
446
|
IssueType_Enum2["Ssrf"] = "SSRF";
|
|
446
447
|
IssueType_Enum2["StringFormatMisuse"] = "STRING_FORMAT_MISUSE";
|
|
448
|
+
IssueType_Enum2["StringTerminationError"] = "STRING_TERMINATION_ERROR";
|
|
447
449
|
IssueType_Enum2["SystemExitShouldReraise"] = "SYSTEM_EXIT_SHOULD_RERAISE";
|
|
448
450
|
IssueType_Enum2["SystemInformationLeak"] = "SYSTEM_INFORMATION_LEAK";
|
|
449
451
|
IssueType_Enum2["SystemInformationLeakExternal"] = "SYSTEM_INFORMATION_LEAK_EXTERNAL";
|
|
@@ -533,6 +535,8 @@ var FixDetailsFragmentDoc = `
|
|
|
533
535
|
confidence
|
|
534
536
|
safeIssueType
|
|
535
537
|
severityText
|
|
538
|
+
gitBlameLogin
|
|
539
|
+
severityValue
|
|
536
540
|
vulnerabilityReportIssues {
|
|
537
541
|
parsedIssueType
|
|
538
542
|
parsedSeverity
|
|
@@ -596,7 +600,15 @@ var FixReportSummaryFieldsFragmentDoc = `
|
|
|
596
600
|
}
|
|
597
601
|
}
|
|
598
602
|
fixes(
|
|
599
|
-
where: {_and: [{vulnerabilityReportIssues: {category: {_eq: "Fixable"}}}, $filters]}
|
|
603
|
+
where: {_and: [{vulnerabilityReportIssues: {category: {_eq: "Fixable"}}}, {_or: [{gitBlameLogin: {_is_null: true}}, {_not: {gitBlameLogin: {_ilike: $currentUserEmail}}}]}, $filters]}
|
|
604
|
+
order_by: {severityValue: desc}
|
|
605
|
+
limit: $limit
|
|
606
|
+
offset: $offset
|
|
607
|
+
) {
|
|
608
|
+
...FixDetails
|
|
609
|
+
}
|
|
610
|
+
userFixes: fixes(
|
|
611
|
+
where: {_and: [{gitBlameLogin: {_ilike: $currentUserEmail}}, {vulnerabilityReportIssues: {category: {_eq: "Fixable"}}}, $filters]}
|
|
600
612
|
order_by: {severityValue: desc}
|
|
601
613
|
limit: $limit
|
|
602
614
|
offset: $offset
|
|
@@ -662,18 +674,18 @@ var MeDocument = `
|
|
|
662
674
|
}
|
|
663
675
|
}
|
|
664
676
|
`;
|
|
665
|
-
var
|
|
666
|
-
query
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
order_by: {
|
|
670
|
-
limit: $limit
|
|
671
|
-
) {
|
|
672
|
-
organization {
|
|
677
|
+
var GetLastOrgAndNamedProjectDocument = `
|
|
678
|
+
query getLastOrgAndNamedProject($email: String!, $projectName: String!) {
|
|
679
|
+
user(where: {email: {_eq: $email}}) {
|
|
680
|
+
id
|
|
681
|
+
userOrganizationsAndUserOrganizationRoles(order_by: {createdOn: desc}) {
|
|
673
682
|
id
|
|
674
|
-
|
|
683
|
+
organization {
|
|
675
684
|
id
|
|
676
|
-
name
|
|
685
|
+
projects(where: {name: {_eq: $projectName}}) {
|
|
686
|
+
name
|
|
687
|
+
id
|
|
688
|
+
}
|
|
677
689
|
}
|
|
678
690
|
}
|
|
679
691
|
}
|
|
@@ -1064,32 +1076,32 @@ var AutoPrAnalysisDocument = `
|
|
|
1064
1076
|
}
|
|
1065
1077
|
}
|
|
1066
1078
|
`;
|
|
1067
|
-
var
|
|
1068
|
-
query
|
|
1069
|
-
fixReport(
|
|
1070
|
-
where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Finished}}]}
|
|
1071
|
-
order_by: {createdOn: desc}
|
|
1072
|
-
limit: 1
|
|
1073
|
-
) {
|
|
1079
|
+
var GetReportFixesDocument = `
|
|
1080
|
+
query GetReportFixes($reportId: uuid!, $filters: fix_bool_exp = {}, $limit: Int!, $offset: Int!, $currentUserEmail: String!) {
|
|
1081
|
+
fixReport(where: {_and: [{id: {_eq: $reportId}}, {state: {_eq: Finished}}]}) {
|
|
1074
1082
|
...FixReportSummaryFields
|
|
1075
1083
|
}
|
|
1076
1084
|
expiredReport: fixReport(
|
|
1077
|
-
where: {_and: [{
|
|
1078
|
-
order_by: {createdOn: desc}
|
|
1079
|
-
limit: 1
|
|
1085
|
+
where: {_and: [{id: {_eq: $reportId}}, {state: {_eq: Expired}}]}
|
|
1080
1086
|
) {
|
|
1081
1087
|
id
|
|
1082
1088
|
expirationOn
|
|
1083
1089
|
}
|
|
1084
1090
|
}
|
|
1085
1091
|
${FixReportSummaryFieldsFragmentDoc}`;
|
|
1086
|
-
var
|
|
1087
|
-
query
|
|
1088
|
-
fixReport(
|
|
1092
|
+
var GetLatestReportByRepoUrlDocument = `
|
|
1093
|
+
query GetLatestReportByRepoUrl($repoUrl: String!, $filters: fix_bool_exp = {}, $limit: Int!, $offset: Int!, $currentUserEmail: String!) {
|
|
1094
|
+
fixReport(
|
|
1095
|
+
where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Finished}}]}
|
|
1096
|
+
order_by: {createdOn: desc}
|
|
1097
|
+
limit: 1
|
|
1098
|
+
) {
|
|
1089
1099
|
...FixReportSummaryFields
|
|
1090
1100
|
}
|
|
1091
1101
|
expiredReport: fixReport(
|
|
1092
|
-
where: {_and: [{
|
|
1102
|
+
where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Expired}}]}
|
|
1103
|
+
order_by: {createdOn: desc}
|
|
1104
|
+
limit: 1
|
|
1093
1105
|
) {
|
|
1094
1106
|
id
|
|
1095
1107
|
expirationOn
|
|
@@ -1109,8 +1121,8 @@ function getSdk(client, withWrapper = defaultWrapper) {
|
|
|
1109
1121
|
Me(variables, requestHeaders, signal) {
|
|
1110
1122
|
return withWrapper((wrappedRequestHeaders) => client.request({ document: MeDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "Me", "query", variables);
|
|
1111
1123
|
},
|
|
1112
|
-
|
|
1113
|
-
return withWrapper((wrappedRequestHeaders) => client.request({ document:
|
|
1124
|
+
getLastOrgAndNamedProject(variables, requestHeaders, signal) {
|
|
1125
|
+
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetLastOrgAndNamedProjectDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getLastOrgAndNamedProject", "query", variables);
|
|
1114
1126
|
},
|
|
1115
1127
|
GetEncryptedApiToken(variables, requestHeaders, signal) {
|
|
1116
1128
|
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetEncryptedApiTokenDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetEncryptedApiToken", "query", variables);
|
|
@@ -1169,12 +1181,12 @@ function getSdk(client, withWrapper = defaultWrapper) {
|
|
|
1169
1181
|
autoPrAnalysis(variables, requestHeaders, signal) {
|
|
1170
1182
|
return withWrapper((wrappedRequestHeaders) => client.request({ document: AutoPrAnalysisDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "autoPrAnalysis", "mutation", variables);
|
|
1171
1183
|
},
|
|
1172
|
-
GetLatestReportByRepoUrl(variables, requestHeaders, signal) {
|
|
1173
|
-
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetLatestReportByRepoUrlDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetLatestReportByRepoUrl", "query", variables);
|
|
1174
|
-
},
|
|
1175
1184
|
GetReportFixes(variables, requestHeaders, signal) {
|
|
1176
1185
|
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetReportFixesDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetReportFixes", "query", variables);
|
|
1177
1186
|
},
|
|
1187
|
+
GetLatestReportByRepoUrl(variables, requestHeaders, signal) {
|
|
1188
|
+
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetLatestReportByRepoUrlDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetLatestReportByRepoUrl", "query", variables);
|
|
1189
|
+
},
|
|
1178
1190
|
updateDownloadedFixData(variables, requestHeaders, signal) {
|
|
1179
1191
|
return withWrapper((wrappedRequestHeaders) => client.request({ document: UpdateDownloadedFixDataDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "updateDownloadedFixData", "mutation", variables);
|
|
1180
1192
|
}
|
|
@@ -1444,7 +1456,15 @@ var fixDetailsData = {
|
|
|
1444
1456
|
["UNNECESSARY_IMPORTS" /* UnnecessaryImports */]: void 0,
|
|
1445
1457
|
["NO_NESTED_TRY" /* NoNestedTry */]: void 0,
|
|
1446
1458
|
["REDOS" /* Redos */]: void 0,
|
|
1447
|
-
["DO_NOT_THROW_GENERIC_EXCEPTION" /* DoNotThrowGenericException */]: void 0
|
|
1459
|
+
["DO_NOT_THROW_GENERIC_EXCEPTION" /* DoNotThrowGenericException */]: void 0,
|
|
1460
|
+
["BUFFER_OVERFLOW" /* BufferOverflow */]: {
|
|
1461
|
+
issueDescription: "Buffer Overflow occurs when a program writes data beyond the allocated memory space, leading to unexpected behavior or security vulnerabilities.",
|
|
1462
|
+
fixInstructions: "Implement proper input validation and bounds checking to prevent buffer overflows. Use safe string manipulation functions and ensure that the buffer size is properly managed."
|
|
1463
|
+
},
|
|
1464
|
+
["STRING_TERMINATION_ERROR" /* StringTerminationError */]: {
|
|
1465
|
+
issueDescription: "String Termination Error occurs when a string is not properly terminated, leading to unexpected behavior or security vulnerabilities.",
|
|
1466
|
+
fixInstructions: "Implement proper input validation and bounds checking to prevent string termination errors. Use safe string manipulation functions and ensure that the buffer size is properly managed."
|
|
1467
|
+
}
|
|
1448
1468
|
};
|
|
1449
1469
|
|
|
1450
1470
|
// src/features/analysis/scm/shared/src/getIssueType.ts
|
|
@@ -1558,7 +1578,9 @@ var issueTypeMap = {
|
|
|
1558
1578
|
["NO_NESTED_TRY" /* NoNestedTry */]: "No Nested Try",
|
|
1559
1579
|
["UNNECESSARY_IMPORTS" /* UnnecessaryImports */]: "Unnecessary Imports",
|
|
1560
1580
|
["REDOS" /* Redos */]: "Regular Expression Denial of Service",
|
|
1561
|
-
["DO_NOT_THROW_GENERIC_EXCEPTION" /* DoNotThrowGenericException */]: "Do Not Throw Generic Exception"
|
|
1581
|
+
["DO_NOT_THROW_GENERIC_EXCEPTION" /* DoNotThrowGenericException */]: "Do Not Throw Generic Exception",
|
|
1582
|
+
["BUFFER_OVERFLOW" /* BufferOverflow */]: "Buffer Overflow",
|
|
1583
|
+
["STRING_TERMINATION_ERROR" /* StringTerminationError */]: "String Termination Error"
|
|
1562
1584
|
};
|
|
1563
1585
|
var issueTypeZ = z.nativeEnum(IssueType_Enum);
|
|
1564
1586
|
var getIssueTypeFriendlyString = (issueType) => {
|
|
@@ -5141,14 +5163,17 @@ import { simpleGit } from "simple-git";
|
|
|
5141
5163
|
// src/mcp/core/configs.ts
|
|
5142
5164
|
var MCP_DEFAULT_API_URL = "https://api.mobb.ai/v1/graphql";
|
|
5143
5165
|
var MCP_API_KEY_HEADER_NAME = "x-mobb-key";
|
|
5144
|
-
var MCP_LOGIN_MAX_WAIT =
|
|
5145
|
-
var MCP_LOGIN_CHECK_DELAY =
|
|
5166
|
+
var MCP_LOGIN_MAX_WAIT = 2 * 60 * 1e3;
|
|
5167
|
+
var MCP_LOGIN_CHECK_DELAY = 2 * 1e3;
|
|
5146
5168
|
var MCP_VUL_REPORT_DIGEST_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
5147
5169
|
var MCP_MAX_FILE_SIZE = MAX_UPLOAD_FILE_SIZE_MB * 1024 * 1024;
|
|
5148
5170
|
var MCP_PERIODIC_CHECK_INTERVAL = 15 * 60 * 1e3;
|
|
5149
5171
|
var MCP_DEFAULT_MAX_FILES_TO_SCAN = 10;
|
|
5150
5172
|
var MCP_REPORT_ID_EXPIRATION_MS = 2 * 60 * 60 * 1e3;
|
|
5151
5173
|
var MCP_TOOLS_BROWSER_COOLDOWN_MS = 24 * 60 * 60 * 1e3;
|
|
5174
|
+
var MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES = "check_for_new_available_fixes";
|
|
5175
|
+
var MCP_TOOL_FETCH_AVAILABLE_FIXES = "fetch_available_fixes";
|
|
5176
|
+
var MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES = "scan_and_fix_vulnerabilities";
|
|
5152
5177
|
|
|
5153
5178
|
// src/features/analysis/scm/FileUtils.ts
|
|
5154
5179
|
import fs2 from "fs";
|
|
@@ -8653,12 +8678,6 @@ var GqlClientError = class extends Error {
|
|
|
8653
8678
|
this.name = "GqlClientError";
|
|
8654
8679
|
}
|
|
8655
8680
|
};
|
|
8656
|
-
var FileProcessingError = class extends Error {
|
|
8657
|
-
constructor(message) {
|
|
8658
|
-
super(message);
|
|
8659
|
-
this.name = "FileProcessingError";
|
|
8660
|
-
}
|
|
8661
|
-
};
|
|
8662
8681
|
var ReportInitializationError = class extends Error {
|
|
8663
8682
|
constructor(message) {
|
|
8664
8683
|
super(message);
|
|
@@ -9592,7 +9611,7 @@ var GQLClient = class {
|
|
|
9592
9611
|
});
|
|
9593
9612
|
return res.insert_cli_login_one?.id || "";
|
|
9594
9613
|
}
|
|
9595
|
-
async
|
|
9614
|
+
async verifyApiConnection() {
|
|
9596
9615
|
try {
|
|
9597
9616
|
await this.getUserInfo();
|
|
9598
9617
|
} catch (e) {
|
|
@@ -9603,7 +9622,7 @@ var GQLClient = class {
|
|
|
9603
9622
|
}
|
|
9604
9623
|
return true;
|
|
9605
9624
|
}
|
|
9606
|
-
async
|
|
9625
|
+
async validateUserToken() {
|
|
9607
9626
|
await this.createCommunityUser();
|
|
9608
9627
|
let info;
|
|
9609
9628
|
try {
|
|
@@ -9614,31 +9633,46 @@ var GQLClient = class {
|
|
|
9614
9633
|
}
|
|
9615
9634
|
return info?.email || true;
|
|
9616
9635
|
}
|
|
9617
|
-
async
|
|
9636
|
+
async getLastOrgAndNamedProject(params) {
|
|
9637
|
+
const me = await this.getUserInfo();
|
|
9638
|
+
const email = me?.email;
|
|
9639
|
+
if (!email) {
|
|
9640
|
+
throw new Error("User email not found");
|
|
9641
|
+
}
|
|
9618
9642
|
const { projectName, userDefinedOrganizationId } = params;
|
|
9619
|
-
|
|
9620
|
-
|
|
9621
|
-
|
|
9643
|
+
if (!projectName) {
|
|
9644
|
+
throw new Error("Project name is required");
|
|
9645
|
+
}
|
|
9646
|
+
const orgAndProjectRes = await this._clientSdk.getLastOrgAndNamedProject({
|
|
9647
|
+
email,
|
|
9648
|
+
projectName
|
|
9622
9649
|
});
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9650
|
+
if (!orgAndProjectRes.user?.[0]?.userOrganizationsAndUserOrganizationRoles?.[0]?.organization?.id) {
|
|
9651
|
+
throw new Error(
|
|
9652
|
+
`The user with email:${email} is not associated with any organization`
|
|
9653
|
+
);
|
|
9654
|
+
}
|
|
9655
|
+
const organization = orgAndProjectRes.user?.at(0)?.userOrganizationsAndUserOrganizationRoles.map((org) => org.organization).filter(
|
|
9656
|
+
(org) => userDefinedOrganizationId ? org.id === userDefinedOrganizationId : true
|
|
9657
|
+
)?.at(0);
|
|
9658
|
+
if (!organization) {
|
|
9659
|
+
throw new Error(
|
|
9660
|
+
`Organization with id:${userDefinedOrganizationId} not found`
|
|
9661
|
+
);
|
|
9626
9662
|
}
|
|
9627
|
-
|
|
9628
|
-
const project = projectName ? org?.projects.find((project2) => project2.name === projectName) ?? null : org?.projects[0];
|
|
9629
|
-
let projectId = project?.id;
|
|
9663
|
+
let projectId = organization?.projects?.[0]?.id;
|
|
9630
9664
|
if (!projectId) {
|
|
9631
9665
|
const createdProject = await this._clientSdk.CreateProject({
|
|
9632
|
-
organizationId:
|
|
9666
|
+
organizationId: organization.id,
|
|
9633
9667
|
projectName: projectName || "My project"
|
|
9634
9668
|
});
|
|
9635
9669
|
projectId = createdProject.createProject.projectId;
|
|
9636
9670
|
}
|
|
9637
|
-
if (!
|
|
9671
|
+
if (!projectId) {
|
|
9638
9672
|
throw new Error("Project not found");
|
|
9639
9673
|
}
|
|
9640
9674
|
return {
|
|
9641
|
-
organizationId:
|
|
9675
|
+
organizationId: organization.id,
|
|
9642
9676
|
projectId
|
|
9643
9677
|
};
|
|
9644
9678
|
}
|
|
@@ -10551,7 +10585,10 @@ async function _scan(params, { skipPrompts = false } = {}) {
|
|
|
10551
10585
|
skipPrompts,
|
|
10552
10586
|
apiKey
|
|
10553
10587
|
});
|
|
10554
|
-
|
|
10588
|
+
if (!mobbProjectName) {
|
|
10589
|
+
throw new Error("mobbProjectName is required");
|
|
10590
|
+
}
|
|
10591
|
+
const { projectId, organizationId } = await gqlClient.getLastOrgAndNamedProject({
|
|
10555
10592
|
projectName: mobbProjectName,
|
|
10556
10593
|
userDefinedOrganizationId: userOrganizationId
|
|
10557
10594
|
});
|
|
@@ -11128,7 +11165,7 @@ async function handleMobbLogin({
|
|
|
11128
11165
|
skipPrompts
|
|
11129
11166
|
}) {
|
|
11130
11167
|
const { createSpinner: createSpinner5 } = Spinner({ ci: skipPrompts });
|
|
11131
|
-
const isConnected = await inGqlClient.
|
|
11168
|
+
const isConnected = await inGqlClient.verifyApiConnection();
|
|
11132
11169
|
if (!isConnected) {
|
|
11133
11170
|
createSpinner5().start().error({
|
|
11134
11171
|
text: "\u{1F513} Connection to Mobb: failed to connect to the Mobb server"
|
|
@@ -11140,7 +11177,7 @@ async function handleMobbLogin({
|
|
|
11140
11177
|
createSpinner5().start().success({
|
|
11141
11178
|
text: `\u{1F513} Connection to Mobb: succeeded`
|
|
11142
11179
|
});
|
|
11143
|
-
const userVerify = await inGqlClient.
|
|
11180
|
+
const userVerify = await inGqlClient.validateUserToken();
|
|
11144
11181
|
if (userVerify) {
|
|
11145
11182
|
createSpinner5().start().success({
|
|
11146
11183
|
text: `\u{1F513} Login to Mobb succeeded. ${typeof userVerify === "string" ? `Logged in as ${userVerify}` : ""}`
|
|
@@ -11194,7 +11231,7 @@ async function handleMobbLogin({
|
|
|
11194
11231
|
throw new CliError();
|
|
11195
11232
|
}
|
|
11196
11233
|
const newGqlClient = new GQLClient({ apiKey: newApiToken, type: "apiKey" });
|
|
11197
|
-
const loginSuccess = await newGqlClient.
|
|
11234
|
+
const loginSuccess = await newGqlClient.validateUserToken();
|
|
11198
11235
|
if (loginSuccess) {
|
|
11199
11236
|
debug18(`set api token ${newApiToken}`);
|
|
11200
11237
|
config3.set("apiToken", newApiToken);
|
|
@@ -11353,7 +11390,7 @@ import {
|
|
|
11353
11390
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
11354
11391
|
|
|
11355
11392
|
// src/mcp/Logger.ts
|
|
11356
|
-
var
|
|
11393
|
+
var loggerUrl = "http://localhost:4444/log";
|
|
11357
11394
|
var isTestEnvironment = process.env["VITEST"] || process.env["TEST"];
|
|
11358
11395
|
var CIRCUIT_BREAKER_TIME = 5e3;
|
|
11359
11396
|
var URL_CHECK_TIMEOUT = 200;
|
|
@@ -11400,11 +11437,14 @@ var Logger = class {
|
|
|
11400
11437
|
this.isProcessing = false;
|
|
11401
11438
|
return;
|
|
11402
11439
|
}
|
|
11403
|
-
const isReachable = await this.isUrlReachable(
|
|
11440
|
+
const isReachable = await this.isUrlReachable(loggerUrl);
|
|
11404
11441
|
if (!isReachable) {
|
|
11405
11442
|
this.triggerCircuitBreaker();
|
|
11406
11443
|
return;
|
|
11407
11444
|
}
|
|
11445
|
+
await this.sendLogEntry(logEntry);
|
|
11446
|
+
}
|
|
11447
|
+
async sendLogEntry(logEntry) {
|
|
11408
11448
|
const logMessage = {
|
|
11409
11449
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11410
11450
|
level: logEntry.level,
|
|
@@ -11416,21 +11456,23 @@ var Logger = class {
|
|
|
11416
11456
|
const timeoutId = setTimeout(() => {
|
|
11417
11457
|
controller.abort();
|
|
11418
11458
|
}, 500);
|
|
11419
|
-
|
|
11420
|
-
|
|
11421
|
-
|
|
11422
|
-
|
|
11423
|
-
|
|
11424
|
-
|
|
11425
|
-
|
|
11426
|
-
|
|
11459
|
+
try {
|
|
11460
|
+
await fetch(loggerUrl, {
|
|
11461
|
+
method: "POST",
|
|
11462
|
+
headers: { "Content-Type": "application/json" },
|
|
11463
|
+
body: JSON.stringify(logMessage),
|
|
11464
|
+
redirect: "error",
|
|
11465
|
+
// do not follow redirects
|
|
11466
|
+
signal: controller.signal
|
|
11467
|
+
});
|
|
11427
11468
|
this.queue.shift();
|
|
11428
11469
|
setTimeout(() => this.processQueue(), 0);
|
|
11429
|
-
}
|
|
11470
|
+
} catch (error) {
|
|
11430
11471
|
this.triggerCircuitBreaker();
|
|
11431
|
-
|
|
11472
|
+
logError("Failed to send log entry", error);
|
|
11473
|
+
} finally {
|
|
11432
11474
|
clearTimeout(timeoutId);
|
|
11433
|
-
}
|
|
11475
|
+
}
|
|
11434
11476
|
}
|
|
11435
11477
|
triggerCircuitBreaker() {
|
|
11436
11478
|
this.isCircuitBroken = true;
|
|
@@ -11456,12 +11498,90 @@ var logDebug = (message, data) => logger.log(message, "debug", data);
|
|
|
11456
11498
|
var log = logger.log.bind(logger);
|
|
11457
11499
|
|
|
11458
11500
|
// src/mcp/services/McpGQLClient.ts
|
|
11459
|
-
import crypto2 from "crypto";
|
|
11460
|
-
import os2 from "os";
|
|
11461
11501
|
import Configstore3 from "configstore";
|
|
11502
|
+
import crypto3 from "crypto";
|
|
11462
11503
|
import { GraphQLClient as GraphQLClient2 } from "graphql-request";
|
|
11463
|
-
import open4 from "open";
|
|
11464
11504
|
import { v4 as uuidv42 } from "uuid";
|
|
11505
|
+
|
|
11506
|
+
// src/mcp/services/McpAuthService.ts
|
|
11507
|
+
import crypto2 from "crypto";
|
|
11508
|
+
import os2 from "os";
|
|
11509
|
+
import open4 from "open";
|
|
11510
|
+
var McpAuthService = class {
|
|
11511
|
+
constructor(client) {
|
|
11512
|
+
__publicField(this, "client");
|
|
11513
|
+
__publicField(this, "lastBrowserOpenTime", 0);
|
|
11514
|
+
this.client = client;
|
|
11515
|
+
}
|
|
11516
|
+
/**
|
|
11517
|
+
* Opens a browser window for authentication
|
|
11518
|
+
* @param url URL to open in browser
|
|
11519
|
+
* @param isBackgoundCall Whether this is called from tools context
|
|
11520
|
+
*/
|
|
11521
|
+
async openBrowser(url, isBackgoundCall) {
|
|
11522
|
+
if (isBackgoundCall) {
|
|
11523
|
+
const now = Date.now();
|
|
11524
|
+
if (now - this.lastBrowserOpenTime < MCP_TOOLS_BROWSER_COOLDOWN_MS) {
|
|
11525
|
+
logDebug(`browser cooldown active, skipping open for ${url}`);
|
|
11526
|
+
return;
|
|
11527
|
+
}
|
|
11528
|
+
}
|
|
11529
|
+
logDebug(`opening browser url ${url}`);
|
|
11530
|
+
await open4(url);
|
|
11531
|
+
this.lastBrowserOpenTime = Date.now();
|
|
11532
|
+
}
|
|
11533
|
+
/**
|
|
11534
|
+
* Handles the complete authentication flow
|
|
11535
|
+
* @param isBackgoundCall Whether this is called from tools context
|
|
11536
|
+
* @returns Authenticated API token
|
|
11537
|
+
*/
|
|
11538
|
+
async authenticate(isBackgoundCall = false) {
|
|
11539
|
+
const { publicKey, privateKey } = crypto2.generateKeyPairSync("rsa", {
|
|
11540
|
+
modulusLength: 2048
|
|
11541
|
+
});
|
|
11542
|
+
logDebug("creating cli login");
|
|
11543
|
+
const loginId = await this.client.createCliLogin({
|
|
11544
|
+
publicKey: publicKey.export({ format: "pem", type: "pkcs1" }).toString()
|
|
11545
|
+
});
|
|
11546
|
+
if (!loginId) {
|
|
11547
|
+
throw new CliLoginError("Error: createCliLogin failed");
|
|
11548
|
+
}
|
|
11549
|
+
logDebug(`cli login created ${loginId}`);
|
|
11550
|
+
const webLoginUrl2 = `${WEB_APP_URL}/cli-login`;
|
|
11551
|
+
const browserUrl = `${webLoginUrl2}/${loginId}?hostname=${os2.hostname()}`;
|
|
11552
|
+
await this.openBrowser(browserUrl, isBackgoundCall);
|
|
11553
|
+
logDebug(`waiting for login to complete`);
|
|
11554
|
+
let newApiToken = null;
|
|
11555
|
+
for (let i = 0; i < MCP_LOGIN_MAX_WAIT / MCP_LOGIN_CHECK_DELAY; i++) {
|
|
11556
|
+
const encryptedApiToken = await this.client.getEncryptedApiToken({
|
|
11557
|
+
loginId
|
|
11558
|
+
});
|
|
11559
|
+
if (encryptedApiToken) {
|
|
11560
|
+
logDebug("encrypted API token received");
|
|
11561
|
+
newApiToken = crypto2.privateDecrypt(privateKey, Buffer.from(encryptedApiToken, "base64")).toString("utf-8");
|
|
11562
|
+
logDebug("API token decrypted");
|
|
11563
|
+
break;
|
|
11564
|
+
}
|
|
11565
|
+
await sleep(MCP_LOGIN_CHECK_DELAY);
|
|
11566
|
+
}
|
|
11567
|
+
if (!newApiToken) {
|
|
11568
|
+
throw new FailedToGetApiTokenError(
|
|
11569
|
+
"Error: failed to get encrypted api token"
|
|
11570
|
+
);
|
|
11571
|
+
}
|
|
11572
|
+
const verifyClient = new McpGQLClient({
|
|
11573
|
+
apiKey: newApiToken,
|
|
11574
|
+
type: "apiKey"
|
|
11575
|
+
});
|
|
11576
|
+
const loginSuccess = await verifyClient.validateUserToken();
|
|
11577
|
+
if (!loginSuccess) {
|
|
11578
|
+
throw new AuthenticationError("Invalid API token");
|
|
11579
|
+
}
|
|
11580
|
+
return newApiToken;
|
|
11581
|
+
}
|
|
11582
|
+
};
|
|
11583
|
+
|
|
11584
|
+
// src/mcp/services/McpGQLClient.ts
|
|
11465
11585
|
var mobbConfigStore = new Configstore3(packageJson.name, { apiToken: "" });
|
|
11466
11586
|
var McpGQLClient = class {
|
|
11467
11587
|
constructor(args) {
|
|
@@ -11498,17 +11618,17 @@ var McpGQLClient = class {
|
|
|
11498
11618
|
}
|
|
11499
11619
|
};
|
|
11500
11620
|
}
|
|
11501
|
-
async
|
|
11621
|
+
async verifyApiConnection() {
|
|
11502
11622
|
try {
|
|
11503
|
-
logDebug("GraphQL: Calling Me query for connection verification");
|
|
11623
|
+
logDebug("GraphQL: Calling Me query for API connection verification");
|
|
11504
11624
|
const result = await this.clientSdk.Me();
|
|
11505
11625
|
logDebug("GraphQL: Me query successful", { result });
|
|
11506
11626
|
return true;
|
|
11507
11627
|
} catch (e) {
|
|
11508
11628
|
const error = e;
|
|
11509
|
-
logDebug(`
|
|
11629
|
+
logDebug(`API connection verification failed ${error.toString()}`);
|
|
11510
11630
|
if (error?.toString().includes("FetchError")) {
|
|
11511
|
-
logError("
|
|
11631
|
+
logError("API connection verification failed", { error });
|
|
11512
11632
|
return false;
|
|
11513
11633
|
}
|
|
11514
11634
|
}
|
|
@@ -11520,7 +11640,7 @@ var McpGQLClient = class {
|
|
|
11520
11640
|
const result = await this.clientSdk.uploadS3BucketInfo({
|
|
11521
11641
|
fileName: "report.json"
|
|
11522
11642
|
});
|
|
11523
|
-
|
|
11643
|
+
logDebug("GraphQL: uploadS3BucketInfo successful", { result });
|
|
11524
11644
|
return result;
|
|
11525
11645
|
} catch (e) {
|
|
11526
11646
|
logError("GraphQL: uploadS3BucketInfo failed", {
|
|
@@ -11536,7 +11656,7 @@ var McpGQLClient = class {
|
|
|
11536
11656
|
const res = await this.clientSdk.getAnalysis({
|
|
11537
11657
|
analysisId
|
|
11538
11658
|
});
|
|
11539
|
-
|
|
11659
|
+
logDebug("GraphQL: getAnalysis successful", { result: res });
|
|
11540
11660
|
if (!res.analysis) {
|
|
11541
11661
|
throw new Error(`Analysis not found: ${analysisId}`);
|
|
11542
11662
|
}
|
|
@@ -11556,7 +11676,7 @@ var McpGQLClient = class {
|
|
|
11556
11676
|
variables
|
|
11557
11677
|
});
|
|
11558
11678
|
const result = await this.clientSdk.SubmitVulnerabilityReport(variables);
|
|
11559
|
-
|
|
11679
|
+
logDebug("GraphQL: SubmitVulnerabilityReport successful", { result });
|
|
11560
11680
|
return result;
|
|
11561
11681
|
} catch (e) {
|
|
11562
11682
|
logError("GraphQL: SubmitVulnerabilityReport failed", {
|
|
@@ -11590,7 +11710,7 @@ var McpGQLClient = class {
|
|
|
11590
11710
|
return;
|
|
11591
11711
|
}
|
|
11592
11712
|
if (callbackStates.includes(data.analysis?.state)) {
|
|
11593
|
-
|
|
11713
|
+
logDebug("GraphQL: Analysis state matches callback states", {
|
|
11594
11714
|
analysisId: data.analysis.id,
|
|
11595
11715
|
state: data.analysis.state,
|
|
11596
11716
|
callbackStates
|
|
@@ -11609,7 +11729,7 @@ var McpGQLClient = class {
|
|
|
11609
11729
|
timeoutInMs: params.timeoutInMs
|
|
11610
11730
|
}
|
|
11611
11731
|
);
|
|
11612
|
-
|
|
11732
|
+
logDebug("GraphQL: GetAnalysis subscription completed", { result });
|
|
11613
11733
|
return result;
|
|
11614
11734
|
} catch (e) {
|
|
11615
11735
|
logError("GraphQL: GetAnalysis subscription failed", {
|
|
@@ -11630,38 +11750,41 @@ var McpGQLClient = class {
|
|
|
11630
11750
|
if (!userEmail) {
|
|
11631
11751
|
throw new Error("User email not found");
|
|
11632
11752
|
}
|
|
11633
|
-
const shortEmailHash =
|
|
11753
|
+
const shortEmailHash = crypto3.createHash("sha256").update(userEmail).digest("hex").slice(0, 8).toUpperCase();
|
|
11634
11754
|
const projectName = `MCP Scans ${shortEmailHash}`;
|
|
11635
|
-
logDebug("GraphQL: Calling
|
|
11636
|
-
|
|
11637
|
-
|
|
11638
|
-
|
|
11755
|
+
logDebug("GraphQL: Calling getLastOrgAndNamedProject query", {
|
|
11756
|
+
projectName
|
|
11757
|
+
});
|
|
11758
|
+
const orgAndProjectRes = await this.clientSdk.getLastOrgAndNamedProject({
|
|
11759
|
+
email: userEmail,
|
|
11760
|
+
projectName
|
|
11639
11761
|
});
|
|
11640
|
-
|
|
11641
|
-
result:
|
|
11762
|
+
logDebug("GraphQL: getLastOrgAndNamedProject successful", {
|
|
11763
|
+
result: orgAndProjectRes
|
|
11642
11764
|
});
|
|
11643
|
-
|
|
11644
|
-
|
|
11645
|
-
|
|
11646
|
-
|
|
11647
|
-
|
|
11648
|
-
const
|
|
11649
|
-
|
|
11650
|
-
|
|
11651
|
-
|
|
11765
|
+
if (!orgAndProjectRes.user?.[0]?.userOrganizationsAndUserOrganizationRoles?.[0]?.organization?.id) {
|
|
11766
|
+
throw new Error(
|
|
11767
|
+
`The user with email:${userEmail} is not associated with any organization`
|
|
11768
|
+
);
|
|
11769
|
+
}
|
|
11770
|
+
const organization = orgAndProjectRes.user?.[0]?.userOrganizationsAndUserOrganizationRoles?.[0]?.organization;
|
|
11771
|
+
const projectId = organization?.projects?.[0]?.id;
|
|
11772
|
+
if (projectId) {
|
|
11773
|
+
logDebug("GraphQL: Found existing project", {
|
|
11774
|
+
projectId,
|
|
11652
11775
|
projectName
|
|
11653
11776
|
});
|
|
11654
|
-
return
|
|
11777
|
+
return projectId;
|
|
11655
11778
|
}
|
|
11656
11779
|
logDebug("GraphQL: Project not found, creating new project", {
|
|
11657
|
-
organizationId:
|
|
11780
|
+
organizationId: organization.id,
|
|
11658
11781
|
projectName
|
|
11659
11782
|
});
|
|
11660
11783
|
const createdProject = await this.clientSdk.CreateProject({
|
|
11661
|
-
organizationId:
|
|
11784
|
+
organizationId: organization.id,
|
|
11662
11785
|
projectName
|
|
11663
11786
|
});
|
|
11664
|
-
|
|
11787
|
+
logDebug("GraphQL: CreateProject successful", { result: createdProject });
|
|
11665
11788
|
return createdProject.createProject.projectId;
|
|
11666
11789
|
} catch (e) {
|
|
11667
11790
|
logError("GraphQL: getProjectId failed", {
|
|
@@ -11675,15 +11798,15 @@ var McpGQLClient = class {
|
|
|
11675
11798
|
const { me } = await this.clientSdk.Me();
|
|
11676
11799
|
return me;
|
|
11677
11800
|
}
|
|
11678
|
-
async
|
|
11679
|
-
logDebug("
|
|
11801
|
+
async validateUserToken() {
|
|
11802
|
+
logDebug("validating user token");
|
|
11680
11803
|
try {
|
|
11681
11804
|
await this.clientSdk.CreateCommunityUser();
|
|
11682
11805
|
const info = await this.getUserInfo();
|
|
11683
|
-
logDebug("token
|
|
11806
|
+
logDebug("user token validated successfully");
|
|
11684
11807
|
return info?.email || true;
|
|
11685
11808
|
} catch (e) {
|
|
11686
|
-
logError("
|
|
11809
|
+
logError("user token validation failed");
|
|
11687
11810
|
return false;
|
|
11688
11811
|
}
|
|
11689
11812
|
}
|
|
@@ -11716,18 +11839,34 @@ var McpGQLClient = class {
|
|
|
11716
11839
|
return null;
|
|
11717
11840
|
}
|
|
11718
11841
|
}
|
|
11719
|
-
|
|
11842
|
+
mergeUserAndSystemFixes(reportData, limit) {
|
|
11843
|
+
if (!reportData) return [];
|
|
11844
|
+
const { userFixes = [], fixes = [] } = reportData;
|
|
11845
|
+
const fixMap = /* @__PURE__ */ new Map();
|
|
11846
|
+
for (const fix of userFixes) {
|
|
11847
|
+
if (fix.id) {
|
|
11848
|
+
fixMap.set(fix.id, fix);
|
|
11849
|
+
}
|
|
11850
|
+
}
|
|
11851
|
+
for (const fix of fixes) {
|
|
11852
|
+
if (fix.id && !fixMap.has(fix.id)) {
|
|
11853
|
+
fixMap.set(fix.id, fix);
|
|
11854
|
+
}
|
|
11855
|
+
}
|
|
11856
|
+
return Array.from(fixMap.values()).slice(0, limit);
|
|
11857
|
+
}
|
|
11858
|
+
async updateFixesDownloadStatus(fixIds) {
|
|
11720
11859
|
if (fixIds.length > 0) {
|
|
11721
11860
|
const resUpdate = await this.clientSdk.updateDownloadedFixData({
|
|
11722
11861
|
fixIds,
|
|
11723
11862
|
source: "MCP" /* Mcp */
|
|
11724
11863
|
});
|
|
11725
|
-
|
|
11864
|
+
logDebug("GraphQL: updateFixesDownloadStatus successful", {
|
|
11726
11865
|
result: resUpdate,
|
|
11727
11866
|
fixIds
|
|
11728
11867
|
});
|
|
11729
11868
|
} else {
|
|
11730
|
-
|
|
11869
|
+
logDebug("GraphQL: No fixes found to update download status");
|
|
11731
11870
|
}
|
|
11732
11871
|
}
|
|
11733
11872
|
async getLatestReportByRepoUrl({
|
|
@@ -11741,19 +11880,35 @@ var McpGQLClient = class {
|
|
|
11741
11880
|
limit,
|
|
11742
11881
|
offset
|
|
11743
11882
|
});
|
|
11883
|
+
let currentUserEmail = "%@%";
|
|
11884
|
+
try {
|
|
11885
|
+
const userInfo = await this.getUserInfo();
|
|
11886
|
+
if (userInfo?.email) {
|
|
11887
|
+
currentUserEmail = `%${userInfo.email}%`;
|
|
11888
|
+
}
|
|
11889
|
+
} catch (err) {
|
|
11890
|
+
logDebug("Failed to get user email, using default pattern", {
|
|
11891
|
+
error: err
|
|
11892
|
+
});
|
|
11893
|
+
}
|
|
11744
11894
|
const res = await this.clientSdk.GetLatestReportByRepoUrl({
|
|
11745
11895
|
repoUrl,
|
|
11746
11896
|
limit,
|
|
11747
|
-
offset
|
|
11897
|
+
offset,
|
|
11898
|
+
currentUserEmail
|
|
11748
11899
|
});
|
|
11749
|
-
|
|
11900
|
+
logDebug("GraphQL: GetLatestReportByRepoUrl successful", {
|
|
11750
11901
|
result: res,
|
|
11751
11902
|
reportCount: res.fixReport?.length || 0
|
|
11752
11903
|
});
|
|
11753
|
-
const
|
|
11754
|
-
|
|
11904
|
+
const fixes = this.mergeUserAndSystemFixes(res.fixReport?.[0], limit);
|
|
11905
|
+
const fixIds = fixes.map((fix) => fix.id);
|
|
11906
|
+
await this.updateFixesDownloadStatus(fixIds);
|
|
11755
11907
|
return {
|
|
11756
|
-
fixReport: res.fixReport?.[0]
|
|
11908
|
+
fixReport: res.fixReport?.[0] ? {
|
|
11909
|
+
...res.fixReport?.[0],
|
|
11910
|
+
fixes
|
|
11911
|
+
} : null,
|
|
11757
11912
|
expiredReport: res.expiredReport?.[0] || null
|
|
11758
11913
|
};
|
|
11759
11914
|
} catch (e) {
|
|
@@ -11772,14 +11927,14 @@ var McpGQLClient = class {
|
|
|
11772
11927
|
issueType,
|
|
11773
11928
|
severity
|
|
11774
11929
|
}) {
|
|
11930
|
+
const filters = {};
|
|
11931
|
+
if (issueType && issueType.length > 0) {
|
|
11932
|
+
filters["safeIssueType"] = { _in: issueType };
|
|
11933
|
+
}
|
|
11934
|
+
if (severity && severity.length > 0) {
|
|
11935
|
+
filters["severityText"] = { _in: severity };
|
|
11936
|
+
}
|
|
11775
11937
|
try {
|
|
11776
|
-
const filters = {};
|
|
11777
|
-
if (issueType && issueType.length > 0) {
|
|
11778
|
-
filters["safeIssueType"] = { _in: issueType };
|
|
11779
|
-
}
|
|
11780
|
-
if (severity && severity.length > 0) {
|
|
11781
|
-
filters["severityText"] = { _in: severity };
|
|
11782
|
-
}
|
|
11783
11938
|
logDebug("GraphQL: Calling GetReportFixes query", {
|
|
11784
11939
|
reportId,
|
|
11785
11940
|
limit,
|
|
@@ -11788,13 +11943,25 @@ var McpGQLClient = class {
|
|
|
11788
11943
|
issueType,
|
|
11789
11944
|
severity
|
|
11790
11945
|
});
|
|
11946
|
+
let currentUserEmail = "%@%";
|
|
11947
|
+
try {
|
|
11948
|
+
const userInfo = await this.getUserInfo();
|
|
11949
|
+
if (userInfo?.email) {
|
|
11950
|
+
currentUserEmail = `%${userInfo.email}%`;
|
|
11951
|
+
}
|
|
11952
|
+
} catch (err) {
|
|
11953
|
+
logDebug("Failed to get user email, using default pattern", {
|
|
11954
|
+
error: err
|
|
11955
|
+
});
|
|
11956
|
+
}
|
|
11791
11957
|
const res = await this.clientSdk.GetReportFixes({
|
|
11792
11958
|
reportId,
|
|
11793
11959
|
limit,
|
|
11794
11960
|
offset,
|
|
11795
|
-
filters
|
|
11961
|
+
filters,
|
|
11962
|
+
currentUserEmail
|
|
11796
11963
|
});
|
|
11797
|
-
|
|
11964
|
+
logDebug("GraphQL: GetReportFixes successful", {
|
|
11798
11965
|
result: res,
|
|
11799
11966
|
fixCount: res.fixReport?.[0]?.fixes?.length || 0,
|
|
11800
11967
|
totalCount: res.fixReport?.[0]?.filteredFixesCount?.aggregate?.count || 0
|
|
@@ -11802,10 +11969,11 @@ var McpGQLClient = class {
|
|
|
11802
11969
|
if (res.fixReport.length === 0) {
|
|
11803
11970
|
return null;
|
|
11804
11971
|
}
|
|
11805
|
-
const
|
|
11806
|
-
|
|
11972
|
+
const fixes = this.mergeUserAndSystemFixes(res.fixReport?.[0], limit);
|
|
11973
|
+
const fixIds = fixes.map((fix) => fix.id);
|
|
11974
|
+
await this.updateFixesDownloadStatus(fixIds);
|
|
11807
11975
|
return {
|
|
11808
|
-
fixes
|
|
11976
|
+
fixes,
|
|
11809
11977
|
totalCount: res.fixReport?.[0]?.filteredFixesCount?.aggregate?.count || 0,
|
|
11810
11978
|
expiredReport: res.expiredReport?.[0] || null
|
|
11811
11979
|
};
|
|
@@ -11819,82 +11987,29 @@ var McpGQLClient = class {
|
|
|
11819
11987
|
}
|
|
11820
11988
|
}
|
|
11821
11989
|
};
|
|
11822
|
-
async function
|
|
11823
|
-
|
|
11824
|
-
const now = Date.now();
|
|
11825
|
-
const lastBrowserOpenTime = mobbConfigStore.get("lastBrowserOpenTime") || 0;
|
|
11826
|
-
if (now - lastBrowserOpenTime < MCP_TOOLS_BROWSER_COOLDOWN_MS) {
|
|
11827
|
-
logDebug(`browser cooldown active, skipping open for ${url}`);
|
|
11828
|
-
return;
|
|
11829
|
-
}
|
|
11830
|
-
}
|
|
11831
|
-
logDebug(`opening browser url ${url}`);
|
|
11832
|
-
await open4(url);
|
|
11833
|
-
mobbConfigStore.set("lastBrowserOpenTime", Date.now());
|
|
11834
|
-
}
|
|
11835
|
-
async function getMcpGQLClient({
|
|
11836
|
-
isToolsCall = false
|
|
11990
|
+
async function createAuthenticatedMcpGQLClient({
|
|
11991
|
+
isBackgoundCall = false
|
|
11837
11992
|
} = {}) {
|
|
11838
11993
|
logDebug("getting config", { apiToken: mobbConfigStore.get("apiToken") });
|
|
11839
|
-
const
|
|
11994
|
+
const initialClient = new McpGQLClient({
|
|
11840
11995
|
apiKey: process.env["MOBB_API_KEY"] || process.env["API_KEY"] || // fallback for backward compatibility
|
|
11841
11996
|
mobbConfigStore.get("apiToken") || "",
|
|
11842
11997
|
type: "apiKey"
|
|
11843
11998
|
});
|
|
11844
|
-
const isConnected = await
|
|
11845
|
-
logDebug("
|
|
11999
|
+
const isConnected = await initialClient.verifyApiConnection();
|
|
12000
|
+
logDebug("API connection status", { isConnected });
|
|
11846
12001
|
if (!isConnected) {
|
|
11847
12002
|
throw new ApiConnectionError("Error: failed to connect to Mobb API");
|
|
11848
12003
|
}
|
|
11849
|
-
logDebug("
|
|
11850
|
-
const userVerify = await
|
|
12004
|
+
logDebug("validating user token");
|
|
12005
|
+
const userVerify = await initialClient.validateUserToken();
|
|
11851
12006
|
if (userVerify) {
|
|
11852
|
-
return
|
|
11853
|
-
}
|
|
11854
|
-
logDebug("verifying token failed");
|
|
11855
|
-
const { publicKey, privateKey } = crypto2.generateKeyPairSync("rsa", {
|
|
11856
|
-
modulusLength: 2048
|
|
11857
|
-
});
|
|
11858
|
-
logDebug("creating cli login");
|
|
11859
|
-
const loginId = await inGqlClient.createCliLogin({
|
|
11860
|
-
publicKey: publicKey.export({ format: "pem", type: "pkcs1" }).toString()
|
|
11861
|
-
});
|
|
11862
|
-
if (!loginId) {
|
|
11863
|
-
throw new CliLoginError("Error: createCliLogin failed");
|
|
11864
|
-
}
|
|
11865
|
-
logDebug(`cli login created ${loginId}`);
|
|
11866
|
-
const webLoginUrl2 = `${WEB_APP_URL}/cli-login`;
|
|
11867
|
-
const browserUrl = `${webLoginUrl2}/${loginId}?hostname=${os2.hostname()}`;
|
|
11868
|
-
logDebug(`opening browser url ${browserUrl}`);
|
|
11869
|
-
await openBrowser(browserUrl, isToolsCall);
|
|
11870
|
-
logDebug(`waiting for login to complete`);
|
|
11871
|
-
let newApiToken = null;
|
|
11872
|
-
for (let i = 0; i < MCP_LOGIN_MAX_WAIT / MCP_LOGIN_CHECK_DELAY; i++) {
|
|
11873
|
-
const encryptedApiToken = await inGqlClient.getEncryptedApiToken({
|
|
11874
|
-
loginId
|
|
11875
|
-
});
|
|
11876
|
-
if (encryptedApiToken) {
|
|
11877
|
-
logDebug("encrypted API token received");
|
|
11878
|
-
newApiToken = crypto2.privateDecrypt(privateKey, Buffer.from(encryptedApiToken, "base64")).toString("utf-8");
|
|
11879
|
-
logDebug("API token decrypted");
|
|
11880
|
-
break;
|
|
11881
|
-
}
|
|
11882
|
-
await sleep(MCP_LOGIN_CHECK_DELAY);
|
|
11883
|
-
}
|
|
11884
|
-
if (!newApiToken) {
|
|
11885
|
-
throw new FailedToGetApiTokenError(
|
|
11886
|
-
"Error: failed to get encrypted api token"
|
|
11887
|
-
);
|
|
11888
|
-
}
|
|
11889
|
-
const newGqlClient = new McpGQLClient({ apiKey: newApiToken, type: "apiKey" });
|
|
11890
|
-
const loginSuccess = await newGqlClient.verifyToken();
|
|
11891
|
-
if (loginSuccess) {
|
|
11892
|
-
logDebug(`set api token ${newApiToken}`);
|
|
11893
|
-
mobbConfigStore.set("apiToken", newApiToken);
|
|
11894
|
-
} else {
|
|
11895
|
-
throw new AuthenticationError("Invalid API token");
|
|
12007
|
+
return initialClient;
|
|
11896
12008
|
}
|
|
11897
|
-
|
|
12009
|
+
const authService = new McpAuthService(initialClient);
|
|
12010
|
+
const newApiToken = await authService.authenticate(isBackgoundCall);
|
|
12011
|
+
mobbConfigStore.set("apiToken", newApiToken);
|
|
12012
|
+
return new McpGQLClient({ apiKey: newApiToken, type: "apiKey" });
|
|
11898
12013
|
}
|
|
11899
12014
|
|
|
11900
12015
|
// src/mcp/core/ToolRegistry.ts
|
|
@@ -11951,14 +12066,11 @@ var McpServer = class {
|
|
|
11951
12066
|
this.toolRegistry = new ToolRegistry();
|
|
11952
12067
|
this.setupHandlers();
|
|
11953
12068
|
this.setupProcessEventHandlers();
|
|
11954
|
-
logInfo("MCP server instance created"
|
|
12069
|
+
logInfo("MCP server instance created");
|
|
12070
|
+
logDebug("MCP server instance config", { config: config4 });
|
|
11955
12071
|
}
|
|
11956
|
-
|
|
11957
|
-
|
|
11958
|
-
logDebug("Process event handlers already setup, skipping");
|
|
11959
|
-
return;
|
|
11960
|
-
}
|
|
11961
|
-
const signals = {
|
|
12072
|
+
handleProcessSignal(signal, error) {
|
|
12073
|
+
const messages = {
|
|
11962
12074
|
SIGINT: "MCP server interrupted",
|
|
11963
12075
|
SIGTERM: "MCP server terminated",
|
|
11964
12076
|
exit: "MCP server exiting",
|
|
@@ -11966,26 +12078,46 @@ var McpServer = class {
|
|
|
11966
12078
|
unhandledRejection: "Unhandled promise rejection in MCP server",
|
|
11967
12079
|
warning: "Warning in MCP server"
|
|
11968
12080
|
};
|
|
11969
|
-
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
|
|
11974
|
-
|
|
11975
|
-
|
|
11976
|
-
|
|
11977
|
-
|
|
11978
|
-
|
|
11979
|
-
|
|
11980
|
-
|
|
11981
|
-
|
|
11982
|
-
|
|
11983
|
-
|
|
11984
|
-
|
|
11985
|
-
|
|
12081
|
+
const message = messages[signal] || `Unhandled signal: ${signal}`;
|
|
12082
|
+
if (signal === "exit") {
|
|
12083
|
+
const exitCode = error;
|
|
12084
|
+
if (exitCode === 0 || exitCode === void 0) {
|
|
12085
|
+
logDebug(`${message} (clean exit)`, { signal, exitCode });
|
|
12086
|
+
} else {
|
|
12087
|
+
logWarn(`${message} (exit code: ${exitCode})`, { signal, exitCode });
|
|
12088
|
+
}
|
|
12089
|
+
} else if (error) {
|
|
12090
|
+
logError(`${message}`, { error, signal });
|
|
12091
|
+
} else {
|
|
12092
|
+
logDebug(message, { signal });
|
|
12093
|
+
}
|
|
12094
|
+
if (signal === "SIGINT" || signal === "SIGTERM") {
|
|
12095
|
+
process.exit(0);
|
|
12096
|
+
}
|
|
12097
|
+
if (signal === "uncaughtException") {
|
|
12098
|
+
process.exit(1);
|
|
12099
|
+
}
|
|
12100
|
+
}
|
|
12101
|
+
setupProcessEventHandlers() {
|
|
12102
|
+
if (this.isEventHandlersSetup) {
|
|
12103
|
+
logDebug("Process event handlers already setup, skipping");
|
|
12104
|
+
return;
|
|
12105
|
+
}
|
|
12106
|
+
const signals = [
|
|
12107
|
+
"SIGINT",
|
|
12108
|
+
"SIGTERM",
|
|
12109
|
+
"exit",
|
|
12110
|
+
"uncaughtException",
|
|
12111
|
+
"unhandledRejection",
|
|
12112
|
+
"warning"
|
|
12113
|
+
];
|
|
12114
|
+
signals.forEach((signal) => {
|
|
12115
|
+
process.on(signal, (error) => {
|
|
12116
|
+
this.handleProcessSignal(signal, error);
|
|
12117
|
+
});
|
|
11986
12118
|
});
|
|
11987
12119
|
this.isEventHandlersSetup = true;
|
|
11988
|
-
|
|
12120
|
+
logInfo("Process event handlers registered");
|
|
11989
12121
|
}
|
|
11990
12122
|
createShutdownPromise() {
|
|
11991
12123
|
return new Promise((resolve) => {
|
|
@@ -11997,12 +12129,45 @@ var McpServer = class {
|
|
|
11997
12129
|
process.once("SIGTERM", cleanup);
|
|
11998
12130
|
});
|
|
11999
12131
|
}
|
|
12132
|
+
async triggerScanForNewAvailableFixes() {
|
|
12133
|
+
const gqlClient = await createAuthenticatedMcpGQLClient({
|
|
12134
|
+
isBackgoundCall: true
|
|
12135
|
+
});
|
|
12136
|
+
const isConnected = await gqlClient.verifyApiConnection();
|
|
12137
|
+
if (!isConnected) {
|
|
12138
|
+
logError("Failed to connect to the API, skipping scan");
|
|
12139
|
+
return;
|
|
12140
|
+
}
|
|
12141
|
+
if (process.env["WORKSPACE_FOLDER_PATHS"]) {
|
|
12142
|
+
logDebug("WORKSPACE_FOLDER_PATHS is set", {
|
|
12143
|
+
WORKSPACE_FOLDER_PATHS: process.env["WORKSPACE_FOLDER_PATHS"]
|
|
12144
|
+
});
|
|
12145
|
+
try {
|
|
12146
|
+
const checkForNewAvailableFixesTool = this.toolRegistry.getTool(
|
|
12147
|
+
MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES
|
|
12148
|
+
);
|
|
12149
|
+
logInfo("Triggering periodic scan for new available fixes");
|
|
12150
|
+
checkForNewAvailableFixesTool.triggerScan({
|
|
12151
|
+
path: process.env["WORKSPACE_FOLDER_PATHS"],
|
|
12152
|
+
gqlClient
|
|
12153
|
+
});
|
|
12154
|
+
} catch (error) {
|
|
12155
|
+
logError("Error getting workspace folder path tool", { error });
|
|
12156
|
+
}
|
|
12157
|
+
}
|
|
12158
|
+
}
|
|
12000
12159
|
async handleListToolsRequest(request) {
|
|
12001
|
-
logInfo("Received list_tools request"
|
|
12002
|
-
|
|
12160
|
+
logInfo("Received list_tools request");
|
|
12161
|
+
logDebug("list_tools request", {
|
|
12162
|
+
request: JSON.parse(JSON.stringify(request))
|
|
12163
|
+
});
|
|
12164
|
+
logDebug("Request", {
|
|
12003
12165
|
request: JSON.parse(JSON.stringify(request))
|
|
12004
12166
|
});
|
|
12005
|
-
|
|
12167
|
+
logDebug("env", {
|
|
12168
|
+
env: process.env
|
|
12169
|
+
});
|
|
12170
|
+
void this.triggerScanForNewAvailableFixes();
|
|
12006
12171
|
const toolsDefinitions = this.toolRegistry.getAllTools();
|
|
12007
12172
|
const response = {
|
|
12008
12173
|
tools: toolsDefinitions.map((tool) => ({
|
|
@@ -12016,13 +12181,13 @@ var McpServer = class {
|
|
|
12016
12181
|
}
|
|
12017
12182
|
}))
|
|
12018
12183
|
};
|
|
12019
|
-
|
|
12184
|
+
logDebug("Returning list_tools response", { response });
|
|
12020
12185
|
return response;
|
|
12021
12186
|
}
|
|
12022
12187
|
async handleCallToolRequest(request) {
|
|
12023
12188
|
const { name, arguments: args } = request.params;
|
|
12024
|
-
logInfo(`Received call tool request for ${name}
|
|
12025
|
-
|
|
12189
|
+
logInfo(`Received call tool request for ${name}`);
|
|
12190
|
+
logDebug("Request", {
|
|
12026
12191
|
request: JSON.parse(JSON.stringify(request))
|
|
12027
12192
|
});
|
|
12028
12193
|
try {
|
|
@@ -12035,10 +12200,11 @@ var McpServer = class {
|
|
|
12035
12200
|
});
|
|
12036
12201
|
throw new Error(errorMsg);
|
|
12037
12202
|
}
|
|
12038
|
-
|
|
12203
|
+
logInfo(`Executing tool: ${name}`);
|
|
12039
12204
|
const response = await tool.execute(args);
|
|
12040
12205
|
const serializedResponse = JSON.parse(JSON.stringify(response));
|
|
12041
|
-
logInfo(`Tool ${name} executed successfully
|
|
12206
|
+
logInfo(`Tool ${name} executed successfully`);
|
|
12207
|
+
logDebug(`Tool ${name} executed successfully`, {
|
|
12042
12208
|
responseType: typeof response,
|
|
12043
12209
|
hasContent: !!serializedResponse.content
|
|
12044
12210
|
});
|
|
@@ -12062,18 +12228,18 @@ var McpServer = class {
|
|
|
12062
12228
|
CallToolRequestSchema,
|
|
12063
12229
|
(request) => this.handleCallToolRequest(request)
|
|
12064
12230
|
);
|
|
12065
|
-
|
|
12231
|
+
logInfo("MCP server handlers registered");
|
|
12066
12232
|
}
|
|
12067
12233
|
registerTool(tool) {
|
|
12068
12234
|
this.toolRegistry.registerTool(tool);
|
|
12069
|
-
|
|
12235
|
+
logInfo(`Tool registered: ${tool.name}`);
|
|
12070
12236
|
}
|
|
12071
12237
|
async start() {
|
|
12072
12238
|
try {
|
|
12073
|
-
|
|
12239
|
+
logInfo("Starting MCP server");
|
|
12074
12240
|
const transport = new StdioServerTransport();
|
|
12075
12241
|
await this.server.connect(transport);
|
|
12076
|
-
|
|
12242
|
+
logDebug("MCP server is running on stdin/stdout");
|
|
12077
12243
|
process.stdin.resume();
|
|
12078
12244
|
await this.createShutdownPromise();
|
|
12079
12245
|
await this.stop();
|
|
@@ -12083,7 +12249,7 @@ var McpServer = class {
|
|
|
12083
12249
|
}
|
|
12084
12250
|
}
|
|
12085
12251
|
async stop() {
|
|
12086
|
-
|
|
12252
|
+
logDebug("MCP server shutting down");
|
|
12087
12253
|
}
|
|
12088
12254
|
};
|
|
12089
12255
|
|
|
@@ -12168,14 +12334,15 @@ var BaseTool = class {
|
|
|
12168
12334
|
};
|
|
12169
12335
|
}
|
|
12170
12336
|
async execute(args) {
|
|
12171
|
-
|
|
12172
|
-
const mcpGqlClient = await
|
|
12337
|
+
logDebug(`Authenticating tool: ${this.name}`, { args });
|
|
12338
|
+
const mcpGqlClient = await createAuthenticatedMcpGQLClient();
|
|
12173
12339
|
const userInfo = await mcpGqlClient.getUserInfo();
|
|
12174
|
-
logDebug("
|
|
12340
|
+
logDebug("User authenticated successfully", { userInfo });
|
|
12175
12341
|
const validatedArgs = this.validateInput(args);
|
|
12176
12342
|
logDebug(`Tool ${this.name} input validation successful`, {
|
|
12177
12343
|
validatedArgs
|
|
12178
12344
|
});
|
|
12345
|
+
logInfo(`Executing tool: ${this.name}`);
|
|
12179
12346
|
const result = await this.executeInternal(validatedArgs);
|
|
12180
12347
|
logInfo(`Tool ${this.name} executed successfully`);
|
|
12181
12348
|
return result;
|
|
@@ -12209,7 +12376,7 @@ var BaseTool = class {
|
|
|
12209
12376
|
};
|
|
12210
12377
|
|
|
12211
12378
|
// src/mcp/core/prompts.ts
|
|
12212
|
-
function
|
|
12379
|
+
function friendlyType(s) {
|
|
12213
12380
|
const withoutUnderscores = s.replace(/_/g, " ");
|
|
12214
12381
|
const result = withoutUnderscores.replace(/([a-z])([A-Z])/g, "$1 $2");
|
|
12215
12382
|
return result.charAt(0).toUpperCase() + result.slice(1);
|
|
@@ -12229,13 +12396,15 @@ var applyFixesPrompt = ({
|
|
|
12229
12396
|
return noFixesReturnedForParameters;
|
|
12230
12397
|
}
|
|
12231
12398
|
const fixList = fixes.map((fix) => {
|
|
12232
|
-
const vulnerabilityType =
|
|
12399
|
+
const vulnerabilityType = friendlyType(fix.safeIssueType);
|
|
12233
12400
|
const vulnerabilityDescription = fix.patchAndQuestions?.__typename === "FixData" ? fix.patchAndQuestions.extraContext?.fixDescription : void 0;
|
|
12234
12401
|
const patch = fix.patchAndQuestions?.__typename === "FixData" ? fix.patchAndQuestions.patch : void 0;
|
|
12402
|
+
const gitBlameLogin = fix.gitBlameLogin;
|
|
12235
12403
|
return {
|
|
12236
12404
|
vulnerabilityType,
|
|
12237
12405
|
vulnerabilityDescription,
|
|
12238
|
-
patch
|
|
12406
|
+
patch,
|
|
12407
|
+
gitBlameLogin
|
|
12239
12408
|
};
|
|
12240
12409
|
});
|
|
12241
12410
|
return `## CRITICAL INSTRUCTIONS - READ CAREFULLY
|
|
@@ -12287,7 +12456,9 @@ ${fixList.map(
|
|
|
12287
12456
|
|
|
12288
12457
|
**\u{1F4DD} Description:** ${fix.vulnerabilityDescription || "Security vulnerability fix"}
|
|
12289
12458
|
|
|
12290
|
-
|
|
12459
|
+
${fix.gitBlameLogin ? `**\u{1F464} Git Blame:** The code that needs to be fixed was last modified by: \`${fix.gitBlameLogin}\`
|
|
12460
|
+
|
|
12461
|
+
` : ""}**\u{1F527} Action Required:** Apply the following patch exactly as shown
|
|
12291
12462
|
|
|
12292
12463
|
**\u{1F4C1} Patch to Apply:**
|
|
12293
12464
|
\`\`\`diff
|
|
@@ -12334,7 +12505,7 @@ We were unable to find a previous vulnerability report for this repository. This
|
|
|
12334
12505
|
|
|
12335
12506
|
### \u{1F3AF} Recommended Actions
|
|
12336
12507
|
1. **Run a new security scan** to analyze your codebase
|
|
12337
|
-
- Use the \`
|
|
12508
|
+
- Use the \`${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES}\` tool to start a fresh scan
|
|
12338
12509
|
- This will analyze your current code for security issues
|
|
12339
12510
|
|
|
12340
12511
|
2. **Verify repository access**
|
|
@@ -12362,7 +12533,7 @@ Your most recent vulnerability report for this repository **expired on ${lastRep
|
|
|
12362
12533
|
|
|
12363
12534
|
### \u{1F3AF} Recommended Actions
|
|
12364
12535
|
1. **Run a fresh security scan** to generate an up-to-date vulnerability report.
|
|
12365
|
-
- Use the \`
|
|
12536
|
+
- Use the \`${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES}\` tool.
|
|
12366
12537
|
2. **Verify repository access** if scans fail to run or the repository has moved.
|
|
12367
12538
|
3. **Review your CI/CD pipeline** to ensure regular scans are triggered.
|
|
12368
12539
|
|
|
@@ -12416,7 +12587,7 @@ ${applyFixesPrompt({
|
|
|
12416
12587
|
hasMore,
|
|
12417
12588
|
nextOffset: 0,
|
|
12418
12589
|
shownCount: fixReport.fixes.length,
|
|
12419
|
-
currentTool:
|
|
12590
|
+
currentTool: MCP_TOOL_FETCH_AVAILABLE_FIXES,
|
|
12420
12591
|
offset
|
|
12421
12592
|
})}`;
|
|
12422
12593
|
};
|
|
@@ -12441,7 +12612,6 @@ To perform a **rescan** of your repository (fetching a brand-new vulnerability r
|
|
|
12441
12612
|
This will start a new analysis, discard any cached results.
|
|
12442
12613
|
|
|
12443
12614
|
\u26A0\uFE0F *Note:* A full rescan may take longer to complete than simply fetching additional fixes because your repository is re-uploaded and re-analyzed from scratch.
|
|
12444
|
-
|
|
12445
12615
|
`;
|
|
12446
12616
|
var noFixesFoundPrompt = ({
|
|
12447
12617
|
scannedFiles
|
|
@@ -12467,20 +12637,19 @@ var fixesPrompt = ({
|
|
|
12467
12637
|
|
|
12468
12638
|
${applyFixesPrompt({
|
|
12469
12639
|
fixes,
|
|
12470
|
-
totalCount,
|
|
12471
12640
|
hasMore,
|
|
12641
|
+
totalCount,
|
|
12472
12642
|
nextOffset,
|
|
12473
12643
|
shownCount,
|
|
12474
|
-
currentTool:
|
|
12644
|
+
currentTool: MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES,
|
|
12475
12645
|
offset
|
|
12476
12646
|
})}
|
|
12477
12647
|
|
|
12478
12648
|
${nextStepsPrompt({ scannedFiles })}
|
|
12479
12649
|
`;
|
|
12480
12650
|
};
|
|
12481
|
-
var noFreshFixesPrompt = `No fresh fixes available for this repository at this time.
|
|
12482
|
-
|
|
12483
|
-
var initialScanInProgressPrompt = `Initial scan in progress. Call the tool again in 1 minute to check for available fixes.`;
|
|
12651
|
+
var noFreshFixesPrompt = `No fresh fixes available for this repository at this time. No user message needed. Do not show any comments to the user.`;
|
|
12652
|
+
var initialScanInProgressPrompt = `Initial scan is still in progress. No user message needed. Do not show any comments to the user.`;
|
|
12484
12653
|
var freshFixesPrompt = ({ fixes }) => {
|
|
12485
12654
|
return `Here are the fresh fixes to the vulnerabilities discovered by Mobb MCP
|
|
12486
12655
|
|
|
@@ -12490,7 +12659,7 @@ ${applyFixesPrompt({
|
|
|
12490
12659
|
hasMore: false,
|
|
12491
12660
|
nextOffset: 0,
|
|
12492
12661
|
shownCount: fixes.length,
|
|
12493
|
-
currentTool:
|
|
12662
|
+
currentTool: MCP_TOOL_FETCH_AVAILABLE_FIXES,
|
|
12494
12663
|
offset: 0
|
|
12495
12664
|
})}
|
|
12496
12665
|
`;
|
|
@@ -12579,23 +12748,135 @@ var getLocalFiles = async ({
|
|
|
12579
12748
|
return filesWithStats.filter((file) => file.lastEdited > 0);
|
|
12580
12749
|
};
|
|
12581
12750
|
|
|
12582
|
-
// src/mcp/services/
|
|
12751
|
+
// src/mcp/services/FileOperations.ts
|
|
12583
12752
|
import fs11 from "fs";
|
|
12584
12753
|
import path12 from "path";
|
|
12585
12754
|
import AdmZip2 from "adm-zip";
|
|
12755
|
+
var FileOperations = class {
|
|
12756
|
+
/**
|
|
12757
|
+
* Creates a ZIP archive containing the specified source files
|
|
12758
|
+
* @param fileList Array of relative file paths to include
|
|
12759
|
+
* @param repositoryPath Base path for resolving relative file paths
|
|
12760
|
+
* @param maxFileSize Maximum size allowed for individual files
|
|
12761
|
+
* @returns ZIP archive as a Buffer with metadata
|
|
12762
|
+
*/
|
|
12763
|
+
async createSourceCodeArchive(fileList, repositoryPath, maxFileSize) {
|
|
12764
|
+
logDebug("FilePacking: packing files");
|
|
12765
|
+
const zip = new AdmZip2();
|
|
12766
|
+
let packedFilesCount = 0;
|
|
12767
|
+
const resolvedRepoPath = path12.resolve(repositoryPath);
|
|
12768
|
+
for (const filepath of fileList) {
|
|
12769
|
+
const absoluteFilepath = path12.join(repositoryPath, filepath);
|
|
12770
|
+
const resolvedFilePath = path12.resolve(absoluteFilepath);
|
|
12771
|
+
if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
|
|
12772
|
+
logDebug(
|
|
12773
|
+
`Skipping ${filepath} due to potential path traversal security risk`
|
|
12774
|
+
);
|
|
12775
|
+
continue;
|
|
12776
|
+
}
|
|
12777
|
+
if (!FileUtils.shouldPackFile(absoluteFilepath, maxFileSize)) {
|
|
12778
|
+
logDebug(
|
|
12779
|
+
`Excluding ${filepath} - file is too large, binary, or matches exclusion rules`
|
|
12780
|
+
);
|
|
12781
|
+
continue;
|
|
12782
|
+
}
|
|
12783
|
+
const fileContent = await this.readSourceFile(absoluteFilepath, filepath);
|
|
12784
|
+
if (fileContent) {
|
|
12785
|
+
zip.addFile(filepath, fileContent);
|
|
12786
|
+
packedFilesCount++;
|
|
12787
|
+
}
|
|
12788
|
+
}
|
|
12789
|
+
const archiveBuffer = zip.toBuffer();
|
|
12790
|
+
const result = {
|
|
12791
|
+
archive: archiveBuffer,
|
|
12792
|
+
packedFilesCount,
|
|
12793
|
+
totalSize: archiveBuffer.length
|
|
12794
|
+
};
|
|
12795
|
+
logInfo("Files packed successfully");
|
|
12796
|
+
return result;
|
|
12797
|
+
}
|
|
12798
|
+
/**
|
|
12799
|
+
* Validates that file paths are within the repository and safe to access
|
|
12800
|
+
* @param fileList Array of relative file paths to validate
|
|
12801
|
+
* @param repositoryPath Base path for validation
|
|
12802
|
+
* @returns Array of validated file paths
|
|
12803
|
+
*/
|
|
12804
|
+
async validateFilePaths(fileList, repositoryPath) {
|
|
12805
|
+
const resolvedRepoPath = path12.resolve(repositoryPath);
|
|
12806
|
+
const validatedPaths = [];
|
|
12807
|
+
for (const filepath of fileList) {
|
|
12808
|
+
const absoluteFilepath = path12.join(repositoryPath, filepath);
|
|
12809
|
+
const resolvedFilePath = path12.resolve(absoluteFilepath);
|
|
12810
|
+
if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
|
|
12811
|
+
logDebug(`Rejecting ${filepath} - path traversal attempt detected`);
|
|
12812
|
+
continue;
|
|
12813
|
+
}
|
|
12814
|
+
try {
|
|
12815
|
+
await fs11.promises.access(absoluteFilepath, fs11.constants.R_OK);
|
|
12816
|
+
validatedPaths.push(filepath);
|
|
12817
|
+
} catch (error) {
|
|
12818
|
+
logDebug(`Skipping ${filepath} - file is not accessible: ${error}`);
|
|
12819
|
+
}
|
|
12820
|
+
}
|
|
12821
|
+
return validatedPaths;
|
|
12822
|
+
}
|
|
12823
|
+
/**
|
|
12824
|
+
* Reads source files and returns their data
|
|
12825
|
+
* @param filePaths Array of absolute file paths to read
|
|
12826
|
+
* @returns Array of file data objects
|
|
12827
|
+
*/
|
|
12828
|
+
async readSourceFiles(filePaths) {
|
|
12829
|
+
const fileDataArray = [];
|
|
12830
|
+
for (const absolutePath of filePaths) {
|
|
12831
|
+
try {
|
|
12832
|
+
const content = await fs11.promises.readFile(absolutePath);
|
|
12833
|
+
const relativePath = path12.basename(absolutePath);
|
|
12834
|
+
fileDataArray.push({
|
|
12835
|
+
relativePath,
|
|
12836
|
+
absolutePath,
|
|
12837
|
+
content
|
|
12838
|
+
});
|
|
12839
|
+
} catch (error) {
|
|
12840
|
+
logError(`Failed to read file ${absolutePath}: ${error}`);
|
|
12841
|
+
}
|
|
12842
|
+
}
|
|
12843
|
+
return fileDataArray;
|
|
12844
|
+
}
|
|
12845
|
+
/**
|
|
12846
|
+
* Safely reads a single source file
|
|
12847
|
+
* @param absoluteFilepath Absolute path to the file
|
|
12848
|
+
* @param relativeFilepath Relative path for logging purposes
|
|
12849
|
+
* @returns File content as Buffer or null if failed
|
|
12850
|
+
*/
|
|
12851
|
+
async readSourceFile(absoluteFilepath, relativeFilepath) {
|
|
12852
|
+
try {
|
|
12853
|
+
return await fs11.promises.readFile(absoluteFilepath);
|
|
12854
|
+
} catch (fsError) {
|
|
12855
|
+
logError(`Failed to read ${relativeFilepath} from filesystem: ${fsError}`);
|
|
12856
|
+
return null;
|
|
12857
|
+
}
|
|
12858
|
+
}
|
|
12859
|
+
};
|
|
12860
|
+
|
|
12861
|
+
// src/mcp/services/ScanFiles.ts
|
|
12586
12862
|
var scanFiles = async (fileList, repositoryPath, gqlClient) => {
|
|
12587
|
-
const repoUploadInfo = await
|
|
12863
|
+
const repoUploadInfo = await initializeSecurityReport(gqlClient);
|
|
12588
12864
|
const fixReportId = repoUploadInfo.fixReportId;
|
|
12589
|
-
const
|
|
12590
|
-
await
|
|
12865
|
+
const fileOperations = new FileOperations();
|
|
12866
|
+
const packingResult = await fileOperations.createSourceCodeArchive(
|
|
12867
|
+
fileList,
|
|
12868
|
+
repositoryPath,
|
|
12869
|
+
MCP_MAX_FILE_SIZE
|
|
12870
|
+
);
|
|
12871
|
+
await uploadSourceCodeArchive(packingResult.archive, repoUploadInfo);
|
|
12591
12872
|
const projectId = await getProjectId(gqlClient);
|
|
12592
|
-
await
|
|
12873
|
+
await executeSecurityScan({ fixReportId, projectId, gqlClient });
|
|
12593
12874
|
return {
|
|
12594
12875
|
fixReportId,
|
|
12595
12876
|
projectId
|
|
12596
12877
|
};
|
|
12597
12878
|
};
|
|
12598
|
-
var
|
|
12879
|
+
var initializeSecurityReport = async (gqlClient) => {
|
|
12599
12880
|
if (!gqlClient) {
|
|
12600
12881
|
throw new GqlClientError();
|
|
12601
12882
|
}
|
|
@@ -12603,74 +12884,33 @@ var initializeReport = async (gqlClient) => {
|
|
|
12603
12884
|
const {
|
|
12604
12885
|
uploadS3BucketInfo: { repoUploadInfo }
|
|
12605
12886
|
} = await gqlClient.uploadS3BucketInfo();
|
|
12606
|
-
|
|
12887
|
+
logDebug("Upload info retrieved");
|
|
12607
12888
|
return repoUploadInfo;
|
|
12608
12889
|
} catch (error) {
|
|
12609
12890
|
const message = error.message;
|
|
12610
|
-
throw new ReportInitializationError(
|
|
12611
|
-
|
|
12612
|
-
};
|
|
12613
|
-
var packFiles = async (fileList, repositoryPath) => {
|
|
12614
|
-
try {
|
|
12615
|
-
logInfo(`FilePacking: packing files from ${repositoryPath}`);
|
|
12616
|
-
const zip = new AdmZip2();
|
|
12617
|
-
let packedFilesCount = 0;
|
|
12618
|
-
const resolvedRepoPath = path12.resolve(repositoryPath);
|
|
12619
|
-
logInfo("FilePacking: compressing files");
|
|
12620
|
-
for (const filepath of fileList) {
|
|
12621
|
-
const absoluteFilepath = path12.join(repositoryPath, filepath);
|
|
12622
|
-
const resolvedFilePath = path12.resolve(absoluteFilepath);
|
|
12623
|
-
if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
|
|
12624
|
-
logInfo(
|
|
12625
|
-
`FilePacking: skipping ${filepath} due to potential path traversal`
|
|
12626
|
-
);
|
|
12627
|
-
continue;
|
|
12628
|
-
}
|
|
12629
|
-
if (!FileUtils.shouldPackFile(absoluteFilepath, MCP_MAX_FILE_SIZE)) {
|
|
12630
|
-
logInfo(
|
|
12631
|
-
`FilePacking: ignoring ${filepath} because it is excluded or invalid`
|
|
12632
|
-
);
|
|
12633
|
-
continue;
|
|
12634
|
-
}
|
|
12635
|
-
let data;
|
|
12636
|
-
try {
|
|
12637
|
-
data = fs11.readFileSync(absoluteFilepath);
|
|
12638
|
-
} catch (fsError) {
|
|
12639
|
-
logInfo(
|
|
12640
|
-
`FilePacking: failed to read ${filepath} from filesystem: ${fsError}`
|
|
12641
|
-
);
|
|
12642
|
-
continue;
|
|
12643
|
-
}
|
|
12644
|
-
zip.addFile(filepath, data);
|
|
12645
|
-
packedFilesCount++;
|
|
12646
|
-
}
|
|
12647
|
-
const zipBuffer = zip.toBuffer();
|
|
12648
|
-
logInfo(
|
|
12649
|
-
`FilePacking: read ${packedFilesCount} source files. total size: ${zipBuffer.length} bytes`
|
|
12891
|
+
throw new ReportInitializationError(
|
|
12892
|
+
`Error initializing security report: ${message}`
|
|
12650
12893
|
);
|
|
12651
|
-
logInfo("Files packed successfully", { fileCount: fileList.length });
|
|
12652
|
-
return zipBuffer;
|
|
12653
|
-
} catch (error) {
|
|
12654
|
-
const message = error.message;
|
|
12655
|
-
throw new FileProcessingError(`Error packing files: ${message}`);
|
|
12656
12894
|
}
|
|
12657
12895
|
};
|
|
12658
|
-
var
|
|
12896
|
+
var uploadSourceCodeArchive = async (archiveBuffer, repoUploadInfo) => {
|
|
12659
12897
|
if (!repoUploadInfo) {
|
|
12660
|
-
throw new FileUploadError("Upload info is required");
|
|
12898
|
+
throw new FileUploadError("Upload info is required for source code archive");
|
|
12661
12899
|
}
|
|
12662
12900
|
try {
|
|
12663
12901
|
await uploadFile({
|
|
12664
|
-
file:
|
|
12902
|
+
file: archiveBuffer,
|
|
12665
12903
|
url: repoUploadInfo.url,
|
|
12666
12904
|
uploadFields: JSON.parse(repoUploadInfo.uploadFieldsJSON),
|
|
12667
12905
|
uploadKey: repoUploadInfo.uploadKey
|
|
12668
12906
|
});
|
|
12669
12907
|
logInfo("File uploaded successfully");
|
|
12670
12908
|
} catch (error) {
|
|
12671
|
-
logError("
|
|
12909
|
+
logError("Source code archive upload failed", {
|
|
12910
|
+
error: error.message
|
|
12911
|
+
});
|
|
12672
12912
|
throw new FileUploadError(
|
|
12673
|
-
`Failed to upload
|
|
12913
|
+
`Failed to upload source code archive: ${error.message}`
|
|
12674
12914
|
);
|
|
12675
12915
|
}
|
|
12676
12916
|
};
|
|
@@ -12679,10 +12919,10 @@ var getProjectId = async (gqlClient) => {
|
|
|
12679
12919
|
throw new GqlClientError();
|
|
12680
12920
|
}
|
|
12681
12921
|
const projectId = await gqlClient.getProjectId();
|
|
12682
|
-
|
|
12922
|
+
logDebug("Project ID retrieved");
|
|
12683
12923
|
return projectId;
|
|
12684
12924
|
};
|
|
12685
|
-
var
|
|
12925
|
+
var executeSecurityScan = async ({
|
|
12686
12926
|
fixReportId,
|
|
12687
12927
|
projectId,
|
|
12688
12928
|
gqlClient
|
|
@@ -12690,7 +12930,7 @@ var runScan = async ({
|
|
|
12690
12930
|
if (!gqlClient) {
|
|
12691
12931
|
throw new GqlClientError();
|
|
12692
12932
|
}
|
|
12693
|
-
logInfo("Starting scan"
|
|
12933
|
+
logInfo("Starting scan");
|
|
12694
12934
|
const submitVulnerabilityReportVariables = {
|
|
12695
12935
|
fixReportId,
|
|
12696
12936
|
projectId,
|
|
@@ -12703,25 +12943,28 @@ var runScan = async ({
|
|
|
12703
12943
|
submitVulnerabilityReportVariables
|
|
12704
12944
|
);
|
|
12705
12945
|
if (submitRes.submitVulnerabilityReport.__typename !== "VulnerabilityReport") {
|
|
12706
|
-
|
|
12707
|
-
|
|
12946
|
+
throw new ScanError(
|
|
12947
|
+
`Security scan submission failed: ${submitRes.submitVulnerabilityReport.__typename}`
|
|
12948
|
+
);
|
|
12949
|
+
}
|
|
12950
|
+
const analysisId = submitRes.submitVulnerabilityReport.fixReportId;
|
|
12951
|
+
logInfo("Vulnerability report submitted successfully");
|
|
12952
|
+
try {
|
|
12953
|
+
await gqlClient.subscribeToGetAnalysis({
|
|
12954
|
+
subscribeToAnalysisParams: { analysisId },
|
|
12955
|
+
callback: async (completedAnalysisId) => {
|
|
12956
|
+
logInfo("Security analysis completed successfully", {
|
|
12957
|
+
analysisId: completedAnalysisId
|
|
12958
|
+
});
|
|
12959
|
+
},
|
|
12960
|
+
callbackStates: ["Finished" /* Finished */],
|
|
12961
|
+
timeoutInMs: MCP_VUL_REPORT_DIGEST_TIMEOUT_MS
|
|
12708
12962
|
});
|
|
12709
|
-
|
|
12963
|
+
} catch (error) {
|
|
12964
|
+
logError("Security analysis failed or timed out", { error, analysisId });
|
|
12965
|
+
throw new ScanError(`Security analysis failed: ${error.message}`);
|
|
12710
12966
|
}
|
|
12711
|
-
|
|
12712
|
-
analysisId: submitRes.submitVulnerabilityReport.fixReportId
|
|
12713
|
-
});
|
|
12714
|
-
logInfo("Starting analysis subscription");
|
|
12715
|
-
await gqlClient.subscribeToGetAnalysis({
|
|
12716
|
-
subscribeToAnalysisParams: {
|
|
12717
|
-
analysisId: submitRes.submitVulnerabilityReport.fixReportId
|
|
12718
|
-
},
|
|
12719
|
-
callback: () => {
|
|
12720
|
-
},
|
|
12721
|
-
callbackStates: ["Finished" /* Finished */],
|
|
12722
|
-
timeoutInMs: MCP_VUL_REPORT_DIGEST_TIMEOUT_MS
|
|
12723
|
-
});
|
|
12724
|
-
logInfo("Analysis subscription completed");
|
|
12967
|
+
logDebug("Security scan completed successfully", { fixReportId, projectId });
|
|
12725
12968
|
};
|
|
12726
12969
|
|
|
12727
12970
|
// src/mcp/tools/checkForNewAvailableFixes/CheckForNewAvailableFixesService.ts
|
|
@@ -12742,6 +12985,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
12742
12985
|
__publicField(this, "reportedFixes", []);
|
|
12743
12986
|
__publicField(this, "intervalId", null);
|
|
12744
12987
|
__publicField(this, "isInitialScanComplete", false);
|
|
12988
|
+
__publicField(this, "gqlClient", null);
|
|
12745
12989
|
}
|
|
12746
12990
|
static getInstance() {
|
|
12747
12991
|
if (!_CheckForNewAvailableFixesService.instance) {
|
|
@@ -12763,25 +13007,28 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
12763
13007
|
}
|
|
12764
13008
|
}
|
|
12765
13009
|
/**
|
|
12766
|
-
*
|
|
12767
|
-
*
|
|
12768
|
-
* simply returns a placeholder string so that the tool can be wired into the
|
|
12769
|
-
* system and used in tests.
|
|
13010
|
+
* Scans repository for security vulnerabilities and identifies new fixes
|
|
13011
|
+
* since the last scan.
|
|
12770
13012
|
*/
|
|
12771
|
-
async
|
|
12772
|
-
|
|
12773
|
-
|
|
12774
|
-
|
|
13013
|
+
async scanForSecurityVulnerabilities({
|
|
13014
|
+
path: path13
|
|
13015
|
+
}) {
|
|
13016
|
+
logDebug("Scanning for new security vulnerabilities", { path: path13 });
|
|
13017
|
+
if (!this.gqlClient) {
|
|
13018
|
+
logInfo("No GQL client found, skipping scan");
|
|
13019
|
+
return;
|
|
13020
|
+
}
|
|
13021
|
+
const isConnected = await this.gqlClient.verifyApiConnection();
|
|
12775
13022
|
if (!isConnected) {
|
|
12776
13023
|
logError("Failed to connect to the API, scan aborted");
|
|
12777
13024
|
return;
|
|
12778
13025
|
}
|
|
12779
|
-
|
|
13026
|
+
logDebug("Connected to the API, assembling list of files to scan", { path: path13 });
|
|
12780
13027
|
const files = await getLocalFiles({
|
|
12781
13028
|
path: path13,
|
|
12782
13029
|
maxFileSize: MCP_MAX_FILE_SIZE
|
|
12783
13030
|
});
|
|
12784
|
-
|
|
13031
|
+
logDebug("Active files", { files });
|
|
12785
13032
|
const filesToScan = files.filter((file) => {
|
|
12786
13033
|
const lastScannedEditTime = this.filesLastScanned[file.fullPath];
|
|
12787
13034
|
if (!lastScannedEditTime) {
|
|
@@ -12790,34 +13037,45 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
12790
13037
|
return file.lastEdited > lastScannedEditTime;
|
|
12791
13038
|
});
|
|
12792
13039
|
if (filesToScan.length === 0) {
|
|
12793
|
-
logInfo("No files
|
|
13040
|
+
logInfo("No files require scanning");
|
|
12794
13041
|
return;
|
|
12795
13042
|
}
|
|
12796
|
-
|
|
13043
|
+
logDebug("Files requiring security scan", { filesToScan });
|
|
12797
13044
|
const { fixReportId, projectId } = await scanFiles(
|
|
12798
13045
|
filesToScan.map((file) => file.relativePath),
|
|
12799
13046
|
path13,
|
|
12800
|
-
gqlClient
|
|
13047
|
+
this.gqlClient
|
|
13048
|
+
);
|
|
13049
|
+
logInfo(
|
|
13050
|
+
`Security scan completed for ${path13} reportId: ${fixReportId} projectId: ${projectId}`
|
|
12801
13051
|
);
|
|
12802
|
-
|
|
12803
|
-
const fixes = await gqlClient.getReportFixesPaginated({
|
|
13052
|
+
const fixes = await this.gqlClient.getReportFixesPaginated({
|
|
12804
13053
|
reportId: fixReportId,
|
|
12805
13054
|
offset: 0,
|
|
12806
13055
|
limit: 1e3
|
|
12807
13056
|
});
|
|
12808
|
-
const newFixes = fixes?.fixes?.filter(
|
|
12809
|
-
|
|
12810
|
-
|
|
12811
|
-
|
|
13057
|
+
const newFixes = fixes?.fixes?.filter(
|
|
13058
|
+
(fix) => !this.isFixAlreadyReported(fix)
|
|
13059
|
+
);
|
|
13060
|
+
logInfo(
|
|
13061
|
+
`Security fixes retrieved, total: ${fixes?.fixes?.length || 0}, new: ${newFixes?.length || 0}`
|
|
13062
|
+
);
|
|
13063
|
+
this.updateFreshFixesCache(newFixes || [], filesToScan);
|
|
13064
|
+
this.updateFilesScanTimestamps(filesToScan);
|
|
13065
|
+
this.isInitialScanComplete = true;
|
|
13066
|
+
}
|
|
13067
|
+
updateFreshFixesCache(newFixes, filesToScan) {
|
|
13068
|
+
this.freshFixes = this.freshFixes.filter((fix) => !this.isFixFromOldScan(fix, filesToScan)).concat(newFixes).sort((a, b) => {
|
|
13069
|
+
return (b.severityValue ?? 0) - (a.severityValue ?? 0);
|
|
12812
13070
|
});
|
|
12813
|
-
|
|
12814
|
-
|
|
13071
|
+
logInfo(`Fresh fixes cache updated, total: ${this.freshFixes.length}`);
|
|
13072
|
+
}
|
|
13073
|
+
updateFilesScanTimestamps(filesToScan) {
|
|
12815
13074
|
filesToScan.forEach((file) => {
|
|
12816
13075
|
this.filesLastScanned[file.fullPath] = file.lastEdited;
|
|
12817
13076
|
});
|
|
12818
|
-
this.isInitialScanComplete = true;
|
|
12819
13077
|
}
|
|
12820
|
-
|
|
13078
|
+
isFixAlreadyReported(fix) {
|
|
12821
13079
|
return this.reportedFixes.some(
|
|
12822
13080
|
(reportedFix) => reportedFix.sharedState?.id === fix.sharedState?.id
|
|
12823
13081
|
);
|
|
@@ -12828,10 +13086,10 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
12828
13086
|
if (!fixFile) {
|
|
12829
13087
|
return false;
|
|
12830
13088
|
}
|
|
12831
|
-
|
|
13089
|
+
logDebug("Checking if fix is from old scan", {
|
|
12832
13090
|
fixFile,
|
|
12833
13091
|
filesToScan,
|
|
12834
|
-
|
|
13092
|
+
isFromOldScan: filesToScan.some((file) => file.relativePath === fixFile)
|
|
12835
13093
|
});
|
|
12836
13094
|
return filesToScan.some((file) => file.relativePath === fixFile);
|
|
12837
13095
|
}
|
|
@@ -12840,31 +13098,51 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
12840
13098
|
this.path = path13;
|
|
12841
13099
|
this.reset();
|
|
12842
13100
|
}
|
|
12843
|
-
|
|
12844
|
-
|
|
12845
|
-
this.intervalId = setInterval(() => {
|
|
12846
|
-
logDebug("Triggering periodic scan", { path: path13 });
|
|
12847
|
-
this.scan({ path: path13 }).catch((error) => {
|
|
12848
|
-
logError("Error during periodic scan", { error });
|
|
12849
|
-
});
|
|
12850
|
-
}, MCP_PERIODIC_CHECK_INTERVAL);
|
|
12851
|
-
logDebug("Triggering initial scan", { path: path13 });
|
|
12852
|
-
this.scan({ path: path13 }).catch((error) => {
|
|
12853
|
-
logError("Error during initial scan", { error });
|
|
12854
|
-
});
|
|
12855
|
-
}
|
|
13101
|
+
this.gqlClient = await createAuthenticatedMcpGQLClient();
|
|
13102
|
+
this.triggerScan({ path: path13, gqlClient: this.gqlClient });
|
|
12856
13103
|
if (this.freshFixes.length > 0) {
|
|
12857
|
-
|
|
12858
|
-
if (freshFixes.length > 0) {
|
|
12859
|
-
this.reportedFixes.concat(freshFixes);
|
|
12860
|
-
return freshFixesPrompt({ fixes: freshFixes });
|
|
12861
|
-
}
|
|
13104
|
+
return this.generateFreshFixesResponse();
|
|
12862
13105
|
}
|
|
12863
13106
|
if (!this.isInitialScanComplete) {
|
|
12864
13107
|
return initialScanInProgressPrompt;
|
|
12865
13108
|
}
|
|
12866
13109
|
return noFreshFixesPrompt;
|
|
12867
13110
|
}
|
|
13111
|
+
triggerScan({
|
|
13112
|
+
path: path13,
|
|
13113
|
+
gqlClient
|
|
13114
|
+
}) {
|
|
13115
|
+
this.gqlClient = gqlClient;
|
|
13116
|
+
if (!this.intervalId) {
|
|
13117
|
+
this.startPeriodicScanning(path13);
|
|
13118
|
+
this.executeInitialScan(path13);
|
|
13119
|
+
}
|
|
13120
|
+
}
|
|
13121
|
+
startPeriodicScanning(path13) {
|
|
13122
|
+
logDebug("Starting periodic scan for new security vulnerabilities", {
|
|
13123
|
+
path: path13
|
|
13124
|
+
});
|
|
13125
|
+
this.intervalId = setInterval(() => {
|
|
13126
|
+
logDebug("Triggering periodic security scan", { path: path13 });
|
|
13127
|
+
this.scanForSecurityVulnerabilities({ path: path13 }).catch((error) => {
|
|
13128
|
+
logError("Error during periodic security scan", { error });
|
|
13129
|
+
});
|
|
13130
|
+
}, MCP_PERIODIC_CHECK_INTERVAL);
|
|
13131
|
+
}
|
|
13132
|
+
executeInitialScan(path13) {
|
|
13133
|
+
logDebug("Triggering initial security scan", { path: path13 });
|
|
13134
|
+
this.scanForSecurityVulnerabilities({ path: path13 }).catch((error) => {
|
|
13135
|
+
logError("Error during initial security scan", { error });
|
|
13136
|
+
});
|
|
13137
|
+
}
|
|
13138
|
+
generateFreshFixesResponse() {
|
|
13139
|
+
const freshFixes = this.freshFixes.splice(0, 3);
|
|
13140
|
+
if (freshFixes.length > 0) {
|
|
13141
|
+
this.reportedFixes.push(...freshFixes);
|
|
13142
|
+
return freshFixesPrompt({ fixes: freshFixes });
|
|
13143
|
+
}
|
|
13144
|
+
return noFreshFixesPrompt;
|
|
13145
|
+
}
|
|
12868
13146
|
};
|
|
12869
13147
|
__publicField(_CheckForNewAvailableFixesService, "instance");
|
|
12870
13148
|
var CheckForNewAvailableFixesService = _CheckForNewAvailableFixesService;
|
|
@@ -12873,7 +13151,7 @@ var CheckForNewAvailableFixesService = _CheckForNewAvailableFixesService;
|
|
|
12873
13151
|
var CheckForNewAvailableFixesTool = class extends BaseTool {
|
|
12874
13152
|
constructor() {
|
|
12875
13153
|
super();
|
|
12876
|
-
__publicField(this, "name",
|
|
13154
|
+
__publicField(this, "name", MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES);
|
|
12877
13155
|
__publicField(this, "displayName", "Check for New Available Fixes");
|
|
12878
13156
|
// A detailed description to guide the LLM on when and how to invoke this tool.
|
|
12879
13157
|
__publicField(this, "description", `Continuesly monitors your code and scans for new security vulnerabilities.
|
|
@@ -12913,6 +13191,9 @@ Example payload:
|
|
|
12913
13191
|
__publicField(this, "newFixesService");
|
|
12914
13192
|
this.newFixesService = new CheckForNewAvailableFixesService();
|
|
12915
13193
|
}
|
|
13194
|
+
triggerScan(args) {
|
|
13195
|
+
this.newFixesService.triggerScan(args);
|
|
13196
|
+
}
|
|
12916
13197
|
async executeInternal(args) {
|
|
12917
13198
|
const pathValidationResult = await validatePath(args.path);
|
|
12918
13199
|
if (!pathValidationResult.isValid) {
|
|
@@ -12927,14 +13208,7 @@ Example payload:
|
|
|
12927
13208
|
logInfo("CheckForNewAvailableFixesTool execution completed", {
|
|
12928
13209
|
resultText
|
|
12929
13210
|
});
|
|
12930
|
-
return
|
|
12931
|
-
content: [
|
|
12932
|
-
{
|
|
12933
|
-
type: "text",
|
|
12934
|
-
text: resultText
|
|
12935
|
-
}
|
|
12936
|
-
]
|
|
12937
|
-
};
|
|
13211
|
+
return this.createSuccessResponse(resultText);
|
|
12938
13212
|
}
|
|
12939
13213
|
};
|
|
12940
13214
|
|
|
@@ -12958,7 +13232,7 @@ var _FetchAvailableFixesService = class _FetchAvailableFixesService {
|
|
|
12958
13232
|
}
|
|
12959
13233
|
async initializeGqlClient() {
|
|
12960
13234
|
if (!this.gqlClient) {
|
|
12961
|
-
this.gqlClient = await
|
|
13235
|
+
this.gqlClient = await createAuthenticatedMcpGQLClient();
|
|
12962
13236
|
}
|
|
12963
13237
|
return this.gqlClient;
|
|
12964
13238
|
}
|
|
@@ -12982,21 +13256,18 @@ var _FetchAvailableFixesService = class _FetchAvailableFixesService {
|
|
|
12982
13256
|
logDebug("received latest report result", { fixReport, expiredReport });
|
|
12983
13257
|
if (!fixReport) {
|
|
12984
13258
|
if (expiredReport) {
|
|
13259
|
+
logInfo("Expired report found");
|
|
12985
13260
|
const lastReportDate = expiredReport.expirationOn ? new Date(expiredReport.expirationOn).toLocaleString() : "Unknown date";
|
|
12986
|
-
|
|
13261
|
+
logDebug("Expired report ", {
|
|
12987
13262
|
repoUrl,
|
|
12988
13263
|
expirationOn: expiredReport.expirationOn
|
|
12989
13264
|
});
|
|
12990
13265
|
return expiredReportPrompt({ lastReportDate });
|
|
12991
13266
|
}
|
|
12992
|
-
logInfo(
|
|
12993
|
-
repoUrl
|
|
12994
|
-
});
|
|
13267
|
+
logInfo(`No report (active or expired) found for repository ${repoUrl}`);
|
|
12995
13268
|
return noReportFoundPrompt;
|
|
12996
13269
|
}
|
|
12997
|
-
logInfo(
|
|
12998
|
-
reportFound: true
|
|
12999
|
-
});
|
|
13270
|
+
logInfo(`Successfully retrieved available fixes for ${repoUrl}`);
|
|
13000
13271
|
const prompt = fixesFoundPrompt({
|
|
13001
13272
|
fixReport,
|
|
13002
13273
|
offset: effectiveOffset
|
|
@@ -13019,7 +13290,7 @@ var FetchAvailableFixesService = _FetchAvailableFixesService;
|
|
|
13019
13290
|
var FetchAvailableFixesTool = class extends BaseTool {
|
|
13020
13291
|
constructor() {
|
|
13021
13292
|
super();
|
|
13022
|
-
__publicField(this, "name",
|
|
13293
|
+
__publicField(this, "name", MCP_TOOL_FETCH_AVAILABLE_FIXES);
|
|
13023
13294
|
__publicField(this, "displayName", "Fetch Available Fixes");
|
|
13024
13295
|
__publicField(this, "description", `Check the MOBB backend for pre-generated fixes (patch sets) that correspond to vulnerabilities detected in the supplied Git repository.
|
|
13025
13296
|
|
|
@@ -13039,7 +13310,7 @@ The tool will:
|
|
|
13039
13310
|
2. Verify that the directory is a valid Git repository with an "origin" remote.
|
|
13040
13311
|
3. Query the MOBB service by the origin remote URL and return a textual summary of available fixes (total and by severity) or a message if none are found.
|
|
13041
13312
|
|
|
13042
|
-
Call this tool instead of
|
|
13313
|
+
Call this tool instead of ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES} when you only need a fixes summary and do NOT want to perform scanning or code modifications.`);
|
|
13043
13314
|
__publicField(this, "inputSchema", {
|
|
13044
13315
|
type: "object",
|
|
13045
13316
|
properties: {
|
|
@@ -13095,17 +13366,10 @@ Call this tool instead of scan_and_fix_vulnerabilities when you only need a fixe
|
|
|
13095
13366
|
limit: args.limit,
|
|
13096
13367
|
offset: args.offset
|
|
13097
13368
|
});
|
|
13098
|
-
|
|
13369
|
+
logDebug("FetchAvailableFixesTool execution completed successfully", {
|
|
13099
13370
|
fixResult
|
|
13100
13371
|
});
|
|
13101
|
-
return
|
|
13102
|
-
content: [
|
|
13103
|
-
{
|
|
13104
|
-
type: "text",
|
|
13105
|
-
text: fixResult
|
|
13106
|
-
}
|
|
13107
|
-
]
|
|
13108
|
-
};
|
|
13372
|
+
return this.createSuccessResponse(fixResult);
|
|
13109
13373
|
}
|
|
13110
13374
|
};
|
|
13111
13375
|
|
|
@@ -13157,9 +13421,10 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
|
|
|
13157
13421
|
limit,
|
|
13158
13422
|
isRescan = false
|
|
13159
13423
|
}) {
|
|
13424
|
+
logInfo("Processing vulnerabilities");
|
|
13160
13425
|
try {
|
|
13161
13426
|
this.gqlClient = await this.initializeGqlClient();
|
|
13162
|
-
|
|
13427
|
+
logDebug("storedFixReportId", {
|
|
13163
13428
|
storedFixReportId: this.storedFixReportId,
|
|
13164
13429
|
currentOffset: this.currentOffset,
|
|
13165
13430
|
fixReportIdTimestamp: this.fixReportIdTimestamp,
|
|
@@ -13167,6 +13432,7 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
|
|
|
13167
13432
|
});
|
|
13168
13433
|
let fixReportId = this.storedFixReportId;
|
|
13169
13434
|
if (!fixReportId || isRescan || this.isFixReportIdExpired()) {
|
|
13435
|
+
logInfo("Scanning files");
|
|
13170
13436
|
this.reset();
|
|
13171
13437
|
this.validateFiles(fileList);
|
|
13172
13438
|
const scanResult = await scanFiles(
|
|
@@ -13175,6 +13441,8 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
|
|
|
13175
13441
|
this.gqlClient
|
|
13176
13442
|
);
|
|
13177
13443
|
fixReportId = scanResult.fixReportId;
|
|
13444
|
+
} else {
|
|
13445
|
+
logInfo("Using stored fixReportId");
|
|
13178
13446
|
}
|
|
13179
13447
|
const effectiveOffset = offset ?? (this.currentOffset || 0);
|
|
13180
13448
|
logDebug("effectiveOffset", { effectiveOffset });
|
|
@@ -13183,6 +13451,7 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
|
|
|
13183
13451
|
effectiveOffset,
|
|
13184
13452
|
limit
|
|
13185
13453
|
);
|
|
13454
|
+
logInfo(`Found ${fixes.totalCount} fixes`);
|
|
13186
13455
|
if (fixes.totalCount > 0) {
|
|
13187
13456
|
this.storedFixReportId = fixReportId;
|
|
13188
13457
|
this.fixReportIdTimestamp = Date.now();
|
|
@@ -13209,13 +13478,14 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
|
|
|
13209
13478
|
}
|
|
13210
13479
|
}
|
|
13211
13480
|
async initializeGqlClient() {
|
|
13212
|
-
const gqlClient = await
|
|
13213
|
-
const isConnected = await gqlClient.
|
|
13481
|
+
const gqlClient = await createAuthenticatedMcpGQLClient();
|
|
13482
|
+
const isConnected = await gqlClient.verifyApiConnection();
|
|
13214
13483
|
if (!isConnected) {
|
|
13215
13484
|
throw new ApiConnectionError(
|
|
13216
13485
|
"Failed to connect to the API. Please check your MOBB_API_KEY"
|
|
13217
13486
|
);
|
|
13218
13487
|
}
|
|
13488
|
+
this.gqlClient = gqlClient;
|
|
13219
13489
|
return gqlClient;
|
|
13220
13490
|
}
|
|
13221
13491
|
async getReportFixes(fixReportId, offset, limit) {
|
|
@@ -13228,7 +13498,7 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
|
|
|
13228
13498
|
offset,
|
|
13229
13499
|
limit
|
|
13230
13500
|
});
|
|
13231
|
-
|
|
13501
|
+
logDebug(`${fixes?.fixes?.length} fixes retrieved`);
|
|
13232
13502
|
return {
|
|
13233
13503
|
fixes: fixes?.fixes || [],
|
|
13234
13504
|
totalCount: fixes?.totalCount || 0
|
|
@@ -13242,7 +13512,7 @@ var ScanAndFixVulnerabilitiesService = _ScanAndFixVulnerabilitiesService;
|
|
|
13242
13512
|
var ScanAndFixVulnerabilitiesTool = class extends BaseTool {
|
|
13243
13513
|
constructor() {
|
|
13244
13514
|
super();
|
|
13245
|
-
__publicField(this, "name",
|
|
13515
|
+
__publicField(this, "name", MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES);
|
|
13246
13516
|
__publicField(this, "displayName", "Scan and Fix Vulnerabilities");
|
|
13247
13517
|
// A detailed description to guide the LLM on when and how to invoke this tool.
|
|
13248
13518
|
__publicField(this, "description", `Scans a given local repository for security vulnerabilities and returns auto-generated code fixes.
|
|
@@ -13322,7 +13592,9 @@ Example payload:
|
|
|
13322
13592
|
this.vulnerabilityFixService.reset();
|
|
13323
13593
|
}
|
|
13324
13594
|
async executeInternal(args) {
|
|
13325
|
-
|
|
13595
|
+
logDebug(`Executing tool: ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES}`, {
|
|
13596
|
+
path: args.path
|
|
13597
|
+
});
|
|
13326
13598
|
if (!args.path) {
|
|
13327
13599
|
throw new Error("Invalid arguments: Missing required parameter 'path'");
|
|
13328
13600
|
}
|
|
@@ -13339,7 +13611,7 @@ Example payload:
|
|
|
13339
13611
|
// 5MB
|
|
13340
13612
|
maxFiles: args.maxFiles
|
|
13341
13613
|
});
|
|
13342
|
-
|
|
13614
|
+
logDebug("Files", { files });
|
|
13343
13615
|
if (files.length === 0) {
|
|
13344
13616
|
return {
|
|
13345
13617
|
content: [
|
|
@@ -13358,34 +13630,20 @@ Example payload:
|
|
|
13358
13630
|
limit: args.limit,
|
|
13359
13631
|
isRescan: args.rescan || !!args.maxFiles
|
|
13360
13632
|
});
|
|
13361
|
-
const
|
|
13362
|
-
|
|
13363
|
-
{
|
|
13364
|
-
type: "text",
|
|
13365
|
-
text: fixResult
|
|
13366
|
-
}
|
|
13367
|
-
]
|
|
13368
|
-
};
|
|
13369
|
-
logInfo("Tool execution completed successfully", {
|
|
13633
|
+
const successResponse = this.createSuccessResponse(fixResult);
|
|
13634
|
+
logDebug("Tool execution completed successfully", {
|
|
13370
13635
|
resultLength: fixResult.length,
|
|
13371
13636
|
fileCount: files.length,
|
|
13372
|
-
result
|
|
13637
|
+
result: successResponse
|
|
13373
13638
|
});
|
|
13374
|
-
return
|
|
13639
|
+
return successResponse;
|
|
13375
13640
|
} catch (error) {
|
|
13376
|
-
const
|
|
13377
|
-
|
|
13378
|
-
{
|
|
13379
|
-
type: "text",
|
|
13380
|
-
text: error.message
|
|
13381
|
-
}
|
|
13382
|
-
]
|
|
13383
|
-
};
|
|
13384
|
-
logInfo("Tool execution failed", {
|
|
13641
|
+
const errorResponse = this.createSuccessResponse(error.message);
|
|
13642
|
+
logError("Tool execution failed", {
|
|
13385
13643
|
error: error.message,
|
|
13386
|
-
result:
|
|
13644
|
+
result: errorResponse
|
|
13387
13645
|
});
|
|
13388
|
-
return
|
|
13646
|
+
return errorResponse;
|
|
13389
13647
|
}
|
|
13390
13648
|
}
|
|
13391
13649
|
};
|