mobbdev 1.1.6 → 1.1.8

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<{
@@ -41,26 +51,18 @@ declare const PromptItemZ: z.ZodObject<{
41
51
  name: string;
42
52
  parameters: string;
43
53
  result: string;
44
- accepted?: boolean | undefined;
45
54
  rawArguments?: string | undefined;
55
+ accepted?: boolean | undefined;
46
56
  }, {
47
57
  name: string;
48
58
  parameters: string;
49
59
  result: string;
50
- accepted?: boolean | undefined;
51
60
  rawArguments?: string | undefined;
61
+ accepted?: boolean | undefined;
52
62
  }>>;
53
63
  }, "strip", z.ZodTypeAny, {
54
64
  type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
55
- tool?: {
56
- name: string;
57
- parameters: string;
58
- result: string;
59
- accepted?: boolean | undefined;
60
- rawArguments?: string | undefined;
61
- } | undefined;
62
65
  date?: Date | undefined;
63
- text?: string | undefined;
64
66
  attachedFiles?: {
65
67
  relativePath: string;
66
68
  startLine?: number | undefined;
@@ -69,17 +71,17 @@ declare const PromptItemZ: z.ZodObject<{
69
71
  inputCount: number;
70
72
  outputCount: number;
71
73
  } | undefined;
72
- }, {
73
- type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
74
+ text?: string | undefined;
74
75
  tool?: {
75
76
  name: string;
76
77
  parameters: string;
77
78
  result: string;
78
- accepted?: boolean | undefined;
79
79
  rawArguments?: string | undefined;
80
+ accepted?: boolean | undefined;
80
81
  } | undefined;
82
+ }, {
83
+ type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
81
84
  date?: Date | undefined;
82
- text?: string | undefined;
83
85
  attachedFiles?: {
84
86
  relativePath: string;
85
87
  startLine?: number | undefined;
@@ -88,6 +90,14 @@ declare const PromptItemZ: z.ZodObject<{
88
90
  inputCount: number;
89
91
  outputCount: number;
90
92
  } | undefined;
93
+ text?: string | undefined;
94
+ tool?: {
95
+ name: string;
96
+ parameters: string;
97
+ result: string;
98
+ rawArguments?: string | undefined;
99
+ accepted?: boolean | undefined;
100
+ } | undefined;
91
101
  }>;
92
102
  type PromptItem = z.infer<typeof PromptItemZ>;
93
103
  declare const PromptItemArrayZ: z.ZodArray<z.ZodObject<{
@@ -124,26 +134,18 @@ declare const PromptItemArrayZ: z.ZodArray<z.ZodObject<{
124
134
  name: string;
125
135
  parameters: string;
126
136
  result: string;
127
- accepted?: boolean | undefined;
128
137
  rawArguments?: string | undefined;
138
+ accepted?: boolean | undefined;
129
139
  }, {
130
140
  name: string;
131
141
  parameters: string;
132
142
  result: string;
133
- accepted?: boolean | undefined;
134
143
  rawArguments?: string | undefined;
144
+ accepted?: boolean | undefined;
135
145
  }>>;
136
146
  }, "strip", z.ZodTypeAny, {
137
147
  type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
138
- tool?: {
139
- name: string;
140
- parameters: string;
141
- result: string;
142
- accepted?: boolean | undefined;
143
- rawArguments?: string | undefined;
144
- } | undefined;
145
148
  date?: Date | undefined;
146
- text?: string | undefined;
147
149
  attachedFiles?: {
148
150
  relativePath: string;
149
151
  startLine?: number | undefined;
@@ -152,17 +154,17 @@ declare const PromptItemArrayZ: z.ZodArray<z.ZodObject<{
152
154
  inputCount: number;
153
155
  outputCount: number;
154
156
  } | undefined;
155
- }, {
156
- type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
157
+ text?: string | undefined;
157
158
  tool?: {
158
159
  name: string;
159
160
  parameters: string;
160
161
  result: string;
161
- accepted?: boolean | undefined;
162
162
  rawArguments?: string | undefined;
163
+ accepted?: boolean | undefined;
163
164
  } | undefined;
165
+ }, {
166
+ type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
164
167
  date?: Date | undefined;
165
- text?: string | undefined;
166
168
  attachedFiles?: {
167
169
  relativePath: string;
168
170
  startLine?: number | undefined;
@@ -171,6 +173,14 @@ declare const PromptItemArrayZ: z.ZodArray<z.ZodObject<{
171
173
  inputCount: number;
172
174
  outputCount: number;
173
175
  } | undefined;
176
+ text?: string | undefined;
177
+ tool?: {
178
+ name: string;
179
+ parameters: string;
180
+ result: string;
181
+ rawArguments?: string | undefined;
182
+ accepted?: boolean | undefined;
183
+ } | undefined;
174
184
  }>, "many">;
175
185
  type PromptItemArray = z.infer<typeof PromptItemArrayZ>;
176
186
  type UploadAiBlameOptions = {
@@ -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 };
@@ -736,7 +736,7 @@ var GetVulByNodesMetadataDocument = `
736
736
  where: {id: {_eq: $vulnerabilityReportId}}
737
737
  ) {
738
738
  vulnerabilityReportIssues(
739
- where: {fixId: {_is_null: true}, category: {_in: [Irrelevant, FalsePositive, Filtered]}}
739
+ where: {fixId: {_is_null: true}, category: {_in: [Irrelevant, FalsePositive, Filtered]}, _not: {vulnerabilityReportIssueTags: {vulnerability_report_issue_tag_value: {_eq: SUPPRESSED}}}}
740
740
  ) {
741
741
  id
742
742
  safeIssueType
@@ -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
@@ -1996,7 +1996,7 @@ var GetVulByNodesMetadataDocument = `
1996
1996
  where: {id: {_eq: $vulnerabilityReportId}}
1997
1997
  ) {
1998
1998
  vulnerabilityReportIssues(
1999
- where: {fixId: {_is_null: true}, category: {_in: [Irrelevant, FalsePositive, Filtered]}}
1999
+ where: {fixId: {_is_null: true}, category: {_in: [Irrelevant, FalsePositive, Filtered]}, _not: {vulnerabilityReportIssueTags: {vulnerability_report_issue_tag_value: {_eq: SUPPRESSED}}}}
2000
2000
  ) {
2001
2001
  id
2002
2002
  safeIssueType
@@ -7512,6 +7512,61 @@ var GET_BLAME_DOCUMENT = `
7512
7512
  }
7513
7513
  }
7514
7514
  `;
7515
+ var GITHUB_GRAPHQL_FRAGMENTS = {
7516
+ /**
7517
+ * Fragment for fetching PR additions/deletions.
7518
+ * Use with pullRequest(number: $n) alias.
7519
+ */
7520
+ PR_CHANGES: `
7521
+ additions
7522
+ deletions
7523
+ `,
7524
+ /**
7525
+ * Fragment for fetching PR comments.
7526
+ * Returns first 100 comments with author info.
7527
+ */
7528
+ PR_COMMENTS: `
7529
+ comments(first: 100) {
7530
+ nodes {
7531
+ author {
7532
+ login
7533
+ __typename
7534
+ }
7535
+ body
7536
+ }
7537
+ }
7538
+ `,
7539
+ /**
7540
+ * Fragment for fetching blame data.
7541
+ * Use with object(expression: $ref) on Commit type.
7542
+ */
7543
+ BLAME_RANGES: `
7544
+ blame(path: "$path") {
7545
+ ranges {
7546
+ startingLine
7547
+ endingLine
7548
+ commit {
7549
+ oid
7550
+ author {
7551
+ user {
7552
+ name
7553
+ login
7554
+ email
7555
+ }
7556
+ }
7557
+ }
7558
+ }
7559
+ }
7560
+ `,
7561
+ /**
7562
+ * Fragment for fetching commit timestamp.
7563
+ * Use with object(oid: $sha) on Commit type.
7564
+ */
7565
+ COMMIT_TIMESTAMP: `
7566
+ oid
7567
+ committedDate
7568
+ `
7569
+ };
7515
7570
 
7516
7571
  // src/features/analysis/scm/github/utils/encrypt_secret.ts
7517
7572
  import sodium from "libsodium-wrappers";
@@ -7649,6 +7704,36 @@ async function githubValidateParams(url, accessToken) {
7649
7704
 
7650
7705
  // src/features/analysis/scm/github/github.ts
7651
7706
  var MAX_GH_PR_BODY_LENGTH = 65536;
7707
+ async function executeBatchGraphQL(octokit, owner, repo, config2) {
7708
+ const { items, aliasPrefix, buildFragment, extractResult } = config2;
7709
+ if (items.length === 0) {
7710
+ return /* @__PURE__ */ new Map();
7711
+ }
7712
+ const fragments = items.map((item, index) => buildFragment(item, index)).join("\n");
7713
+ const query = `
7714
+ query Batch${aliasPrefix}($owner: String!, $repo: String!) {
7715
+ repository(owner: $owner, name: $repo) {
7716
+ ${fragments}
7717
+ }
7718
+ }
7719
+ `;
7720
+ const response = await octokit.graphql(query, { owner, repo });
7721
+ const result = /* @__PURE__ */ new Map();
7722
+ items.forEach((item, index) => {
7723
+ const data = response.repository[`${aliasPrefix}${index}`];
7724
+ if (data) {
7725
+ const extracted = extractResult(
7726
+ data,
7727
+ item,
7728
+ index
7729
+ );
7730
+ if (extracted !== void 0) {
7731
+ result.set(item, extracted);
7732
+ }
7733
+ }
7734
+ });
7735
+ return result;
7736
+ }
7652
7737
  function getGithubSdk(params = {}) {
7653
7738
  const octokit = getOctoKit(params);
7654
7739
  return {
@@ -8114,6 +8199,125 @@ function getGithubSdk(params = {}) {
8114
8199
  pull_number: params2.pull_number
8115
8200
  });
8116
8201
  return { data };
8202
+ },
8203
+ /**
8204
+ * Batch fetch additions/deletions for multiple PRs via GraphQL.
8205
+ * Uses GITHUB_GRAPHQL_FRAGMENTS.PR_CHANGES for the field selection.
8206
+ */
8207
+ async getPrAdditionsDeletionsBatch(params2) {
8208
+ return executeBatchGraphQL(octokit, params2.owner, params2.repo, {
8209
+ items: params2.prNumbers,
8210
+ aliasPrefix: "pr",
8211
+ buildFragment: (prNumber, index) => `
8212
+ pr${index}: pullRequest(number: ${prNumber}) {
8213
+ ${GITHUB_GRAPHQL_FRAGMENTS.PR_CHANGES}
8214
+ }`,
8215
+ extractResult: (data) => {
8216
+ const prData = data;
8217
+ if (prData.additions !== void 0 && prData.deletions !== void 0) {
8218
+ return {
8219
+ additions: prData.additions,
8220
+ deletions: prData.deletions
8221
+ };
8222
+ }
8223
+ return void 0;
8224
+ }
8225
+ });
8226
+ },
8227
+ /**
8228
+ * Batch fetch comments for multiple PRs via GraphQL.
8229
+ * Uses GITHUB_GRAPHQL_FRAGMENTS.PR_COMMENTS for the field selection.
8230
+ */
8231
+ async getPrCommentsBatch(params2) {
8232
+ return executeBatchGraphQL(octokit, params2.owner, params2.repo, {
8233
+ items: params2.prNumbers,
8234
+ aliasPrefix: "pr",
8235
+ buildFragment: (prNumber, index) => `
8236
+ pr${index}: pullRequest(number: ${prNumber}) {
8237
+ ${GITHUB_GRAPHQL_FRAGMENTS.PR_COMMENTS}
8238
+ }`,
8239
+ extractResult: (data) => {
8240
+ const prData = data;
8241
+ if (prData.comments?.nodes) {
8242
+ return prData.comments.nodes.map((node) => ({
8243
+ author: node.author ? { login: node.author.login, type: node.author.__typename } : null,
8244
+ body: node.body
8245
+ }));
8246
+ }
8247
+ return void 0;
8248
+ }
8249
+ });
8250
+ },
8251
+ /**
8252
+ * Batch fetch blame data for multiple files via GraphQL.
8253
+ * Field selection matches GITHUB_GRAPHQL_FRAGMENTS.BLAME_RANGES pattern.
8254
+ */
8255
+ async getBlameBatch(params2) {
8256
+ return executeBatchGraphQL(octokit, params2.owner, params2.repo, {
8257
+ items: params2.filePaths,
8258
+ aliasPrefix: "file",
8259
+ buildFragment: (path22, index) => `
8260
+ file${index}: object(expression: "${params2.ref}") {
8261
+ ... on Commit {
8262
+ blame(path: "${path22}") {
8263
+ ranges {
8264
+ startingLine
8265
+ endingLine
8266
+ commit {
8267
+ oid
8268
+ author {
8269
+ user {
8270
+ name
8271
+ login
8272
+ email
8273
+ }
8274
+ }
8275
+ }
8276
+ }
8277
+ }
8278
+ }
8279
+ }`,
8280
+ extractResult: (data) => {
8281
+ const fileData = data;
8282
+ if (fileData.blame?.ranges) {
8283
+ return fileData.blame.ranges.map((range) => ({
8284
+ startingLine: range.startingLine,
8285
+ endingLine: range.endingLine,
8286
+ commitSha: range.commit.oid,
8287
+ email: range.commit.author.user?.email || "",
8288
+ name: range.commit.author.user?.name || "",
8289
+ login: range.commit.author.user?.login || ""
8290
+ }));
8291
+ }
8292
+ return void 0;
8293
+ }
8294
+ });
8295
+ },
8296
+ /**
8297
+ * Batch fetch commit timestamps for multiple commits via GraphQL.
8298
+ * Uses GITHUB_GRAPHQL_FRAGMENTS.COMMIT_TIMESTAMP for the field selection.
8299
+ */
8300
+ async getCommitsBatch(params2) {
8301
+ return executeBatchGraphQL(octokit, params2.owner, params2.repo, {
8302
+ items: params2.commitShas,
8303
+ aliasPrefix: "commit",
8304
+ buildFragment: (sha, index) => `
8305
+ commit${index}: object(oid: "${sha}") {
8306
+ ... on Commit {
8307
+ ${GITHUB_GRAPHQL_FRAGMENTS.COMMIT_TIMESTAMP}
8308
+ }
8309
+ }`,
8310
+ extractResult: (data) => {
8311
+ const commitData = data;
8312
+ if (commitData.oid && commitData.committedDate) {
8313
+ return {
8314
+ sha: commitData.oid,
8315
+ timestamp: new Date(commitData.committedDate)
8316
+ };
8317
+ }
8318
+ return void 0;
8319
+ }
8320
+ });
8117
8321
  }
8118
8322
  };
8119
8323
  }
@@ -8382,7 +8586,7 @@ var GithubSCMLib = class extends SCMLib {
8382
8586
  comment_id: commentId
8383
8587
  });
8384
8588
  }
8385
- async getCommitDiff(commitSha) {
8589
+ async getCommitDiff(commitSha, options) {
8386
8590
  this._validateAccessTokenAndUrl();
8387
8591
  const { owner, repo } = parseGithubOwnerAndRepo(this.url);
8388
8592
  const { commit, diff } = await this.githubSdk.getCommitWithDiff({
@@ -8393,43 +8597,49 @@ var GithubSCMLib = class extends SCMLib {
8393
8597
  const commitTimestamp = commit.commit.committer?.date ? new Date(commit.commit.committer.date) : new Date(commit.commit.author?.date || Date.now());
8394
8598
  let parentCommits;
8395
8599
  if (commit.parents && commit.parents.length > 0) {
8600
+ if (options?.parentCommitTimestamps) {
8601
+ parentCommits = commit.parents.map((p) => options.parentCommitTimestamps.get(p.sha)).filter((p) => p !== void 0);
8602
+ } else {
8603
+ try {
8604
+ parentCommits = await Promise.all(
8605
+ commit.parents.map(async (parent) => {
8606
+ const parentCommit = await this.githubSdk.getCommit({
8607
+ owner,
8608
+ repo,
8609
+ commitSha: parent.sha
8610
+ });
8611
+ const parentTimestamp = parentCommit.data.committer?.date ? new Date(parentCommit.data.committer.date) : new Date(Date.now());
8612
+ return {
8613
+ sha: parent.sha,
8614
+ timestamp: parentTimestamp
8615
+ };
8616
+ })
8617
+ );
8618
+ } catch (error) {
8619
+ console.error("Failed to fetch parent commit timestamps", {
8620
+ error,
8621
+ commitSha,
8622
+ owner,
8623
+ repo
8624
+ });
8625
+ parentCommits = void 0;
8626
+ }
8627
+ }
8628
+ }
8629
+ let repositoryCreatedAt = options?.repositoryCreatedAt;
8630
+ if (repositoryCreatedAt === void 0) {
8396
8631
  try {
8397
- parentCommits = await Promise.all(
8398
- commit.parents.map(async (parent) => {
8399
- const parentCommit = await this.githubSdk.getCommit({
8400
- owner,
8401
- repo,
8402
- commitSha: parent.sha
8403
- });
8404
- const parentTimestamp = parentCommit.data.committer?.date ? new Date(parentCommit.data.committer.date) : new Date(Date.now());
8405
- return {
8406
- sha: parent.sha,
8407
- timestamp: parentTimestamp
8408
- };
8409
- })
8410
- );
8632
+ const repoData = await this.githubSdk.getRepository({ owner, repo });
8633
+ repositoryCreatedAt = repoData.data.created_at ? new Date(repoData.data.created_at) : void 0;
8411
8634
  } catch (error) {
8412
- console.error("Failed to fetch parent commit timestamps", {
8635
+ console.error("Failed to fetch repository creation date", {
8413
8636
  error,
8414
- commitSha,
8415
8637
  owner,
8416
8638
  repo
8417
8639
  });
8418
- parentCommits = void 0;
8640
+ repositoryCreatedAt = void 0;
8419
8641
  }
8420
8642
  }
8421
- let repositoryCreatedAt;
8422
- try {
8423
- const repoData = await this.githubSdk.getRepository({ owner, repo });
8424
- repositoryCreatedAt = repoData.data.created_at ? new Date(repoData.data.created_at) : void 0;
8425
- } catch (error) {
8426
- console.error("Failed to fetch repository creation date", {
8427
- error,
8428
- owner,
8429
- repo
8430
- });
8431
- repositoryCreatedAt = void 0;
8432
- }
8433
8643
  return {
8434
8644
  diff,
8435
8645
  commitTimestamp,
@@ -8445,15 +8655,37 @@ var GithubSCMLib = class extends SCMLib {
8445
8655
  this._validateAccessTokenAndUrl();
8446
8656
  const { owner, repo } = parseGithubOwnerAndRepo(this.url);
8447
8657
  const prNumber = Number(submitRequestId);
8448
- const [prRes, commitsRes, filesRes] = await Promise.all([
8658
+ const [prRes, commitsRes, filesRes, repoData] = await Promise.all([
8449
8659
  this.githubSdk.getPr({ owner, repo, pull_number: prNumber }),
8450
8660
  this.githubSdk.getPrCommits({ owner, repo, pull_number: prNumber }),
8451
- this.githubSdk.listPRFiles({ owner, repo, pull_number: prNumber })
8661
+ this.githubSdk.listPRFiles({ owner, repo, pull_number: prNumber }),
8662
+ this.githubSdk.getRepository({ owner, repo })
8452
8663
  ]);
8453
8664
  const pr = prRes.data;
8454
- const prDiff = await this.getPrDiff({ pull_number: prNumber });
8665
+ const repositoryCreatedAt = repoData.data.created_at ? new Date(repoData.data.created_at) : void 0;
8666
+ const allParentShas = /* @__PURE__ */ new Set();
8667
+ for (const commit of commitsRes.data) {
8668
+ if (commit.parents) {
8669
+ for (const parent of commit.parents) {
8670
+ allParentShas.add(parent.sha);
8671
+ }
8672
+ }
8673
+ }
8674
+ const [parentCommitTimestamps, prDiff] = await Promise.all([
8675
+ this.githubSdk.getCommitsBatch({
8676
+ owner,
8677
+ repo,
8678
+ commitShas: Array.from(allParentShas)
8679
+ }),
8680
+ this.getPrDiff({ pull_number: prNumber })
8681
+ ]);
8455
8682
  const commits = await Promise.all(
8456
- commitsRes.data.map((commit) => this.getCommitDiff(commit.sha))
8683
+ commitsRes.data.map(
8684
+ (commit) => this.getCommitDiff(commit.sha, {
8685
+ repositoryCreatedAt,
8686
+ parentCommitTimestamps
8687
+ })
8688
+ )
8457
8689
  );
8458
8690
  const diffLines = await this._attributeLinesViaBlame(
8459
8691
  pr.head.ref,
@@ -8480,104 +8712,83 @@ var GithubSCMLib = class extends SCMLib {
8480
8712
  this._validateAccessToken();
8481
8713
  const { owner, repo } = parseGithubOwnerAndRepo(repoUrl);
8482
8714
  const pullsRes = await this.githubSdk.getRepoPullRequests({ owner, repo });
8483
- const submitRequests = await Promise.all(
8484
- pullsRes.data.map(async (pr) => {
8485
- let status = "open";
8486
- if (pr.state === "closed") {
8487
- status = pr.merged_at ? "merged" : "closed";
8488
- } else if (pr.draft) {
8489
- status = "draft";
8490
- }
8491
- const [tickets, changedLines] = await Promise.all([
8492
- this._extractLinearTicketsFromPR(owner, repo, pr.number),
8493
- this._calculateChangedLinesFromPR(owner, repo, pr.number)
8494
- ]);
8495
- return {
8496
- submitRequestId: String(pr.number),
8497
- submitRequestNumber: pr.number,
8498
- title: pr.title,
8499
- status,
8500
- sourceBranch: pr.head.ref,
8501
- targetBranch: pr.base.ref,
8502
- authorName: pr.user?.name || pr.user?.login,
8503
- authorEmail: pr.user?.email || void 0,
8504
- createdAt: new Date(pr.created_at),
8505
- updatedAt: new Date(pr.updated_at),
8506
- description: pr.body || void 0,
8507
- tickets,
8508
- changedLines
8509
- };
8510
- })
8511
- );
8715
+ const prNumbers = pullsRes.data.map((pr) => pr.number);
8716
+ const [additionsDeletionsMap, commentsMap] = await Promise.all([
8717
+ this.githubSdk.getPrAdditionsDeletionsBatch({ owner, repo, prNumbers }),
8718
+ this.githubSdk.getPrCommentsBatch({ owner, repo, prNumbers })
8719
+ ]);
8720
+ const submitRequests = pullsRes.data.map((pr) => {
8721
+ let status = "open";
8722
+ if (pr.state === "closed") {
8723
+ status = pr.merged_at ? "merged" : "closed";
8724
+ } else if (pr.draft) {
8725
+ status = "draft";
8726
+ }
8727
+ const changedLinesData = additionsDeletionsMap.get(pr.number);
8728
+ const changedLines = changedLinesData ? {
8729
+ added: changedLinesData.additions,
8730
+ removed: changedLinesData.deletions
8731
+ } : { added: 0, removed: 0 };
8732
+ const comments = commentsMap.get(pr.number) || [];
8733
+ const tickets = this._extractLinearTicketsFromComments(comments);
8734
+ return {
8735
+ submitRequestId: String(pr.number),
8736
+ submitRequestNumber: pr.number,
8737
+ title: pr.title,
8738
+ status,
8739
+ sourceBranch: pr.head.ref,
8740
+ targetBranch: pr.base.ref,
8741
+ authorName: pr.user?.name || pr.user?.login,
8742
+ authorEmail: pr.user?.email || void 0,
8743
+ createdAt: new Date(pr.created_at),
8744
+ updatedAt: new Date(pr.updated_at),
8745
+ description: pr.body || void 0,
8746
+ tickets,
8747
+ changedLines
8748
+ };
8749
+ });
8512
8750
  return submitRequests;
8513
8751
  }
8514
- async _extractLinearTicketsFromPR(owner, repo, prNumber) {
8515
- try {
8516
- const commentsRes = await this.githubSdk.getGeneralPrComments({
8517
- owner,
8518
- repo,
8519
- issue_number: prNumber
8520
- });
8521
- const tickets = [];
8522
- for (const comment of commentsRes.data) {
8523
- if (comment.user?.login === "linear[bot]" || comment.user?.type === "Bot") {
8524
- const htmlLinkPattern = /<a href="(https:\/\/linear\.app\/[^"]+)">([A-Z]+-\d+)<\/a>/g;
8525
- let match;
8526
- while ((match = htmlLinkPattern.exec(comment.body || "")) !== null) {
8527
- const url = match[1];
8528
- const name = match[2];
8529
- if (!name || !url) {
8530
- continue;
8531
- }
8532
- const urlParts = url.split("/");
8533
- const titleSlug = urlParts[urlParts.length - 1] || "";
8534
- const title = titleSlug.replace(/-/g, " ");
8535
- tickets.push({ name, title, url });
8536
- }
8537
- const markdownLinkPattern = /\[([A-Z]+-\d+)\]\((https:\/\/linear\.app\/[^)]+)\)/g;
8538
- while ((match = markdownLinkPattern.exec(comment.body || "")) !== null) {
8539
- const name = match[1];
8540
- const url = match[2];
8541
- if (tickets.some((t) => t.name === name && t.url === url)) {
8542
- continue;
8543
- }
8544
- if (!name || !url) {
8545
- continue;
8546
- }
8547
- const urlParts = url.split("/");
8548
- const titleSlug = urlParts[urlParts.length - 1] || "";
8549
- const title = titleSlug.replace(/-/g, " ");
8550
- tickets.push({ name, title, url });
8752
+ /**
8753
+ * Parse a Linear ticket from URL and name
8754
+ * Returns null if invalid or missing data
8755
+ */
8756
+ _parseLinearTicket(url, name) {
8757
+ if (!name || !url) return null;
8758
+ const urlParts = url.split("/");
8759
+ const titleSlug = urlParts[urlParts.length - 1] || "";
8760
+ const title = titleSlug.replace(/-/g, " ");
8761
+ return { name, title, url };
8762
+ }
8763
+ /**
8764
+ * Extract Linear ticket links from pre-fetched comments (pure function, no API calls)
8765
+ */
8766
+ _extractLinearTicketsFromComments(comments) {
8767
+ const tickets = [];
8768
+ const seen = /* @__PURE__ */ new Set();
8769
+ for (const comment of comments) {
8770
+ if (comment.author?.login === "linear[bot]" || comment.author?.type === "Bot") {
8771
+ const body = comment.body || "";
8772
+ const htmlPattern = /<a href="(https:\/\/linear\.app\/[^"]+)">([A-Z]+-\d+)<\/a>/g;
8773
+ let match;
8774
+ while ((match = htmlPattern.exec(body)) !== null) {
8775
+ const ticket = this._parseLinearTicket(match[1], match[2]);
8776
+ if (ticket && !seen.has(`${ticket.name}|${ticket.url}`)) {
8777
+ seen.add(`${ticket.name}|${ticket.url}`);
8778
+ tickets.push(ticket);
8551
8779
  }
8552
8780
  }
8553
- }
8554
- return tickets;
8555
- } catch (error) {
8556
- return [];
8557
- }
8558
- }
8559
- async _calculateChangedLinesFromPR(owner, repo, prNumber) {
8560
- try {
8561
- const diffRes = await this.githubSdk.getPrDiff({
8562
- owner,
8563
- repo,
8564
- pull_number: prNumber
8565
- });
8566
- const diff = z21.string().parse(diffRes.data);
8567
- let added = 0;
8568
- let removed = 0;
8569
- const lines = diff.split("\n");
8570
- for (const line of lines) {
8571
- if (line.startsWith("+") && !line.startsWith("+++")) {
8572
- added++;
8573
- } else if (line.startsWith("-") && !line.startsWith("---")) {
8574
- removed++;
8781
+ const markdownPattern = /\[([A-Z]+-\d+)\]\((https:\/\/linear\.app\/[^)]+)\)/g;
8782
+ while ((match = markdownPattern.exec(body)) !== null) {
8783
+ const ticket = this._parseLinearTicket(match[2], match[1]);
8784
+ if (ticket && !seen.has(`${ticket.name}|${ticket.url}`)) {
8785
+ seen.add(`${ticket.name}|${ticket.url}`);
8786
+ tickets.push(ticket);
8787
+ }
8575
8788
  }
8576
8789
  }
8577
- return { added, removed };
8578
- } catch (error) {
8579
- return { added: 0, removed: 0 };
8580
8790
  }
8791
+ return tickets;
8581
8792
  }
8582
8793
  /**
8583
8794
  * Optimized helper to parse added line numbers from a unified diff patch
@@ -8605,48 +8816,59 @@ var GithubSCMLib = class extends SCMLib {
8605
8816
  return addedLines;
8606
8817
  }
8607
8818
  /**
8608
- * Attribute lines in a single file to their commits using blame
8819
+ * Process blame data for a single file to attribute lines to commits
8820
+ * Uses pre-fetched blame data instead of making API calls
8609
8821
  */
8610
- async _attributeFileLines(file, headRef, prCommitShas) {
8611
- try {
8612
- const blame = await this.getRepoBlameRanges(headRef, file.filename);
8613
- const addedLines = this._parseAddedLinesFromPatch(file.patch);
8614
- const addedLinesSet = new Set(addedLines);
8615
- const fileAttributions = [];
8616
- for (const blameRange of blame) {
8617
- if (!prCommitShas.has(blameRange.commitSha)) {
8618
- continue;
8619
- }
8620
- for (let lineNum = blameRange.startingLine; lineNum <= blameRange.endingLine; lineNum++) {
8621
- if (addedLinesSet.has(lineNum)) {
8622
- fileAttributions.push({
8623
- file: file.filename,
8624
- line: lineNum,
8625
- commitSha: blameRange.commitSha
8626
- });
8627
- }
8822
+ _processFileBlameSafe(file, blameData, prCommitShas) {
8823
+ const addedLines = this._parseAddedLinesFromPatch(file.patch);
8824
+ const addedLinesSet = new Set(addedLines);
8825
+ const fileAttributions = [];
8826
+ for (const blameRange of blameData) {
8827
+ if (!prCommitShas.has(blameRange.commitSha)) {
8828
+ continue;
8829
+ }
8830
+ for (let lineNum = blameRange.startingLine; lineNum <= blameRange.endingLine; lineNum++) {
8831
+ if (addedLinesSet.has(lineNum)) {
8832
+ fileAttributions.push({
8833
+ file: file.filename,
8834
+ line: lineNum,
8835
+ commitSha: blameRange.commitSha
8836
+ });
8628
8837
  }
8629
8838
  }
8630
- return fileAttributions;
8631
- } catch (error) {
8632
- return [];
8633
8839
  }
8840
+ return fileAttributions;
8634
8841
  }
8635
8842
  /**
8636
8843
  * Optimized helper to attribute PR lines to commits using blame API
8637
- * Parallel blame queries for minimal API call time
8844
+ * Batch blame queries for minimal API call time (1 call instead of M calls)
8638
8845
  */
8639
8846
  async _attributeLinesViaBlame(headRef, changedFiles, prCommits) {
8640
8847
  const prCommitShas = new Set(prCommits.map((c) => c.commitSha));
8641
8848
  const filesWithAdditions = changedFiles.filter(
8642
8849
  (file) => file.patch?.includes("\n+")
8643
8850
  );
8644
- const attributions = await Promise.all(
8645
- filesWithAdditions.map(
8646
- (file) => this._attributeFileLines(file, headRef, prCommitShas)
8647
- )
8648
- );
8649
- return attributions.flat();
8851
+ if (filesWithAdditions.length === 0) {
8852
+ return [];
8853
+ }
8854
+ const { owner, repo } = parseGithubOwnerAndRepo(this.url);
8855
+ const blameMap = await this.githubSdk.getBlameBatch({
8856
+ owner,
8857
+ repo,
8858
+ ref: headRef,
8859
+ filePaths: filesWithAdditions.map((f) => f.filename)
8860
+ });
8861
+ const allAttributions = [];
8862
+ for (const file of filesWithAdditions) {
8863
+ const blameData = blameMap.get(file.filename) || [];
8864
+ const fileAttributions = this._processFileBlameSafe(
8865
+ file,
8866
+ blameData,
8867
+ prCommitShas
8868
+ );
8869
+ allAttributions.push(...fileAttributions);
8870
+ }
8871
+ return allAttributions;
8650
8872
  }
8651
8873
  };
8652
8874
 
@@ -13046,6 +13268,145 @@ import path11 from "path";
13046
13268
  import chalk9 from "chalk";
13047
13269
  import { withFile } from "tmp-promise";
13048
13270
  import z31 from "zod";
13271
+
13272
+ // src/utils/sanitize-sensitive-data.ts
13273
+ import { OpenRedaction } from "@openredaction/openredaction";
13274
+ import { spawn } from "child_process";
13275
+ import { installGitleaks } from "gitleaks-secret-scanner/lib/installer.js";
13276
+ var openRedaction = new OpenRedaction();
13277
+ var gitleaksBinaryPath = null;
13278
+ async function initializeGitleaks() {
13279
+ try {
13280
+ gitleaksBinaryPath = await installGitleaks({ version: "8.27.2" });
13281
+ return gitleaksBinaryPath;
13282
+ } catch {
13283
+ return null;
13284
+ }
13285
+ }
13286
+ var gitleaksInitPromise = initializeGitleaks();
13287
+ async function detectSecretsWithGitleaks(text) {
13288
+ const secrets = /* @__PURE__ */ new Set();
13289
+ const binaryPath = gitleaksBinaryPath || await gitleaksInitPromise;
13290
+ if (!binaryPath) {
13291
+ return secrets;
13292
+ }
13293
+ return new Promise((resolve) => {
13294
+ const gitleaks = spawn(
13295
+ binaryPath,
13296
+ [
13297
+ "detect",
13298
+ "--pipe",
13299
+ "--no-banner",
13300
+ "--exit-code",
13301
+ "0",
13302
+ "--report-format",
13303
+ "json"
13304
+ ],
13305
+ {
13306
+ stdio: ["pipe", "pipe", "ignore"]
13307
+ }
13308
+ );
13309
+ let output = "";
13310
+ gitleaks.stdout.on("data", (data) => {
13311
+ output += data.toString();
13312
+ });
13313
+ gitleaks.on("close", () => {
13314
+ try {
13315
+ const findings = JSON.parse(output);
13316
+ if (Array.isArray(findings)) {
13317
+ for (const finding of findings) {
13318
+ if (finding.Secret) {
13319
+ secrets.add(finding.Secret);
13320
+ }
13321
+ }
13322
+ }
13323
+ } catch {
13324
+ }
13325
+ resolve(secrets);
13326
+ });
13327
+ gitleaks.on("error", () => {
13328
+ resolve(secrets);
13329
+ });
13330
+ gitleaks.stdin.write(text);
13331
+ gitleaks.stdin.end();
13332
+ });
13333
+ }
13334
+ function maskString(str, showStart = 2, showEnd = 2) {
13335
+ if (str.length <= showStart + showEnd) {
13336
+ return "*".repeat(str.length);
13337
+ }
13338
+ return str.slice(0, showStart) + "*".repeat(str.length - showStart - showEnd) + str.slice(-showEnd);
13339
+ }
13340
+ async function sanitizeDataWithCounts(obj) {
13341
+ const counts = {
13342
+ pii: { total: 0, high: 0, medium: 0, low: 0 },
13343
+ secrets: 0
13344
+ };
13345
+ const sanitizeString = async (str) => {
13346
+ let result = str;
13347
+ const piiDetections = openRedaction.scan(str);
13348
+ if (piiDetections && piiDetections.total > 0) {
13349
+ const allDetections = [
13350
+ ...piiDetections.high,
13351
+ ...piiDetections.medium,
13352
+ ...piiDetections.low
13353
+ ];
13354
+ const filteredDetections = allDetections.filter((detection) => {
13355
+ if (detection.type === "INSTAGRAM_USERNAME") {
13356
+ return false;
13357
+ }
13358
+ if (detection.value.length < 3 && detection.severity !== "high") {
13359
+ return false;
13360
+ }
13361
+ return true;
13362
+ });
13363
+ for (const detection of filteredDetections) {
13364
+ counts.pii.total++;
13365
+ if (detection.severity === "high") counts.pii.high++;
13366
+ else if (detection.severity === "medium") counts.pii.medium++;
13367
+ else if (detection.severity === "low") counts.pii.low++;
13368
+ const masked = maskString(detection.value);
13369
+ result = result.replaceAll(detection.value, masked);
13370
+ }
13371
+ }
13372
+ const secrets = await detectSecretsWithGitleaks(result);
13373
+ counts.secrets += secrets.size;
13374
+ for (const secret of secrets) {
13375
+ const masked = maskString(secret);
13376
+ result = result.replaceAll(secret, masked);
13377
+ }
13378
+ return result;
13379
+ };
13380
+ const sanitizeRecursive = async (data) => {
13381
+ if (typeof data === "string") {
13382
+ return sanitizeString(data);
13383
+ } else if (Array.isArray(data)) {
13384
+ return Promise.all(data.map((item) => sanitizeRecursive(item)));
13385
+ } else if (data instanceof Error) {
13386
+ return data;
13387
+ } else if (data instanceof Date) {
13388
+ return data;
13389
+ } else if (typeof data === "object" && data !== null) {
13390
+ const sanitized = {};
13391
+ const record = data;
13392
+ for (const key in record) {
13393
+ if (Object.prototype.hasOwnProperty.call(record, key)) {
13394
+ sanitized[key] = await sanitizeRecursive(record[key]);
13395
+ }
13396
+ }
13397
+ return sanitized;
13398
+ }
13399
+ return data;
13400
+ };
13401
+ const sanitizedData = await sanitizeRecursive(obj);
13402
+ return { sanitizedData, counts };
13403
+ }
13404
+ async function sanitizeData(obj) {
13405
+ const result = await sanitizeDataWithCounts(obj);
13406
+ return result.sanitizedData;
13407
+ }
13408
+
13409
+ // src/args/commands/upload_ai_blame.ts
13049
13410
  var PromptItemZ = z31.object({
13050
13411
  type: z31.enum(["USER_PROMPT", "AI_RESPONSE", "TOOL_EXECUTION", "AI_THINKING"]),
13051
13412
  attachedFiles: z31.array(
@@ -13114,15 +13475,32 @@ async function uploadAiBlameHandlerFromExtension(args) {
13114
13475
  aiResponseAt: [],
13115
13476
  blameType: []
13116
13477
  };
13478
+ let promptsCounts;
13479
+ let inferenceCounts;
13480
+ let promptsUUID;
13481
+ let inferenceUUID;
13117
13482
  await withFile(async (promptFile) => {
13483
+ const promptsResult = await sanitizeDataWithCounts(args.prompts);
13484
+ promptsCounts = promptsResult.counts;
13485
+ promptsUUID = path11.basename(promptFile.path, path11.extname(promptFile.path));
13118
13486
  await fsPromises3.writeFile(
13119
13487
  promptFile.path,
13120
- JSON.stringify(args.prompts, null, 2),
13488
+ JSON.stringify(promptsResult.sanitizedData, null, 2),
13121
13489
  "utf-8"
13122
13490
  );
13123
13491
  uploadArgs.prompt.push(promptFile.path);
13124
13492
  await withFile(async (inferenceFile) => {
13125
- await fsPromises3.writeFile(inferenceFile.path, args.inference, "utf-8");
13493
+ const inferenceResult = await sanitizeDataWithCounts(args.inference);
13494
+ inferenceCounts = inferenceResult.counts;
13495
+ inferenceUUID = path11.basename(
13496
+ inferenceFile.path,
13497
+ path11.extname(inferenceFile.path)
13498
+ );
13499
+ await fsPromises3.writeFile(
13500
+ inferenceFile.path,
13501
+ inferenceResult.sanitizedData,
13502
+ "utf-8"
13503
+ );
13126
13504
  uploadArgs.inference.push(inferenceFile.path);
13127
13505
  uploadArgs.model.push(args.model);
13128
13506
  uploadArgs.toolName.push(args.tool);
@@ -13131,6 +13509,12 @@ async function uploadAiBlameHandlerFromExtension(args) {
13131
13509
  await uploadAiBlameHandler(uploadArgs, false);
13132
13510
  });
13133
13511
  });
13512
+ return {
13513
+ promptsCounts,
13514
+ inferenceCounts,
13515
+ promptsUUID,
13516
+ inferenceUUID
13517
+ };
13134
13518
  }
13135
13519
  async function uploadAiBlameHandler(args, exitOnError = true) {
13136
13520
  const prompts = args.prompt || [];
@@ -13177,8 +13561,11 @@ async function uploadAiBlameHandler(args, exitOnError = true) {
13177
13561
  const authenticatedClient = await getAuthenticatedGQLClient({
13178
13562
  isSkipPrompts: true
13179
13563
  });
13180
- const initRes = await authenticatedClient.uploadAIBlameInferencesInitRaw({
13564
+ const sanitizedSessions = await sanitizeData(
13181
13565
  sessions
13566
+ );
13567
+ const initRes = await authenticatedClient.uploadAIBlameInferencesInitRaw({
13568
+ sessions: sanitizedSessions
13182
13569
  });
13183
13570
  const uploadSessions = initRes.uploadAIBlameInferencesInit?.uploadSessions ?? [];
13184
13571
  if (uploadSessions.length !== sessions.length) {
@@ -13222,8 +13609,11 @@ async function uploadAiBlameHandler(args, exitOnError = true) {
13222
13609
  blameType: s.blameType
13223
13610
  };
13224
13611
  });
13612
+ const sanitizedFinalizeSessions = await sanitizeData(
13613
+ finalizeSessions
13614
+ );
13225
13615
  const finRes = await authenticatedClient.finalizeAIBlameInferencesUploadRaw({
13226
- sessions: finalizeSessions
13616
+ sessions: sanitizedFinalizeSessions
13227
13617
  });
13228
13618
  const status = finRes?.finalizeAIBlameInferencesUpload?.status;
13229
13619
  if (status !== "OK") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobbdev",
3
- "version": "1.1.6",
3
+ "version": "1.1.8",
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",