mobbdev 1.0.112 → 1.0.114
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 +678 -426
- package/package.json +10 -7
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";
|
|
@@ -533,6 +534,8 @@ var FixDetailsFragmentDoc = `
|
|
|
533
534
|
confidence
|
|
534
535
|
safeIssueType
|
|
535
536
|
severityText
|
|
537
|
+
gitBlameLogin
|
|
538
|
+
severityValue
|
|
536
539
|
vulnerabilityReportIssues {
|
|
537
540
|
parsedIssueType
|
|
538
541
|
parsedSeverity
|
|
@@ -596,7 +599,15 @@ var FixReportSummaryFieldsFragmentDoc = `
|
|
|
596
599
|
}
|
|
597
600
|
}
|
|
598
601
|
fixes(
|
|
599
|
-
where: {_and: [{vulnerabilityReportIssues: {category: {_eq: "Fixable"}}}, $filters]}
|
|
602
|
+
where: {_and: [{vulnerabilityReportIssues: {category: {_eq: "Fixable"}}}, {_or: [{gitBlameLogin: {_is_null: true}}, {_not: {gitBlameLogin: {_ilike: $currentUserEmail}}}]}, $filters]}
|
|
603
|
+
order_by: {severityValue: desc}
|
|
604
|
+
limit: $limit
|
|
605
|
+
offset: $offset
|
|
606
|
+
) {
|
|
607
|
+
...FixDetails
|
|
608
|
+
}
|
|
609
|
+
userFixes: fixes(
|
|
610
|
+
where: {_and: [{gitBlameLogin: {_ilike: $currentUserEmail}}, {vulnerabilityReportIssues: {category: {_eq: "Fixable"}}}, $filters]}
|
|
600
611
|
order_by: {severityValue: desc}
|
|
601
612
|
limit: $limit
|
|
602
613
|
offset: $offset
|
|
@@ -662,18 +673,18 @@ var MeDocument = `
|
|
|
662
673
|
}
|
|
663
674
|
}
|
|
664
675
|
`;
|
|
665
|
-
var
|
|
666
|
-
query
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
order_by: {
|
|
670
|
-
limit: $limit
|
|
671
|
-
) {
|
|
672
|
-
organization {
|
|
676
|
+
var GetLastOrgAndNamedProjectDocument = `
|
|
677
|
+
query getLastOrgAndNamedProject($email: String!, $projectName: String!) {
|
|
678
|
+
user(where: {email: {_eq: $email}}) {
|
|
679
|
+
id
|
|
680
|
+
userOrganizationsAndUserOrganizationRoles(order_by: {createdOn: desc}) {
|
|
673
681
|
id
|
|
674
|
-
|
|
682
|
+
organization {
|
|
675
683
|
id
|
|
676
|
-
name
|
|
684
|
+
projects(where: {name: {_eq: $projectName}}) {
|
|
685
|
+
name
|
|
686
|
+
id
|
|
687
|
+
}
|
|
677
688
|
}
|
|
678
689
|
}
|
|
679
690
|
}
|
|
@@ -1064,32 +1075,32 @@ var AutoPrAnalysisDocument = `
|
|
|
1064
1075
|
}
|
|
1065
1076
|
}
|
|
1066
1077
|
`;
|
|
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
|
-
) {
|
|
1078
|
+
var GetReportFixesDocument = `
|
|
1079
|
+
query GetReportFixes($reportId: uuid!, $filters: fix_bool_exp = {}, $limit: Int!, $offset: Int!, $currentUserEmail: String!) {
|
|
1080
|
+
fixReport(where: {_and: [{id: {_eq: $reportId}}, {state: {_eq: Finished}}]}) {
|
|
1074
1081
|
...FixReportSummaryFields
|
|
1075
1082
|
}
|
|
1076
1083
|
expiredReport: fixReport(
|
|
1077
|
-
where: {_and: [{
|
|
1078
|
-
order_by: {createdOn: desc}
|
|
1079
|
-
limit: 1
|
|
1084
|
+
where: {_and: [{id: {_eq: $reportId}}, {state: {_eq: Expired}}]}
|
|
1080
1085
|
) {
|
|
1081
1086
|
id
|
|
1082
1087
|
expirationOn
|
|
1083
1088
|
}
|
|
1084
1089
|
}
|
|
1085
1090
|
${FixReportSummaryFieldsFragmentDoc}`;
|
|
1086
|
-
var
|
|
1087
|
-
query
|
|
1088
|
-
fixReport(
|
|
1091
|
+
var GetLatestReportByRepoUrlDocument = `
|
|
1092
|
+
query GetLatestReportByRepoUrl($repoUrl: String!, $filters: fix_bool_exp = {}, $limit: Int!, $offset: Int!, $currentUserEmail: String!) {
|
|
1093
|
+
fixReport(
|
|
1094
|
+
where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Finished}}]}
|
|
1095
|
+
order_by: {createdOn: desc}
|
|
1096
|
+
limit: 1
|
|
1097
|
+
) {
|
|
1089
1098
|
...FixReportSummaryFields
|
|
1090
1099
|
}
|
|
1091
1100
|
expiredReport: fixReport(
|
|
1092
|
-
where: {_and: [{
|
|
1101
|
+
where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Expired}}]}
|
|
1102
|
+
order_by: {createdOn: desc}
|
|
1103
|
+
limit: 1
|
|
1093
1104
|
) {
|
|
1094
1105
|
id
|
|
1095
1106
|
expirationOn
|
|
@@ -1109,8 +1120,8 @@ function getSdk(client, withWrapper = defaultWrapper) {
|
|
|
1109
1120
|
Me(variables, requestHeaders, signal) {
|
|
1110
1121
|
return withWrapper((wrappedRequestHeaders) => client.request({ document: MeDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "Me", "query", variables);
|
|
1111
1122
|
},
|
|
1112
|
-
|
|
1113
|
-
return withWrapper((wrappedRequestHeaders) => client.request({ document:
|
|
1123
|
+
getLastOrgAndNamedProject(variables, requestHeaders, signal) {
|
|
1124
|
+
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetLastOrgAndNamedProjectDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getLastOrgAndNamedProject", "query", variables);
|
|
1114
1125
|
},
|
|
1115
1126
|
GetEncryptedApiToken(variables, requestHeaders, signal) {
|
|
1116
1127
|
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetEncryptedApiTokenDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetEncryptedApiToken", "query", variables);
|
|
@@ -1169,12 +1180,12 @@ function getSdk(client, withWrapper = defaultWrapper) {
|
|
|
1169
1180
|
autoPrAnalysis(variables, requestHeaders, signal) {
|
|
1170
1181
|
return withWrapper((wrappedRequestHeaders) => client.request({ document: AutoPrAnalysisDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "autoPrAnalysis", "mutation", variables);
|
|
1171
1182
|
},
|
|
1172
|
-
GetLatestReportByRepoUrl(variables, requestHeaders, signal) {
|
|
1173
|
-
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetLatestReportByRepoUrlDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetLatestReportByRepoUrl", "query", variables);
|
|
1174
|
-
},
|
|
1175
1183
|
GetReportFixes(variables, requestHeaders, signal) {
|
|
1176
1184
|
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetReportFixesDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetReportFixes", "query", variables);
|
|
1177
1185
|
},
|
|
1186
|
+
GetLatestReportByRepoUrl(variables, requestHeaders, signal) {
|
|
1187
|
+
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetLatestReportByRepoUrlDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetLatestReportByRepoUrl", "query", variables);
|
|
1188
|
+
},
|
|
1178
1189
|
updateDownloadedFixData(variables, requestHeaders, signal) {
|
|
1179
1190
|
return withWrapper((wrappedRequestHeaders) => client.request({ document: UpdateDownloadedFixDataDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "updateDownloadedFixData", "mutation", variables);
|
|
1180
1191
|
}
|
|
@@ -1444,7 +1455,11 @@ var fixDetailsData = {
|
|
|
1444
1455
|
["UNNECESSARY_IMPORTS" /* UnnecessaryImports */]: void 0,
|
|
1445
1456
|
["NO_NESTED_TRY" /* NoNestedTry */]: void 0,
|
|
1446
1457
|
["REDOS" /* Redos */]: void 0,
|
|
1447
|
-
["DO_NOT_THROW_GENERIC_EXCEPTION" /* DoNotThrowGenericException */]: void 0
|
|
1458
|
+
["DO_NOT_THROW_GENERIC_EXCEPTION" /* DoNotThrowGenericException */]: void 0,
|
|
1459
|
+
["BUFFER_OVERFLOW" /* BufferOverflow */]: {
|
|
1460
|
+
issueDescription: "Buffer Overflow occurs when a program writes data beyond the allocated memory space, leading to unexpected behavior or security vulnerabilities.",
|
|
1461
|
+
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."
|
|
1462
|
+
}
|
|
1448
1463
|
};
|
|
1449
1464
|
|
|
1450
1465
|
// src/features/analysis/scm/shared/src/getIssueType.ts
|
|
@@ -1558,7 +1573,8 @@ var issueTypeMap = {
|
|
|
1558
1573
|
["NO_NESTED_TRY" /* NoNestedTry */]: "No Nested Try",
|
|
1559
1574
|
["UNNECESSARY_IMPORTS" /* UnnecessaryImports */]: "Unnecessary Imports",
|
|
1560
1575
|
["REDOS" /* Redos */]: "Regular Expression Denial of Service",
|
|
1561
|
-
["DO_NOT_THROW_GENERIC_EXCEPTION" /* DoNotThrowGenericException */]: "Do Not Throw Generic Exception"
|
|
1576
|
+
["DO_NOT_THROW_GENERIC_EXCEPTION" /* DoNotThrowGenericException */]: "Do Not Throw Generic Exception",
|
|
1577
|
+
["BUFFER_OVERFLOW" /* BufferOverflow */]: "Buffer Overflow"
|
|
1562
1578
|
};
|
|
1563
1579
|
var issueTypeZ = z.nativeEnum(IssueType_Enum);
|
|
1564
1580
|
var getIssueTypeFriendlyString = (issueType) => {
|
|
@@ -5141,14 +5157,17 @@ import { simpleGit } from "simple-git";
|
|
|
5141
5157
|
// src/mcp/core/configs.ts
|
|
5142
5158
|
var MCP_DEFAULT_API_URL = "https://api.mobb.ai/v1/graphql";
|
|
5143
5159
|
var MCP_API_KEY_HEADER_NAME = "x-mobb-key";
|
|
5144
|
-
var MCP_LOGIN_MAX_WAIT =
|
|
5145
|
-
var MCP_LOGIN_CHECK_DELAY =
|
|
5160
|
+
var MCP_LOGIN_MAX_WAIT = 2 * 60 * 1e3;
|
|
5161
|
+
var MCP_LOGIN_CHECK_DELAY = 2 * 1e3;
|
|
5146
5162
|
var MCP_VUL_REPORT_DIGEST_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
5147
5163
|
var MCP_MAX_FILE_SIZE = MAX_UPLOAD_FILE_SIZE_MB * 1024 * 1024;
|
|
5148
5164
|
var MCP_PERIODIC_CHECK_INTERVAL = 15 * 60 * 1e3;
|
|
5149
5165
|
var MCP_DEFAULT_MAX_FILES_TO_SCAN = 10;
|
|
5150
5166
|
var MCP_REPORT_ID_EXPIRATION_MS = 2 * 60 * 60 * 1e3;
|
|
5151
5167
|
var MCP_TOOLS_BROWSER_COOLDOWN_MS = 24 * 60 * 60 * 1e3;
|
|
5168
|
+
var MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES = "check_for_new_available_fixes";
|
|
5169
|
+
var MCP_TOOL_FETCH_AVAILABLE_FIXES = "fetch_available_fixes";
|
|
5170
|
+
var MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES = "scan_and_fix_vulnerabilities";
|
|
5152
5171
|
|
|
5153
5172
|
// src/features/analysis/scm/FileUtils.ts
|
|
5154
5173
|
import fs2 from "fs";
|
|
@@ -8653,12 +8672,6 @@ var GqlClientError = class extends Error {
|
|
|
8653
8672
|
this.name = "GqlClientError";
|
|
8654
8673
|
}
|
|
8655
8674
|
};
|
|
8656
|
-
var FileProcessingError = class extends Error {
|
|
8657
|
-
constructor(message) {
|
|
8658
|
-
super(message);
|
|
8659
|
-
this.name = "FileProcessingError";
|
|
8660
|
-
}
|
|
8661
|
-
};
|
|
8662
8675
|
var ReportInitializationError = class extends Error {
|
|
8663
8676
|
constructor(message) {
|
|
8664
8677
|
super(message);
|
|
@@ -9592,7 +9605,7 @@ var GQLClient = class {
|
|
|
9592
9605
|
});
|
|
9593
9606
|
return res.insert_cli_login_one?.id || "";
|
|
9594
9607
|
}
|
|
9595
|
-
async
|
|
9608
|
+
async verifyApiConnection() {
|
|
9596
9609
|
try {
|
|
9597
9610
|
await this.getUserInfo();
|
|
9598
9611
|
} catch (e) {
|
|
@@ -9603,7 +9616,7 @@ var GQLClient = class {
|
|
|
9603
9616
|
}
|
|
9604
9617
|
return true;
|
|
9605
9618
|
}
|
|
9606
|
-
async
|
|
9619
|
+
async validateUserToken() {
|
|
9607
9620
|
await this.createCommunityUser();
|
|
9608
9621
|
let info;
|
|
9609
9622
|
try {
|
|
@@ -9614,31 +9627,46 @@ var GQLClient = class {
|
|
|
9614
9627
|
}
|
|
9615
9628
|
return info?.email || true;
|
|
9616
9629
|
}
|
|
9617
|
-
async
|
|
9630
|
+
async getLastOrgAndNamedProject(params) {
|
|
9631
|
+
const me = await this.getUserInfo();
|
|
9632
|
+
const email = me?.email;
|
|
9633
|
+
if (!email) {
|
|
9634
|
+
throw new Error("User email not found");
|
|
9635
|
+
}
|
|
9618
9636
|
const { projectName, userDefinedOrganizationId } = params;
|
|
9619
|
-
|
|
9620
|
-
|
|
9621
|
-
|
|
9637
|
+
if (!projectName) {
|
|
9638
|
+
throw new Error("Project name is required");
|
|
9639
|
+
}
|
|
9640
|
+
const orgAndProjectRes = await this._clientSdk.getLastOrgAndNamedProject({
|
|
9641
|
+
email,
|
|
9642
|
+
projectName
|
|
9622
9643
|
});
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9644
|
+
if (!orgAndProjectRes.user?.[0]?.userOrganizationsAndUserOrganizationRoles?.[0]?.organization?.id) {
|
|
9645
|
+
throw new Error(
|
|
9646
|
+
`The user with email:${email} is not associated with any organization`
|
|
9647
|
+
);
|
|
9648
|
+
}
|
|
9649
|
+
const organization = orgAndProjectRes.user?.at(0)?.userOrganizationsAndUserOrganizationRoles.map((org) => org.organization).filter(
|
|
9650
|
+
(org) => userDefinedOrganizationId ? org.id === userDefinedOrganizationId : true
|
|
9651
|
+
)?.at(0);
|
|
9652
|
+
if (!organization) {
|
|
9653
|
+
throw new Error(
|
|
9654
|
+
`Organization with id:${userDefinedOrganizationId} not found`
|
|
9655
|
+
);
|
|
9626
9656
|
}
|
|
9627
|
-
|
|
9628
|
-
const project = projectName ? org?.projects.find((project2) => project2.name === projectName) ?? null : org?.projects[0];
|
|
9629
|
-
let projectId = project?.id;
|
|
9657
|
+
let projectId = organization?.projects?.[0]?.id;
|
|
9630
9658
|
if (!projectId) {
|
|
9631
9659
|
const createdProject = await this._clientSdk.CreateProject({
|
|
9632
|
-
organizationId:
|
|
9660
|
+
organizationId: organization.id,
|
|
9633
9661
|
projectName: projectName || "My project"
|
|
9634
9662
|
});
|
|
9635
9663
|
projectId = createdProject.createProject.projectId;
|
|
9636
9664
|
}
|
|
9637
|
-
if (!
|
|
9665
|
+
if (!projectId) {
|
|
9638
9666
|
throw new Error("Project not found");
|
|
9639
9667
|
}
|
|
9640
9668
|
return {
|
|
9641
|
-
organizationId:
|
|
9669
|
+
organizationId: organization.id,
|
|
9642
9670
|
projectId
|
|
9643
9671
|
};
|
|
9644
9672
|
}
|
|
@@ -10551,7 +10579,10 @@ async function _scan(params, { skipPrompts = false } = {}) {
|
|
|
10551
10579
|
skipPrompts,
|
|
10552
10580
|
apiKey
|
|
10553
10581
|
});
|
|
10554
|
-
|
|
10582
|
+
if (!mobbProjectName) {
|
|
10583
|
+
throw new Error("mobbProjectName is required");
|
|
10584
|
+
}
|
|
10585
|
+
const { projectId, organizationId } = await gqlClient.getLastOrgAndNamedProject({
|
|
10555
10586
|
projectName: mobbProjectName,
|
|
10556
10587
|
userDefinedOrganizationId: userOrganizationId
|
|
10557
10588
|
});
|
|
@@ -11128,7 +11159,7 @@ async function handleMobbLogin({
|
|
|
11128
11159
|
skipPrompts
|
|
11129
11160
|
}) {
|
|
11130
11161
|
const { createSpinner: createSpinner5 } = Spinner({ ci: skipPrompts });
|
|
11131
|
-
const isConnected = await inGqlClient.
|
|
11162
|
+
const isConnected = await inGqlClient.verifyApiConnection();
|
|
11132
11163
|
if (!isConnected) {
|
|
11133
11164
|
createSpinner5().start().error({
|
|
11134
11165
|
text: "\u{1F513} Connection to Mobb: failed to connect to the Mobb server"
|
|
@@ -11140,7 +11171,7 @@ async function handleMobbLogin({
|
|
|
11140
11171
|
createSpinner5().start().success({
|
|
11141
11172
|
text: `\u{1F513} Connection to Mobb: succeeded`
|
|
11142
11173
|
});
|
|
11143
|
-
const userVerify = await inGqlClient.
|
|
11174
|
+
const userVerify = await inGqlClient.validateUserToken();
|
|
11144
11175
|
if (userVerify) {
|
|
11145
11176
|
createSpinner5().start().success({
|
|
11146
11177
|
text: `\u{1F513} Login to Mobb succeeded. ${typeof userVerify === "string" ? `Logged in as ${userVerify}` : ""}`
|
|
@@ -11194,7 +11225,7 @@ async function handleMobbLogin({
|
|
|
11194
11225
|
throw new CliError();
|
|
11195
11226
|
}
|
|
11196
11227
|
const newGqlClient = new GQLClient({ apiKey: newApiToken, type: "apiKey" });
|
|
11197
|
-
const loginSuccess = await newGqlClient.
|
|
11228
|
+
const loginSuccess = await newGqlClient.validateUserToken();
|
|
11198
11229
|
if (loginSuccess) {
|
|
11199
11230
|
debug18(`set api token ${newApiToken}`);
|
|
11200
11231
|
config3.set("apiToken", newApiToken);
|
|
@@ -11257,7 +11288,7 @@ function validateRepoUrl(args) {
|
|
|
11257
11288
|
});
|
|
11258
11289
|
}
|
|
11259
11290
|
}
|
|
11260
|
-
var supportExtensions = [".json", ".xml", ".fpr", ".sarif"];
|
|
11291
|
+
var supportExtensions = [".json", ".xml", ".fpr", ".sarif", ".zip"];
|
|
11261
11292
|
function validateReportFileFormat(reportFile) {
|
|
11262
11293
|
if (!supportExtensions.includes(path10.extname(reportFile))) {
|
|
11263
11294
|
throw new CliError(
|
|
@@ -11353,7 +11384,7 @@ import {
|
|
|
11353
11384
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
11354
11385
|
|
|
11355
11386
|
// src/mcp/Logger.ts
|
|
11356
|
-
var
|
|
11387
|
+
var loggerUrl = "http://localhost:4444/log";
|
|
11357
11388
|
var isTestEnvironment = process.env["VITEST"] || process.env["TEST"];
|
|
11358
11389
|
var CIRCUIT_BREAKER_TIME = 5e3;
|
|
11359
11390
|
var URL_CHECK_TIMEOUT = 200;
|
|
@@ -11400,11 +11431,14 @@ var Logger = class {
|
|
|
11400
11431
|
this.isProcessing = false;
|
|
11401
11432
|
return;
|
|
11402
11433
|
}
|
|
11403
|
-
const isReachable = await this.isUrlReachable(
|
|
11434
|
+
const isReachable = await this.isUrlReachable(loggerUrl);
|
|
11404
11435
|
if (!isReachable) {
|
|
11405
11436
|
this.triggerCircuitBreaker();
|
|
11406
11437
|
return;
|
|
11407
11438
|
}
|
|
11439
|
+
await this.sendLogEntry(logEntry);
|
|
11440
|
+
}
|
|
11441
|
+
async sendLogEntry(logEntry) {
|
|
11408
11442
|
const logMessage = {
|
|
11409
11443
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11410
11444
|
level: logEntry.level,
|
|
@@ -11416,21 +11450,23 @@ var Logger = class {
|
|
|
11416
11450
|
const timeoutId = setTimeout(() => {
|
|
11417
11451
|
controller.abort();
|
|
11418
11452
|
}, 500);
|
|
11419
|
-
|
|
11420
|
-
|
|
11421
|
-
|
|
11422
|
-
|
|
11423
|
-
|
|
11424
|
-
|
|
11425
|
-
|
|
11426
|
-
|
|
11453
|
+
try {
|
|
11454
|
+
await fetch(loggerUrl, {
|
|
11455
|
+
method: "POST",
|
|
11456
|
+
headers: { "Content-Type": "application/json" },
|
|
11457
|
+
body: JSON.stringify(logMessage),
|
|
11458
|
+
redirect: "error",
|
|
11459
|
+
// do not follow redirects
|
|
11460
|
+
signal: controller.signal
|
|
11461
|
+
});
|
|
11427
11462
|
this.queue.shift();
|
|
11428
11463
|
setTimeout(() => this.processQueue(), 0);
|
|
11429
|
-
}
|
|
11464
|
+
} catch (error) {
|
|
11430
11465
|
this.triggerCircuitBreaker();
|
|
11431
|
-
|
|
11466
|
+
logError("Failed to send log entry", error);
|
|
11467
|
+
} finally {
|
|
11432
11468
|
clearTimeout(timeoutId);
|
|
11433
|
-
}
|
|
11469
|
+
}
|
|
11434
11470
|
}
|
|
11435
11471
|
triggerCircuitBreaker() {
|
|
11436
11472
|
this.isCircuitBroken = true;
|
|
@@ -11456,12 +11492,90 @@ var logDebug = (message, data) => logger.log(message, "debug", data);
|
|
|
11456
11492
|
var log = logger.log.bind(logger);
|
|
11457
11493
|
|
|
11458
11494
|
// src/mcp/services/McpGQLClient.ts
|
|
11459
|
-
import crypto2 from "crypto";
|
|
11460
|
-
import os2 from "os";
|
|
11461
11495
|
import Configstore3 from "configstore";
|
|
11496
|
+
import crypto3 from "crypto";
|
|
11462
11497
|
import { GraphQLClient as GraphQLClient2 } from "graphql-request";
|
|
11463
|
-
import open4 from "open";
|
|
11464
11498
|
import { v4 as uuidv42 } from "uuid";
|
|
11499
|
+
|
|
11500
|
+
// src/mcp/services/McpAuthService.ts
|
|
11501
|
+
import crypto2 from "crypto";
|
|
11502
|
+
import os2 from "os";
|
|
11503
|
+
import open4 from "open";
|
|
11504
|
+
var McpAuthService = class {
|
|
11505
|
+
constructor(client) {
|
|
11506
|
+
__publicField(this, "client");
|
|
11507
|
+
__publicField(this, "lastBrowserOpenTime", 0);
|
|
11508
|
+
this.client = client;
|
|
11509
|
+
}
|
|
11510
|
+
/**
|
|
11511
|
+
* Opens a browser window for authentication
|
|
11512
|
+
* @param url URL to open in browser
|
|
11513
|
+
* @param isBackgoundCall Whether this is called from tools context
|
|
11514
|
+
*/
|
|
11515
|
+
async openBrowser(url, isBackgoundCall) {
|
|
11516
|
+
if (isBackgoundCall) {
|
|
11517
|
+
const now = Date.now();
|
|
11518
|
+
if (now - this.lastBrowserOpenTime < MCP_TOOLS_BROWSER_COOLDOWN_MS) {
|
|
11519
|
+
logDebug(`browser cooldown active, skipping open for ${url}`);
|
|
11520
|
+
return;
|
|
11521
|
+
}
|
|
11522
|
+
}
|
|
11523
|
+
logDebug(`opening browser url ${url}`);
|
|
11524
|
+
await open4(url);
|
|
11525
|
+
this.lastBrowserOpenTime = Date.now();
|
|
11526
|
+
}
|
|
11527
|
+
/**
|
|
11528
|
+
* Handles the complete authentication flow
|
|
11529
|
+
* @param isBackgoundCall Whether this is called from tools context
|
|
11530
|
+
* @returns Authenticated API token
|
|
11531
|
+
*/
|
|
11532
|
+
async authenticate(isBackgoundCall = false) {
|
|
11533
|
+
const { publicKey, privateKey } = crypto2.generateKeyPairSync("rsa", {
|
|
11534
|
+
modulusLength: 2048
|
|
11535
|
+
});
|
|
11536
|
+
logDebug("creating cli login");
|
|
11537
|
+
const loginId = await this.client.createCliLogin({
|
|
11538
|
+
publicKey: publicKey.export({ format: "pem", type: "pkcs1" }).toString()
|
|
11539
|
+
});
|
|
11540
|
+
if (!loginId) {
|
|
11541
|
+
throw new CliLoginError("Error: createCliLogin failed");
|
|
11542
|
+
}
|
|
11543
|
+
logDebug(`cli login created ${loginId}`);
|
|
11544
|
+
const webLoginUrl2 = `${WEB_APP_URL}/cli-login`;
|
|
11545
|
+
const browserUrl = `${webLoginUrl2}/${loginId}?hostname=${os2.hostname()}`;
|
|
11546
|
+
await this.openBrowser(browserUrl, isBackgoundCall);
|
|
11547
|
+
logDebug(`waiting for login to complete`);
|
|
11548
|
+
let newApiToken = null;
|
|
11549
|
+
for (let i = 0; i < MCP_LOGIN_MAX_WAIT / MCP_LOGIN_CHECK_DELAY; i++) {
|
|
11550
|
+
const encryptedApiToken = await this.client.getEncryptedApiToken({
|
|
11551
|
+
loginId
|
|
11552
|
+
});
|
|
11553
|
+
if (encryptedApiToken) {
|
|
11554
|
+
logDebug("encrypted API token received");
|
|
11555
|
+
newApiToken = crypto2.privateDecrypt(privateKey, Buffer.from(encryptedApiToken, "base64")).toString("utf-8");
|
|
11556
|
+
logDebug("API token decrypted");
|
|
11557
|
+
break;
|
|
11558
|
+
}
|
|
11559
|
+
await sleep(MCP_LOGIN_CHECK_DELAY);
|
|
11560
|
+
}
|
|
11561
|
+
if (!newApiToken) {
|
|
11562
|
+
throw new FailedToGetApiTokenError(
|
|
11563
|
+
"Error: failed to get encrypted api token"
|
|
11564
|
+
);
|
|
11565
|
+
}
|
|
11566
|
+
const verifyClient = new McpGQLClient({
|
|
11567
|
+
apiKey: newApiToken,
|
|
11568
|
+
type: "apiKey"
|
|
11569
|
+
});
|
|
11570
|
+
const loginSuccess = await verifyClient.validateUserToken();
|
|
11571
|
+
if (!loginSuccess) {
|
|
11572
|
+
throw new AuthenticationError("Invalid API token");
|
|
11573
|
+
}
|
|
11574
|
+
return newApiToken;
|
|
11575
|
+
}
|
|
11576
|
+
};
|
|
11577
|
+
|
|
11578
|
+
// src/mcp/services/McpGQLClient.ts
|
|
11465
11579
|
var mobbConfigStore = new Configstore3(packageJson.name, { apiToken: "" });
|
|
11466
11580
|
var McpGQLClient = class {
|
|
11467
11581
|
constructor(args) {
|
|
@@ -11498,17 +11612,17 @@ var McpGQLClient = class {
|
|
|
11498
11612
|
}
|
|
11499
11613
|
};
|
|
11500
11614
|
}
|
|
11501
|
-
async
|
|
11615
|
+
async verifyApiConnection() {
|
|
11502
11616
|
try {
|
|
11503
|
-
logDebug("GraphQL: Calling Me query for connection verification");
|
|
11617
|
+
logDebug("GraphQL: Calling Me query for API connection verification");
|
|
11504
11618
|
const result = await this.clientSdk.Me();
|
|
11505
11619
|
logDebug("GraphQL: Me query successful", { result });
|
|
11506
11620
|
return true;
|
|
11507
11621
|
} catch (e) {
|
|
11508
11622
|
const error = e;
|
|
11509
|
-
logDebug(`
|
|
11623
|
+
logDebug(`API connection verification failed ${error.toString()}`);
|
|
11510
11624
|
if (error?.toString().includes("FetchError")) {
|
|
11511
|
-
logError("
|
|
11625
|
+
logError("API connection verification failed", { error });
|
|
11512
11626
|
return false;
|
|
11513
11627
|
}
|
|
11514
11628
|
}
|
|
@@ -11520,7 +11634,7 @@ var McpGQLClient = class {
|
|
|
11520
11634
|
const result = await this.clientSdk.uploadS3BucketInfo({
|
|
11521
11635
|
fileName: "report.json"
|
|
11522
11636
|
});
|
|
11523
|
-
|
|
11637
|
+
logDebug("GraphQL: uploadS3BucketInfo successful", { result });
|
|
11524
11638
|
return result;
|
|
11525
11639
|
} catch (e) {
|
|
11526
11640
|
logError("GraphQL: uploadS3BucketInfo failed", {
|
|
@@ -11536,7 +11650,7 @@ var McpGQLClient = class {
|
|
|
11536
11650
|
const res = await this.clientSdk.getAnalysis({
|
|
11537
11651
|
analysisId
|
|
11538
11652
|
});
|
|
11539
|
-
|
|
11653
|
+
logDebug("GraphQL: getAnalysis successful", { result: res });
|
|
11540
11654
|
if (!res.analysis) {
|
|
11541
11655
|
throw new Error(`Analysis not found: ${analysisId}`);
|
|
11542
11656
|
}
|
|
@@ -11556,7 +11670,7 @@ var McpGQLClient = class {
|
|
|
11556
11670
|
variables
|
|
11557
11671
|
});
|
|
11558
11672
|
const result = await this.clientSdk.SubmitVulnerabilityReport(variables);
|
|
11559
|
-
|
|
11673
|
+
logDebug("GraphQL: SubmitVulnerabilityReport successful", { result });
|
|
11560
11674
|
return result;
|
|
11561
11675
|
} catch (e) {
|
|
11562
11676
|
logError("GraphQL: SubmitVulnerabilityReport failed", {
|
|
@@ -11590,7 +11704,7 @@ var McpGQLClient = class {
|
|
|
11590
11704
|
return;
|
|
11591
11705
|
}
|
|
11592
11706
|
if (callbackStates.includes(data.analysis?.state)) {
|
|
11593
|
-
|
|
11707
|
+
logDebug("GraphQL: Analysis state matches callback states", {
|
|
11594
11708
|
analysisId: data.analysis.id,
|
|
11595
11709
|
state: data.analysis.state,
|
|
11596
11710
|
callbackStates
|
|
@@ -11609,7 +11723,7 @@ var McpGQLClient = class {
|
|
|
11609
11723
|
timeoutInMs: params.timeoutInMs
|
|
11610
11724
|
}
|
|
11611
11725
|
);
|
|
11612
|
-
|
|
11726
|
+
logDebug("GraphQL: GetAnalysis subscription completed", { result });
|
|
11613
11727
|
return result;
|
|
11614
11728
|
} catch (e) {
|
|
11615
11729
|
logError("GraphQL: GetAnalysis subscription failed", {
|
|
@@ -11630,38 +11744,41 @@ var McpGQLClient = class {
|
|
|
11630
11744
|
if (!userEmail) {
|
|
11631
11745
|
throw new Error("User email not found");
|
|
11632
11746
|
}
|
|
11633
|
-
const shortEmailHash =
|
|
11747
|
+
const shortEmailHash = crypto3.createHash("sha256").update(userEmail).digest("hex").slice(0, 8).toUpperCase();
|
|
11634
11748
|
const projectName = `MCP Scans ${shortEmailHash}`;
|
|
11635
|
-
logDebug("GraphQL: Calling
|
|
11636
|
-
|
|
11637
|
-
filters: {},
|
|
11638
|
-
limit: 1
|
|
11749
|
+
logDebug("GraphQL: Calling getLastOrgAndNamedProject query", {
|
|
11750
|
+
projectName
|
|
11639
11751
|
});
|
|
11640
|
-
|
|
11641
|
-
|
|
11752
|
+
const orgAndProjectRes = await this.clientSdk.getLastOrgAndNamedProject({
|
|
11753
|
+
email: userEmail,
|
|
11754
|
+
projectName
|
|
11755
|
+
});
|
|
11756
|
+
logDebug("GraphQL: getLastOrgAndNamedProject successful", {
|
|
11757
|
+
result: orgAndProjectRes
|
|
11642
11758
|
});
|
|
11643
|
-
|
|
11644
|
-
|
|
11645
|
-
|
|
11646
|
-
|
|
11647
|
-
|
|
11648
|
-
const
|
|
11649
|
-
|
|
11650
|
-
|
|
11651
|
-
|
|
11759
|
+
if (!orgAndProjectRes.user?.[0]?.userOrganizationsAndUserOrganizationRoles?.[0]?.organization?.id) {
|
|
11760
|
+
throw new Error(
|
|
11761
|
+
`The user with email:${userEmail} is not associated with any organization`
|
|
11762
|
+
);
|
|
11763
|
+
}
|
|
11764
|
+
const organization = orgAndProjectRes.user?.[0]?.userOrganizationsAndUserOrganizationRoles?.[0]?.organization;
|
|
11765
|
+
const projectId = organization?.projects?.[0]?.id;
|
|
11766
|
+
if (projectId) {
|
|
11767
|
+
logDebug("GraphQL: Found existing project", {
|
|
11768
|
+
projectId,
|
|
11652
11769
|
projectName
|
|
11653
11770
|
});
|
|
11654
|
-
return
|
|
11771
|
+
return projectId;
|
|
11655
11772
|
}
|
|
11656
11773
|
logDebug("GraphQL: Project not found, creating new project", {
|
|
11657
|
-
organizationId:
|
|
11774
|
+
organizationId: organization.id,
|
|
11658
11775
|
projectName
|
|
11659
11776
|
});
|
|
11660
11777
|
const createdProject = await this.clientSdk.CreateProject({
|
|
11661
|
-
organizationId:
|
|
11778
|
+
organizationId: organization.id,
|
|
11662
11779
|
projectName
|
|
11663
11780
|
});
|
|
11664
|
-
|
|
11781
|
+
logDebug("GraphQL: CreateProject successful", { result: createdProject });
|
|
11665
11782
|
return createdProject.createProject.projectId;
|
|
11666
11783
|
} catch (e) {
|
|
11667
11784
|
logError("GraphQL: getProjectId failed", {
|
|
@@ -11675,15 +11792,15 @@ var McpGQLClient = class {
|
|
|
11675
11792
|
const { me } = await this.clientSdk.Me();
|
|
11676
11793
|
return me;
|
|
11677
11794
|
}
|
|
11678
|
-
async
|
|
11679
|
-
logDebug("
|
|
11795
|
+
async validateUserToken() {
|
|
11796
|
+
logDebug("validating user token");
|
|
11680
11797
|
try {
|
|
11681
11798
|
await this.clientSdk.CreateCommunityUser();
|
|
11682
11799
|
const info = await this.getUserInfo();
|
|
11683
|
-
logDebug("token
|
|
11800
|
+
logDebug("user token validated successfully");
|
|
11684
11801
|
return info?.email || true;
|
|
11685
11802
|
} catch (e) {
|
|
11686
|
-
logError("
|
|
11803
|
+
logError("user token validation failed");
|
|
11687
11804
|
return false;
|
|
11688
11805
|
}
|
|
11689
11806
|
}
|
|
@@ -11716,18 +11833,34 @@ var McpGQLClient = class {
|
|
|
11716
11833
|
return null;
|
|
11717
11834
|
}
|
|
11718
11835
|
}
|
|
11719
|
-
|
|
11836
|
+
mergeUserAndSystemFixes(reportData, limit) {
|
|
11837
|
+
if (!reportData) return [];
|
|
11838
|
+
const { userFixes = [], fixes = [] } = reportData;
|
|
11839
|
+
const fixMap = /* @__PURE__ */ new Map();
|
|
11840
|
+
for (const fix of userFixes) {
|
|
11841
|
+
if (fix.id) {
|
|
11842
|
+
fixMap.set(fix.id, fix);
|
|
11843
|
+
}
|
|
11844
|
+
}
|
|
11845
|
+
for (const fix of fixes) {
|
|
11846
|
+
if (fix.id && !fixMap.has(fix.id)) {
|
|
11847
|
+
fixMap.set(fix.id, fix);
|
|
11848
|
+
}
|
|
11849
|
+
}
|
|
11850
|
+
return Array.from(fixMap.values()).slice(0, limit);
|
|
11851
|
+
}
|
|
11852
|
+
async updateFixesDownloadStatus(fixIds) {
|
|
11720
11853
|
if (fixIds.length > 0) {
|
|
11721
11854
|
const resUpdate = await this.clientSdk.updateDownloadedFixData({
|
|
11722
11855
|
fixIds,
|
|
11723
11856
|
source: "MCP" /* Mcp */
|
|
11724
11857
|
});
|
|
11725
|
-
|
|
11858
|
+
logDebug("GraphQL: updateFixesDownloadStatus successful", {
|
|
11726
11859
|
result: resUpdate,
|
|
11727
11860
|
fixIds
|
|
11728
11861
|
});
|
|
11729
11862
|
} else {
|
|
11730
|
-
|
|
11863
|
+
logDebug("GraphQL: No fixes found to update download status");
|
|
11731
11864
|
}
|
|
11732
11865
|
}
|
|
11733
11866
|
async getLatestReportByRepoUrl({
|
|
@@ -11741,19 +11874,35 @@ var McpGQLClient = class {
|
|
|
11741
11874
|
limit,
|
|
11742
11875
|
offset
|
|
11743
11876
|
});
|
|
11877
|
+
let currentUserEmail = "%@%";
|
|
11878
|
+
try {
|
|
11879
|
+
const userInfo = await this.getUserInfo();
|
|
11880
|
+
if (userInfo?.email) {
|
|
11881
|
+
currentUserEmail = `%${userInfo.email}%`;
|
|
11882
|
+
}
|
|
11883
|
+
} catch (err) {
|
|
11884
|
+
logDebug("Failed to get user email, using default pattern", {
|
|
11885
|
+
error: err
|
|
11886
|
+
});
|
|
11887
|
+
}
|
|
11744
11888
|
const res = await this.clientSdk.GetLatestReportByRepoUrl({
|
|
11745
11889
|
repoUrl,
|
|
11746
11890
|
limit,
|
|
11747
|
-
offset
|
|
11891
|
+
offset,
|
|
11892
|
+
currentUserEmail
|
|
11748
11893
|
});
|
|
11749
|
-
|
|
11894
|
+
logDebug("GraphQL: GetLatestReportByRepoUrl successful", {
|
|
11750
11895
|
result: res,
|
|
11751
11896
|
reportCount: res.fixReport?.length || 0
|
|
11752
11897
|
});
|
|
11753
|
-
const
|
|
11754
|
-
|
|
11898
|
+
const fixes = this.mergeUserAndSystemFixes(res.fixReport?.[0], limit);
|
|
11899
|
+
const fixIds = fixes.map((fix) => fix.id);
|
|
11900
|
+
await this.updateFixesDownloadStatus(fixIds);
|
|
11755
11901
|
return {
|
|
11756
|
-
fixReport: res.fixReport?.[0]
|
|
11902
|
+
fixReport: res.fixReport?.[0] ? {
|
|
11903
|
+
...res.fixReport?.[0],
|
|
11904
|
+
fixes
|
|
11905
|
+
} : null,
|
|
11757
11906
|
expiredReport: res.expiredReport?.[0] || null
|
|
11758
11907
|
};
|
|
11759
11908
|
} catch (e) {
|
|
@@ -11772,14 +11921,14 @@ var McpGQLClient = class {
|
|
|
11772
11921
|
issueType,
|
|
11773
11922
|
severity
|
|
11774
11923
|
}) {
|
|
11924
|
+
const filters = {};
|
|
11925
|
+
if (issueType && issueType.length > 0) {
|
|
11926
|
+
filters["safeIssueType"] = { _in: issueType };
|
|
11927
|
+
}
|
|
11928
|
+
if (severity && severity.length > 0) {
|
|
11929
|
+
filters["severityText"] = { _in: severity };
|
|
11930
|
+
}
|
|
11775
11931
|
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
11932
|
logDebug("GraphQL: Calling GetReportFixes query", {
|
|
11784
11933
|
reportId,
|
|
11785
11934
|
limit,
|
|
@@ -11788,13 +11937,25 @@ var McpGQLClient = class {
|
|
|
11788
11937
|
issueType,
|
|
11789
11938
|
severity
|
|
11790
11939
|
});
|
|
11940
|
+
let currentUserEmail = "%@%";
|
|
11941
|
+
try {
|
|
11942
|
+
const userInfo = await this.getUserInfo();
|
|
11943
|
+
if (userInfo?.email) {
|
|
11944
|
+
currentUserEmail = `%${userInfo.email}%`;
|
|
11945
|
+
}
|
|
11946
|
+
} catch (err) {
|
|
11947
|
+
logDebug("Failed to get user email, using default pattern", {
|
|
11948
|
+
error: err
|
|
11949
|
+
});
|
|
11950
|
+
}
|
|
11791
11951
|
const res = await this.clientSdk.GetReportFixes({
|
|
11792
11952
|
reportId,
|
|
11793
11953
|
limit,
|
|
11794
11954
|
offset,
|
|
11795
|
-
filters
|
|
11955
|
+
filters,
|
|
11956
|
+
currentUserEmail
|
|
11796
11957
|
});
|
|
11797
|
-
|
|
11958
|
+
logDebug("GraphQL: GetReportFixes successful", {
|
|
11798
11959
|
result: res,
|
|
11799
11960
|
fixCount: res.fixReport?.[0]?.fixes?.length || 0,
|
|
11800
11961
|
totalCount: res.fixReport?.[0]?.filteredFixesCount?.aggregate?.count || 0
|
|
@@ -11802,10 +11963,11 @@ var McpGQLClient = class {
|
|
|
11802
11963
|
if (res.fixReport.length === 0) {
|
|
11803
11964
|
return null;
|
|
11804
11965
|
}
|
|
11805
|
-
const
|
|
11806
|
-
|
|
11966
|
+
const fixes = this.mergeUserAndSystemFixes(res.fixReport?.[0], limit);
|
|
11967
|
+
const fixIds = fixes.map((fix) => fix.id);
|
|
11968
|
+
await this.updateFixesDownloadStatus(fixIds);
|
|
11807
11969
|
return {
|
|
11808
|
-
fixes
|
|
11970
|
+
fixes,
|
|
11809
11971
|
totalCount: res.fixReport?.[0]?.filteredFixesCount?.aggregate?.count || 0,
|
|
11810
11972
|
expiredReport: res.expiredReport?.[0] || null
|
|
11811
11973
|
};
|
|
@@ -11819,82 +11981,29 @@ var McpGQLClient = class {
|
|
|
11819
11981
|
}
|
|
11820
11982
|
}
|
|
11821
11983
|
};
|
|
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
|
|
11984
|
+
async function createAuthenticatedMcpGQLClient({
|
|
11985
|
+
isBackgoundCall = false
|
|
11837
11986
|
} = {}) {
|
|
11838
11987
|
logDebug("getting config", { apiToken: mobbConfigStore.get("apiToken") });
|
|
11839
|
-
const
|
|
11988
|
+
const initialClient = new McpGQLClient({
|
|
11840
11989
|
apiKey: process.env["MOBB_API_KEY"] || process.env["API_KEY"] || // fallback for backward compatibility
|
|
11841
11990
|
mobbConfigStore.get("apiToken") || "",
|
|
11842
11991
|
type: "apiKey"
|
|
11843
11992
|
});
|
|
11844
|
-
const isConnected = await
|
|
11845
|
-
logDebug("
|
|
11993
|
+
const isConnected = await initialClient.verifyApiConnection();
|
|
11994
|
+
logDebug("API connection status", { isConnected });
|
|
11846
11995
|
if (!isConnected) {
|
|
11847
11996
|
throw new ApiConnectionError("Error: failed to connect to Mobb API");
|
|
11848
11997
|
}
|
|
11849
|
-
logDebug("
|
|
11850
|
-
const userVerify = await
|
|
11998
|
+
logDebug("validating user token");
|
|
11999
|
+
const userVerify = await initialClient.validateUserToken();
|
|
11851
12000
|
if (userVerify) {
|
|
11852
|
-
return
|
|
12001
|
+
return initialClient;
|
|
11853
12002
|
}
|
|
11854
|
-
|
|
11855
|
-
const
|
|
11856
|
-
|
|
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");
|
|
11896
|
-
}
|
|
11897
|
-
return newGqlClient;
|
|
12003
|
+
const authService = new McpAuthService(initialClient);
|
|
12004
|
+
const newApiToken = await authService.authenticate(isBackgoundCall);
|
|
12005
|
+
mobbConfigStore.set("apiToken", newApiToken);
|
|
12006
|
+
return new McpGQLClient({ apiKey: newApiToken, type: "apiKey" });
|
|
11898
12007
|
}
|
|
11899
12008
|
|
|
11900
12009
|
// src/mcp/core/ToolRegistry.ts
|
|
@@ -11951,14 +12060,11 @@ var McpServer = class {
|
|
|
11951
12060
|
this.toolRegistry = new ToolRegistry();
|
|
11952
12061
|
this.setupHandlers();
|
|
11953
12062
|
this.setupProcessEventHandlers();
|
|
11954
|
-
logInfo("MCP server instance created"
|
|
12063
|
+
logInfo("MCP server instance created");
|
|
12064
|
+
logDebug("MCP server instance config", { config: config4 });
|
|
11955
12065
|
}
|
|
11956
|
-
|
|
11957
|
-
|
|
11958
|
-
logDebug("Process event handlers already setup, skipping");
|
|
11959
|
-
return;
|
|
11960
|
-
}
|
|
11961
|
-
const signals = {
|
|
12066
|
+
handleProcessSignal(signal, error) {
|
|
12067
|
+
const messages = {
|
|
11962
12068
|
SIGINT: "MCP server interrupted",
|
|
11963
12069
|
SIGTERM: "MCP server terminated",
|
|
11964
12070
|
exit: "MCP server exiting",
|
|
@@ -11966,26 +12072,46 @@ var McpServer = class {
|
|
|
11966
12072
|
unhandledRejection: "Unhandled promise rejection in MCP server",
|
|
11967
12073
|
warning: "Warning in MCP server"
|
|
11968
12074
|
};
|
|
11969
|
-
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
|
|
11974
|
-
|
|
11975
|
-
|
|
11976
|
-
|
|
11977
|
-
|
|
11978
|
-
|
|
11979
|
-
|
|
11980
|
-
|
|
11981
|
-
|
|
11982
|
-
|
|
11983
|
-
|
|
11984
|
-
|
|
11985
|
-
|
|
12075
|
+
const message = messages[signal] || `Unhandled signal: ${signal}`;
|
|
12076
|
+
if (signal === "exit") {
|
|
12077
|
+
const exitCode = error;
|
|
12078
|
+
if (exitCode === 0 || exitCode === void 0) {
|
|
12079
|
+
logDebug(`${message} (clean exit)`, { signal, exitCode });
|
|
12080
|
+
} else {
|
|
12081
|
+
logWarn(`${message} (exit code: ${exitCode})`, { signal, exitCode });
|
|
12082
|
+
}
|
|
12083
|
+
} else if (error) {
|
|
12084
|
+
logError(`${message}`, { error, signal });
|
|
12085
|
+
} else {
|
|
12086
|
+
logDebug(message, { signal });
|
|
12087
|
+
}
|
|
12088
|
+
if (signal === "SIGINT" || signal === "SIGTERM") {
|
|
12089
|
+
process.exit(0);
|
|
12090
|
+
}
|
|
12091
|
+
if (signal === "uncaughtException") {
|
|
12092
|
+
process.exit(1);
|
|
12093
|
+
}
|
|
12094
|
+
}
|
|
12095
|
+
setupProcessEventHandlers() {
|
|
12096
|
+
if (this.isEventHandlersSetup) {
|
|
12097
|
+
logDebug("Process event handlers already setup, skipping");
|
|
12098
|
+
return;
|
|
12099
|
+
}
|
|
12100
|
+
const signals = [
|
|
12101
|
+
"SIGINT",
|
|
12102
|
+
"SIGTERM",
|
|
12103
|
+
"exit",
|
|
12104
|
+
"uncaughtException",
|
|
12105
|
+
"unhandledRejection",
|
|
12106
|
+
"warning"
|
|
12107
|
+
];
|
|
12108
|
+
signals.forEach((signal) => {
|
|
12109
|
+
process.on(signal, (error) => {
|
|
12110
|
+
this.handleProcessSignal(signal, error);
|
|
12111
|
+
});
|
|
11986
12112
|
});
|
|
11987
12113
|
this.isEventHandlersSetup = true;
|
|
11988
|
-
|
|
12114
|
+
logInfo("Process event handlers registered");
|
|
11989
12115
|
}
|
|
11990
12116
|
createShutdownPromise() {
|
|
11991
12117
|
return new Promise((resolve) => {
|
|
@@ -11997,12 +12123,45 @@ var McpServer = class {
|
|
|
11997
12123
|
process.once("SIGTERM", cleanup);
|
|
11998
12124
|
});
|
|
11999
12125
|
}
|
|
12126
|
+
async triggerScanForNewAvailableFixes() {
|
|
12127
|
+
const gqlClient = await createAuthenticatedMcpGQLClient({
|
|
12128
|
+
isBackgoundCall: true
|
|
12129
|
+
});
|
|
12130
|
+
const isConnected = await gqlClient.verifyApiConnection();
|
|
12131
|
+
if (!isConnected) {
|
|
12132
|
+
logError("Failed to connect to the API, skipping scan");
|
|
12133
|
+
return;
|
|
12134
|
+
}
|
|
12135
|
+
if (process.env["WORKSPACE_FOLDER_PATHS"]) {
|
|
12136
|
+
logDebug("WORKSPACE_FOLDER_PATHS is set", {
|
|
12137
|
+
WORKSPACE_FOLDER_PATHS: process.env["WORKSPACE_FOLDER_PATHS"]
|
|
12138
|
+
});
|
|
12139
|
+
try {
|
|
12140
|
+
const checkForNewAvailableFixesTool = this.toolRegistry.getTool(
|
|
12141
|
+
MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES
|
|
12142
|
+
);
|
|
12143
|
+
logInfo("Triggering periodic scan for new available fixes");
|
|
12144
|
+
checkForNewAvailableFixesTool.triggerScan({
|
|
12145
|
+
path: process.env["WORKSPACE_FOLDER_PATHS"],
|
|
12146
|
+
gqlClient
|
|
12147
|
+
});
|
|
12148
|
+
} catch (error) {
|
|
12149
|
+
logError("Error getting workspace folder path tool", { error });
|
|
12150
|
+
}
|
|
12151
|
+
}
|
|
12152
|
+
}
|
|
12000
12153
|
async handleListToolsRequest(request) {
|
|
12001
|
-
logInfo("Received list_tools request"
|
|
12002
|
-
|
|
12154
|
+
logInfo("Received list_tools request");
|
|
12155
|
+
logDebug("list_tools request", {
|
|
12156
|
+
request: JSON.parse(JSON.stringify(request))
|
|
12157
|
+
});
|
|
12158
|
+
logDebug("Request", {
|
|
12003
12159
|
request: JSON.parse(JSON.stringify(request))
|
|
12004
12160
|
});
|
|
12005
|
-
|
|
12161
|
+
logDebug("env", {
|
|
12162
|
+
env: process.env
|
|
12163
|
+
});
|
|
12164
|
+
void this.triggerScanForNewAvailableFixes();
|
|
12006
12165
|
const toolsDefinitions = this.toolRegistry.getAllTools();
|
|
12007
12166
|
const response = {
|
|
12008
12167
|
tools: toolsDefinitions.map((tool) => ({
|
|
@@ -12016,13 +12175,13 @@ var McpServer = class {
|
|
|
12016
12175
|
}
|
|
12017
12176
|
}))
|
|
12018
12177
|
};
|
|
12019
|
-
|
|
12178
|
+
logDebug("Returning list_tools response", { response });
|
|
12020
12179
|
return response;
|
|
12021
12180
|
}
|
|
12022
12181
|
async handleCallToolRequest(request) {
|
|
12023
12182
|
const { name, arguments: args } = request.params;
|
|
12024
|
-
logInfo(`Received call tool request for ${name}
|
|
12025
|
-
|
|
12183
|
+
logInfo(`Received call tool request for ${name}`);
|
|
12184
|
+
logDebug("Request", {
|
|
12026
12185
|
request: JSON.parse(JSON.stringify(request))
|
|
12027
12186
|
});
|
|
12028
12187
|
try {
|
|
@@ -12035,10 +12194,11 @@ var McpServer = class {
|
|
|
12035
12194
|
});
|
|
12036
12195
|
throw new Error(errorMsg);
|
|
12037
12196
|
}
|
|
12038
|
-
|
|
12197
|
+
logInfo(`Executing tool: ${name}`);
|
|
12039
12198
|
const response = await tool.execute(args);
|
|
12040
12199
|
const serializedResponse = JSON.parse(JSON.stringify(response));
|
|
12041
|
-
logInfo(`Tool ${name} executed successfully
|
|
12200
|
+
logInfo(`Tool ${name} executed successfully`);
|
|
12201
|
+
logDebug(`Tool ${name} executed successfully`, {
|
|
12042
12202
|
responseType: typeof response,
|
|
12043
12203
|
hasContent: !!serializedResponse.content
|
|
12044
12204
|
});
|
|
@@ -12062,18 +12222,18 @@ var McpServer = class {
|
|
|
12062
12222
|
CallToolRequestSchema,
|
|
12063
12223
|
(request) => this.handleCallToolRequest(request)
|
|
12064
12224
|
);
|
|
12065
|
-
|
|
12225
|
+
logInfo("MCP server handlers registered");
|
|
12066
12226
|
}
|
|
12067
12227
|
registerTool(tool) {
|
|
12068
12228
|
this.toolRegistry.registerTool(tool);
|
|
12069
|
-
|
|
12229
|
+
logInfo(`Tool registered: ${tool.name}`);
|
|
12070
12230
|
}
|
|
12071
12231
|
async start() {
|
|
12072
12232
|
try {
|
|
12073
|
-
|
|
12233
|
+
logInfo("Starting MCP server");
|
|
12074
12234
|
const transport = new StdioServerTransport();
|
|
12075
12235
|
await this.server.connect(transport);
|
|
12076
|
-
|
|
12236
|
+
logDebug("MCP server is running on stdin/stdout");
|
|
12077
12237
|
process.stdin.resume();
|
|
12078
12238
|
await this.createShutdownPromise();
|
|
12079
12239
|
await this.stop();
|
|
@@ -12083,7 +12243,7 @@ var McpServer = class {
|
|
|
12083
12243
|
}
|
|
12084
12244
|
}
|
|
12085
12245
|
async stop() {
|
|
12086
|
-
|
|
12246
|
+
logDebug("MCP server shutting down");
|
|
12087
12247
|
}
|
|
12088
12248
|
};
|
|
12089
12249
|
|
|
@@ -12168,14 +12328,15 @@ var BaseTool = class {
|
|
|
12168
12328
|
};
|
|
12169
12329
|
}
|
|
12170
12330
|
async execute(args) {
|
|
12171
|
-
|
|
12172
|
-
const mcpGqlClient = await
|
|
12331
|
+
logDebug(`Authenticating tool: ${this.name}`, { args });
|
|
12332
|
+
const mcpGqlClient = await createAuthenticatedMcpGQLClient();
|
|
12173
12333
|
const userInfo = await mcpGqlClient.getUserInfo();
|
|
12174
|
-
logDebug("
|
|
12334
|
+
logDebug("User authenticated successfully", { userInfo });
|
|
12175
12335
|
const validatedArgs = this.validateInput(args);
|
|
12176
12336
|
logDebug(`Tool ${this.name} input validation successful`, {
|
|
12177
12337
|
validatedArgs
|
|
12178
12338
|
});
|
|
12339
|
+
logInfo(`Executing tool: ${this.name}`);
|
|
12179
12340
|
const result = await this.executeInternal(validatedArgs);
|
|
12180
12341
|
logInfo(`Tool ${this.name} executed successfully`);
|
|
12181
12342
|
return result;
|
|
@@ -12209,7 +12370,7 @@ var BaseTool = class {
|
|
|
12209
12370
|
};
|
|
12210
12371
|
|
|
12211
12372
|
// src/mcp/core/prompts.ts
|
|
12212
|
-
function
|
|
12373
|
+
function friendlyType(s) {
|
|
12213
12374
|
const withoutUnderscores = s.replace(/_/g, " ");
|
|
12214
12375
|
const result = withoutUnderscores.replace(/([a-z])([A-Z])/g, "$1 $2");
|
|
12215
12376
|
return result.charAt(0).toUpperCase() + result.slice(1);
|
|
@@ -12229,13 +12390,15 @@ var applyFixesPrompt = ({
|
|
|
12229
12390
|
return noFixesReturnedForParameters;
|
|
12230
12391
|
}
|
|
12231
12392
|
const fixList = fixes.map((fix) => {
|
|
12232
|
-
const vulnerabilityType =
|
|
12393
|
+
const vulnerabilityType = friendlyType(fix.safeIssueType);
|
|
12233
12394
|
const vulnerabilityDescription = fix.patchAndQuestions?.__typename === "FixData" ? fix.patchAndQuestions.extraContext?.fixDescription : void 0;
|
|
12234
12395
|
const patch = fix.patchAndQuestions?.__typename === "FixData" ? fix.patchAndQuestions.patch : void 0;
|
|
12396
|
+
const gitBlameLogin = fix.gitBlameLogin;
|
|
12235
12397
|
return {
|
|
12236
12398
|
vulnerabilityType,
|
|
12237
12399
|
vulnerabilityDescription,
|
|
12238
|
-
patch
|
|
12400
|
+
patch,
|
|
12401
|
+
gitBlameLogin
|
|
12239
12402
|
};
|
|
12240
12403
|
});
|
|
12241
12404
|
return `## CRITICAL INSTRUCTIONS - READ CAREFULLY
|
|
@@ -12287,7 +12450,9 @@ ${fixList.map(
|
|
|
12287
12450
|
|
|
12288
12451
|
**\u{1F4DD} Description:** ${fix.vulnerabilityDescription || "Security vulnerability fix"}
|
|
12289
12452
|
|
|
12290
|
-
|
|
12453
|
+
${fix.gitBlameLogin ? `**\u{1F464} Git Blame:** The code that needs to be fixed was last modified by: \`${fix.gitBlameLogin}\`
|
|
12454
|
+
|
|
12455
|
+
` : ""}**\u{1F527} Action Required:** Apply the following patch exactly as shown
|
|
12291
12456
|
|
|
12292
12457
|
**\u{1F4C1} Patch to Apply:**
|
|
12293
12458
|
\`\`\`diff
|
|
@@ -12334,7 +12499,7 @@ We were unable to find a previous vulnerability report for this repository. This
|
|
|
12334
12499
|
|
|
12335
12500
|
### \u{1F3AF} Recommended Actions
|
|
12336
12501
|
1. **Run a new security scan** to analyze your codebase
|
|
12337
|
-
- Use the \`
|
|
12502
|
+
- Use the \`${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES}\` tool to start a fresh scan
|
|
12338
12503
|
- This will analyze your current code for security issues
|
|
12339
12504
|
|
|
12340
12505
|
2. **Verify repository access**
|
|
@@ -12362,7 +12527,7 @@ Your most recent vulnerability report for this repository **expired on ${lastRep
|
|
|
12362
12527
|
|
|
12363
12528
|
### \u{1F3AF} Recommended Actions
|
|
12364
12529
|
1. **Run a fresh security scan** to generate an up-to-date vulnerability report.
|
|
12365
|
-
- Use the \`
|
|
12530
|
+
- Use the \`${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES}\` tool.
|
|
12366
12531
|
2. **Verify repository access** if scans fail to run or the repository has moved.
|
|
12367
12532
|
3. **Review your CI/CD pipeline** to ensure regular scans are triggered.
|
|
12368
12533
|
|
|
@@ -12416,7 +12581,7 @@ ${applyFixesPrompt({
|
|
|
12416
12581
|
hasMore,
|
|
12417
12582
|
nextOffset: 0,
|
|
12418
12583
|
shownCount: fixReport.fixes.length,
|
|
12419
|
-
currentTool:
|
|
12584
|
+
currentTool: MCP_TOOL_FETCH_AVAILABLE_FIXES,
|
|
12420
12585
|
offset
|
|
12421
12586
|
})}`;
|
|
12422
12587
|
};
|
|
@@ -12441,7 +12606,6 @@ To perform a **rescan** of your repository (fetching a brand-new vulnerability r
|
|
|
12441
12606
|
This will start a new analysis, discard any cached results.
|
|
12442
12607
|
|
|
12443
12608
|
\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
12609
|
`;
|
|
12446
12610
|
var noFixesFoundPrompt = ({
|
|
12447
12611
|
scannedFiles
|
|
@@ -12467,20 +12631,19 @@ var fixesPrompt = ({
|
|
|
12467
12631
|
|
|
12468
12632
|
${applyFixesPrompt({
|
|
12469
12633
|
fixes,
|
|
12470
|
-
totalCount,
|
|
12471
12634
|
hasMore,
|
|
12635
|
+
totalCount,
|
|
12472
12636
|
nextOffset,
|
|
12473
12637
|
shownCount,
|
|
12474
|
-
currentTool:
|
|
12638
|
+
currentTool: MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES,
|
|
12475
12639
|
offset
|
|
12476
12640
|
})}
|
|
12477
12641
|
|
|
12478
12642
|
${nextStepsPrompt({ scannedFiles })}
|
|
12479
12643
|
`;
|
|
12480
12644
|
};
|
|
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.`;
|
|
12645
|
+
var noFreshFixesPrompt = `No fresh fixes available for this repository at this time. No user message needed. Do not show any comments to the user.`;
|
|
12646
|
+
var initialScanInProgressPrompt = `Initial scan is still in progress. No user message needed. Do not show any comments to the user.`;
|
|
12484
12647
|
var freshFixesPrompt = ({ fixes }) => {
|
|
12485
12648
|
return `Here are the fresh fixes to the vulnerabilities discovered by Mobb MCP
|
|
12486
12649
|
|
|
@@ -12490,7 +12653,7 @@ ${applyFixesPrompt({
|
|
|
12490
12653
|
hasMore: false,
|
|
12491
12654
|
nextOffset: 0,
|
|
12492
12655
|
shownCount: fixes.length,
|
|
12493
|
-
currentTool:
|
|
12656
|
+
currentTool: MCP_TOOL_FETCH_AVAILABLE_FIXES,
|
|
12494
12657
|
offset: 0
|
|
12495
12658
|
})}
|
|
12496
12659
|
`;
|
|
@@ -12579,23 +12742,135 @@ var getLocalFiles = async ({
|
|
|
12579
12742
|
return filesWithStats.filter((file) => file.lastEdited > 0);
|
|
12580
12743
|
};
|
|
12581
12744
|
|
|
12582
|
-
// src/mcp/services/
|
|
12745
|
+
// src/mcp/services/FileOperations.ts
|
|
12583
12746
|
import fs11 from "fs";
|
|
12584
12747
|
import path12 from "path";
|
|
12585
12748
|
import AdmZip2 from "adm-zip";
|
|
12749
|
+
var FileOperations = class {
|
|
12750
|
+
/**
|
|
12751
|
+
* Creates a ZIP archive containing the specified source files
|
|
12752
|
+
* @param fileList Array of relative file paths to include
|
|
12753
|
+
* @param repositoryPath Base path for resolving relative file paths
|
|
12754
|
+
* @param maxFileSize Maximum size allowed for individual files
|
|
12755
|
+
* @returns ZIP archive as a Buffer with metadata
|
|
12756
|
+
*/
|
|
12757
|
+
async createSourceCodeArchive(fileList, repositoryPath, maxFileSize) {
|
|
12758
|
+
logDebug("FilePacking: packing files");
|
|
12759
|
+
const zip = new AdmZip2();
|
|
12760
|
+
let packedFilesCount = 0;
|
|
12761
|
+
const resolvedRepoPath = path12.resolve(repositoryPath);
|
|
12762
|
+
for (const filepath of fileList) {
|
|
12763
|
+
const absoluteFilepath = path12.join(repositoryPath, filepath);
|
|
12764
|
+
const resolvedFilePath = path12.resolve(absoluteFilepath);
|
|
12765
|
+
if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
|
|
12766
|
+
logDebug(
|
|
12767
|
+
`Skipping ${filepath} due to potential path traversal security risk`
|
|
12768
|
+
);
|
|
12769
|
+
continue;
|
|
12770
|
+
}
|
|
12771
|
+
if (!FileUtils.shouldPackFile(absoluteFilepath, maxFileSize)) {
|
|
12772
|
+
logDebug(
|
|
12773
|
+
`Excluding ${filepath} - file is too large, binary, or matches exclusion rules`
|
|
12774
|
+
);
|
|
12775
|
+
continue;
|
|
12776
|
+
}
|
|
12777
|
+
const fileContent = await this.readSourceFile(absoluteFilepath, filepath);
|
|
12778
|
+
if (fileContent) {
|
|
12779
|
+
zip.addFile(filepath, fileContent);
|
|
12780
|
+
packedFilesCount++;
|
|
12781
|
+
}
|
|
12782
|
+
}
|
|
12783
|
+
const archiveBuffer = zip.toBuffer();
|
|
12784
|
+
const result = {
|
|
12785
|
+
archive: archiveBuffer,
|
|
12786
|
+
packedFilesCount,
|
|
12787
|
+
totalSize: archiveBuffer.length
|
|
12788
|
+
};
|
|
12789
|
+
logInfo("Files packed successfully");
|
|
12790
|
+
return result;
|
|
12791
|
+
}
|
|
12792
|
+
/**
|
|
12793
|
+
* Validates that file paths are within the repository and safe to access
|
|
12794
|
+
* @param fileList Array of relative file paths to validate
|
|
12795
|
+
* @param repositoryPath Base path for validation
|
|
12796
|
+
* @returns Array of validated file paths
|
|
12797
|
+
*/
|
|
12798
|
+
async validateFilePaths(fileList, repositoryPath) {
|
|
12799
|
+
const resolvedRepoPath = path12.resolve(repositoryPath);
|
|
12800
|
+
const validatedPaths = [];
|
|
12801
|
+
for (const filepath of fileList) {
|
|
12802
|
+
const absoluteFilepath = path12.join(repositoryPath, filepath);
|
|
12803
|
+
const resolvedFilePath = path12.resolve(absoluteFilepath);
|
|
12804
|
+
if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
|
|
12805
|
+
logDebug(`Rejecting ${filepath} - path traversal attempt detected`);
|
|
12806
|
+
continue;
|
|
12807
|
+
}
|
|
12808
|
+
try {
|
|
12809
|
+
await fs11.promises.access(absoluteFilepath, fs11.constants.R_OK);
|
|
12810
|
+
validatedPaths.push(filepath);
|
|
12811
|
+
} catch (error) {
|
|
12812
|
+
logDebug(`Skipping ${filepath} - file is not accessible: ${error}`);
|
|
12813
|
+
}
|
|
12814
|
+
}
|
|
12815
|
+
return validatedPaths;
|
|
12816
|
+
}
|
|
12817
|
+
/**
|
|
12818
|
+
* Reads source files and returns their data
|
|
12819
|
+
* @param filePaths Array of absolute file paths to read
|
|
12820
|
+
* @returns Array of file data objects
|
|
12821
|
+
*/
|
|
12822
|
+
async readSourceFiles(filePaths) {
|
|
12823
|
+
const fileDataArray = [];
|
|
12824
|
+
for (const absolutePath of filePaths) {
|
|
12825
|
+
try {
|
|
12826
|
+
const content = await fs11.promises.readFile(absolutePath);
|
|
12827
|
+
const relativePath = path12.basename(absolutePath);
|
|
12828
|
+
fileDataArray.push({
|
|
12829
|
+
relativePath,
|
|
12830
|
+
absolutePath,
|
|
12831
|
+
content
|
|
12832
|
+
});
|
|
12833
|
+
} catch (error) {
|
|
12834
|
+
logError(`Failed to read file ${absolutePath}: ${error}`);
|
|
12835
|
+
}
|
|
12836
|
+
}
|
|
12837
|
+
return fileDataArray;
|
|
12838
|
+
}
|
|
12839
|
+
/**
|
|
12840
|
+
* Safely reads a single source file
|
|
12841
|
+
* @param absoluteFilepath Absolute path to the file
|
|
12842
|
+
* @param relativeFilepath Relative path for logging purposes
|
|
12843
|
+
* @returns File content as Buffer or null if failed
|
|
12844
|
+
*/
|
|
12845
|
+
async readSourceFile(absoluteFilepath, relativeFilepath) {
|
|
12846
|
+
try {
|
|
12847
|
+
return await fs11.promises.readFile(absoluteFilepath);
|
|
12848
|
+
} catch (fsError) {
|
|
12849
|
+
logError(`Failed to read ${relativeFilepath} from filesystem: ${fsError}`);
|
|
12850
|
+
return null;
|
|
12851
|
+
}
|
|
12852
|
+
}
|
|
12853
|
+
};
|
|
12854
|
+
|
|
12855
|
+
// src/mcp/services/ScanFiles.ts
|
|
12586
12856
|
var scanFiles = async (fileList, repositoryPath, gqlClient) => {
|
|
12587
|
-
const repoUploadInfo = await
|
|
12857
|
+
const repoUploadInfo = await initializeSecurityReport(gqlClient);
|
|
12588
12858
|
const fixReportId = repoUploadInfo.fixReportId;
|
|
12589
|
-
const
|
|
12590
|
-
await
|
|
12859
|
+
const fileOperations = new FileOperations();
|
|
12860
|
+
const packingResult = await fileOperations.createSourceCodeArchive(
|
|
12861
|
+
fileList,
|
|
12862
|
+
repositoryPath,
|
|
12863
|
+
MCP_MAX_FILE_SIZE
|
|
12864
|
+
);
|
|
12865
|
+
await uploadSourceCodeArchive(packingResult.archive, repoUploadInfo);
|
|
12591
12866
|
const projectId = await getProjectId(gqlClient);
|
|
12592
|
-
await
|
|
12867
|
+
await executeSecurityScan({ fixReportId, projectId, gqlClient });
|
|
12593
12868
|
return {
|
|
12594
12869
|
fixReportId,
|
|
12595
12870
|
projectId
|
|
12596
12871
|
};
|
|
12597
12872
|
};
|
|
12598
|
-
var
|
|
12873
|
+
var initializeSecurityReport = async (gqlClient) => {
|
|
12599
12874
|
if (!gqlClient) {
|
|
12600
12875
|
throw new GqlClientError();
|
|
12601
12876
|
}
|
|
@@ -12603,74 +12878,33 @@ var initializeReport = async (gqlClient) => {
|
|
|
12603
12878
|
const {
|
|
12604
12879
|
uploadS3BucketInfo: { repoUploadInfo }
|
|
12605
12880
|
} = await gqlClient.uploadS3BucketInfo();
|
|
12606
|
-
|
|
12881
|
+
logDebug("Upload info retrieved");
|
|
12607
12882
|
return repoUploadInfo;
|
|
12608
12883
|
} catch (error) {
|
|
12609
12884
|
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`
|
|
12885
|
+
throw new ReportInitializationError(
|
|
12886
|
+
`Error initializing security report: ${message}`
|
|
12650
12887
|
);
|
|
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
12888
|
}
|
|
12657
12889
|
};
|
|
12658
|
-
var
|
|
12890
|
+
var uploadSourceCodeArchive = async (archiveBuffer, repoUploadInfo) => {
|
|
12659
12891
|
if (!repoUploadInfo) {
|
|
12660
|
-
throw new FileUploadError("Upload info is required");
|
|
12892
|
+
throw new FileUploadError("Upload info is required for source code archive");
|
|
12661
12893
|
}
|
|
12662
12894
|
try {
|
|
12663
12895
|
await uploadFile({
|
|
12664
|
-
file:
|
|
12896
|
+
file: archiveBuffer,
|
|
12665
12897
|
url: repoUploadInfo.url,
|
|
12666
12898
|
uploadFields: JSON.parse(repoUploadInfo.uploadFieldsJSON),
|
|
12667
12899
|
uploadKey: repoUploadInfo.uploadKey
|
|
12668
12900
|
});
|
|
12669
12901
|
logInfo("File uploaded successfully");
|
|
12670
12902
|
} catch (error) {
|
|
12671
|
-
logError("
|
|
12903
|
+
logError("Source code archive upload failed", {
|
|
12904
|
+
error: error.message
|
|
12905
|
+
});
|
|
12672
12906
|
throw new FileUploadError(
|
|
12673
|
-
`Failed to upload
|
|
12907
|
+
`Failed to upload source code archive: ${error.message}`
|
|
12674
12908
|
);
|
|
12675
12909
|
}
|
|
12676
12910
|
};
|
|
@@ -12679,10 +12913,10 @@ var getProjectId = async (gqlClient) => {
|
|
|
12679
12913
|
throw new GqlClientError();
|
|
12680
12914
|
}
|
|
12681
12915
|
const projectId = await gqlClient.getProjectId();
|
|
12682
|
-
|
|
12916
|
+
logDebug("Project ID retrieved");
|
|
12683
12917
|
return projectId;
|
|
12684
12918
|
};
|
|
12685
|
-
var
|
|
12919
|
+
var executeSecurityScan = async ({
|
|
12686
12920
|
fixReportId,
|
|
12687
12921
|
projectId,
|
|
12688
12922
|
gqlClient
|
|
@@ -12690,7 +12924,7 @@ var runScan = async ({
|
|
|
12690
12924
|
if (!gqlClient) {
|
|
12691
12925
|
throw new GqlClientError();
|
|
12692
12926
|
}
|
|
12693
|
-
logInfo("Starting scan"
|
|
12927
|
+
logInfo("Starting scan");
|
|
12694
12928
|
const submitVulnerabilityReportVariables = {
|
|
12695
12929
|
fixReportId,
|
|
12696
12930
|
projectId,
|
|
@@ -12703,25 +12937,28 @@ var runScan = async ({
|
|
|
12703
12937
|
submitVulnerabilityReportVariables
|
|
12704
12938
|
);
|
|
12705
12939
|
if (submitRes.submitVulnerabilityReport.__typename !== "VulnerabilityReport") {
|
|
12706
|
-
|
|
12707
|
-
|
|
12940
|
+
throw new ScanError(
|
|
12941
|
+
`Security scan submission failed: ${submitRes.submitVulnerabilityReport.__typename}`
|
|
12942
|
+
);
|
|
12943
|
+
}
|
|
12944
|
+
const analysisId = submitRes.submitVulnerabilityReport.fixReportId;
|
|
12945
|
+
logInfo("Vulnerability report submitted successfully");
|
|
12946
|
+
try {
|
|
12947
|
+
await gqlClient.subscribeToGetAnalysis({
|
|
12948
|
+
subscribeToAnalysisParams: { analysisId },
|
|
12949
|
+
callback: async (completedAnalysisId) => {
|
|
12950
|
+
logInfo("Security analysis completed successfully", {
|
|
12951
|
+
analysisId: completedAnalysisId
|
|
12952
|
+
});
|
|
12953
|
+
},
|
|
12954
|
+
callbackStates: ["Finished" /* Finished */],
|
|
12955
|
+
timeoutInMs: MCP_VUL_REPORT_DIGEST_TIMEOUT_MS
|
|
12708
12956
|
});
|
|
12709
|
-
|
|
12957
|
+
} catch (error) {
|
|
12958
|
+
logError("Security analysis failed or timed out", { error, analysisId });
|
|
12959
|
+
throw new ScanError(`Security analysis failed: ${error.message}`);
|
|
12710
12960
|
}
|
|
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");
|
|
12961
|
+
logDebug("Security scan completed successfully", { fixReportId, projectId });
|
|
12725
12962
|
};
|
|
12726
12963
|
|
|
12727
12964
|
// src/mcp/tools/checkForNewAvailableFixes/CheckForNewAvailableFixesService.ts
|
|
@@ -12742,6 +12979,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
12742
12979
|
__publicField(this, "reportedFixes", []);
|
|
12743
12980
|
__publicField(this, "intervalId", null);
|
|
12744
12981
|
__publicField(this, "isInitialScanComplete", false);
|
|
12982
|
+
__publicField(this, "gqlClient", null);
|
|
12745
12983
|
}
|
|
12746
12984
|
static getInstance() {
|
|
12747
12985
|
if (!_CheckForNewAvailableFixesService.instance) {
|
|
@@ -12763,25 +13001,28 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
12763
13001
|
}
|
|
12764
13002
|
}
|
|
12765
13003
|
/**
|
|
12766
|
-
*
|
|
12767
|
-
*
|
|
12768
|
-
* simply returns a placeholder string so that the tool can be wired into the
|
|
12769
|
-
* system and used in tests.
|
|
13004
|
+
* Scans repository for security vulnerabilities and identifies new fixes
|
|
13005
|
+
* since the last scan.
|
|
12770
13006
|
*/
|
|
12771
|
-
async
|
|
12772
|
-
|
|
12773
|
-
|
|
12774
|
-
|
|
13007
|
+
async scanForSecurityVulnerabilities({
|
|
13008
|
+
path: path13
|
|
13009
|
+
}) {
|
|
13010
|
+
logDebug("Scanning for new security vulnerabilities", { path: path13 });
|
|
13011
|
+
if (!this.gqlClient) {
|
|
13012
|
+
logInfo("No GQL client found, skipping scan");
|
|
13013
|
+
return;
|
|
13014
|
+
}
|
|
13015
|
+
const isConnected = await this.gqlClient.verifyApiConnection();
|
|
12775
13016
|
if (!isConnected) {
|
|
12776
13017
|
logError("Failed to connect to the API, scan aborted");
|
|
12777
13018
|
return;
|
|
12778
13019
|
}
|
|
12779
|
-
|
|
13020
|
+
logDebug("Connected to the API, assembling list of files to scan", { path: path13 });
|
|
12780
13021
|
const files = await getLocalFiles({
|
|
12781
13022
|
path: path13,
|
|
12782
13023
|
maxFileSize: MCP_MAX_FILE_SIZE
|
|
12783
13024
|
});
|
|
12784
|
-
|
|
13025
|
+
logDebug("Active files", { files });
|
|
12785
13026
|
const filesToScan = files.filter((file) => {
|
|
12786
13027
|
const lastScannedEditTime = this.filesLastScanned[file.fullPath];
|
|
12787
13028
|
if (!lastScannedEditTime) {
|
|
@@ -12790,34 +13031,45 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
12790
13031
|
return file.lastEdited > lastScannedEditTime;
|
|
12791
13032
|
});
|
|
12792
13033
|
if (filesToScan.length === 0) {
|
|
12793
|
-
logInfo("No files
|
|
13034
|
+
logInfo("No files require scanning");
|
|
12794
13035
|
return;
|
|
12795
13036
|
}
|
|
12796
|
-
|
|
13037
|
+
logDebug("Files requiring security scan", { filesToScan });
|
|
12797
13038
|
const { fixReportId, projectId } = await scanFiles(
|
|
12798
13039
|
filesToScan.map((file) => file.relativePath),
|
|
12799
13040
|
path13,
|
|
12800
|
-
gqlClient
|
|
13041
|
+
this.gqlClient
|
|
13042
|
+
);
|
|
13043
|
+
logInfo(
|
|
13044
|
+
`Security scan completed for ${path13} reportId: ${fixReportId} projectId: ${projectId}`
|
|
12801
13045
|
);
|
|
12802
|
-
|
|
12803
|
-
const fixes = await gqlClient.getReportFixesPaginated({
|
|
13046
|
+
const fixes = await this.gqlClient.getReportFixesPaginated({
|
|
12804
13047
|
reportId: fixReportId,
|
|
12805
13048
|
offset: 0,
|
|
12806
13049
|
limit: 1e3
|
|
12807
13050
|
});
|
|
12808
|
-
const newFixes = fixes?.fixes?.filter(
|
|
12809
|
-
|
|
12810
|
-
|
|
12811
|
-
|
|
13051
|
+
const newFixes = fixes?.fixes?.filter(
|
|
13052
|
+
(fix) => !this.isFixAlreadyReported(fix)
|
|
13053
|
+
);
|
|
13054
|
+
logInfo(
|
|
13055
|
+
`Security fixes retrieved, total: ${fixes?.fixes?.length || 0}, new: ${newFixes?.length || 0}`
|
|
13056
|
+
);
|
|
13057
|
+
this.updateFreshFixesCache(newFixes || [], filesToScan);
|
|
13058
|
+
this.updateFilesScanTimestamps(filesToScan);
|
|
13059
|
+
this.isInitialScanComplete = true;
|
|
13060
|
+
}
|
|
13061
|
+
updateFreshFixesCache(newFixes, filesToScan) {
|
|
13062
|
+
this.freshFixes = this.freshFixes.filter((fix) => !this.isFixFromOldScan(fix, filesToScan)).concat(newFixes).sort((a, b) => {
|
|
13063
|
+
return (b.severityValue ?? 0) - (a.severityValue ?? 0);
|
|
12812
13064
|
});
|
|
12813
|
-
|
|
12814
|
-
|
|
13065
|
+
logInfo(`Fresh fixes cache updated, total: ${this.freshFixes.length}`);
|
|
13066
|
+
}
|
|
13067
|
+
updateFilesScanTimestamps(filesToScan) {
|
|
12815
13068
|
filesToScan.forEach((file) => {
|
|
12816
13069
|
this.filesLastScanned[file.fullPath] = file.lastEdited;
|
|
12817
13070
|
});
|
|
12818
|
-
this.isInitialScanComplete = true;
|
|
12819
13071
|
}
|
|
12820
|
-
|
|
13072
|
+
isFixAlreadyReported(fix) {
|
|
12821
13073
|
return this.reportedFixes.some(
|
|
12822
13074
|
(reportedFix) => reportedFix.sharedState?.id === fix.sharedState?.id
|
|
12823
13075
|
);
|
|
@@ -12828,10 +13080,10 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
12828
13080
|
if (!fixFile) {
|
|
12829
13081
|
return false;
|
|
12830
13082
|
}
|
|
12831
|
-
|
|
13083
|
+
logDebug("Checking if fix is from old scan", {
|
|
12832
13084
|
fixFile,
|
|
12833
13085
|
filesToScan,
|
|
12834
|
-
|
|
13086
|
+
isFromOldScan: filesToScan.some((file) => file.relativePath === fixFile)
|
|
12835
13087
|
});
|
|
12836
13088
|
return filesToScan.some((file) => file.relativePath === fixFile);
|
|
12837
13089
|
}
|
|
@@ -12840,31 +13092,51 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
12840
13092
|
this.path = path13;
|
|
12841
13093
|
this.reset();
|
|
12842
13094
|
}
|
|
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
|
-
}
|
|
13095
|
+
this.gqlClient = await createAuthenticatedMcpGQLClient();
|
|
13096
|
+
this.triggerScan({ path: path13, gqlClient: this.gqlClient });
|
|
12856
13097
|
if (this.freshFixes.length > 0) {
|
|
12857
|
-
|
|
12858
|
-
if (freshFixes.length > 0) {
|
|
12859
|
-
this.reportedFixes.concat(freshFixes);
|
|
12860
|
-
return freshFixesPrompt({ fixes: freshFixes });
|
|
12861
|
-
}
|
|
13098
|
+
return this.generateFreshFixesResponse();
|
|
12862
13099
|
}
|
|
12863
13100
|
if (!this.isInitialScanComplete) {
|
|
12864
13101
|
return initialScanInProgressPrompt;
|
|
12865
13102
|
}
|
|
12866
13103
|
return noFreshFixesPrompt;
|
|
12867
13104
|
}
|
|
13105
|
+
triggerScan({
|
|
13106
|
+
path: path13,
|
|
13107
|
+
gqlClient
|
|
13108
|
+
}) {
|
|
13109
|
+
this.gqlClient = gqlClient;
|
|
13110
|
+
if (!this.intervalId) {
|
|
13111
|
+
this.startPeriodicScanning(path13);
|
|
13112
|
+
this.executeInitialScan(path13);
|
|
13113
|
+
}
|
|
13114
|
+
}
|
|
13115
|
+
startPeriodicScanning(path13) {
|
|
13116
|
+
logDebug("Starting periodic scan for new security vulnerabilities", {
|
|
13117
|
+
path: path13
|
|
13118
|
+
});
|
|
13119
|
+
this.intervalId = setInterval(() => {
|
|
13120
|
+
logDebug("Triggering periodic security scan", { path: path13 });
|
|
13121
|
+
this.scanForSecurityVulnerabilities({ path: path13 }).catch((error) => {
|
|
13122
|
+
logError("Error during periodic security scan", { error });
|
|
13123
|
+
});
|
|
13124
|
+
}, MCP_PERIODIC_CHECK_INTERVAL);
|
|
13125
|
+
}
|
|
13126
|
+
executeInitialScan(path13) {
|
|
13127
|
+
logDebug("Triggering initial security scan", { path: path13 });
|
|
13128
|
+
this.scanForSecurityVulnerabilities({ path: path13 }).catch((error) => {
|
|
13129
|
+
logError("Error during initial security scan", { error });
|
|
13130
|
+
});
|
|
13131
|
+
}
|
|
13132
|
+
generateFreshFixesResponse() {
|
|
13133
|
+
const freshFixes = this.freshFixes.splice(0, 3);
|
|
13134
|
+
if (freshFixes.length > 0) {
|
|
13135
|
+
this.reportedFixes.push(...freshFixes);
|
|
13136
|
+
return freshFixesPrompt({ fixes: freshFixes });
|
|
13137
|
+
}
|
|
13138
|
+
return noFreshFixesPrompt;
|
|
13139
|
+
}
|
|
12868
13140
|
};
|
|
12869
13141
|
__publicField(_CheckForNewAvailableFixesService, "instance");
|
|
12870
13142
|
var CheckForNewAvailableFixesService = _CheckForNewAvailableFixesService;
|
|
@@ -12873,7 +13145,7 @@ var CheckForNewAvailableFixesService = _CheckForNewAvailableFixesService;
|
|
|
12873
13145
|
var CheckForNewAvailableFixesTool = class extends BaseTool {
|
|
12874
13146
|
constructor() {
|
|
12875
13147
|
super();
|
|
12876
|
-
__publicField(this, "name",
|
|
13148
|
+
__publicField(this, "name", MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES);
|
|
12877
13149
|
__publicField(this, "displayName", "Check for New Available Fixes");
|
|
12878
13150
|
// A detailed description to guide the LLM on when and how to invoke this tool.
|
|
12879
13151
|
__publicField(this, "description", `Continuesly monitors your code and scans for new security vulnerabilities.
|
|
@@ -12913,6 +13185,9 @@ Example payload:
|
|
|
12913
13185
|
__publicField(this, "newFixesService");
|
|
12914
13186
|
this.newFixesService = new CheckForNewAvailableFixesService();
|
|
12915
13187
|
}
|
|
13188
|
+
triggerScan(args) {
|
|
13189
|
+
this.newFixesService.triggerScan(args);
|
|
13190
|
+
}
|
|
12916
13191
|
async executeInternal(args) {
|
|
12917
13192
|
const pathValidationResult = await validatePath(args.path);
|
|
12918
13193
|
if (!pathValidationResult.isValid) {
|
|
@@ -12927,14 +13202,7 @@ Example payload:
|
|
|
12927
13202
|
logInfo("CheckForNewAvailableFixesTool execution completed", {
|
|
12928
13203
|
resultText
|
|
12929
13204
|
});
|
|
12930
|
-
return
|
|
12931
|
-
content: [
|
|
12932
|
-
{
|
|
12933
|
-
type: "text",
|
|
12934
|
-
text: resultText
|
|
12935
|
-
}
|
|
12936
|
-
]
|
|
12937
|
-
};
|
|
13205
|
+
return this.createSuccessResponse(resultText);
|
|
12938
13206
|
}
|
|
12939
13207
|
};
|
|
12940
13208
|
|
|
@@ -12958,7 +13226,7 @@ var _FetchAvailableFixesService = class _FetchAvailableFixesService {
|
|
|
12958
13226
|
}
|
|
12959
13227
|
async initializeGqlClient() {
|
|
12960
13228
|
if (!this.gqlClient) {
|
|
12961
|
-
this.gqlClient = await
|
|
13229
|
+
this.gqlClient = await createAuthenticatedMcpGQLClient();
|
|
12962
13230
|
}
|
|
12963
13231
|
return this.gqlClient;
|
|
12964
13232
|
}
|
|
@@ -12982,21 +13250,18 @@ var _FetchAvailableFixesService = class _FetchAvailableFixesService {
|
|
|
12982
13250
|
logDebug("received latest report result", { fixReport, expiredReport });
|
|
12983
13251
|
if (!fixReport) {
|
|
12984
13252
|
if (expiredReport) {
|
|
13253
|
+
logInfo("Expired report found");
|
|
12985
13254
|
const lastReportDate = expiredReport.expirationOn ? new Date(expiredReport.expirationOn).toLocaleString() : "Unknown date";
|
|
12986
|
-
|
|
13255
|
+
logDebug("Expired report ", {
|
|
12987
13256
|
repoUrl,
|
|
12988
13257
|
expirationOn: expiredReport.expirationOn
|
|
12989
13258
|
});
|
|
12990
13259
|
return expiredReportPrompt({ lastReportDate });
|
|
12991
13260
|
}
|
|
12992
|
-
logInfo(
|
|
12993
|
-
repoUrl
|
|
12994
|
-
});
|
|
13261
|
+
logInfo(`No report (active or expired) found for repository ${repoUrl}`);
|
|
12995
13262
|
return noReportFoundPrompt;
|
|
12996
13263
|
}
|
|
12997
|
-
logInfo(
|
|
12998
|
-
reportFound: true
|
|
12999
|
-
});
|
|
13264
|
+
logInfo(`Successfully retrieved available fixes for ${repoUrl}`);
|
|
13000
13265
|
const prompt = fixesFoundPrompt({
|
|
13001
13266
|
fixReport,
|
|
13002
13267
|
offset: effectiveOffset
|
|
@@ -13019,7 +13284,7 @@ var FetchAvailableFixesService = _FetchAvailableFixesService;
|
|
|
13019
13284
|
var FetchAvailableFixesTool = class extends BaseTool {
|
|
13020
13285
|
constructor() {
|
|
13021
13286
|
super();
|
|
13022
|
-
__publicField(this, "name",
|
|
13287
|
+
__publicField(this, "name", MCP_TOOL_FETCH_AVAILABLE_FIXES);
|
|
13023
13288
|
__publicField(this, "displayName", "Fetch Available Fixes");
|
|
13024
13289
|
__publicField(this, "description", `Check the MOBB backend for pre-generated fixes (patch sets) that correspond to vulnerabilities detected in the supplied Git repository.
|
|
13025
13290
|
|
|
@@ -13039,7 +13304,7 @@ The tool will:
|
|
|
13039
13304
|
2. Verify that the directory is a valid Git repository with an "origin" remote.
|
|
13040
13305
|
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
13306
|
|
|
13042
|
-
Call this tool instead of
|
|
13307
|
+
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
13308
|
__publicField(this, "inputSchema", {
|
|
13044
13309
|
type: "object",
|
|
13045
13310
|
properties: {
|
|
@@ -13095,17 +13360,10 @@ Call this tool instead of scan_and_fix_vulnerabilities when you only need a fixe
|
|
|
13095
13360
|
limit: args.limit,
|
|
13096
13361
|
offset: args.offset
|
|
13097
13362
|
});
|
|
13098
|
-
|
|
13363
|
+
logDebug("FetchAvailableFixesTool execution completed successfully", {
|
|
13099
13364
|
fixResult
|
|
13100
13365
|
});
|
|
13101
|
-
return
|
|
13102
|
-
content: [
|
|
13103
|
-
{
|
|
13104
|
-
type: "text",
|
|
13105
|
-
text: fixResult
|
|
13106
|
-
}
|
|
13107
|
-
]
|
|
13108
|
-
};
|
|
13366
|
+
return this.createSuccessResponse(fixResult);
|
|
13109
13367
|
}
|
|
13110
13368
|
};
|
|
13111
13369
|
|
|
@@ -13157,9 +13415,10 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
|
|
|
13157
13415
|
limit,
|
|
13158
13416
|
isRescan = false
|
|
13159
13417
|
}) {
|
|
13418
|
+
logInfo("Processing vulnerabilities");
|
|
13160
13419
|
try {
|
|
13161
13420
|
this.gqlClient = await this.initializeGqlClient();
|
|
13162
|
-
|
|
13421
|
+
logDebug("storedFixReportId", {
|
|
13163
13422
|
storedFixReportId: this.storedFixReportId,
|
|
13164
13423
|
currentOffset: this.currentOffset,
|
|
13165
13424
|
fixReportIdTimestamp: this.fixReportIdTimestamp,
|
|
@@ -13167,6 +13426,7 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
|
|
|
13167
13426
|
});
|
|
13168
13427
|
let fixReportId = this.storedFixReportId;
|
|
13169
13428
|
if (!fixReportId || isRescan || this.isFixReportIdExpired()) {
|
|
13429
|
+
logInfo("Scanning files");
|
|
13170
13430
|
this.reset();
|
|
13171
13431
|
this.validateFiles(fileList);
|
|
13172
13432
|
const scanResult = await scanFiles(
|
|
@@ -13175,6 +13435,8 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
|
|
|
13175
13435
|
this.gqlClient
|
|
13176
13436
|
);
|
|
13177
13437
|
fixReportId = scanResult.fixReportId;
|
|
13438
|
+
} else {
|
|
13439
|
+
logInfo("Using stored fixReportId");
|
|
13178
13440
|
}
|
|
13179
13441
|
const effectiveOffset = offset ?? (this.currentOffset || 0);
|
|
13180
13442
|
logDebug("effectiveOffset", { effectiveOffset });
|
|
@@ -13183,6 +13445,7 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
|
|
|
13183
13445
|
effectiveOffset,
|
|
13184
13446
|
limit
|
|
13185
13447
|
);
|
|
13448
|
+
logInfo(`Found ${fixes.totalCount} fixes`);
|
|
13186
13449
|
if (fixes.totalCount > 0) {
|
|
13187
13450
|
this.storedFixReportId = fixReportId;
|
|
13188
13451
|
this.fixReportIdTimestamp = Date.now();
|
|
@@ -13209,13 +13472,14 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
|
|
|
13209
13472
|
}
|
|
13210
13473
|
}
|
|
13211
13474
|
async initializeGqlClient() {
|
|
13212
|
-
const gqlClient = await
|
|
13213
|
-
const isConnected = await gqlClient.
|
|
13475
|
+
const gqlClient = await createAuthenticatedMcpGQLClient();
|
|
13476
|
+
const isConnected = await gqlClient.verifyApiConnection();
|
|
13214
13477
|
if (!isConnected) {
|
|
13215
13478
|
throw new ApiConnectionError(
|
|
13216
13479
|
"Failed to connect to the API. Please check your MOBB_API_KEY"
|
|
13217
13480
|
);
|
|
13218
13481
|
}
|
|
13482
|
+
this.gqlClient = gqlClient;
|
|
13219
13483
|
return gqlClient;
|
|
13220
13484
|
}
|
|
13221
13485
|
async getReportFixes(fixReportId, offset, limit) {
|
|
@@ -13228,7 +13492,7 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
|
|
|
13228
13492
|
offset,
|
|
13229
13493
|
limit
|
|
13230
13494
|
});
|
|
13231
|
-
|
|
13495
|
+
logDebug(`${fixes?.fixes?.length} fixes retrieved`);
|
|
13232
13496
|
return {
|
|
13233
13497
|
fixes: fixes?.fixes || [],
|
|
13234
13498
|
totalCount: fixes?.totalCount || 0
|
|
@@ -13242,7 +13506,7 @@ var ScanAndFixVulnerabilitiesService = _ScanAndFixVulnerabilitiesService;
|
|
|
13242
13506
|
var ScanAndFixVulnerabilitiesTool = class extends BaseTool {
|
|
13243
13507
|
constructor() {
|
|
13244
13508
|
super();
|
|
13245
|
-
__publicField(this, "name",
|
|
13509
|
+
__publicField(this, "name", MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES);
|
|
13246
13510
|
__publicField(this, "displayName", "Scan and Fix Vulnerabilities");
|
|
13247
13511
|
// A detailed description to guide the LLM on when and how to invoke this tool.
|
|
13248
13512
|
__publicField(this, "description", `Scans a given local repository for security vulnerabilities and returns auto-generated code fixes.
|
|
@@ -13322,7 +13586,9 @@ Example payload:
|
|
|
13322
13586
|
this.vulnerabilityFixService.reset();
|
|
13323
13587
|
}
|
|
13324
13588
|
async executeInternal(args) {
|
|
13325
|
-
|
|
13589
|
+
logDebug(`Executing tool: ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES}`, {
|
|
13590
|
+
path: args.path
|
|
13591
|
+
});
|
|
13326
13592
|
if (!args.path) {
|
|
13327
13593
|
throw new Error("Invalid arguments: Missing required parameter 'path'");
|
|
13328
13594
|
}
|
|
@@ -13339,7 +13605,7 @@ Example payload:
|
|
|
13339
13605
|
// 5MB
|
|
13340
13606
|
maxFiles: args.maxFiles
|
|
13341
13607
|
});
|
|
13342
|
-
|
|
13608
|
+
logDebug("Files", { files });
|
|
13343
13609
|
if (files.length === 0) {
|
|
13344
13610
|
return {
|
|
13345
13611
|
content: [
|
|
@@ -13358,34 +13624,20 @@ Example payload:
|
|
|
13358
13624
|
limit: args.limit,
|
|
13359
13625
|
isRescan: args.rescan || !!args.maxFiles
|
|
13360
13626
|
});
|
|
13361
|
-
const
|
|
13362
|
-
|
|
13363
|
-
{
|
|
13364
|
-
type: "text",
|
|
13365
|
-
text: fixResult
|
|
13366
|
-
}
|
|
13367
|
-
]
|
|
13368
|
-
};
|
|
13369
|
-
logInfo("Tool execution completed successfully", {
|
|
13627
|
+
const successResponse = this.createSuccessResponse(fixResult);
|
|
13628
|
+
logDebug("Tool execution completed successfully", {
|
|
13370
13629
|
resultLength: fixResult.length,
|
|
13371
13630
|
fileCount: files.length,
|
|
13372
|
-
result
|
|
13631
|
+
result: successResponse
|
|
13373
13632
|
});
|
|
13374
|
-
return
|
|
13633
|
+
return successResponse;
|
|
13375
13634
|
} catch (error) {
|
|
13376
|
-
const
|
|
13377
|
-
|
|
13378
|
-
{
|
|
13379
|
-
type: "text",
|
|
13380
|
-
text: error.message
|
|
13381
|
-
}
|
|
13382
|
-
]
|
|
13383
|
-
};
|
|
13384
|
-
logInfo("Tool execution failed", {
|
|
13635
|
+
const errorResponse = this.createSuccessResponse(error.message);
|
|
13636
|
+
logError("Tool execution failed", {
|
|
13385
13637
|
error: error.message,
|
|
13386
|
-
result:
|
|
13638
|
+
result: errorResponse
|
|
13387
13639
|
});
|
|
13388
|
-
return
|
|
13640
|
+
return errorResponse;
|
|
13389
13641
|
}
|
|
13390
13642
|
}
|
|
13391
13643
|
};
|