mobbdev 1.2.24 → 1.2.28

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.
@@ -237,6 +237,7 @@ var init_client_generates = __esm({
237
237
  IssueType_Enum2["DuplicatedStrings"] = "DUPLICATED_STRINGS";
238
238
  IssueType_Enum2["ErroneousStringCompare"] = "ERRONEOUS_STRING_COMPARE";
239
239
  IssueType_Enum2["ErrorCondtionWithoutAction"] = "ERROR_CONDTION_WITHOUT_ACTION";
240
+ IssueType_Enum2["ExcessiveSecretsExposure"] = "EXCESSIVE_SECRETS_EXPOSURE";
240
241
  IssueType_Enum2["FrameableLoginPage"] = "FRAMEABLE_LOGIN_PAGE";
241
242
  IssueType_Enum2["FunctionCallWithoutParentheses"] = "FUNCTION_CALL_WITHOUT_PARENTHESES";
242
243
  IssueType_Enum2["GhActionsShellInjection"] = "GH_ACTIONS_SHELL_INJECTION";
@@ -1712,7 +1713,8 @@ var init_getIssueType = __esm({
1712
1713
  ["ACTION_NOT_PINNED_TO_COMMIT_SHA" /* ActionNotPinnedToCommitSha */]: "Action Not Pinned to Commit Sha",
1713
1714
  ["DJANGO_BLANK_FIELD_NEEDS_NULL_OR_DEFAULT" /* DjangoBlankFieldNeedsNullOrDefault */]: "Django Blank Field Needs Null or Default",
1714
1715
  ["REDUNDANT_NIL_ERROR_CHECK" /* RedundantNilErrorCheck */]: "Redundant Nil Error Check",
1715
- ["MISSING_WORKFLOW_PERMISSIONS" /* MissingWorkflowPermissions */]: "Missing Workflow Permissions"
1716
+ ["MISSING_WORKFLOW_PERMISSIONS" /* MissingWorkflowPermissions */]: "Missing Workflow Permissions",
1717
+ ["EXCESSIVE_SECRETS_EXPOSURE" /* ExcessiveSecretsExposure */]: "Excessive Secrets Exposure"
1716
1718
  };
1717
1719
  issueTypeZ = z5.nativeEnum(IssueType_Enum);
1718
1720
  getIssueTypeFriendlyString = (issueType) => {
@@ -4556,7 +4558,8 @@ var fixDetailsData = {
4556
4558
  ["ACTION_NOT_PINNED_TO_COMMIT_SHA" /* ActionNotPinnedToCommitSha */]: void 0,
4557
4559
  ["DJANGO_BLANK_FIELD_NEEDS_NULL_OR_DEFAULT" /* DjangoBlankFieldNeedsNullOrDefault */]: void 0,
4558
4560
  ["REDUNDANT_NIL_ERROR_CHECK" /* RedundantNilErrorCheck */]: void 0,
4559
- ["MISSING_WORKFLOW_PERMISSIONS" /* MissingWorkflowPermissions */]: void 0
4561
+ ["MISSING_WORKFLOW_PERMISSIONS" /* MissingWorkflowPermissions */]: void 0,
4562
+ ["EXCESSIVE_SECRETS_EXPOSURE" /* ExcessiveSecretsExposure */]: void 0
4560
4563
  };
4561
4564
 
4562
4565
  // src/features/analysis/scm/shared/src/commitDescriptionMarkup.ts
@@ -7344,8 +7347,8 @@ var openRedaction = new OpenRedaction({
7344
7347
  "MONERO_ADDRESS",
7345
7348
  "RIPPLE_ADDRESS",
7346
7349
  // Medical Data (removed PRESCRIPTION_NUMBER - too broad, matches words containing "ription")
7350
+ // Removed MEDICAL_RECORD_NUMBER - too broad, "MR" prefix matches "Merge Request" in SCM contexts (e.g. "MR branches" → "MR br****es")
7347
7351
  "NHS_NUMBER",
7348
- "MEDICAL_RECORD_NUMBER",
7349
7352
  "AUSTRALIAN_MEDICARE",
7350
7353
  "HEALTH_PLAN_NUMBER",
7351
7354
  "PATIENT_ID",
@@ -7389,10 +7392,10 @@ var openRedaction = new OpenRedaction({
7389
7392
  "DOCKER_AUTH",
7390
7393
  "KUBERNETES_SECRET",
7391
7394
  // Government & Legal
7395
+ // Removed CLIENT_ID - too broad, "client" is ubiquitous in code (npm packages like @scope/client-*, class names like ClientSdkOptions)
7392
7396
  "POLICE_REPORT_NUMBER",
7393
7397
  "IMMIGRATION_NUMBER",
7394
- "COURT_REPORTER_LICENSE",
7395
- "CLIENT_ID"
7398
+ "COURT_REPORTER_LICENSE"
7396
7399
  ]
7397
7400
  });
7398
7401
  function maskString(str, showStart = 2, showEnd = 2) {
@@ -7415,6 +7418,15 @@ async function sanitizeDataWithCounts(obj) {
7415
7418
  ...piiDetections.low
7416
7419
  ];
7417
7420
  for (const detection of allDetections) {
7421
+ if (detection.type === "CREDIT_CARD") {
7422
+ const start = detection.position[0];
7423
+ const end = detection.position[1];
7424
+ const charBefore = (start > 0 ? str[start - 1] : "") ?? "";
7425
+ const charAfter = str[end] ?? "";
7426
+ if (charBefore === "." || charBefore >= "0" && charBefore <= "9" || charAfter >= "0" && charAfter <= "9") {
7427
+ continue;
7428
+ }
7429
+ }
7418
7430
  counts.detections.total++;
7419
7431
  if (detection.severity === "high") counts.detections.high++;
7420
7432
  else if (detection.severity === "medium") counts.detections.medium++;
package/dist/index.mjs CHANGED
@@ -237,6 +237,7 @@ var init_client_generates = __esm({
237
237
  IssueType_Enum2["DuplicatedStrings"] = "DUPLICATED_STRINGS";
238
238
  IssueType_Enum2["ErroneousStringCompare"] = "ERRONEOUS_STRING_COMPARE";
239
239
  IssueType_Enum2["ErrorCondtionWithoutAction"] = "ERROR_CONDTION_WITHOUT_ACTION";
240
+ IssueType_Enum2["ExcessiveSecretsExposure"] = "EXCESSIVE_SECRETS_EXPOSURE";
240
241
  IssueType_Enum2["FrameableLoginPage"] = "FRAMEABLE_LOGIN_PAGE";
241
242
  IssueType_Enum2["FunctionCallWithoutParentheses"] = "FUNCTION_CALL_WITHOUT_PARENTHESES";
242
243
  IssueType_Enum2["GhActionsShellInjection"] = "GH_ACTIONS_SHELL_INJECTION";
@@ -1412,7 +1413,8 @@ var init_getIssueType = __esm({
1412
1413
  ["ACTION_NOT_PINNED_TO_COMMIT_SHA" /* ActionNotPinnedToCommitSha */]: "Action Not Pinned to Commit Sha",
1413
1414
  ["DJANGO_BLANK_FIELD_NEEDS_NULL_OR_DEFAULT" /* DjangoBlankFieldNeedsNullOrDefault */]: "Django Blank Field Needs Null or Default",
1414
1415
  ["REDUNDANT_NIL_ERROR_CHECK" /* RedundantNilErrorCheck */]: "Redundant Nil Error Check",
1415
- ["MISSING_WORKFLOW_PERMISSIONS" /* MissingWorkflowPermissions */]: "Missing Workflow Permissions"
1416
+ ["MISSING_WORKFLOW_PERMISSIONS" /* MissingWorkflowPermissions */]: "Missing Workflow Permissions",
1417
+ ["EXCESSIVE_SECRETS_EXPOSURE" /* ExcessiveSecretsExposure */]: "Excessive Secrets Exposure"
1416
1418
  };
1417
1419
  issueTypeZ = z.nativeEnum(IssueType_Enum);
1418
1420
  getIssueTypeFriendlyString = (issueType) => {
@@ -4284,7 +4286,8 @@ var fixDetailsData = {
4284
4286
  ["ACTION_NOT_PINNED_TO_COMMIT_SHA" /* ActionNotPinnedToCommitSha */]: void 0,
4285
4287
  ["DJANGO_BLANK_FIELD_NEEDS_NULL_OR_DEFAULT" /* DjangoBlankFieldNeedsNullOrDefault */]: void 0,
4286
4288
  ["REDUNDANT_NIL_ERROR_CHECK" /* RedundantNilErrorCheck */]: void 0,
4287
- ["MISSING_WORKFLOW_PERMISSIONS" /* MissingWorkflowPermissions */]: void 0
4289
+ ["MISSING_WORKFLOW_PERMISSIONS" /* MissingWorkflowPermissions */]: void 0,
4290
+ ["EXCESSIVE_SECRETS_EXPOSURE" /* ExcessiveSecretsExposure */]: void 0
4288
4291
  };
4289
4292
 
4290
4293
  // src/features/analysis/scm/shared/src/commitDescriptionMarkup.ts
@@ -6241,6 +6244,23 @@ function parseLinearTicket(url, name) {
6241
6244
  const title = titleSlug.replace(/-/g, " ");
6242
6245
  return { name, title, url };
6243
6246
  }
6247
+ function isLinearBotComment(comment) {
6248
+ if (!comment.author) return false;
6249
+ const login = comment.author.login.toLowerCase();
6250
+ if (login === "linear[bot]" || login === "linear") return true;
6251
+ if (comment.author.type === "Bot" && login.includes("linear")) return true;
6252
+ return false;
6253
+ }
6254
+ function extractLinearTicketsFromComments(comments) {
6255
+ const tickets = [];
6256
+ const seen = /* @__PURE__ */ new Set();
6257
+ for (const comment of comments) {
6258
+ if (isLinearBotComment(comment)) {
6259
+ tickets.push(...extractLinearTicketsFromBody(comment.body || "", seen));
6260
+ }
6261
+ }
6262
+ return tickets;
6263
+ }
6244
6264
  var userNamePattern = /^(https?:\/\/)([^@]+@)?([^/]+\/.+)$/;
6245
6265
  var sshPattern = /^git@([\w.-]+):([\w./-]+)$/;
6246
6266
  function normalizeUrl(repoUrl) {
@@ -7124,11 +7144,11 @@ var SCMLib = class {
7124
7144
  }
7125
7145
  /**
7126
7146
  * Extract Linear ticket links from PR/MR comments.
7127
- * Default implementation returns empty array - subclasses can override.
7147
+ * Uses shared isLinearBotComment() for unified bot detection across all providers.
7128
7148
  * Public so it can be reused by backend services.
7129
7149
  */
7130
- extractLinearTicketsFromComments(_comments) {
7131
- return [];
7150
+ extractLinearTicketsFromComments(comments) {
7151
+ return extractLinearTicketsFromComments(comments);
7132
7152
  }
7133
7153
  _validateAccessTokenAndUrl() {
7134
7154
  this._validateAccessToken();
@@ -8829,7 +8849,7 @@ function determinePrStatus(state, isDraft) {
8829
8849
  return isDraft ? "DRAFT" /* Draft */ : "ACTIVE" /* Active */;
8830
8850
  }
8831
8851
  }
8832
- var GithubSCMLib = class _GithubSCMLib extends SCMLib {
8852
+ var GithubSCMLib = class extends SCMLib {
8833
8853
  // we don't always need a url, what's important is that we have an access token
8834
8854
  constructor(url, accessToken, scmOrg) {
8835
8855
  super(url, accessToken, scmOrg);
@@ -9301,27 +9321,6 @@ var GithubSCMLib = class _GithubSCMLib extends SCMLib {
9301
9321
  commentIds
9302
9322
  };
9303
9323
  }
9304
- /**
9305
- * Extract Linear ticket links from pre-fetched comments (pure function, no API calls)
9306
- * Instance method that overrides base class - can also be called statically for backwards compatibility.
9307
- */
9308
- extractLinearTicketsFromComments(comments) {
9309
- return _GithubSCMLib._extractLinearTicketsFromCommentsImpl(comments);
9310
- }
9311
- /**
9312
- * Static implementation for backwards compatibility and reuse.
9313
- * Called by both the instance method and direct static calls.
9314
- */
9315
- static _extractLinearTicketsFromCommentsImpl(comments) {
9316
- const tickets = [];
9317
- const seen = /* @__PURE__ */ new Set();
9318
- for (const comment of comments) {
9319
- if (comment.author?.login === "linear[bot]" || comment.author?.type === "Bot") {
9320
- tickets.push(...extractLinearTicketsFromBody(comment.body || "", seen));
9321
- }
9322
- }
9323
- return tickets;
9324
- }
9325
9324
  };
9326
9325
 
9327
9326
  // src/features/analysis/scm/gitlab/gitlab.ts
@@ -9565,34 +9564,74 @@ async function getGitlabIsRemoteBranch({
9565
9564
  return false;
9566
9565
  }
9567
9566
  }
9568
- async function getGitlabRepoList(url, accessToken) {
9567
+ async function searchGitlabProjects({
9568
+ url,
9569
+ accessToken,
9570
+ perPage = 20,
9571
+ page = 1
9572
+ }) {
9573
+ if (perPage > GITLAB_MAX_PER_PAGE) {
9574
+ throw new Error(
9575
+ `perPage ${perPage} exceeds GitLab maximum of ${GITLAB_MAX_PER_PAGE}`
9576
+ );
9577
+ }
9569
9578
  const api2 = getGitBeaker({ url, gitlabAuthToken: accessToken });
9570
- const res = await api2.Projects.all({
9571
- membership: true,
9572
- //TODO: a bug in the sorting mechanism of this api call
9573
- //disallows us to sort by updated_at in descending order
9574
- //so we have to sort by updated_at in ascending order.
9575
- //We can wait for the bug to be fixed or call the api
9576
- //directly with fetch()
9577
- sort: "asc",
9578
- orderBy: "updated_at",
9579
- perPage: 100
9580
- });
9581
- return Promise.all(
9582
- res.map(async (project) => {
9583
- const proj = await api2.Projects.show(project.id);
9584
- const owner = proj.namespace.name;
9585
- const repoLanguages = await api2.Projects.showLanguages(project.id);
9586
- return {
9587
- repoName: project.path,
9588
- repoUrl: project.web_url,
9589
- repoOwner: owner,
9590
- repoLanguages: Object.keys(repoLanguages),
9591
- repoIsPublic: project.visibility === "public",
9592
- repoUpdatedAt: project.last_activity_at
9593
- };
9594
- })
9595
- );
9579
+ let response;
9580
+ try {
9581
+ response = await api2.Projects.all({
9582
+ membership: true,
9583
+ orderBy: "last_activity_at",
9584
+ sort: "desc",
9585
+ pagination: "offset",
9586
+ perPage,
9587
+ page,
9588
+ showExpanded: true
9589
+ });
9590
+ } catch (e) {
9591
+ debug4(
9592
+ "[searchGitlabProjects] order_by=last_activity_at failed, falling back to created_at: %s",
9593
+ e instanceof Error ? e.message : String(e)
9594
+ );
9595
+ response = await api2.Projects.all({
9596
+ membership: true,
9597
+ orderBy: "created_at",
9598
+ sort: "desc",
9599
+ pagination: "offset",
9600
+ perPage,
9601
+ page,
9602
+ showExpanded: true
9603
+ });
9604
+ }
9605
+ const projects = response.data.map((p) => ({
9606
+ id: p.id,
9607
+ path: p.path,
9608
+ web_url: p.web_url,
9609
+ namespace_name: p.namespace?.name ?? "",
9610
+ visibility: p.visibility,
9611
+ last_activity_at: p.last_activity_at
9612
+ }));
9613
+ return {
9614
+ projects,
9615
+ hasMore: response.paginationInfo.next !== null
9616
+ };
9617
+ }
9618
+ async function getGitlabProjectLanguages({
9619
+ url,
9620
+ accessToken,
9621
+ projectId
9622
+ }) {
9623
+ try {
9624
+ const api2 = getGitBeaker({ url, gitlabAuthToken: accessToken });
9625
+ const languages3 = await api2.Projects.showLanguages(projectId);
9626
+ return Object.keys(languages3);
9627
+ } catch (e) {
9628
+ debug4(
9629
+ "[getGitlabProjectLanguages] Failed for project %d: %s",
9630
+ projectId,
9631
+ e instanceof Error ? e.message : String(e)
9632
+ );
9633
+ return [];
9634
+ }
9596
9635
  }
9597
9636
  async function getGitlabBranchList({
9598
9637
  accessToken,
@@ -9662,6 +9701,11 @@ async function searchGitlabMergeRequests({
9662
9701
  perPage = GITLAB_PER_PAGE,
9663
9702
  page = 1
9664
9703
  }) {
9704
+ if (perPage > GITLAB_MAX_PER_PAGE) {
9705
+ throw new Error(
9706
+ `perPage ${perPage} exceeds GitLab maximum of ${GITLAB_MAX_PER_PAGE}`
9707
+ );
9708
+ }
9665
9709
  const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
9666
9710
  debug4(
9667
9711
  "[searchGitlabMergeRequests] Fetching MRs for %s (page=%d, perPage=%d)",
@@ -9673,16 +9717,17 @@ async function searchGitlabMergeRequests({
9673
9717
  url: repoUrl,
9674
9718
  gitlabAuthToken: accessToken
9675
9719
  });
9676
- const mergeRequests = await api2.MergeRequests.all({
9720
+ const response = await api2.MergeRequests.all({
9677
9721
  projectId: projectPath,
9678
9722
  state: state === "all" ? void 0 : state,
9679
9723
  updatedAfter: updatedAfter?.toISOString(),
9680
9724
  orderBy,
9681
9725
  sort,
9682
9726
  perPage,
9683
- page
9727
+ page,
9728
+ showExpanded: true
9684
9729
  });
9685
- const items = mergeRequests.map((mr) => ({
9730
+ const items = response.data.map((mr) => ({
9686
9731
  iid: mr.iid,
9687
9732
  title: mr.title,
9688
9733
  state: mr.state,
@@ -9700,7 +9745,7 @@ async function searchGitlabMergeRequests({
9700
9745
  );
9701
9746
  return {
9702
9747
  items,
9703
- hasMore: mergeRequests.length === perPage
9748
+ hasMore: response.paginationInfo.next !== null
9704
9749
  };
9705
9750
  }
9706
9751
  var GITLAB_API_CONCURRENCY = 5;
@@ -9765,7 +9810,7 @@ async function getGitlabMrDataBatch({
9765
9810
  const comments = notes.map((note) => ({
9766
9811
  author: note.author ? {
9767
9812
  login: note.author.username,
9768
- type: note.author.username.endsWith("[bot]") || note.author.username.toLowerCase() === "linear" ? "Bot" : "User"
9813
+ type: note.author.username.endsWith("[bot]") ? "Bot" : "User"
9769
9814
  } : null,
9770
9815
  body: note.body
9771
9816
  }));
@@ -9897,7 +9942,8 @@ function parseGitlabOwnerAndRepo(gitlabUrl) {
9897
9942
  return { owner: organization, repo: repoName, projectPath };
9898
9943
  }
9899
9944
  var GITLAB_MAX_RESULTS_LIMIT = 1024;
9900
- var GITLAB_PER_PAGE = 128;
9945
+ var GITLAB_MAX_PER_PAGE = 100;
9946
+ var GITLAB_PER_PAGE = GITLAB_MAX_PER_PAGE;
9901
9947
  async function getGitlabRecentCommits({
9902
9948
  repoUrl,
9903
9949
  accessToken,
@@ -9906,14 +9952,17 @@ async function getGitlabRecentCommits({
9906
9952
  const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
9907
9953
  const api2 = getGitBeaker({ url: repoUrl, gitlabAuthToken: accessToken });
9908
9954
  const allCommits = [];
9955
+ const perPage = GITLAB_PER_PAGE;
9909
9956
  let page = 1;
9910
9957
  let hasMore = true;
9911
9958
  while (hasMore && allCommits.length < GITLAB_MAX_RESULTS_LIMIT) {
9912
- const commits = await api2.Commits.all(projectPath, {
9959
+ const response = await api2.Commits.all(projectPath, {
9913
9960
  since,
9914
- perPage: GITLAB_PER_PAGE,
9915
- page
9961
+ perPage,
9962
+ page,
9963
+ showExpanded: true
9916
9964
  });
9965
+ const commits = response.data;
9917
9966
  if (commits.length === 0) {
9918
9967
  hasMore = false;
9919
9968
  break;
@@ -9935,11 +9984,8 @@ async function getGitlabRecentCommits({
9935
9984
  parents: commit.parent_ids?.map((sha) => ({ sha })) || []
9936
9985
  });
9937
9986
  }
9938
- if (commits.length < GITLAB_PER_PAGE) {
9939
- hasMore = false;
9940
- } else {
9941
- page++;
9942
- }
9987
+ hasMore = response.paginationInfo.next !== null;
9988
+ page++;
9943
9989
  }
9944
9990
  if (allCommits.length >= GITLAB_MAX_RESULTS_LIMIT) {
9945
9991
  contextLogger.warn("[getGitlabRecentCommits] Hit commit pagination limit", {
@@ -10064,7 +10110,20 @@ var GitlabSCMLib = class extends SCMLib {
10064
10110
  contextLogger.warn("[GitlabSCMLib.getRepoList] No access token provided");
10065
10111
  throw new Error("no access token");
10066
10112
  }
10067
- return getGitlabRepoList(this.url, this.accessToken);
10113
+ const allRepos = [];
10114
+ let cursor;
10115
+ let hasMore = true;
10116
+ while (hasMore) {
10117
+ const page = await this.searchRepos({
10118
+ scmOrg: _scmOrg,
10119
+ limit: 100,
10120
+ cursor
10121
+ });
10122
+ allRepos.push(...page.results);
10123
+ hasMore = page.hasMore;
10124
+ cursor = page.nextCursor;
10125
+ }
10126
+ return allRepos;
10068
10127
  }
10069
10128
  async getBranchList() {
10070
10129
  this._validateAccessTokenAndUrl();
@@ -10294,8 +10353,47 @@ var GitlabSCMLib = class extends SCMLib {
10294
10353
  mrNumbers: prNumbers
10295
10354
  });
10296
10355
  }
10297
- async searchRepos(_params) {
10298
- throw new Error("searchRepos not implemented for GitLab");
10356
+ async searchRepos(params) {
10357
+ if (!this.accessToken) {
10358
+ throw new Error("no access token");
10359
+ }
10360
+ const page = parseCursorSafe(params.cursor, 1);
10361
+ const perPage = params.limit || 10;
10362
+ const { projects, hasMore } = await searchGitlabProjects({
10363
+ url: this.url,
10364
+ accessToken: this.accessToken,
10365
+ perPage,
10366
+ page
10367
+ });
10368
+ const includeLanguages = params.includeLanguages !== false;
10369
+ const languageMap = /* @__PURE__ */ new Map();
10370
+ if (includeLanguages && projects.length > 0) {
10371
+ const languageResults = await Promise.all(
10372
+ projects.map(
10373
+ (p) => getGitlabProjectLanguages({
10374
+ url: this.url,
10375
+ accessToken: this.accessToken,
10376
+ projectId: p.id
10377
+ }).then((langs) => [p.id, langs])
10378
+ )
10379
+ );
10380
+ for (const [id, langs] of languageResults) {
10381
+ languageMap.set(id, langs);
10382
+ }
10383
+ }
10384
+ const results = projects.map((p) => ({
10385
+ repoName: p.path,
10386
+ repoUrl: p.web_url,
10387
+ repoOwner: p.namespace_name,
10388
+ repoLanguages: languageMap.get(p.id) || [],
10389
+ repoIsPublic: p.visibility === "public",
10390
+ repoUpdatedAt: p.last_activity_at
10391
+ }));
10392
+ return {
10393
+ results,
10394
+ nextCursor: hasMore ? String(page + 1) : void 0,
10395
+ hasMore
10396
+ };
10299
10397
  }
10300
10398
  async getPullRequestMetrics(prNumber) {
10301
10399
  this._validateAccessTokenAndUrl();
@@ -10341,23 +10439,6 @@ var GitlabSCMLib = class extends SCMLib {
10341
10439
  accessToken: this.accessToken
10342
10440
  });
10343
10441
  }
10344
- /**
10345
- * Extract Linear ticket links from pre-fetched comments (pure function, no API calls).
10346
- * Linear bot uses the same comment format on GitLab as on GitHub.
10347
- * Bot username may be 'linear' or 'linear[bot]' on GitLab.
10348
- */
10349
- extractLinearTicketsFromComments(comments) {
10350
- const tickets = [];
10351
- const seen = /* @__PURE__ */ new Set();
10352
- for (const comment of comments) {
10353
- const authorLogin = comment.author?.login?.toLowerCase() || "";
10354
- const isLinearBot = authorLogin === "linear" || authorLogin === "linear[bot]" || comment.author?.type === "Bot" && authorLogin.includes("linear");
10355
- if (isLinearBot) {
10356
- tickets.push(...extractLinearTicketsFromBody(comment.body || "", seen));
10357
- }
10358
- }
10359
- return tickets;
10360
- }
10361
10442
  };
10362
10443
 
10363
10444
  // src/features/analysis/scm/scmFactory.ts
@@ -14948,8 +15029,8 @@ var openRedaction = new OpenRedaction({
14948
15029
  "MONERO_ADDRESS",
14949
15030
  "RIPPLE_ADDRESS",
14950
15031
  // Medical Data (removed PRESCRIPTION_NUMBER - too broad, matches words containing "ription")
15032
+ // Removed MEDICAL_RECORD_NUMBER - too broad, "MR" prefix matches "Merge Request" in SCM contexts (e.g. "MR branches" → "MR br****es")
14951
15033
  "NHS_NUMBER",
14952
- "MEDICAL_RECORD_NUMBER",
14953
15034
  "AUSTRALIAN_MEDICARE",
14954
15035
  "HEALTH_PLAN_NUMBER",
14955
15036
  "PATIENT_ID",
@@ -14993,10 +15074,10 @@ var openRedaction = new OpenRedaction({
14993
15074
  "DOCKER_AUTH",
14994
15075
  "KUBERNETES_SECRET",
14995
15076
  // Government & Legal
15077
+ // Removed CLIENT_ID - too broad, "client" is ubiquitous in code (npm packages like @scope/client-*, class names like ClientSdkOptions)
14996
15078
  "POLICE_REPORT_NUMBER",
14997
15079
  "IMMIGRATION_NUMBER",
14998
- "COURT_REPORTER_LICENSE",
14999
- "CLIENT_ID"
15080
+ "COURT_REPORTER_LICENSE"
15000
15081
  ]
15001
15082
  });
15002
15083
  function maskString(str, showStart = 2, showEnd = 2) {
@@ -15019,6 +15100,15 @@ async function sanitizeDataWithCounts(obj) {
15019
15100
  ...piiDetections.low
15020
15101
  ];
15021
15102
  for (const detection of allDetections) {
15103
+ if (detection.type === "CREDIT_CARD") {
15104
+ const start = detection.position[0];
15105
+ const end = detection.position[1];
15106
+ const charBefore = (start > 0 ? str[start - 1] : "") ?? "";
15107
+ const charAfter = str[end] ?? "";
15108
+ if (charBefore === "." || charBefore >= "0" && charBefore <= "9" || charAfter >= "0" && charAfter <= "9") {
15109
+ continue;
15110
+ }
15111
+ }
15022
15112
  counts.detections.total++;
15023
15113
  if (detection.severity === "high") counts.detections.high++;
15024
15114
  else if (detection.severity === "medium") counts.detections.medium++;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobbdev",
3
- "version": "1.2.24",
3
+ "version": "1.2.28",
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",