mobbdev 1.1.5 → 1.1.7

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.
@@ -7,6 +7,16 @@ declare enum AiBlameInferenceType {
7
7
  TabAutocomplete = "TAB_AUTOCOMPLETE"
8
8
  }
9
9
 
10
+ type SanitizationCounts = {
11
+ pii: {
12
+ total: number;
13
+ high: number;
14
+ medium: number;
15
+ low: number;
16
+ };
17
+ secrets: number;
18
+ };
19
+
10
20
  declare const PromptItemZ: z.ZodObject<{
11
21
  type: z.ZodEnum<["USER_PROMPT", "AI_RESPONSE", "TOOL_EXECUTION", "AI_THINKING"]>;
12
22
  attachedFiles: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -185,6 +195,12 @@ type UploadAiBlameOptions = {
185
195
  'blame-type'?: AiBlameInferenceType[];
186
196
  };
187
197
  declare function uploadAiBlameBuilder(args: Yargs.Argv<unknown>): Yargs.Argv<UploadAiBlameOptions>;
198
+ type UploadAiBlameResult = {
199
+ promptsCounts: SanitizationCounts;
200
+ inferenceCounts: SanitizationCounts;
201
+ promptsUUID?: string;
202
+ inferenceUUID?: string;
203
+ };
188
204
  declare function uploadAiBlameHandlerFromExtension(args: {
189
205
  prompts: PromptItemArray;
190
206
  inference: string;
@@ -192,7 +208,7 @@ declare function uploadAiBlameHandlerFromExtension(args: {
192
208
  tool: string;
193
209
  responseTime: string;
194
210
  blameType?: AiBlameInferenceType;
195
- }): Promise<void>;
211
+ }): Promise<UploadAiBlameResult>;
196
212
  declare function uploadAiBlameHandler(args: UploadAiBlameOptions, exitOnError?: boolean): Promise<void>;
197
213
 
198
- export { type PromptItem, type PromptItemArray, type UploadAiBlameOptions, uploadAiBlameBuilder, uploadAiBlameHandler, uploadAiBlameHandlerFromExtension };
214
+ export { type PromptItem, type PromptItemArray, type UploadAiBlameOptions, type UploadAiBlameResult, uploadAiBlameBuilder, uploadAiBlameHandler, uploadAiBlameHandlerFromExtension };
@@ -5128,6 +5128,143 @@ async function uploadFile({
5128
5128
  logInfo(`FileUpload: upload file done`);
5129
5129
  }
5130
5130
 
5131
+ // src/utils/sanitize-sensitive-data.ts
5132
+ import { OpenRedaction } from "@openredaction/openredaction";
5133
+ import { spawn } from "child_process";
5134
+ import { installGitleaks } from "gitleaks-secret-scanner/lib/installer.js";
5135
+ var openRedaction = new OpenRedaction();
5136
+ var gitleaksBinaryPath = null;
5137
+ async function initializeGitleaks() {
5138
+ try {
5139
+ gitleaksBinaryPath = await installGitleaks({ version: "8.27.2" });
5140
+ return gitleaksBinaryPath;
5141
+ } catch {
5142
+ return null;
5143
+ }
5144
+ }
5145
+ var gitleaksInitPromise = initializeGitleaks();
5146
+ async function detectSecretsWithGitleaks(text) {
5147
+ const secrets = /* @__PURE__ */ new Set();
5148
+ const binaryPath = gitleaksBinaryPath || await gitleaksInitPromise;
5149
+ if (!binaryPath) {
5150
+ return secrets;
5151
+ }
5152
+ return new Promise((resolve) => {
5153
+ const gitleaks = spawn(
5154
+ binaryPath,
5155
+ [
5156
+ "detect",
5157
+ "--pipe",
5158
+ "--no-banner",
5159
+ "--exit-code",
5160
+ "0",
5161
+ "--report-format",
5162
+ "json"
5163
+ ],
5164
+ {
5165
+ stdio: ["pipe", "pipe", "ignore"]
5166
+ }
5167
+ );
5168
+ let output = "";
5169
+ gitleaks.stdout.on("data", (data) => {
5170
+ output += data.toString();
5171
+ });
5172
+ gitleaks.on("close", () => {
5173
+ try {
5174
+ const findings = JSON.parse(output);
5175
+ if (Array.isArray(findings)) {
5176
+ for (const finding of findings) {
5177
+ if (finding.Secret) {
5178
+ secrets.add(finding.Secret);
5179
+ }
5180
+ }
5181
+ }
5182
+ } catch {
5183
+ }
5184
+ resolve(secrets);
5185
+ });
5186
+ gitleaks.on("error", () => {
5187
+ resolve(secrets);
5188
+ });
5189
+ gitleaks.stdin.write(text);
5190
+ gitleaks.stdin.end();
5191
+ });
5192
+ }
5193
+ function maskString(str, showStart = 2, showEnd = 2) {
5194
+ if (str.length <= showStart + showEnd) {
5195
+ return "*".repeat(str.length);
5196
+ }
5197
+ return str.slice(0, showStart) + "*".repeat(str.length - showStart - showEnd) + str.slice(-showEnd);
5198
+ }
5199
+ async function sanitizeDataWithCounts(obj) {
5200
+ const counts = {
5201
+ pii: { total: 0, high: 0, medium: 0, low: 0 },
5202
+ secrets: 0
5203
+ };
5204
+ const sanitizeString = async (str) => {
5205
+ let result = str;
5206
+ const piiDetections = openRedaction.scan(str);
5207
+ if (piiDetections && piiDetections.total > 0) {
5208
+ const allDetections = [
5209
+ ...piiDetections.high,
5210
+ ...piiDetections.medium,
5211
+ ...piiDetections.low
5212
+ ];
5213
+ const filteredDetections = allDetections.filter((detection) => {
5214
+ if (detection.type === "INSTAGRAM_USERNAME") {
5215
+ return false;
5216
+ }
5217
+ if (detection.value.length < 3 && detection.severity !== "high") {
5218
+ return false;
5219
+ }
5220
+ return true;
5221
+ });
5222
+ for (const detection of filteredDetections) {
5223
+ counts.pii.total++;
5224
+ if (detection.severity === "high") counts.pii.high++;
5225
+ else if (detection.severity === "medium") counts.pii.medium++;
5226
+ else if (detection.severity === "low") counts.pii.low++;
5227
+ const masked = maskString(detection.value);
5228
+ result = result.replaceAll(detection.value, masked);
5229
+ }
5230
+ }
5231
+ const secrets = await detectSecretsWithGitleaks(result);
5232
+ counts.secrets += secrets.size;
5233
+ for (const secret of secrets) {
5234
+ const masked = maskString(secret);
5235
+ result = result.replaceAll(secret, masked);
5236
+ }
5237
+ return result;
5238
+ };
5239
+ const sanitizeRecursive = async (data) => {
5240
+ if (typeof data === "string") {
5241
+ return sanitizeString(data);
5242
+ } else if (Array.isArray(data)) {
5243
+ return Promise.all(data.map((item) => sanitizeRecursive(item)));
5244
+ } else if (data instanceof Error) {
5245
+ return data;
5246
+ } else if (data instanceof Date) {
5247
+ return data;
5248
+ } else if (typeof data === "object" && data !== null) {
5249
+ const sanitized = {};
5250
+ const record = data;
5251
+ for (const key in record) {
5252
+ if (Object.prototype.hasOwnProperty.call(record, key)) {
5253
+ sanitized[key] = await sanitizeRecursive(record[key]);
5254
+ }
5255
+ }
5256
+ return sanitized;
5257
+ }
5258
+ return data;
5259
+ };
5260
+ const sanitizedData = await sanitizeRecursive(obj);
5261
+ return { sanitizedData, counts };
5262
+ }
5263
+ async function sanitizeData(obj) {
5264
+ const result = await sanitizeDataWithCounts(obj);
5265
+ return result.sanitizedData;
5266
+ }
5267
+
5131
5268
  // src/args/commands/upload_ai_blame.ts
5132
5269
  var PromptItemZ = z26.object({
5133
5270
  type: z26.enum(["USER_PROMPT", "AI_RESPONSE", "TOOL_EXECUTION", "AI_THINKING"]),
@@ -5197,15 +5334,32 @@ async function uploadAiBlameHandlerFromExtension(args) {
5197
5334
  aiResponseAt: [],
5198
5335
  blameType: []
5199
5336
  };
5337
+ let promptsCounts;
5338
+ let inferenceCounts;
5339
+ let promptsUUID;
5340
+ let inferenceUUID;
5200
5341
  await withFile(async (promptFile) => {
5342
+ const promptsResult = await sanitizeDataWithCounts(args.prompts);
5343
+ promptsCounts = promptsResult.counts;
5344
+ promptsUUID = path6.basename(promptFile.path, path6.extname(promptFile.path));
5201
5345
  await fsPromises2.writeFile(
5202
5346
  promptFile.path,
5203
- JSON.stringify(args.prompts, null, 2),
5347
+ JSON.stringify(promptsResult.sanitizedData, null, 2),
5204
5348
  "utf-8"
5205
5349
  );
5206
5350
  uploadArgs.prompt.push(promptFile.path);
5207
5351
  await withFile(async (inferenceFile) => {
5208
- await fsPromises2.writeFile(inferenceFile.path, args.inference, "utf-8");
5352
+ const inferenceResult = await sanitizeDataWithCounts(args.inference);
5353
+ inferenceCounts = inferenceResult.counts;
5354
+ inferenceUUID = path6.basename(
5355
+ inferenceFile.path,
5356
+ path6.extname(inferenceFile.path)
5357
+ );
5358
+ await fsPromises2.writeFile(
5359
+ inferenceFile.path,
5360
+ inferenceResult.sanitizedData,
5361
+ "utf-8"
5362
+ );
5209
5363
  uploadArgs.inference.push(inferenceFile.path);
5210
5364
  uploadArgs.model.push(args.model);
5211
5365
  uploadArgs.toolName.push(args.tool);
@@ -5214,6 +5368,12 @@ async function uploadAiBlameHandlerFromExtension(args) {
5214
5368
  await uploadAiBlameHandler(uploadArgs, false);
5215
5369
  });
5216
5370
  });
5371
+ return {
5372
+ promptsCounts,
5373
+ inferenceCounts,
5374
+ promptsUUID,
5375
+ inferenceUUID
5376
+ };
5217
5377
  }
5218
5378
  async function uploadAiBlameHandler(args, exitOnError = true) {
5219
5379
  const prompts = args.prompt || [];
@@ -5260,8 +5420,11 @@ async function uploadAiBlameHandler(args, exitOnError = true) {
5260
5420
  const authenticatedClient = await getAuthenticatedGQLClient({
5261
5421
  isSkipPrompts: true
5262
5422
  });
5263
- const initRes = await authenticatedClient.uploadAIBlameInferencesInitRaw({
5423
+ const sanitizedSessions = await sanitizeData(
5264
5424
  sessions
5425
+ );
5426
+ const initRes = await authenticatedClient.uploadAIBlameInferencesInitRaw({
5427
+ sessions: sanitizedSessions
5265
5428
  });
5266
5429
  const uploadSessions = initRes.uploadAIBlameInferencesInit?.uploadSessions ?? [];
5267
5430
  if (uploadSessions.length !== sessions.length) {
@@ -5305,8 +5468,11 @@ async function uploadAiBlameHandler(args, exitOnError = true) {
5305
5468
  blameType: s.blameType
5306
5469
  };
5307
5470
  });
5471
+ const sanitizedFinalizeSessions = await sanitizeData(
5472
+ finalizeSessions
5473
+ );
5308
5474
  const finRes = await authenticatedClient.finalizeAIBlameInferencesUploadRaw({
5309
- sessions: finalizeSessions
5475
+ sessions: sanitizedFinalizeSessions
5310
5476
  });
5311
5477
  const status = finRes?.finalizeAIBlameInferencesUpload?.status;
5312
5478
  if (status !== "OK") {
package/dist/index.mjs CHANGED
@@ -11705,7 +11705,16 @@ async function pack(srcDirPath, vulnFiles, isIncludeAllFiles = false) {
11705
11705
  debug15("ignoring %s because the size is > 5MB", filepath);
11706
11706
  continue;
11707
11707
  }
11708
- const data = git ? await git.showBuffer([`HEAD:./${filepath}`]) : fs8.readFileSync(absFilepath);
11708
+ let data;
11709
+ if (git) {
11710
+ try {
11711
+ data = await git.showBuffer([`HEAD:./${filepath}`]);
11712
+ } catch {
11713
+ data = fs8.readFileSync(absFilepath);
11714
+ }
11715
+ } else {
11716
+ data = fs8.readFileSync(absFilepath);
11717
+ }
11709
11718
  if (isBinary2(null, data)) {
11710
11719
  debug15("ignoring %s because is seems to be a binary file", filepath);
11711
11720
  continue;
@@ -13037,6 +13046,145 @@ import path11 from "path";
13037
13046
  import chalk9 from "chalk";
13038
13047
  import { withFile } from "tmp-promise";
13039
13048
  import z31 from "zod";
13049
+
13050
+ // src/utils/sanitize-sensitive-data.ts
13051
+ import { OpenRedaction } from "@openredaction/openredaction";
13052
+ import { spawn } from "child_process";
13053
+ import { installGitleaks } from "gitleaks-secret-scanner/lib/installer.js";
13054
+ var openRedaction = new OpenRedaction();
13055
+ var gitleaksBinaryPath = null;
13056
+ async function initializeGitleaks() {
13057
+ try {
13058
+ gitleaksBinaryPath = await installGitleaks({ version: "8.27.2" });
13059
+ return gitleaksBinaryPath;
13060
+ } catch {
13061
+ return null;
13062
+ }
13063
+ }
13064
+ var gitleaksInitPromise = initializeGitleaks();
13065
+ async function detectSecretsWithGitleaks(text) {
13066
+ const secrets = /* @__PURE__ */ new Set();
13067
+ const binaryPath = gitleaksBinaryPath || await gitleaksInitPromise;
13068
+ if (!binaryPath) {
13069
+ return secrets;
13070
+ }
13071
+ return new Promise((resolve) => {
13072
+ const gitleaks = spawn(
13073
+ binaryPath,
13074
+ [
13075
+ "detect",
13076
+ "--pipe",
13077
+ "--no-banner",
13078
+ "--exit-code",
13079
+ "0",
13080
+ "--report-format",
13081
+ "json"
13082
+ ],
13083
+ {
13084
+ stdio: ["pipe", "pipe", "ignore"]
13085
+ }
13086
+ );
13087
+ let output = "";
13088
+ gitleaks.stdout.on("data", (data) => {
13089
+ output += data.toString();
13090
+ });
13091
+ gitleaks.on("close", () => {
13092
+ try {
13093
+ const findings = JSON.parse(output);
13094
+ if (Array.isArray(findings)) {
13095
+ for (const finding of findings) {
13096
+ if (finding.Secret) {
13097
+ secrets.add(finding.Secret);
13098
+ }
13099
+ }
13100
+ }
13101
+ } catch {
13102
+ }
13103
+ resolve(secrets);
13104
+ });
13105
+ gitleaks.on("error", () => {
13106
+ resolve(secrets);
13107
+ });
13108
+ gitleaks.stdin.write(text);
13109
+ gitleaks.stdin.end();
13110
+ });
13111
+ }
13112
+ function maskString(str, showStart = 2, showEnd = 2) {
13113
+ if (str.length <= showStart + showEnd) {
13114
+ return "*".repeat(str.length);
13115
+ }
13116
+ return str.slice(0, showStart) + "*".repeat(str.length - showStart - showEnd) + str.slice(-showEnd);
13117
+ }
13118
+ async function sanitizeDataWithCounts(obj) {
13119
+ const counts = {
13120
+ pii: { total: 0, high: 0, medium: 0, low: 0 },
13121
+ secrets: 0
13122
+ };
13123
+ const sanitizeString = async (str) => {
13124
+ let result = str;
13125
+ const piiDetections = openRedaction.scan(str);
13126
+ if (piiDetections && piiDetections.total > 0) {
13127
+ const allDetections = [
13128
+ ...piiDetections.high,
13129
+ ...piiDetections.medium,
13130
+ ...piiDetections.low
13131
+ ];
13132
+ const filteredDetections = allDetections.filter((detection) => {
13133
+ if (detection.type === "INSTAGRAM_USERNAME") {
13134
+ return false;
13135
+ }
13136
+ if (detection.value.length < 3 && detection.severity !== "high") {
13137
+ return false;
13138
+ }
13139
+ return true;
13140
+ });
13141
+ for (const detection of filteredDetections) {
13142
+ counts.pii.total++;
13143
+ if (detection.severity === "high") counts.pii.high++;
13144
+ else if (detection.severity === "medium") counts.pii.medium++;
13145
+ else if (detection.severity === "low") counts.pii.low++;
13146
+ const masked = maskString(detection.value);
13147
+ result = result.replaceAll(detection.value, masked);
13148
+ }
13149
+ }
13150
+ const secrets = await detectSecretsWithGitleaks(result);
13151
+ counts.secrets += secrets.size;
13152
+ for (const secret of secrets) {
13153
+ const masked = maskString(secret);
13154
+ result = result.replaceAll(secret, masked);
13155
+ }
13156
+ return result;
13157
+ };
13158
+ const sanitizeRecursive = async (data) => {
13159
+ if (typeof data === "string") {
13160
+ return sanitizeString(data);
13161
+ } else if (Array.isArray(data)) {
13162
+ return Promise.all(data.map((item) => sanitizeRecursive(item)));
13163
+ } else if (data instanceof Error) {
13164
+ return data;
13165
+ } else if (data instanceof Date) {
13166
+ return data;
13167
+ } else if (typeof data === "object" && data !== null) {
13168
+ const sanitized = {};
13169
+ const record = data;
13170
+ for (const key in record) {
13171
+ if (Object.prototype.hasOwnProperty.call(record, key)) {
13172
+ sanitized[key] = await sanitizeRecursive(record[key]);
13173
+ }
13174
+ }
13175
+ return sanitized;
13176
+ }
13177
+ return data;
13178
+ };
13179
+ const sanitizedData = await sanitizeRecursive(obj);
13180
+ return { sanitizedData, counts };
13181
+ }
13182
+ async function sanitizeData(obj) {
13183
+ const result = await sanitizeDataWithCounts(obj);
13184
+ return result.sanitizedData;
13185
+ }
13186
+
13187
+ // src/args/commands/upload_ai_blame.ts
13040
13188
  var PromptItemZ = z31.object({
13041
13189
  type: z31.enum(["USER_PROMPT", "AI_RESPONSE", "TOOL_EXECUTION", "AI_THINKING"]),
13042
13190
  attachedFiles: z31.array(
@@ -13105,15 +13253,32 @@ async function uploadAiBlameHandlerFromExtension(args) {
13105
13253
  aiResponseAt: [],
13106
13254
  blameType: []
13107
13255
  };
13256
+ let promptsCounts;
13257
+ let inferenceCounts;
13258
+ let promptsUUID;
13259
+ let inferenceUUID;
13108
13260
  await withFile(async (promptFile) => {
13261
+ const promptsResult = await sanitizeDataWithCounts(args.prompts);
13262
+ promptsCounts = promptsResult.counts;
13263
+ promptsUUID = path11.basename(promptFile.path, path11.extname(promptFile.path));
13109
13264
  await fsPromises3.writeFile(
13110
13265
  promptFile.path,
13111
- JSON.stringify(args.prompts, null, 2),
13266
+ JSON.stringify(promptsResult.sanitizedData, null, 2),
13112
13267
  "utf-8"
13113
13268
  );
13114
13269
  uploadArgs.prompt.push(promptFile.path);
13115
13270
  await withFile(async (inferenceFile) => {
13116
- await fsPromises3.writeFile(inferenceFile.path, args.inference, "utf-8");
13271
+ const inferenceResult = await sanitizeDataWithCounts(args.inference);
13272
+ inferenceCounts = inferenceResult.counts;
13273
+ inferenceUUID = path11.basename(
13274
+ inferenceFile.path,
13275
+ path11.extname(inferenceFile.path)
13276
+ );
13277
+ await fsPromises3.writeFile(
13278
+ inferenceFile.path,
13279
+ inferenceResult.sanitizedData,
13280
+ "utf-8"
13281
+ );
13117
13282
  uploadArgs.inference.push(inferenceFile.path);
13118
13283
  uploadArgs.model.push(args.model);
13119
13284
  uploadArgs.toolName.push(args.tool);
@@ -13122,6 +13287,12 @@ async function uploadAiBlameHandlerFromExtension(args) {
13122
13287
  await uploadAiBlameHandler(uploadArgs, false);
13123
13288
  });
13124
13289
  });
13290
+ return {
13291
+ promptsCounts,
13292
+ inferenceCounts,
13293
+ promptsUUID,
13294
+ inferenceUUID
13295
+ };
13125
13296
  }
13126
13297
  async function uploadAiBlameHandler(args, exitOnError = true) {
13127
13298
  const prompts = args.prompt || [];
@@ -13168,8 +13339,11 @@ async function uploadAiBlameHandler(args, exitOnError = true) {
13168
13339
  const authenticatedClient = await getAuthenticatedGQLClient({
13169
13340
  isSkipPrompts: true
13170
13341
  });
13171
- const initRes = await authenticatedClient.uploadAIBlameInferencesInitRaw({
13342
+ const sanitizedSessions = await sanitizeData(
13172
13343
  sessions
13344
+ );
13345
+ const initRes = await authenticatedClient.uploadAIBlameInferencesInitRaw({
13346
+ sessions: sanitizedSessions
13173
13347
  });
13174
13348
  const uploadSessions = initRes.uploadAIBlameInferencesInit?.uploadSessions ?? [];
13175
13349
  if (uploadSessions.length !== sessions.length) {
@@ -13213,8 +13387,11 @@ async function uploadAiBlameHandler(args, exitOnError = true) {
13213
13387
  blameType: s.blameType
13214
13388
  };
13215
13389
  });
13390
+ const sanitizedFinalizeSessions = await sanitizeData(
13391
+ finalizeSessions
13392
+ );
13216
13393
  const finRes = await authenticatedClient.finalizeAIBlameInferencesUploadRaw({
13217
- sessions: finalizeSessions
13394
+ sessions: sanitizedFinalizeSessions
13218
13395
  });
13219
13396
  const status = finRes?.finalizeAIBlameInferencesUpload?.status;
13220
13397
  if (status !== "OK") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobbdev",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "description": "Automated secure code remediation tool",
5
5
  "repository": "git+https://github.com/mobb-dev/bugsy.git",
6
6
  "main": "dist/index.mjs",
@@ -56,6 +56,7 @@
56
56
  "@modelcontextprotocol/sdk": "1.21.1",
57
57
  "@octokit/core": "5.2.0",
58
58
  "@octokit/request-error": "5.1.1",
59
+ "@openredaction/openredaction": "1.0.4",
59
60
  "adm-zip": "0.5.16",
60
61
  "axios": "1.13.2",
61
62
  "azure-devops-node-api": "15.1.1",
@@ -67,6 +68,7 @@
67
68
  "debug": "4.4.3",
68
69
  "dotenv": "16.6.1",
69
70
  "extract-zip": "2.0.1",
71
+ "gitleaks-secret-scanner": "1.2.2",
70
72
  "globby": "14.1.0",
71
73
  "graphql": "16.12.0",
72
74
  "graphql-request": "6.1.0",