mobbdev 1.4.67 → 1.5.0

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 CHANGED
@@ -34,6 +34,18 @@ function getSdk(client, withWrapper = defaultWrapper) {
34
34
  getAnalysisSubscription(variables, requestHeaders, signal) {
35
35
  return withWrapper((wrappedRequestHeaders) => client.request({ document: GetAnalysisSubscriptionDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getAnalysisSubscription", "subscription", variables);
36
36
  },
37
+ GetOrganizationId(variables, requestHeaders, signal) {
38
+ return withWrapper((wrappedRequestHeaders) => client.request({ document: GetOrganizationIdDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetOrganizationId", "query", variables);
39
+ },
40
+ UpdateOrgScanPolicy(variables, requestHeaders, signal) {
41
+ return withWrapper((wrappedRequestHeaders) => client.request({ document: UpdateOrgScanPolicyDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "UpdateOrgScanPolicy", "mutation", variables);
42
+ },
43
+ GetReportFindingRuleIds(variables, requestHeaders, signal) {
44
+ return withWrapper((wrappedRequestHeaders) => client.request({ document: GetReportFindingRuleIdsDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetReportFindingRuleIds", "query", variables);
45
+ },
46
+ getGateVerdict(variables, requestHeaders, signal) {
47
+ return withWrapper((wrappedRequestHeaders) => client.request({ document: GetGateVerdictDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getGateVerdict", "query", variables);
48
+ },
37
49
  getAnalysis(variables, requestHeaders, signal) {
38
50
  return withWrapper((wrappedRequestHeaders) => client.request({ document: GetAnalysisDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getAnalysis", "query", variables);
39
51
  },
@@ -150,7 +162,7 @@ function getSdk(client, withWrapper = defaultWrapper) {
150
162
  }
151
163
  };
152
164
  }
153
- var AiBlameInferenceType, FixQuestionInputType, Language, ManifestAction, Effort_To_Apply_Fix_Enum, Fix_Rating_Tag_Enum, Fix_Report_State_Enum, Fix_State_Enum, IssueLanguage_Enum, Pr_Status_Enum, Project_Role_Type_Enum, Scan_Source_Enum, Vulnerability_Report_Issue_Category_Enum, Vulnerability_Report_Issue_State_Enum, Vulnerability_Report_Issue_Tag_Enum, Vulnerability_Report_Vendor_Enum, Vulnerability_Severity_Enum, FixDetailsFragmentDoc, FixReportSummaryFieldsFragmentDoc, MeDocument, GetLastOrgAndNamedProjectDocument, GetLastOrgDocument, GetEncryptedApiTokenDocument, FixReportStateDocument, GetVulnerabilityReportPathsDocument, GetAnalysisSubscriptionDocument, GetAnalysisDocument, GetFixesDocument, GetVulByNodesMetadataDocument, GetFalsePositiveDocument, UpdateScmTokenDocument, UploadS3BucketInfoDocument, GetTracyDiffUploadUrlDocument, AnalyzeCommitForExtensionAiBlameDocument, GetAiBlameInferenceDocument, GetAiBlameAttributionPromptDocument, GetPromptSummaryDocument, UploadAiBlameInferencesInitDocument, FinalizeAiBlameInferencesUploadDocument, UploadTracyRecordsDocument, GetTracyRawDataUploadUrlDocument, DigestVulnerabilityReportDocument, SubmitVulnerabilityReportDocument, CreateCommunityUserDocument, CreateCliLoginDocument, PerformCliLoginDocument, SetQuarantineEnabledDocument, CreateProjectDocument, ValidateRepoUrlDocument, GitReferenceDocument, AutoPrAnalysisDocument, GetFixWithAnswersDocument, GetFixReportsByRepoUrlDocument, GetReportFixesDocument, GetLatestReportByRepoUrlDocument, UpdateDownloadedFixDataDocument, GetUserMvsAutoFixDocument, StreamBlameAiAnalysisRequestsDocument, StreamCommitBlameRequestsDocument, ScanSkillDocument, SkillVerdictsByMd5Document, LogMvsEventDocument, GetMvsProjectDocument, IssueTypesDocument, defaultWrapper;
165
+ var AiBlameInferenceType, FixQuestionInputType, Language, ManifestAction, Effort_To_Apply_Fix_Enum, Fix_Rating_Tag_Enum, Fix_Report_State_Enum, Fix_State_Enum, IssueLanguage_Enum, Pr_Status_Enum, Project_Role_Type_Enum, Scan_Source_Enum, Vulnerability_Report_Issue_Category_Enum, Vulnerability_Report_Issue_State_Enum, Vulnerability_Report_Issue_Tag_Enum, Vulnerability_Report_Vendor_Enum, Vulnerability_Severity_Enum, FixDetailsFragmentDoc, FixReportSummaryFieldsFragmentDoc, MeDocument, GetLastOrgAndNamedProjectDocument, GetLastOrgDocument, GetEncryptedApiTokenDocument, FixReportStateDocument, GetVulnerabilityReportPathsDocument, GetAnalysisSubscriptionDocument, GetOrganizationIdDocument, UpdateOrgScanPolicyDocument, GetReportFindingRuleIdsDocument, GetGateVerdictDocument, GetAnalysisDocument, GetFixesDocument, GetVulByNodesMetadataDocument, GetFalsePositiveDocument, UpdateScmTokenDocument, UploadS3BucketInfoDocument, GetTracyDiffUploadUrlDocument, AnalyzeCommitForExtensionAiBlameDocument, GetAiBlameInferenceDocument, GetAiBlameAttributionPromptDocument, GetPromptSummaryDocument, UploadAiBlameInferencesInitDocument, FinalizeAiBlameInferencesUploadDocument, UploadTracyRecordsDocument, GetTracyRawDataUploadUrlDocument, DigestVulnerabilityReportDocument, SubmitVulnerabilityReportDocument, CreateCommunityUserDocument, CreateCliLoginDocument, PerformCliLoginDocument, SetQuarantineEnabledDocument, CreateProjectDocument, ValidateRepoUrlDocument, GitReferenceDocument, AutoPrAnalysisDocument, GetFixWithAnswersDocument, GetFixReportsByRepoUrlDocument, GetReportFixesDocument, GetLatestReportByRepoUrlDocument, UpdateDownloadedFixDataDocument, GetUserMvsAutoFixDocument, StreamBlameAiAnalysisRequestsDocument, StreamCommitBlameRequestsDocument, ScanSkillDocument, SkillVerdictsByMd5Document, LogMvsEventDocument, GetMvsProjectDocument, IssueTypesDocument, defaultWrapper;
154
166
  var init_client_generates = __esm({
155
167
  "src/features/analysis/scm/generates/client_generates.ts"() {
156
168
  "use strict";
@@ -558,6 +570,66 @@ var init_client_generates = __esm({
558
570
  state
559
571
  failReason
560
572
  }
573
+ }
574
+ `;
575
+ GetOrganizationIdDocument = `
576
+ query GetOrganizationId {
577
+ organization {
578
+ id
579
+ }
580
+ }
581
+ `;
582
+ UpdateOrgScanPolicyDocument = `
583
+ mutation UpdateOrgScanPolicy($organizationId: String!, $policyJson: String) {
584
+ updateOrganizationScanPolicy(
585
+ organizationId: $organizationId
586
+ policyJson: $policyJson
587
+ ) {
588
+ __typename
589
+ ... on ScanPolicyUpdateSuccess {
590
+ storedPolicyJson
591
+ warnings
592
+ }
593
+ ... on ScanPolicyValidationError {
594
+ error
595
+ }
596
+ }
597
+ }
598
+ `;
599
+ GetReportFindingRuleIdsDocument = `
600
+ query GetReportFindingRuleIds($fixReportId: uuid!) {
601
+ fixReport_by_pk(id: $fixReportId) {
602
+ vulnerabilityReport {
603
+ vulnerabilityReportIssues {
604
+ extraData(path: "rule_id")
605
+ }
606
+ }
607
+ }
608
+ }
609
+ `;
610
+ GetGateVerdictDocument = `
611
+ query getGateVerdict($fixReportId: String!) {
612
+ getGateVerdict(fixReportId: $fixReportId) {
613
+ __typename
614
+ ... on GateVerdictSuccess {
615
+ blocking
616
+ blockingCount
617
+ totalIssues
618
+ criticalCount
619
+ highCount
620
+ mediumCount
621
+ lowCount
622
+ unclassifiedCount
623
+ description
624
+ degraded
625
+ }
626
+ ... on GateVerdictNotReady {
627
+ state
628
+ }
629
+ ... on GateVerdictError {
630
+ error
631
+ }
632
+ }
561
633
  }
562
634
  `;
563
635
  GetAnalysisDocument = `
@@ -643,35 +715,20 @@ var init_client_generates = __esm({
643
715
  }
644
716
  `;
645
717
  GetVulByNodesMetadataDocument = `
646
- query getVulByNodesMetadata($filters: [vulnerability_report_issue_code_node_bool_exp!], $vulnerabilityReportId: uuid!) {
647
- vulnerabilityReportIssueCodeNodes: vulnerability_report_issue_code_node(
648
- order_by: {index: desc}
649
- where: {_or: $filters, vulnerabilityReportIssue: {fixId: {_is_null: false}, vulnerabilityReportId: {_eq: $vulnerabilityReportId}}}
650
- ) {
651
- vulnerabilityReportIssueId
652
- path
653
- startLine
654
- vulnerabilityReportIssue {
655
- safeIssueType
656
- fixId
657
- category
658
- vulnerabilityReportIssueTags {
659
- tag: vulnerability_report_issue_tag_value
660
- }
661
- }
662
- }
663
- fixablePrVuls: vulnerability_report_issue_aggregate(
664
- where: {fixId: {_is_null: false}, vulnerabilityReportId: {_eq: $vulnerabilityReportId}, codeNodes: {_or: $filters}}
718
+ query getVulByNodesMetadata($pathFilters: [vulnerability_report_issue_bool_exp!], $vulnerabilityReportId: uuid!) {
719
+ vulnerabilityReportIssues: vulnerability_report_issue(
720
+ where: {vulnerabilityReportId: {_eq: $vulnerabilityReportId}, _or: $pathFilters}
665
721
  ) {
666
- aggregate {
667
- count
722
+ id
723
+ safeIssueType
724
+ fixId
725
+ fpId
726
+ category
727
+ vulnerabilityReportIssueTags {
728
+ tag: vulnerability_report_issue_tag_value
668
729
  }
669
- }
670
- nonFixablePrVuls: vulnerability_report_issue_aggregate(
671
- where: {fixId: {_is_null: true}, vulnerabilityReportId: {_eq: $vulnerabilityReportId}, codeNodes: {_or: $filters}}
672
- ) {
673
- aggregate {
674
- count
730
+ codeNodesJson {
731
+ nodes
675
732
  }
676
733
  }
677
734
  totalScanVulnerabilities: vulnerability_report_issue_aggregate(
@@ -681,26 +738,6 @@ var init_client_generates = __esm({
681
738
  count
682
739
  }
683
740
  }
684
- irrelevantVulnerabilityReportIssue: vulnerability_report(
685
- where: {id: {_eq: $vulnerabilityReportId}}
686
- ) {
687
- vulnerabilityReportIssues(
688
- where: {fixId: {_is_null: true}, category: {_in: [Irrelevant, FalsePositive, Filtered]}, _not: {vulnerabilityReportIssueTags: {vulnerability_report_issue_tag_value: {_eq: SUPPRESSED}}}}
689
- ) {
690
- id
691
- safeIssueType
692
- fixId
693
- category
694
- vulnerabilityReportIssueTags {
695
- tag: vulnerability_report_issue_tag_value
696
- }
697
- codeNodes(order_by: {index: desc}, where: {_or: $filters}, limit: 1) {
698
- path
699
- startLine
700
- }
701
- fpId
702
- }
703
- }
704
741
  }
705
742
  `;
706
743
  GetFalsePositiveDocument = `
@@ -1862,7 +1899,7 @@ var init_validations = __esm({
1862
1899
 
1863
1900
  // src/features/analysis/scm/shared/src/types/types.ts
1864
1901
  import { z as z7 } from "zod";
1865
- var OrganizationScreenQueryParamsZ, ProjectPageQueryParamsZ, AnalysisPageQueryParamsZ, FixPageQueryParamsZ, IssuePageQueryParamsZ, CliLoginPageQueryParamsZ, AnalysisReportDigestedZ, IssueSharedStateZ2, ReportQueryResultZ, ReportFixesQueryFixZ, BaseVulnerabilityReportIssueZ, VulnerabilityReportIssueZ, VulnerabilityReportIssueWithCodeFilePathZ, GetReportIssuesQueryZ, FixReportByProjectZ, FixScreenQueryResultZ, FixPageQueryZ, GetReportFixesQueryZ, GetFixReportStatsQueryZ, ProjectVulnerabilityReport, GetProjectsQueryZ, ProjectPageQueryResultZ, GetProjectMembersDataZ, RepoArgsZ, scmCloudUrl, ScmType, ConvertToSarifInputFileFormat;
1902
+ var OrganizationScreenQueryParamsZ, ProjectPageQueryParamsZ, AnalysisPageQueryParamsZ, FixPageQueryParamsZ, IssuePageQueryParamsZ, CliLoginPageQueryParamsZ, AnalysisReportDigestedZ, IssueSharedStateZ2, ReportQueryResultZ, ReportFixesQueryFixZ, BaseVulnerabilityReportIssueZ, VulnerabilityReportIssueWithCodeFilePathZ, GetReportIssuesQueryZ, FixReportByProjectZ, FixScreenQueryResultZ, FixPageQueryZ, GetReportFixesQueryZ, GetFixReportStatsQueryZ, ProjectVulnerabilityReport, GetProjectsQueryZ, ProjectPageQueryResultZ, GetProjectMembersDataZ, RepoArgsZ, scmCloudUrl, ScmType, ConvertToSarifInputFileFormat;
1866
1903
  var init_types = __esm({
1867
1904
  "src/features/analysis/scm/shared/src/types/types.ts"() {
1868
1905
  "use strict";
@@ -2124,11 +2161,6 @@ var init_types = __esm({
2124
2161
  ),
2125
2162
  sharedState: VulnerabilityReportIssueSharedStateZ
2126
2163
  });
2127
- VulnerabilityReportIssueZ = BaseVulnerabilityReportIssueZ.extend(
2128
- z7.object({
2129
- codeNodes: z7.array(z7.object({ path: z7.string() }))
2130
- }).shape
2131
- );
2132
2164
  VulnerabilityReportIssueWithCodeFilePathZ = BaseVulnerabilityReportIssueZ.extend(
2133
2165
  z7.object({
2134
2166
  codeFilePath: z7.string().nullable()
@@ -4263,7 +4295,7 @@ import Debug23 from "debug";
4263
4295
  import { hideBin } from "yargs/helpers";
4264
4296
 
4265
4297
  // src/args/yargs.ts
4266
- import chalk15 from "chalk";
4298
+ import chalk16 from "chalk";
4267
4299
  import yargs from "yargs/yargs";
4268
4300
 
4269
4301
  // src/args/commands/convert_to_sarif.ts
@@ -10795,6 +10827,17 @@ function createGitWithLogging(dirName, logger3 = defaultLogger) {
10795
10827
  // src/utils/index.ts
10796
10828
  var sleep = (ms = 2e3) => new Promise((r) => setTimeout(r, ms));
10797
10829
  var CliError = class extends Error {
10830
+ constructor(message, options = {}) {
10831
+ super(message);
10832
+ /**
10833
+ * The process exit code index.ts maps this error to. Defaults to 1 (plain
10834
+ * operational error) so every existing throw keeps its behavior; 2 means the
10835
+ * gate policy blocked the run (MOBB-3810), so CI can tell "your code is
10836
+ * gated" from "the tool broke".
10837
+ */
10838
+ __publicField(this, "exitCode");
10839
+ this.exitCode = options.exitCode ?? 1;
10840
+ }
10798
10841
  };
10799
10842
 
10800
10843
  // src/commands/fpr_stream_parser.ts
@@ -11441,6 +11484,13 @@ var scanOnlyOption = {
11441
11484
  type: "boolean",
11442
11485
  default: false
11443
11486
  };
11487
+ var gateOption = {
11488
+ describe: chalk2.bold(
11489
+ "Enforce the organization's Scan & Gate Policy: after the scan, the backend decides whether any finding blocks, and the CLI exits 2 when it does (0 = pass, 1 = operational error; a scan that fails to finish exits 1, not 2). Mobb scans only \u2014 cannot be combined with --scan-file. In PR pipelines pair it with --baseline-commit so only findings new in the PR can block."
11490
+ ),
11491
+ type: "boolean",
11492
+ default: false
11493
+ };
11444
11494
  var pollingOption = {
11445
11495
  describe: chalk2.bold(
11446
11496
  "Use HTTP polling instead of WebSocket for status updates. Useful for proxy environments or firewalls that block WebSocket connections. Polling interval: 5 seconds, timeout: 30 minutes."
@@ -11494,10 +11544,10 @@ var ScanContext = {
11494
11544
 
11495
11545
  // src/args/commands/analyze.ts
11496
11546
  import fs13 from "fs";
11497
- import chalk10 from "chalk";
11547
+ import chalk11 from "chalk";
11498
11548
 
11499
11549
  // src/commands/index.ts
11500
- import chalk8 from "chalk";
11550
+ import chalk9 from "chalk";
11501
11551
  import chalkAnimation from "chalk-animation";
11502
11552
 
11503
11553
  // src/features/analysis/index.ts
@@ -11506,7 +11556,7 @@ import fsPromises3 from "fs/promises";
11506
11556
  import path11 from "path";
11507
11557
  import { env as env2 } from "process";
11508
11558
  import { pipeline as pipeline2 } from "stream/promises";
11509
- import chalk7 from "chalk";
11559
+ import chalk8 from "chalk";
11510
11560
  import Debug22 from "debug";
11511
11561
  import extract from "extract-zip";
11512
11562
  import { createSpinner as createSpinner4 } from "nanospinner";
@@ -11828,65 +11878,104 @@ init_client_generates();
11828
11878
  // src/features/analysis/graphql/types.ts
11829
11879
  init_client_generates();
11830
11880
  import { z as z22 } from "zod";
11831
- var VulnerabilityReportIssueCodeNodeZ = z22.object({
11832
- vulnerabilityReportIssueId: z22.string(),
11881
+ function codeNodesJsonPathFilter(path38) {
11882
+ return { codeNodesJson: { nodes: { _contains: [{ path: path38 }] } } };
11883
+ }
11884
+ var VulByNodesMetadataCodeNodeZ = z22.object({
11885
+ index: z22.number().int(),
11833
11886
  path: z22.string(),
11834
- startLine: z22.number(),
11835
- vulnerabilityReportIssue: z22.object({
11836
- fixId: z22.string(),
11837
- category: z22.enum(Vulnerability_Report_Issue_Category_Enum),
11838
- safeIssueType: z22.string(),
11839
- vulnerabilityReportIssueTags: z22.array(
11840
- z22.object({
11841
- tag: z22.enum(Vulnerability_Report_Issue_Tag_Enum)
11842
- })
11843
- )
11844
- })
11845
- });
11846
- var VulnerabilityReportIssueNoFixCodeNodeZ = z22.object({
11847
- vulnerabilityReportIssues: z22.array(
11887
+ startLine: z22.number().int(),
11888
+ endLine: z22.number().int()
11889
+ }).loose();
11890
+ var VulnerabilityReportIssueWithNodesZ = z22.object({
11891
+ id: z22.string(),
11892
+ fixId: z22.string().nullable(),
11893
+ category: z22.enum(Vulnerability_Report_Issue_Category_Enum),
11894
+ safeIssueType: z22.string(),
11895
+ fpId: z22.guid().nullable(),
11896
+ vulnerabilityReportIssueTags: z22.array(
11848
11897
  z22.object({
11849
- id: z22.string(),
11850
- fixId: z22.string().nullable(),
11851
- category: z22.enum(Vulnerability_Report_Issue_Category_Enum),
11852
- safeIssueType: z22.string(),
11853
- fpId: z22.guid().nullable(),
11854
- codeNodes: z22.array(
11855
- z22.object({
11856
- path: z22.string(),
11857
- startLine: z22.number()
11858
- })
11859
- ),
11860
- vulnerabilityReportIssueTags: z22.array(
11861
- z22.object({
11862
- tag: z22.enum(Vulnerability_Report_Issue_Tag_Enum)
11863
- })
11864
- )
11898
+ tag: z22.enum(Vulnerability_Report_Issue_Tag_Enum)
11865
11899
  })
11866
- )
11900
+ ),
11901
+ codeNodesJson: z22.object({ nodes: z22.unknown() }).nullable()
11867
11902
  });
11868
11903
  var GetVulByNodesMetadataZ = z22.object({
11869
- vulnerabilityReportIssueCodeNodes: z22.array(VulnerabilityReportIssueCodeNodeZ),
11870
- nonFixablePrVuls: z22.object({
11871
- aggregate: z22.object({
11872
- count: z22.number()
11873
- })
11874
- }),
11875
- fixablePrVuls: z22.object({
11876
- aggregate: z22.object({
11877
- count: z22.number()
11878
- })
11879
- }),
11904
+ vulnerabilityReportIssues: z22.array(VulnerabilityReportIssueWithNodesZ),
11880
11905
  totalScanVulnerabilities: z22.object({
11881
11906
  aggregate: z22.object({
11882
11907
  count: z22.number()
11883
11908
  })
11884
- }),
11885
- irrelevantVulnerabilityReportIssue: z22.array(
11886
- VulnerabilityReportIssueNoFixCodeNodeZ
11887
- )
11909
+ })
11888
11910
  });
11889
11911
 
11912
+ // src/features/analysis/graphql/matchVulByNodesMetadata.ts
11913
+ var IRRELEVANT_CATEGORIES = /* @__PURE__ */ new Set([
11914
+ "Irrelevant",
11915
+ "FalsePositive",
11916
+ "Filtered"
11917
+ ]);
11918
+ var SUPPRESSED_TAG = "SUPPRESSED";
11919
+ function nodeMatchesHunks(node, hunks) {
11920
+ return hunks.some(
11921
+ (hunk) => hunk.path === node.path && hunk.ranges.some(
11922
+ ({ startLine, endLine }) => node.startLine >= startLine && node.startLine <= endLine || node.endLine >= startLine && node.endLine <= endLine
11923
+ )
11924
+ );
11925
+ }
11926
+ function matchingNodes(issue, hunks) {
11927
+ const rawNodes = issue.codeNodesJson?.nodes;
11928
+ if (!Array.isArray(rawNodes)) {
11929
+ return [];
11930
+ }
11931
+ return rawNodes.map((rawNode) => VulByNodesMetadataCodeNodeZ.safeParse(rawNode)).filter((parsed) => parsed.success).map((parsed) => parsed.data).filter((node) => nodeMatchesHunks(node, hunks)).sort((a, b) => b.index - a.index);
11932
+ }
11933
+ function matchVulByNodesMetadata(issues, hunks) {
11934
+ const vulnerabilityReportIssueCodeNodes = [];
11935
+ let fixablePrVuls = 0;
11936
+ let nonFixablePrVuls = 0;
11937
+ const irrelevantVulnerabilityReportIssues = [];
11938
+ for (const issue of issues) {
11939
+ const nodes = matchingNodes(issue, hunks);
11940
+ const bestNode = nodes[0];
11941
+ if (issue.fixId !== null && bestNode) {
11942
+ fixablePrVuls += 1;
11943
+ vulnerabilityReportIssueCodeNodes.push({
11944
+ vulnerabilityReportIssueId: issue.id,
11945
+ path: bestNode.path,
11946
+ startLine: bestNode.startLine,
11947
+ vulnerabilityReportIssue: {
11948
+ fixId: issue.fixId,
11949
+ category: issue.category,
11950
+ safeIssueType: issue.safeIssueType,
11951
+ vulnerabilityReportIssueTags: issue.vulnerabilityReportIssueTags
11952
+ }
11953
+ });
11954
+ } else if (issue.fixId === null && bestNode) {
11955
+ nonFixablePrVuls += 1;
11956
+ }
11957
+ if (issue.fixId === null && IRRELEVANT_CATEGORIES.has(issue.category) && !issue.vulnerabilityReportIssueTags.some(
11958
+ (issueTag) => issueTag.tag === SUPPRESSED_TAG
11959
+ )) {
11960
+ irrelevantVulnerabilityReportIssues.push({
11961
+ id: issue.id,
11962
+ safeIssueType: issue.safeIssueType,
11963
+ fixId: issue.fixId,
11964
+ category: issue.category,
11965
+ vulnerabilityReportIssueTags: issue.vulnerabilityReportIssueTags,
11966
+ codeNodes: bestNode ? [{ path: bestNode.path, startLine: bestNode.startLine }] : [],
11967
+ fpId: issue.fpId
11968
+ });
11969
+ }
11970
+ }
11971
+ return {
11972
+ vulnerabilityReportIssueCodeNodes,
11973
+ fixablePrVuls,
11974
+ nonFixablePrVuls,
11975
+ irrelevantVulnerabilityReportIssues
11976
+ };
11977
+ }
11978
+
11890
11979
  // src/features/analysis/graphql/gql.ts
11891
11980
  var debug7 = Debug6("mobbdev:gql");
11892
11981
  function isAuthError(error) {
@@ -12094,44 +12183,28 @@ var GQLClient = class {
12094
12183
  hunks,
12095
12184
  vulnerabilityReportId
12096
12185
  }) {
12097
- const filters = hunks.map((hunk) => {
12098
- const filter = {
12099
- path: { _eq: hunk.path },
12100
- _or: hunk.ranges.flatMap(({ endLine, startLine }) => {
12101
- return [
12102
- { startLine: { _gte: startLine, _lte: endLine } },
12103
- { endLine: { _gte: startLine, _lte: endLine } }
12104
- ];
12105
- })
12106
- };
12107
- return filter;
12108
- });
12186
+ const pathFilters = hunks.map((hunk) => codeNodesJsonPathFilter(hunk.path));
12109
12187
  const getVulByNodesMetadataRes = await this._clientSdk.getVulByNodesMetadata({
12110
- filters: { _or: filters },
12188
+ pathFilters,
12111
12189
  vulnerabilityReportId
12112
12190
  });
12113
12191
  const parsedGetVulByNodesMetadataRes = GetVulByNodesMetadataZ.parse(
12114
12192
  getVulByNodesMetadataRes
12115
12193
  );
12116
- const uniqueVulByNodesMetadata = parsedGetVulByNodesMetadataRes.vulnerabilityReportIssueCodeNodes.reduce((acc, vulnerabilityReportIssueCodeNode) => {
12117
- if (acc[vulnerabilityReportIssueCodeNode.vulnerabilityReportIssueId]) {
12118
- return acc;
12119
- }
12120
- return {
12121
- ...acc,
12122
- [vulnerabilityReportIssueCodeNode.vulnerabilityReportIssueId]: vulnerabilityReportIssueCodeNode
12123
- };
12124
- }, {});
12125
- const nonFixablePrVuls = parsedGetVulByNodesMetadataRes.nonFixablePrVuls.aggregate.count;
12126
- const fixablePrVuls = parsedGetVulByNodesMetadataRes.fixablePrVuls.aggregate.count;
12194
+ const {
12195
+ vulnerabilityReportIssueCodeNodes,
12196
+ fixablePrVuls,
12197
+ nonFixablePrVuls,
12198
+ irrelevantVulnerabilityReportIssues
12199
+ } = matchVulByNodesMetadata(
12200
+ parsedGetVulByNodesMetadataRes.vulnerabilityReportIssues,
12201
+ hunks
12202
+ );
12127
12203
  const totalScanVulnerabilities = parsedGetVulByNodesMetadataRes.totalScanVulnerabilities.aggregate.count;
12128
12204
  const vulnerabilitiesOutsidePr = totalScanVulnerabilities - nonFixablePrVuls - fixablePrVuls;
12129
12205
  const totalPrVulnerabilities = nonFixablePrVuls + fixablePrVuls;
12130
- const irrelevantVulnerabilityReportIssues = parsedGetVulByNodesMetadataRes.irrelevantVulnerabilityReportIssue?.[0]?.vulnerabilityReportIssues ?? [];
12131
12206
  return {
12132
- vulnerabilityReportIssueCodeNodes: Object.values(
12133
- uniqueVulByNodesMetadata
12134
- ),
12207
+ vulnerabilityReportIssueCodeNodes,
12135
12208
  nonFixablePrVuls,
12136
12209
  fixablePrVuls,
12137
12210
  totalScanVulnerabilities,
@@ -12339,6 +12412,14 @@ var GQLClient = class {
12339
12412
  }
12340
12413
  return res.analysis;
12341
12414
  }
12415
+ /**
12416
+ * The gate verdict for a Mobb-scan report (MOBB-3810). The backend is the
12417
+ * only decider; the caller maps the answer onto an exit code.
12418
+ */
12419
+ async getGateVerdict(fixReportId) {
12420
+ const res = await this._clientSdk.getGateVerdict({ fixReportId });
12421
+ return res.getGateVerdict;
12422
+ }
12342
12423
  async autoPrAnalysis({
12343
12424
  analysisId,
12344
12425
  commitDirectly,
@@ -14571,6 +14652,36 @@ async function handleAutoPr(params) {
14571
14652
  }
14572
14653
  }
14573
14654
 
14655
+ // src/features/analysis/gate_verdict.ts
14656
+ import chalk5 from "chalk";
14657
+ async function enforceGateVerdict({
14658
+ gqlClient,
14659
+ fixReportId
14660
+ }) {
14661
+ const verdict = await gqlClient.getGateVerdict(fixReportId);
14662
+ if (verdict.__typename === "GateVerdictError") {
14663
+ throw new CliError(`\u{1F6A7} Gate verdict unavailable: ${verdict.error}`);
14664
+ }
14665
+ if (verdict.__typename === "GateVerdictNotReady") {
14666
+ throw new CliError(
14667
+ `\u{1F6A7} Gate verdict not ready \u2014 report state is ${verdict.state}`
14668
+ );
14669
+ }
14670
+ if (verdict.degraded) {
14671
+ console.log(
14672
+ chalk5.yellow(
14673
+ "\u26A0\uFE0F The gate policy could not be read; the fail-closed default was applied."
14674
+ )
14675
+ );
14676
+ }
14677
+ if (verdict.blocking) {
14678
+ throw new CliError(chalk5.red(`\u{1F6A7} Gate: ${verdict.description}`), {
14679
+ exitCode: 2
14680
+ });
14681
+ }
14682
+ console.log(chalk5.green(`\u{1F6A7} Gate: ${verdict.description}`));
14683
+ }
14684
+
14574
14685
  // src/features/analysis/git.ts
14575
14686
  init_GitService();
14576
14687
  import Debug17 from "debug";
@@ -14799,7 +14910,7 @@ async function snykArticlePrompt() {
14799
14910
 
14800
14911
  // src/features/analysis/scanners/checkmarx.ts
14801
14912
  import { createRequire } from "module";
14802
- import chalk5 from "chalk";
14913
+ import chalk6 from "chalk";
14803
14914
  import Debug20 from "debug";
14804
14915
  import { existsSync } from "fs";
14805
14916
  import { createSpinner as createSpinner2 } from "nanospinner";
@@ -14970,7 +15081,7 @@ async function throwCheckmarxConfigError() {
14970
15081
  await createSpinner2("\u{1F513} Checkmarx is not configued correctly").start().error();
14971
15082
  throw new CliError(
14972
15083
  `Checkmarx is not configued correctly
14973
- you can configure it by using the ${chalk5.bold(
15084
+ you can configure it by using the ${chalk6.bold(
14974
15085
  "cx configure"
14975
15086
  )} command`
14976
15087
  );
@@ -14978,8 +15089,8 @@ async function throwCheckmarxConfigError() {
14978
15089
  async function validateCheckamxCredentials() {
14979
15090
  console.log(`
14980
15091
  Here's a suggestion for checkmarx configuation:
14981
- ${chalk5.bold("AST Base URI:")} https://ast.checkmarx.net
14982
- ${chalk5.bold("AST Base Auth URI (IAM):")} https://iam.checkmarx.net
15092
+ ${chalk6.bold("AST Base URI:")} https://ast.checkmarx.net
15093
+ ${chalk6.bold("AST Base Auth URI (IAM):")} https://iam.checkmarx.net
14983
15094
  `);
14984
15095
  await forkCheckmarx(CONFIGURE_COMMAND, { display: true });
14985
15096
  const { code: loginCode } = await forkCheckmarx(VALIDATE_COMMAND, {
@@ -15004,7 +15115,7 @@ import os4 from "os";
15004
15115
  import path10 from "path";
15005
15116
  import { Readable } from "stream";
15006
15117
  import { pipeline } from "stream/promises";
15007
- import chalk6 from "chalk";
15118
+ import chalk7 from "chalk";
15008
15119
  import Debug21 from "debug";
15009
15120
  import { createSpinner as createSpinner3 } from "nanospinner";
15010
15121
  import open2 from "open";
@@ -15168,7 +15279,7 @@ async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
15168
15279
  await open2(SNYK_ARTICLE_URL);
15169
15280
  }
15170
15281
  console.log(
15171
- chalk6.bgBlue(
15282
+ chalk7.bgBlue(
15172
15283
  "\nPlease enable Snyk Code in your Snyk account and try again."
15173
15284
  )
15174
15285
  );
@@ -15224,7 +15335,7 @@ async function downloadRepo({
15224
15335
  if (!response.ok) {
15225
15336
  debug22("SCM zipball request failed %s %s", response.body, response.status);
15226
15337
  repoSpinner.error({ text: "\u{1F4BE} Repo download failed" });
15227
- throw new Error(`Can't access ${chalk7.bold(repoUrl)}`);
15338
+ throw new Error(`Can't access ${chalk8.bold(repoUrl)}`);
15228
15339
  }
15229
15340
  const fileWriterStream = fs11.createWriteStream(zipFilePath);
15230
15341
  if (!response.body) {
@@ -15391,7 +15502,8 @@ async function _scan(params, { skipPrompts = false } = {}) {
15391
15502
  pullRequest,
15392
15503
  polling,
15393
15504
  baselineCommit,
15394
- scanOnly: requestedScanOnly
15505
+ scanOnly: requestedScanOnly,
15506
+ gate
15395
15507
  } = params;
15396
15508
  debug22("start %s %s", dirname, repo);
15397
15509
  const { createSpinner: createSpinner5 } = Spinner2({ ci });
@@ -15401,6 +15513,15 @@ async function _scan(params, { skipPrompts = false } = {}) {
15401
15513
  isSkipPrompts: skipPrompts
15402
15514
  });
15403
15515
  await gqlClient.loadIssueTypeCatalog();
15516
+ const runGate = async (fixReportId) => {
15517
+ await waitForAnalysisFinished({
15518
+ gqlClient,
15519
+ analysisId: fixReportId,
15520
+ polling,
15521
+ ci
15522
+ });
15523
+ await enforceGateVerdict({ gqlClient, fixReportId });
15524
+ };
15404
15525
  if (!mobbProjectName) {
15405
15526
  throw new Error("mobbProjectName is required");
15406
15527
  }
@@ -15416,7 +15537,7 @@ async function _scan(params, { skipPrompts = false } = {}) {
15416
15537
  const scanOnly = requestedScanOnly || !orgIsFixEnabled;
15417
15538
  if (scanOnly && !requestedScanOnly) {
15418
15539
  console.log(
15419
- chalk7.yellow(
15540
+ chalk8.yellow(
15420
15541
  "\u2139\uFE0F Your organization is on Mobb Scan (scan-only): issues will be found and reported, but no fixes are generated. Contact support to enable Mobb Fixer."
15421
15542
  )
15422
15543
  );
@@ -15571,6 +15692,9 @@ async function _scan(params, { skipPrompts = false } = {}) {
15571
15692
  polling
15572
15693
  });
15573
15694
  }
15695
+ if (gate) {
15696
+ await runGate(reportUploadInfo.fixReportId);
15697
+ }
15574
15698
  return reportUploadInfo.fixReportId;
15575
15699
  async function askToOpenAnalysis() {
15576
15700
  if (!repoUploadInfo || !reportUploadInfo) {
@@ -15582,11 +15706,11 @@ async function _scan(params, { skipPrompts = false } = {}) {
15582
15706
  fixReportId: reportUploadInfo.fixReportId
15583
15707
  });
15584
15708
  !ci && console.log("You can access the analysis at: \n");
15585
- console.log(chalk7.bold(reportUrl));
15709
+ console.log(chalk8.bold(reportUrl));
15586
15710
  !skipPrompts && await mobbAnalysisPrompt();
15587
15711
  !ci && open3(reportUrl);
15588
15712
  !ci && console.log(
15589
- chalk7.bgBlue("\n\n My work here is done for now, see you soon! \u{1F575}\uFE0F\u200D\u2642\uFE0F ")
15713
+ chalk8.bgBlue("\n\n My work here is done for now, see you soon! \u{1F575}\uFE0F\u200D\u2642\uFE0F ")
15590
15714
  );
15591
15715
  }
15592
15716
  async function handleScmIntegration(oldToken, scmAuthUrl2, repoUrl) {
@@ -15745,6 +15869,9 @@ async function _scan(params, { skipPrompts = false } = {}) {
15745
15869
  });
15746
15870
  }
15747
15871
  await askToOpenAnalysis();
15872
+ if (gate) {
15873
+ await runGate(reportUploadInfo.fixReportId);
15874
+ }
15748
15875
  return reportUploadInfo.fixReportId;
15749
15876
  }
15750
15877
  }
@@ -15823,7 +15950,7 @@ async function _digestReport({
15823
15950
  "[_digestReport] Using POLLING mode for analysis state updates (--polling flag enabled)"
15824
15951
  );
15825
15952
  console.log(
15826
- chalk7.cyan(
15953
+ chalk8.cyan(
15827
15954
  "\u{1F504} [Polling Mode] Using HTTP polling instead of WebSocket for status updates"
15828
15955
  )
15829
15956
  );
@@ -15838,7 +15965,7 @@ async function _digestReport({
15838
15965
  "[_digestReport] Using WEBSOCKET mode for analysis state updates (default)"
15839
15966
  );
15840
15967
  console.log(
15841
- chalk7.cyan(
15968
+ chalk8.cyan(
15842
15969
  "\u{1F50C} [WebSocket Mode] Using WebSocket subscription for status updates"
15843
15970
  )
15844
15971
  );
@@ -15902,7 +16029,7 @@ async function waitForAnaysisAndReviewPr({
15902
16029
  "[waitForAnaysisAndReviewPr] Using POLLING mode for analysis state updates"
15903
16030
  );
15904
16031
  console.log(
15905
- chalk7.cyan(
16032
+ chalk8.cyan(
15906
16033
  "\u{1F504} [Polling Mode] Waiting for analysis completion using HTTP polling"
15907
16034
  )
15908
16035
  );
@@ -15916,7 +16043,7 @@ async function waitForAnaysisAndReviewPr({
15916
16043
  "[waitForAnaysisAndReviewPr] Using WEBSOCKET mode for analysis state updates"
15917
16044
  );
15918
16045
  console.log(
15919
- chalk7.cyan(
16046
+ chalk8.cyan(
15920
16047
  "\u{1F50C} [WebSocket Mode] Waiting for analysis completion using WebSocket"
15921
16048
  )
15922
16049
  );
@@ -15929,6 +16056,41 @@ async function waitForAnaysisAndReviewPr({
15929
16056
  });
15930
16057
  }
15931
16058
  }
16059
+ async function waitForAnalysisFinished({
16060
+ gqlClient,
16061
+ analysisId,
16062
+ polling,
16063
+ ci
16064
+ }) {
16065
+ const { createSpinner: createSpinner5 } = Spinner2({ ci });
16066
+ const gateSpinner = createSpinner5(
16067
+ "\u{1F6A7} Waiting for the analysis to finish to evaluate the gate\u2026"
16068
+ ).start();
16069
+ const callback = () => void 0;
16070
+ try {
16071
+ if (polling) {
16072
+ await gqlClient.pollForAnalysisState({
16073
+ analysisId,
16074
+ callback,
16075
+ callbackStates: ["Finished" /* Finished */]
16076
+ });
16077
+ } else {
16078
+ await gqlClient.subscribeToAnalysis({
16079
+ subscribeToAnalysisParams: {
16080
+ analysisId
16081
+ },
16082
+ callback,
16083
+ callbackStates: ["Finished" /* Finished */]
16084
+ });
16085
+ }
16086
+ } catch (e) {
16087
+ gateSpinner.error({
16088
+ text: e instanceof ReportDigestError ? "\u{1F6A7} Analysis failed \u2014 cannot evaluate the gate" : "\u{1F6A7} Analysis did not finish"
16089
+ });
16090
+ throw e;
16091
+ }
16092
+ gateSpinner.success({ text: "\u{1F6A7} Analysis finished, evaluating the gate" });
16093
+ }
15932
16094
 
15933
16095
  // src/commands/scan_skill_input.ts
15934
16096
  import fs12 from "fs";
@@ -16060,7 +16222,8 @@ async function analyze({
16060
16222
  pullRequest,
16061
16223
  polling,
16062
16224
  baselineCommit,
16063
- scanOnly
16225
+ scanOnly,
16226
+ gate
16064
16227
  }, { skipPrompts = false } = {}) {
16065
16228
  !ci && await showWelcomeMessage(skipPrompts);
16066
16229
  await runAnalysis(
@@ -16081,7 +16244,8 @@ async function analyze({
16081
16244
  createOnePr,
16082
16245
  polling,
16083
16246
  baselineCommit,
16084
- scanOnly
16247
+ scanOnly,
16248
+ gate
16085
16249
  },
16086
16250
  { skipPrompts }
16087
16251
  );
@@ -16156,14 +16320,14 @@ async function scanSkill(options) {
16156
16320
  inputApiKey: apiKey,
16157
16321
  isSkipPrompts: ci
16158
16322
  });
16159
- console.log(chalk8.dim(`Scanning skill: ${url}`));
16323
+ console.log(chalk9.dim(`Scanning skill: ${url}`));
16160
16324
  console.log();
16161
16325
  const skillUrl = await resolveSkillScanInput(url);
16162
16326
  const result = await gqlClient.scanSkill({ skillUrl });
16163
16327
  const scan2 = result.scanSkill;
16164
16328
  const verdictColor = VERDICT_COLORS[scan2.verdict] ?? "white";
16165
16329
  console.log(
16166
- chalk8.bold(`Verdict: `) + chalk8[verdictColor](
16330
+ chalk9.bold(`Verdict: `) + chalk9[verdictColor](
16167
16331
  scan2.verdict
16168
16332
  )
16169
16333
  );
@@ -16175,32 +16339,32 @@ async function scanSkill(options) {
16175
16339
  console.log(`Findings: ${scan2.findingsCount}`);
16176
16340
  console.log(`Duration: ${scan2.scanDurationMs}ms`);
16177
16341
  if (scan2.cached) {
16178
- console.log(chalk8.dim("(cached result)"));
16342
+ console.log(chalk9.dim("(cached result)"));
16179
16343
  }
16180
16344
  console.log();
16181
16345
  if (scan2.findings.length > 0) {
16182
- console.log(chalk8.bold("Findings:"));
16346
+ console.log(chalk9.bold("Findings:"));
16183
16347
  console.log();
16184
16348
  for (const f of scan2.findings) {
16185
16349
  const sevColor = SEVERITY_COLORS[f.severity] ?? "white";
16186
16350
  const location = [f.filePath, f.lineNumber].filter(Boolean).join(":");
16187
16351
  console.log(
16188
- ` ${chalk8[sevColor](f.severity)} [${f.layer}] ${f.category}${f.ruleId ? ` (${f.ruleId})` : ""}`
16352
+ ` ${chalk9[sevColor](f.severity)} [${f.layer}] ${f.category}${f.ruleId ? ` (${f.ruleId})` : ""}`
16189
16353
  );
16190
16354
  if (location) {
16191
- console.log(` ${chalk8.dim(location)}`);
16355
+ console.log(` ${chalk9.dim(location)}`);
16192
16356
  }
16193
16357
  console.log(` ${f.explanation}`);
16194
16358
  if (f.evidence) {
16195
16359
  console.log(
16196
- ` ${String(chalk8.dim("Evidence: " + f.evidence.slice(0, 120))).replace(/\n|\r/g, "")}`
16360
+ ` ${String(chalk9.dim("Evidence: " + f.evidence.slice(0, 120))).replace(/\n|\r/g, "")}`
16197
16361
  );
16198
16362
  }
16199
16363
  console.log();
16200
16364
  }
16201
16365
  }
16202
16366
  if (scan2.summary) {
16203
- console.log(chalk8.bold("Analysis:"));
16367
+ console.log(chalk9.bold("Analysis:"));
16204
16368
  console.log(` ${scan2.summary}`);
16205
16369
  console.log();
16206
16370
  }
@@ -16210,7 +16374,7 @@ async function scanSkill(options) {
16210
16374
  }
16211
16375
 
16212
16376
  // src/args/validation.ts
16213
- import chalk9 from "chalk";
16377
+ import chalk10 from "chalk";
16214
16378
  import path13 from "path";
16215
16379
  import { z as z28 } from "zod";
16216
16380
  function throwRepoUrlErrorMessage({
@@ -16220,11 +16384,11 @@ function throwRepoUrlErrorMessage({
16220
16384
  }) {
16221
16385
  const errorMessage3 = error.issues[error.issues.length - 1]?.message;
16222
16386
  const formattedErrorMessage = `
16223
- Error: ${chalk9.bold(
16387
+ Error: ${chalk10.bold(
16224
16388
  repoUrl
16225
16389
  )} is ${errorMessage3}
16226
16390
  Example:
16227
- mobbdev ${command} -r ${chalk9.bold(
16391
+ mobbdev ${command} -r ${chalk10.bold(
16228
16392
  "https://github.com/WebGoat/WebGoat"
16229
16393
  )}`;
16230
16394
  throw new CliError(formattedErrorMessage);
@@ -16258,9 +16422,9 @@ function validateReportFileFormat(reportFile) {
16258
16422
  if (!supportExtensions.includes(path13.extname(reportFile))) {
16259
16423
  throw new CliError(
16260
16424
  `
16261
- ${chalk9.bold(
16425
+ ${chalk10.bold(
16262
16426
  reportFile
16263
- )} is not a supported file extension. Supported extensions are: ${chalk9.bold(
16427
+ )} is not a supported file extension. Supported extensions are: ${chalk10.bold(
16264
16428
  supportExtensions.join(", ")
16265
16429
  )}
16266
16430
  `
@@ -16273,27 +16437,30 @@ function analyzeBuilder(yargs2) {
16273
16437
  return yargs2.option("f", {
16274
16438
  alias: "scan-file",
16275
16439
  type: "string",
16276
- describe: chalk10.bold(
16440
+ describe: chalk11.bold(
16277
16441
  "Select the vulnerability report to analyze (Checkmarx, Snyk, Fortify, CodeQL, Sonarqube, Semgrep, Datadog, Black Duck)"
16278
16442
  )
16279
16443
  }).option("repo", repoOption).option("p", {
16280
16444
  alias: "src-path",
16281
- describe: chalk10.bold(
16445
+ describe: chalk11.bold(
16282
16446
  "Path to the repository folder with the source code; alternatively, you can specify the Fortify FPR file to extract source code out of it"
16283
16447
  ),
16284
16448
  type: "string"
16285
16449
  }).option("ref", refOption).option("ch", {
16286
16450
  alias: "commit-hash",
16287
- describe: chalk10.bold("Hash of the commit"),
16451
+ describe: chalk11.bold("Hash of the commit"),
16288
16452
  type: "string"
16289
16453
  }).option("mobb-project-name", mobbProjectNameOption).option("y", yesOption).option("ci", ciOption).option("org", organizationIdOptions).option("api-key", apiKeyOption).option("commit-hash", commitHashOption).option("auto-pr", autoPrOption).option("create-one-pr", createOnePrOption).option("commit-directly", commitDirectlyOption).option("pull-request", {
16290
16454
  alias: ["pr", "pr-number", "pr-id"],
16291
- describe: chalk10.bold("Number of the pull request"),
16455
+ describe: chalk11.bold("Number of the pull request"),
16292
16456
  type: "number",
16293
16457
  demandOption: false
16294
- }).option("polling", pollingOption).option("baseline-commit", baselineCommitOption).option("scan-only", scanOnlyOption).example(
16458
+ }).option("polling", pollingOption).option("baseline-commit", baselineCommitOption).option("scan-only", scanOnlyOption).option("gate", gateOption).example(
16295
16459
  "npx mobbdev@latest analyze -r https://github.com/org/repo --scan-only",
16296
16460
  "scan only \u2014 report findings without generating fixes"
16461
+ ).example(
16462
+ "npx mobbdev@latest analyze -r https://github.com/org/repo --ci --api-key <key> --baseline-commit <sha> --gate",
16463
+ "PR pipeline: scan the diff and fail the build (exit 2) when the Scan & Gate Policy blocks"
16297
16464
  ).example(
16298
16465
  "npx mobbdev@latest analyze -r https://github.com/WebGoat/WebGoat -f <your_vulnerability_report_path>",
16299
16466
  "analyze an existing repository"
@@ -16303,9 +16470,14 @@ function analyzeBuilder(yargs2) {
16303
16470
  ).help();
16304
16471
  }
16305
16472
  function validateAnalyzeOptions(argv) {
16473
+ if (argv.gate && argv.f) {
16474
+ throw new CliError(
16475
+ "--gate enforces the policy on a Mobb scan and cannot be combined with --scan-file"
16476
+ );
16477
+ }
16306
16478
  if (argv.f && !fs13.existsSync(argv.f)) {
16307
16479
  throw new CliError(`
16308
- Can't access ${chalk10.bold(argv.f)}`);
16480
+ Can't access ${chalk11.bold(argv.f)}`);
16309
16481
  }
16310
16482
  validateOrganizationId(argv.organizationId);
16311
16483
  if (!argv.srcPath && !argv.repo) {
@@ -16343,7 +16515,15 @@ Can't access ${chalk10.bold(argv.f)}`);
16343
16515
  }
16344
16516
  async function analyzeHandler(args) {
16345
16517
  validateAnalyzeOptions(args);
16346
- await analyze(args, { skipPrompts: args.yes });
16518
+ try {
16519
+ await analyze(args, { skipPrompts: args.yes });
16520
+ } catch (err) {
16521
+ if (err instanceof CliError && err.exitCode !== 1) {
16522
+ console.error(err.message);
16523
+ process.exit(err.exitCode);
16524
+ }
16525
+ throw err;
16526
+ }
16347
16527
  }
16348
16528
 
16349
16529
  // src/args/commands/claude_code.ts
@@ -18614,7 +18794,7 @@ function createLogger(config2) {
18614
18794
 
18615
18795
  // src/features/claude_code/hook_logger.ts
18616
18796
  var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
18617
- var CLI_VERSION = true ? "1.4.67" : "unknown";
18797
+ var CLI_VERSION = true ? "1.5.0" : "unknown";
18618
18798
  var NAMESPACE = "mobbdev-claude-code-hook-logs";
18619
18799
  var claudeCodeVersion;
18620
18800
  function buildDdTags() {
@@ -19281,7 +19461,7 @@ import fs15 from "fs";
19281
19461
  import fsPromises4 from "fs/promises";
19282
19462
  import os7 from "os";
19283
19463
  import path23 from "path";
19284
- import chalk11 from "chalk";
19464
+ import chalk12 from "chalk";
19285
19465
 
19286
19466
  // src/features/claude_code/daemon-check-shim.tmpl.js
19287
19467
  var daemon_check_shim_tmpl_default = "// Mobb daemon shim \u2014 checks if daemon is alive, spawns if dead.\n// Auto-generated by mobbdev CLI. Do not edit.\nvar fs = require('fs')\nvar spawn = require('child_process').spawn\nvar path = require('path')\nvar os = require('os')\n\nvar pidFile = path.join(os.homedir(), '.mobbdev', 'daemon.pid')\nvar HEARTBEAT_STALE_MS = __HEARTBEAT_STALE_MS__\n\ntry {\n var data = JSON.parse(fs.readFileSync(pidFile, 'utf8'))\n if (Date.now() - data.heartbeat > HEARTBEAT_STALE_MS) throw new Error('stale')\n process.kill(data.pid, 0) // throws ESRCH if the process is gone\n} catch (e) {\n var localCli = process.env.MOBBDEV_LOCAL_CLI\n var child = localCli\n ? spawn('node', [localCli, 'claude-code-daemon'], { detached: true, stdio: 'ignore', windowsHide: true })\n : spawn('npx', ['--yes', 'mobbdev@latest', 'claude-code-daemon'], { detached: true, stdio: 'ignore', shell: true, windowsHide: true })\n child.unref()\n}\n";
@@ -19367,12 +19547,12 @@ async function autoUpgradeMatcherIfStale() {
19367
19547
  }
19368
19548
  }
19369
19549
  async function installMobbHooks(options = {}) {
19370
- console.log(chalk11.blue("Installing Mobb hooks in Claude Code settings..."));
19550
+ console.log(chalk12.blue("Installing Mobb hooks in Claude Code settings..."));
19371
19551
  if (!await claudeSettingsExists()) {
19372
- console.log(chalk11.red("\u274C Claude Code settings file not found"));
19373
- console.log(chalk11.yellow(`Expected location: ${CLAUDE_SETTINGS_PATH}`));
19374
- console.log(chalk11.yellow("Is Claude Code installed on your system?"));
19375
- console.log(chalk11.yellow("Please install Claude Code and try again."));
19552
+ console.log(chalk12.red("\u274C Claude Code settings file not found"));
19553
+ console.log(chalk12.yellow(`Expected location: ${CLAUDE_SETTINGS_PATH}`));
19554
+ console.log(chalk12.yellow("Is Claude Code installed on your system?"));
19555
+ console.log(chalk12.yellow("Please install Claude Code and try again."));
19376
19556
  throw new Error(
19377
19557
  "Claude Code settings file not found. Is Claude Code installed?"
19378
19558
  );
@@ -19397,7 +19577,7 @@ async function installMobbHooks(options = {}) {
19397
19577
  if (envVars.length > 0) {
19398
19578
  envPrefix = envVars.join(" ");
19399
19579
  console.log(
19400
- chalk11.blue(
19580
+ chalk12.blue(
19401
19581
  `Adding environment variables to hook command: ${envVars.join(", ")}`
19402
19582
  )
19403
19583
  );
@@ -19418,15 +19598,15 @@ async function installMobbHooks(options = {}) {
19418
19598
  (hook) => hook.hooks.some((h) => isMobbHookCommand(h.command))
19419
19599
  );
19420
19600
  if (existingHookIndex >= 0) {
19421
- console.log(chalk11.yellow("Mobb hook already exists, updating..."));
19601
+ console.log(chalk12.yellow("Mobb hook already exists, updating..."));
19422
19602
  settings.hooks.PostToolUse[existingHookIndex] = mobbHookConfig;
19423
19603
  } else {
19424
- console.log(chalk11.green("Adding new Mobb hook..."));
19604
+ console.log(chalk12.green("Adding new Mobb hook..."));
19425
19605
  settings.hooks.PostToolUse.push(mobbHookConfig);
19426
19606
  }
19427
19607
  await writeClaudeSettings(settings);
19428
19608
  console.log(
19429
- chalk11.green(
19609
+ chalk12.green(
19430
19610
  `\u2705 Mobb hooks ${options.saveEnv ? "and environment variables " : ""}installed successfully in ${CLAUDE_SETTINGS_PATH}`
19431
19611
  )
19432
19612
  );
@@ -20395,6 +20575,13 @@ var partitionInteractiveFixes = (fixes) => {
20395
20575
 
20396
20576
  // src/mcp/services/McpGQLClient.ts
20397
20577
  var MVS_SCAN_ONLY_ERROR = "Mobb Vibe Shield is not available for your organization: it is on Mobb Scan (scan-only), which reports issues but does not generate fixes. Contact support to enable Mobb Fixer.";
20578
+ function issueTouchesFilesFilter(fileFilter) {
20579
+ return {
20580
+ _or: fileFilter.map((path38) => ({
20581
+ codeNodesJson: { nodes: { _contains: [{ path: path38 }] } }
20582
+ }))
20583
+ };
20584
+ }
20398
20585
  var McpGQLClient = class extends GQLClient {
20399
20586
  constructor(args) {
20400
20587
  super({
@@ -20764,9 +20951,7 @@ var McpGQLClient = class extends GQLClient {
20764
20951
  }
20765
20952
  const filters = {};
20766
20953
  if (fileFilter && fileFilter.length > 0) {
20767
- filters["vulnerabilityReportIssues"] = {
20768
- codeNodes: { path: { _in: fileFilter } }
20769
- };
20954
+ filters["vulnerabilityReportIssues"] = issueTouchesFilesFilter(fileFilter);
20770
20955
  }
20771
20956
  const escapedRepoUrl = repoUrl.replace(/[%_\\]/g, (c) => `\\${c}`);
20772
20957
  const resp = await this._clientSdk.GetLatestReportByRepoUrl({
@@ -20818,9 +21003,7 @@ var McpGQLClient = class extends GQLClient {
20818
21003
  filters["severityText"] = { _in: severity };
20819
21004
  }
20820
21005
  if (fileFilter && fileFilter.length > 0) {
20821
- filters["vulnerabilityReportIssues"] = {
20822
- codeNodes: { path: { _in: fileFilter } }
20823
- };
21006
+ filters["vulnerabilityReportIssues"] = issueTouchesFilesFilter(fileFilter);
20824
21007
  }
20825
21008
  try {
20826
21009
  logDebug("[GraphQL] Calling GetReportFixes query", {
@@ -28482,32 +28665,32 @@ var mcpHandler = async (_args) => {
28482
28665
 
28483
28666
  // src/args/commands/review.ts
28484
28667
  import fs27 from "fs";
28485
- import chalk12 from "chalk";
28668
+ import chalk13 from "chalk";
28486
28669
  function reviewBuilder(yargs2) {
28487
28670
  return yargs2.option("f", {
28488
28671
  alias: "scan-file",
28489
28672
  demandOption: true,
28490
28673
  type: "string",
28491
- describe: chalk12.bold(
28674
+ describe: chalk13.bold(
28492
28675
  "Select the vulnerability report to analyze (Checkmarx, Snyk, Fortify, CodeQL, Sonarqube, Semgrep)"
28493
28676
  )
28494
28677
  }).option("repo", { ...repoOption, demandOption: true }).option("scanner", { ...scannerOptions, demandOption: true }).option("ref", { ...refOption, demandOption: true }).option("ch", {
28495
28678
  alias: "commit-hash",
28496
- describe: chalk12.bold("Hash of the commit"),
28679
+ describe: chalk13.bold("Hash of the commit"),
28497
28680
  type: "string",
28498
28681
  demandOption: true
28499
28682
  }).option("mobb-project-name", mobbProjectNameOption).option("api-key", { ...apiKeyOption, demandOption: true }).option("commit-hash", { ...commitHashOption, demandOption: true }).option("github-token", {
28500
- describe: chalk12.bold("Github action token"),
28683
+ describe: chalk13.bold("Github action token"),
28501
28684
  type: "string",
28502
28685
  demandOption: true
28503
28686
  }).option("pull-request", {
28504
28687
  alias: ["pr", "pr-number", "pr-id"],
28505
- describe: chalk12.bold("Number of the pull request"),
28688
+ describe: chalk13.bold("Number of the pull request"),
28506
28689
  type: "number",
28507
28690
  demandOption: true
28508
28691
  }).option("p", {
28509
28692
  alias: "src-path",
28510
- describe: chalk12.bold(
28693
+ describe: chalk13.bold(
28511
28694
  "Path to the repository folder with the source code"
28512
28695
  ),
28513
28696
  type: "string",
@@ -28520,7 +28703,7 @@ function reviewBuilder(yargs2) {
28520
28703
  function validateReviewOptions(argv) {
28521
28704
  if (!fs27.existsSync(argv.f)) {
28522
28705
  throw new CliError(`
28523
- Can't access ${chalk12.bold(argv.f)}`);
28706
+ Can't access ${chalk13.bold(argv.f)}`);
28524
28707
  }
28525
28708
  validateRepoUrl(argv);
28526
28709
  validateReportFileFormat(argv.f);
@@ -28556,12 +28739,12 @@ async function scanHandler(args) {
28556
28739
  }
28557
28740
 
28558
28741
  // src/args/commands/scan_skill.ts
28559
- import chalk13 from "chalk";
28742
+ import chalk14 from "chalk";
28560
28743
  function scanSkillBuilder(args) {
28561
28744
  return args.option("url", {
28562
28745
  demandOption: true,
28563
28746
  type: "string",
28564
- describe: chalk13.bold(
28747
+ describe: chalk14.bold(
28565
28748
  "URL or local directory of the skill to scan (GitHub repo, ZIP archive, raw SKILL.md, or folder containing SKILL.md)"
28566
28749
  )
28567
28750
  }).option("api-key", apiKeyOption).option("ci", ciOption).example(
@@ -28984,7 +29167,7 @@ function processChatStepCodeAction(step) {
28984
29167
  import fsPromises5 from "fs/promises";
28985
29168
  import os19 from "os";
28986
29169
  import path37 from "path";
28987
- import chalk14 from "chalk";
29170
+ import chalk15 from "chalk";
28988
29171
  function getCodeiumHooksPath() {
28989
29172
  return path37.join(os19.homedir(), ".codeium", "hooks.json");
28990
29173
  }
@@ -29009,7 +29192,7 @@ async function writeCodeiumHooks(config2) {
29009
29192
  }
29010
29193
  async function installWindsurfHooks(options = {}) {
29011
29194
  const hooksPath = getCodeiumHooksPath();
29012
- console.log(chalk14.blue("Installing Mobb hooks in Windsurf IntelliJ..."));
29195
+ console.log(chalk15.blue("Installing Mobb hooks in Windsurf IntelliJ..."));
29013
29196
  const config2 = await readCodeiumHooks();
29014
29197
  if (!config2.hooks) {
29015
29198
  config2.hooks = {};
@@ -29029,7 +29212,7 @@ async function installWindsurfHooks(options = {}) {
29029
29212
  if (envVars.length > 0) {
29030
29213
  command = `${envVars.join(" ")} ${command}`;
29031
29214
  console.log(
29032
- chalk14.blue(
29215
+ chalk15.blue(
29033
29216
  `Adding environment variables to hook command: ${envVars.join(", ")}`
29034
29217
  )
29035
29218
  );
@@ -29043,15 +29226,15 @@ async function installWindsurfHooks(options = {}) {
29043
29226
  (hook) => hook.command?.includes("mobbdev@latest windsurf-intellij-process-hook")
29044
29227
  );
29045
29228
  if (existingHookIndex >= 0) {
29046
- console.log(chalk14.yellow("Mobb hook already exists, updating..."));
29229
+ console.log(chalk15.yellow("Mobb hook already exists, updating..."));
29047
29230
  config2.hooks.post_write_code[existingHookIndex] = mobbHook;
29048
29231
  } else {
29049
- console.log(chalk14.green("Adding new Mobb hook..."));
29232
+ console.log(chalk15.green("Adding new Mobb hook..."));
29050
29233
  config2.hooks.post_write_code.push(mobbHook);
29051
29234
  }
29052
29235
  await writeCodeiumHooks(config2);
29053
29236
  console.log(
29054
- chalk14.green(
29237
+ chalk15.green(
29055
29238
  `\u2705 Mobb hooks ${options.saveEnv ? "and environment variables " : ""}installed successfully in ${hooksPath}`
29056
29239
  )
29057
29240
  );
@@ -29101,93 +29284,93 @@ var windsurfIntellijProcessHookHandler = async () => {
29101
29284
  var parseArgs = async (args) => {
29102
29285
  const yargsInstance = yargs(args);
29103
29286
  return yargsInstance.updateStrings({
29104
- "Commands:": chalk15.yellow.underline.bold("Commands:"),
29105
- "Options:": chalk15.yellow.underline.bold("Options:"),
29106
- "Examples:": chalk15.yellow.underline.bold("Examples:"),
29107
- "Show help": chalk15.bold("Show help")
29287
+ "Commands:": chalk16.yellow.underline.bold("Commands:"),
29288
+ "Options:": chalk16.yellow.underline.bold("Options:"),
29289
+ "Examples:": chalk16.yellow.underline.bold("Examples:"),
29290
+ "Show help": chalk16.bold("Show help")
29108
29291
  }).usage(
29109
- `${chalk15.bold(
29292
+ `${chalk16.bold(
29110
29293
  "\n Bugsy - Trusted, Automatic Vulnerability Fixer \u{1F575}\uFE0F\u200D\u2642\uFE0F\n\n"
29111
- )} ${chalk15.yellow.underline.bold("Usage:")}
29112
- $0 ${chalk15.green(
29294
+ )} ${chalk16.yellow.underline.bold("Usage:")}
29295
+ $0 ${chalk16.green(
29113
29296
  "<command>"
29114
- )} ${chalk15.dim("[options]")}
29297
+ )} ${chalk16.dim("[options]")}
29115
29298
  `
29116
29299
  ).version(false).command(
29117
29300
  mobbCliCommand.scan,
29118
- chalk15.bold(
29301
+ chalk16.bold(
29119
29302
  "Scan your code for vulnerabilities, get automated fixes right away."
29120
29303
  ),
29121
29304
  scanBuilder,
29122
29305
  scanHandler
29123
29306
  ).command(
29124
29307
  mobbCliCommand.analyze,
29125
- chalk15.bold(
29308
+ chalk16.bold(
29126
29309
  "Provide a code repository, get automated fixes right away. You can also provide a vulnerability report to analyze or have Mobb scan the code for you."
29127
29310
  ),
29128
29311
  analyzeBuilder,
29129
29312
  analyzeHandler
29130
29313
  ).command(
29131
29314
  mobbCliCommand.review,
29132
- chalk15.bold(
29315
+ chalk16.bold(
29133
29316
  "Mobb will review your github pull requests and provide comments with fixes "
29134
29317
  ),
29135
29318
  reviewBuilder,
29136
29319
  reviewHandler
29137
29320
  ).command(
29138
29321
  mobbCliCommand.addScmToken,
29139
- chalk15.bold(
29322
+ chalk16.bold(
29140
29323
  "Add your SCM (Github, Gitlab, Azure DevOps) token to Mobb to enable automated fixes."
29141
29324
  ),
29142
29325
  addScmTokenBuilder,
29143
29326
  addScmTokenHandler
29144
29327
  ).command(
29145
29328
  mobbCliCommand.scanSkill,
29146
- chalk15.bold("Scan an OpenClaw/ClawHub skill for security threats."),
29329
+ chalk16.bold("Scan an OpenClaw/ClawHub skill for security threats."),
29147
29330
  scanSkillBuilder,
29148
29331
  scanSkillHandler
29149
29332
  ).command(
29150
29333
  mobbCliCommand.convertToSarif,
29151
- chalk15.bold("Convert an existing SAST report to SARIF format."),
29334
+ chalk16.bold("Convert an existing SAST report to SARIF format."),
29152
29335
  convertToSarifBuilder,
29153
29336
  convertToSarifHandler
29154
29337
  ).command(
29155
29338
  mobbCliCommand.mcp,
29156
- chalk15.bold("Launch the MCP (Model Context Protocol) server."),
29339
+ chalk16.bold("Launch the MCP (Model Context Protocol) server."),
29157
29340
  mcpBuilder,
29158
29341
  mcpHandler
29159
29342
  ).command(
29160
29343
  mobbCliCommand.uploadAiBlame,
29161
- chalk15.bold(
29344
+ chalk16.bold(
29162
29345
  "Upload AI Blame inference artifacts (prompt + inference) and finalize them."
29163
29346
  ),
29164
29347
  uploadAiBlameBuilder,
29165
29348
  uploadAiBlameCommandHandler
29166
29349
  ).command(
29167
29350
  mobbCliCommand.claudeCodeInstallHook,
29168
- chalk15.bold("Install Claude Code hooks for data collection."),
29351
+ chalk16.bold("Install Claude Code hooks for data collection."),
29169
29352
  claudeCodeInstallHookBuilder,
29170
29353
  claudeCodeInstallHookHandler
29171
29354
  ).command(
29172
29355
  mobbCliCommand.claudeCodeProcessHook,
29173
- chalk15.bold("Process Claude Code hook data (legacy \u2014 spawns daemon)."),
29356
+ chalk16.bold("Process Claude Code hook data (legacy \u2014 spawns daemon)."),
29174
29357
  claudeCodeProcessHookBuilder,
29175
29358
  claudeCodeProcessHookHandler
29176
29359
  ).command(
29177
29360
  mobbCliCommand.claudeCodeDaemon,
29178
- chalk15.bold(
29361
+ chalk16.bold(
29179
29362
  "Run the background daemon for Claude Code transcript processing."
29180
29363
  ),
29181
29364
  claudeCodeDaemonBuilder,
29182
29365
  claudeCodeDaemonHandler
29183
29366
  ).command(
29184
29367
  mobbCliCommand.windsurfIntellijInstallHook,
29185
- chalk15.bold("Install Windsurf IntelliJ hooks for data collection."),
29368
+ chalk16.bold("Install Windsurf IntelliJ hooks for data collection."),
29186
29369
  windsurfIntellijInstallHookBuilder,
29187
29370
  windsurfIntellijInstallHookHandler
29188
29371
  ).command(
29189
29372
  mobbCliCommand.windsurfIntellijProcessHook,
29190
- chalk15.bold("Process Windsurf IntelliJ hook data and upload to backend."),
29373
+ chalk16.bold("Process Windsurf IntelliJ hook data and upload to backend."),
29191
29374
  windsurfIntellijProcessHookBuilder,
29192
29375
  windsurfIntellijProcessHookHandler
29193
29376
  ).example(
@@ -29198,7 +29381,7 @@ var parseArgs = async (args) => {
29198
29381
  handler() {
29199
29382
  yargsInstance.showHelp();
29200
29383
  }
29201
- }).strictOptions().help("h").alias("h", "help").epilog(chalk15.bgBlue("Made with \u2764\uFE0F by Mobb")).showHelpOnFail(true).wrap(Math.min(120, yargsInstance.terminalWidth())).parse();
29384
+ }).strictOptions().help("h").alias("h", "help").epilog(chalk16.bgBlue("Made with \u2764\uFE0F by Mobb")).showHelpOnFail(true).wrap(Math.min(120, yargsInstance.terminalWidth())).parse();
29202
29385
  };
29203
29386
 
29204
29387
  // src/index.ts
@@ -29214,7 +29397,7 @@ async function run() {
29214
29397
  } catch (err) {
29215
29398
  if (err instanceof CliError) {
29216
29399
  console.error(err.message);
29217
- process.exit(1);
29400
+ process.exit(err.exitCode);
29218
29401
  }
29219
29402
  console.error(
29220
29403
  "Something went wrong, please try again or contact support if issue persists."