mobbdev 1.0.90 → 1.0.91
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 +1426 -1055
- package/package.json +4 -3
package/dist/index.mjs
CHANGED
|
@@ -7,15 +7,15 @@ var __export = (target, all) => {
|
|
|
7
7
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
8
8
|
|
|
9
9
|
// src/index.ts
|
|
10
|
-
import
|
|
10
|
+
import Debug20 from "debug";
|
|
11
11
|
import { hideBin } from "yargs/helpers";
|
|
12
12
|
|
|
13
13
|
// src/args/commands/convert_to_sarif.ts
|
|
14
|
-
import
|
|
14
|
+
import fs5 from "fs";
|
|
15
15
|
|
|
16
16
|
// src/commands/convert_to_sarif.ts
|
|
17
|
-
import
|
|
18
|
-
import
|
|
17
|
+
import fs4 from "fs";
|
|
18
|
+
import path5 from "path";
|
|
19
19
|
|
|
20
20
|
// src/commands/fpr_stream_parser.ts
|
|
21
21
|
import fs from "fs";
|
|
@@ -4658,7 +4658,7 @@ async function getAdoSdk(params) {
|
|
|
4658
4658
|
const url = new URL(repoUrl);
|
|
4659
4659
|
const origin2 = url.origin.toLowerCase().endsWith(".visualstudio.com") ? DEFUALT_ADO_ORIGIN : url.origin.toLowerCase();
|
|
4660
4660
|
const params2 = `path=/&versionDescriptor[versionOptions]=0&versionDescriptor[versionType]=commit&versionDescriptor[version]=${branch}&resolveLfs=true&$format=zip&api-version=5.0&download=true`;
|
|
4661
|
-
const
|
|
4661
|
+
const path13 = [
|
|
4662
4662
|
prefixPath,
|
|
4663
4663
|
owner,
|
|
4664
4664
|
projectName,
|
|
@@ -4669,7 +4669,7 @@ async function getAdoSdk(params) {
|
|
|
4669
4669
|
"items",
|
|
4670
4670
|
"items"
|
|
4671
4671
|
].filter(Boolean).join("/");
|
|
4672
|
-
return new URL(`${
|
|
4672
|
+
return new URL(`${path13}?${params2}`, origin2).toString();
|
|
4673
4673
|
},
|
|
4674
4674
|
async getAdoBranchList({ repoUrl }) {
|
|
4675
4675
|
try {
|
|
@@ -4892,106 +4892,599 @@ async function getAdoRepoList({
|
|
|
4892
4892
|
// src/features/analysis/scm/ado/AdoSCMLib.ts
|
|
4893
4893
|
import { setTimeout as setTimeout2 } from "timers/promises";
|
|
4894
4894
|
|
|
4895
|
-
// src/features/analysis/scm/
|
|
4895
|
+
// src/features/analysis/scm/git/GitService.ts
|
|
4896
|
+
import * as path2 from "path";
|
|
4896
4897
|
import { simpleGit } from "simple-git";
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4898
|
+
|
|
4899
|
+
// src/features/analysis/scm/FileUtils.ts
|
|
4900
|
+
import fs2 from "fs";
|
|
4901
|
+
import { isBinary } from "istextorbinary";
|
|
4902
|
+
import path from "path";
|
|
4903
|
+
var EXCLUDED_FILE_PATTERNS = [
|
|
4904
|
+
// ... (copy the full array from FilePacking.ts)
|
|
4905
|
+
".json",
|
|
4906
|
+
".yaml",
|
|
4907
|
+
".yml",
|
|
4908
|
+
".toml",
|
|
4909
|
+
".ini",
|
|
4910
|
+
".conf",
|
|
4911
|
+
".config",
|
|
4912
|
+
".xml",
|
|
4913
|
+
".env",
|
|
4914
|
+
".md",
|
|
4915
|
+
".txt",
|
|
4916
|
+
".rst",
|
|
4917
|
+
".adoc",
|
|
4918
|
+
".lock",
|
|
4919
|
+
".png",
|
|
4920
|
+
".jpg",
|
|
4921
|
+
".jpeg",
|
|
4922
|
+
".gif",
|
|
4923
|
+
".svg",
|
|
4924
|
+
".ico",
|
|
4925
|
+
".webp",
|
|
4926
|
+
".bmp",
|
|
4927
|
+
".tiff",
|
|
4928
|
+
".ttf",
|
|
4929
|
+
".otf",
|
|
4930
|
+
".woff",
|
|
4931
|
+
".woff2",
|
|
4932
|
+
".eot",
|
|
4933
|
+
".zip",
|
|
4934
|
+
".tar",
|
|
4935
|
+
".gz",
|
|
4936
|
+
".rar",
|
|
4937
|
+
".7z",
|
|
4938
|
+
".log",
|
|
4939
|
+
".db",
|
|
4940
|
+
".sqlite",
|
|
4941
|
+
".sql",
|
|
4942
|
+
".pem",
|
|
4943
|
+
".crt",
|
|
4944
|
+
".key",
|
|
4945
|
+
".p12",
|
|
4946
|
+
".pfx",
|
|
4947
|
+
".editorconfig",
|
|
4948
|
+
".sublime-project",
|
|
4949
|
+
".sublime-workspace",
|
|
4950
|
+
".DS_Store",
|
|
4951
|
+
"Thumbs.db",
|
|
4952
|
+
".lcov",
|
|
4953
|
+
".exe",
|
|
4954
|
+
".dll",
|
|
4955
|
+
".so",
|
|
4956
|
+
".dylib",
|
|
4957
|
+
".class",
|
|
4958
|
+
".pyc",
|
|
4959
|
+
".pyo",
|
|
4960
|
+
".o",
|
|
4961
|
+
".obj",
|
|
4962
|
+
".min.js",
|
|
4963
|
+
".min.css",
|
|
4964
|
+
".min.html",
|
|
4965
|
+
".test.js",
|
|
4966
|
+
".test.ts",
|
|
4967
|
+
".test.jsx",
|
|
4968
|
+
".test.tsx",
|
|
4969
|
+
".spec.js",
|
|
4970
|
+
".spec.ts",
|
|
4971
|
+
".spec.jsx",
|
|
4972
|
+
".spec.tsx",
|
|
4973
|
+
".d.ts",
|
|
4974
|
+
".bundle.js",
|
|
4975
|
+
".chunk.js",
|
|
4976
|
+
"dockerfile",
|
|
4977
|
+
"jenkinsfile",
|
|
4978
|
+
"go.sum",
|
|
4979
|
+
".gitignore",
|
|
4980
|
+
".gitattributes",
|
|
4981
|
+
".gitmodules",
|
|
4982
|
+
".gitkeep",
|
|
4983
|
+
".keep",
|
|
4984
|
+
".hgignore",
|
|
4985
|
+
".nvmrc",
|
|
4986
|
+
".node-version",
|
|
4987
|
+
".npmrc",
|
|
4988
|
+
".yarnrc",
|
|
4989
|
+
".pnpmfile.cjs",
|
|
4990
|
+
".ruby-version",
|
|
4991
|
+
".python-version",
|
|
4992
|
+
".rvmrc",
|
|
4993
|
+
".rbenv-version",
|
|
4994
|
+
".gvmrc",
|
|
4995
|
+
"makefile",
|
|
4996
|
+
"rakefile",
|
|
4997
|
+
"gulpfile.js",
|
|
4998
|
+
"gruntfile.js",
|
|
4999
|
+
"webpack.config.js",
|
|
5000
|
+
"webpack.config.ts",
|
|
5001
|
+
"rollup.config.js",
|
|
5002
|
+
"vite.config.js",
|
|
5003
|
+
"vite.config.ts",
|
|
5004
|
+
"next.config.js",
|
|
5005
|
+
"nuxt.config.js",
|
|
5006
|
+
"tailwind.config.js",
|
|
5007
|
+
"postcss.config.js",
|
|
5008
|
+
".babelrc",
|
|
5009
|
+
".babelrc.js",
|
|
5010
|
+
".swcrc",
|
|
5011
|
+
".browserslistrc",
|
|
5012
|
+
"jest.config.js",
|
|
5013
|
+
"jest.config.ts",
|
|
5014
|
+
"vitest.config.js",
|
|
5015
|
+
"karma.conf.js",
|
|
5016
|
+
"protractor.conf.js",
|
|
5017
|
+
"cypress.config.js",
|
|
5018
|
+
"playwright.config.js",
|
|
5019
|
+
".nycrc",
|
|
5020
|
+
".c8rc",
|
|
5021
|
+
".eslintrc",
|
|
5022
|
+
".eslintrc.js",
|
|
5023
|
+
".prettierrc",
|
|
5024
|
+
".prettierrc.js",
|
|
5025
|
+
".stylelintrc",
|
|
5026
|
+
".stylelintrc.js",
|
|
5027
|
+
"pipfile",
|
|
5028
|
+
"gemfile",
|
|
5029
|
+
"go.mod",
|
|
5030
|
+
"project.clj",
|
|
5031
|
+
"setup.py",
|
|
5032
|
+
"setup.cfg",
|
|
5033
|
+
"manifest.in",
|
|
5034
|
+
".pythonrc",
|
|
5035
|
+
"readme",
|
|
5036
|
+
"changelog",
|
|
5037
|
+
"authors",
|
|
5038
|
+
"contributors",
|
|
5039
|
+
"license",
|
|
5040
|
+
"notice",
|
|
5041
|
+
"copyright",
|
|
5042
|
+
".htaccess"
|
|
5043
|
+
];
|
|
5044
|
+
var FileUtils = class {
|
|
5045
|
+
static isExcludedFileType(filepath) {
|
|
5046
|
+
const basename = path.basename(filepath).toLowerCase();
|
|
5047
|
+
if (basename === ".env" || basename.startsWith(".env.")) {
|
|
5048
|
+
return true;
|
|
5049
|
+
}
|
|
5050
|
+
if (EXCLUDED_FILE_PATTERNS.some((pattern) => basename.endsWith(pattern))) {
|
|
4902
5051
|
return true;
|
|
4903
5052
|
}
|
|
4904
|
-
return false;
|
|
4905
|
-
} catch (e) {
|
|
4906
5053
|
return false;
|
|
4907
5054
|
}
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
this.scmOrg = scmOrg;
|
|
4919
|
-
}
|
|
4920
|
-
async getUrlWithCredentials() {
|
|
4921
|
-
if (!this.url) {
|
|
4922
|
-
console.error("no url for getUrlWithCredentials()");
|
|
4923
|
-
throw new Error("no url");
|
|
5055
|
+
static shouldPackFile(filepath, maxFileSize = 1024 * 1024 * 5) {
|
|
5056
|
+
const absoluteFilepath = path.resolve(filepath);
|
|
5057
|
+
if (this.isExcludedFileType(filepath)) return false;
|
|
5058
|
+
if (!fs2.existsSync(absoluteFilepath)) return false;
|
|
5059
|
+
if (fs2.lstatSync(absoluteFilepath).size > maxFileSize) return false;
|
|
5060
|
+
let data;
|
|
5061
|
+
try {
|
|
5062
|
+
data = fs2.readFileSync(absoluteFilepath);
|
|
5063
|
+
} catch {
|
|
5064
|
+
return false;
|
|
4924
5065
|
}
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
5066
|
+
if (isBinary(null, data)) return false;
|
|
5067
|
+
return true;
|
|
5068
|
+
}
|
|
5069
|
+
static getAllFiles(dir, rootDir) {
|
|
5070
|
+
const root = rootDir || dir;
|
|
5071
|
+
const results = [];
|
|
5072
|
+
const relativeDepth = path.relative(root, dir).split(path.sep).length;
|
|
5073
|
+
if (relativeDepth > 20) {
|
|
5074
|
+
return [];
|
|
4929
5075
|
}
|
|
4930
|
-
if (
|
|
4931
|
-
|
|
4932
|
-
return `${protocol}//${accessToken}@${host}${pathname}`;
|
|
5076
|
+
if (results.length > 1e5) {
|
|
5077
|
+
return [];
|
|
4933
5078
|
}
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
username,
|
|
4939
|
-
password: accessToken
|
|
4940
|
-
});
|
|
4941
|
-
}
|
|
4942
|
-
getAccessToken() {
|
|
4943
|
-
return this.accessToken || "";
|
|
4944
|
-
}
|
|
4945
|
-
getUrl() {
|
|
4946
|
-
return this.url;
|
|
4947
|
-
}
|
|
4948
|
-
getName() {
|
|
4949
|
-
if (!this.url) {
|
|
4950
|
-
return "";
|
|
5079
|
+
try {
|
|
5080
|
+
fs2.accessSync(dir, fs2.constants.R_OK);
|
|
5081
|
+
} catch {
|
|
5082
|
+
return [];
|
|
4951
5083
|
}
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
5084
|
+
const items = fs2.readdirSync(dir);
|
|
5085
|
+
for (const item of items) {
|
|
5086
|
+
const fullPath = path.join(dir, item);
|
|
5087
|
+
try {
|
|
5088
|
+
fs2.accessSync(fullPath, fs2.constants.R_OK);
|
|
5089
|
+
} catch {
|
|
5090
|
+
continue;
|
|
5091
|
+
}
|
|
5092
|
+
const stat = fs2.statSync(fullPath);
|
|
5093
|
+
if (stat.isDirectory()) {
|
|
5094
|
+
results.push(...this.getAllFiles(fullPath, root));
|
|
5095
|
+
} else {
|
|
5096
|
+
results.push({
|
|
5097
|
+
name: item,
|
|
5098
|
+
fullPath,
|
|
5099
|
+
relativePath: path.relative(root, fullPath),
|
|
5100
|
+
time: stat.mtime.getTime(),
|
|
5101
|
+
isFile: true
|
|
5102
|
+
});
|
|
5103
|
+
}
|
|
4958
5104
|
}
|
|
5105
|
+
return results;
|
|
4959
5106
|
}
|
|
4960
|
-
static
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
this._validateAccessToken();
|
|
4965
|
-
this._validateUrl();
|
|
4966
|
-
}
|
|
4967
|
-
_validateUrl() {
|
|
4968
|
-
if (!this.url) {
|
|
4969
|
-
console.error("no url");
|
|
4970
|
-
throw new InvalidRepoUrlError("no url");
|
|
4971
|
-
}
|
|
5107
|
+
static getLastChangedFiles(dir, maxFileSize = 1024 * 1024 * 5, count = 10) {
|
|
5108
|
+
if (!fs2.existsSync(dir) || !fs2.lstatSync(dir).isDirectory()) return [];
|
|
5109
|
+
const files = this.getAllFiles(dir);
|
|
5110
|
+
return files.filter((file) => this.shouldPackFile(file.fullPath, maxFileSize)).sort((a, b) => b.time - a.time).slice(0, count).map((file) => file.relativePath);
|
|
4972
5111
|
}
|
|
4973
5112
|
};
|
|
4974
5113
|
|
|
4975
|
-
// src/features/analysis/scm/
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
}
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
super(url, accessToken, scmOrg);
|
|
4988
|
-
__publicField(this, "_adoSdkPromise");
|
|
4989
|
-
this._adoSdkPromise = initAdoSdk({ accessToken, url, scmOrg });
|
|
5114
|
+
// src/features/analysis/scm/git/GitService.ts
|
|
5115
|
+
var GitService = class {
|
|
5116
|
+
constructor(repositoryPath, log2) {
|
|
5117
|
+
__publicField(this, "git");
|
|
5118
|
+
__publicField(this, "repositoryPath");
|
|
5119
|
+
__publicField(this, "log");
|
|
5120
|
+
const noopLog = (_message, _level, _data) => {
|
|
5121
|
+
};
|
|
5122
|
+
this.log = log2 || noopLog;
|
|
5123
|
+
this.git = simpleGit(repositoryPath, { binary: "git" });
|
|
5124
|
+
this.repositoryPath = repositoryPath;
|
|
5125
|
+
this.log("Git service initialized", "debug", { repositoryPath });
|
|
4990
5126
|
}
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
5127
|
+
/**
|
|
5128
|
+
* Validates that the path is a valid git repository
|
|
5129
|
+
*/
|
|
5130
|
+
async validateRepository() {
|
|
5131
|
+
this.log("Validating git repository", "debug");
|
|
5132
|
+
try {
|
|
5133
|
+
const isRepo = await this.git.checkIsRepo();
|
|
5134
|
+
if (!isRepo) {
|
|
5135
|
+
const error = "Path is not a valid git repository";
|
|
5136
|
+
this.log(error, "error");
|
|
5137
|
+
return { isValid: false, error };
|
|
5138
|
+
}
|
|
5139
|
+
this.log("Git repository validation successful", "debug");
|
|
5140
|
+
return { isValid: true };
|
|
5141
|
+
} catch (error) {
|
|
5142
|
+
const errorMessage = `Failed to verify git repository: ${error.message}`;
|
|
5143
|
+
this.log(errorMessage, "error", { error });
|
|
5144
|
+
return { isValid: false, error: errorMessage };
|
|
5145
|
+
}
|
|
5146
|
+
}
|
|
5147
|
+
/**
|
|
5148
|
+
* Gets the current git status and returns changed files
|
|
5149
|
+
*/
|
|
5150
|
+
async getChangedFiles() {
|
|
5151
|
+
this.log("Getting git status", "debug");
|
|
5152
|
+
try {
|
|
5153
|
+
const status = await this.git.status();
|
|
5154
|
+
const gitRoot = await this.git.revparse(["--show-toplevel"]);
|
|
5155
|
+
const relativePathFromGitRoot = path2.relative(
|
|
5156
|
+
gitRoot,
|
|
5157
|
+
this.repositoryPath
|
|
5158
|
+
);
|
|
5159
|
+
const files = status.files.map((file) => {
|
|
5160
|
+
const gitRelativePath = file.path;
|
|
5161
|
+
if (relativePathFromGitRoot === "") {
|
|
5162
|
+
return gitRelativePath;
|
|
5163
|
+
}
|
|
5164
|
+
if (gitRelativePath.startsWith(relativePathFromGitRoot + "/")) {
|
|
5165
|
+
return gitRelativePath.substring(relativePathFromGitRoot.length + 1);
|
|
5166
|
+
}
|
|
5167
|
+
return path2.relative(
|
|
5168
|
+
this.repositoryPath,
|
|
5169
|
+
path2.join(gitRoot, gitRelativePath)
|
|
5170
|
+
);
|
|
5171
|
+
});
|
|
5172
|
+
this.log("Git status retrieved", "info", {
|
|
5173
|
+
fileCount: files.length,
|
|
5174
|
+
files: files.slice(0, 10),
|
|
5175
|
+
// Log first 10 files to avoid spam
|
|
5176
|
+
gitRoot,
|
|
5177
|
+
workingDir: this.repositoryPath,
|
|
5178
|
+
relativePathFromGitRoot
|
|
5179
|
+
});
|
|
5180
|
+
return { files, status };
|
|
5181
|
+
} catch (error) {
|
|
5182
|
+
const errorMessage = `Failed to get git status: ${error.message}`;
|
|
5183
|
+
this.log(errorMessage, "error", { error });
|
|
5184
|
+
throw new Error(errorMessage);
|
|
5185
|
+
}
|
|
5186
|
+
}
|
|
5187
|
+
/**
|
|
5188
|
+
* Gets git repository information including remote URL, current commit hash, and branch name
|
|
5189
|
+
*/
|
|
5190
|
+
async getGitInfo() {
|
|
5191
|
+
this.log("Getting git repository information", "debug");
|
|
5192
|
+
try {
|
|
5193
|
+
const [repoUrl, hash, reference] = await Promise.all([
|
|
5194
|
+
this.git.getConfig("remote.origin.url"),
|
|
5195
|
+
this.git.revparse(["HEAD"]),
|
|
5196
|
+
this.git.revparse(["--abbrev-ref", "HEAD"])
|
|
5197
|
+
]);
|
|
5198
|
+
let normalizedRepoUrl = repoUrl.value || "";
|
|
5199
|
+
if (normalizedRepoUrl.endsWith(".git")) {
|
|
5200
|
+
normalizedRepoUrl = normalizedRepoUrl.slice(0, -".git".length);
|
|
5201
|
+
}
|
|
5202
|
+
if (normalizedRepoUrl.startsWith("git@github.com:")) {
|
|
5203
|
+
normalizedRepoUrl = normalizedRepoUrl.replace(
|
|
5204
|
+
"git@github.com:",
|
|
5205
|
+
"https://github.com/"
|
|
5206
|
+
);
|
|
5207
|
+
}
|
|
5208
|
+
this.log("Git repository information retrieved", "debug", {
|
|
5209
|
+
repoUrl: normalizedRepoUrl,
|
|
5210
|
+
hash,
|
|
5211
|
+
reference
|
|
5212
|
+
});
|
|
5213
|
+
return {
|
|
5214
|
+
repoUrl: normalizedRepoUrl,
|
|
5215
|
+
hash,
|
|
5216
|
+
reference
|
|
5217
|
+
};
|
|
5218
|
+
} catch (error) {
|
|
5219
|
+
const errorMessage = `Failed to get git repository information: ${error.message}`;
|
|
5220
|
+
this.log(errorMessage, "error", { error });
|
|
5221
|
+
throw new Error(errorMessage);
|
|
5222
|
+
}
|
|
5223
|
+
}
|
|
5224
|
+
/**
|
|
5225
|
+
* Validates if a branch name is valid according to git's rules
|
|
5226
|
+
*/
|
|
5227
|
+
async isValidBranchName(branchName) {
|
|
5228
|
+
this.log("Validating branch name", "debug", { branchName });
|
|
5229
|
+
try {
|
|
5230
|
+
const result = await this.git.raw([
|
|
5231
|
+
"check-ref-format",
|
|
5232
|
+
"--branch",
|
|
5233
|
+
branchName
|
|
5234
|
+
]);
|
|
5235
|
+
const isValid = Boolean(result);
|
|
5236
|
+
this.log("Branch name validation result", "debug", {
|
|
5237
|
+
branchName,
|
|
5238
|
+
isValid
|
|
5239
|
+
});
|
|
5240
|
+
return isValid;
|
|
5241
|
+
} catch (error) {
|
|
5242
|
+
this.log("Branch name validation failed", "debug", { branchName, error });
|
|
5243
|
+
return false;
|
|
5244
|
+
}
|
|
5245
|
+
}
|
|
5246
|
+
/**
|
|
5247
|
+
* Gets the current branch name
|
|
5248
|
+
*/
|
|
5249
|
+
async getCurrentBranch() {
|
|
5250
|
+
this.log("Getting current branch name", "debug");
|
|
5251
|
+
try {
|
|
5252
|
+
const branch = await this.git.revparse(["--abbrev-ref", "HEAD"]);
|
|
5253
|
+
this.log("Current branch retrieved", "debug", { branch });
|
|
5254
|
+
return branch;
|
|
5255
|
+
} catch (error) {
|
|
5256
|
+
const errorMessage = `Failed to get current branch: ${error.message}`;
|
|
5257
|
+
this.log(errorMessage, "error", { error });
|
|
5258
|
+
throw new Error(errorMessage);
|
|
5259
|
+
}
|
|
5260
|
+
}
|
|
5261
|
+
/**
|
|
5262
|
+
* Gets the current commit hash
|
|
5263
|
+
*/
|
|
5264
|
+
async getCurrentCommitHash() {
|
|
5265
|
+
this.log("Getting current commit hash", "debug");
|
|
5266
|
+
try {
|
|
5267
|
+
const hash = await this.git.revparse(["HEAD"]);
|
|
5268
|
+
this.log("Current commit hash retrieved", "debug", { hash });
|
|
5269
|
+
return hash;
|
|
5270
|
+
} catch (error) {
|
|
5271
|
+
const errorMessage = `Failed to get current commit hash: ${error.message}`;
|
|
5272
|
+
this.log(errorMessage, "error", { error });
|
|
5273
|
+
throw new Error(errorMessage);
|
|
5274
|
+
}
|
|
5275
|
+
}
|
|
5276
|
+
/**
|
|
5277
|
+
* Gets the remote repository URL
|
|
5278
|
+
*/
|
|
5279
|
+
async getRemoteUrl() {
|
|
5280
|
+
this.log("Getting remote repository URL", "debug");
|
|
5281
|
+
try {
|
|
5282
|
+
const remoteUrl = await this.git.getConfig("remote.origin.url");
|
|
5283
|
+
const url = remoteUrl.value || "";
|
|
5284
|
+
let normalizedUrl = url;
|
|
5285
|
+
if (normalizedUrl.endsWith(".git")) {
|
|
5286
|
+
normalizedUrl = normalizedUrl.slice(0, -".git".length);
|
|
5287
|
+
}
|
|
5288
|
+
if (normalizedUrl.startsWith("git@github.com:")) {
|
|
5289
|
+
normalizedUrl = normalizedUrl.replace(
|
|
5290
|
+
"git@github.com:",
|
|
5291
|
+
"https://github.com/"
|
|
5292
|
+
);
|
|
5293
|
+
}
|
|
5294
|
+
this.log("Remote repository URL retrieved", "debug", {
|
|
5295
|
+
url: normalizedUrl
|
|
5296
|
+
});
|
|
5297
|
+
return normalizedUrl;
|
|
5298
|
+
} catch (error) {
|
|
5299
|
+
const errorMessage = `Failed to get remote repository URL: ${error.message}`;
|
|
5300
|
+
this.log(errorMessage, "error", { error });
|
|
5301
|
+
throw new Error(errorMessage);
|
|
5302
|
+
}
|
|
5303
|
+
}
|
|
5304
|
+
/**
|
|
5305
|
+
* Gets the 10 most recently changed files based on commit history
|
|
5306
|
+
*/
|
|
5307
|
+
async getRecentlyChangedFiles() {
|
|
5308
|
+
this.log(
|
|
5309
|
+
"Getting the 10 most recently changed files from commit history",
|
|
5310
|
+
"debug"
|
|
5311
|
+
);
|
|
5312
|
+
try {
|
|
5313
|
+
const gitRoot = await this.git.revparse(["--show-toplevel"]);
|
|
5314
|
+
const relativePathFromGitRoot = path2.relative(
|
|
5315
|
+
gitRoot,
|
|
5316
|
+
this.repositoryPath
|
|
5317
|
+
);
|
|
5318
|
+
const fileSet = /* @__PURE__ */ new Set();
|
|
5319
|
+
const files = [];
|
|
5320
|
+
let commitsProcessed = 0;
|
|
5321
|
+
const logResult = await this.git.log({
|
|
5322
|
+
maxCount: 100,
|
|
5323
|
+
// Get last 100 commits - should be enough to find 10 unique files
|
|
5324
|
+
format: {
|
|
5325
|
+
hash: "%H",
|
|
5326
|
+
date: "%ai",
|
|
5327
|
+
message: "%s",
|
|
5328
|
+
//the field name author_name can't follow the naming convention as we are using the git log command
|
|
5329
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
5330
|
+
author_name: "%an"
|
|
5331
|
+
}
|
|
5332
|
+
});
|
|
5333
|
+
for (const commit of logResult.all) {
|
|
5334
|
+
if (files.length >= 10) {
|
|
5335
|
+
break;
|
|
5336
|
+
}
|
|
5337
|
+
commitsProcessed++;
|
|
5338
|
+
try {
|
|
5339
|
+
const filesOutput = await this.git.show([
|
|
5340
|
+
"--name-only",
|
|
5341
|
+
"--pretty=format:",
|
|
5342
|
+
commit.hash
|
|
5343
|
+
]);
|
|
5344
|
+
const commitFiles = filesOutput.split("\n").filter((file) => file.trim() !== "");
|
|
5345
|
+
for (const file of commitFiles) {
|
|
5346
|
+
if (files.length >= 10) {
|
|
5347
|
+
break;
|
|
5348
|
+
}
|
|
5349
|
+
const gitRelativePath = file.trim();
|
|
5350
|
+
let adjustedPath;
|
|
5351
|
+
if (relativePathFromGitRoot === "") {
|
|
5352
|
+
adjustedPath = gitRelativePath;
|
|
5353
|
+
} else if (gitRelativePath.startsWith(relativePathFromGitRoot + "/")) {
|
|
5354
|
+
adjustedPath = gitRelativePath.substring(
|
|
5355
|
+
relativePathFromGitRoot.length + 1
|
|
5356
|
+
);
|
|
5357
|
+
} else {
|
|
5358
|
+
adjustedPath = path2.relative(
|
|
5359
|
+
this.repositoryPath,
|
|
5360
|
+
path2.join(gitRoot, gitRelativePath)
|
|
5361
|
+
);
|
|
5362
|
+
}
|
|
5363
|
+
this.log(`Considering file: ${adjustedPath}`, "debug");
|
|
5364
|
+
if (!fileSet.has(adjustedPath) && FileUtils.shouldPackFile(path2.join(gitRoot, gitRelativePath))) {
|
|
5365
|
+
fileSet.add(adjustedPath);
|
|
5366
|
+
files.push(adjustedPath);
|
|
5367
|
+
}
|
|
5368
|
+
}
|
|
5369
|
+
} catch (showError) {
|
|
5370
|
+
this.log(`Could not get files for commit ${commit.hash}`, "debug", {
|
|
5371
|
+
error: showError
|
|
5372
|
+
});
|
|
5373
|
+
}
|
|
5374
|
+
}
|
|
5375
|
+
this.log("Recently changed files retrieved", "info", {
|
|
5376
|
+
fileCount: files.length,
|
|
5377
|
+
commitsProcessed,
|
|
5378
|
+
totalCommitsAvailable: logResult.all.length,
|
|
5379
|
+
files: files.slice(0, 10),
|
|
5380
|
+
// Log the files (should be all of them since we limit to 10)
|
|
5381
|
+
gitRoot,
|
|
5382
|
+
workingDir: this.repositoryPath,
|
|
5383
|
+
relativePathFromGitRoot
|
|
5384
|
+
});
|
|
5385
|
+
return {
|
|
5386
|
+
files,
|
|
5387
|
+
commitCount: commitsProcessed
|
|
5388
|
+
};
|
|
5389
|
+
} catch (error) {
|
|
5390
|
+
const errorMessage = `Failed to get recently changed files: ${error.message}`;
|
|
5391
|
+
this.log(errorMessage, "error", { error });
|
|
5392
|
+
throw new Error(errorMessage);
|
|
5393
|
+
}
|
|
5394
|
+
}
|
|
5395
|
+
};
|
|
5396
|
+
|
|
5397
|
+
// src/features/analysis/scm/scmSubmit/index.ts
|
|
5398
|
+
var isValidBranchName = async (branchName) => {
|
|
5399
|
+
const gitService = new GitService(process.cwd());
|
|
5400
|
+
return gitService.isValidBranchName(branchName);
|
|
5401
|
+
};
|
|
5402
|
+
|
|
5403
|
+
// src/features/analysis/scm/scm.ts
|
|
5404
|
+
var SCMLib = class {
|
|
5405
|
+
constructor(url, accessToken, scmOrg) {
|
|
5406
|
+
__publicField(this, "url");
|
|
5407
|
+
__publicField(this, "accessToken");
|
|
5408
|
+
__publicField(this, "scmOrg");
|
|
5409
|
+
this.accessToken = accessToken;
|
|
5410
|
+
this.url = url;
|
|
5411
|
+
this.scmOrg = scmOrg;
|
|
5412
|
+
}
|
|
5413
|
+
async getUrlWithCredentials() {
|
|
5414
|
+
if (!this.url) {
|
|
5415
|
+
console.error("no url for getUrlWithCredentials()");
|
|
5416
|
+
throw new Error("no url");
|
|
5417
|
+
}
|
|
5418
|
+
const trimmedUrl = this.url.trim().replace(/\/$/, "");
|
|
5419
|
+
const accessToken = this.getAccessToken();
|
|
5420
|
+
if (!accessToken) {
|
|
5421
|
+
return trimmedUrl;
|
|
5422
|
+
}
|
|
5423
|
+
if (this.scmLibType === "ADO" /* ADO */) {
|
|
5424
|
+
const { host, protocol, pathname } = new URL(trimmedUrl);
|
|
5425
|
+
return `${protocol}//${accessToken}@${host}${pathname}`;
|
|
5426
|
+
}
|
|
5427
|
+
const finalUrl = this.scmLibType === "GITLAB" /* GITLAB */ ? `${trimmedUrl}.git` : trimmedUrl;
|
|
5428
|
+
const username = await this._getUsernameForAuthUrl();
|
|
5429
|
+
return buildAuthorizedRepoUrl({
|
|
5430
|
+
url: finalUrl,
|
|
5431
|
+
username,
|
|
5432
|
+
password: accessToken
|
|
5433
|
+
});
|
|
5434
|
+
}
|
|
5435
|
+
getAccessToken() {
|
|
5436
|
+
return this.accessToken || "";
|
|
5437
|
+
}
|
|
5438
|
+
getUrl() {
|
|
5439
|
+
return this.url;
|
|
5440
|
+
}
|
|
5441
|
+
getName() {
|
|
5442
|
+
if (!this.url) {
|
|
5443
|
+
return "";
|
|
5444
|
+
}
|
|
5445
|
+
return this.url.split("/").at(-1) || "";
|
|
5446
|
+
}
|
|
5447
|
+
_validateAccessToken() {
|
|
5448
|
+
if (!this.accessToken) {
|
|
5449
|
+
console.error("no access token");
|
|
5450
|
+
throw new Error("no access token");
|
|
5451
|
+
}
|
|
5452
|
+
}
|
|
5453
|
+
static async getIsValidBranchName(branchName) {
|
|
5454
|
+
return isValidBranchName(branchName);
|
|
5455
|
+
}
|
|
5456
|
+
_validateAccessTokenAndUrl() {
|
|
5457
|
+
this._validateAccessToken();
|
|
5458
|
+
this._validateUrl();
|
|
5459
|
+
}
|
|
5460
|
+
_validateUrl() {
|
|
5461
|
+
if (!this.url) {
|
|
5462
|
+
console.error("no url");
|
|
5463
|
+
throw new InvalidRepoUrlError("no url");
|
|
5464
|
+
}
|
|
5465
|
+
}
|
|
5466
|
+
};
|
|
5467
|
+
|
|
5468
|
+
// src/features/analysis/scm/ado/AdoSCMLib.ts
|
|
5469
|
+
async function initAdoSdk(params) {
|
|
5470
|
+
const { url, accessToken, scmOrg } = params;
|
|
5471
|
+
const adoClientParams = await getAdoClientParams({
|
|
5472
|
+
tokenOrg: scmOrg,
|
|
5473
|
+
accessToken,
|
|
5474
|
+
url
|
|
5475
|
+
});
|
|
5476
|
+
return getAdoSdk(adoClientParams);
|
|
5477
|
+
}
|
|
5478
|
+
var AdoSCMLib = class extends SCMLib {
|
|
5479
|
+
constructor(url, accessToken, scmOrg) {
|
|
5480
|
+
super(url, accessToken, scmOrg);
|
|
5481
|
+
__publicField(this, "_adoSdkPromise");
|
|
5482
|
+
this._adoSdkPromise = initAdoSdk({ accessToken, url, scmOrg });
|
|
5483
|
+
}
|
|
5484
|
+
async getAdoSdk() {
|
|
5485
|
+
if (!this._adoSdkPromise) {
|
|
5486
|
+
console.error("ado sdk was not initialized");
|
|
5487
|
+
throw new InvalidAccessTokenError("ado sdk was not initialized");
|
|
4995
5488
|
}
|
|
4996
5489
|
return this._adoSdkPromise;
|
|
4997
5490
|
}
|
|
@@ -6127,14 +6620,14 @@ function getGithubSdk(params = {}) {
|
|
|
6127
6620
|
};
|
|
6128
6621
|
},
|
|
6129
6622
|
async getGithubBlameRanges(params2) {
|
|
6130
|
-
const { ref, gitHubUrl, path:
|
|
6623
|
+
const { ref, gitHubUrl, path: path13 } = params2;
|
|
6131
6624
|
const { owner, repo } = parseGithubOwnerAndRepo(gitHubUrl);
|
|
6132
6625
|
const res = await octokit.graphql(
|
|
6133
6626
|
GET_BLAME_DOCUMENT,
|
|
6134
6627
|
{
|
|
6135
6628
|
owner,
|
|
6136
6629
|
repo,
|
|
6137
|
-
path:
|
|
6630
|
+
path: path13,
|
|
6138
6631
|
ref
|
|
6139
6632
|
}
|
|
6140
6633
|
);
|
|
@@ -6440,11 +6933,11 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
6440
6933
|
markdownComment: comment
|
|
6441
6934
|
});
|
|
6442
6935
|
}
|
|
6443
|
-
async getRepoBlameRanges(ref,
|
|
6936
|
+
async getRepoBlameRanges(ref, path13) {
|
|
6444
6937
|
this._validateUrl();
|
|
6445
6938
|
return await this.githubSdk.getGithubBlameRanges({
|
|
6446
6939
|
ref,
|
|
6447
|
-
path:
|
|
6940
|
+
path: path13,
|
|
6448
6941
|
gitHubUrl: this.url
|
|
6449
6942
|
});
|
|
6450
6943
|
}
|
|
@@ -6846,13 +7339,13 @@ function parseGitlabOwnerAndRepo(gitlabUrl) {
|
|
|
6846
7339
|
const { organization, repoName, projectPath } = parsingResult;
|
|
6847
7340
|
return { owner: organization, repo: repoName, projectPath };
|
|
6848
7341
|
}
|
|
6849
|
-
async function getGitlabBlameRanges({ ref, gitlabUrl, path:
|
|
7342
|
+
async function getGitlabBlameRanges({ ref, gitlabUrl, path: path13 }, options) {
|
|
6850
7343
|
const { projectPath } = parseGitlabOwnerAndRepo(gitlabUrl);
|
|
6851
7344
|
const api2 = getGitBeaker({
|
|
6852
7345
|
url: gitlabUrl,
|
|
6853
7346
|
gitlabAuthToken: options?.gitlabAuthToken
|
|
6854
7347
|
});
|
|
6855
|
-
const resp = await api2.RepositoryFiles.allFileBlames(projectPath,
|
|
7348
|
+
const resp = await api2.RepositoryFiles.allFileBlames(projectPath, path13, ref);
|
|
6856
7349
|
let lineNumber = 1;
|
|
6857
7350
|
return resp.filter((range) => range.lines).map((range) => {
|
|
6858
7351
|
const oldLineNumber = lineNumber;
|
|
@@ -7028,10 +7521,10 @@ var GitlabSCMLib = class extends SCMLib {
|
|
|
7028
7521
|
markdownComment: comment
|
|
7029
7522
|
});
|
|
7030
7523
|
}
|
|
7031
|
-
async getRepoBlameRanges(ref,
|
|
7524
|
+
async getRepoBlameRanges(ref, path13) {
|
|
7032
7525
|
this._validateUrl();
|
|
7033
7526
|
return await getGitlabBlameRanges(
|
|
7034
|
-
{ ref, path:
|
|
7527
|
+
{ ref, path: path13, gitlabUrl: this.url },
|
|
7035
7528
|
{
|
|
7036
7529
|
url: this.url,
|
|
7037
7530
|
gitlabAuthToken: this.accessToken
|
|
@@ -7234,13 +7727,13 @@ __export(utils_exports, {
|
|
|
7234
7727
|
});
|
|
7235
7728
|
|
|
7236
7729
|
// src/utils/dirname.ts
|
|
7237
|
-
import
|
|
7730
|
+
import path3 from "path";
|
|
7238
7731
|
import { fileURLToPath } from "url";
|
|
7239
7732
|
function getDirName() {
|
|
7240
|
-
return
|
|
7733
|
+
return path3.dirname(fileURLToPath(import.meta.url));
|
|
7241
7734
|
}
|
|
7242
7735
|
function getTopLevelDirName(fullPath) {
|
|
7243
|
-
return
|
|
7736
|
+
return path3.parse(fullPath).name;
|
|
7244
7737
|
}
|
|
7245
7738
|
|
|
7246
7739
|
// src/utils/keypress.ts
|
|
@@ -7303,15 +7796,15 @@ function Spinner({ ci = false } = {}) {
|
|
|
7303
7796
|
}
|
|
7304
7797
|
|
|
7305
7798
|
// src/utils/check_node_version.ts
|
|
7306
|
-
import
|
|
7307
|
-
import
|
|
7799
|
+
import fs3 from "fs";
|
|
7800
|
+
import path4 from "path";
|
|
7308
7801
|
import semver from "semver";
|
|
7309
7802
|
function getPackageJson() {
|
|
7310
|
-
let manifestPath =
|
|
7311
|
-
if (!
|
|
7312
|
-
manifestPath =
|
|
7803
|
+
let manifestPath = path4.join(getDirName(), "../package.json");
|
|
7804
|
+
if (!fs3.existsSync(manifestPath)) {
|
|
7805
|
+
manifestPath = path4.join(getDirName(), "../../package.json");
|
|
7313
7806
|
}
|
|
7314
|
-
return JSON.parse(
|
|
7807
|
+
return JSON.parse(fs3.readFileSync(manifestPath, "utf8"));
|
|
7315
7808
|
}
|
|
7316
7809
|
var packageJson = getPackageJson();
|
|
7317
7810
|
if (!semver.satisfies(process.version, packageJson.engines.node)) {
|
|
@@ -7354,12 +7847,12 @@ async function convertFprToSarif(inputFilePath, outputFilePath, codePathPatterns
|
|
|
7354
7847
|
unsafeCleanup: true
|
|
7355
7848
|
});
|
|
7356
7849
|
try {
|
|
7357
|
-
const auditFvdlPath =
|
|
7850
|
+
const auditFvdlPath = path5.join(tmpObj.name, "audit.fvdl");
|
|
7358
7851
|
await zipIn.extract("audit.fvdl", auditFvdlPath);
|
|
7359
7852
|
const auditFvdlSaxParser = initSaxParser(auditFvdlPath);
|
|
7360
7853
|
const vulnerabilityParser = new VulnerabilityParser(
|
|
7361
7854
|
auditFvdlSaxParser.parser,
|
|
7362
|
-
|
|
7855
|
+
path5.join(tmpObj.name, "vulns.json")
|
|
7363
7856
|
);
|
|
7364
7857
|
const unifiedNodePoolParser = new UnifiedNodePoolParser(
|
|
7365
7858
|
auditFvdlSaxParser.parser
|
|
@@ -7370,14 +7863,14 @@ async function convertFprToSarif(inputFilePath, outputFilePath, codePathPatterns
|
|
|
7370
7863
|
let auditMetadataParser = null;
|
|
7371
7864
|
await auditFvdlSaxParser.parse();
|
|
7372
7865
|
if ("audit.xml" in zipInEntries) {
|
|
7373
|
-
const auditXmlPath =
|
|
7866
|
+
const auditXmlPath = path5.join(tmpObj.name, "audit.xml");
|
|
7374
7867
|
await zipIn.extract("audit.xml", auditXmlPath);
|
|
7375
7868
|
const auditXmlSaxParser = initSaxParser(auditXmlPath);
|
|
7376
7869
|
auditMetadataParser = new AuditMetadataParser(auditXmlSaxParser.parser);
|
|
7377
7870
|
await auditXmlSaxParser.parse();
|
|
7378
7871
|
}
|
|
7379
7872
|
await zipIn.close();
|
|
7380
|
-
const writer =
|
|
7873
|
+
const writer = fs4.createWriteStream(outputFilePath);
|
|
7381
7874
|
writer.write(`{
|
|
7382
7875
|
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
|
|
7383
7876
|
"version": "2.1.0",
|
|
@@ -7482,15 +7975,15 @@ function fortifyNodesToSarifLocations(nodes, unifiedNodePoolParser) {
|
|
|
7482
7975
|
import chalk2 from "chalk";
|
|
7483
7976
|
|
|
7484
7977
|
// src/constants.ts
|
|
7485
|
-
import
|
|
7978
|
+
import path6 from "path";
|
|
7486
7979
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7487
7980
|
import chalk from "chalk";
|
|
7488
7981
|
import Debug4 from "debug";
|
|
7489
7982
|
import * as dotenv from "dotenv";
|
|
7490
7983
|
import { z as z24 } from "zod";
|
|
7491
7984
|
var debug4 = Debug4("mobbdev:constants");
|
|
7492
|
-
var __dirname =
|
|
7493
|
-
dotenv.config({ path:
|
|
7985
|
+
var __dirname = path6.dirname(fileURLToPath2(import.meta.url));
|
|
7986
|
+
dotenv.config({ path: path6.join(__dirname, "../.env") });
|
|
7494
7987
|
var scmFriendlyText = {
|
|
7495
7988
|
["Ado" /* Ado */]: "Azure DevOps",
|
|
7496
7989
|
["Bitbucket" /* Bitbucket */]: "Bitbucket",
|
|
@@ -7711,7 +8204,7 @@ function convertToSarifBuilder(args) {
|
|
|
7711
8204
|
).help().demandOption(["input-file-path", "input-file-format", "output-file-path"]);
|
|
7712
8205
|
}
|
|
7713
8206
|
async function validateConvertToSarifOptions(args) {
|
|
7714
|
-
if (!
|
|
8207
|
+
if (!fs5.existsSync(args.inputFilePath)) {
|
|
7715
8208
|
throw new CliError(
|
|
7716
8209
|
"\nError: --input-file-path flag should point to an existing file"
|
|
7717
8210
|
);
|
|
@@ -7737,16 +8230,16 @@ import chalk10 from "chalk";
|
|
|
7737
8230
|
import yargs from "yargs/yargs";
|
|
7738
8231
|
|
|
7739
8232
|
// src/args/commands/analyze.ts
|
|
7740
|
-
import
|
|
8233
|
+
import fs8 from "fs";
|
|
7741
8234
|
|
|
7742
8235
|
// src/commands/index.ts
|
|
7743
8236
|
import crypto from "crypto";
|
|
7744
8237
|
import os from "os";
|
|
7745
8238
|
|
|
7746
8239
|
// src/features/analysis/index.ts
|
|
7747
|
-
import
|
|
8240
|
+
import fs7 from "fs";
|
|
7748
8241
|
import fsPromises from "fs/promises";
|
|
7749
|
-
import
|
|
8242
|
+
import path9 from "path";
|
|
7750
8243
|
import { env as env2 } from "process";
|
|
7751
8244
|
import { pipeline } from "stream/promises";
|
|
7752
8245
|
import chalk5 from "chalk";
|
|
@@ -8030,7 +8523,7 @@ async function postIssueComment(params) {
|
|
|
8030
8523
|
fpDescription
|
|
8031
8524
|
} = params;
|
|
8032
8525
|
const {
|
|
8033
|
-
path:
|
|
8526
|
+
path: path13,
|
|
8034
8527
|
startLine,
|
|
8035
8528
|
vulnerabilityReportIssue: {
|
|
8036
8529
|
vulnerabilityReportIssueTags,
|
|
@@ -8045,7 +8538,7 @@ async function postIssueComment(params) {
|
|
|
8045
8538
|
Refresh the page in order to see the changes.`,
|
|
8046
8539
|
pull_number: pullRequest,
|
|
8047
8540
|
commit_id: commitSha,
|
|
8048
|
-
path:
|
|
8541
|
+
path: path13,
|
|
8049
8542
|
line: startLine
|
|
8050
8543
|
});
|
|
8051
8544
|
const commentId = commentRes.data.id;
|
|
@@ -8079,7 +8572,7 @@ async function postFixComment(params) {
|
|
|
8079
8572
|
scanner
|
|
8080
8573
|
} = params;
|
|
8081
8574
|
const {
|
|
8082
|
-
path:
|
|
8575
|
+
path: path13,
|
|
8083
8576
|
startLine,
|
|
8084
8577
|
vulnerabilityReportIssue: { fixId, vulnerabilityReportIssueTags, category },
|
|
8085
8578
|
vulnerabilityReportIssueId
|
|
@@ -8097,7 +8590,7 @@ async function postFixComment(params) {
|
|
|
8097
8590
|
Refresh the page in order to see the changes.`,
|
|
8098
8591
|
pull_number: pullRequest,
|
|
8099
8592
|
commit_id: commitSha,
|
|
8100
|
-
path:
|
|
8593
|
+
path: path13,
|
|
8101
8594
|
line: startLine
|
|
8102
8595
|
});
|
|
8103
8596
|
const commentId = commentRes.data.id;
|
|
@@ -8376,54 +8869,37 @@ async function handleAutoPr(params) {
|
|
|
8376
8869
|
|
|
8377
8870
|
// src/features/analysis/git.ts
|
|
8378
8871
|
import Debug10 from "debug";
|
|
8379
|
-
import { simpleGit as simpleGit2 } from "simple-git";
|
|
8380
8872
|
var debug10 = Debug10("mobbdev:git");
|
|
8381
|
-
var GIT_NOT_INITIALIZED_ERROR_MESSAGE = "not a git repository";
|
|
8382
8873
|
async function getGitInfo(srcDirPath) {
|
|
8383
8874
|
debug10("getting git info for %s", srcDirPath);
|
|
8384
|
-
const
|
|
8385
|
-
baseDir: srcDirPath,
|
|
8386
|
-
maxConcurrentProcesses: 1,
|
|
8387
|
-
trimmed: true
|
|
8388
|
-
});
|
|
8389
|
-
let repoUrl = "";
|
|
8390
|
-
let hash = "";
|
|
8391
|
-
let reference = "";
|
|
8875
|
+
const gitService = new GitService(srcDirPath);
|
|
8392
8876
|
try {
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8877
|
+
const validationResult = await gitService.validateRepository();
|
|
8878
|
+
if (!validationResult.isValid) {
|
|
8879
|
+
debug10("folder is not a git repo");
|
|
8880
|
+
return {
|
|
8881
|
+
success: false,
|
|
8882
|
+
hash: void 0,
|
|
8883
|
+
reference: void 0,
|
|
8884
|
+
repoUrl: void 0
|
|
8885
|
+
};
|
|
8886
|
+
}
|
|
8887
|
+
const gitInfo = await gitService.getGitInfo();
|
|
8888
|
+
return {
|
|
8889
|
+
success: true,
|
|
8890
|
+
...gitInfo
|
|
8891
|
+
};
|
|
8396
8892
|
} catch (e) {
|
|
8397
8893
|
if (e instanceof Error) {
|
|
8398
8894
|
debug10("failed to run git %o", e);
|
|
8399
8895
|
if (e.message.includes(" spawn ")) {
|
|
8400
8896
|
debug10("git cli not installed");
|
|
8401
|
-
} else if (e.message.includes(GIT_NOT_INITIALIZED_ERROR_MESSAGE)) {
|
|
8402
|
-
debug10("folder is not a git repo");
|
|
8403
|
-
return {
|
|
8404
|
-
success: false,
|
|
8405
|
-
hash: void 0,
|
|
8406
|
-
reference: void 0,
|
|
8407
|
-
repoUrl: void 0
|
|
8408
|
-
};
|
|
8409
8897
|
} else {
|
|
8410
8898
|
throw e;
|
|
8411
8899
|
}
|
|
8412
8900
|
}
|
|
8413
8901
|
throw e;
|
|
8414
8902
|
}
|
|
8415
|
-
if (repoUrl.endsWith(".git")) {
|
|
8416
|
-
repoUrl = repoUrl.slice(0, -".git".length);
|
|
8417
|
-
}
|
|
8418
|
-
if (repoUrl.startsWith("git@github.com:")) {
|
|
8419
|
-
repoUrl = repoUrl.replace("git@github.com:", "https://github.com/");
|
|
8420
|
-
}
|
|
8421
|
-
return {
|
|
8422
|
-
success: true,
|
|
8423
|
-
repoUrl,
|
|
8424
|
-
hash,
|
|
8425
|
-
reference
|
|
8426
|
-
};
|
|
8427
8903
|
}
|
|
8428
8904
|
|
|
8429
8905
|
// src/features/analysis/graphql/gql.ts
|
|
@@ -8439,6 +8915,7 @@ import Debug11 from "debug";
|
|
|
8439
8915
|
import { createClient } from "graphql-ws";
|
|
8440
8916
|
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
8441
8917
|
import WebSocket from "ws";
|
|
8918
|
+
var DEFAULT_API_URL = "https://api.mobb.ai/v1/graphql";
|
|
8442
8919
|
var debug11 = Debug11("mobbdev:subscribe");
|
|
8443
8920
|
var SUBSCRIPTION_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
8444
8921
|
function createWSClient(options) {
|
|
@@ -8470,10 +8947,11 @@ function subscribe(query, variables, callback, wsClientOptions) {
|
|
|
8470
8947
|
return new Promise((resolve, reject) => {
|
|
8471
8948
|
let timer = null;
|
|
8472
8949
|
const { timeoutInMs = SUBSCRIPTION_TIMEOUT_MS } = wsClientOptions;
|
|
8950
|
+
const API_URL2 = process.env["API_URL"] || DEFAULT_API_URL;
|
|
8473
8951
|
const client = createWSClient({
|
|
8474
8952
|
...wsClientOptions,
|
|
8475
8953
|
websocket: WebSocket,
|
|
8476
|
-
url:
|
|
8954
|
+
url: API_URL2.replace("http", "ws")
|
|
8477
8955
|
});
|
|
8478
8956
|
const unsubscribe = client.subscribe(
|
|
8479
8957
|
{ query, variables },
|
|
@@ -8936,13 +9414,13 @@ var GQLClient = class {
|
|
|
8936
9414
|
};
|
|
8937
9415
|
|
|
8938
9416
|
// src/features/analysis/pack.ts
|
|
8939
|
-
import
|
|
8940
|
-
import
|
|
9417
|
+
import fs6 from "fs";
|
|
9418
|
+
import path7 from "path";
|
|
8941
9419
|
import AdmZip from "adm-zip";
|
|
8942
9420
|
import Debug13 from "debug";
|
|
8943
9421
|
import { globby } from "globby";
|
|
8944
|
-
import { isBinary } from "istextorbinary";
|
|
8945
|
-
import { simpleGit as
|
|
9422
|
+
import { isBinary as isBinary2 } from "istextorbinary";
|
|
9423
|
+
import { simpleGit as simpleGit2 } from "simple-git";
|
|
8946
9424
|
import { parseStringPromise } from "xml2js";
|
|
8947
9425
|
import { z as z28 } from "zod";
|
|
8948
9426
|
var debug13 = Debug13("mobbdev:pack");
|
|
@@ -8971,7 +9449,7 @@ async function pack(srcDirPath, vulnFiles, isIncludeAllFiles = false) {
|
|
|
8971
9449
|
debug13("pack folder %s", srcDirPath);
|
|
8972
9450
|
let git = void 0;
|
|
8973
9451
|
try {
|
|
8974
|
-
git =
|
|
9452
|
+
git = simpleGit2({
|
|
8975
9453
|
baseDir: srcDirPath,
|
|
8976
9454
|
maxConcurrentProcesses: 1,
|
|
8977
9455
|
trimmed: true
|
|
@@ -9003,23 +9481,23 @@ async function pack(srcDirPath, vulnFiles, isIncludeAllFiles = false) {
|
|
|
9003
9481
|
const zip = new AdmZip();
|
|
9004
9482
|
debug13("compressing files");
|
|
9005
9483
|
for (const filepath of filepaths) {
|
|
9006
|
-
const absFilepath =
|
|
9484
|
+
const absFilepath = path7.join(srcDirPath, filepath.toString());
|
|
9007
9485
|
if (!isIncludeAllFiles) {
|
|
9008
9486
|
vulnFiles = vulnFiles.concat(getManifestFilesSuffixes());
|
|
9009
9487
|
if (!endsWithAny(
|
|
9010
|
-
absFilepath.toString().replaceAll(
|
|
9488
|
+
absFilepath.toString().replaceAll(path7.win32.sep, path7.posix.sep),
|
|
9011
9489
|
vulnFiles
|
|
9012
9490
|
)) {
|
|
9013
9491
|
debug13("ignoring %s because it is not a vulnerability file", filepath);
|
|
9014
9492
|
continue;
|
|
9015
9493
|
}
|
|
9016
9494
|
}
|
|
9017
|
-
if (
|
|
9495
|
+
if (fs6.lstatSync(absFilepath).size > MAX_FILE_SIZE) {
|
|
9018
9496
|
debug13("ignoring %s because the size is > 5MB", filepath);
|
|
9019
9497
|
continue;
|
|
9020
9498
|
}
|
|
9021
|
-
const data = git ? await git.showBuffer([`HEAD:./${filepath}`]) :
|
|
9022
|
-
if (
|
|
9499
|
+
const data = git ? await git.showBuffer([`HEAD:./${filepath}`]) : fs6.readFileSync(absFilepath);
|
|
9500
|
+
if (isBinary2(null, data)) {
|
|
9023
9501
|
debug13("ignoring %s because is seems to be a binary file", filepath);
|
|
9024
9502
|
continue;
|
|
9025
9503
|
}
|
|
@@ -9139,16 +9617,16 @@ function createSpawn({ args, processPath, name, cwd }, options) {
|
|
|
9139
9617
|
return createChildProcess({ childProcess: child, name }, options);
|
|
9140
9618
|
}
|
|
9141
9619
|
function createChildProcess({ childProcess, name }, options) {
|
|
9142
|
-
const
|
|
9620
|
+
const debug20 = Debug14(`mobbdev:${name}`);
|
|
9143
9621
|
const { display } = options;
|
|
9144
9622
|
return new Promise((resolve, reject) => {
|
|
9145
9623
|
let out = "";
|
|
9146
9624
|
const onData = (chunk) => {
|
|
9147
|
-
|
|
9625
|
+
debug20(`chunk received from ${name} std ${chunk}`);
|
|
9148
9626
|
out += chunk;
|
|
9149
9627
|
};
|
|
9150
9628
|
if (!childProcess?.stdout || !childProcess?.stderr) {
|
|
9151
|
-
|
|
9629
|
+
debug20(`unable to fork ${name}`);
|
|
9152
9630
|
reject(new Error(`unable to fork ${name}`));
|
|
9153
9631
|
}
|
|
9154
9632
|
childProcess.stdout?.on("data", onData);
|
|
@@ -9158,11 +9636,11 @@ function createChildProcess({ childProcess, name }, options) {
|
|
|
9158
9636
|
childProcess.stderr?.pipe(process2.stderr);
|
|
9159
9637
|
}
|
|
9160
9638
|
childProcess.on("exit", (code) => {
|
|
9161
|
-
|
|
9639
|
+
debug20(`${name} exit code ${code}`);
|
|
9162
9640
|
resolve({ message: out, code });
|
|
9163
9641
|
});
|
|
9164
9642
|
childProcess.on("error", (err) => {
|
|
9165
|
-
|
|
9643
|
+
debug20(`${name} error %o`, err);
|
|
9166
9644
|
reject(err);
|
|
9167
9645
|
});
|
|
9168
9646
|
});
|
|
@@ -9174,12 +9652,12 @@ import Debug15 from "debug";
|
|
|
9174
9652
|
import { existsSync } from "fs";
|
|
9175
9653
|
import { createSpinner as createSpinner2 } from "nanospinner";
|
|
9176
9654
|
import { type } from "os";
|
|
9177
|
-
import
|
|
9655
|
+
import path8 from "path";
|
|
9178
9656
|
var debug14 = Debug15("mobbdev:checkmarx");
|
|
9179
9657
|
var require2 = createRequire(import.meta.url);
|
|
9180
9658
|
var getCheckmarxPath = () => {
|
|
9181
|
-
const
|
|
9182
|
-
const cxFileName =
|
|
9659
|
+
const os3 = type();
|
|
9660
|
+
const cxFileName = os3 === "Windows_NT" ? "cx.exe" : "cx";
|
|
9183
9661
|
try {
|
|
9184
9662
|
return require2.resolve(`.bin/${cxFileName}`);
|
|
9185
9663
|
} catch (e) {
|
|
@@ -9234,9 +9712,9 @@ async function getCheckmarxReport({ reportPath, repositoryRoot, branch, projectN
|
|
|
9234
9712
|
await startCheckmarxConfigationPrompt();
|
|
9235
9713
|
await validateCheckamxCredentials();
|
|
9236
9714
|
}
|
|
9237
|
-
const extension =
|
|
9238
|
-
const filePath =
|
|
9239
|
-
const fileName =
|
|
9715
|
+
const extension = path8.extname(reportPath);
|
|
9716
|
+
const filePath = path8.dirname(reportPath);
|
|
9717
|
+
const fileName = path8.basename(reportPath, extension);
|
|
9240
9718
|
const checkmarxCommandArgs = getCheckmarxCommandArgs({
|
|
9241
9719
|
repoPath: repositoryRoot,
|
|
9242
9720
|
branch,
|
|
@@ -9305,8 +9783,8 @@ async function forkSnyk(args, { display }) {
|
|
|
9305
9783
|
}
|
|
9306
9784
|
async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
|
|
9307
9785
|
debug15("get snyk report start %s %s", reportPath, repoRoot);
|
|
9308
|
-
const
|
|
9309
|
-
const { message: configMessage } =
|
|
9786
|
+
const config5 = await forkSnyk(["config"], { display: false });
|
|
9787
|
+
const { message: configMessage } = config5;
|
|
9310
9788
|
if (!configMessage.includes("api: ")) {
|
|
9311
9789
|
const snykLoginSpinner = createSpinner3().start();
|
|
9312
9790
|
if (!skipPrompts) {
|
|
@@ -9318,7 +9796,7 @@ async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
|
|
|
9318
9796
|
snykLoginSpinner.update({
|
|
9319
9797
|
text: "\u{1F513} Waiting for Snyk login to complete"
|
|
9320
9798
|
});
|
|
9321
|
-
debug15("no token in the config %s",
|
|
9799
|
+
debug15("no token in the config %s", config5);
|
|
9322
9800
|
await forkSnyk(["auth"], { display: true });
|
|
9323
9801
|
snykLoginSpinner.success({ text: "\u{1F513} Login to Snyk Successful" });
|
|
9324
9802
|
}
|
|
@@ -9355,8 +9833,14 @@ async function uploadFile({
|
|
|
9355
9833
|
file,
|
|
9356
9834
|
url,
|
|
9357
9835
|
uploadKey,
|
|
9358
|
-
uploadFields
|
|
9836
|
+
uploadFields,
|
|
9837
|
+
logger: logger2
|
|
9359
9838
|
}) {
|
|
9839
|
+
const logInfo2 = logger2 || ((_message, _data) => {
|
|
9840
|
+
});
|
|
9841
|
+
logInfo2(`FileUpload: upload file start ${url}`);
|
|
9842
|
+
logInfo2(`FileUpload: upload fields`, uploadFields);
|
|
9843
|
+
logInfo2(`FileUpload: upload key ${uploadKey}`);
|
|
9360
9844
|
debug16("upload file start %s", url);
|
|
9361
9845
|
debug16("upload fields %o", uploadFields);
|
|
9362
9846
|
debug16("upload key %s", uploadKey);
|
|
@@ -9369,9 +9853,11 @@ async function uploadFile({
|
|
|
9369
9853
|
}
|
|
9370
9854
|
if (typeof file === "string") {
|
|
9371
9855
|
debug16("upload file from path %s", file);
|
|
9856
|
+
logInfo2(`FileUpload: upload file from path ${file}`);
|
|
9372
9857
|
form.append("file", await fileFrom(file));
|
|
9373
9858
|
} else {
|
|
9374
9859
|
debug16("upload file from buffer");
|
|
9860
|
+
logInfo2(`FileUpload: upload file from buffer`);
|
|
9375
9861
|
form.append("file", new File([file], "file"));
|
|
9376
9862
|
}
|
|
9377
9863
|
const agent = getProxyAgent(url);
|
|
@@ -9382,9 +9868,11 @@ async function uploadFile({
|
|
|
9382
9868
|
});
|
|
9383
9869
|
if (!response.ok) {
|
|
9384
9870
|
debug16("error from S3 %s %s", response.body, response.status);
|
|
9871
|
+
logInfo2(`FileUpload: error from S3 ${response.body} ${response.status}`);
|
|
9385
9872
|
throw new Error(`Failed to upload the file: ${response.status}`);
|
|
9386
9873
|
}
|
|
9387
9874
|
debug16("upload file done");
|
|
9875
|
+
logInfo2(`FileUpload: upload file done`);
|
|
9388
9876
|
}
|
|
9389
9877
|
|
|
9390
9878
|
// src/features/analysis/index.ts
|
|
@@ -9419,7 +9907,7 @@ async function downloadRepo({
|
|
|
9419
9907
|
const { createSpinner: createSpinner5 } = Spinner2({ ci });
|
|
9420
9908
|
const repoSpinner = createSpinner5("\u{1F4BE} Downloading Repo").start();
|
|
9421
9909
|
debug17("download repo %s %s %s", repoUrl, dirname);
|
|
9422
|
-
const zipFilePath =
|
|
9910
|
+
const zipFilePath = path9.join(dirname, "repo.zip");
|
|
9423
9911
|
debug17("download URL: %s auth headers: %o", downloadUrl, authHeaders);
|
|
9424
9912
|
const response = await fetch4(downloadUrl, {
|
|
9425
9913
|
method: "GET",
|
|
@@ -9432,19 +9920,19 @@ async function downloadRepo({
|
|
|
9432
9920
|
repoSpinner.error({ text: "\u{1F4BE} Repo download failed" });
|
|
9433
9921
|
throw new Error(`Can't access ${chalk5.bold(repoUrl)}`);
|
|
9434
9922
|
}
|
|
9435
|
-
const fileWriterStream =
|
|
9923
|
+
const fileWriterStream = fs7.createWriteStream(zipFilePath);
|
|
9436
9924
|
if (!response.body) {
|
|
9437
9925
|
throw new Error("Response body is empty");
|
|
9438
9926
|
}
|
|
9439
9927
|
await pipeline(response.body, fileWriterStream);
|
|
9440
9928
|
await extract(zipFilePath, { dir: dirname });
|
|
9441
|
-
const repoRoot =
|
|
9929
|
+
const repoRoot = fs7.readdirSync(dirname, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name)[0];
|
|
9442
9930
|
if (!repoRoot) {
|
|
9443
9931
|
throw new Error("Repo root not found");
|
|
9444
9932
|
}
|
|
9445
9933
|
debug17("repo root %s", repoRoot);
|
|
9446
9934
|
repoSpinner.success({ text: "\u{1F4BE} Repo downloaded successfully" });
|
|
9447
|
-
return
|
|
9935
|
+
return path9.join(dirname, repoRoot);
|
|
9448
9936
|
}
|
|
9449
9937
|
var getReportUrl = ({
|
|
9450
9938
|
organizationId,
|
|
@@ -9554,7 +10042,7 @@ async function getReport(params, { skipPrompts }) {
|
|
|
9554
10042
|
authHeaders: scm.getAuthHeaders(),
|
|
9555
10043
|
downloadUrl
|
|
9556
10044
|
});
|
|
9557
|
-
const reportPath =
|
|
10045
|
+
const reportPath = path9.join(dirname, REPORT_DEFAULT_FILE_NAME);
|
|
9558
10046
|
switch (scanner) {
|
|
9559
10047
|
case "snyk":
|
|
9560
10048
|
await getSnykReport(reportPath, repositoryRoot, { skipPrompts });
|
|
@@ -9930,7 +10418,7 @@ async function _zipAndUploadRepo({
|
|
|
9930
10418
|
const zippingSpinner = createSpinner4("\u{1F4E6} Zipping repo").start();
|
|
9931
10419
|
let zipBuffer;
|
|
9932
10420
|
let gitInfo = { success: false };
|
|
9933
|
-
if (srcFileStatus.isFile() &&
|
|
10421
|
+
if (srcFileStatus.isFile() && path9.extname(srcPath).toLowerCase() === ".fpr") {
|
|
9934
10422
|
zipBuffer = await repackFpr(srcPath);
|
|
9935
10423
|
} else {
|
|
9936
10424
|
gitInfo = await getGitInfo(srcPath);
|
|
@@ -10277,7 +10765,7 @@ import chalk8 from "chalk";
|
|
|
10277
10765
|
|
|
10278
10766
|
// src/args/validation.ts
|
|
10279
10767
|
import chalk7 from "chalk";
|
|
10280
|
-
import
|
|
10768
|
+
import path10 from "path";
|
|
10281
10769
|
import { z as z30 } from "zod";
|
|
10282
10770
|
function throwRepoUrlErrorMessage({
|
|
10283
10771
|
error,
|
|
@@ -10321,7 +10809,7 @@ function validateRepoUrl(args) {
|
|
|
10321
10809
|
}
|
|
10322
10810
|
var supportExtensions = [".json", ".xml", ".fpr", ".sarif"];
|
|
10323
10811
|
function validateReportFileFormat(reportFile) {
|
|
10324
|
-
if (!supportExtensions.includes(
|
|
10812
|
+
if (!supportExtensions.includes(path10.extname(reportFile))) {
|
|
10325
10813
|
throw new CliError(
|
|
10326
10814
|
`
|
|
10327
10815
|
${chalk7.bold(
|
|
@@ -10363,7 +10851,7 @@ function analyzeBuilder(yargs2) {
|
|
|
10363
10851
|
).help();
|
|
10364
10852
|
}
|
|
10365
10853
|
function validateAnalyzeOptions(argv) {
|
|
10366
|
-
if (argv.f && !
|
|
10854
|
+
if (argv.f && !fs8.existsSync(argv.f)) {
|
|
10367
10855
|
throw new CliError(`
|
|
10368
10856
|
Can't access ${chalk8.bold(argv.f)}`);
|
|
10369
10857
|
}
|
|
@@ -10416,6 +10904,7 @@ import {
|
|
|
10416
10904
|
|
|
10417
10905
|
// src/mcp/Logger.ts
|
|
10418
10906
|
var logglerUrl = "http://localhost:4444/log";
|
|
10907
|
+
var isTestEnvironment = process.env["VITEST"] || process.env["TEST"];
|
|
10419
10908
|
var Logger = class {
|
|
10420
10909
|
log(message, level = "info", data) {
|
|
10421
10910
|
const logMessage = {
|
|
@@ -10424,13 +10913,15 @@ var Logger = class {
|
|
|
10424
10913
|
message,
|
|
10425
10914
|
data
|
|
10426
10915
|
};
|
|
10427
|
-
|
|
10428
|
-
|
|
10429
|
-
|
|
10430
|
-
|
|
10431
|
-
|
|
10432
|
-
|
|
10433
|
-
|
|
10916
|
+
if (!isTestEnvironment) {
|
|
10917
|
+
try {
|
|
10918
|
+
fetch(logglerUrl, {
|
|
10919
|
+
method: "POST",
|
|
10920
|
+
headers: { "Content-Type": "application/json" },
|
|
10921
|
+
body: JSON.stringify(logMessage)
|
|
10922
|
+
});
|
|
10923
|
+
} catch (error) {
|
|
10924
|
+
}
|
|
10434
10925
|
}
|
|
10435
10926
|
}
|
|
10436
10927
|
};
|
|
@@ -10439,775 +10930,150 @@ var logInfo = (message, data) => logger.log(message, "info", data);
|
|
|
10439
10930
|
var logError = (message, data) => logger.log(message, "error", data);
|
|
10440
10931
|
var logWarn = (message, data) => logger.log(message, "warn", data);
|
|
10441
10932
|
var logDebug = (message, data) => logger.log(message, "debug", data);
|
|
10442
|
-
var
|
|
10933
|
+
var log = logger.log;
|
|
10443
10934
|
|
|
10444
|
-
// src/mcp/
|
|
10445
|
-
|
|
10446
|
-
|
|
10447
|
-
|
|
10935
|
+
// src/mcp/services/McpGQLClient.ts
|
|
10936
|
+
import crypto2 from "crypto";
|
|
10937
|
+
import os2 from "os";
|
|
10938
|
+
import Configstore3 from "configstore";
|
|
10939
|
+
import { GraphQLClient as GraphQLClient2 } from "graphql-request";
|
|
10940
|
+
import open4 from "open";
|
|
10941
|
+
import { v4 as uuidv42 } from "uuid";
|
|
10942
|
+
|
|
10943
|
+
// src/mcp/constants.ts
|
|
10944
|
+
var DEFAULT_API_URL2 = "https://api.mobb.ai/v1/graphql";
|
|
10945
|
+
var API_KEY_HEADER_NAME2 = "x-mobb-key";
|
|
10946
|
+
|
|
10947
|
+
// src/mcp/tools/fixVulnerabilities/errors/VulnerabilityFixErrors.ts
|
|
10948
|
+
var ApiConnectionError = class extends Error {
|
|
10949
|
+
constructor(message = "Failed to connect to the API") {
|
|
10950
|
+
super(message);
|
|
10951
|
+
this.name = "ApiConnectionError";
|
|
10448
10952
|
}
|
|
10449
|
-
|
|
10450
|
-
|
|
10451
|
-
|
|
10452
|
-
|
|
10453
|
-
|
|
10454
|
-
}
|
|
10455
|
-
this.tools.set(tool.name, tool);
|
|
10456
|
-
logDebug(`Tool registered: ${tool.name}`, {
|
|
10457
|
-
toolName: tool.name,
|
|
10458
|
-
description: tool.definition.description
|
|
10459
|
-
});
|
|
10953
|
+
};
|
|
10954
|
+
var CliLoginError = class extends Error {
|
|
10955
|
+
constructor(message = "CLI login failed") {
|
|
10956
|
+
super(message);
|
|
10957
|
+
this.name = "CliLoginError";
|
|
10460
10958
|
}
|
|
10461
|
-
|
|
10462
|
-
|
|
10959
|
+
};
|
|
10960
|
+
var AuthenticationError = class extends Error {
|
|
10961
|
+
constructor(message = "Authentication failed") {
|
|
10962
|
+
super(message);
|
|
10963
|
+
this.name = "AuthenticationError";
|
|
10463
10964
|
}
|
|
10464
|
-
|
|
10465
|
-
|
|
10965
|
+
};
|
|
10966
|
+
var NoFilesError = class extends Error {
|
|
10967
|
+
constructor(message = "No files to fix") {
|
|
10968
|
+
super(message);
|
|
10969
|
+
this.name = "NoFilesError";
|
|
10466
10970
|
}
|
|
10467
|
-
|
|
10468
|
-
|
|
10971
|
+
};
|
|
10972
|
+
var GqlClientError = class extends Error {
|
|
10973
|
+
constructor(message = "GraphQL client not initialized") {
|
|
10974
|
+
super(message);
|
|
10975
|
+
this.name = "GqlClientError";
|
|
10469
10976
|
}
|
|
10470
|
-
|
|
10471
|
-
|
|
10977
|
+
};
|
|
10978
|
+
var FileProcessingError = class extends Error {
|
|
10979
|
+
constructor(message) {
|
|
10980
|
+
super(message);
|
|
10981
|
+
this.name = "FileProcessingError";
|
|
10472
10982
|
}
|
|
10473
|
-
|
|
10474
|
-
|
|
10983
|
+
};
|
|
10984
|
+
var ReportInitializationError = class extends Error {
|
|
10985
|
+
constructor(message) {
|
|
10986
|
+
super(message);
|
|
10987
|
+
this.name = "ReportInitializationError";
|
|
10475
10988
|
}
|
|
10476
10989
|
};
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
|
|
10481
|
-
__publicField(this, "server");
|
|
10482
|
-
__publicField(this, "toolRegistry");
|
|
10483
|
-
__publicField(this, "isEventHandlersSetup", false);
|
|
10484
|
-
this.server = new Server(
|
|
10485
|
-
{
|
|
10486
|
-
name: config4.name,
|
|
10487
|
-
version: config4.version
|
|
10488
|
-
},
|
|
10489
|
-
{
|
|
10490
|
-
capabilities: {
|
|
10491
|
-
tools: {}
|
|
10492
|
-
}
|
|
10493
|
-
}
|
|
10494
|
-
);
|
|
10495
|
-
this.toolRegistry = new ToolRegistry();
|
|
10496
|
-
this.setupHandlers();
|
|
10497
|
-
this.setupProcessEventHandlers();
|
|
10498
|
-
logInfo("MCP server instance created", config4);
|
|
10990
|
+
var FileUploadError = class extends Error {
|
|
10991
|
+
constructor(message) {
|
|
10992
|
+
super(message);
|
|
10993
|
+
this.name = "FileUploadError";
|
|
10499
10994
|
}
|
|
10500
|
-
|
|
10501
|
-
|
|
10502
|
-
|
|
10503
|
-
|
|
10504
|
-
|
|
10505
|
-
const signals = {
|
|
10506
|
-
SIGINT: "MCP server interrupted",
|
|
10507
|
-
SIGTERM: "MCP server terminated",
|
|
10508
|
-
exit: "MCP server exiting",
|
|
10509
|
-
uncaughtException: "Uncaught exception in MCP server",
|
|
10510
|
-
unhandledRejection: "Unhandled promise rejection in MCP server",
|
|
10511
|
-
warning: "Warning in MCP server"
|
|
10512
|
-
};
|
|
10513
|
-
Object.entries(signals).forEach(([signal, message]) => {
|
|
10514
|
-
process.on(
|
|
10515
|
-
signal,
|
|
10516
|
-
(error) => {
|
|
10517
|
-
if (error && signal !== "exit") {
|
|
10518
|
-
logError(`${message}`, { error, signal });
|
|
10519
|
-
} else {
|
|
10520
|
-
logInfo(message, { signal });
|
|
10521
|
-
}
|
|
10522
|
-
if (signal === "SIGINT" || signal === "SIGTERM") {
|
|
10523
|
-
process.exit(0);
|
|
10524
|
-
}
|
|
10525
|
-
if (signal === "uncaughtException") {
|
|
10526
|
-
process.exit(1);
|
|
10527
|
-
}
|
|
10528
|
-
}
|
|
10529
|
-
);
|
|
10530
|
-
});
|
|
10531
|
-
this.isEventHandlersSetup = true;
|
|
10532
|
-
logDebug("Process event handlers registered");
|
|
10995
|
+
};
|
|
10996
|
+
var ScanError = class extends Error {
|
|
10997
|
+
constructor(message) {
|
|
10998
|
+
super(message);
|
|
10999
|
+
this.name = "ScanError";
|
|
10533
11000
|
}
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
|
|
10537
|
-
|
|
10538
|
-
|
|
10539
|
-
};
|
|
10540
|
-
process.once("SIGINT", cleanup);
|
|
10541
|
-
process.once("SIGTERM", cleanup);
|
|
10542
|
-
});
|
|
11001
|
+
};
|
|
11002
|
+
var FailedToGetApiTokenError = class extends Error {
|
|
11003
|
+
constructor(message) {
|
|
11004
|
+
super(message);
|
|
11005
|
+
this.name = "FailedToGetApiTokenError";
|
|
10543
11006
|
}
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
|
|
10547
|
-
|
|
10548
|
-
|
|
10549
|
-
|
|
10550
|
-
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
|
|
10555
|
-
|
|
10556
|
-
|
|
10557
|
-
|
|
10558
|
-
|
|
10559
|
-
this.
|
|
10560
|
-
|
|
10561
|
-
|
|
10562
|
-
|
|
10563
|
-
|
|
10564
|
-
|
|
10565
|
-
|
|
10566
|
-
|
|
10567
|
-
|
|
10568
|
-
|
|
10569
|
-
|
|
10570
|
-
availableTools: this.toolRegistry.getToolNames()
|
|
10571
|
-
});
|
|
10572
|
-
throw new Error(errorMsg);
|
|
11007
|
+
};
|
|
11008
|
+
|
|
11009
|
+
// src/mcp/services/McpGQLClient.ts
|
|
11010
|
+
var LOGIN_MAX_WAIT2 = 10 * 1e3;
|
|
11011
|
+
var LOGIN_CHECK_DELAY2 = 1 * 1e3;
|
|
11012
|
+
var config4 = new Configstore3(packageJson.name, { apiToken: "" });
|
|
11013
|
+
var BROWSER_COOLDOWN_MS = 5e3;
|
|
11014
|
+
var lastBrowserOpenTime = 0;
|
|
11015
|
+
var McpGQLClient = class {
|
|
11016
|
+
constructor(args) {
|
|
11017
|
+
__publicField(this, "client");
|
|
11018
|
+
__publicField(this, "clientSdk");
|
|
11019
|
+
__publicField(this, "_auth");
|
|
11020
|
+
this._auth = args;
|
|
11021
|
+
const API_URL2 = process.env["API_URL"] || DEFAULT_API_URL2;
|
|
11022
|
+
this.client = new GraphQLClient2(API_URL2, {
|
|
11023
|
+
headers: args.type === "apiKey" ? { [API_KEY_HEADER_NAME2]: args.apiKey || "" } : {
|
|
11024
|
+
Authorization: `Bearer ${args.token}`
|
|
11025
|
+
},
|
|
11026
|
+
requestMiddleware: (request) => {
|
|
11027
|
+
const requestId = uuidv42();
|
|
11028
|
+
return {
|
|
11029
|
+
...request,
|
|
11030
|
+
headers: {
|
|
11031
|
+
...request.headers,
|
|
11032
|
+
"x-hasura-request-id": requestId
|
|
10573
11033
|
}
|
|
10574
|
-
|
|
10575
|
-
const response = await tool.execute(args);
|
|
10576
|
-
const serializedResponse = JSON.parse(JSON.stringify(response));
|
|
10577
|
-
logInfo(`Tool ${name} executed successfully`, {
|
|
10578
|
-
responseType: typeof response,
|
|
10579
|
-
hasContent: !!serializedResponse.content
|
|
10580
|
-
});
|
|
10581
|
-
return serializedResponse;
|
|
10582
|
-
} catch (error) {
|
|
10583
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
10584
|
-
logError(`Error executing tool ${name}: ${errorMessage}`, {
|
|
10585
|
-
error,
|
|
10586
|
-
toolName: name,
|
|
10587
|
-
args
|
|
10588
|
-
});
|
|
10589
|
-
throw error;
|
|
10590
|
-
}
|
|
11034
|
+
};
|
|
10591
11035
|
}
|
|
10592
|
-
);
|
|
10593
|
-
logDebug("MCP server handlers registered");
|
|
10594
|
-
}
|
|
10595
|
-
registerTool(tool) {
|
|
10596
|
-
this.toolRegistry.registerTool({
|
|
10597
|
-
name: tool.name,
|
|
10598
|
-
definition: tool.definition,
|
|
10599
|
-
execute: tool.execute
|
|
10600
11036
|
});
|
|
10601
|
-
|
|
10602
|
-
}
|
|
10603
|
-
async start() {
|
|
10604
|
-
try {
|
|
10605
|
-
logDebug("Starting MCP server");
|
|
10606
|
-
const transport = new StdioServerTransport();
|
|
10607
|
-
await this.server.connect(transport);
|
|
10608
|
-
logInfo("MCP server is running on stdin/stdout");
|
|
10609
|
-
process.stdin.resume();
|
|
10610
|
-
await this.createShutdownPromise();
|
|
10611
|
-
await this.stop();
|
|
10612
|
-
} catch (error) {
|
|
10613
|
-
logError("Failed to start MCP server", { error });
|
|
10614
|
-
throw error;
|
|
10615
|
-
}
|
|
10616
|
-
}
|
|
10617
|
-
async stop() {
|
|
10618
|
-
logInfo("MCP server shutting down");
|
|
11037
|
+
this.clientSdk = getSdk(this.client);
|
|
10619
11038
|
}
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
|
|
10624
|
-
|
|
10625
|
-
|
|
10626
|
-
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
this.git = simpleGit4(repositoryPath, { binary: "git" });
|
|
10630
|
-
this.repositoryPath = repositoryPath;
|
|
10631
|
-
logDebug("Git service initialized", { repositoryPath });
|
|
11039
|
+
getErrorContext() {
|
|
11040
|
+
return {
|
|
11041
|
+
endpoint: process.env["API_URL"] || DEFAULT_API_URL2,
|
|
11042
|
+
apiKey: this._auth.type === "apiKey" ? this._auth.apiKey : "",
|
|
11043
|
+
headers: {
|
|
11044
|
+
[API_KEY_HEADER_NAME2]: this._auth.type === "apiKey" ? "[REDACTED]" : "undefined",
|
|
11045
|
+
"x-hasura-request-id": "[DYNAMIC]"
|
|
11046
|
+
}
|
|
11047
|
+
};
|
|
10632
11048
|
}
|
|
10633
|
-
|
|
10634
|
-
* Validates that the path is a valid git repository
|
|
10635
|
-
*/
|
|
10636
|
-
async validateRepository() {
|
|
10637
|
-
logDebug("Validating git repository");
|
|
11049
|
+
async verifyConnection() {
|
|
10638
11050
|
try {
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
|
|
10642
|
-
|
|
10643
|
-
|
|
11051
|
+
logDebug("GraphQL: Calling Me query for connection verification");
|
|
11052
|
+
const result = await this.clientSdk.Me();
|
|
11053
|
+
logInfo("GraphQL: Me query successful", { result });
|
|
11054
|
+
return true;
|
|
11055
|
+
} catch (e) {
|
|
11056
|
+
if (e?.toString().includes("FetchError")) {
|
|
11057
|
+
logError("verify connection failed %o", e);
|
|
11058
|
+
return false;
|
|
10644
11059
|
}
|
|
10645
|
-
logDebug("Git repository validation successful");
|
|
10646
|
-
return { isValid: true };
|
|
10647
|
-
} catch (error) {
|
|
10648
|
-
const errorMessage = `Failed to verify git repository: ${error.message}`;
|
|
10649
|
-
logError(errorMessage, { error });
|
|
10650
|
-
return { isValid: false, error: errorMessage };
|
|
10651
11060
|
}
|
|
11061
|
+
return true;
|
|
10652
11062
|
}
|
|
10653
|
-
|
|
10654
|
-
* Gets the current git status and returns changed files
|
|
10655
|
-
*/
|
|
10656
|
-
async getChangedFiles() {
|
|
10657
|
-
logDebug("Getting git status");
|
|
11063
|
+
async uploadS3BucketInfo() {
|
|
10658
11064
|
try {
|
|
10659
|
-
|
|
10660
|
-
const
|
|
10661
|
-
|
|
10662
|
-
gitRoot,
|
|
10663
|
-
this.repositoryPath
|
|
10664
|
-
);
|
|
10665
|
-
const files = status.files.map((file) => {
|
|
10666
|
-
const gitRelativePath = file.path;
|
|
10667
|
-
if (relativePathFromGitRoot === "") {
|
|
10668
|
-
return gitRelativePath;
|
|
10669
|
-
}
|
|
10670
|
-
if (gitRelativePath.startsWith(relativePathFromGitRoot + "/")) {
|
|
10671
|
-
return gitRelativePath.substring(relativePathFromGitRoot.length + 1);
|
|
10672
|
-
}
|
|
10673
|
-
return path9.relative(
|
|
10674
|
-
this.repositoryPath,
|
|
10675
|
-
path9.join(gitRoot, gitRelativePath)
|
|
10676
|
-
);
|
|
11065
|
+
logDebug("GraphQL: Calling uploadS3BucketInfo mutation");
|
|
11066
|
+
const result = await this.clientSdk.uploadS3BucketInfo({
|
|
11067
|
+
fileName: "report.json"
|
|
10677
11068
|
});
|
|
10678
|
-
logInfo("
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
10683
|
-
|
|
10684
|
-
relativePathFromGitRoot
|
|
11069
|
+
logInfo("GraphQL: uploadS3BucketInfo successful", { result });
|
|
11070
|
+
return result;
|
|
11071
|
+
} catch (e) {
|
|
11072
|
+
logError("GraphQL: uploadS3BucketInfo failed", {
|
|
11073
|
+
error: e,
|
|
11074
|
+
...this.getErrorContext()
|
|
10685
11075
|
});
|
|
10686
|
-
|
|
10687
|
-
} catch (error) {
|
|
10688
|
-
const errorMessage = `Failed to get git status: ${error.message}`;
|
|
10689
|
-
logError(errorMessage, { error });
|
|
10690
|
-
throw new Error(errorMessage);
|
|
10691
|
-
}
|
|
10692
|
-
}
|
|
10693
|
-
};
|
|
10694
|
-
|
|
10695
|
-
// src/mcp/services/PathValidation.ts
|
|
10696
|
-
import fs8 from "fs";
|
|
10697
|
-
import path10 from "path";
|
|
10698
|
-
var PathValidation = class {
|
|
10699
|
-
/**
|
|
10700
|
-
* Validates a path for MCP usage - combines security and existence checks
|
|
10701
|
-
*/
|
|
10702
|
-
async validatePath(inputPath) {
|
|
10703
|
-
logDebug("Validating MCP path", { inputPath });
|
|
10704
|
-
if (inputPath.includes("..")) {
|
|
10705
|
-
const error = `Path contains path traversal patterns: ${inputPath}`;
|
|
10706
|
-
logError(error);
|
|
10707
|
-
return { isValid: false, error };
|
|
10708
|
-
}
|
|
10709
|
-
const normalizedPath = path10.normalize(inputPath);
|
|
10710
|
-
if (normalizedPath.includes("..")) {
|
|
10711
|
-
const error = `Normalized path contains path traversal patterns: ${inputPath}`;
|
|
10712
|
-
logError(error);
|
|
10713
|
-
return { isValid: false, error };
|
|
10714
|
-
}
|
|
10715
|
-
const decodedPath = decodeURIComponent(inputPath);
|
|
10716
|
-
if (decodedPath.includes("..") || decodedPath !== inputPath) {
|
|
10717
|
-
const error = `Path contains encoded traversal attempts: ${inputPath}`;
|
|
10718
|
-
logError(error);
|
|
10719
|
-
return { isValid: false, error };
|
|
10720
|
-
}
|
|
10721
|
-
if (inputPath.includes("\0") || inputPath.includes("\0")) {
|
|
10722
|
-
const error = `Path contains dangerous characters: ${inputPath}`;
|
|
10723
|
-
logError(error);
|
|
10724
|
-
return { isValid: false, error };
|
|
10725
|
-
}
|
|
10726
|
-
logDebug("Path validation successful", { inputPath });
|
|
10727
|
-
logDebug("Checking path existence", { inputPath });
|
|
10728
|
-
try {
|
|
10729
|
-
await fs8.promises.access(inputPath);
|
|
10730
|
-
logDebug("Path exists and is accessible", { inputPath });
|
|
10731
|
-
return { isValid: true };
|
|
10732
|
-
} catch (error) {
|
|
10733
|
-
const errorMessage = `Path does not exist or is not accessible: ${inputPath}`;
|
|
10734
|
-
logError(errorMessage, { error });
|
|
10735
|
-
return { isValid: false, error: errorMessage };
|
|
10736
|
-
}
|
|
10737
|
-
}
|
|
10738
|
-
};
|
|
10739
|
-
|
|
10740
|
-
// src/mcp/services/FilePacking.ts
|
|
10741
|
-
import fs9 from "fs";
|
|
10742
|
-
import path11 from "path";
|
|
10743
|
-
import AdmZip2 from "adm-zip";
|
|
10744
|
-
import { isBinary as isBinary2 } from "istextorbinary";
|
|
10745
|
-
var MAX_FILE_SIZE2 = 1024 * 1024 * 5;
|
|
10746
|
-
var EXCLUDED_FILE_PATTERNS = [
|
|
10747
|
-
// Configuration files
|
|
10748
|
-
".json",
|
|
10749
|
-
".yaml",
|
|
10750
|
-
".yml",
|
|
10751
|
-
".toml",
|
|
10752
|
-
".ini",
|
|
10753
|
-
".conf",
|
|
10754
|
-
".config",
|
|
10755
|
-
".xml",
|
|
10756
|
-
".env",
|
|
10757
|
-
// Documentation
|
|
10758
|
-
".md",
|
|
10759
|
-
".txt",
|
|
10760
|
-
".rst",
|
|
10761
|
-
".adoc",
|
|
10762
|
-
// Lock/dependency files
|
|
10763
|
-
".lock",
|
|
10764
|
-
// Images and media
|
|
10765
|
-
".png",
|
|
10766
|
-
".jpg",
|
|
10767
|
-
".jpeg",
|
|
10768
|
-
".gif",
|
|
10769
|
-
".svg",
|
|
10770
|
-
".ico",
|
|
10771
|
-
".webp",
|
|
10772
|
-
".bmp",
|
|
10773
|
-
".tiff",
|
|
10774
|
-
// Fonts
|
|
10775
|
-
".ttf",
|
|
10776
|
-
".otf",
|
|
10777
|
-
".woff",
|
|
10778
|
-
".woff2",
|
|
10779
|
-
".eot",
|
|
10780
|
-
// Archives
|
|
10781
|
-
".zip",
|
|
10782
|
-
".tar",
|
|
10783
|
-
".gz",
|
|
10784
|
-
".rar",
|
|
10785
|
-
".7z",
|
|
10786
|
-
// Logs and databases
|
|
10787
|
-
".log",
|
|
10788
|
-
".db",
|
|
10789
|
-
".sqlite",
|
|
10790
|
-
".sql",
|
|
10791
|
-
// Certificates and keys
|
|
10792
|
-
".pem",
|
|
10793
|
-
".crt",
|
|
10794
|
-
".key",
|
|
10795
|
-
".p12",
|
|
10796
|
-
".pfx",
|
|
10797
|
-
// IDE/Editor files
|
|
10798
|
-
".editorconfig",
|
|
10799
|
-
".sublime-project",
|
|
10800
|
-
".sublime-workspace",
|
|
10801
|
-
// System files
|
|
10802
|
-
".DS_Store",
|
|
10803
|
-
"Thumbs.db",
|
|
10804
|
-
// Coverage reports
|
|
10805
|
-
".lcov",
|
|
10806
|
-
// Compiled/binary files
|
|
10807
|
-
".exe",
|
|
10808
|
-
".dll",
|
|
10809
|
-
".so",
|
|
10810
|
-
".dylib",
|
|
10811
|
-
".class",
|
|
10812
|
-
".pyc",
|
|
10813
|
-
".pyo",
|
|
10814
|
-
".o",
|
|
10815
|
-
".obj",
|
|
10816
|
-
// Minified files
|
|
10817
|
-
".min.js",
|
|
10818
|
-
".min.css",
|
|
10819
|
-
".min.html",
|
|
10820
|
-
// Test files
|
|
10821
|
-
".test.js",
|
|
10822
|
-
".test.ts",
|
|
10823
|
-
".test.jsx",
|
|
10824
|
-
".test.tsx",
|
|
10825
|
-
".spec.js",
|
|
10826
|
-
".spec.ts",
|
|
10827
|
-
".spec.jsx",
|
|
10828
|
-
".spec.tsx",
|
|
10829
|
-
// TypeScript declaration files
|
|
10830
|
-
".d.ts",
|
|
10831
|
-
// Build/generated files
|
|
10832
|
-
".bundle.js",
|
|
10833
|
-
".chunk.js",
|
|
10834
|
-
// Build/CI files (exact filenames)
|
|
10835
|
-
"dockerfile",
|
|
10836
|
-
"jenkinsfile",
|
|
10837
|
-
// Lock files (ones without standard extensions)
|
|
10838
|
-
"go.sum",
|
|
10839
|
-
// Version control
|
|
10840
|
-
".gitignore",
|
|
10841
|
-
".gitattributes",
|
|
10842
|
-
".gitmodules",
|
|
10843
|
-
".gitkeep",
|
|
10844
|
-
".keep",
|
|
10845
|
-
".hgignore",
|
|
10846
|
-
// Node.js specific
|
|
10847
|
-
".nvmrc",
|
|
10848
|
-
".node-version",
|
|
10849
|
-
".npmrc",
|
|
10850
|
-
".yarnrc",
|
|
10851
|
-
".pnpmfile.cjs",
|
|
10852
|
-
// Language version files
|
|
10853
|
-
".ruby-version",
|
|
10854
|
-
".python-version",
|
|
10855
|
-
".rvmrc",
|
|
10856
|
-
".rbenv-version",
|
|
10857
|
-
".gvmrc",
|
|
10858
|
-
// Build tools and task runners
|
|
10859
|
-
"makefile",
|
|
10860
|
-
"rakefile",
|
|
10861
|
-
"gulpfile.js",
|
|
10862
|
-
"gruntfile.js",
|
|
10863
|
-
"webpack.config.js",
|
|
10864
|
-
"webpack.config.ts",
|
|
10865
|
-
"rollup.config.js",
|
|
10866
|
-
"vite.config.js",
|
|
10867
|
-
"vite.config.ts",
|
|
10868
|
-
"next.config.js",
|
|
10869
|
-
"nuxt.config.js",
|
|
10870
|
-
"tailwind.config.js",
|
|
10871
|
-
"postcss.config.js",
|
|
10872
|
-
// JavaScript/TypeScript config
|
|
10873
|
-
".babelrc",
|
|
10874
|
-
".babelrc.js",
|
|
10875
|
-
".swcrc",
|
|
10876
|
-
".browserslistrc",
|
|
10877
|
-
// Testing frameworks
|
|
10878
|
-
"jest.config.js",
|
|
10879
|
-
"jest.config.ts",
|
|
10880
|
-
"vitest.config.js",
|
|
10881
|
-
"karma.conf.js",
|
|
10882
|
-
"protractor.conf.js",
|
|
10883
|
-
"cypress.config.js",
|
|
10884
|
-
"playwright.config.js",
|
|
10885
|
-
".nycrc",
|
|
10886
|
-
".c8rc",
|
|
10887
|
-
// Linting/formatting configs
|
|
10888
|
-
".eslintrc",
|
|
10889
|
-
".eslintrc.js",
|
|
10890
|
-
".prettierrc",
|
|
10891
|
-
".prettierrc.js",
|
|
10892
|
-
".stylelintrc",
|
|
10893
|
-
".stylelintrc.js",
|
|
10894
|
-
// Package manager configs (ones without standard extensions)
|
|
10895
|
-
"pipfile",
|
|
10896
|
-
"gemfile",
|
|
10897
|
-
"go.mod",
|
|
10898
|
-
"project.clj",
|
|
10899
|
-
// Python specific
|
|
10900
|
-
"setup.py",
|
|
10901
|
-
"setup.cfg",
|
|
10902
|
-
"manifest.in",
|
|
10903
|
-
".pythonrc",
|
|
10904
|
-
// Documentation files (ones without standard extensions)
|
|
10905
|
-
"readme",
|
|
10906
|
-
"changelog",
|
|
10907
|
-
"authors",
|
|
10908
|
-
"contributors",
|
|
10909
|
-
// License and legal (ones without standard extensions)
|
|
10910
|
-
"license",
|
|
10911
|
-
"notice",
|
|
10912
|
-
"copyright",
|
|
10913
|
-
// Web specific
|
|
10914
|
-
".htaccess"
|
|
10915
|
-
];
|
|
10916
|
-
var FilePacking = class {
|
|
10917
|
-
isExcludedFileType(filepath) {
|
|
10918
|
-
const basename = path11.basename(filepath).toLowerCase();
|
|
10919
|
-
if (basename === ".env" || basename.startsWith(".env.")) {
|
|
10920
|
-
return true;
|
|
10921
|
-
}
|
|
10922
|
-
if (EXCLUDED_FILE_PATTERNS.some((pattern) => basename.endsWith(pattern))) {
|
|
10923
|
-
return true;
|
|
10924
|
-
}
|
|
10925
|
-
return false;
|
|
10926
|
-
}
|
|
10927
|
-
async packFiles(sourceDirectoryPath, filesToPack) {
|
|
10928
|
-
logInfo(`FilePacking: packing files from ${sourceDirectoryPath}`);
|
|
10929
|
-
const zip = new AdmZip2();
|
|
10930
|
-
let packedFilesCount = 0;
|
|
10931
|
-
logInfo("FilePacking: compressing files");
|
|
10932
|
-
for (const filepath of filesToPack) {
|
|
10933
|
-
const absoluteFilepath = path11.join(sourceDirectoryPath, filepath);
|
|
10934
|
-
if (this.isExcludedFileType(filepath)) {
|
|
10935
|
-
logInfo(
|
|
10936
|
-
`FilePacking: ignoring ${filepath} because it is an excluded file type`
|
|
10937
|
-
);
|
|
10938
|
-
continue;
|
|
10939
|
-
}
|
|
10940
|
-
if (!fs9.existsSync(absoluteFilepath)) {
|
|
10941
|
-
logInfo(`FilePacking: ignoring ${filepath} because it does not exist`);
|
|
10942
|
-
continue;
|
|
10943
|
-
}
|
|
10944
|
-
if (fs9.lstatSync(absoluteFilepath).size > MAX_FILE_SIZE2) {
|
|
10945
|
-
logInfo(
|
|
10946
|
-
`FilePacking: ignoring ${filepath} because the size is > ${MAX_FILE_SIZE2 / (1024 * 1024)}MB`
|
|
10947
|
-
);
|
|
10948
|
-
continue;
|
|
10949
|
-
}
|
|
10950
|
-
let data;
|
|
10951
|
-
try {
|
|
10952
|
-
data = fs9.readFileSync(absoluteFilepath);
|
|
10953
|
-
} catch (fsError) {
|
|
10954
|
-
logInfo(
|
|
10955
|
-
`FilePacking: failed to read ${filepath} from filesystem: ${fsError}`
|
|
10956
|
-
);
|
|
10957
|
-
continue;
|
|
10958
|
-
}
|
|
10959
|
-
if (isBinary2(null, data)) {
|
|
10960
|
-
logInfo(
|
|
10961
|
-
`FilePacking: ignoring ${filepath} because it seems to be a binary file`
|
|
10962
|
-
);
|
|
10963
|
-
continue;
|
|
10964
|
-
}
|
|
10965
|
-
zip.addFile(filepath, data);
|
|
10966
|
-
packedFilesCount++;
|
|
10967
|
-
}
|
|
10968
|
-
const zipBuffer = zip.toBuffer();
|
|
10969
|
-
logInfo(
|
|
10970
|
-
`FilePacking: read ${packedFilesCount} source files. total size: ${zipBuffer.length} bytes`
|
|
10971
|
-
);
|
|
10972
|
-
logInfo("FilePacking: Files packed successfully");
|
|
10973
|
-
return zipBuffer;
|
|
10974
|
-
}
|
|
10975
|
-
};
|
|
10976
|
-
|
|
10977
|
-
// src/mcp/services/FileUpload.ts
|
|
10978
|
-
import { HttpProxyAgent as HttpProxyAgent2 } from "http-proxy-agent";
|
|
10979
|
-
import { HttpsProxyAgent as HttpsProxyAgent3 } from "https-proxy-agent";
|
|
10980
|
-
var FileUpload = class {
|
|
10981
|
-
getProxyAgent(url) {
|
|
10982
|
-
const HTTPS_PROXY2 = process.env["HTTPS_PROXY"];
|
|
10983
|
-
const HTTP_PROXY2 = process.env["HTTP_PROXY"];
|
|
10984
|
-
try {
|
|
10985
|
-
const parsedUrl = new URL(url);
|
|
10986
|
-
const isHttp = parsedUrl.protocol === "http:";
|
|
10987
|
-
const isHttps = parsedUrl.protocol === "https:";
|
|
10988
|
-
const proxy = isHttps ? HTTPS_PROXY2 : isHttp ? HTTP_PROXY2 : null;
|
|
10989
|
-
if (proxy) {
|
|
10990
|
-
logInfo(`FileUpload: Using proxy ${proxy}`);
|
|
10991
|
-
return isHttps ? new HttpsProxyAgent3(proxy) : new HttpProxyAgent2(proxy);
|
|
10992
|
-
}
|
|
10993
|
-
} catch (err) {
|
|
10994
|
-
logInfo(
|
|
10995
|
-
`FileUpload: Skipping proxy for ${url}. Reason: ${err.message}`
|
|
10996
|
-
);
|
|
10997
|
-
}
|
|
10998
|
-
return void 0;
|
|
10999
|
-
}
|
|
11000
|
-
async uploadFile(options) {
|
|
11001
|
-
const { file, url, uploadKey, uploadFields } = options;
|
|
11002
|
-
logInfo(`FileUpload: upload file start ${url}`);
|
|
11003
|
-
logInfo(`FileUpload: upload fields`, uploadFields);
|
|
11004
|
-
logInfo(`FileUpload: upload key ${uploadKey}`);
|
|
11005
|
-
const {
|
|
11006
|
-
default: fetch5,
|
|
11007
|
-
File: File2,
|
|
11008
|
-
fileFrom: fileFrom2,
|
|
11009
|
-
FormData: FormData2
|
|
11010
|
-
} = await import("node-fetch");
|
|
11011
|
-
const form = new FormData2();
|
|
11012
|
-
Object.entries(uploadFields).forEach(([key, value]) => {
|
|
11013
|
-
form.append(key, value);
|
|
11014
|
-
});
|
|
11015
|
-
if (!form.has("key")) {
|
|
11016
|
-
form.append("key", uploadKey);
|
|
11017
|
-
}
|
|
11018
|
-
if (typeof file === "string") {
|
|
11019
|
-
logInfo(`FileUpload: upload file from path ${file}`);
|
|
11020
|
-
form.append("file", await fileFrom2(file));
|
|
11021
|
-
} else {
|
|
11022
|
-
logInfo(`FileUpload: upload file from buffer`);
|
|
11023
|
-
form.append("file", new File2([file], "file"));
|
|
11024
|
-
}
|
|
11025
|
-
const agent = this.getProxyAgent(url);
|
|
11026
|
-
const response = await fetch5(url, {
|
|
11027
|
-
method: "POST",
|
|
11028
|
-
body: form,
|
|
11029
|
-
agent
|
|
11030
|
-
});
|
|
11031
|
-
if (!response.ok) {
|
|
11032
|
-
logInfo(`FileUpload: error from S3 ${response.body} ${response.status}`);
|
|
11033
|
-
throw new Error(`Failed to upload the file: ${response.status}`);
|
|
11034
|
-
}
|
|
11035
|
-
logInfo(`FileUpload: upload file done`);
|
|
11036
|
-
}
|
|
11037
|
-
};
|
|
11038
|
-
|
|
11039
|
-
// src/mcp/services/McpGQLClient.ts
|
|
11040
|
-
import { GraphQLClient as GraphQLClient2 } from "graphql-request";
|
|
11041
|
-
import { v4 as uuidv42 } from "uuid";
|
|
11042
|
-
|
|
11043
|
-
// src/mcp/constants.ts
|
|
11044
|
-
var DEFAULT_API_URL = "https://api.mobb.ai/v1/graphql";
|
|
11045
|
-
var API_KEY_HEADER_NAME2 = "x-mobb-key";
|
|
11046
|
-
|
|
11047
|
-
// src/mcp/services/Subscribe.ts
|
|
11048
|
-
import Debug20 from "debug";
|
|
11049
|
-
import { createClient as createClient2 } from "graphql-ws";
|
|
11050
|
-
import { HttpsProxyAgent as HttpsProxyAgent4 } from "https-proxy-agent";
|
|
11051
|
-
import WebSocket2 from "ws";
|
|
11052
|
-
var debug19 = Debug20("mobbdev:subscribe");
|
|
11053
|
-
var SUBSCRIPTION_TIMEOUT_MS2 = 30 * 60 * 1e3;
|
|
11054
|
-
function createWSClient2(options) {
|
|
11055
|
-
const proxy = options.url.startsWith("wss://") && process.env["HTTPS_PROXY"] ? new HttpsProxyAgent4(process.env["HTTPS_PROXY"]) : options.url.startsWith("ws://") && process.env["HTTP_PROXY"] ? new HttpsProxyAgent4(process.env["HTTP_PROXY"]) : null;
|
|
11056
|
-
debug19(
|
|
11057
|
-
`Using proxy: ${proxy ? "yes" : "no"} with url: ${options.url} and with proxy: ${process.env["HTTP_PROXY"]} for the websocket connection`
|
|
11058
|
-
);
|
|
11059
|
-
const CustomWebSocket = class extends WebSocket2 {
|
|
11060
|
-
constructor(address, protocols) {
|
|
11061
|
-
super(address, protocols, proxy ? { agent: proxy } : void 0);
|
|
11062
|
-
}
|
|
11063
|
-
};
|
|
11064
|
-
return createClient2({
|
|
11065
|
-
//this is needed to prevent AWS from killing the connection
|
|
11066
|
-
//currently our load balancer has a 29s idle timeout
|
|
11067
|
-
keepAlive: 1e4,
|
|
11068
|
-
url: options.url,
|
|
11069
|
-
webSocketImpl: proxy ? CustomWebSocket : options.websocket || WebSocket2,
|
|
11070
|
-
connectionParams: () => {
|
|
11071
|
-
return {
|
|
11072
|
-
headers: options.type === "apiKey" ? {
|
|
11073
|
-
[API_KEY_HEADER_NAME2]: options.apiKey
|
|
11074
|
-
} : { authorization: `Bearer ${options.token}` }
|
|
11075
|
-
};
|
|
11076
|
-
}
|
|
11077
|
-
});
|
|
11078
|
-
}
|
|
11079
|
-
var Subscribe = class {
|
|
11080
|
-
static subscribe(query, variables, callback, wsClientOptions) {
|
|
11081
|
-
return new Promise((resolve, reject) => {
|
|
11082
|
-
let timer = null;
|
|
11083
|
-
const { timeoutInMs = SUBSCRIPTION_TIMEOUT_MS2 } = wsClientOptions;
|
|
11084
|
-
const API_URL2 = process.env["API_URL"] || DEFAULT_API_URL;
|
|
11085
|
-
const client = createWSClient2({
|
|
11086
|
-
...wsClientOptions,
|
|
11087
|
-
websocket: WebSocket2,
|
|
11088
|
-
url: API_URL2.replace("http", "ws")
|
|
11089
|
-
});
|
|
11090
|
-
const unsubscribe = client.subscribe(
|
|
11091
|
-
{ query, variables },
|
|
11092
|
-
{
|
|
11093
|
-
next: (data) => {
|
|
11094
|
-
function callbackResolve(data2) {
|
|
11095
|
-
unsubscribe();
|
|
11096
|
-
if (timer) {
|
|
11097
|
-
clearTimeout(timer);
|
|
11098
|
-
}
|
|
11099
|
-
resolve(data2);
|
|
11100
|
-
}
|
|
11101
|
-
function callbackReject(data2) {
|
|
11102
|
-
unsubscribe();
|
|
11103
|
-
if (timer) {
|
|
11104
|
-
clearTimeout(timer);
|
|
11105
|
-
}
|
|
11106
|
-
reject(data2);
|
|
11107
|
-
}
|
|
11108
|
-
if (!data.data) {
|
|
11109
|
-
reject(
|
|
11110
|
-
new Error(
|
|
11111
|
-
`Broken data object from graphQL subscribe: ${JSON.stringify(
|
|
11112
|
-
data
|
|
11113
|
-
)} for query: ${query}`
|
|
11114
|
-
)
|
|
11115
|
-
);
|
|
11116
|
-
} else {
|
|
11117
|
-
callback(callbackResolve, callbackReject, data.data);
|
|
11118
|
-
}
|
|
11119
|
-
},
|
|
11120
|
-
error: (error) => {
|
|
11121
|
-
if (timer) {
|
|
11122
|
-
clearTimeout(timer);
|
|
11123
|
-
}
|
|
11124
|
-
reject(error);
|
|
11125
|
-
},
|
|
11126
|
-
complete: () => {
|
|
11127
|
-
return;
|
|
11128
|
-
}
|
|
11129
|
-
}
|
|
11130
|
-
);
|
|
11131
|
-
if (typeof timeoutInMs === "number") {
|
|
11132
|
-
timer = setTimeout(() => {
|
|
11133
|
-
unsubscribe();
|
|
11134
|
-
reject(
|
|
11135
|
-
new Error(
|
|
11136
|
-
`Timeout expired for graphQL subscribe query: ${query} with timeout: ${timeoutInMs}`
|
|
11137
|
-
)
|
|
11138
|
-
);
|
|
11139
|
-
}, timeoutInMs);
|
|
11140
|
-
}
|
|
11141
|
-
});
|
|
11142
|
-
}
|
|
11143
|
-
};
|
|
11144
|
-
var subscribe2 = Subscribe.subscribe;
|
|
11145
|
-
|
|
11146
|
-
// src/mcp/services/McpGQLClient.ts
|
|
11147
|
-
var McpGQLClient = class {
|
|
11148
|
-
constructor(args) {
|
|
11149
|
-
__publicField(this, "client");
|
|
11150
|
-
__publicField(this, "clientSdk");
|
|
11151
|
-
__publicField(this, "apiKey");
|
|
11152
|
-
__publicField(this, "apiUrl");
|
|
11153
|
-
const API_URL2 = process.env["API_URL"] || DEFAULT_API_URL;
|
|
11154
|
-
this.apiKey = args.apiKey;
|
|
11155
|
-
this.apiUrl = API_URL2;
|
|
11156
|
-
this.client = new GraphQLClient2(API_URL2, {
|
|
11157
|
-
headers: { [API_KEY_HEADER_NAME2]: args.apiKey || "" },
|
|
11158
|
-
requestMiddleware: (request) => {
|
|
11159
|
-
const requestId = uuidv42();
|
|
11160
|
-
return {
|
|
11161
|
-
...request,
|
|
11162
|
-
headers: {
|
|
11163
|
-
...request.headers,
|
|
11164
|
-
"x-hasura-request-id": requestId
|
|
11165
|
-
}
|
|
11166
|
-
};
|
|
11167
|
-
}
|
|
11168
|
-
});
|
|
11169
|
-
this.clientSdk = getSdk(this.client);
|
|
11170
|
-
}
|
|
11171
|
-
getErrorContext() {
|
|
11172
|
-
return {
|
|
11173
|
-
endpoint: this.apiUrl,
|
|
11174
|
-
headers: {
|
|
11175
|
-
[API_KEY_HEADER_NAME2]: this.apiKey ? "[REDACTED]" : "undefined",
|
|
11176
|
-
"x-hasura-request-id": "[DYNAMIC]"
|
|
11177
|
-
}
|
|
11178
|
-
};
|
|
11179
|
-
}
|
|
11180
|
-
async verifyConnection() {
|
|
11181
|
-
try {
|
|
11182
|
-
logDebug("GraphQL: Calling Me query for connection verification");
|
|
11183
|
-
const result = await this.clientSdk.Me();
|
|
11184
|
-
logInfo("GraphQL: Me query successful", { result });
|
|
11185
|
-
return true;
|
|
11186
|
-
} catch (e) {
|
|
11187
|
-
logError("GraphQL: Me query failed", {
|
|
11188
|
-
error: e,
|
|
11189
|
-
...this.getErrorContext()
|
|
11190
|
-
});
|
|
11191
|
-
if (e?.toString().startsWith("FetchError")) {
|
|
11192
|
-
console.error("Connection verification failed:", e);
|
|
11193
|
-
}
|
|
11194
|
-
return false;
|
|
11195
|
-
}
|
|
11196
|
-
}
|
|
11197
|
-
async uploadS3BucketInfo() {
|
|
11198
|
-
try {
|
|
11199
|
-
logDebug("GraphQL: Calling uploadS3BucketInfo mutation");
|
|
11200
|
-
const result = await this.clientSdk.uploadS3BucketInfo({
|
|
11201
|
-
fileName: "report.json"
|
|
11202
|
-
});
|
|
11203
|
-
logInfo("GraphQL: uploadS3BucketInfo successful", { result });
|
|
11204
|
-
return result;
|
|
11205
|
-
} catch (e) {
|
|
11206
|
-
logError("GraphQL: uploadS3BucketInfo failed", {
|
|
11207
|
-
error: e,
|
|
11208
|
-
...this.getErrorContext()
|
|
11209
|
-
});
|
|
11210
|
-
throw e;
|
|
11076
|
+
throw e;
|
|
11211
11077
|
}
|
|
11212
11078
|
}
|
|
11213
11079
|
async getAnalysis(analysisId) {
|
|
@@ -11253,7 +11119,7 @@ var McpGQLClient = class {
|
|
|
11253
11119
|
params: params.subscribeToAnalysisParams
|
|
11254
11120
|
});
|
|
11255
11121
|
const { callbackStates } = params;
|
|
11256
|
-
const result = await
|
|
11122
|
+
const result = await subscribe(
|
|
11257
11123
|
GetAnalysisSubscriptionDocument,
|
|
11258
11124
|
params.subscribeToAnalysisParams,
|
|
11259
11125
|
async (resolve, reject, data) => {
|
|
@@ -11267,92 +11133,489 @@ var McpGQLClient = class {
|
|
|
11267
11133
|
reject(new Error(`Analysis failed with id: ${data.analysis?.id}`));
|
|
11268
11134
|
return;
|
|
11269
11135
|
}
|
|
11270
|
-
if (callbackStates.includes(data.analysis?.state)) {
|
|
11271
|
-
logInfo("GraphQL: Analysis state matches callback states", {
|
|
11272
|
-
analysisId: data.analysis.id,
|
|
11273
|
-
state: data.analysis.state,
|
|
11274
|
-
callbackStates
|
|
11275
|
-
});
|
|
11276
|
-
await params.callback(data.analysis.id);
|
|
11277
|
-
resolve(data);
|
|
11136
|
+
if (callbackStates.includes(data.analysis?.state)) {
|
|
11137
|
+
logInfo("GraphQL: Analysis state matches callback states", {
|
|
11138
|
+
analysisId: data.analysis.id,
|
|
11139
|
+
state: data.analysis.state,
|
|
11140
|
+
callbackStates
|
|
11141
|
+
});
|
|
11142
|
+
await params.callback(data.analysis.id);
|
|
11143
|
+
resolve(data);
|
|
11144
|
+
}
|
|
11145
|
+
},
|
|
11146
|
+
this._auth.type === "apiKey" ? {
|
|
11147
|
+
apiKey: this._auth.apiKey,
|
|
11148
|
+
type: "apiKey",
|
|
11149
|
+
timeoutInMs: params.timeoutInMs
|
|
11150
|
+
} : {
|
|
11151
|
+
token: this._auth.token,
|
|
11152
|
+
type: "token",
|
|
11153
|
+
timeoutInMs: params.timeoutInMs
|
|
11154
|
+
}
|
|
11155
|
+
);
|
|
11156
|
+
logInfo("GraphQL: GetAnalysis subscription completed", { result });
|
|
11157
|
+
return result;
|
|
11158
|
+
} catch (e) {
|
|
11159
|
+
logError("GraphQL: GetAnalysis subscription failed", {
|
|
11160
|
+
error: e,
|
|
11161
|
+
params: params.subscribeToAnalysisParams,
|
|
11162
|
+
...this.getErrorContext()
|
|
11163
|
+
});
|
|
11164
|
+
throw e;
|
|
11165
|
+
}
|
|
11166
|
+
}
|
|
11167
|
+
async getProjectId() {
|
|
11168
|
+
try {
|
|
11169
|
+
const projectName = "MCP Scans";
|
|
11170
|
+
logDebug("GraphQL: Calling getOrgAndProjectId query", { projectName });
|
|
11171
|
+
const getOrgAndProjectIdResult = await this.clientSdk.getOrgAndProjectId({
|
|
11172
|
+
filters: {},
|
|
11173
|
+
limit: 1
|
|
11174
|
+
});
|
|
11175
|
+
logInfo("GraphQL: getOrgAndProjectId successful", {
|
|
11176
|
+
result: getOrgAndProjectIdResult
|
|
11177
|
+
});
|
|
11178
|
+
const [organizationToOrganizationRole] = getOrgAndProjectIdResult.organization_to_organization_role;
|
|
11179
|
+
if (!organizationToOrganizationRole) {
|
|
11180
|
+
throw new Error("Organization not found");
|
|
11181
|
+
}
|
|
11182
|
+
const { organization: org } = organizationToOrganizationRole;
|
|
11183
|
+
const project = projectName ? org?.projects.find((project2) => project2.name === projectName) ?? null : org?.projects[0];
|
|
11184
|
+
if (project?.id) {
|
|
11185
|
+
logInfo("GraphQL: Found existing project", {
|
|
11186
|
+
projectId: project.id,
|
|
11187
|
+
projectName
|
|
11188
|
+
});
|
|
11189
|
+
return project.id;
|
|
11190
|
+
}
|
|
11191
|
+
logDebug("GraphQL: Project not found, creating new project", {
|
|
11192
|
+
organizationId: org.id,
|
|
11193
|
+
projectName
|
|
11194
|
+
});
|
|
11195
|
+
const createdProject = await this.clientSdk.CreateProject({
|
|
11196
|
+
organizationId: org.id,
|
|
11197
|
+
projectName
|
|
11198
|
+
});
|
|
11199
|
+
logInfo("GraphQL: CreateProject successful", { result: createdProject });
|
|
11200
|
+
return createdProject.createProject.projectId;
|
|
11201
|
+
} catch (e) {
|
|
11202
|
+
logError("GraphQL: getProjectId failed", {
|
|
11203
|
+
error: e,
|
|
11204
|
+
...this.getErrorContext()
|
|
11205
|
+
});
|
|
11206
|
+
throw e;
|
|
11207
|
+
}
|
|
11208
|
+
}
|
|
11209
|
+
async getReportFixes(fixReportId) {
|
|
11210
|
+
try {
|
|
11211
|
+
logDebug("GraphQL: Calling GetMCPFixes query", { fixReportId });
|
|
11212
|
+
const res = await this.clientSdk.GetMCPFixes({ fixReportId });
|
|
11213
|
+
logInfo("GraphQL: GetMCPFixes successful", {
|
|
11214
|
+
result: res,
|
|
11215
|
+
fixCount: res.fix?.length || 0
|
|
11216
|
+
});
|
|
11217
|
+
return res.fix;
|
|
11218
|
+
} catch (e) {
|
|
11219
|
+
logError("GraphQL: GetMCPFixes failed", {
|
|
11220
|
+
error: e,
|
|
11221
|
+
fixReportId,
|
|
11222
|
+
...this.getErrorContext()
|
|
11223
|
+
});
|
|
11224
|
+
throw e;
|
|
11225
|
+
}
|
|
11226
|
+
}
|
|
11227
|
+
async getUserInfo() {
|
|
11228
|
+
const { me } = await this.clientSdk.Me();
|
|
11229
|
+
return me;
|
|
11230
|
+
}
|
|
11231
|
+
async verifyToken() {
|
|
11232
|
+
logDebug("verifying token");
|
|
11233
|
+
try {
|
|
11234
|
+
await this.clientSdk.CreateCommunityUser();
|
|
11235
|
+
const info = await this.getUserInfo();
|
|
11236
|
+
logDebug("token verified");
|
|
11237
|
+
return info?.email || true;
|
|
11238
|
+
} catch (e) {
|
|
11239
|
+
logError("verify token failed");
|
|
11240
|
+
return false;
|
|
11241
|
+
}
|
|
11242
|
+
}
|
|
11243
|
+
async createCliLogin(variables) {
|
|
11244
|
+
try {
|
|
11245
|
+
const res = await this.clientSdk.CreateCliLogin(variables, {
|
|
11246
|
+
// We may have outdated API key in the config storage. Avoid using it for the login request.
|
|
11247
|
+
[API_KEY_HEADER_NAME2]: ""
|
|
11248
|
+
});
|
|
11249
|
+
const loginId = res.insert_cli_login_one?.id || "";
|
|
11250
|
+
if (!loginId) {
|
|
11251
|
+
logError("create cli login failed - no login ID returned");
|
|
11252
|
+
return "";
|
|
11253
|
+
}
|
|
11254
|
+
return loginId;
|
|
11255
|
+
} catch (e) {
|
|
11256
|
+
logError("create cli login failed", { error: e });
|
|
11257
|
+
return "";
|
|
11258
|
+
}
|
|
11259
|
+
}
|
|
11260
|
+
async getEncryptedApiToken(variables) {
|
|
11261
|
+
try {
|
|
11262
|
+
const res = await this.clientSdk.GetEncryptedApiToken(variables, {
|
|
11263
|
+
// We may have outdated API key in the config storage. Avoid using it for the login request.
|
|
11264
|
+
[API_KEY_HEADER_NAME2]: ""
|
|
11265
|
+
});
|
|
11266
|
+
return res?.cli_login_by_pk?.encryptedApiToken || null;
|
|
11267
|
+
} catch (e) {
|
|
11268
|
+
logError("get encrypted api token failed", { error: e });
|
|
11269
|
+
return null;
|
|
11270
|
+
}
|
|
11271
|
+
}
|
|
11272
|
+
};
|
|
11273
|
+
async function openBrowser(url) {
|
|
11274
|
+
const now = Date.now();
|
|
11275
|
+
if (!process.env["TEST"] && now - lastBrowserOpenTime < BROWSER_COOLDOWN_MS) {
|
|
11276
|
+
logDebug(`browser cooldown active, skipping open for ${url}`);
|
|
11277
|
+
return;
|
|
11278
|
+
}
|
|
11279
|
+
logDebug(`opening browser url ${url}`);
|
|
11280
|
+
await open4(url);
|
|
11281
|
+
lastBrowserOpenTime = now;
|
|
11282
|
+
}
|
|
11283
|
+
async function getMcpGQLClient() {
|
|
11284
|
+
logDebug("getting config", { apiToken: config4.get("apiToken") });
|
|
11285
|
+
const inGqlClient = new McpGQLClient({
|
|
11286
|
+
apiKey: config4.get("apiToken") || process.env["API_KEY"] || "",
|
|
11287
|
+
type: "apiKey"
|
|
11288
|
+
});
|
|
11289
|
+
const isConnected = await inGqlClient.verifyConnection();
|
|
11290
|
+
if (!isConnected) {
|
|
11291
|
+
throw new ApiConnectionError("Error: failed to connect to the API");
|
|
11292
|
+
}
|
|
11293
|
+
const userVerify = await inGqlClient.verifyToken();
|
|
11294
|
+
if (userVerify) {
|
|
11295
|
+
return inGqlClient;
|
|
11296
|
+
}
|
|
11297
|
+
const { publicKey, privateKey } = crypto2.generateKeyPairSync("rsa", {
|
|
11298
|
+
modulusLength: 2048
|
|
11299
|
+
});
|
|
11300
|
+
logDebug("creating cli login");
|
|
11301
|
+
const loginId = await inGqlClient.createCliLogin({
|
|
11302
|
+
publicKey: publicKey.export({ format: "pem", type: "pkcs1" }).toString()
|
|
11303
|
+
});
|
|
11304
|
+
if (!loginId) {
|
|
11305
|
+
throw new CliLoginError("Error: createCliLogin failed");
|
|
11306
|
+
}
|
|
11307
|
+
logDebug(`cli login created ${loginId}`);
|
|
11308
|
+
const webLoginUrl2 = `${WEB_APP_URL}/cli-login`;
|
|
11309
|
+
const browserUrl = `${webLoginUrl2}/${loginId}?hostname=${os2.hostname()}`;
|
|
11310
|
+
logDebug(`opening browser url ${browserUrl}`);
|
|
11311
|
+
await openBrowser(browserUrl);
|
|
11312
|
+
logDebug(`waiting for login to complete`);
|
|
11313
|
+
let newApiToken = null;
|
|
11314
|
+
for (let i = 0; i < LOGIN_MAX_WAIT2 / LOGIN_CHECK_DELAY2; i++) {
|
|
11315
|
+
const encryptedApiToken = await inGqlClient.getEncryptedApiToken({
|
|
11316
|
+
loginId
|
|
11317
|
+
});
|
|
11318
|
+
if (encryptedApiToken) {
|
|
11319
|
+
logDebug("encrypted API token received");
|
|
11320
|
+
newApiToken = crypto2.privateDecrypt(privateKey, Buffer.from(encryptedApiToken, "base64")).toString("utf-8");
|
|
11321
|
+
logDebug("API token decrypted");
|
|
11322
|
+
break;
|
|
11323
|
+
}
|
|
11324
|
+
await sleep(LOGIN_CHECK_DELAY2);
|
|
11325
|
+
}
|
|
11326
|
+
if (!newApiToken) {
|
|
11327
|
+
throw new FailedToGetApiTokenError(
|
|
11328
|
+
"Error: failed to get encrypted api token"
|
|
11329
|
+
);
|
|
11330
|
+
}
|
|
11331
|
+
const newGqlClient = new McpGQLClient({ apiKey: newApiToken, type: "apiKey" });
|
|
11332
|
+
const loginSuccess = await newGqlClient.verifyToken();
|
|
11333
|
+
if (loginSuccess) {
|
|
11334
|
+
logDebug("set api token %s", newApiToken);
|
|
11335
|
+
config4.set("apiToken", newApiToken);
|
|
11336
|
+
} else {
|
|
11337
|
+
throw new AuthenticationError("Something went wrong, API token is invalid.");
|
|
11338
|
+
}
|
|
11339
|
+
return newGqlClient;
|
|
11340
|
+
}
|
|
11341
|
+
|
|
11342
|
+
// src/mcp/core/ToolRegistry.ts
|
|
11343
|
+
var ToolRegistry = class {
|
|
11344
|
+
constructor() {
|
|
11345
|
+
__publicField(this, "tools", /* @__PURE__ */ new Map());
|
|
11346
|
+
}
|
|
11347
|
+
registerTool(tool) {
|
|
11348
|
+
if (this.tools.has(tool.name)) {
|
|
11349
|
+
logWarn(`Tool ${tool.name} is already registered, overwriting`, {
|
|
11350
|
+
toolName: tool.name
|
|
11351
|
+
});
|
|
11352
|
+
}
|
|
11353
|
+
this.tools.set(tool.name, tool);
|
|
11354
|
+
logDebug(`Tool registered: ${tool.name}`, {
|
|
11355
|
+
toolName: tool.name,
|
|
11356
|
+
description: tool.definition.description
|
|
11357
|
+
});
|
|
11358
|
+
}
|
|
11359
|
+
getTool(name) {
|
|
11360
|
+
return this.tools.get(name);
|
|
11361
|
+
}
|
|
11362
|
+
getAllTools() {
|
|
11363
|
+
return Array.from(this.tools.values()).map((tool) => tool.definition);
|
|
11364
|
+
}
|
|
11365
|
+
getToolNames() {
|
|
11366
|
+
return Array.from(this.tools.keys());
|
|
11367
|
+
}
|
|
11368
|
+
hasTool(name) {
|
|
11369
|
+
return this.tools.has(name);
|
|
11370
|
+
}
|
|
11371
|
+
getToolCount() {
|
|
11372
|
+
return this.tools.size;
|
|
11373
|
+
}
|
|
11374
|
+
};
|
|
11375
|
+
|
|
11376
|
+
// src/mcp/core/McpServer.ts
|
|
11377
|
+
var McpServer = class {
|
|
11378
|
+
constructor(config5) {
|
|
11379
|
+
__publicField(this, "server");
|
|
11380
|
+
__publicField(this, "toolRegistry");
|
|
11381
|
+
__publicField(this, "isEventHandlersSetup", false);
|
|
11382
|
+
this.server = new Server(
|
|
11383
|
+
{
|
|
11384
|
+
name: config5.name,
|
|
11385
|
+
version: config5.version
|
|
11386
|
+
},
|
|
11387
|
+
{
|
|
11388
|
+
capabilities: {
|
|
11389
|
+
tools: {}
|
|
11390
|
+
}
|
|
11391
|
+
}
|
|
11392
|
+
);
|
|
11393
|
+
this.toolRegistry = new ToolRegistry();
|
|
11394
|
+
this.setupHandlers();
|
|
11395
|
+
this.setupProcessEventHandlers();
|
|
11396
|
+
logInfo("MCP server instance created", config5);
|
|
11397
|
+
}
|
|
11398
|
+
setupProcessEventHandlers() {
|
|
11399
|
+
if (this.isEventHandlersSetup) {
|
|
11400
|
+
logDebug("Process event handlers already setup, skipping");
|
|
11401
|
+
return;
|
|
11402
|
+
}
|
|
11403
|
+
const signals = {
|
|
11404
|
+
SIGINT: "MCP server interrupted",
|
|
11405
|
+
SIGTERM: "MCP server terminated",
|
|
11406
|
+
exit: "MCP server exiting",
|
|
11407
|
+
uncaughtException: "Uncaught exception in MCP server",
|
|
11408
|
+
unhandledRejection: "Unhandled promise rejection in MCP server",
|
|
11409
|
+
warning: "Warning in MCP server"
|
|
11410
|
+
};
|
|
11411
|
+
Object.entries(signals).forEach(([signal, message]) => {
|
|
11412
|
+
process.on(
|
|
11413
|
+
signal,
|
|
11414
|
+
(error) => {
|
|
11415
|
+
if (error && signal !== "exit") {
|
|
11416
|
+
logError(`${message}`, { error, signal });
|
|
11417
|
+
} else {
|
|
11418
|
+
logInfo(message, { signal });
|
|
11419
|
+
}
|
|
11420
|
+
if (signal === "SIGINT" || signal === "SIGTERM") {
|
|
11421
|
+
process.exit(0);
|
|
11422
|
+
}
|
|
11423
|
+
if (signal === "uncaughtException") {
|
|
11424
|
+
process.exit(1);
|
|
11278
11425
|
}
|
|
11279
|
-
},
|
|
11280
|
-
{
|
|
11281
|
-
apiKey: this.apiKey,
|
|
11282
|
-
type: "apiKey",
|
|
11283
|
-
timeoutInMs: params.timeoutInMs
|
|
11284
11426
|
}
|
|
11285
11427
|
);
|
|
11286
|
-
|
|
11287
|
-
|
|
11288
|
-
|
|
11289
|
-
|
|
11290
|
-
|
|
11291
|
-
|
|
11292
|
-
|
|
11293
|
-
|
|
11294
|
-
|
|
11428
|
+
});
|
|
11429
|
+
this.isEventHandlersSetup = true;
|
|
11430
|
+
logDebug("Process event handlers registered");
|
|
11431
|
+
}
|
|
11432
|
+
createShutdownPromise() {
|
|
11433
|
+
return new Promise((resolve) => {
|
|
11434
|
+
const cleanup = () => {
|
|
11435
|
+
logInfo("Process shutdown initiated");
|
|
11436
|
+
resolve();
|
|
11437
|
+
};
|
|
11438
|
+
process.once("SIGINT", cleanup);
|
|
11439
|
+
process.once("SIGTERM", cleanup);
|
|
11440
|
+
});
|
|
11441
|
+
}
|
|
11442
|
+
async handleListToolsRequest(request) {
|
|
11443
|
+
logInfo("Received list_tools request", { params: request.params });
|
|
11444
|
+
try {
|
|
11445
|
+
await getMcpGQLClient();
|
|
11446
|
+
} catch (error) {
|
|
11447
|
+
logError("Failed to get MCPGQLClient", { error });
|
|
11448
|
+
const authError = new Error(
|
|
11449
|
+
"Please authorize this client by visiting: https://mobb.ai"
|
|
11450
|
+
);
|
|
11451
|
+
authError.name = "AuthorizationRequired";
|
|
11452
|
+
throw authError;
|
|
11295
11453
|
}
|
|
11454
|
+
const tools = this.toolRegistry.getAllTools();
|
|
11455
|
+
return {
|
|
11456
|
+
tools: tools.map((tool) => ({
|
|
11457
|
+
name: tool.name,
|
|
11458
|
+
display_name: tool.name,
|
|
11459
|
+
description: tool.description || "",
|
|
11460
|
+
inputSchema: {
|
|
11461
|
+
type: "object",
|
|
11462
|
+
properties: tool.inputSchema.properties || {},
|
|
11463
|
+
required: tool.inputSchema.required || []
|
|
11464
|
+
}
|
|
11465
|
+
}))
|
|
11466
|
+
};
|
|
11296
11467
|
}
|
|
11297
|
-
async
|
|
11468
|
+
async handleCallToolRequest(request) {
|
|
11469
|
+
const { name, arguments: args } = request.params;
|
|
11470
|
+
logInfo(`Received call tool request for ${name}`, { name, args });
|
|
11298
11471
|
try {
|
|
11299
|
-
const
|
|
11300
|
-
|
|
11301
|
-
|
|
11302
|
-
|
|
11303
|
-
|
|
11304
|
-
|
|
11305
|
-
logInfo("GraphQL: getOrgAndProjectId successful", {
|
|
11306
|
-
result: getOrgAndProjectIdResult
|
|
11307
|
-
});
|
|
11308
|
-
const [organizationToOrganizationRole] = getOrgAndProjectIdResult.organization_to_organization_role;
|
|
11309
|
-
if (!organizationToOrganizationRole) {
|
|
11310
|
-
throw new Error("Organization not found");
|
|
11311
|
-
}
|
|
11312
|
-
const { organization: org } = organizationToOrganizationRole;
|
|
11313
|
-
const project = projectName ? org?.projects.find((project2) => project2.name === projectName) ?? null : org?.projects[0];
|
|
11314
|
-
if (project?.id) {
|
|
11315
|
-
logInfo("GraphQL: Found existing project", {
|
|
11316
|
-
projectId: project.id,
|
|
11317
|
-
projectName
|
|
11472
|
+
const tool = this.toolRegistry.getTool(name);
|
|
11473
|
+
if (!tool) {
|
|
11474
|
+
const errorMsg = `Unknown tool: ${name}`;
|
|
11475
|
+
logWarn(errorMsg, {
|
|
11476
|
+
name,
|
|
11477
|
+
availableTools: this.toolRegistry.getToolNames()
|
|
11318
11478
|
});
|
|
11319
|
-
|
|
11479
|
+
throw new Error(errorMsg);
|
|
11320
11480
|
}
|
|
11321
|
-
logDebug(
|
|
11322
|
-
|
|
11323
|
-
|
|
11324
|
-
}
|
|
11325
|
-
|
|
11326
|
-
|
|
11327
|
-
projectName
|
|
11481
|
+
logDebug(`Executing tool: ${name}`, { args });
|
|
11482
|
+
const response = await tool.execute(args);
|
|
11483
|
+
const serializedResponse = JSON.parse(JSON.stringify(response));
|
|
11484
|
+
logInfo(`Tool ${name} executed successfully`, {
|
|
11485
|
+
responseType: typeof response,
|
|
11486
|
+
hasContent: !!serializedResponse.content
|
|
11328
11487
|
});
|
|
11329
|
-
|
|
11330
|
-
|
|
11331
|
-
|
|
11332
|
-
logError(
|
|
11333
|
-
error
|
|
11334
|
-
|
|
11488
|
+
return serializedResponse;
|
|
11489
|
+
} catch (error) {
|
|
11490
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
11491
|
+
logError(`Error executing tool ${name}: ${errorMessage}`, {
|
|
11492
|
+
error,
|
|
11493
|
+
toolName: name,
|
|
11494
|
+
args
|
|
11335
11495
|
});
|
|
11336
|
-
throw
|
|
11496
|
+
throw error;
|
|
11337
11497
|
}
|
|
11338
11498
|
}
|
|
11339
|
-
|
|
11499
|
+
setupHandlers() {
|
|
11500
|
+
this.server.setRequestHandler(
|
|
11501
|
+
ListToolsRequestSchema,
|
|
11502
|
+
(request) => this.handleListToolsRequest(request)
|
|
11503
|
+
);
|
|
11504
|
+
this.server.setRequestHandler(
|
|
11505
|
+
CallToolRequestSchema,
|
|
11506
|
+
(request) => this.handleCallToolRequest(request)
|
|
11507
|
+
);
|
|
11508
|
+
logDebug("MCP server handlers registered");
|
|
11509
|
+
}
|
|
11510
|
+
registerTool(tool) {
|
|
11511
|
+
this.toolRegistry.registerTool({
|
|
11512
|
+
name: tool.name,
|
|
11513
|
+
definition: tool.definition,
|
|
11514
|
+
execute: tool.execute
|
|
11515
|
+
});
|
|
11516
|
+
logDebug(`Tool registered: ${tool.name}`);
|
|
11517
|
+
}
|
|
11518
|
+
async start() {
|
|
11340
11519
|
try {
|
|
11341
|
-
logDebug("
|
|
11342
|
-
const
|
|
11343
|
-
|
|
11344
|
-
|
|
11345
|
-
|
|
11346
|
-
|
|
11347
|
-
|
|
11348
|
-
} catch (
|
|
11349
|
-
logError("
|
|
11350
|
-
|
|
11351
|
-
|
|
11352
|
-
|
|
11353
|
-
|
|
11354
|
-
|
|
11520
|
+
logDebug("Starting MCP server");
|
|
11521
|
+
const transport = new StdioServerTransport();
|
|
11522
|
+
await this.server.connect(transport);
|
|
11523
|
+
logInfo("MCP server is running on stdin/stdout");
|
|
11524
|
+
process.stdin.resume();
|
|
11525
|
+
await this.createShutdownPromise();
|
|
11526
|
+
await this.stop();
|
|
11527
|
+
} catch (error) {
|
|
11528
|
+
logError("Failed to start MCP server", { error });
|
|
11529
|
+
throw error;
|
|
11530
|
+
}
|
|
11531
|
+
}
|
|
11532
|
+
async stop() {
|
|
11533
|
+
logInfo("MCP server shutting down");
|
|
11534
|
+
}
|
|
11535
|
+
};
|
|
11536
|
+
|
|
11537
|
+
// src/mcp/services/PathValidation.ts
|
|
11538
|
+
import fs9 from "fs";
|
|
11539
|
+
import path11 from "path";
|
|
11540
|
+
var PathValidation = class {
|
|
11541
|
+
/**
|
|
11542
|
+
* Validates a path for MCP usage - combines security and existence checks
|
|
11543
|
+
*/
|
|
11544
|
+
async validatePath(inputPath) {
|
|
11545
|
+
logDebug("Validating MCP path", { inputPath });
|
|
11546
|
+
if (inputPath.includes("..")) {
|
|
11547
|
+
const error = `Path contains path traversal patterns: ${inputPath}`;
|
|
11548
|
+
logError(error);
|
|
11549
|
+
return { isValid: false, error };
|
|
11550
|
+
}
|
|
11551
|
+
const normalizedPath = path11.normalize(inputPath);
|
|
11552
|
+
if (normalizedPath.includes("..")) {
|
|
11553
|
+
const error = `Normalized path contains path traversal patterns: ${inputPath}`;
|
|
11554
|
+
logError(error);
|
|
11555
|
+
return { isValid: false, error };
|
|
11556
|
+
}
|
|
11557
|
+
const decodedPath = decodeURIComponent(inputPath);
|
|
11558
|
+
if (decodedPath.includes("..") || decodedPath !== inputPath) {
|
|
11559
|
+
const error = `Path contains encoded traversal attempts: ${inputPath}`;
|
|
11560
|
+
logError(error);
|
|
11561
|
+
return { isValid: false, error };
|
|
11562
|
+
}
|
|
11563
|
+
if (inputPath.includes("\0") || inputPath.includes("\0")) {
|
|
11564
|
+
const error = `Path contains dangerous characters: ${inputPath}`;
|
|
11565
|
+
logError(error);
|
|
11566
|
+
return { isValid: false, error };
|
|
11567
|
+
}
|
|
11568
|
+
logDebug("Path validation successful", { inputPath });
|
|
11569
|
+
logDebug("Checking path existence", { inputPath });
|
|
11570
|
+
try {
|
|
11571
|
+
await fs9.promises.access(inputPath);
|
|
11572
|
+
logDebug("Path exists and is accessible", { inputPath });
|
|
11573
|
+
return { isValid: true };
|
|
11574
|
+
} catch (error) {
|
|
11575
|
+
const errorMessage = `Path does not exist or is not accessible: ${inputPath}`;
|
|
11576
|
+
logError(errorMessage, { error });
|
|
11577
|
+
return { isValid: false, error: errorMessage };
|
|
11578
|
+
}
|
|
11579
|
+
}
|
|
11580
|
+
};
|
|
11581
|
+
|
|
11582
|
+
// src/mcp/services/FilePacking.ts
|
|
11583
|
+
import fs10 from "fs";
|
|
11584
|
+
import path12 from "path";
|
|
11585
|
+
import AdmZip2 from "adm-zip";
|
|
11586
|
+
var MAX_FILE_SIZE2 = 1024 * 1024 * 5;
|
|
11587
|
+
var FilePacking = class {
|
|
11588
|
+
async packFiles(sourceDirectoryPath, filesToPack) {
|
|
11589
|
+
logInfo(`FilePacking: packing files from ${sourceDirectoryPath}`);
|
|
11590
|
+
const zip = new AdmZip2();
|
|
11591
|
+
let packedFilesCount = 0;
|
|
11592
|
+
logInfo("FilePacking: compressing files");
|
|
11593
|
+
for (const filepath of filesToPack) {
|
|
11594
|
+
const absoluteFilepath = path12.join(sourceDirectoryPath, filepath);
|
|
11595
|
+
if (!FileUtils.shouldPackFile(absoluteFilepath, MAX_FILE_SIZE2)) {
|
|
11596
|
+
logInfo(
|
|
11597
|
+
`FilePacking: ignoring ${filepath} because it is excluded or invalid`
|
|
11598
|
+
);
|
|
11599
|
+
continue;
|
|
11600
|
+
}
|
|
11601
|
+
let data;
|
|
11602
|
+
try {
|
|
11603
|
+
data = fs10.readFileSync(absoluteFilepath);
|
|
11604
|
+
} catch (fsError) {
|
|
11605
|
+
logInfo(
|
|
11606
|
+
`FilePacking: failed to read ${filepath} from filesystem: ${fsError}`
|
|
11607
|
+
);
|
|
11608
|
+
continue;
|
|
11609
|
+
}
|
|
11610
|
+
zip.addFile(filepath, data);
|
|
11611
|
+
packedFilesCount++;
|
|
11355
11612
|
}
|
|
11613
|
+
const zipBuffer = zip.toBuffer();
|
|
11614
|
+
logInfo(
|
|
11615
|
+
`FilePacking: read ${packedFilesCount} source files. total size: ${zipBuffer.length} bytes`
|
|
11616
|
+
);
|
|
11617
|
+
logInfo("FilePacking: Files packed successfully");
|
|
11618
|
+
return zipBuffer;
|
|
11356
11619
|
}
|
|
11357
11620
|
};
|
|
11358
11621
|
|
|
@@ -11480,6 +11743,85 @@ ${fix2.patch || "No patch available"}
|
|
|
11480
11743
|
- If any patch fails, continue with the others and report issues at the end
|
|
11481
11744
|
`;
|
|
11482
11745
|
};
|
|
11746
|
+
var failedToConnectToApiPrompt = `# CONNECTION ERROR: FAILED TO REACH MOBB API
|
|
11747
|
+
|
|
11748
|
+
## ANALYSIS SUMMARY
|
|
11749
|
+
- **Status:** \u274C Failed
|
|
11750
|
+
- **Issue Type:** Connection Error
|
|
11751
|
+
- **Error Details:** Unable to establish connection to the Mobb API service
|
|
11752
|
+
|
|
11753
|
+
## TROUBLESHOOTING STEPS FOR THE USER
|
|
11754
|
+
|
|
11755
|
+
The Mobb security scanning service is currently not reachable. This may be due to:
|
|
11756
|
+
|
|
11757
|
+
1. **Missing or invalid authentication credentials**
|
|
11758
|
+
- Ensure the \`API_KEY\` environment variable is properly set with your valid Mobb authentication token
|
|
11759
|
+
- Example: \`export API_KEY=your_mobb_api_key_here\`
|
|
11760
|
+
|
|
11761
|
+
2. **Incorrect API endpoint configuration**
|
|
11762
|
+
- Check if the \`API_URL\` environment variable needs to be set to the correct Mobb service endpoint
|
|
11763
|
+
- Example: \`export API_URL=https://api.mobb.ai/graphql\`
|
|
11764
|
+
|
|
11765
|
+
3. **Network connectivity issues**
|
|
11766
|
+
- Verify your internet connection is working properly
|
|
11767
|
+
- Check if any firewall or proxy settings might be blocking the connection
|
|
11768
|
+
|
|
11769
|
+
4. **Service outage**
|
|
11770
|
+
- The Mobb service might be temporarily unavailable
|
|
11771
|
+
- Please try again later or check the Mobb status page
|
|
11772
|
+
|
|
11773
|
+
## NEXT STEPS
|
|
11774
|
+
|
|
11775
|
+
Please resolve the connection issue using the steps above and try running the security scan again.
|
|
11776
|
+
|
|
11777
|
+
For additional assistance, please:
|
|
11778
|
+
- Visit the Mobb documentation at https://docs.mobb.ai
|
|
11779
|
+
- Contact Mobb support at support@mobb.ai
|
|
11780
|
+
|
|
11781
|
+
`;
|
|
11782
|
+
var failedToAuthenticatePrompt = `# AUTHENTICATION ERROR: MOBB LOGIN REQUIRED
|
|
11783
|
+
|
|
11784
|
+
## ANALYSIS SUMMARY
|
|
11785
|
+
- **Status:** \u274C Failed
|
|
11786
|
+
- **Issue Type:** Authentication Error
|
|
11787
|
+
- **Error Details:** Unable to authenticate with the Mobb service
|
|
11788
|
+
|
|
11789
|
+
## AUTHENTICATION REQUIRED
|
|
11790
|
+
|
|
11791
|
+
The Mobb security scanning service requires authentication before it can analyze your code for vulnerabilities. You need to:
|
|
11792
|
+
|
|
11793
|
+
1. **Login and authorize access to Mobb**
|
|
11794
|
+
- A browser window should have opened to complete the authentication process
|
|
11795
|
+
- If no browser window opened, please run the command again
|
|
11796
|
+
|
|
11797
|
+
2. **Create a Mobb account if you don't have one**
|
|
11798
|
+
- If you don't already have a Mobb account, you'll need to sign up
|
|
11799
|
+
- Visit https://app.mobb.ai/auth/signup to create your free account
|
|
11800
|
+
- Use your work email for easier team collaboration
|
|
11801
|
+
|
|
11802
|
+
3. **Authorization flow**
|
|
11803
|
+
- After logging in, you'll be asked to authorize the CLI tool
|
|
11804
|
+
- This creates a secure token that allows the CLI to access Mobb services
|
|
11805
|
+
- You only need to do this once per device
|
|
11806
|
+
|
|
11807
|
+
## TROUBLESHOOTING
|
|
11808
|
+
|
|
11809
|
+
If you're experiencing issues with authentication:
|
|
11810
|
+
|
|
11811
|
+
- Ensure you have an active internet connection
|
|
11812
|
+
- Check that you can access https://app.mobb.ai in your browser
|
|
11813
|
+
- Try running the command again with the \`--debug\` flag for more detailed output
|
|
11814
|
+
- Make sure your browser isn't blocking pop-ups from the authentication window
|
|
11815
|
+
|
|
11816
|
+
## NEXT STEPS
|
|
11817
|
+
|
|
11818
|
+
Please complete the authentication process and try running the security scan again.
|
|
11819
|
+
|
|
11820
|
+
For additional assistance, please:
|
|
11821
|
+
- Visit the Mobb documentation at https://docs.mobb.ai/cli/authentication
|
|
11822
|
+
- Contact Mobb support at support@mobb.ai
|
|
11823
|
+
|
|
11824
|
+
`;
|
|
11483
11825
|
|
|
11484
11826
|
// src/mcp/tools/fixVulnerabilities/VulnerabilityFixService.ts
|
|
11485
11827
|
var VUL_REPORT_DIGEST_TIMEOUT_MS2 = 1e3 * 60 * 5;
|
|
@@ -11487,15 +11829,12 @@ var VulnerabilityFixService = class {
|
|
|
11487
11829
|
constructor() {
|
|
11488
11830
|
__publicField(this, "gqlClient");
|
|
11489
11831
|
__publicField(this, "filePacking");
|
|
11490
|
-
__publicField(this, "fileUpload");
|
|
11491
11832
|
this.filePacking = new FilePacking();
|
|
11492
|
-
this.fileUpload = new FileUpload();
|
|
11493
11833
|
}
|
|
11494
11834
|
async processVulnerabilities(fileList, repositoryPath) {
|
|
11495
11835
|
try {
|
|
11496
11836
|
this.validateFiles(fileList);
|
|
11497
|
-
|
|
11498
|
-
this.gqlClient = await this.initializeGqlClient(apiKey);
|
|
11837
|
+
this.gqlClient = await this.initializeGqlClient();
|
|
11499
11838
|
const repoUploadInfo = await this.initializeReport();
|
|
11500
11839
|
const zipBuffer = await this.packFiles(fileList, repositoryPath);
|
|
11501
11840
|
await this.uploadFiles(zipBuffer, repoUploadInfo);
|
|
@@ -11507,6 +11846,12 @@ var VulnerabilityFixService = class {
|
|
|
11507
11846
|
const fixes = await this.getReportFixes(repoUploadInfo.fixReportId);
|
|
11508
11847
|
return fixesPrompt(fixes);
|
|
11509
11848
|
} catch (error) {
|
|
11849
|
+
if (error instanceof ApiConnectionError || error instanceof CliLoginError) {
|
|
11850
|
+
return failedToConnectToApiPrompt;
|
|
11851
|
+
}
|
|
11852
|
+
if (error instanceof AuthenticationError || error instanceof FailedToGetApiTokenError) {
|
|
11853
|
+
return failedToAuthenticatePrompt;
|
|
11854
|
+
}
|
|
11510
11855
|
const message = error.message;
|
|
11511
11856
|
logError("Vulnerability processing failed", { error: message });
|
|
11512
11857
|
throw error;
|
|
@@ -11514,30 +11859,22 @@ var VulnerabilityFixService = class {
|
|
|
11514
11859
|
}
|
|
11515
11860
|
validateFiles(fileList) {
|
|
11516
11861
|
if (fileList.length === 0) {
|
|
11517
|
-
throw new
|
|
11518
|
-
}
|
|
11519
|
-
}
|
|
11520
|
-
validateApiKey() {
|
|
11521
|
-
const apiKey = process.env["API_KEY"];
|
|
11522
|
-
if (!apiKey) {
|
|
11523
|
-
throw new Error("API_KEY environment variable is not set");
|
|
11862
|
+
throw new NoFilesError();
|
|
11524
11863
|
}
|
|
11525
|
-
return apiKey;
|
|
11526
11864
|
}
|
|
11527
|
-
async initializeGqlClient(
|
|
11528
|
-
const gqlClient =
|
|
11529
|
-
apiKey,
|
|
11530
|
-
type: "apiKey"
|
|
11531
|
-
});
|
|
11865
|
+
async initializeGqlClient() {
|
|
11866
|
+
const gqlClient = await getMcpGQLClient();
|
|
11532
11867
|
const isConnected = await gqlClient.verifyConnection();
|
|
11533
11868
|
if (!isConnected) {
|
|
11534
|
-
throw new
|
|
11869
|
+
throw new ApiConnectionError(
|
|
11870
|
+
"Failed to connect to the API. Please check your API_KEY"
|
|
11871
|
+
);
|
|
11535
11872
|
}
|
|
11536
11873
|
return gqlClient;
|
|
11537
11874
|
}
|
|
11538
11875
|
async initializeReport() {
|
|
11539
11876
|
if (!this.gqlClient) {
|
|
11540
|
-
throw new
|
|
11877
|
+
throw new GqlClientError();
|
|
11541
11878
|
}
|
|
11542
11879
|
try {
|
|
11543
11880
|
const {
|
|
@@ -11547,7 +11884,9 @@ var VulnerabilityFixService = class {
|
|
|
11547
11884
|
return repoUploadInfo;
|
|
11548
11885
|
} catch (error) {
|
|
11549
11886
|
const message = error.message;
|
|
11550
|
-
throw new
|
|
11887
|
+
throw new ReportInitializationError(
|
|
11888
|
+
`Error initializing report: ${message}`
|
|
11889
|
+
);
|
|
11551
11890
|
}
|
|
11552
11891
|
}
|
|
11553
11892
|
async packFiles(fileList, repositoryPath) {
|
|
@@ -11560,15 +11899,15 @@ var VulnerabilityFixService = class {
|
|
|
11560
11899
|
return zipBuffer;
|
|
11561
11900
|
} catch (error) {
|
|
11562
11901
|
const message = error.message;
|
|
11563
|
-
throw new
|
|
11902
|
+
throw new FileProcessingError(`Error packing files: ${message}`);
|
|
11564
11903
|
}
|
|
11565
11904
|
}
|
|
11566
11905
|
async uploadFiles(zipBuffer, repoUploadInfo) {
|
|
11567
11906
|
if (!repoUploadInfo) {
|
|
11568
|
-
throw new
|
|
11907
|
+
throw new FileUploadError("Upload info is required");
|
|
11569
11908
|
}
|
|
11570
11909
|
try {
|
|
11571
|
-
await
|
|
11910
|
+
await uploadFile({
|
|
11572
11911
|
file: zipBuffer,
|
|
11573
11912
|
url: repoUploadInfo.url,
|
|
11574
11913
|
uploadFields: JSON.parse(repoUploadInfo.uploadFieldsJSON),
|
|
@@ -11577,12 +11916,14 @@ var VulnerabilityFixService = class {
|
|
|
11577
11916
|
logInfo("File uploaded successfully");
|
|
11578
11917
|
} catch (error) {
|
|
11579
11918
|
logError("File upload failed", { error: error.message });
|
|
11580
|
-
throw new
|
|
11919
|
+
throw new FileUploadError(
|
|
11920
|
+
`Failed to upload the file: ${error.message}`
|
|
11921
|
+
);
|
|
11581
11922
|
}
|
|
11582
11923
|
}
|
|
11583
11924
|
async getProjectId() {
|
|
11584
11925
|
if (!this.gqlClient) {
|
|
11585
|
-
throw new
|
|
11926
|
+
throw new GqlClientError();
|
|
11586
11927
|
}
|
|
11587
11928
|
const projectId = await this.gqlClient.getProjectId();
|
|
11588
11929
|
logInfo("Project ID retrieved", { projectId });
|
|
@@ -11590,7 +11931,7 @@ var VulnerabilityFixService = class {
|
|
|
11590
11931
|
}
|
|
11591
11932
|
async runScan(params) {
|
|
11592
11933
|
if (!this.gqlClient) {
|
|
11593
|
-
throw new
|
|
11934
|
+
throw new GqlClientError();
|
|
11594
11935
|
}
|
|
11595
11936
|
const { fixReportId, projectId } = params;
|
|
11596
11937
|
logInfo("Starting scan", { fixReportId, projectId });
|
|
@@ -11609,7 +11950,7 @@ var VulnerabilityFixService = class {
|
|
|
11609
11950
|
logError("Vulnerability report submission failed", {
|
|
11610
11951
|
response: submitRes
|
|
11611
11952
|
});
|
|
11612
|
-
throw new
|
|
11953
|
+
throw new ScanError("\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed");
|
|
11613
11954
|
}
|
|
11614
11955
|
logInfo("Vulnerability report submitted successfully", {
|
|
11615
11956
|
analysisId: submitRes.submitVulnerabilityReport.fixReportId
|
|
@@ -11628,7 +11969,7 @@ var VulnerabilityFixService = class {
|
|
|
11628
11969
|
}
|
|
11629
11970
|
async getReportFixes(fixReportId) {
|
|
11630
11971
|
if (!this.gqlClient) {
|
|
11631
|
-
throw new
|
|
11972
|
+
throw new GqlClientError();
|
|
11632
11973
|
}
|
|
11633
11974
|
const fixes = await this.gqlClient.getReportFixes(fixReportId);
|
|
11634
11975
|
logInfo("Fixes retrieved", { fixCount: fixes.length });
|
|
@@ -11665,18 +12006,48 @@ var FixVulnerabilitiesTool = class {
|
|
|
11665
12006
|
`Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
|
|
11666
12007
|
);
|
|
11667
12008
|
}
|
|
11668
|
-
const gitService = new GitService(args.path);
|
|
12009
|
+
const gitService = new GitService(args.path, log);
|
|
11669
12010
|
const gitValidation = await gitService.validateRepository();
|
|
12011
|
+
let files = [];
|
|
11670
12012
|
if (!gitValidation.isValid) {
|
|
11671
|
-
|
|
12013
|
+
logDebug(
|
|
12014
|
+
"Git repository validation failed, using all files in the repository",
|
|
12015
|
+
{
|
|
12016
|
+
path: args.path
|
|
12017
|
+
}
|
|
12018
|
+
);
|
|
12019
|
+
files = FileUtils.getLastChangedFiles(args.path);
|
|
12020
|
+
logDebug("Found files in the repository", {
|
|
12021
|
+
files,
|
|
12022
|
+
fileCount: files.length
|
|
12023
|
+
});
|
|
12024
|
+
} else {
|
|
12025
|
+
const gitResult = await gitService.getChangedFiles();
|
|
12026
|
+
files = gitResult.files;
|
|
12027
|
+
if (files.length === 0) {
|
|
12028
|
+
const recentResult = await gitService.getRecentlyChangedFiles();
|
|
12029
|
+
files = recentResult.files;
|
|
12030
|
+
logDebug(
|
|
12031
|
+
"No changes found, using recently changed files from git history",
|
|
12032
|
+
{
|
|
12033
|
+
files,
|
|
12034
|
+
fileCount: files.length,
|
|
12035
|
+
commitsChecked: recentResult.commitCount
|
|
12036
|
+
}
|
|
12037
|
+
);
|
|
12038
|
+
} else {
|
|
12039
|
+
logDebug("Found changed files in the git repository", {
|
|
12040
|
+
files,
|
|
12041
|
+
fileCount: files.length
|
|
12042
|
+
});
|
|
12043
|
+
}
|
|
11672
12044
|
}
|
|
11673
|
-
|
|
11674
|
-
if (gitResult.files.length === 0) {
|
|
12045
|
+
if (files.length === 0) {
|
|
11675
12046
|
return {
|
|
11676
12047
|
content: [
|
|
11677
12048
|
{
|
|
11678
12049
|
type: "text",
|
|
11679
|
-
text: "No changed files found in the
|
|
12050
|
+
text: "No changed files found in the repository. The vulnerability scanner analyzes modified, added, or staged files. Make some changes to your code and try again."
|
|
11680
12051
|
}
|
|
11681
12052
|
]
|
|
11682
12053
|
};
|
|
@@ -11684,7 +12055,7 @@ var FixVulnerabilitiesTool = class {
|
|
|
11684
12055
|
try {
|
|
11685
12056
|
const vulnerabilityFixService = new VulnerabilityFixService();
|
|
11686
12057
|
const fixResult = await vulnerabilityFixService.processVulnerabilities(
|
|
11687
|
-
|
|
12058
|
+
files,
|
|
11688
12059
|
args.path
|
|
11689
12060
|
);
|
|
11690
12061
|
const result = {
|
|
@@ -11697,7 +12068,7 @@ var FixVulnerabilitiesTool = class {
|
|
|
11697
12068
|
};
|
|
11698
12069
|
logInfo("Tool execution completed successfully", {
|
|
11699
12070
|
resultLength: fixResult.length,
|
|
11700
|
-
fileCount:
|
|
12071
|
+
fileCount: files.length,
|
|
11701
12072
|
result
|
|
11702
12073
|
});
|
|
11703
12074
|
return result;
|
|
@@ -11770,7 +12141,7 @@ var mcpHandler = async (_args) => {
|
|
|
11770
12141
|
};
|
|
11771
12142
|
|
|
11772
12143
|
// src/args/commands/review.ts
|
|
11773
|
-
import
|
|
12144
|
+
import fs11 from "fs";
|
|
11774
12145
|
import chalk9 from "chalk";
|
|
11775
12146
|
function reviewBuilder(yargs2) {
|
|
11776
12147
|
return yargs2.option("f", {
|
|
@@ -11807,7 +12178,7 @@ function reviewBuilder(yargs2) {
|
|
|
11807
12178
|
).help();
|
|
11808
12179
|
}
|
|
11809
12180
|
function validateReviewOptions(argv) {
|
|
11810
|
-
if (!
|
|
12181
|
+
if (!fs11.existsSync(argv.f)) {
|
|
11811
12182
|
throw new CliError(`
|
|
11812
12183
|
Can't access ${chalk9.bold(argv.f)}`);
|
|
11813
12184
|
}
|
|
@@ -11945,13 +12316,13 @@ var parseArgs = async (args) => {
|
|
|
11945
12316
|
};
|
|
11946
12317
|
|
|
11947
12318
|
// src/index.ts
|
|
11948
|
-
var
|
|
12319
|
+
var debug19 = Debug20("mobbdev:index");
|
|
11949
12320
|
async function run() {
|
|
11950
12321
|
return parseArgs(hideBin(process.argv));
|
|
11951
12322
|
}
|
|
11952
12323
|
(async () => {
|
|
11953
12324
|
try {
|
|
11954
|
-
|
|
12325
|
+
debug19("Bugsy CLI v%s running...", packageJson.version);
|
|
11955
12326
|
await run();
|
|
11956
12327
|
process.exit(0);
|
|
11957
12328
|
} catch (err) {
|