mobbdev 1.2.32 → 1.2.34
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/args/commands/upload_ai_blame.d.mts +15 -1
- package/dist/args/commands/upload_ai_blame.mjs +264 -203
- package/dist/index.mjs +2264 -2181
- package/package.json +13 -13
package/dist/index.mjs
CHANGED
|
@@ -73,6 +73,12 @@ function getSdk(client, withWrapper = defaultWrapper) {
|
|
|
73
73
|
FinalizeAIBlameInferencesUpload(variables, requestHeaders, signal) {
|
|
74
74
|
return withWrapper((wrappedRequestHeaders) => client.request({ document: FinalizeAiBlameInferencesUploadDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "FinalizeAIBlameInferencesUpload", "mutation", variables);
|
|
75
75
|
},
|
|
76
|
+
UploadTracyRecords(variables, requestHeaders, signal) {
|
|
77
|
+
return withWrapper((wrappedRequestHeaders) => client.request({ document: UploadTracyRecordsDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "UploadTracyRecords", "mutation", variables);
|
|
78
|
+
},
|
|
79
|
+
GetTracyRawDataUploadUrls(variables, requestHeaders, signal) {
|
|
80
|
+
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetTracyRawDataUploadUrlsDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetTracyRawDataUploadUrls", "mutation", variables);
|
|
81
|
+
},
|
|
76
82
|
DigestVulnerabilityReport(variables, requestHeaders, signal) {
|
|
77
83
|
return withWrapper((wrappedRequestHeaders) => client.request({ document: DigestVulnerabilityReportDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "DigestVulnerabilityReport", "mutation", variables);
|
|
78
84
|
},
|
|
@@ -126,7 +132,7 @@ function getSdk(client, withWrapper = defaultWrapper) {
|
|
|
126
132
|
}
|
|
127
133
|
};
|
|
128
134
|
}
|
|
129
|
-
var AiBlameInferenceType, FixQuestionInputType, Language, ManifestAction, Effort_To_Apply_Fix_Enum, Fix_Rating_Tag_Enum, Fix_Report_State_Enum, Fix_State_Enum, IssueLanguage_Enum, IssueType_Enum, Pr_Status_Enum, Project_Role_Type_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, DigestVulnerabilityReportDocument, SubmitVulnerabilityReportDocument, CreateCommunityUserDocument, CreateCliLoginDocument, PerformCliLoginDocument, CreateProjectDocument, ValidateRepoUrlDocument, GitReferenceDocument, AutoPrAnalysisDocument, GetFixReportsByRepoUrlDocument, GetReportFixesDocument, GetLatestReportByRepoUrlDocument, UpdateDownloadedFixDataDocument, GetUserMvsAutoFixDocument, StreamBlameAiAnalysisRequestsDocument, StreamCommitBlameRequestsDocument, ScanSkillDocument, defaultWrapper;
|
|
135
|
+
var AiBlameInferenceType, FixQuestionInputType, Language, ManifestAction, Effort_To_Apply_Fix_Enum, Fix_Rating_Tag_Enum, Fix_Report_State_Enum, Fix_State_Enum, IssueLanguage_Enum, IssueType_Enum, Pr_Status_Enum, Project_Role_Type_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, GetTracyRawDataUploadUrlsDocument, DigestVulnerabilityReportDocument, SubmitVulnerabilityReportDocument, CreateCommunityUserDocument, CreateCliLoginDocument, PerformCliLoginDocument, CreateProjectDocument, ValidateRepoUrlDocument, GitReferenceDocument, AutoPrAnalysisDocument, GetFixReportsByRepoUrlDocument, GetReportFixesDocument, GetLatestReportByRepoUrlDocument, UpdateDownloadedFixDataDocument, GetUserMvsAutoFixDocument, StreamBlameAiAnalysisRequestsDocument, StreamCommitBlameRequestsDocument, ScanSkillDocument, defaultWrapper;
|
|
130
136
|
var init_client_generates = __esm({
|
|
131
137
|
"src/features/analysis/scm/generates/client_generates.ts"() {
|
|
132
138
|
"use strict";
|
|
@@ -962,6 +968,28 @@ var init_client_generates = __esm({
|
|
|
962
968
|
status
|
|
963
969
|
error
|
|
964
970
|
}
|
|
971
|
+
}
|
|
972
|
+
`;
|
|
973
|
+
UploadTracyRecordsDocument = `
|
|
974
|
+
mutation UploadTracyRecords($records: [TracyRecordInput!]!) {
|
|
975
|
+
uploadTracyRecords(records: $records) {
|
|
976
|
+
status
|
|
977
|
+
error
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
`;
|
|
981
|
+
GetTracyRawDataUploadUrlsDocument = `
|
|
982
|
+
mutation GetTracyRawDataUploadUrls($recordIds: [String!]!) {
|
|
983
|
+
getTracyRawDataUploadUrls(recordIds: $recordIds) {
|
|
984
|
+
status
|
|
985
|
+
error
|
|
986
|
+
uploads {
|
|
987
|
+
recordId
|
|
988
|
+
url
|
|
989
|
+
uploadFieldsJSON
|
|
990
|
+
uploadKey
|
|
991
|
+
}
|
|
992
|
+
}
|
|
965
993
|
}
|
|
966
994
|
`;
|
|
967
995
|
DigestVulnerabilityReportDocument = `
|
|
@@ -4126,7 +4154,7 @@ ${rootContent}`;
|
|
|
4126
4154
|
});
|
|
4127
4155
|
|
|
4128
4156
|
// src/index.ts
|
|
4129
|
-
import
|
|
4157
|
+
import Debug22 from "debug";
|
|
4130
4158
|
import { hideBin } from "yargs/helpers";
|
|
4131
4159
|
|
|
4132
4160
|
// src/args/yargs.ts
|
|
@@ -7575,6 +7603,9 @@ var AdoSCMLib = class extends SCMLib {
|
|
|
7575
7603
|
async getSubmitRequestMetadata(_submitRequestId) {
|
|
7576
7604
|
throw new Error("getSubmitRequestMetadata not implemented for ADO");
|
|
7577
7605
|
}
|
|
7606
|
+
async getPrFiles(_prNumber) {
|
|
7607
|
+
throw new Error("getPrFiles not implemented for ADO");
|
|
7608
|
+
}
|
|
7578
7609
|
async searchSubmitRequests(_params) {
|
|
7579
7610
|
throw new Error("searchSubmitRequests not implemented for ADO");
|
|
7580
7611
|
}
|
|
@@ -8154,6 +8185,9 @@ var BitbucketSCMLib = class extends SCMLib {
|
|
|
8154
8185
|
async getSubmitRequestMetadata(_submitRequestId) {
|
|
8155
8186
|
throw new Error("getSubmitRequestMetadata not implemented for Bitbucket");
|
|
8156
8187
|
}
|
|
8188
|
+
async getPrFiles(_prNumber) {
|
|
8189
|
+
throw new Error("getPrFiles not implemented for Bitbucket");
|
|
8190
|
+
}
|
|
8157
8191
|
async searchSubmitRequests(_params) {
|
|
8158
8192
|
throw new Error("searchSubmitRequests not implemented for Bitbucket");
|
|
8159
8193
|
}
|
|
@@ -9355,6 +9389,19 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
9355
9389
|
headCommitSha: pr.head.sha
|
|
9356
9390
|
};
|
|
9357
9391
|
}
|
|
9392
|
+
async getPrFiles(prNumber) {
|
|
9393
|
+
this._validateAccessTokenAndUrl();
|
|
9394
|
+
const { owner, repo } = parseGithubOwnerAndRepo(this.url);
|
|
9395
|
+
const filesRes = await this.githubSdk.listPRFiles({
|
|
9396
|
+
owner,
|
|
9397
|
+
repo,
|
|
9398
|
+
pull_number: prNumber
|
|
9399
|
+
});
|
|
9400
|
+
return filesRes.data.filter((file) => {
|
|
9401
|
+
const status = file.status;
|
|
9402
|
+
return status === "added" || status === "modified" || status === "renamed" && file.changes > 0 || status === "copied" && file.changes > 0;
|
|
9403
|
+
}).map((file) => file.filename);
|
|
9404
|
+
}
|
|
9358
9405
|
/**
|
|
9359
9406
|
* Override searchSubmitRequests to use GitHub's Search API for efficient pagination.
|
|
9360
9407
|
* This is much faster than fetching all PRs and filtering in-memory.
|
|
@@ -10491,6 +10538,9 @@ var GitlabSCMLib = class extends SCMLib {
|
|
|
10491
10538
|
headCommitSha: mr.sha
|
|
10492
10539
|
};
|
|
10493
10540
|
}
|
|
10541
|
+
async getPrFiles(_prNumber) {
|
|
10542
|
+
throw new Error("getPrFiles not implemented for GitLab");
|
|
10543
|
+
}
|
|
10494
10544
|
async searchSubmitRequests(params) {
|
|
10495
10545
|
this._validateAccessTokenAndUrl();
|
|
10496
10546
|
const page = parseCursorSafe(params.cursor, 1);
|
|
@@ -10752,6 +10802,10 @@ var StubSCMLib = class extends SCMLib {
|
|
|
10752
10802
|
async getSubmitRequestMetadata(_submitRequestId) {
|
|
10753
10803
|
throw new Error("getSubmitRequestMetadata() not implemented");
|
|
10754
10804
|
}
|
|
10805
|
+
async getPrFiles(_prNumber) {
|
|
10806
|
+
console.warn("getPrFiles() returning empty array");
|
|
10807
|
+
return [];
|
|
10808
|
+
}
|
|
10755
10809
|
async getPullRequestMetrics(_prNumber) {
|
|
10756
10810
|
console.warn("getPullRequestMetrics() returning empty object");
|
|
10757
10811
|
throw new Error("getPullRequestMetrics() not implemented");
|
|
@@ -11674,34 +11728,35 @@ var ScanContext = {
|
|
|
11674
11728
|
|
|
11675
11729
|
// src/args/commands/analyze.ts
|
|
11676
11730
|
import fs12 from "fs";
|
|
11677
|
-
import
|
|
11731
|
+
import chalk10 from "chalk";
|
|
11678
11732
|
|
|
11679
11733
|
// src/commands/index.ts
|
|
11680
|
-
import
|
|
11734
|
+
import chalk8 from "chalk";
|
|
11681
11735
|
import chalkAnimation from "chalk-animation";
|
|
11682
11736
|
|
|
11683
11737
|
// src/features/analysis/index.ts
|
|
11684
11738
|
import fs10 from "fs";
|
|
11685
|
-
import
|
|
11686
|
-
import
|
|
11739
|
+
import fsPromises3 from "fs/promises";
|
|
11740
|
+
import path10 from "path";
|
|
11687
11741
|
import { env as env2 } from "process";
|
|
11688
11742
|
import { pipeline } from "stream/promises";
|
|
11689
|
-
import
|
|
11690
|
-
import
|
|
11743
|
+
import chalk7 from "chalk";
|
|
11744
|
+
import Debug21 from "debug";
|
|
11691
11745
|
import extract from "extract-zip";
|
|
11692
11746
|
import { createSpinner as createSpinner4 } from "nanospinner";
|
|
11693
11747
|
import fetch4 from "node-fetch";
|
|
11694
11748
|
import open3 from "open";
|
|
11695
11749
|
import tmp2 from "tmp";
|
|
11696
|
-
import { z as
|
|
11750
|
+
import { z as z31 } from "zod";
|
|
11697
11751
|
|
|
11698
11752
|
// src/commands/handleMobbLogin.ts
|
|
11699
|
-
import
|
|
11700
|
-
import
|
|
11753
|
+
import chalk4 from "chalk";
|
|
11754
|
+
import Debug10 from "debug";
|
|
11701
11755
|
|
|
11702
11756
|
// src/commands/AuthManager.ts
|
|
11703
11757
|
import crypto from "crypto";
|
|
11704
|
-
import
|
|
11758
|
+
import os3 from "os";
|
|
11759
|
+
import Debug9 from "debug";
|
|
11705
11760
|
import open from "open";
|
|
11706
11761
|
|
|
11707
11762
|
// src/features/analysis/graphql/gql.ts
|
|
@@ -11821,8 +11876,7 @@ function getProxyAgent(url) {
|
|
|
11821
11876
|
const isHttps = parsedUrl.protocol === "https:";
|
|
11822
11877
|
const proxy = isHttps ? getHttpProxy() : isHttp ? getHttpProxyOnly() : null;
|
|
11823
11878
|
if (proxy) {
|
|
11824
|
-
debug6("Using proxy %s", proxy);
|
|
11825
|
-
debug6("Proxy agent %o", proxy);
|
|
11879
|
+
debug6("Using proxy %s for %s", proxy, url);
|
|
11826
11880
|
return new HttpsProxyAgent(proxy);
|
|
11827
11881
|
}
|
|
11828
11882
|
} catch (err) {
|
|
@@ -12474,6 +12528,12 @@ var GQLClient = class {
|
|
|
12474
12528
|
async finalizeAIBlameInferencesUploadRaw(variables) {
|
|
12475
12529
|
return await this._clientSdk.FinalizeAIBlameInferencesUpload(variables);
|
|
12476
12530
|
}
|
|
12531
|
+
async uploadTracyRecords(variables) {
|
|
12532
|
+
return await this._clientSdk.UploadTracyRecords(variables);
|
|
12533
|
+
}
|
|
12534
|
+
async getTracyRawDataUploadUrls(variables) {
|
|
12535
|
+
return await this._clientSdk.GetTracyRawDataUploadUrls(variables);
|
|
12536
|
+
}
|
|
12477
12537
|
async analyzeCommitForExtensionAIBlame(variables) {
|
|
12478
12538
|
return await this._clientSdk.AnalyzeCommitForExtensionAIBlame(variables);
|
|
12479
12539
|
}
|
|
@@ -12491,38 +12551,76 @@ var GQLClient = class {
|
|
|
12491
12551
|
}
|
|
12492
12552
|
};
|
|
12493
12553
|
|
|
12494
|
-
// src/
|
|
12495
|
-
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
|
|
12499
|
-
|
|
12500
|
-
|
|
12501
|
-
|
|
12502
|
-
|
|
12503
|
-
|
|
12504
|
-
|
|
12505
|
-
|
|
12506
|
-
|
|
12507
|
-
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
|
|
12511
|
-
|
|
12512
|
-
|
|
12513
|
-
|
|
12514
|
-
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
|
|
12518
|
-
|
|
12519
|
-
|
|
12520
|
-
|
|
12521
|
-
|
|
12554
|
+
// src/features/analysis/graphql/tracy-batch-upload.ts
|
|
12555
|
+
import { promisify } from "util";
|
|
12556
|
+
import { gzip } from "zlib";
|
|
12557
|
+
import Debug8 from "debug";
|
|
12558
|
+
|
|
12559
|
+
// src/args/commands/upload_ai_blame.ts
|
|
12560
|
+
import fsPromises2 from "fs/promises";
|
|
12561
|
+
import * as os2 from "os";
|
|
12562
|
+
import path7 from "path";
|
|
12563
|
+
import chalk3 from "chalk";
|
|
12564
|
+
import { withFile } from "tmp-promise";
|
|
12565
|
+
import z27 from "zod";
|
|
12566
|
+
init_client_generates();
|
|
12567
|
+
init_GitService();
|
|
12568
|
+
init_urlParser2();
|
|
12569
|
+
|
|
12570
|
+
// src/features/analysis/upload-file.ts
|
|
12571
|
+
import Debug7 from "debug";
|
|
12572
|
+
import fetch3, { File, fileFrom, FormData } from "node-fetch";
|
|
12573
|
+
var debug8 = Debug7("mobbdev:upload-file");
|
|
12574
|
+
async function uploadFile({
|
|
12575
|
+
file,
|
|
12576
|
+
url,
|
|
12577
|
+
uploadKey,
|
|
12578
|
+
uploadFields,
|
|
12579
|
+
logger: logger2
|
|
12580
|
+
}) {
|
|
12581
|
+
const logInfo2 = logger2 || ((_message, _data) => {
|
|
12582
|
+
});
|
|
12583
|
+
logInfo2(`FileUpload: upload file start ${url}`);
|
|
12584
|
+
logInfo2(`FileUpload: upload fields`, uploadFields);
|
|
12585
|
+
logInfo2(`FileUpload: upload key ${uploadKey}`);
|
|
12586
|
+
debug8("upload file start %s", url);
|
|
12587
|
+
debug8("upload fields %o", uploadFields);
|
|
12588
|
+
debug8("upload key %s", uploadKey);
|
|
12589
|
+
const form = new FormData();
|
|
12590
|
+
Object.entries(uploadFields).forEach(([key, value]) => {
|
|
12591
|
+
form.append(key, value);
|
|
12592
|
+
});
|
|
12593
|
+
if (!form.has("key")) {
|
|
12594
|
+
form.append("key", uploadKey);
|
|
12522
12595
|
}
|
|
12523
|
-
|
|
12596
|
+
if (typeof file === "string") {
|
|
12597
|
+
debug8("upload file from path %s", file);
|
|
12598
|
+
logInfo2(`FileUpload: upload file from path ${file}`);
|
|
12599
|
+
form.append("file", await fileFrom(file));
|
|
12600
|
+
} else {
|
|
12601
|
+
debug8("upload file from buffer");
|
|
12602
|
+
logInfo2(`FileUpload: upload file from buffer`);
|
|
12603
|
+
form.append("file", new File([new Uint8Array(file)], "file"));
|
|
12604
|
+
}
|
|
12605
|
+
const agent = getProxyAgent(url);
|
|
12606
|
+
const response = await fetch3(url, {
|
|
12607
|
+
method: "POST",
|
|
12608
|
+
body: form,
|
|
12609
|
+
agent
|
|
12610
|
+
});
|
|
12611
|
+
if (!response.ok) {
|
|
12612
|
+
debug8("error from S3 %s %s", response.body, response.status);
|
|
12613
|
+
logInfo2(`FileUpload: error from S3 ${response.body} ${response.status}`);
|
|
12614
|
+
throw new Error(`Failed to upload the file: ${response.status}`);
|
|
12615
|
+
}
|
|
12616
|
+
debug8("upload file done");
|
|
12617
|
+
logInfo2(`FileUpload: upload file done`);
|
|
12524
12618
|
}
|
|
12525
12619
|
|
|
12620
|
+
// src/utils/computerName.ts
|
|
12621
|
+
import { execSync } from "child_process";
|
|
12622
|
+
import os from "os";
|
|
12623
|
+
|
|
12526
12624
|
// src/utils/ConfigStoreService.ts
|
|
12527
12625
|
import Configstore from "configstore";
|
|
12528
12626
|
function createConfigStore(defaultValues = { apiToken: "" }) {
|
|
@@ -12542,340 +12640,942 @@ function getConfigStore() {
|
|
|
12542
12640
|
}
|
|
12543
12641
|
var configStore = getConfigStore();
|
|
12544
12642
|
|
|
12545
|
-
// src/
|
|
12546
|
-
var
|
|
12547
|
-
var
|
|
12548
|
-
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
|
|
12555
|
-
|
|
12556
|
-
|
|
12557
|
-
|
|
12558
|
-
|
|
12559
|
-
|
|
12560
|
-
|
|
12561
|
-
|
|
12562
|
-
|
|
12563
|
-
|
|
12564
|
-
|
|
12565
|
-
|
|
12566
|
-
|
|
12567
|
-
|
|
12568
|
-
|
|
12569
|
-
|
|
12570
|
-
|
|
12571
|
-
|
|
12572
|
-
|
|
12573
|
-
|
|
12643
|
+
// src/utils/computerName.ts
|
|
12644
|
+
var STABLE_COMPUTER_NAME_CONFIG_KEY = "stableComputerName";
|
|
12645
|
+
var HOSTNAME_SUFFIXES = [
|
|
12646
|
+
// Cloud providers (must be first - most specific)
|
|
12647
|
+
".ec2.internal",
|
|
12648
|
+
".compute.internal",
|
|
12649
|
+
".cloudapp.net",
|
|
12650
|
+
// mDNS/Bonjour
|
|
12651
|
+
".local",
|
|
12652
|
+
".localhost",
|
|
12653
|
+
".localdomain",
|
|
12654
|
+
// Home networks
|
|
12655
|
+
".lan",
|
|
12656
|
+
".home",
|
|
12657
|
+
".homelan",
|
|
12658
|
+
// Corporate networks
|
|
12659
|
+
".corp",
|
|
12660
|
+
".internal",
|
|
12661
|
+
".intranet",
|
|
12662
|
+
".domain",
|
|
12663
|
+
".work",
|
|
12664
|
+
".office",
|
|
12665
|
+
// Container environments
|
|
12666
|
+
".docker",
|
|
12667
|
+
".kubernetes",
|
|
12668
|
+
".k8s"
|
|
12669
|
+
];
|
|
12670
|
+
function getOsSpecificComputerName() {
|
|
12671
|
+
const platform2 = os.platform();
|
|
12672
|
+
try {
|
|
12673
|
+
if (platform2 === "darwin") {
|
|
12674
|
+
const result = execSync("scutil --get LocalHostName", {
|
|
12675
|
+
encoding: "utf-8",
|
|
12676
|
+
timeout: 1e3
|
|
12677
|
+
});
|
|
12678
|
+
return result.trim();
|
|
12679
|
+
} else if (platform2 === "win32") {
|
|
12680
|
+
return process.env["COMPUTERNAME"] || null;
|
|
12681
|
+
} else {
|
|
12682
|
+
try {
|
|
12683
|
+
const result2 = execSync("hostnamectl --static", {
|
|
12684
|
+
encoding: "utf-8",
|
|
12685
|
+
timeout: 1e3
|
|
12686
|
+
});
|
|
12687
|
+
const hostname = result2.trim();
|
|
12688
|
+
if (hostname) return hostname;
|
|
12689
|
+
} catch {
|
|
12574
12690
|
}
|
|
12575
|
-
|
|
12576
|
-
|
|
12577
|
-
|
|
12578
|
-
|
|
12579
|
-
|
|
12580
|
-
this.gqlClient = new GQLClient({
|
|
12581
|
-
apiKey: newApiToken,
|
|
12582
|
-
type: "apiKey",
|
|
12583
|
-
apiUrl: this.resolvedApiUrl
|
|
12584
|
-
});
|
|
12585
|
-
const loginSuccess = await this.gqlClient.validateUserToken();
|
|
12586
|
-
if (loginSuccess) {
|
|
12587
|
-
configStore.set("apiToken", newApiToken);
|
|
12588
|
-
this.authenticated = true;
|
|
12589
|
-
return true;
|
|
12691
|
+
const result = execSync("cat /etc/hostname", {
|
|
12692
|
+
encoding: "utf-8",
|
|
12693
|
+
timeout: 1e3
|
|
12694
|
+
});
|
|
12695
|
+
return result.trim();
|
|
12590
12696
|
}
|
|
12591
|
-
|
|
12697
|
+
} catch (error) {
|
|
12698
|
+
return null;
|
|
12592
12699
|
}
|
|
12593
|
-
|
|
12594
|
-
|
|
12595
|
-
|
|
12596
|
-
|
|
12597
|
-
|
|
12598
|
-
|
|
12599
|
-
|
|
12700
|
+
}
|
|
12701
|
+
function stripHostnameSuffixes(hostname) {
|
|
12702
|
+
if (!hostname) return hostname;
|
|
12703
|
+
let normalized = hostname;
|
|
12704
|
+
for (const suffix of HOSTNAME_SUFFIXES) {
|
|
12705
|
+
if (normalized.endsWith(suffix)) {
|
|
12706
|
+
normalized = normalized.slice(0, -suffix.length);
|
|
12707
|
+
break;
|
|
12600
12708
|
}
|
|
12601
|
-
return this.authenticated;
|
|
12602
12709
|
}
|
|
12603
|
-
|
|
12604
|
-
|
|
12605
|
-
|
|
12606
|
-
|
|
12607
|
-
|
|
12608
|
-
|
|
12609
|
-
|
|
12610
|
-
|
|
12611
|
-
|
|
12612
|
-
|
|
12613
|
-
|
|
12614
|
-
|
|
12615
|
-
|
|
12616
|
-
|
|
12617
|
-
|
|
12618
|
-
|
|
12619
|
-
|
|
12620
|
-
|
|
12621
|
-
|
|
12622
|
-
|
|
12623
|
-
|
|
12710
|
+
return normalized;
|
|
12711
|
+
}
|
|
12712
|
+
function generateStableComputerName() {
|
|
12713
|
+
const osSpecificName = getOsSpecificComputerName();
|
|
12714
|
+
if (osSpecificName) {
|
|
12715
|
+
return osSpecificName;
|
|
12716
|
+
}
|
|
12717
|
+
const currentHostname = os.hostname();
|
|
12718
|
+
return stripHostnameSuffixes(currentHostname);
|
|
12719
|
+
}
|
|
12720
|
+
function getStableComputerName() {
|
|
12721
|
+
const cached = configStore.get(STABLE_COMPUTER_NAME_CONFIG_KEY);
|
|
12722
|
+
if (cached) {
|
|
12723
|
+
return cached;
|
|
12724
|
+
}
|
|
12725
|
+
const currentName = generateStableComputerName();
|
|
12726
|
+
configStore.set(STABLE_COMPUTER_NAME_CONFIG_KEY, currentName);
|
|
12727
|
+
return currentName;
|
|
12728
|
+
}
|
|
12729
|
+
|
|
12730
|
+
// src/utils/sanitize-sensitive-data.ts
|
|
12731
|
+
import { OpenRedaction } from "@openredaction/openredaction";
|
|
12732
|
+
var openRedaction = new OpenRedaction({
|
|
12733
|
+
patterns: [
|
|
12734
|
+
// Core Personal Data
|
|
12735
|
+
// Removed EMAIL - causes false positives in code/test snippets (e.g. --author="Eve Author <eve@example.com>")
|
|
12736
|
+
// Prefer false negatives over false positives for this use case.
|
|
12737
|
+
"SSN",
|
|
12738
|
+
"NATIONAL_INSURANCE_UK",
|
|
12739
|
+
"DATE_OF_BIRTH",
|
|
12740
|
+
// Identity Documents
|
|
12741
|
+
"PASSPORT_UK",
|
|
12742
|
+
"PASSPORT_US",
|
|
12743
|
+
"PASSPORT_MRZ_TD1",
|
|
12744
|
+
"PASSPORT_MRZ_TD3",
|
|
12745
|
+
"DRIVING_LICENSE_UK",
|
|
12746
|
+
"DRIVING_LICENSE_US",
|
|
12747
|
+
"VISA_NUMBER",
|
|
12748
|
+
"VISA_MRZ",
|
|
12749
|
+
"TAX_ID",
|
|
12750
|
+
// Financial Data (removed SWIFT_BIC, CARD_AUTH_CODE - too broad, causing false positives with authentication words)
|
|
12751
|
+
// Removed CREDIT_CARD - causes false positives on zero-filled UUIDs (e.g. '00000000-0000-0000-0000-000000000000')
|
|
12752
|
+
// Prefer false negatives over false positives for this use case.
|
|
12753
|
+
"IBAN",
|
|
12754
|
+
"BANK_ACCOUNT_UK",
|
|
12755
|
+
"ROUTING_NUMBER_US",
|
|
12756
|
+
"CARD_TRACK1_DATA",
|
|
12757
|
+
"CARD_TRACK2_DATA",
|
|
12758
|
+
"CARD_EXPIRY",
|
|
12759
|
+
// Cryptocurrency (removed BITCOIN_ADDRESS - too broad, matches hash-like strings)
|
|
12760
|
+
"ETHEREUM_ADDRESS",
|
|
12761
|
+
"LITECOIN_ADDRESS",
|
|
12762
|
+
"CARDANO_ADDRESS",
|
|
12763
|
+
"SOLANA_ADDRESS",
|
|
12764
|
+
"MONERO_ADDRESS",
|
|
12765
|
+
"RIPPLE_ADDRESS",
|
|
12766
|
+
// Medical Data (removed PRESCRIPTION_NUMBER - too broad, matches words containing "ription")
|
|
12767
|
+
// Removed MEDICAL_RECORD_NUMBER - too broad, "MR" prefix matches "Merge Request" in SCM contexts (e.g. "MR branches" → "MR br****es")
|
|
12768
|
+
"NHS_NUMBER",
|
|
12769
|
+
"AUSTRALIAN_MEDICARE",
|
|
12770
|
+
"HEALTH_PLAN_NUMBER",
|
|
12771
|
+
"PATIENT_ID",
|
|
12772
|
+
// Communications (removed EMERGENCY_CONTACT, ADDRESS_PO_BOX, ZIP_CODE_US, PHONE_US, PHONE_INTERNATIONAL - too broad, causing false positives)
|
|
12773
|
+
"PHONE_UK",
|
|
12774
|
+
"PHONE_UK_MOBILE",
|
|
12775
|
+
"PHONE_LINE_NUMBER",
|
|
12776
|
+
"ADDRESS_STREET",
|
|
12777
|
+
"POSTCODE_UK",
|
|
12778
|
+
// Network & Technical
|
|
12779
|
+
"IPV4",
|
|
12780
|
+
"IPV6",
|
|
12781
|
+
"MAC_ADDRESS",
|
|
12782
|
+
"URL_WITH_AUTH",
|
|
12783
|
+
// Security Keys & Tokens
|
|
12784
|
+
"PRIVATE_KEY",
|
|
12785
|
+
"SSH_PRIVATE_KEY",
|
|
12786
|
+
"AWS_SECRET_KEY",
|
|
12787
|
+
"AWS_ACCESS_KEY",
|
|
12788
|
+
"AZURE_STORAGE_KEY",
|
|
12789
|
+
"GCP_SERVICE_ACCOUNT",
|
|
12790
|
+
"JWT_TOKEN",
|
|
12791
|
+
"OAUTH_TOKEN",
|
|
12792
|
+
"OAUTH_CLIENT_SECRET",
|
|
12793
|
+
"BEARER_TOKEN",
|
|
12794
|
+
"PAYMENT_TOKEN",
|
|
12795
|
+
"GENERIC_SECRET",
|
|
12796
|
+
"GENERIC_API_KEY",
|
|
12797
|
+
// Platform-Specific API Keys
|
|
12798
|
+
"GITHUB_TOKEN",
|
|
12799
|
+
"SLACK_TOKEN",
|
|
12800
|
+
"STRIPE_API_KEY",
|
|
12801
|
+
"GOOGLE_API_KEY",
|
|
12802
|
+
"FIREBASE_API_KEY",
|
|
12803
|
+
"HEROKU_API_KEY",
|
|
12804
|
+
"MAILGUN_API_KEY",
|
|
12805
|
+
"SENDGRID_API_KEY",
|
|
12806
|
+
"TWILIO_API_KEY",
|
|
12807
|
+
"NPM_TOKEN",
|
|
12808
|
+
"PYPI_TOKEN",
|
|
12809
|
+
"DOCKER_AUTH",
|
|
12810
|
+
"KUBERNETES_SECRET",
|
|
12811
|
+
// Government & Legal
|
|
12812
|
+
// Removed CLIENT_ID - too broad, "client" is ubiquitous in code (npm packages like @scope/client-*, class names like ClientSdkOptions)
|
|
12813
|
+
"POLICE_REPORT_NUMBER",
|
|
12814
|
+
"IMMIGRATION_NUMBER",
|
|
12815
|
+
"COURT_REPORTER_LICENSE"
|
|
12816
|
+
]
|
|
12817
|
+
});
|
|
12818
|
+
function maskString(str, showStart = 2, showEnd = 2) {
|
|
12819
|
+
if (str.length <= showStart + showEnd) {
|
|
12820
|
+
return "*".repeat(str.length);
|
|
12821
|
+
}
|
|
12822
|
+
return str.slice(0, showStart) + "*".repeat(str.length - showStart - showEnd) + str.slice(-showEnd);
|
|
12823
|
+
}
|
|
12824
|
+
async function sanitizeDataWithCounts(obj) {
|
|
12825
|
+
const counts = {
|
|
12826
|
+
detections: { total: 0, high: 0, medium: 0, low: 0 }
|
|
12827
|
+
};
|
|
12828
|
+
const sanitizeString = async (str) => {
|
|
12829
|
+
let result = str;
|
|
12830
|
+
const piiDetections = openRedaction.scan(str);
|
|
12831
|
+
if (piiDetections && piiDetections.total > 0) {
|
|
12832
|
+
const allDetections = [
|
|
12833
|
+
...piiDetections.high,
|
|
12834
|
+
...piiDetections.medium,
|
|
12835
|
+
...piiDetections.low
|
|
12836
|
+
];
|
|
12837
|
+
for (const detection of allDetections) {
|
|
12838
|
+
counts.detections.total++;
|
|
12839
|
+
if (detection.severity === "high") counts.detections.high++;
|
|
12840
|
+
else if (detection.severity === "medium") counts.detections.medium++;
|
|
12841
|
+
else if (detection.severity === "low") counts.detections.low++;
|
|
12842
|
+
const masked = maskString(detection.value);
|
|
12843
|
+
result = result.replaceAll(detection.value, masked);
|
|
12624
12844
|
}
|
|
12625
|
-
} catch (error) {
|
|
12626
|
-
return {
|
|
12627
|
-
isAuthenticated: false,
|
|
12628
|
-
message: error instanceof Error ? error.message : "Unknown authentication error"
|
|
12629
|
-
};
|
|
12630
12845
|
}
|
|
12631
|
-
return
|
|
12632
|
-
}
|
|
12633
|
-
|
|
12634
|
-
|
|
12635
|
-
|
|
12636
|
-
|
|
12637
|
-
|
|
12638
|
-
|
|
12639
|
-
|
|
12846
|
+
return result;
|
|
12847
|
+
};
|
|
12848
|
+
const sanitizeRecursive = async (data) => {
|
|
12849
|
+
if (typeof data === "string") {
|
|
12850
|
+
return sanitizeString(data);
|
|
12851
|
+
} else if (Array.isArray(data)) {
|
|
12852
|
+
return Promise.all(data.map((item) => sanitizeRecursive(item)));
|
|
12853
|
+
} else if (data instanceof Error) {
|
|
12854
|
+
return data;
|
|
12855
|
+
} else if (data instanceof Date) {
|
|
12856
|
+
return data;
|
|
12857
|
+
} else if (typeof data === "object" && data !== null) {
|
|
12858
|
+
const sanitized = {};
|
|
12859
|
+
const record = data;
|
|
12860
|
+
for (const key in record) {
|
|
12861
|
+
if (Object.prototype.hasOwnProperty.call(record, key)) {
|
|
12862
|
+
sanitized[key] = await sanitizeRecursive(record[key]);
|
|
12863
|
+
}
|
|
12640
12864
|
}
|
|
12641
|
-
|
|
12642
|
-
|
|
12643
|
-
|
|
12644
|
-
|
|
12645
|
-
|
|
12646
|
-
|
|
12647
|
-
|
|
12648
|
-
|
|
12649
|
-
|
|
12650
|
-
|
|
12651
|
-
|
|
12652
|
-
|
|
12653
|
-
|
|
12654
|
-
|
|
12655
|
-
|
|
12865
|
+
return sanitized;
|
|
12866
|
+
}
|
|
12867
|
+
return data;
|
|
12868
|
+
};
|
|
12869
|
+
const sanitizedData = await sanitizeRecursive(obj);
|
|
12870
|
+
return { sanitizedData, counts };
|
|
12871
|
+
}
|
|
12872
|
+
|
|
12873
|
+
// src/args/commands/upload_ai_blame.ts
|
|
12874
|
+
var defaultLogger2 = {
|
|
12875
|
+
info: (msg, data) => {
|
|
12876
|
+
if (data !== void 0) {
|
|
12877
|
+
console.log(msg, data);
|
|
12878
|
+
} else {
|
|
12879
|
+
console.log(msg);
|
|
12880
|
+
}
|
|
12881
|
+
},
|
|
12882
|
+
error: (msg, data) => {
|
|
12883
|
+
if (data !== void 0) {
|
|
12884
|
+
console.error(msg, data);
|
|
12885
|
+
} else {
|
|
12886
|
+
console.error(msg);
|
|
12656
12887
|
}
|
|
12657
12888
|
}
|
|
12658
|
-
|
|
12659
|
-
|
|
12660
|
-
|
|
12661
|
-
|
|
12662
|
-
|
|
12889
|
+
};
|
|
12890
|
+
var PromptItemZ = z27.object({
|
|
12891
|
+
type: z27.enum([
|
|
12892
|
+
"USER_PROMPT",
|
|
12893
|
+
"AI_RESPONSE",
|
|
12894
|
+
"TOOL_EXECUTION",
|
|
12895
|
+
"AI_THINKING",
|
|
12896
|
+
"MCP_TOOL_CALL"
|
|
12897
|
+
// MCP (Model Context Protocol) tool invocation
|
|
12898
|
+
]),
|
|
12899
|
+
attachedFiles: z27.array(
|
|
12900
|
+
z27.object({
|
|
12901
|
+
relativePath: z27.string(),
|
|
12902
|
+
startLine: z27.number().optional()
|
|
12903
|
+
})
|
|
12904
|
+
).optional(),
|
|
12905
|
+
tokens: z27.object({
|
|
12906
|
+
inputCount: z27.number(),
|
|
12907
|
+
outputCount: z27.number()
|
|
12908
|
+
}).optional(),
|
|
12909
|
+
text: z27.string().optional(),
|
|
12910
|
+
date: z27.date().optional(),
|
|
12911
|
+
tool: z27.object({
|
|
12912
|
+
name: z27.string(),
|
|
12913
|
+
parameters: z27.string(),
|
|
12914
|
+
result: z27.string(),
|
|
12915
|
+
rawArguments: z27.string().optional(),
|
|
12916
|
+
accepted: z27.boolean().optional(),
|
|
12917
|
+
// MCP-specific fields (only populated for MCP_TOOL_CALL type)
|
|
12918
|
+
mcpServer: z27.string().optional(),
|
|
12919
|
+
// MCP server name (e.g., "datadog", "mobb-mcp")
|
|
12920
|
+
mcpToolName: z27.string().optional()
|
|
12921
|
+
// MCP tool name without prefix (e.g., "scan_and_fix_vulnerabilities")
|
|
12922
|
+
}).optional()
|
|
12923
|
+
});
|
|
12924
|
+
var PromptItemArrayZ = z27.array(PromptItemZ);
|
|
12925
|
+
async function getRepositoryUrl() {
|
|
12926
|
+
try {
|
|
12927
|
+
const gitService = new GitService(process.cwd());
|
|
12928
|
+
const isRepo = await gitService.isGitRepository();
|
|
12929
|
+
if (!isRepo) {
|
|
12663
12930
|
return null;
|
|
12664
12931
|
}
|
|
12665
|
-
const
|
|
12666
|
-
|
|
12667
|
-
|
|
12668
|
-
|
|
12669
|
-
return crypto.privateDecrypt(
|
|
12670
|
-
this.privateKey,
|
|
12671
|
-
Buffer.from(encryptedApiToken, "base64")
|
|
12672
|
-
).toString("utf-8");
|
|
12673
|
-
}
|
|
12932
|
+
const remoteUrl = await gitService.getRemoteUrl();
|
|
12933
|
+
const parsed = parseScmURL(remoteUrl);
|
|
12934
|
+
return parsed?.scmType === "GitHub" /* GitHub */ || parsed?.scmType === "GitLab" /* GitLab */ ? remoteUrl : null;
|
|
12935
|
+
} catch {
|
|
12674
12936
|
return null;
|
|
12675
12937
|
}
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
|
|
12938
|
+
}
|
|
12939
|
+
function getSystemInfo() {
|
|
12940
|
+
let userName;
|
|
12941
|
+
try {
|
|
12942
|
+
userName = os2.userInfo().username;
|
|
12943
|
+
} catch {
|
|
12944
|
+
userName = void 0;
|
|
12945
|
+
}
|
|
12946
|
+
return {
|
|
12947
|
+
computerName: getStableComputerName(),
|
|
12948
|
+
userName
|
|
12949
|
+
};
|
|
12950
|
+
}
|
|
12951
|
+
function uploadAiBlameBuilder(args) {
|
|
12952
|
+
return args.option("prompt", {
|
|
12953
|
+
type: "string",
|
|
12954
|
+
array: true,
|
|
12955
|
+
demandOption: true,
|
|
12956
|
+
describe: chalk3.bold("Path(s) to prompt artifact(s) (one per session)")
|
|
12957
|
+
}).option("inference", {
|
|
12958
|
+
type: "string",
|
|
12959
|
+
array: true,
|
|
12960
|
+
demandOption: true,
|
|
12961
|
+
describe: chalk3.bold(
|
|
12962
|
+
"Path(s) to inference artifact(s) (one per session)"
|
|
12963
|
+
)
|
|
12964
|
+
}).option("ai-response-at", {
|
|
12965
|
+
type: "string",
|
|
12966
|
+
array: true,
|
|
12967
|
+
describe: chalk3.bold(
|
|
12968
|
+
"ISO timestamp(s) for AI response (one per session, defaults to now)"
|
|
12969
|
+
)
|
|
12970
|
+
}).option("model", {
|
|
12971
|
+
type: "string",
|
|
12972
|
+
array: true,
|
|
12973
|
+
describe: chalk3.bold("AI model name(s) (optional, one per session)")
|
|
12974
|
+
}).option("tool-name", {
|
|
12975
|
+
type: "string",
|
|
12976
|
+
array: true,
|
|
12977
|
+
describe: chalk3.bold("Tool/IDE name(s) (optional, one per session)")
|
|
12978
|
+
}).option("blame-type", {
|
|
12979
|
+
type: "string",
|
|
12980
|
+
array: true,
|
|
12981
|
+
choices: Object.values(AiBlameInferenceType),
|
|
12982
|
+
describe: chalk3.bold(
|
|
12983
|
+
"Blame type(s) (optional, one per session, defaults to CHAT)"
|
|
12984
|
+
)
|
|
12985
|
+
}).strict();
|
|
12986
|
+
}
|
|
12987
|
+
async function uploadAiBlameHandlerFromExtension(args) {
|
|
12988
|
+
const uploadArgs = {
|
|
12989
|
+
prompt: [],
|
|
12990
|
+
inference: [],
|
|
12991
|
+
model: [],
|
|
12992
|
+
toolName: [],
|
|
12993
|
+
aiResponseAt: [],
|
|
12994
|
+
blameType: [],
|
|
12995
|
+
sessionId: []
|
|
12996
|
+
};
|
|
12997
|
+
let promptsCounts;
|
|
12998
|
+
let inferenceCounts;
|
|
12999
|
+
let promptsUUID;
|
|
13000
|
+
let inferenceUUID;
|
|
13001
|
+
await withFile(async (promptFile) => {
|
|
13002
|
+
const promptsResult = await sanitizeDataWithCounts(args.prompts);
|
|
13003
|
+
promptsCounts = promptsResult.counts;
|
|
13004
|
+
promptsUUID = path7.basename(promptFile.path, path7.extname(promptFile.path));
|
|
13005
|
+
await fsPromises2.writeFile(
|
|
13006
|
+
promptFile.path,
|
|
13007
|
+
JSON.stringify(promptsResult.sanitizedData, null, 2),
|
|
13008
|
+
"utf-8"
|
|
13009
|
+
);
|
|
13010
|
+
uploadArgs.prompt.push(promptFile.path);
|
|
13011
|
+
await withFile(async (inferenceFile) => {
|
|
13012
|
+
const inferenceResult = await sanitizeDataWithCounts(args.inference);
|
|
13013
|
+
inferenceCounts = inferenceResult.counts;
|
|
13014
|
+
inferenceUUID = path7.basename(
|
|
13015
|
+
inferenceFile.path,
|
|
13016
|
+
path7.extname(inferenceFile.path)
|
|
13017
|
+
);
|
|
13018
|
+
await fsPromises2.writeFile(
|
|
13019
|
+
inferenceFile.path,
|
|
13020
|
+
inferenceResult.sanitizedData,
|
|
13021
|
+
"utf-8"
|
|
13022
|
+
);
|
|
13023
|
+
uploadArgs.inference.push(inferenceFile.path);
|
|
13024
|
+
uploadArgs.model.push(args.model);
|
|
13025
|
+
uploadArgs.toolName.push(args.tool);
|
|
13026
|
+
uploadArgs.aiResponseAt.push(args.responseTime);
|
|
13027
|
+
uploadArgs.blameType.push(args.blameType || "CHAT" /* Chat */);
|
|
13028
|
+
if (args.sessionId) {
|
|
13029
|
+
uploadArgs.sessionId.push(args.sessionId);
|
|
13030
|
+
}
|
|
13031
|
+
await uploadAiBlameHandler({
|
|
13032
|
+
args: uploadArgs,
|
|
13033
|
+
exitOnError: false,
|
|
13034
|
+
apiUrl: args.apiUrl,
|
|
13035
|
+
webAppUrl: args.webAppUrl,
|
|
13036
|
+
repositoryUrl: args.repositoryUrl
|
|
12685
13037
|
});
|
|
13038
|
+
});
|
|
13039
|
+
});
|
|
13040
|
+
return {
|
|
13041
|
+
promptsCounts,
|
|
13042
|
+
inferenceCounts,
|
|
13043
|
+
promptsUUID,
|
|
13044
|
+
inferenceUUID
|
|
13045
|
+
};
|
|
13046
|
+
}
|
|
13047
|
+
async function uploadAiBlameHandler(options) {
|
|
13048
|
+
const {
|
|
13049
|
+
args,
|
|
13050
|
+
exitOnError = true,
|
|
13051
|
+
apiUrl,
|
|
13052
|
+
webAppUrl,
|
|
13053
|
+
logger: logger2 = defaultLogger2
|
|
13054
|
+
} = options;
|
|
13055
|
+
const prompts = args.prompt || [];
|
|
13056
|
+
const inferences = args.inference || [];
|
|
13057
|
+
const models = args.model || [];
|
|
13058
|
+
const tools = args.toolName || args["tool-name"] || [];
|
|
13059
|
+
const responseTimes = args.aiResponseAt || args["ai-response-at"] || [];
|
|
13060
|
+
const blameTypes = args.blameType || args["blame-type"] || [];
|
|
13061
|
+
const sessionIds = args.sessionId || args["session-id"] || [];
|
|
13062
|
+
if (prompts.length !== inferences.length) {
|
|
13063
|
+
const errorMsg = "prompt and inference must have the same number of entries";
|
|
13064
|
+
logger2.error(chalk3.red(errorMsg));
|
|
13065
|
+
if (exitOnError) {
|
|
13066
|
+
process.exit(1);
|
|
12686
13067
|
}
|
|
12687
|
-
|
|
12688
|
-
}
|
|
12689
|
-
/**
|
|
12690
|
-
* Assigns a GQL client instance to the AuthManager, and resets auth state
|
|
12691
|
-
* @param gqlClient The GQL client instance to set
|
|
12692
|
-
*/
|
|
12693
|
-
setGQLClient(gqlClient) {
|
|
12694
|
-
this.gqlClient = gqlClient;
|
|
12695
|
-
this.cleanup();
|
|
13068
|
+
throw new Error(errorMsg);
|
|
12696
13069
|
}
|
|
12697
|
-
|
|
12698
|
-
|
|
12699
|
-
|
|
12700
|
-
|
|
12701
|
-
|
|
12702
|
-
|
|
12703
|
-
|
|
12704
|
-
|
|
12705
|
-
|
|
13070
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
13071
|
+
const { computerName, userName } = getSystemInfo();
|
|
13072
|
+
const repositoryUrl = options.repositoryUrl !== void 0 ? options.repositoryUrl : await getRepositoryUrl();
|
|
13073
|
+
const sessions = [];
|
|
13074
|
+
for (let i = 0; i < prompts.length; i++) {
|
|
13075
|
+
const promptPath = String(prompts[i]);
|
|
13076
|
+
const inferencePath = String(inferences[i]);
|
|
13077
|
+
try {
|
|
13078
|
+
await Promise.all([
|
|
13079
|
+
fsPromises2.access(promptPath),
|
|
13080
|
+
fsPromises2.access(inferencePath)
|
|
13081
|
+
]);
|
|
13082
|
+
} catch {
|
|
13083
|
+
const errorMsg = `File not found for session ${i + 1}`;
|
|
13084
|
+
logger2.error(chalk3.red(errorMsg));
|
|
13085
|
+
if (exitOnError) {
|
|
13086
|
+
process.exit(1);
|
|
13087
|
+
}
|
|
13088
|
+
throw new Error(errorMsg);
|
|
13089
|
+
}
|
|
13090
|
+
sessions.push({
|
|
13091
|
+
promptFileName: path7.basename(promptPath),
|
|
13092
|
+
inferenceFileName: path7.basename(inferencePath),
|
|
13093
|
+
aiResponseAt: responseTimes[i] || nowIso,
|
|
13094
|
+
model: models[i],
|
|
13095
|
+
toolName: tools[i],
|
|
13096
|
+
blameType: blameTypes[i] || "CHAT" /* Chat */,
|
|
13097
|
+
computerName,
|
|
13098
|
+
userName,
|
|
13099
|
+
sessionId: sessionIds[i],
|
|
13100
|
+
repositoryUrl
|
|
13101
|
+
});
|
|
12706
13102
|
}
|
|
12707
|
-
|
|
12708
|
-
|
|
12709
|
-
// src/commands/handleMobbLogin.ts
|
|
12710
|
-
var debug8 = Debug7("mobbdev:commands");
|
|
12711
|
-
var LOGIN_MAX_WAIT2 = 10 * 60 * 1e3;
|
|
12712
|
-
var LOGIN_CHECK_DELAY2 = 5 * 1e3;
|
|
12713
|
-
var MOBB_LOGIN_REQUIRED_MSG = `\u{1F513} Login to Mobb is Required, you will be redirected to our login page, once the authorization is complete return to this prompt, ${chalk3.bgBlue(
|
|
12714
|
-
"press any key to continue"
|
|
12715
|
-
)};`;
|
|
12716
|
-
async function getAuthenticatedGQLClient({
|
|
12717
|
-
inputApiKey = "",
|
|
12718
|
-
isSkipPrompts = true,
|
|
12719
|
-
apiUrl,
|
|
12720
|
-
webAppUrl
|
|
12721
|
-
}) {
|
|
12722
|
-
debug8(
|
|
12723
|
-
"getAuthenticatedGQLClient called with: apiUrl=%s, webAppUrl=%s",
|
|
12724
|
-
apiUrl || "undefined",
|
|
12725
|
-
webAppUrl || "undefined"
|
|
12726
|
-
);
|
|
12727
|
-
const authManager = new AuthManager(webAppUrl, apiUrl);
|
|
12728
|
-
let gqlClient = authManager.getGQLClient(inputApiKey);
|
|
12729
|
-
gqlClient = await handleMobbLogin({
|
|
12730
|
-
inGqlClient: gqlClient,
|
|
12731
|
-
skipPrompts: isSkipPrompts,
|
|
13103
|
+
const authenticatedClient = await getAuthenticatedGQLClient({
|
|
13104
|
+
isSkipPrompts: true,
|
|
12732
13105
|
apiUrl,
|
|
12733
13106
|
webAppUrl
|
|
12734
13107
|
});
|
|
12735
|
-
|
|
12736
|
-
}
|
|
12737
|
-
async function handleMobbLogin({
|
|
12738
|
-
inGqlClient,
|
|
12739
|
-
apiKey,
|
|
12740
|
-
skipPrompts,
|
|
12741
|
-
apiUrl,
|
|
12742
|
-
webAppUrl,
|
|
12743
|
-
loginContext
|
|
12744
|
-
}) {
|
|
12745
|
-
debug8(
|
|
12746
|
-
"handleMobbLogin: resolved URLs - apiUrl=%s (from param: %s), webAppUrl=%s (from param: %s)",
|
|
12747
|
-
apiUrl || "fallback",
|
|
12748
|
-
apiUrl || "fallback",
|
|
12749
|
-
webAppUrl || "fallback",
|
|
12750
|
-
webAppUrl || "fallback"
|
|
13108
|
+
const initSessions = sessions.map(
|
|
13109
|
+
({ sessionId: _sessionId, ...rest }) => rest
|
|
12751
13110
|
);
|
|
12752
|
-
const
|
|
12753
|
-
|
|
12754
|
-
|
|
12755
|
-
|
|
12756
|
-
|
|
12757
|
-
|
|
12758
|
-
|
|
12759
|
-
|
|
12760
|
-
|
|
12761
|
-
return authManager.getGQLClient();
|
|
13111
|
+
const initRes = await authenticatedClient.uploadAIBlameInferencesInitRaw({
|
|
13112
|
+
sessions: initSessions
|
|
13113
|
+
});
|
|
13114
|
+
const uploadSessions = initRes.uploadAIBlameInferencesInit?.uploadSessions ?? [];
|
|
13115
|
+
if (uploadSessions.length !== sessions.length) {
|
|
13116
|
+
const errorMsg = "Init failed to return expected number of sessions";
|
|
13117
|
+
logger2.error(chalk3.red(errorMsg));
|
|
13118
|
+
if (exitOnError) {
|
|
13119
|
+
process.exit(1);
|
|
12762
13120
|
}
|
|
12763
|
-
|
|
12764
|
-
debug8("Authentication check failed:", error);
|
|
13121
|
+
throw new Error(errorMsg);
|
|
12765
13122
|
}
|
|
12766
|
-
|
|
12767
|
-
|
|
12768
|
-
|
|
12769
|
-
|
|
12770
|
-
|
|
12771
|
-
|
|
12772
|
-
|
|
13123
|
+
for (let i = 0; i < uploadSessions.length; i++) {
|
|
13124
|
+
const us = uploadSessions[i];
|
|
13125
|
+
const promptPath = String(prompts[i]);
|
|
13126
|
+
const inferencePath = String(inferences[i]);
|
|
13127
|
+
await Promise.all([
|
|
13128
|
+
// Prompt
|
|
13129
|
+
uploadFile({
|
|
13130
|
+
file: promptPath,
|
|
13131
|
+
url: us.prompt.url,
|
|
13132
|
+
uploadFields: JSON.parse(us.prompt.uploadFieldsJSON),
|
|
13133
|
+
uploadKey: us.prompt.uploadKey
|
|
13134
|
+
}),
|
|
13135
|
+
// Inference
|
|
13136
|
+
uploadFile({
|
|
13137
|
+
file: inferencePath,
|
|
13138
|
+
url: us.inference.url,
|
|
13139
|
+
uploadFields: JSON.parse(us.inference.uploadFieldsJSON),
|
|
13140
|
+
uploadKey: us.inference.uploadKey
|
|
13141
|
+
})
|
|
13142
|
+
]);
|
|
12773
13143
|
}
|
|
12774
|
-
const
|
|
12775
|
-
|
|
12776
|
-
|
|
12777
|
-
|
|
12778
|
-
|
|
12779
|
-
|
|
12780
|
-
|
|
13144
|
+
const finalizeSessions = uploadSessions.map((us, i) => {
|
|
13145
|
+
const s = sessions[i];
|
|
13146
|
+
return {
|
|
13147
|
+
aiBlameInferenceId: us.aiBlameInferenceId,
|
|
13148
|
+
promptKey: us.prompt.uploadKey,
|
|
13149
|
+
inferenceKey: us.inference.uploadKey,
|
|
13150
|
+
aiResponseAt: s.aiResponseAt,
|
|
13151
|
+
model: s.model,
|
|
13152
|
+
toolName: s.toolName,
|
|
13153
|
+
blameType: s.blameType,
|
|
13154
|
+
computerName: s.computerName,
|
|
13155
|
+
userName: s.userName,
|
|
13156
|
+
sessionId: s.sessionId,
|
|
13157
|
+
repositoryUrl: s.repositoryUrl
|
|
13158
|
+
};
|
|
12781
13159
|
});
|
|
12782
13160
|
try {
|
|
12783
|
-
|
|
12784
|
-
|
|
12785
|
-
loginSpinner.error({
|
|
12786
|
-
text: "Failed to generate login URL"
|
|
12787
|
-
});
|
|
12788
|
-
throw new CliError("Failed to generate login URL");
|
|
12789
|
-
}
|
|
12790
|
-
!skipPrompts && console.log(
|
|
12791
|
-
`If the page does not open automatically, kindly access it through ${loginUrl}.`
|
|
13161
|
+
logger2.info(
|
|
13162
|
+
`[UPLOAD] Calling finalizeAIBlameInferencesUploadRaw with ${finalizeSessions.length} sessions`
|
|
12792
13163
|
);
|
|
12793
|
-
|
|
12794
|
-
|
|
12795
|
-
|
|
12796
|
-
|
|
12797
|
-
|
|
12798
|
-
|
|
12799
|
-
|
|
13164
|
+
const finRes = await authenticatedClient.finalizeAIBlameInferencesUploadRaw(
|
|
13165
|
+
{
|
|
13166
|
+
sessions: finalizeSessions
|
|
13167
|
+
}
|
|
13168
|
+
);
|
|
13169
|
+
logger2.info("[UPLOAD] Finalize response:", JSON.stringify(finRes, null, 2));
|
|
13170
|
+
const status = finRes?.finalizeAIBlameInferencesUpload?.status;
|
|
13171
|
+
if (status !== "OK") {
|
|
13172
|
+
const errorMsg = finRes?.finalizeAIBlameInferencesUpload?.error || "unknown error";
|
|
13173
|
+
logger2.error(
|
|
13174
|
+
chalk3.red(
|
|
13175
|
+
`[UPLOAD] Finalize failed with status: ${status}, error: ${errorMsg}`
|
|
13176
|
+
)
|
|
13177
|
+
);
|
|
13178
|
+
if (exitOnError) {
|
|
13179
|
+
process.exit(1);
|
|
13180
|
+
}
|
|
13181
|
+
throw new Error(errorMsg);
|
|
12800
13182
|
}
|
|
12801
|
-
|
|
12802
|
-
|
|
12803
|
-
|
|
12804
|
-
|
|
12805
|
-
} finally {
|
|
12806
|
-
authManager.cleanup();
|
|
13183
|
+
logger2.info(chalk3.green("[UPLOAD] AI Blame uploads finalized successfully"));
|
|
13184
|
+
} catch (error) {
|
|
13185
|
+
logger2.error("[UPLOAD] Finalize threw error:", error);
|
|
13186
|
+
throw error;
|
|
12807
13187
|
}
|
|
12808
13188
|
}
|
|
13189
|
+
async function uploadAiBlameCommandHandler(args) {
|
|
13190
|
+
await uploadAiBlameHandler({ args });
|
|
13191
|
+
}
|
|
12809
13192
|
|
|
12810
|
-
// src/features/analysis/
|
|
12811
|
-
|
|
12812
|
-
|
|
12813
|
-
|
|
12814
|
-
import Debug10 from "debug";
|
|
12815
|
-
import parseDiff from "parse-diff";
|
|
12816
|
-
import { z as z28 } from "zod";
|
|
13193
|
+
// src/features/analysis/graphql/tracy-batch-upload.ts
|
|
13194
|
+
var gzipAsync = promisify(gzip);
|
|
13195
|
+
var debug9 = Debug8("mobbdev:tracy-batch-upload");
|
|
13196
|
+
var MAX_BATCH_PAYLOAD_BYTES = 3 * 1024 * 1024;
|
|
12817
13197
|
|
|
12818
|
-
// src/
|
|
12819
|
-
function
|
|
12820
|
-
|
|
12821
|
-
|
|
12822
|
-
|
|
13198
|
+
// src/mcp/services/types.ts
|
|
13199
|
+
function detectIDE() {
|
|
13200
|
+
const env3 = process.env;
|
|
13201
|
+
if (env3["CURSOR_TRACE_ID"] || env3["CURSOR_SESSION_ID"]) return "cursor";
|
|
13202
|
+
if (env3["WINDSURF_IPC_HOOK"] || env3["WINDSURF_PID"]) return "windsurf";
|
|
13203
|
+
if (env3["CLAUDE_DESKTOP"] || env3["ANTHROPIC_CLAUDE"]) return "claude";
|
|
13204
|
+
if (env3["WEBSTORM_VM_OPTIONS"] || env3["IDEA_VM_OPTIONS"] || env3["JETBRAINS_IDE"])
|
|
13205
|
+
return "webstorm";
|
|
13206
|
+
if (env3["VSCODE_IPC_HOOK"] || env3["VSCODE_PID"]) return "vscode";
|
|
13207
|
+
const termProgram = env3["TERM_PROGRAM"]?.toLowerCase();
|
|
13208
|
+
if (termProgram === "windsurf") return "windsurf";
|
|
13209
|
+
if (termProgram === "vscode") return "vscode";
|
|
13210
|
+
return void 0;
|
|
13211
|
+
}
|
|
13212
|
+
function createMcpLoginContext(trigger) {
|
|
13213
|
+
return {
|
|
13214
|
+
trigger,
|
|
13215
|
+
source: "mcp",
|
|
13216
|
+
ide: detectIDE()
|
|
13217
|
+
};
|
|
13218
|
+
}
|
|
13219
|
+
function buildLoginUrl(baseUrl, loginId, hostname, context) {
|
|
13220
|
+
const url = new URL(`${baseUrl}/${loginId}`);
|
|
13221
|
+
url.searchParams.set("hostname", hostname);
|
|
13222
|
+
url.searchParams.set("trigger", context.trigger);
|
|
13223
|
+
url.searchParams.set("source", context.source);
|
|
13224
|
+
if (context.ide) {
|
|
13225
|
+
url.searchParams.set("ide", context.ide);
|
|
13226
|
+
}
|
|
13227
|
+
return url.toString();
|
|
12823
13228
|
}
|
|
12824
13229
|
|
|
12825
|
-
// src/
|
|
12826
|
-
|
|
12827
|
-
|
|
12828
|
-
var
|
|
12829
|
-
|
|
12830
|
-
|
|
12831
|
-
|
|
12832
|
-
|
|
12833
|
-
|
|
12834
|
-
|
|
12835
|
-
|
|
12836
|
-
|
|
12837
|
-
|
|
12838
|
-
);
|
|
12839
|
-
|
|
12840
|
-
|
|
12841
|
-
|
|
13230
|
+
// src/commands/AuthManager.ts
|
|
13231
|
+
var debug10 = Debug9("mobbdev:auth");
|
|
13232
|
+
var LOGIN_MAX_WAIT = 10 * 60 * 1e3;
|
|
13233
|
+
var LOGIN_CHECK_DELAY = 5 * 1e3;
|
|
13234
|
+
var _AuthManager = class _AuthManager {
|
|
13235
|
+
constructor(webAppUrl, apiUrl) {
|
|
13236
|
+
__publicField(this, "publicKey");
|
|
13237
|
+
__publicField(this, "privateKey");
|
|
13238
|
+
__publicField(this, "loginId");
|
|
13239
|
+
__publicField(this, "gqlClient");
|
|
13240
|
+
__publicField(this, "currentBrowserUrl");
|
|
13241
|
+
__publicField(this, "authenticated", null);
|
|
13242
|
+
__publicField(this, "resolvedWebAppUrl");
|
|
13243
|
+
__publicField(this, "resolvedApiUrl");
|
|
13244
|
+
this.resolvedWebAppUrl = webAppUrl || WEB_APP_URL;
|
|
13245
|
+
this.resolvedApiUrl = apiUrl || API_URL;
|
|
13246
|
+
}
|
|
13247
|
+
openUrlInBrowser() {
|
|
13248
|
+
if (this.currentBrowserUrl) {
|
|
13249
|
+
open(this.currentBrowserUrl);
|
|
13250
|
+
return true;
|
|
12842
13251
|
}
|
|
12843
|
-
|
|
12844
|
-
|
|
12845
|
-
|
|
13252
|
+
return false;
|
|
13253
|
+
}
|
|
13254
|
+
async waitForAuthentication() {
|
|
13255
|
+
let newApiToken = null;
|
|
13256
|
+
for (let i = 0; i < _AuthManager.loginMaxWait / LOGIN_CHECK_DELAY; i++) {
|
|
13257
|
+
newApiToken = await this.getApiToken();
|
|
13258
|
+
if (newApiToken) {
|
|
13259
|
+
break;
|
|
13260
|
+
}
|
|
13261
|
+
await sleep(LOGIN_CHECK_DELAY);
|
|
13262
|
+
}
|
|
13263
|
+
if (!newApiToken) {
|
|
13264
|
+
return false;
|
|
13265
|
+
}
|
|
13266
|
+
this.gqlClient = new GQLClient({
|
|
13267
|
+
apiKey: newApiToken,
|
|
13268
|
+
type: "apiKey",
|
|
13269
|
+
apiUrl: this.resolvedApiUrl
|
|
12846
13270
|
});
|
|
12847
|
-
const
|
|
12848
|
-
|
|
12849
|
-
"
|
|
12850
|
-
|
|
12851
|
-
|
|
12852
|
-
|
|
12853
|
-
|
|
12854
|
-
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
|
|
13271
|
+
const loginSuccess = await this.gqlClient.validateUserToken();
|
|
13272
|
+
if (loginSuccess) {
|
|
13273
|
+
configStore.set("apiToken", newApiToken);
|
|
13274
|
+
this.authenticated = true;
|
|
13275
|
+
return true;
|
|
13276
|
+
}
|
|
13277
|
+
return false;
|
|
13278
|
+
}
|
|
13279
|
+
/**
|
|
13280
|
+
* Checks if the user is already authenticated
|
|
13281
|
+
*/
|
|
13282
|
+
async isAuthenticated() {
|
|
13283
|
+
if (this.authenticated === null) {
|
|
13284
|
+
const result = await this.checkAuthentication();
|
|
13285
|
+
this.authenticated = result.isAuthenticated;
|
|
13286
|
+
if (!result.isAuthenticated) {
|
|
13287
|
+
debug10("isAuthenticated: false \u2014 %s", result.message);
|
|
13288
|
+
}
|
|
13289
|
+
}
|
|
13290
|
+
return this.authenticated;
|
|
13291
|
+
}
|
|
13292
|
+
/**
|
|
13293
|
+
* Private function to check if the user is authenticated with the server
|
|
13294
|
+
*/
|
|
13295
|
+
async checkAuthentication(apiKey) {
|
|
13296
|
+
try {
|
|
13297
|
+
if (!this.gqlClient) {
|
|
13298
|
+
this.gqlClient = this.getGQLClient(apiKey);
|
|
13299
|
+
}
|
|
13300
|
+
const isConnected = await this.gqlClient.verifyApiConnection();
|
|
13301
|
+
if (!isConnected) {
|
|
13302
|
+
return {
|
|
13303
|
+
isAuthenticated: false,
|
|
13304
|
+
message: "Failed to connect to Mobb server"
|
|
13305
|
+
};
|
|
13306
|
+
}
|
|
13307
|
+
const userVerify = await this.gqlClient.validateUserToken();
|
|
13308
|
+
if (!userVerify) {
|
|
13309
|
+
return {
|
|
13310
|
+
isAuthenticated: false,
|
|
13311
|
+
message: "User token validation failed"
|
|
13312
|
+
};
|
|
13313
|
+
}
|
|
13314
|
+
} catch (error) {
|
|
13315
|
+
return {
|
|
13316
|
+
isAuthenticated: false,
|
|
13317
|
+
message: error instanceof Error ? error.message : "Unknown authentication error"
|
|
13318
|
+
};
|
|
13319
|
+
}
|
|
13320
|
+
return { isAuthenticated: true, message: "Successfully authenticated" };
|
|
13321
|
+
}
|
|
13322
|
+
/**
|
|
13323
|
+
* Generates a login URL for manual authentication
|
|
13324
|
+
*/
|
|
13325
|
+
async generateLoginUrl(loginContext) {
|
|
13326
|
+
try {
|
|
13327
|
+
if (!this.gqlClient) {
|
|
13328
|
+
this.gqlClient = this.getGQLClient();
|
|
13329
|
+
}
|
|
13330
|
+
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
|
|
13331
|
+
modulusLength: 2048
|
|
12858
13332
|
});
|
|
12859
|
-
|
|
12860
|
-
|
|
12861
|
-
await gqlClient.
|
|
12862
|
-
|
|
12863
|
-
analysisId: submitRes.submitVulnerabilityReport.fixReportId
|
|
12864
|
-
},
|
|
12865
|
-
callback,
|
|
12866
|
-
callbackStates,
|
|
12867
|
-
timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
|
|
13333
|
+
this.publicKey = publicKey;
|
|
13334
|
+
this.privateKey = privateKey;
|
|
13335
|
+
this.loginId = await this.gqlClient.createCliLogin({
|
|
13336
|
+
publicKey: this.publicKey.export({ format: "pem", type: "pkcs1" }).toString()
|
|
12868
13337
|
});
|
|
13338
|
+
const webLoginUrl = `${this.resolvedWebAppUrl}/cli-login`;
|
|
13339
|
+
const browserUrl = loginContext ? buildLoginUrl(webLoginUrl, this.loginId, os3.hostname(), loginContext) : `${webLoginUrl}/${this.loginId}?hostname=${os3.hostname()}`;
|
|
13340
|
+
this.currentBrowserUrl = browserUrl;
|
|
13341
|
+
return browserUrl;
|
|
13342
|
+
} catch (error) {
|
|
13343
|
+
console.error("Failed to generate login URL:", error);
|
|
13344
|
+
return null;
|
|
12869
13345
|
}
|
|
12870
|
-
return submitRes;
|
|
12871
|
-
} catch (e) {
|
|
12872
|
-
spinner.error({ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed" });
|
|
12873
|
-
throw e;
|
|
12874
13346
|
}
|
|
12875
|
-
|
|
12876
|
-
|
|
12877
|
-
|
|
12878
|
-
|
|
13347
|
+
/**
|
|
13348
|
+
* Retrieves and decrypts the API token after authentication
|
|
13349
|
+
*/
|
|
13350
|
+
async getApiToken() {
|
|
13351
|
+
if (!this.gqlClient || !this.loginId || !this.privateKey) {
|
|
13352
|
+
return null;
|
|
13353
|
+
}
|
|
13354
|
+
const encryptedApiToken = await this.gqlClient.getEncryptedApiToken({
|
|
13355
|
+
loginId: this.loginId
|
|
13356
|
+
});
|
|
13357
|
+
if (encryptedApiToken) {
|
|
13358
|
+
return crypto.privateDecrypt(
|
|
13359
|
+
this.privateKey,
|
|
13360
|
+
Buffer.from(encryptedApiToken, "base64")
|
|
13361
|
+
).toString("utf-8");
|
|
13362
|
+
}
|
|
13363
|
+
return null;
|
|
13364
|
+
}
|
|
13365
|
+
/**
|
|
13366
|
+
* Returns true if a non-empty API token is stored in the configStore.
|
|
13367
|
+
* Used for diagnostics — does NOT validate the token.
|
|
13368
|
+
*/
|
|
13369
|
+
hasStoredToken() {
|
|
13370
|
+
const token = configStore.get("apiToken");
|
|
13371
|
+
return typeof token === "string" && token.length > 0;
|
|
13372
|
+
}
|
|
13373
|
+
/**
|
|
13374
|
+
* Gets the current GQL client (if authenticated)
|
|
13375
|
+
*/
|
|
13376
|
+
getGQLClient(inputApiKey) {
|
|
13377
|
+
if (this.gqlClient === void 0) {
|
|
13378
|
+
this.gqlClient = new GQLClient({
|
|
13379
|
+
apiKey: inputApiKey || configStore.get("apiToken") || "",
|
|
13380
|
+
type: "apiKey",
|
|
13381
|
+
apiUrl: this.resolvedApiUrl
|
|
13382
|
+
});
|
|
13383
|
+
}
|
|
13384
|
+
return this.gqlClient;
|
|
13385
|
+
}
|
|
13386
|
+
/**
|
|
13387
|
+
* Assigns a GQL client instance to the AuthManager, and resets auth state
|
|
13388
|
+
* @param gqlClient The GQL client instance to set
|
|
13389
|
+
*/
|
|
13390
|
+
setGQLClient(gqlClient) {
|
|
13391
|
+
this.gqlClient = gqlClient;
|
|
13392
|
+
this.cleanup();
|
|
13393
|
+
}
|
|
13394
|
+
/**
|
|
13395
|
+
* Cleans up any active login session
|
|
13396
|
+
*/
|
|
13397
|
+
cleanup() {
|
|
13398
|
+
this.publicKey = void 0;
|
|
13399
|
+
this.privateKey = void 0;
|
|
13400
|
+
this.loginId = void 0;
|
|
13401
|
+
this.authenticated = null;
|
|
13402
|
+
this.currentBrowserUrl = null;
|
|
13403
|
+
}
|
|
13404
|
+
};
|
|
13405
|
+
/** Maximum time (ms) to wait for login authentication. Override in tests for faster failures. */
|
|
13406
|
+
__publicField(_AuthManager, "loginMaxWait", LOGIN_MAX_WAIT);
|
|
13407
|
+
var AuthManager = _AuthManager;
|
|
13408
|
+
|
|
13409
|
+
// src/commands/handleMobbLogin.ts
|
|
13410
|
+
var debug11 = Debug10("mobbdev:commands");
|
|
13411
|
+
var LOGIN_MAX_WAIT2 = 10 * 60 * 1e3;
|
|
13412
|
+
var LOGIN_CHECK_DELAY2 = 5 * 1e3;
|
|
13413
|
+
var MOBB_LOGIN_REQUIRED_MSG = `\u{1F513} Login to Mobb is Required, you will be redirected to our login page, once the authorization is complete return to this prompt, ${chalk4.bgBlue(
|
|
13414
|
+
"press any key to continue"
|
|
13415
|
+
)};`;
|
|
13416
|
+
async function getAuthenticatedGQLClient({
|
|
13417
|
+
inputApiKey = "",
|
|
13418
|
+
isSkipPrompts = true,
|
|
13419
|
+
apiUrl,
|
|
13420
|
+
webAppUrl
|
|
13421
|
+
}) {
|
|
13422
|
+
debug11(
|
|
13423
|
+
"getAuthenticatedGQLClient called with: apiUrl=%s, webAppUrl=%s",
|
|
13424
|
+
apiUrl || "undefined",
|
|
13425
|
+
webAppUrl || "undefined"
|
|
13426
|
+
);
|
|
13427
|
+
const authManager = new AuthManager(webAppUrl, apiUrl);
|
|
13428
|
+
let gqlClient = authManager.getGQLClient(inputApiKey);
|
|
13429
|
+
gqlClient = await handleMobbLogin({
|
|
13430
|
+
inGqlClient: gqlClient,
|
|
13431
|
+
skipPrompts: isSkipPrompts,
|
|
13432
|
+
apiUrl,
|
|
13433
|
+
webAppUrl
|
|
13434
|
+
});
|
|
13435
|
+
return gqlClient;
|
|
13436
|
+
}
|
|
13437
|
+
async function handleMobbLogin({
|
|
13438
|
+
inGqlClient,
|
|
13439
|
+
apiKey,
|
|
13440
|
+
skipPrompts,
|
|
13441
|
+
apiUrl,
|
|
13442
|
+
webAppUrl,
|
|
13443
|
+
loginContext
|
|
13444
|
+
}) {
|
|
13445
|
+
debug11(
|
|
13446
|
+
"handleMobbLogin: resolved URLs - apiUrl=%s (from param: %s), webAppUrl=%s (from param: %s)",
|
|
13447
|
+
apiUrl || "fallback",
|
|
13448
|
+
apiUrl || "fallback",
|
|
13449
|
+
webAppUrl || "fallback",
|
|
13450
|
+
webAppUrl || "fallback"
|
|
13451
|
+
);
|
|
13452
|
+
const { createSpinner: createSpinner5 } = Spinner({ ci: skipPrompts });
|
|
13453
|
+
const authManager = new AuthManager(webAppUrl, apiUrl);
|
|
13454
|
+
authManager.setGQLClient(inGqlClient);
|
|
13455
|
+
try {
|
|
13456
|
+
const isAuthenticated = await authManager.isAuthenticated();
|
|
13457
|
+
if (isAuthenticated) {
|
|
13458
|
+
createSpinner5().start().success({
|
|
13459
|
+
text: `\u{1F513} Login to Mobb succeeded. Already authenticated`
|
|
13460
|
+
});
|
|
13461
|
+
return authManager.getGQLClient();
|
|
13462
|
+
}
|
|
13463
|
+
} catch (error) {
|
|
13464
|
+
debug11("Authentication check failed:", error);
|
|
13465
|
+
}
|
|
13466
|
+
if (apiKey) {
|
|
13467
|
+
createSpinner5().start().error({
|
|
13468
|
+
text: "\u{1F513} Login to Mobb failed: The provided API key does not match any configured API key on the system"
|
|
13469
|
+
});
|
|
13470
|
+
throw new CliError(
|
|
13471
|
+
"Login to Mobb failed: The provided API key does not match any configured API key on the system"
|
|
13472
|
+
);
|
|
13473
|
+
}
|
|
13474
|
+
const loginSpinner = createSpinner5().start();
|
|
13475
|
+
if (!skipPrompts) {
|
|
13476
|
+
loginSpinner.update({ text: MOBB_LOGIN_REQUIRED_MSG });
|
|
13477
|
+
await keypress();
|
|
13478
|
+
}
|
|
13479
|
+
loginSpinner.update({
|
|
13480
|
+
text: "\u{1F513} Waiting for Mobb login..."
|
|
13481
|
+
});
|
|
13482
|
+
try {
|
|
13483
|
+
const loginUrl = await authManager.generateLoginUrl(loginContext);
|
|
13484
|
+
if (!loginUrl) {
|
|
13485
|
+
loginSpinner.error({
|
|
13486
|
+
text: "Failed to generate login URL"
|
|
13487
|
+
});
|
|
13488
|
+
throw new CliError("Failed to generate login URL");
|
|
13489
|
+
}
|
|
13490
|
+
!skipPrompts && console.log(
|
|
13491
|
+
`If the page does not open automatically, kindly access it through ${loginUrl}.`
|
|
13492
|
+
);
|
|
13493
|
+
authManager.openUrlInBrowser();
|
|
13494
|
+
const authSuccess = await authManager.waitForAuthentication();
|
|
13495
|
+
if (!authSuccess) {
|
|
13496
|
+
loginSpinner.error({
|
|
13497
|
+
text: "Login timeout error"
|
|
13498
|
+
});
|
|
13499
|
+
throw new CliError("Login timeout error");
|
|
13500
|
+
}
|
|
13501
|
+
loginSpinner.success({
|
|
13502
|
+
text: `\u{1F513} Login to Mobb successful!`
|
|
13503
|
+
});
|
|
13504
|
+
return authManager.getGQLClient();
|
|
13505
|
+
} finally {
|
|
13506
|
+
authManager.cleanup();
|
|
13507
|
+
}
|
|
13508
|
+
}
|
|
13509
|
+
|
|
13510
|
+
// src/features/analysis/add_fix_comments_for_pr/add_fix_comments_for_pr.ts
|
|
13511
|
+
import Debug14 from "debug";
|
|
13512
|
+
|
|
13513
|
+
// src/features/analysis/add_fix_comments_for_pr/utils/utils.ts
|
|
13514
|
+
import Debug13 from "debug";
|
|
13515
|
+
import parseDiff from "parse-diff";
|
|
13516
|
+
import { z as z29 } from "zod";
|
|
13517
|
+
|
|
13518
|
+
// src/features/analysis/utils/by_key.ts
|
|
13519
|
+
function keyBy(array, keyBy2) {
|
|
13520
|
+
return array.reduce((acc, item) => {
|
|
13521
|
+
return { ...acc, [item[keyBy2]]: item };
|
|
13522
|
+
}, {});
|
|
13523
|
+
}
|
|
13524
|
+
|
|
13525
|
+
// src/features/analysis/utils/send_report.ts
|
|
13526
|
+
import Debug11 from "debug";
|
|
13527
|
+
init_client_generates();
|
|
13528
|
+
var debug12 = Debug11("mobbdev:index");
|
|
13529
|
+
async function sendReport({
|
|
13530
|
+
spinner,
|
|
13531
|
+
submitVulnerabilityReportVariables,
|
|
13532
|
+
gqlClient,
|
|
13533
|
+
polling
|
|
13534
|
+
}) {
|
|
13535
|
+
try {
|
|
13536
|
+
const submitRes = await gqlClient.submitVulnerabilityReport(
|
|
13537
|
+
submitVulnerabilityReportVariables
|
|
13538
|
+
);
|
|
13539
|
+
if (submitRes.submitVulnerabilityReport.__typename !== "VulnerabilityReport") {
|
|
13540
|
+
debug12("error submit vul report %s", submitRes);
|
|
13541
|
+
throw new Error("\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed");
|
|
13542
|
+
}
|
|
13543
|
+
spinner.update({ text: progressMassages.processingVulnerabilityReport });
|
|
13544
|
+
const callback = (_analysisId) => spinner.update({
|
|
13545
|
+
text: "\u2699\uFE0F Vulnerability report processed successfully"
|
|
13546
|
+
});
|
|
13547
|
+
const callbackStates = [
|
|
13548
|
+
"Digested" /* Digested */,
|
|
13549
|
+
"Finished" /* Finished */
|
|
13550
|
+
];
|
|
13551
|
+
if (polling) {
|
|
13552
|
+
debug12("[sendReport] Using POLLING mode for analysis state updates");
|
|
13553
|
+
await gqlClient.pollForAnalysisState({
|
|
13554
|
+
analysisId: submitRes.submitVulnerabilityReport.fixReportId,
|
|
13555
|
+
callback,
|
|
13556
|
+
callbackStates,
|
|
13557
|
+
timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
|
|
13558
|
+
});
|
|
13559
|
+
} else {
|
|
13560
|
+
debug12("[sendReport] Using WEBSOCKET mode for analysis state updates");
|
|
13561
|
+
await gqlClient.subscribeToAnalysis({
|
|
13562
|
+
subscribeToAnalysisParams: {
|
|
13563
|
+
analysisId: submitRes.submitVulnerabilityReport.fixReportId
|
|
13564
|
+
},
|
|
13565
|
+
callback,
|
|
13566
|
+
callbackStates,
|
|
13567
|
+
timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
|
|
13568
|
+
});
|
|
13569
|
+
}
|
|
13570
|
+
return submitRes;
|
|
13571
|
+
} catch (e) {
|
|
13572
|
+
spinner.error({ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed" });
|
|
13573
|
+
throw e;
|
|
13574
|
+
}
|
|
13575
|
+
}
|
|
13576
|
+
|
|
13577
|
+
// src/features/analysis/utils/index.ts
|
|
13578
|
+
function getFromArraySafe(array) {
|
|
12879
13579
|
return array.reduce((acc, nullableItem) => {
|
|
12880
13580
|
if (nullableItem) {
|
|
12881
13581
|
acc.push(nullableItem);
|
|
@@ -12900,10 +13600,10 @@ var scannerToFriendlyString = {
|
|
|
12900
13600
|
};
|
|
12901
13601
|
|
|
12902
13602
|
// src/features/analysis/add_fix_comments_for_pr/utils/buildCommentBody.ts
|
|
12903
|
-
import
|
|
12904
|
-
import { z as
|
|
13603
|
+
import Debug12 from "debug";
|
|
13604
|
+
import { z as z28 } from "zod";
|
|
12905
13605
|
init_client_generates();
|
|
12906
|
-
var
|
|
13606
|
+
var debug13 = Debug12("mobbdev:handle-finished-analysis");
|
|
12907
13607
|
var getCommitFixButton = (commitUrl) => `<a href="${commitUrl}"><img src=${COMMIT_FIX_SVG}></a>`;
|
|
12908
13608
|
function buildFixCommentBody({
|
|
12909
13609
|
fix,
|
|
@@ -12955,14 +13655,14 @@ function buildFixCommentBody({
|
|
|
12955
13655
|
});
|
|
12956
13656
|
const issueType = getIssueTypeFriendlyString(fix.safeIssueType);
|
|
12957
13657
|
const title = `# ${MobbIconMarkdown} ${issueType} fix is ready`;
|
|
12958
|
-
const validFixParseRes =
|
|
13658
|
+
const validFixParseRes = z28.object({
|
|
12959
13659
|
patchAndQuestions: PatchAndQuestionsZ,
|
|
12960
|
-
safeIssueLanguage:
|
|
12961
|
-
severityText:
|
|
12962
|
-
safeIssueType:
|
|
13660
|
+
safeIssueLanguage: z28.nativeEnum(IssueLanguage_Enum),
|
|
13661
|
+
severityText: z28.nativeEnum(Vulnerability_Severity_Enum),
|
|
13662
|
+
safeIssueType: z28.nativeEnum(IssueType_Enum)
|
|
12963
13663
|
}).safeParse(fix);
|
|
12964
13664
|
if (!validFixParseRes.success) {
|
|
12965
|
-
|
|
13665
|
+
debug13(
|
|
12966
13666
|
`fix ${fixId} has custom issue type or language, therefore the commit description will not be added`,
|
|
12967
13667
|
validFixParseRes.error
|
|
12968
13668
|
);
|
|
@@ -13026,7 +13726,7 @@ ${issuePageLink}`;
|
|
|
13026
13726
|
}
|
|
13027
13727
|
|
|
13028
13728
|
// src/features/analysis/add_fix_comments_for_pr/utils/utils.ts
|
|
13029
|
-
var
|
|
13729
|
+
var debug14 = Debug13("mobbdev:handle-finished-analysis");
|
|
13030
13730
|
function calculateRanges(integers) {
|
|
13031
13731
|
if (integers.length === 0) {
|
|
13032
13732
|
return [];
|
|
@@ -13060,7 +13760,7 @@ function deleteAllPreviousComments({
|
|
|
13060
13760
|
try {
|
|
13061
13761
|
return scm.deleteComment({ comment_id: comment.id });
|
|
13062
13762
|
} catch (e) {
|
|
13063
|
-
|
|
13763
|
+
debug14("delete comment failed %s", e);
|
|
13064
13764
|
return Promise.resolve();
|
|
13065
13765
|
}
|
|
13066
13766
|
});
|
|
@@ -13076,7 +13776,7 @@ function deleteAllPreviousGeneralPrComments(params) {
|
|
|
13076
13776
|
try {
|
|
13077
13777
|
return scm.deleteGeneralPrComment({ commentId: comment.id });
|
|
13078
13778
|
} catch (e) {
|
|
13079
|
-
|
|
13779
|
+
debug14("delete comment failed %s", e);
|
|
13080
13780
|
return Promise.resolve();
|
|
13081
13781
|
}
|
|
13082
13782
|
});
|
|
@@ -13193,7 +13893,7 @@ async function getRelevantVulenrabilitiesFromDiff(params) {
|
|
|
13193
13893
|
});
|
|
13194
13894
|
const lineAddedRanges = calculateRanges(fileNumbers);
|
|
13195
13895
|
const fileFilter = {
|
|
13196
|
-
path:
|
|
13896
|
+
path: z29.string().parse(file.to),
|
|
13197
13897
|
ranges: lineAddedRanges.map(([startLine, endLine]) => ({
|
|
13198
13898
|
endLine,
|
|
13199
13899
|
startLine
|
|
@@ -13220,7 +13920,7 @@ async function postAnalysisInsightComment(params) {
|
|
|
13220
13920
|
fixablePrVuls,
|
|
13221
13921
|
nonFixablePrVuls
|
|
13222
13922
|
} = prVulenrabilities;
|
|
13223
|
-
|
|
13923
|
+
debug14({
|
|
13224
13924
|
fixablePrVuls,
|
|
13225
13925
|
nonFixablePrVuls,
|
|
13226
13926
|
vulnerabilitiesOutsidePr,
|
|
@@ -13275,7 +13975,7 @@ ${contactUsMarkdown}`;
|
|
|
13275
13975
|
}
|
|
13276
13976
|
|
|
13277
13977
|
// src/features/analysis/add_fix_comments_for_pr/add_fix_comments_for_pr.ts
|
|
13278
|
-
var
|
|
13978
|
+
var debug15 = Debug14("mobbdev:handle-finished-analysis");
|
|
13279
13979
|
async function addFixCommentsForPr({
|
|
13280
13980
|
analysisId,
|
|
13281
13981
|
scm: _scm,
|
|
@@ -13287,7 +13987,7 @@ async function addFixCommentsForPr({
|
|
|
13287
13987
|
}
|
|
13288
13988
|
const scm = _scm;
|
|
13289
13989
|
const getAnalysisRes = await gqlClient.getAnalysis(analysisId);
|
|
13290
|
-
|
|
13990
|
+
debug15("getAnalysis %o", getAnalysisRes);
|
|
13291
13991
|
const {
|
|
13292
13992
|
vulnerabilityReport: {
|
|
13293
13993
|
projectId,
|
|
@@ -13397,8 +14097,8 @@ ${contextString}` : description;
|
|
|
13397
14097
|
|
|
13398
14098
|
// src/features/analysis/auto_pr_handler.ts
|
|
13399
14099
|
init_client_generates();
|
|
13400
|
-
import
|
|
13401
|
-
var
|
|
14100
|
+
import Debug15 from "debug";
|
|
14101
|
+
var debug16 = Debug15("mobbdev:handleAutoPr");
|
|
13402
14102
|
async function handleAutoPr(params) {
|
|
13403
14103
|
const {
|
|
13404
14104
|
gqlClient,
|
|
@@ -13419,7 +14119,7 @@ async function handleAutoPr(params) {
|
|
|
13419
14119
|
prId,
|
|
13420
14120
|
prStrategy: createOnePr ? "CONDENSE" /* Condense */ : "SPREAD" /* Spread */
|
|
13421
14121
|
});
|
|
13422
|
-
|
|
14122
|
+
debug16("auto pr analysis res %o", autoPrAnalysisRes);
|
|
13423
14123
|
if (autoPrAnalysisRes.autoPrAnalysis?.__typename === "AutoPrError") {
|
|
13424
14124
|
createAutoPrSpinner.error({
|
|
13425
14125
|
text: `\u{1F504} Automatic pull request failed - ${autoPrAnalysisRes.autoPrAnalysis.error}`
|
|
@@ -13441,14 +14141,14 @@ async function handleAutoPr(params) {
|
|
|
13441
14141
|
};
|
|
13442
14142
|
const callbackStates = ["Finished" /* Finished */];
|
|
13443
14143
|
if (polling) {
|
|
13444
|
-
|
|
14144
|
+
debug16("[handleAutoPr] Using POLLING mode for analysis state updates");
|
|
13445
14145
|
return await gqlClient.pollForAnalysisState({
|
|
13446
14146
|
analysisId,
|
|
13447
14147
|
callback,
|
|
13448
14148
|
callbackStates
|
|
13449
14149
|
});
|
|
13450
14150
|
} else {
|
|
13451
|
-
|
|
14151
|
+
debug16("[handleAutoPr] Using WEBSOCKET mode for analysis state updates");
|
|
13452
14152
|
return await gqlClient.subscribeToAnalysis({
|
|
13453
14153
|
subscribeToAnalysisParams: {
|
|
13454
14154
|
analysisId
|
|
@@ -13461,15 +14161,15 @@ async function handleAutoPr(params) {
|
|
|
13461
14161
|
|
|
13462
14162
|
// src/features/analysis/git.ts
|
|
13463
14163
|
init_GitService();
|
|
13464
|
-
import
|
|
13465
|
-
var
|
|
14164
|
+
import Debug16 from "debug";
|
|
14165
|
+
var debug17 = Debug16("mobbdev:git");
|
|
13466
14166
|
async function getGitInfo(srcDirPath) {
|
|
13467
|
-
|
|
14167
|
+
debug17("getting git info for %s", srcDirPath);
|
|
13468
14168
|
const gitService = new GitService(srcDirPath);
|
|
13469
14169
|
try {
|
|
13470
14170
|
const validationResult = await gitService.validateRepository();
|
|
13471
14171
|
if (!validationResult.isValid) {
|
|
13472
|
-
|
|
14172
|
+
debug17("folder is not a git repo");
|
|
13473
14173
|
return {
|
|
13474
14174
|
success: false,
|
|
13475
14175
|
hash: void 0,
|
|
@@ -13484,9 +14184,9 @@ async function getGitInfo(srcDirPath) {
|
|
|
13484
14184
|
};
|
|
13485
14185
|
} catch (e) {
|
|
13486
14186
|
if (e instanceof Error) {
|
|
13487
|
-
|
|
14187
|
+
debug17("failed to run git %o", e);
|
|
13488
14188
|
if (e.message.includes(" spawn ")) {
|
|
13489
|
-
|
|
14189
|
+
debug17("git cli not installed");
|
|
13490
14190
|
} else {
|
|
13491
14191
|
throw e;
|
|
13492
14192
|
}
|
|
@@ -13498,22 +14198,22 @@ async function getGitInfo(srcDirPath) {
|
|
|
13498
14198
|
// src/features/analysis/pack.ts
|
|
13499
14199
|
init_configs();
|
|
13500
14200
|
import fs9 from "fs";
|
|
13501
|
-
import
|
|
14201
|
+
import path8 from "path";
|
|
13502
14202
|
import AdmZip from "adm-zip";
|
|
13503
|
-
import
|
|
14203
|
+
import Debug17 from "debug";
|
|
13504
14204
|
import { globby } from "globby";
|
|
13505
14205
|
import { isBinary as isBinary2 } from "istextorbinary";
|
|
13506
14206
|
import { simpleGit as simpleGit3 } from "simple-git";
|
|
13507
14207
|
import { parseStringPromise } from "xml2js";
|
|
13508
|
-
import { z as
|
|
13509
|
-
var
|
|
13510
|
-
var FPR_SOURCE_CODE_FILE_MAPPING_SCHEMA =
|
|
13511
|
-
properties:
|
|
13512
|
-
entry:
|
|
13513
|
-
|
|
13514
|
-
_:
|
|
13515
|
-
$:
|
|
13516
|
-
key:
|
|
14208
|
+
import { z as z30 } from "zod";
|
|
14209
|
+
var debug18 = Debug17("mobbdev:pack");
|
|
14210
|
+
var FPR_SOURCE_CODE_FILE_MAPPING_SCHEMA = z30.object({
|
|
14211
|
+
properties: z30.object({
|
|
14212
|
+
entry: z30.array(
|
|
14213
|
+
z30.object({
|
|
14214
|
+
_: z30.string(),
|
|
14215
|
+
$: z30.object({
|
|
14216
|
+
key: z30.string()
|
|
13517
14217
|
})
|
|
13518
14218
|
})
|
|
13519
14219
|
)
|
|
@@ -13528,7 +14228,7 @@ function getManifestFilesSuffixes() {
|
|
|
13528
14228
|
return ["package.json", "pom.xml"];
|
|
13529
14229
|
}
|
|
13530
14230
|
async function pack(srcDirPath, vulnFiles, isIncludeAllFiles = false) {
|
|
13531
|
-
|
|
14231
|
+
debug18("pack folder %s", srcDirPath);
|
|
13532
14232
|
let git = void 0;
|
|
13533
14233
|
try {
|
|
13534
14234
|
git = simpleGit3({
|
|
@@ -13538,13 +14238,13 @@ async function pack(srcDirPath, vulnFiles, isIncludeAllFiles = false) {
|
|
|
13538
14238
|
});
|
|
13539
14239
|
await git.status();
|
|
13540
14240
|
} catch (e) {
|
|
13541
|
-
|
|
14241
|
+
debug18("failed to run git %o", e);
|
|
13542
14242
|
git = void 0;
|
|
13543
14243
|
if (e instanceof Error) {
|
|
13544
14244
|
if (e.message.includes(" spawn ")) {
|
|
13545
|
-
|
|
14245
|
+
debug18("git cli not installed");
|
|
13546
14246
|
} else if (e.message.includes("not a git repository")) {
|
|
13547
|
-
|
|
14247
|
+
debug18("folder is not a git repo");
|
|
13548
14248
|
} else {
|
|
13549
14249
|
throw e;
|
|
13550
14250
|
}
|
|
@@ -13559,23 +14259,23 @@ async function pack(srcDirPath, vulnFiles, isIncludeAllFiles = false) {
|
|
|
13559
14259
|
followSymbolicLinks: false,
|
|
13560
14260
|
dot: true
|
|
13561
14261
|
});
|
|
13562
|
-
|
|
14262
|
+
debug18("files found %d", filepaths.length);
|
|
13563
14263
|
const zip = new AdmZip();
|
|
13564
|
-
|
|
14264
|
+
debug18("compressing files");
|
|
13565
14265
|
for (const filepath of filepaths) {
|
|
13566
|
-
const absFilepath =
|
|
14266
|
+
const absFilepath = path8.join(srcDirPath, filepath.toString());
|
|
13567
14267
|
if (!isIncludeAllFiles) {
|
|
13568
14268
|
vulnFiles = vulnFiles.concat(getManifestFilesSuffixes());
|
|
13569
14269
|
if (!endsWithAny(
|
|
13570
|
-
absFilepath.toString().replaceAll(
|
|
14270
|
+
absFilepath.toString().replaceAll(path8.win32.sep, path8.posix.sep),
|
|
13571
14271
|
vulnFiles
|
|
13572
14272
|
)) {
|
|
13573
|
-
|
|
14273
|
+
debug18("ignoring %s because it is not a vulnerability file", filepath);
|
|
13574
14274
|
continue;
|
|
13575
14275
|
}
|
|
13576
14276
|
}
|
|
13577
14277
|
if (fs9.lstatSync(absFilepath).size > MCP_MAX_FILE_SIZE) {
|
|
13578
|
-
|
|
14278
|
+
debug18("ignoring %s because the size is > 5MB", filepath);
|
|
13579
14279
|
continue;
|
|
13580
14280
|
}
|
|
13581
14281
|
let data;
|
|
@@ -13589,16 +14289,16 @@ async function pack(srcDirPath, vulnFiles, isIncludeAllFiles = false) {
|
|
|
13589
14289
|
data = fs9.readFileSync(absFilepath);
|
|
13590
14290
|
}
|
|
13591
14291
|
if (isBinary2(null, data)) {
|
|
13592
|
-
|
|
14292
|
+
debug18("ignoring %s because is seems to be a binary file", filepath);
|
|
13593
14293
|
continue;
|
|
13594
14294
|
}
|
|
13595
14295
|
zip.addFile(filepath.toString(), data);
|
|
13596
14296
|
}
|
|
13597
|
-
|
|
14297
|
+
debug18("get zip file buffer");
|
|
13598
14298
|
return zip.toBuffer();
|
|
13599
14299
|
}
|
|
13600
14300
|
async function repackFpr(fprPath) {
|
|
13601
|
-
|
|
14301
|
+
debug18("repack fpr file %s", fprPath);
|
|
13602
14302
|
const zipIn = new AdmZip(fprPath);
|
|
13603
14303
|
const zipOut = new AdmZip();
|
|
13604
14304
|
const mappingXML = zipIn.readAsText("src-archive/index.xml", "utf-8");
|
|
@@ -13613,7 +14313,7 @@ async function repackFpr(fprPath) {
|
|
|
13613
14313
|
zipOut.addFile(realPath, buf);
|
|
13614
14314
|
}
|
|
13615
14315
|
}
|
|
13616
|
-
|
|
14316
|
+
debug18("get repacked zip file buffer");
|
|
13617
14317
|
return zipOut.toBuffer();
|
|
13618
14318
|
}
|
|
13619
14319
|
|
|
@@ -13683,12 +14383,12 @@ async function snykArticlePrompt() {
|
|
|
13683
14383
|
|
|
13684
14384
|
// src/features/analysis/scanners/checkmarx.ts
|
|
13685
14385
|
import { createRequire } from "module";
|
|
13686
|
-
import
|
|
13687
|
-
import
|
|
14386
|
+
import chalk5 from "chalk";
|
|
14387
|
+
import Debug19 from "debug";
|
|
13688
14388
|
import { existsSync } from "fs";
|
|
13689
14389
|
import { createSpinner as createSpinner2 } from "nanospinner";
|
|
13690
14390
|
import { type } from "os";
|
|
13691
|
-
import
|
|
14391
|
+
import path9 from "path";
|
|
13692
14392
|
|
|
13693
14393
|
// src/post_install/constants.mjs
|
|
13694
14394
|
var cxOperatingSystemSupportMessage = `Your operating system does not support checkmarx.
|
|
@@ -13696,7 +14396,7 @@ var cxOperatingSystemSupportMessage = `Your operating system does not support ch
|
|
|
13696
14396
|
|
|
13697
14397
|
// src/utils/child_process.ts
|
|
13698
14398
|
import cp from "child_process";
|
|
13699
|
-
import
|
|
14399
|
+
import Debug18 from "debug";
|
|
13700
14400
|
import * as process2 from "process";
|
|
13701
14401
|
function createFork({ args, processPath, name }, options) {
|
|
13702
14402
|
const child = cp.fork(processPath, args, {
|
|
@@ -13714,16 +14414,16 @@ function createSpawn({ args, processPath, name, cwd }, options) {
|
|
|
13714
14414
|
return createChildProcess({ childProcess: child, name }, options);
|
|
13715
14415
|
}
|
|
13716
14416
|
function createChildProcess({ childProcess, name }, options) {
|
|
13717
|
-
const
|
|
14417
|
+
const debug23 = Debug18(`mobbdev:${name}`);
|
|
13718
14418
|
const { display } = options;
|
|
13719
14419
|
return new Promise((resolve, reject) => {
|
|
13720
14420
|
let out = "";
|
|
13721
14421
|
const onData = (chunk) => {
|
|
13722
|
-
|
|
14422
|
+
debug23(`chunk received from ${name} std ${chunk}`);
|
|
13723
14423
|
out += chunk;
|
|
13724
14424
|
};
|
|
13725
14425
|
if (!childProcess?.stdout || !childProcess?.stderr) {
|
|
13726
|
-
|
|
14426
|
+
debug23(`unable to fork ${name}`);
|
|
13727
14427
|
reject(new Error(`unable to fork ${name}`));
|
|
13728
14428
|
}
|
|
13729
14429
|
childProcess.stdout?.on("data", onData);
|
|
@@ -13733,18 +14433,18 @@ function createChildProcess({ childProcess, name }, options) {
|
|
|
13733
14433
|
childProcess.stderr?.pipe(process2.stderr);
|
|
13734
14434
|
}
|
|
13735
14435
|
childProcess.on("exit", (code) => {
|
|
13736
|
-
|
|
14436
|
+
debug23(`${name} exit code ${code}`);
|
|
13737
14437
|
resolve({ message: out, code });
|
|
13738
14438
|
});
|
|
13739
14439
|
childProcess.on("error", (err) => {
|
|
13740
|
-
|
|
14440
|
+
debug23(`${name} error %o`, err);
|
|
13741
14441
|
reject(err);
|
|
13742
14442
|
});
|
|
13743
14443
|
});
|
|
13744
14444
|
}
|
|
13745
14445
|
|
|
13746
14446
|
// src/features/analysis/scanners/checkmarx.ts
|
|
13747
|
-
var
|
|
14447
|
+
var debug19 = Debug19("mobbdev:checkmarx");
|
|
13748
14448
|
var moduleUrl;
|
|
13749
14449
|
if (typeof __filename !== "undefined") {
|
|
13750
14450
|
moduleUrl = __filename;
|
|
@@ -13803,1900 +14503,1283 @@ function validateCheckmarxInstallation() {
|
|
|
13803
14503
|
existsSync(getCheckmarxPath());
|
|
13804
14504
|
}
|
|
13805
14505
|
async function forkCheckmarx(args, { display }) {
|
|
13806
|
-
|
|
14506
|
+
debug19("fork checkmarx with args %o %s", args.join(" "), display);
|
|
13807
14507
|
return createSpawn(
|
|
13808
14508
|
{ args, processPath: getCheckmarxPath(), name: "checkmarx" },
|
|
13809
14509
|
{ display }
|
|
13810
14510
|
);
|
|
13811
14511
|
}
|
|
13812
14512
|
async function getCheckmarxReport({ reportPath, repositoryRoot, branch, projectName }, { skipPrompts = false }) {
|
|
13813
|
-
|
|
14513
|
+
debug19("get checkmarx report start %s %s", reportPath, repositoryRoot);
|
|
13814
14514
|
const { code: loginCode } = await forkCheckmarx(VALIDATE_COMMAND, {
|
|
13815
|
-
display: false
|
|
13816
|
-
});
|
|
13817
|
-
if (loginCode !== CHECKMARX_SUCCESS_CODE) {
|
|
13818
|
-
if (skipPrompts) {
|
|
13819
|
-
await throwCheckmarxConfigError();
|
|
13820
|
-
}
|
|
13821
|
-
await startCheckmarxConfigationPrompt();
|
|
13822
|
-
await validateCheckamxCredentials();
|
|
13823
|
-
}
|
|
13824
|
-
const extension = path8.extname(reportPath);
|
|
13825
|
-
const filePath = path8.dirname(reportPath);
|
|
13826
|
-
const fileName = path8.basename(reportPath, extension);
|
|
13827
|
-
const checkmarxCommandArgs = getCheckmarxCommandArgs({
|
|
13828
|
-
repoPath: repositoryRoot,
|
|
13829
|
-
branch,
|
|
13830
|
-
filePath,
|
|
13831
|
-
fileName,
|
|
13832
|
-
projectName
|
|
13833
|
-
});
|
|
13834
|
-
console.log("\u280B \u{1F50D} Initiating Checkmarx Scan ");
|
|
13835
|
-
const { code: scanCode } = await forkCheckmarx(
|
|
13836
|
-
[...SCAN_COMMAND, ...checkmarxCommandArgs],
|
|
13837
|
-
{
|
|
13838
|
-
display: true
|
|
13839
|
-
}
|
|
13840
|
-
);
|
|
13841
|
-
if (scanCode !== CHECKMARX_SUCCESS_CODE) {
|
|
13842
|
-
createSpinner2("\u{1F50D} Something went wrong with the checkmarx scan").start().error();
|
|
13843
|
-
throw new CliError();
|
|
13844
|
-
}
|
|
13845
|
-
await createSpinner2("\u{1F50D} Checkmarx Scan completed").start().success();
|
|
13846
|
-
return true;
|
|
13847
|
-
}
|
|
13848
|
-
async function throwCheckmarxConfigError() {
|
|
13849
|
-
await createSpinner2("\u{1F513} Checkmarx is not configued correctly").start().error();
|
|
13850
|
-
throw new CliError(
|
|
13851
|
-
`Checkmarx is not configued correctly
|
|
13852
|
-
you can configure it by using the ${chalk4.bold(
|
|
13853
|
-
"cx configure"
|
|
13854
|
-
)} command`
|
|
13855
|
-
);
|
|
13856
|
-
}
|
|
13857
|
-
async function validateCheckamxCredentials() {
|
|
13858
|
-
console.log(`
|
|
13859
|
-
Here's a suggestion for checkmarx configuation:
|
|
13860
|
-
${chalk4.bold("AST Base URI:")} https://ast.checkmarx.net
|
|
13861
|
-
${chalk4.bold("AST Base Auth URI (IAM):")} https://iam.checkmarx.net
|
|
13862
|
-
`);
|
|
13863
|
-
await forkCheckmarx(CONFIGURE_COMMAND, { display: true });
|
|
13864
|
-
const { code: loginCode } = await forkCheckmarx(VALIDATE_COMMAND, {
|
|
13865
|
-
display: false
|
|
13866
|
-
});
|
|
13867
|
-
if (loginCode !== CHECKMARX_SUCCESS_CODE) {
|
|
13868
|
-
const tryAgain = await tryCheckmarxConfiguarationAgain();
|
|
13869
|
-
if (!tryAgain) {
|
|
13870
|
-
await throwCheckmarxConfigError();
|
|
13871
|
-
}
|
|
13872
|
-
await validateCheckamxCredentials();
|
|
13873
|
-
return;
|
|
13874
|
-
}
|
|
13875
|
-
await createSpinner2("\u{1F513} Checkmarx configured successfully!").start().success();
|
|
13876
|
-
}
|
|
13877
|
-
|
|
13878
|
-
// src/features/analysis/scanners/snyk.ts
|
|
13879
|
-
import { createRequire as createRequire2 } from "module";
|
|
13880
|
-
import chalk5 from "chalk";
|
|
13881
|
-
import Debug17 from "debug";
|
|
13882
|
-
import { createSpinner as createSpinner3 } from "nanospinner";
|
|
13883
|
-
import open2 from "open";
|
|
13884
|
-
var debug17 = Debug17("mobbdev:snyk");
|
|
13885
|
-
var moduleUrl2;
|
|
13886
|
-
if (typeof __filename !== "undefined") {
|
|
13887
|
-
moduleUrl2 = __filename;
|
|
13888
|
-
} else {
|
|
13889
|
-
try {
|
|
13890
|
-
const getImportMetaUrl = new Function("return import.meta.url");
|
|
13891
|
-
moduleUrl2 = getImportMetaUrl();
|
|
13892
|
-
} catch {
|
|
13893
|
-
const err = new Error();
|
|
13894
|
-
const stack = err.stack || "";
|
|
13895
|
-
const match = stack.match(/file:\/\/[^\s)]+/);
|
|
13896
|
-
if (match) {
|
|
13897
|
-
moduleUrl2 = match[0];
|
|
13898
|
-
} else {
|
|
13899
|
-
throw new Error("Unable to determine module URL in this environment");
|
|
13900
|
-
}
|
|
13901
|
-
}
|
|
13902
|
-
}
|
|
13903
|
-
var costumeRequire2 = createRequire2(moduleUrl2);
|
|
13904
|
-
var SNYK_PATH = costumeRequire2.resolve("snyk/bin/snyk");
|
|
13905
|
-
var SNYK_ARTICLE_URL = "https://docs.snyk.io/scan-using-snyk/snyk-code/configure-snyk-code#enable-snyk-code";
|
|
13906
|
-
debug17("snyk executable path %s", SNYK_PATH);
|
|
13907
|
-
async function forkSnyk(args, { display }) {
|
|
13908
|
-
debug17("fork snyk with args %o %s", args, display);
|
|
13909
|
-
return createFork({ args, processPath: SNYK_PATH, name: "snyk" }, { display });
|
|
13910
|
-
}
|
|
13911
|
-
async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
|
|
13912
|
-
debug17("get snyk report start %s %s", reportPath, repoRoot);
|
|
13913
|
-
const config2 = await forkSnyk(["config"], { display: false });
|
|
13914
|
-
const { message: configMessage } = config2;
|
|
13915
|
-
if (!configMessage.includes("api: ")) {
|
|
13916
|
-
const snykLoginSpinner = createSpinner3().start();
|
|
13917
|
-
if (!skipPrompts) {
|
|
13918
|
-
snykLoginSpinner.update({
|
|
13919
|
-
text: "\u{1F513} Login to Snyk is required, press any key to continue"
|
|
13920
|
-
});
|
|
13921
|
-
await keypress();
|
|
13922
|
-
}
|
|
13923
|
-
snykLoginSpinner.update({
|
|
13924
|
-
text: "\u{1F513} Waiting for Snyk login to complete"
|
|
13925
|
-
});
|
|
13926
|
-
debug17("no token in the config %s", config2);
|
|
13927
|
-
await forkSnyk(["auth"], { display: true });
|
|
13928
|
-
snykLoginSpinner.success({ text: "\u{1F513} Login to Snyk Successful" });
|
|
13929
|
-
}
|
|
13930
|
-
const snykSpinner = createSpinner3("\u{1F50D} Scanning your repo with Snyk ").start();
|
|
13931
|
-
const { message: scanOutput } = await forkSnyk(
|
|
13932
|
-
["code", "test", `--sarif-file-output=${reportPath}`, repoRoot],
|
|
13933
|
-
{ display: true }
|
|
13934
|
-
);
|
|
13935
|
-
if (scanOutput.includes("Snyk Code is not supported for org")) {
|
|
13936
|
-
debug17("snyk code is not enabled %s", scanOutput);
|
|
13937
|
-
snykSpinner.error({ text: "\u{1F50D} Snyk configuration needed" });
|
|
13938
|
-
const answer = await snykArticlePrompt();
|
|
13939
|
-
debug17("answer %s", answer);
|
|
13940
|
-
if (answer) {
|
|
13941
|
-
debug17("opening the browser");
|
|
13942
|
-
await open2(SNYK_ARTICLE_URL);
|
|
13943
|
-
}
|
|
13944
|
-
console.log(
|
|
13945
|
-
chalk5.bgBlue(
|
|
13946
|
-
"\nPlease enable Snyk Code in your Snyk account and try again."
|
|
13947
|
-
)
|
|
13948
|
-
);
|
|
13949
|
-
throw Error("snyk is not enbabled");
|
|
13950
|
-
}
|
|
13951
|
-
snykSpinner.success({ text: "\u{1F50D} Snyk code scan completed" });
|
|
13952
|
-
return true;
|
|
13953
|
-
}
|
|
13954
|
-
|
|
13955
|
-
// src/features/analysis/index.ts
|
|
13956
|
-
init_client_generates();
|
|
13957
|
-
|
|
13958
|
-
// src/features/analysis/upload-file.ts
|
|
13959
|
-
import Debug18 from "debug";
|
|
13960
|
-
import fetch3, { File, fileFrom, FormData } from "node-fetch";
|
|
13961
|
-
var debug18 = Debug18("mobbdev:upload-file");
|
|
13962
|
-
async function uploadFile({
|
|
13963
|
-
file,
|
|
13964
|
-
url,
|
|
13965
|
-
uploadKey,
|
|
13966
|
-
uploadFields,
|
|
13967
|
-
logger: logger2
|
|
13968
|
-
}) {
|
|
13969
|
-
const logInfo2 = logger2 || ((_message, _data) => {
|
|
13970
|
-
});
|
|
13971
|
-
logInfo2(`FileUpload: upload file start ${url}`);
|
|
13972
|
-
logInfo2(`FileUpload: upload fields`, uploadFields);
|
|
13973
|
-
logInfo2(`FileUpload: upload key ${uploadKey}`);
|
|
13974
|
-
debug18("upload file start %s", url);
|
|
13975
|
-
debug18("upload fields %o", uploadFields);
|
|
13976
|
-
debug18("upload key %s", uploadKey);
|
|
13977
|
-
const form = new FormData();
|
|
13978
|
-
Object.entries(uploadFields).forEach(([key, value]) => {
|
|
13979
|
-
form.append(key, value);
|
|
13980
|
-
});
|
|
13981
|
-
if (!form.has("key")) {
|
|
13982
|
-
form.append("key", uploadKey);
|
|
13983
|
-
}
|
|
13984
|
-
if (typeof file === "string") {
|
|
13985
|
-
debug18("upload file from path %s", file);
|
|
13986
|
-
logInfo2(`FileUpload: upload file from path ${file}`);
|
|
13987
|
-
form.append("file", await fileFrom(file));
|
|
13988
|
-
} else {
|
|
13989
|
-
debug18("upload file from buffer");
|
|
13990
|
-
logInfo2(`FileUpload: upload file from buffer`);
|
|
13991
|
-
form.append("file", new File([new Uint8Array(file)], "file"));
|
|
13992
|
-
}
|
|
13993
|
-
const agent = getProxyAgent(url);
|
|
13994
|
-
const response = await fetch3(url, {
|
|
13995
|
-
method: "POST",
|
|
13996
|
-
body: form,
|
|
13997
|
-
agent
|
|
13998
|
-
});
|
|
13999
|
-
if (!response.ok) {
|
|
14000
|
-
debug18("error from S3 %s %s", response.body, response.status);
|
|
14001
|
-
logInfo2(`FileUpload: error from S3 ${response.body} ${response.status}`);
|
|
14002
|
-
throw new Error(`Failed to upload the file: ${response.status}`);
|
|
14003
|
-
}
|
|
14004
|
-
debug18("upload file done");
|
|
14005
|
-
logInfo2(`FileUpload: upload file done`);
|
|
14006
|
-
}
|
|
14007
|
-
|
|
14008
|
-
// src/features/analysis/index.ts
|
|
14009
|
-
var { CliError: CliError2, Spinner: Spinner2 } = utils_exports;
|
|
14010
|
-
function _getScanSource(command, ci) {
|
|
14011
|
-
if (command === "review") return "AUTO_FIXER" /* AutoFixer */;
|
|
14012
|
-
const envToCi = [
|
|
14013
|
-
["GITLAB_CI", "CI_GITLAB" /* CiGitlab */],
|
|
14014
|
-
["GITHUB_ACTIONS", "CI_GITHUB" /* CiGithub */],
|
|
14015
|
-
["JENKINS_URL", "CI_JENKINS" /* CiJenkins */],
|
|
14016
|
-
["CIRCLECI", "CI_CIRCLECI" /* CiCircleci */],
|
|
14017
|
-
["TF_BUILD", "CI_AZURE" /* CiAzure */],
|
|
14018
|
-
["bamboo_buildKey", "CI_BAMBOO" /* CiBamboo */]
|
|
14019
|
-
];
|
|
14020
|
-
for (const [envKey, source] of envToCi) {
|
|
14021
|
-
if (env2[envKey]) {
|
|
14022
|
-
return source;
|
|
14023
|
-
}
|
|
14024
|
-
}
|
|
14025
|
-
if (ci) {
|
|
14026
|
-
return "CI_UNKNOWN" /* CiUnknown */;
|
|
14027
|
-
}
|
|
14028
|
-
return "CLI" /* Cli */;
|
|
14029
|
-
}
|
|
14030
|
-
async function downloadRepo({
|
|
14031
|
-
repoUrl,
|
|
14032
|
-
authHeaders,
|
|
14033
|
-
downloadUrl,
|
|
14034
|
-
dirname,
|
|
14035
|
-
ci
|
|
14036
|
-
}) {
|
|
14037
|
-
const { createSpinner: createSpinner5 } = Spinner2({ ci });
|
|
14038
|
-
const repoSpinner = createSpinner5("\u{1F4BE} Downloading Repo").start();
|
|
14039
|
-
debug19("download repo %s %s %s", repoUrl, dirname);
|
|
14040
|
-
const zipFilePath = path9.join(dirname, "repo.zip");
|
|
14041
|
-
debug19("download URL: %s auth headers: %o", downloadUrl, authHeaders);
|
|
14042
|
-
const response = await fetch4(downloadUrl, {
|
|
14043
|
-
method: "GET",
|
|
14044
|
-
headers: {
|
|
14045
|
-
...authHeaders
|
|
14046
|
-
}
|
|
14047
|
-
});
|
|
14048
|
-
if (!response.ok) {
|
|
14049
|
-
debug19("SCM zipball request failed %s %s", response.body, response.status);
|
|
14050
|
-
repoSpinner.error({ text: "\u{1F4BE} Repo download failed" });
|
|
14051
|
-
throw new Error(`Can't access ${chalk6.bold(repoUrl)}`);
|
|
14052
|
-
}
|
|
14053
|
-
const fileWriterStream = fs10.createWriteStream(zipFilePath);
|
|
14054
|
-
if (!response.body) {
|
|
14055
|
-
throw new Error("Response body is empty");
|
|
14056
|
-
}
|
|
14057
|
-
await pipeline(response.body, fileWriterStream);
|
|
14058
|
-
await extract(zipFilePath, { dir: dirname });
|
|
14059
|
-
const repoRoot = fs10.readdirSync(dirname, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name)[0];
|
|
14060
|
-
if (!repoRoot) {
|
|
14061
|
-
throw new Error("Repo root not found");
|
|
14062
|
-
}
|
|
14063
|
-
debug19("repo root %s", repoRoot);
|
|
14064
|
-
repoSpinner.success({ text: "\u{1F4BE} Repo downloaded successfully" });
|
|
14065
|
-
return path9.join(dirname, repoRoot);
|
|
14066
|
-
}
|
|
14067
|
-
var getReportUrl = ({
|
|
14068
|
-
organizationId,
|
|
14069
|
-
projectId,
|
|
14070
|
-
fixReportId
|
|
14071
|
-
}) => `${WEB_APP_URL}/organization/${organizationId}/project/${projectId}/report/${fixReportId}`;
|
|
14072
|
-
var debug19 = Debug19("mobbdev:index");
|
|
14073
|
-
async function runAnalysis(params, options) {
|
|
14074
|
-
const tmpObj = tmp2.dirSync({
|
|
14075
|
-
unsafeCleanup: true
|
|
14076
|
-
});
|
|
14077
|
-
try {
|
|
14078
|
-
return await _scan(
|
|
14079
|
-
{
|
|
14080
|
-
...params,
|
|
14081
|
-
dirname: tmpObj.name
|
|
14082
|
-
},
|
|
14083
|
-
options
|
|
14084
|
-
);
|
|
14085
|
-
} finally {
|
|
14086
|
-
tmpObj.removeCallback();
|
|
14087
|
-
}
|
|
14088
|
-
}
|
|
14089
|
-
function _getUrlForScmType({
|
|
14090
|
-
scmLibType
|
|
14091
|
-
}) {
|
|
14092
|
-
const githubAuthUrl = `${WEB_APP_URL}/github-auth`;
|
|
14093
|
-
const gitlabAuthUrl = `${WEB_APP_URL}/gitlab-auth`;
|
|
14094
|
-
const adoAuthUrl = `${WEB_APP_URL}/ado-auth`;
|
|
14095
|
-
switch (scmLibType) {
|
|
14096
|
-
case "GITHUB" /* GITHUB */:
|
|
14097
|
-
return {
|
|
14098
|
-
authUrl: githubAuthUrl
|
|
14099
|
-
};
|
|
14100
|
-
case "GITLAB" /* GITLAB */:
|
|
14101
|
-
return {
|
|
14102
|
-
authUrl: gitlabAuthUrl
|
|
14103
|
-
};
|
|
14104
|
-
case "ADO" /* ADO */:
|
|
14105
|
-
return {
|
|
14106
|
-
authUrl: adoAuthUrl
|
|
14107
|
-
};
|
|
14108
|
-
default:
|
|
14109
|
-
return {
|
|
14110
|
-
authUrl: void 0
|
|
14111
|
-
};
|
|
14112
|
-
}
|
|
14113
|
-
}
|
|
14114
|
-
function getBrokerHosts(userOrgsAnUserOrgRoles) {
|
|
14115
|
-
const brokerHosts = [];
|
|
14116
|
-
if (!userOrgsAnUserOrgRoles) {
|
|
14117
|
-
return brokerHosts;
|
|
14118
|
-
}
|
|
14119
|
-
userOrgsAnUserOrgRoles.forEach((org) => {
|
|
14120
|
-
org?.organization?.brokerHosts.forEach((brokerHost) => {
|
|
14121
|
-
if (brokerHost) {
|
|
14122
|
-
brokerHosts.push(brokerHost);
|
|
14123
|
-
}
|
|
14124
|
-
});
|
|
14125
|
-
});
|
|
14126
|
-
return brokerHosts;
|
|
14127
|
-
}
|
|
14128
|
-
async function getScmTokenInfo(params) {
|
|
14129
|
-
const { gqlClient, repo } = params;
|
|
14130
|
-
const userInfo2 = await gqlClient.getUserInfo();
|
|
14131
|
-
if (!userInfo2) {
|
|
14132
|
-
throw new Error("userInfo is null");
|
|
14133
|
-
}
|
|
14134
|
-
const scmConfigs = getFromArraySafe(userInfo2.scmConfigs);
|
|
14135
|
-
return getScmConfig({
|
|
14136
|
-
url: repo,
|
|
14137
|
-
scmConfigs,
|
|
14138
|
-
includeOrgTokens: false,
|
|
14139
|
-
brokerHosts: getBrokerHosts(
|
|
14140
|
-
userInfo2.userOrganizationsAndUserOrganizationRoles
|
|
14141
|
-
)
|
|
14142
|
-
});
|
|
14143
|
-
}
|
|
14144
|
-
async function getReport(params, { skipPrompts }) {
|
|
14145
|
-
const {
|
|
14146
|
-
scanner,
|
|
14147
|
-
repoUrl,
|
|
14148
|
-
gqlClient,
|
|
14149
|
-
sha,
|
|
14150
|
-
dirname,
|
|
14151
|
-
reference,
|
|
14152
|
-
cxProjectName,
|
|
14153
|
-
ci
|
|
14154
|
-
} = params;
|
|
14155
|
-
const tokenInfo = await getScmTokenInfo({ gqlClient, repo: repoUrl });
|
|
14156
|
-
const scm = await createScmLib(
|
|
14157
|
-
{
|
|
14158
|
-
url: repoUrl,
|
|
14159
|
-
accessToken: tokenInfo.accessToken,
|
|
14160
|
-
scmOrg: tokenInfo.scmOrg,
|
|
14161
|
-
scmType: tokenInfo.scmLibType
|
|
14162
|
-
},
|
|
14163
|
-
{ propagateExceptions: true }
|
|
14164
|
-
);
|
|
14165
|
-
const downloadUrl = await scm.getDownloadUrl(sha);
|
|
14166
|
-
const repositoryRoot = await downloadRepo({
|
|
14167
|
-
repoUrl,
|
|
14168
|
-
dirname,
|
|
14169
|
-
ci,
|
|
14170
|
-
authHeaders: scm.getAuthHeaders(),
|
|
14171
|
-
downloadUrl
|
|
14172
|
-
});
|
|
14173
|
-
const reportPath = path9.join(dirname, REPORT_DEFAULT_FILE_NAME);
|
|
14174
|
-
switch (scanner) {
|
|
14175
|
-
case "snyk":
|
|
14176
|
-
await getSnykReport(reportPath, repositoryRoot, { skipPrompts });
|
|
14177
|
-
break;
|
|
14178
|
-
case "checkmarx":
|
|
14179
|
-
if (!cxProjectName) {
|
|
14180
|
-
throw new Error("cxProjectName is required for checkmarx scanner");
|
|
14181
|
-
}
|
|
14182
|
-
await getCheckmarxReport(
|
|
14183
|
-
{
|
|
14184
|
-
reportPath,
|
|
14185
|
-
repositoryRoot,
|
|
14186
|
-
branch: reference,
|
|
14187
|
-
projectName: cxProjectName
|
|
14188
|
-
},
|
|
14189
|
-
{ skipPrompts }
|
|
14190
|
-
);
|
|
14191
|
-
break;
|
|
14192
|
-
}
|
|
14193
|
-
return reportPath;
|
|
14194
|
-
}
|
|
14195
|
-
async function _scan(params, { skipPrompts = false } = {}) {
|
|
14196
|
-
const {
|
|
14197
|
-
dirname,
|
|
14198
|
-
repo,
|
|
14199
|
-
scanFile,
|
|
14200
|
-
apiKey,
|
|
14201
|
-
ci,
|
|
14202
|
-
srcPath,
|
|
14203
|
-
commitHash,
|
|
14204
|
-
ref,
|
|
14205
|
-
experimentalEnabled,
|
|
14206
|
-
scanner,
|
|
14207
|
-
cxProjectName,
|
|
14208
|
-
mobbProjectName,
|
|
14209
|
-
githubToken: githubActionToken,
|
|
14210
|
-
command,
|
|
14211
|
-
organizationId: userOrganizationId,
|
|
14212
|
-
autoPr,
|
|
14213
|
-
createOnePr,
|
|
14214
|
-
commitDirectly,
|
|
14215
|
-
pullRequest,
|
|
14216
|
-
polling
|
|
14217
|
-
} = params;
|
|
14218
|
-
debug19("start %s %s", dirname, repo);
|
|
14219
|
-
const { createSpinner: createSpinner5 } = Spinner2({ ci });
|
|
14220
|
-
skipPrompts = skipPrompts || ci;
|
|
14221
|
-
const gqlClient = await getAuthenticatedGQLClient({
|
|
14222
|
-
inputApiKey: apiKey,
|
|
14223
|
-
isSkipPrompts: skipPrompts
|
|
14224
|
-
});
|
|
14225
|
-
if (!mobbProjectName) {
|
|
14226
|
-
throw new Error("mobbProjectName is required");
|
|
14227
|
-
}
|
|
14228
|
-
const { projectId, organizationId } = await gqlClient.getLastOrgAndNamedProject({
|
|
14229
|
-
projectName: mobbProjectName,
|
|
14230
|
-
userDefinedOrganizationId: userOrganizationId
|
|
14231
|
-
});
|
|
14232
|
-
const {
|
|
14233
|
-
uploadS3BucketInfo: { repoUploadInfo, reportUploadInfo }
|
|
14234
|
-
} = await gqlClient.uploadS3BucketInfo();
|
|
14235
|
-
if (!reportUploadInfo || !repoUploadInfo) {
|
|
14236
|
-
throw new Error("uploadS3BucketInfo is null");
|
|
14237
|
-
}
|
|
14238
|
-
let reportPath = scanFile;
|
|
14239
|
-
if (srcPath) {
|
|
14240
|
-
return await uploadExistingRepo();
|
|
14241
|
-
}
|
|
14242
|
-
if (!repo) {
|
|
14243
|
-
throw new Error("repo is required in case srcPath is not provided");
|
|
14244
|
-
}
|
|
14245
|
-
const tokenInfo = await getScmTokenInfo({ gqlClient, repo });
|
|
14246
|
-
const validateRes = await gqlClient.validateRepoUrl({ repoUrl: repo });
|
|
14247
|
-
const isRepoAvailable = validateRes.validateRepoUrl?.__typename === "RepoValidationSuccess";
|
|
14248
|
-
const cloudScmLibType = getCloudScmLibTypeFromUrl(repo);
|
|
14249
|
-
const { authUrl: scmAuthUrl } = _getUrlForScmType({
|
|
14250
|
-
scmLibType: cloudScmLibType
|
|
14251
|
-
});
|
|
14252
|
-
if (!isRepoAvailable) {
|
|
14253
|
-
if (ci || !cloudScmLibType || !scmAuthUrl) {
|
|
14254
|
-
const errorMessage = scmAuthUrl ? `Cannot access repo ${repo}. Make sure that the repo is accessible and the SCM token configured on Mobb is correct.` : `Cannot access repo ${repo} with the provided token, please visit ${scmAuthUrl} to refresh your source control management system token`;
|
|
14255
|
-
throw new Error(errorMessage);
|
|
14256
|
-
}
|
|
14257
|
-
if (cloudScmLibType && scmAuthUrl) {
|
|
14258
|
-
await handleScmIntegration(tokenInfo.accessToken, scmAuthUrl, repo);
|
|
14259
|
-
const repoValidationResponse = await gqlClient.validateRepoUrl({
|
|
14260
|
-
repoUrl: repo
|
|
14261
|
-
});
|
|
14262
|
-
const isRepoAvailable2 = repoValidationResponse.validateRepoUrl?.__typename === "RepoValidationSuccess";
|
|
14263
|
-
if (!isRepoAvailable2) {
|
|
14264
|
-
throw new Error(
|
|
14265
|
-
`Cannot access repo ${repo} with the provided credentials: ${repoValidationResponse.validateRepoUrl?.__typename}`
|
|
14266
|
-
);
|
|
14267
|
-
}
|
|
14268
|
-
}
|
|
14269
|
-
}
|
|
14270
|
-
const revalidateRes = await gqlClient.validateRepoUrl({ repoUrl: repo });
|
|
14271
|
-
if (revalidateRes.validateRepoUrl?.__typename !== "RepoValidationSuccess") {
|
|
14272
|
-
throw new Error(
|
|
14273
|
-
`could not reach repo ${repo}: ${revalidateRes.validateRepoUrl?.__typename}`
|
|
14274
|
-
);
|
|
14275
|
-
}
|
|
14276
|
-
const reference = ref ?? revalidateRes.validateRepoUrl.defaultBranch;
|
|
14277
|
-
const getReferenceDataRes = await gqlClient.getReferenceData({
|
|
14278
|
-
reference,
|
|
14279
|
-
repoUrl: repo
|
|
14280
|
-
});
|
|
14281
|
-
if (getReferenceDataRes.gitReference?.__typename !== "GitReferenceData") {
|
|
14282
|
-
throw new Error(
|
|
14283
|
-
`Could not get reference data for ${reference}: ${getReferenceDataRes.gitReference?.__typename}`
|
|
14284
|
-
);
|
|
14285
|
-
}
|
|
14286
|
-
const { sha } = getReferenceDataRes.gitReference;
|
|
14287
|
-
debug19("project id %s", projectId);
|
|
14288
|
-
debug19("default branch %s", reference);
|
|
14289
|
-
if (command === "scan") {
|
|
14290
|
-
reportPath = await getReport(
|
|
14291
|
-
{
|
|
14292
|
-
scanner: SupportedScannersZ.parse(scanner),
|
|
14293
|
-
repoUrl: repo,
|
|
14294
|
-
sha,
|
|
14295
|
-
gqlClient,
|
|
14296
|
-
cxProjectName,
|
|
14297
|
-
dirname,
|
|
14298
|
-
reference,
|
|
14299
|
-
ci
|
|
14300
|
-
},
|
|
14301
|
-
{ skipPrompts }
|
|
14302
|
-
);
|
|
14303
|
-
}
|
|
14304
|
-
const shouldScan = !reportPath;
|
|
14305
|
-
const uploadReportSpinner = createSpinner5("\u{1F4C1} Uploading Report").start();
|
|
14306
|
-
try {
|
|
14307
|
-
if (reportPath) {
|
|
14308
|
-
await uploadFile({
|
|
14309
|
-
file: reportPath,
|
|
14310
|
-
url: reportUploadInfo.url,
|
|
14311
|
-
uploadFields: JSON.parse(reportUploadInfo.uploadFieldsJSON),
|
|
14312
|
-
uploadKey: reportUploadInfo.uploadKey
|
|
14313
|
-
});
|
|
14314
|
-
}
|
|
14315
|
-
} catch (e) {
|
|
14316
|
-
uploadReportSpinner.error({ text: "\u{1F4C1} Report upload failed" });
|
|
14317
|
-
throw e;
|
|
14318
|
-
}
|
|
14319
|
-
await _digestReport({
|
|
14320
|
-
gqlClient,
|
|
14321
|
-
fixReportId: reportUploadInfo.fixReportId,
|
|
14322
|
-
projectId,
|
|
14323
|
-
command,
|
|
14324
|
-
ci,
|
|
14325
|
-
repoUrl: repo,
|
|
14326
|
-
sha,
|
|
14327
|
-
reference,
|
|
14328
|
-
shouldScan,
|
|
14329
|
-
polling
|
|
14330
|
-
});
|
|
14331
|
-
uploadReportSpinner.success({ text: "\u{1F4C1} Report uploaded successfully" });
|
|
14332
|
-
const mobbSpinner = createSpinner5("\u{1F575}\uFE0F\u200D\u2642\uFE0F Initiating Mobb analysis").start();
|
|
14333
|
-
const sendReportRes = await sendReport({
|
|
14334
|
-
gqlClient,
|
|
14335
|
-
spinner: mobbSpinner,
|
|
14336
|
-
submitVulnerabilityReportVariables: {
|
|
14337
|
-
fixReportId: reportUploadInfo.fixReportId,
|
|
14338
|
-
repoUrl: z30.string().parse(repo),
|
|
14339
|
-
reference,
|
|
14340
|
-
projectId,
|
|
14341
|
-
vulnerabilityReportFileName: shouldScan ? void 0 : REPORT_DEFAULT_FILE_NAME,
|
|
14342
|
-
sha,
|
|
14343
|
-
experimentalEnabled: !!experimentalEnabled,
|
|
14344
|
-
pullRequest: params.pullRequest,
|
|
14345
|
-
scanSource: _getScanSource(command, ci),
|
|
14346
|
-
scanContext: ScanContext.BUGSY
|
|
14347
|
-
},
|
|
14348
|
-
polling
|
|
14349
|
-
});
|
|
14350
|
-
if (sendReportRes.submitVulnerabilityReport.__typename !== "VulnerabilityReport") {
|
|
14351
|
-
mobbSpinner.error({ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed" });
|
|
14352
|
-
throw new Error("\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed");
|
|
14353
|
-
}
|
|
14354
|
-
mobbSpinner.success({
|
|
14355
|
-
text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Generating fixes..."
|
|
14356
|
-
});
|
|
14357
|
-
if (autoPr) {
|
|
14358
|
-
await handleAutoPr({
|
|
14359
|
-
gqlClient,
|
|
14360
|
-
analysisId: reportUploadInfo.fixReportId,
|
|
14361
|
-
commitDirectly,
|
|
14362
|
-
prId: pullRequest,
|
|
14363
|
-
createSpinner: createSpinner5,
|
|
14364
|
-
createOnePr,
|
|
14365
|
-
polling
|
|
14366
|
-
});
|
|
14367
|
-
}
|
|
14368
|
-
await askToOpenAnalysis();
|
|
14369
|
-
if (command === "review") {
|
|
14370
|
-
await waitForAnaysisAndReviewPr({
|
|
14371
|
-
repo,
|
|
14372
|
-
githubActionToken,
|
|
14373
|
-
analysisId: reportUploadInfo.fixReportId,
|
|
14374
|
-
scanner,
|
|
14375
|
-
gqlClient,
|
|
14376
|
-
polling
|
|
14377
|
-
});
|
|
14378
|
-
}
|
|
14379
|
-
return reportUploadInfo.fixReportId;
|
|
14380
|
-
async function askToOpenAnalysis() {
|
|
14381
|
-
if (!repoUploadInfo || !reportUploadInfo) {
|
|
14382
|
-
throw new Error("uploadS3BucketInfo is null");
|
|
14383
|
-
}
|
|
14384
|
-
const reportUrl = getReportUrl({
|
|
14385
|
-
organizationId,
|
|
14386
|
-
projectId,
|
|
14387
|
-
fixReportId: reportUploadInfo.fixReportId
|
|
14388
|
-
});
|
|
14389
|
-
!ci && console.log("You can access the analysis at: \n");
|
|
14390
|
-
console.log(chalk6.bold(reportUrl));
|
|
14391
|
-
!skipPrompts && await mobbAnalysisPrompt();
|
|
14392
|
-
!ci && open3(reportUrl);
|
|
14393
|
-
!ci && console.log(
|
|
14394
|
-
chalk6.bgBlue("\n\n My work here is done for now, see you soon! \u{1F575}\uFE0F\u200D\u2642\uFE0F ")
|
|
14395
|
-
);
|
|
14396
|
-
}
|
|
14397
|
-
async function handleScmIntegration(oldToken, scmAuthUrl2, repoUrl) {
|
|
14398
|
-
const scmLibType = getCloudScmLibTypeFromUrl(repoUrl);
|
|
14399
|
-
const scmName = scmLibType === "GITHUB" /* GITHUB */ ? "Github" : scmLibType === "GITLAB" /* GITLAB */ ? "Gitlab" : scmLibType === "ADO" /* ADO */ ? "Azure DevOps" : "";
|
|
14400
|
-
const addScmIntegration = skipPrompts ? true : await scmIntegrationPrompt(scmName);
|
|
14401
|
-
const scmSpinner = createSpinner5(
|
|
14402
|
-
`\u{1F517} Waiting for ${scmName} integration...`
|
|
14403
|
-
).start();
|
|
14404
|
-
if (!addScmIntegration) {
|
|
14405
|
-
scmSpinner.error();
|
|
14406
|
-
throw Error(`Could not reach ${scmName} repo`);
|
|
14407
|
-
}
|
|
14408
|
-
console.log(
|
|
14409
|
-
`If the page does not open automatically, kindly access it through ${scmAuthUrl2}.`
|
|
14410
|
-
);
|
|
14411
|
-
await open3(scmAuthUrl2);
|
|
14412
|
-
for (let i = 0; i < LOGIN_MAX_WAIT2 / LOGIN_CHECK_DELAY2; i++) {
|
|
14413
|
-
const userInfo2 = await gqlClient.getUserInfo();
|
|
14414
|
-
if (!userInfo2) {
|
|
14415
|
-
throw new CliError2("User info not found");
|
|
14416
|
-
}
|
|
14417
|
-
const scmConfigs = getFromArraySafe(userInfo2.scmConfigs);
|
|
14418
|
-
const tokenInfo2 = getScmConfig({
|
|
14419
|
-
url: repoUrl,
|
|
14420
|
-
scmConfigs,
|
|
14421
|
-
brokerHosts: getBrokerHosts(
|
|
14422
|
-
userInfo2.userOrganizationsAndUserOrganizationRoles
|
|
14423
|
-
),
|
|
14424
|
-
includeOrgTokens: false
|
|
14425
|
-
});
|
|
14426
|
-
if (tokenInfo2.accessToken && tokenInfo2.accessToken !== oldToken) {
|
|
14427
|
-
scmSpinner.success({ text: `\u{1F517} ${scmName} integration successful!` });
|
|
14428
|
-
return tokenInfo2.accessToken;
|
|
14429
|
-
}
|
|
14430
|
-
scmSpinner.spin();
|
|
14431
|
-
await sleep(LOGIN_CHECK_DELAY2);
|
|
14432
|
-
}
|
|
14433
|
-
scmSpinner.error({
|
|
14434
|
-
text: `${scmName} login timeout error`
|
|
14435
|
-
});
|
|
14436
|
-
throw new CliError2(`${scmName} login timeout`);
|
|
14437
|
-
}
|
|
14438
|
-
async function uploadExistingRepo() {
|
|
14439
|
-
if (!repoUploadInfo || !reportUploadInfo) {
|
|
14440
|
-
throw new Error("uploadS3BucketInfo is null");
|
|
14441
|
-
}
|
|
14442
|
-
if (!srcPath) {
|
|
14443
|
-
throw new Error("src path is required");
|
|
14444
|
-
}
|
|
14445
|
-
const shouldScan2 = !reportPath;
|
|
14446
|
-
if (reportPath) {
|
|
14447
|
-
const uploadReportSpinner2 = createSpinner5("\u{1F4C1} Uploading Report").start();
|
|
14448
|
-
try {
|
|
14449
|
-
await uploadFile({
|
|
14450
|
-
file: reportPath,
|
|
14451
|
-
url: reportUploadInfo.url,
|
|
14452
|
-
uploadFields: JSON.parse(reportUploadInfo.uploadFieldsJSON),
|
|
14453
|
-
uploadKey: reportUploadInfo.uploadKey
|
|
14454
|
-
});
|
|
14455
|
-
} catch (e) {
|
|
14456
|
-
uploadReportSpinner2.error({ text: "\u{1F4C1} Report upload failed" });
|
|
14457
|
-
throw e;
|
|
14458
|
-
}
|
|
14459
|
-
uploadReportSpinner2.success({
|
|
14460
|
-
text: "\u{1F4C1} Uploading Report successful!"
|
|
14461
|
-
});
|
|
14462
|
-
}
|
|
14463
|
-
let gitInfo2 = { success: false };
|
|
14464
|
-
if (reportPath) {
|
|
14465
|
-
const vulnFiles = await _digestReport({
|
|
14466
|
-
gqlClient,
|
|
14467
|
-
fixReportId: reportUploadInfo.fixReportId,
|
|
14468
|
-
projectId,
|
|
14469
|
-
command,
|
|
14470
|
-
ci,
|
|
14471
|
-
shouldScan: shouldScan2,
|
|
14472
|
-
polling
|
|
14473
|
-
});
|
|
14474
|
-
const res = await _zipAndUploadRepo({
|
|
14475
|
-
srcPath,
|
|
14476
|
-
vulnFiles,
|
|
14477
|
-
repoUploadInfo,
|
|
14478
|
-
isIncludeAllFiles: false
|
|
14479
|
-
});
|
|
14480
|
-
gitInfo2 = res.gitInfo;
|
|
14481
|
-
} else {
|
|
14482
|
-
const res = await _zipAndUploadRepo({
|
|
14483
|
-
srcPath,
|
|
14484
|
-
vulnFiles: [],
|
|
14485
|
-
repoUploadInfo,
|
|
14486
|
-
isIncludeAllFiles: true
|
|
14487
|
-
});
|
|
14488
|
-
gitInfo2 = res.gitInfo;
|
|
14489
|
-
await _digestReport({
|
|
14490
|
-
gqlClient,
|
|
14491
|
-
fixReportId: reportUploadInfo.fixReportId,
|
|
14492
|
-
projectId,
|
|
14493
|
-
command,
|
|
14494
|
-
ci,
|
|
14495
|
-
shouldScan: shouldScan2,
|
|
14496
|
-
polling
|
|
14497
|
-
});
|
|
14498
|
-
}
|
|
14499
|
-
const mobbSpinner2 = createSpinner5("\u{1F575}\uFE0F\u200D\u2642\uFE0F Initiating Mobb analysis").start();
|
|
14500
|
-
try {
|
|
14501
|
-
await sendReport({
|
|
14502
|
-
gqlClient,
|
|
14503
|
-
spinner: mobbSpinner2,
|
|
14504
|
-
submitVulnerabilityReportVariables: {
|
|
14505
|
-
fixReportId: reportUploadInfo.fixReportId,
|
|
14506
|
-
projectId,
|
|
14507
|
-
repoUrl: repo || gitInfo2.repoUrl || getTopLevelDirName(srcPath),
|
|
14508
|
-
reference: ref || gitInfo2.reference || "no-branch",
|
|
14509
|
-
sha: commitHash || gitInfo2.hash || "0123456789abcdef",
|
|
14510
|
-
scanSource: _getScanSource(command, ci),
|
|
14511
|
-
pullRequest: params.pullRequest,
|
|
14512
|
-
experimentalEnabled: !!experimentalEnabled,
|
|
14513
|
-
scanContext: ScanContext.BUGSY
|
|
14514
|
-
},
|
|
14515
|
-
polling
|
|
14516
|
-
});
|
|
14517
|
-
if (command === "review") {
|
|
14518
|
-
await waitForAnaysisAndReviewPr({
|
|
14519
|
-
repo,
|
|
14520
|
-
githubActionToken,
|
|
14521
|
-
analysisId: reportUploadInfo.fixReportId,
|
|
14522
|
-
scanner,
|
|
14523
|
-
gqlClient,
|
|
14524
|
-
polling
|
|
14525
|
-
});
|
|
14526
|
-
}
|
|
14527
|
-
} catch (e) {
|
|
14528
|
-
mobbSpinner2.error({ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed" });
|
|
14529
|
-
throw e;
|
|
14530
|
-
}
|
|
14531
|
-
mobbSpinner2.success({
|
|
14532
|
-
text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Generating fixes..."
|
|
14533
|
-
});
|
|
14534
|
-
if (autoPr) {
|
|
14535
|
-
await handleAutoPr({
|
|
14536
|
-
gqlClient,
|
|
14537
|
-
analysisId: reportUploadInfo.fixReportId,
|
|
14538
|
-
commitDirectly,
|
|
14539
|
-
prId: pullRequest,
|
|
14540
|
-
createSpinner: createSpinner5,
|
|
14541
|
-
createOnePr,
|
|
14542
|
-
polling
|
|
14543
|
-
});
|
|
14515
|
+
display: false
|
|
14516
|
+
});
|
|
14517
|
+
if (loginCode !== CHECKMARX_SUCCESS_CODE) {
|
|
14518
|
+
if (skipPrompts) {
|
|
14519
|
+
await throwCheckmarxConfigError();
|
|
14544
14520
|
}
|
|
14545
|
-
await
|
|
14546
|
-
|
|
14521
|
+
await startCheckmarxConfigationPrompt();
|
|
14522
|
+
await validateCheckamxCredentials();
|
|
14547
14523
|
}
|
|
14548
|
-
|
|
14549
|
-
|
|
14550
|
-
|
|
14551
|
-
|
|
14552
|
-
|
|
14553
|
-
|
|
14554
|
-
|
|
14555
|
-
|
|
14556
|
-
|
|
14557
|
-
|
|
14558
|
-
|
|
14559
|
-
|
|
14560
|
-
|
|
14561
|
-
|
|
14562
|
-
|
|
14563
|
-
|
|
14524
|
+
const extension = path9.extname(reportPath);
|
|
14525
|
+
const filePath = path9.dirname(reportPath);
|
|
14526
|
+
const fileName = path9.basename(reportPath, extension);
|
|
14527
|
+
const checkmarxCommandArgs = getCheckmarxCommandArgs({
|
|
14528
|
+
repoPath: repositoryRoot,
|
|
14529
|
+
branch,
|
|
14530
|
+
filePath,
|
|
14531
|
+
fileName,
|
|
14532
|
+
projectName
|
|
14533
|
+
});
|
|
14534
|
+
console.log("\u280B \u{1F50D} Initiating Checkmarx Scan ");
|
|
14535
|
+
const { code: scanCode } = await forkCheckmarx(
|
|
14536
|
+
[...SCAN_COMMAND, ...checkmarxCommandArgs],
|
|
14537
|
+
{
|
|
14538
|
+
display: true
|
|
14539
|
+
}
|
|
14540
|
+
);
|
|
14541
|
+
if (scanCode !== CHECKMARX_SUCCESS_CODE) {
|
|
14542
|
+
createSpinner2("\u{1F50D} Something went wrong with the checkmarx scan").start().error();
|
|
14543
|
+
throw new CliError();
|
|
14564
14544
|
}
|
|
14565
|
-
|
|
14566
|
-
|
|
14567
|
-
|
|
14568
|
-
|
|
14569
|
-
|
|
14570
|
-
|
|
14571
|
-
|
|
14572
|
-
|
|
14573
|
-
|
|
14574
|
-
|
|
14575
|
-
|
|
14576
|
-
|
|
14545
|
+
await createSpinner2("\u{1F50D} Checkmarx Scan completed").start().success();
|
|
14546
|
+
return true;
|
|
14547
|
+
}
|
|
14548
|
+
async function throwCheckmarxConfigError() {
|
|
14549
|
+
await createSpinner2("\u{1F513} Checkmarx is not configued correctly").start().error();
|
|
14550
|
+
throw new CliError(
|
|
14551
|
+
`Checkmarx is not configued correctly
|
|
14552
|
+
you can configure it by using the ${chalk5.bold(
|
|
14553
|
+
"cx configure"
|
|
14554
|
+
)} command`
|
|
14555
|
+
);
|
|
14556
|
+
}
|
|
14557
|
+
async function validateCheckamxCredentials() {
|
|
14558
|
+
console.log(`
|
|
14559
|
+
Here's a suggestion for checkmarx configuation:
|
|
14560
|
+
${chalk5.bold("AST Base URI:")} https://ast.checkmarx.net
|
|
14561
|
+
${chalk5.bold("AST Base Auth URI (IAM):")} https://iam.checkmarx.net
|
|
14562
|
+
`);
|
|
14563
|
+
await forkCheckmarx(CONFIGURE_COMMAND, { display: true });
|
|
14564
|
+
const { code: loginCode } = await forkCheckmarx(VALIDATE_COMMAND, {
|
|
14565
|
+
display: false
|
|
14566
|
+
});
|
|
14567
|
+
if (loginCode !== CHECKMARX_SUCCESS_CODE) {
|
|
14568
|
+
const tryAgain = await tryCheckmarxConfiguarationAgain();
|
|
14569
|
+
if (!tryAgain) {
|
|
14570
|
+
await throwCheckmarxConfigError();
|
|
14571
|
+
}
|
|
14572
|
+
await validateCheckamxCredentials();
|
|
14573
|
+
return;
|
|
14577
14574
|
}
|
|
14578
|
-
|
|
14579
|
-
return { gitInfo: gitInfo2 };
|
|
14575
|
+
await createSpinner2("\u{1F513} Checkmarx configured successfully!").start().success();
|
|
14580
14576
|
}
|
|
14581
|
-
|
|
14582
|
-
|
|
14583
|
-
|
|
14584
|
-
|
|
14585
|
-
|
|
14586
|
-
|
|
14587
|
-
|
|
14588
|
-
|
|
14589
|
-
|
|
14590
|
-
|
|
14591
|
-
|
|
14592
|
-
}
|
|
14593
|
-
const digestSpinner = createSpinner4(
|
|
14594
|
-
progressMassages.processingVulnerabilityReport
|
|
14595
|
-
).start();
|
|
14577
|
+
|
|
14578
|
+
// src/features/analysis/scanners/snyk.ts
|
|
14579
|
+
import { createRequire as createRequire2 } from "module";
|
|
14580
|
+
import chalk6 from "chalk";
|
|
14581
|
+
import Debug20 from "debug";
|
|
14582
|
+
import { createSpinner as createSpinner3 } from "nanospinner";
|
|
14583
|
+
import open2 from "open";
|
|
14584
|
+
var debug20 = Debug20("mobbdev:snyk");
|
|
14585
|
+
var moduleUrl2;
|
|
14586
|
+
if (typeof __filename !== "undefined") {
|
|
14587
|
+
moduleUrl2 = __filename;
|
|
14588
|
+
} else {
|
|
14596
14589
|
try {
|
|
14597
|
-
const
|
|
14598
|
-
|
|
14599
|
-
|
|
14600
|
-
|
|
14601
|
-
|
|
14602
|
-
|
|
14603
|
-
|
|
14604
|
-
|
|
14605
|
-
shouldScan
|
|
14606
|
-
}
|
|
14607
|
-
);
|
|
14608
|
-
const callbackStates = [
|
|
14609
|
-
"Digested" /* Digested */,
|
|
14610
|
-
"Finished" /* Finished */
|
|
14611
|
-
];
|
|
14612
|
-
const callback = (_analysisId) => digestSpinner.update({
|
|
14613
|
-
text: progressMassages.processingVulnerabilityReportSuccess
|
|
14614
|
-
});
|
|
14615
|
-
if (polling) {
|
|
14616
|
-
debug19(
|
|
14617
|
-
"[_digestReport] Using POLLING mode for analysis state updates (--polling flag enabled)"
|
|
14618
|
-
);
|
|
14619
|
-
console.log(
|
|
14620
|
-
chalk6.cyan(
|
|
14621
|
-
"\u{1F504} [Polling Mode] Using HTTP polling instead of WebSocket for status updates"
|
|
14622
|
-
)
|
|
14623
|
-
);
|
|
14624
|
-
await gqlClient.pollForAnalysisState({
|
|
14625
|
-
analysisId: fixReportId,
|
|
14626
|
-
callback,
|
|
14627
|
-
callbackStates,
|
|
14628
|
-
timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
|
|
14629
|
-
});
|
|
14590
|
+
const getImportMetaUrl = new Function("return import.meta.url");
|
|
14591
|
+
moduleUrl2 = getImportMetaUrl();
|
|
14592
|
+
} catch {
|
|
14593
|
+
const err = new Error();
|
|
14594
|
+
const stack = err.stack || "";
|
|
14595
|
+
const match = stack.match(/file:\/\/[^\s)]+/);
|
|
14596
|
+
if (match) {
|
|
14597
|
+
moduleUrl2 = match[0];
|
|
14630
14598
|
} else {
|
|
14631
|
-
|
|
14632
|
-
"[_digestReport] Using WEBSOCKET mode for analysis state updates (default)"
|
|
14633
|
-
);
|
|
14634
|
-
console.log(
|
|
14635
|
-
chalk6.cyan(
|
|
14636
|
-
"\u{1F50C} [WebSocket Mode] Using WebSocket subscription for status updates"
|
|
14637
|
-
)
|
|
14638
|
-
);
|
|
14639
|
-
await gqlClient.subscribeToAnalysis({
|
|
14640
|
-
subscribeToAnalysisParams: {
|
|
14641
|
-
analysisId: fixReportId
|
|
14642
|
-
},
|
|
14643
|
-
callback,
|
|
14644
|
-
callbackStates,
|
|
14645
|
-
timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
|
|
14646
|
-
});
|
|
14599
|
+
throw new Error("Unable to determine module URL in this environment");
|
|
14647
14600
|
}
|
|
14648
|
-
const vulnFiles = await gqlClient.getVulnerabilityReportPaths(
|
|
14649
|
-
vulnerabilityReportId
|
|
14650
|
-
);
|
|
14651
|
-
digestSpinner.success({
|
|
14652
|
-
text: progressMassages.processingVulnerabilityReportSuccess
|
|
14653
|
-
});
|
|
14654
|
-
return vulnFiles;
|
|
14655
|
-
} catch (e) {
|
|
14656
|
-
const errorMessage = e instanceof ReportDigestError ? e.getDisplayMessage() : ReportDigestError.defaultMessage;
|
|
14657
|
-
digestSpinner.error({
|
|
14658
|
-
text: errorMessage
|
|
14659
|
-
});
|
|
14660
|
-
throw e;
|
|
14661
14601
|
}
|
|
14662
14602
|
}
|
|
14663
|
-
|
|
14664
|
-
|
|
14665
|
-
|
|
14666
|
-
|
|
14667
|
-
|
|
14668
|
-
|
|
14669
|
-
|
|
14670
|
-
}
|
|
14671
|
-
|
|
14672
|
-
|
|
14673
|
-
|
|
14674
|
-
|
|
14675
|
-
|
|
14676
|
-
|
|
14677
|
-
|
|
14678
|
-
|
|
14679
|
-
|
|
14680
|
-
|
|
14681
|
-
|
|
14682
|
-
{
|
|
14683
|
-
propagateExceptions: true
|
|
14603
|
+
var costumeRequire2 = createRequire2(moduleUrl2);
|
|
14604
|
+
var SNYK_PATH = costumeRequire2.resolve("snyk/bin/snyk");
|
|
14605
|
+
var SNYK_ARTICLE_URL = "https://docs.snyk.io/scan-using-snyk/snyk-code/configure-snyk-code#enable-snyk-code";
|
|
14606
|
+
debug20("snyk executable path %s", SNYK_PATH);
|
|
14607
|
+
async function forkSnyk(args, { display }) {
|
|
14608
|
+
debug20("fork snyk with args %o %s", args, display);
|
|
14609
|
+
return createFork({ args, processPath: SNYK_PATH, name: "snyk" }, { display });
|
|
14610
|
+
}
|
|
14611
|
+
async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
|
|
14612
|
+
debug20("get snyk report start %s %s", reportPath, repoRoot);
|
|
14613
|
+
const config2 = await forkSnyk(["config"], { display: false });
|
|
14614
|
+
const { message: configMessage } = config2;
|
|
14615
|
+
if (!configMessage.includes("api: ")) {
|
|
14616
|
+
const snykLoginSpinner = createSpinner3().start();
|
|
14617
|
+
if (!skipPrompts) {
|
|
14618
|
+
snykLoginSpinner.update({
|
|
14619
|
+
text: "\u{1F513} Login to Snyk is required, press any key to continue"
|
|
14620
|
+
});
|
|
14621
|
+
await keypress();
|
|
14684
14622
|
}
|
|
14685
|
-
|
|
14686
|
-
|
|
14687
|
-
return addFixCommentsForPr({
|
|
14688
|
-
analysisId: analysisId2,
|
|
14689
|
-
gqlClient,
|
|
14690
|
-
scm,
|
|
14691
|
-
scanner: z30.nativeEnum(SCANNERS).parse(scanner)
|
|
14692
|
-
});
|
|
14693
|
-
};
|
|
14694
|
-
if (polling) {
|
|
14695
|
-
debug19(
|
|
14696
|
-
"[waitForAnaysisAndReviewPr] Using POLLING mode for analysis state updates"
|
|
14697
|
-
);
|
|
14698
|
-
console.log(
|
|
14699
|
-
chalk6.cyan(
|
|
14700
|
-
"\u{1F504} [Polling Mode] Waiting for analysis completion using HTTP polling"
|
|
14701
|
-
)
|
|
14702
|
-
);
|
|
14703
|
-
await gqlClient.pollForAnalysisState({
|
|
14704
|
-
analysisId,
|
|
14705
|
-
callback,
|
|
14706
|
-
callbackStates: ["Finished" /* Finished */]
|
|
14623
|
+
snykLoginSpinner.update({
|
|
14624
|
+
text: "\u{1F513} Waiting for Snyk login to complete"
|
|
14707
14625
|
});
|
|
14708
|
-
|
|
14709
|
-
|
|
14710
|
-
|
|
14711
|
-
|
|
14626
|
+
debug20("no token in the config %s", config2);
|
|
14627
|
+
await forkSnyk(["auth"], { display: true });
|
|
14628
|
+
snykLoginSpinner.success({ text: "\u{1F513} Login to Snyk Successful" });
|
|
14629
|
+
}
|
|
14630
|
+
const snykSpinner = createSpinner3("\u{1F50D} Scanning your repo with Snyk ").start();
|
|
14631
|
+
const { message: scanOutput } = await forkSnyk(
|
|
14632
|
+
["code", "test", `--sarif-file-output=${reportPath}`, repoRoot],
|
|
14633
|
+
{ display: true }
|
|
14634
|
+
);
|
|
14635
|
+
if (scanOutput.includes("Snyk Code is not supported for org")) {
|
|
14636
|
+
debug20("snyk code is not enabled %s", scanOutput);
|
|
14637
|
+
snykSpinner.error({ text: "\u{1F50D} Snyk configuration needed" });
|
|
14638
|
+
const answer = await snykArticlePrompt();
|
|
14639
|
+
debug20("answer %s", answer);
|
|
14640
|
+
if (answer) {
|
|
14641
|
+
debug20("opening the browser");
|
|
14642
|
+
await open2(SNYK_ARTICLE_URL);
|
|
14643
|
+
}
|
|
14712
14644
|
console.log(
|
|
14713
|
-
chalk6.
|
|
14714
|
-
"\
|
|
14715
|
-
)
|
|
14716
|
-
);
|
|
14717
|
-
|
|
14718
|
-
subscribeToAnalysisParams: {
|
|
14719
|
-
analysisId
|
|
14720
|
-
},
|
|
14721
|
-
callback,
|
|
14722
|
-
callbackStates: ["Finished" /* Finished */]
|
|
14723
|
-
});
|
|
14645
|
+
chalk6.bgBlue(
|
|
14646
|
+
"\nPlease enable Snyk Code in your Snyk account and try again."
|
|
14647
|
+
)
|
|
14648
|
+
);
|
|
14649
|
+
throw Error("snyk is not enbabled");
|
|
14724
14650
|
}
|
|
14651
|
+
snykSpinner.success({ text: "\u{1F50D} Snyk code scan completed" });
|
|
14652
|
+
return true;
|
|
14725
14653
|
}
|
|
14726
14654
|
|
|
14727
|
-
// src/
|
|
14728
|
-
|
|
14729
|
-
|
|
14730
|
-
|
|
14731
|
-
|
|
14732
|
-
|
|
14733
|
-
|
|
14734
|
-
|
|
14735
|
-
|
|
14736
|
-
|
|
14655
|
+
// src/features/analysis/index.ts
|
|
14656
|
+
init_client_generates();
|
|
14657
|
+
var { CliError: CliError2, Spinner: Spinner2 } = utils_exports;
|
|
14658
|
+
function _getScanSource(command, ci) {
|
|
14659
|
+
if (command === "review") return "AUTO_FIXER" /* AutoFixer */;
|
|
14660
|
+
const envToCi = [
|
|
14661
|
+
["GITLAB_CI", "CI_GITLAB" /* CiGitlab */],
|
|
14662
|
+
["GITHUB_ACTIONS", "CI_GITHUB" /* CiGithub */],
|
|
14663
|
+
["JENKINS_URL", "CI_JENKINS" /* CiJenkins */],
|
|
14664
|
+
["CIRCLECI", "CI_CIRCLECI" /* CiCircleci */],
|
|
14665
|
+
["TF_BUILD", "CI_AZURE" /* CiAzure */],
|
|
14666
|
+
["bamboo_buildKey", "CI_BAMBOO" /* CiBamboo */]
|
|
14667
|
+
];
|
|
14668
|
+
for (const [envKey, source] of envToCi) {
|
|
14669
|
+
if (env2[envKey]) {
|
|
14670
|
+
return source;
|
|
14671
|
+
}
|
|
14737
14672
|
}
|
|
14738
|
-
|
|
14739
|
-
|
|
14740
|
-
throw new CliError(
|
|
14741
|
-
"Local skill input must be a directory containing SKILL.md"
|
|
14742
|
-
);
|
|
14673
|
+
if (ci) {
|
|
14674
|
+
return "CI_UNKNOWN" /* CiUnknown */;
|
|
14743
14675
|
}
|
|
14744
|
-
return
|
|
14676
|
+
return "CLI" /* Cli */;
|
|
14745
14677
|
}
|
|
14746
|
-
function
|
|
14747
|
-
|
|
14748
|
-
|
|
14749
|
-
|
|
14750
|
-
|
|
14751
|
-
|
|
14752
|
-
|
|
14753
|
-
|
|
14754
|
-
|
|
14755
|
-
|
|
14756
|
-
|
|
14757
|
-
|
|
14758
|
-
|
|
14759
|
-
|
|
14760
|
-
|
|
14678
|
+
async function downloadRepo({
|
|
14679
|
+
repoUrl,
|
|
14680
|
+
authHeaders,
|
|
14681
|
+
downloadUrl,
|
|
14682
|
+
dirname,
|
|
14683
|
+
ci
|
|
14684
|
+
}) {
|
|
14685
|
+
const { createSpinner: createSpinner5 } = Spinner2({ ci });
|
|
14686
|
+
const repoSpinner = createSpinner5("\u{1F4BE} Downloading Repo").start();
|
|
14687
|
+
debug21("download repo %s %s %s", repoUrl, dirname);
|
|
14688
|
+
const zipFilePath = path10.join(dirname, "repo.zip");
|
|
14689
|
+
debug21("download URL: %s auth headers: %o", downloadUrl, authHeaders);
|
|
14690
|
+
const response = await fetch4(downloadUrl, {
|
|
14691
|
+
method: "GET",
|
|
14692
|
+
headers: {
|
|
14693
|
+
...authHeaders
|
|
14761
14694
|
}
|
|
14695
|
+
});
|
|
14696
|
+
if (!response.ok) {
|
|
14697
|
+
debug21("SCM zipball request failed %s %s", response.body, response.status);
|
|
14698
|
+
repoSpinner.error({ text: "\u{1F4BE} Repo download failed" });
|
|
14699
|
+
throw new Error(`Can't access ${chalk7.bold(repoUrl)}`);
|
|
14762
14700
|
}
|
|
14763
|
-
|
|
14764
|
-
|
|
14701
|
+
const fileWriterStream = fs10.createWriteStream(zipFilePath);
|
|
14702
|
+
if (!response.body) {
|
|
14703
|
+
throw new Error("Response body is empty");
|
|
14765
14704
|
}
|
|
14766
|
-
|
|
14767
|
-
|
|
14768
|
-
|
|
14769
|
-
|
|
14705
|
+
await pipeline(response.body, fileWriterStream);
|
|
14706
|
+
await extract(zipFilePath, { dir: dirname });
|
|
14707
|
+
const repoRoot = fs10.readdirSync(dirname, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name)[0];
|
|
14708
|
+
if (!repoRoot) {
|
|
14709
|
+
throw new Error("Repo root not found");
|
|
14710
|
+
}
|
|
14711
|
+
debug21("repo root %s", repoRoot);
|
|
14712
|
+
repoSpinner.success({ text: "\u{1F4BE} Repo downloaded successfully" });
|
|
14713
|
+
return path10.join(dirname, repoRoot);
|
|
14714
|
+
}
|
|
14715
|
+
var getReportUrl = ({
|
|
14716
|
+
organizationId,
|
|
14717
|
+
projectId,
|
|
14718
|
+
fixReportId
|
|
14719
|
+
}) => `${WEB_APP_URL}/organization/${organizationId}/project/${projectId}/report/${fixReportId}`;
|
|
14720
|
+
var debug21 = Debug21("mobbdev:index");
|
|
14721
|
+
async function runAnalysis(params, options) {
|
|
14722
|
+
const tmpObj = tmp2.dirSync({
|
|
14723
|
+
unsafeCleanup: true
|
|
14724
|
+
});
|
|
14725
|
+
try {
|
|
14726
|
+
return await _scan(
|
|
14727
|
+
{
|
|
14728
|
+
...params,
|
|
14729
|
+
dirname: tmpObj.name
|
|
14730
|
+
},
|
|
14731
|
+
options
|
|
14770
14732
|
);
|
|
14733
|
+
} finally {
|
|
14734
|
+
tmpObj.removeCallback();
|
|
14771
14735
|
}
|
|
14772
|
-
return `${LOCAL_SKILL_ZIP_DATA_URL_PREFIX}${zipBuffer.toString("base64")}`;
|
|
14773
14736
|
}
|
|
14774
|
-
function
|
|
14775
|
-
|
|
14776
|
-
|
|
14777
|
-
|
|
14778
|
-
|
|
14779
|
-
|
|
14780
|
-
|
|
14781
|
-
|
|
14782
|
-
|
|
14783
|
-
|
|
14784
|
-
|
|
14785
|
-
|
|
14786
|
-
|
|
14787
|
-
|
|
14788
|
-
|
|
14789
|
-
|
|
14790
|
-
|
|
14791
|
-
|
|
14792
|
-
}
|
|
14793
|
-
|
|
14794
|
-
|
|
14795
|
-
|
|
14796
|
-
}
|
|
14797
|
-
|
|
14798
|
-
|
|
14737
|
+
function _getUrlForScmType({
|
|
14738
|
+
scmLibType
|
|
14739
|
+
}) {
|
|
14740
|
+
const githubAuthUrl = `${WEB_APP_URL}/github-auth`;
|
|
14741
|
+
const gitlabAuthUrl = `${WEB_APP_URL}/gitlab-auth`;
|
|
14742
|
+
const adoAuthUrl = `${WEB_APP_URL}/ado-auth`;
|
|
14743
|
+
switch (scmLibType) {
|
|
14744
|
+
case "GITHUB" /* GITHUB */:
|
|
14745
|
+
return {
|
|
14746
|
+
authUrl: githubAuthUrl
|
|
14747
|
+
};
|
|
14748
|
+
case "GITLAB" /* GITLAB */:
|
|
14749
|
+
return {
|
|
14750
|
+
authUrl: gitlabAuthUrl
|
|
14751
|
+
};
|
|
14752
|
+
case "ADO" /* ADO */:
|
|
14753
|
+
return {
|
|
14754
|
+
authUrl: adoAuthUrl
|
|
14755
|
+
};
|
|
14756
|
+
default:
|
|
14757
|
+
return {
|
|
14758
|
+
authUrl: void 0
|
|
14759
|
+
};
|
|
14760
|
+
}
|
|
14761
|
+
}
|
|
14762
|
+
function getBrokerHosts(userOrgsAnUserOrgRoles) {
|
|
14763
|
+
const brokerHosts = [];
|
|
14764
|
+
if (!userOrgsAnUserOrgRoles) {
|
|
14765
|
+
return brokerHosts;
|
|
14766
|
+
}
|
|
14767
|
+
userOrgsAnUserOrgRoles.forEach((org) => {
|
|
14768
|
+
org?.organization?.brokerHosts.forEach((brokerHost) => {
|
|
14769
|
+
if (brokerHost) {
|
|
14770
|
+
brokerHosts.push(brokerHost);
|
|
14799
14771
|
}
|
|
14800
|
-
}
|
|
14772
|
+
});
|
|
14773
|
+
});
|
|
14774
|
+
return brokerHosts;
|
|
14775
|
+
}
|
|
14776
|
+
async function getScmTokenInfo(params) {
|
|
14777
|
+
const { gqlClient, repo } = params;
|
|
14778
|
+
const userInfo2 = await gqlClient.getUserInfo();
|
|
14779
|
+
if (!userInfo2) {
|
|
14780
|
+
throw new Error("userInfo is null");
|
|
14801
14781
|
}
|
|
14802
|
-
|
|
14803
|
-
return
|
|
14782
|
+
const scmConfigs = getFromArraySafe(userInfo2.scmConfigs);
|
|
14783
|
+
return getScmConfig({
|
|
14784
|
+
url: repo,
|
|
14785
|
+
scmConfigs,
|
|
14786
|
+
includeOrgTokens: false,
|
|
14787
|
+
brokerHosts: getBrokerHosts(
|
|
14788
|
+
userInfo2.userOrganizationsAndUserOrganizationRoles
|
|
14789
|
+
)
|
|
14790
|
+
});
|
|
14804
14791
|
}
|
|
14805
|
-
|
|
14806
|
-
// src/commands/index.ts
|
|
14807
|
-
async function review(params, { skipPrompts = true } = {}) {
|
|
14792
|
+
async function getReport(params, { skipPrompts }) {
|
|
14808
14793
|
const {
|
|
14809
|
-
repo,
|
|
14810
|
-
f: scanFile,
|
|
14811
|
-
ref,
|
|
14812
|
-
apiKey,
|
|
14813
|
-
commitHash,
|
|
14814
|
-
mobbProjectName,
|
|
14815
|
-
pullRequest,
|
|
14816
|
-
githubToken,
|
|
14817
14794
|
scanner,
|
|
14818
|
-
|
|
14819
|
-
|
|
14795
|
+
repoUrl,
|
|
14796
|
+
gqlClient,
|
|
14797
|
+
sha,
|
|
14798
|
+
dirname,
|
|
14799
|
+
reference,
|
|
14800
|
+
cxProjectName,
|
|
14801
|
+
ci
|
|
14820
14802
|
} = params;
|
|
14821
|
-
await
|
|
14822
|
-
|
|
14823
|
-
repo,
|
|
14824
|
-
scanFile,
|
|
14825
|
-
ref,
|
|
14826
|
-
apiKey,
|
|
14827
|
-
ci: true,
|
|
14828
|
-
commitHash,
|
|
14829
|
-
experimentalEnabled: false,
|
|
14830
|
-
mobbProjectName,
|
|
14831
|
-
pullRequest,
|
|
14832
|
-
githubToken,
|
|
14833
|
-
scanner,
|
|
14834
|
-
command: "review",
|
|
14835
|
-
srcPath,
|
|
14836
|
-
polling
|
|
14837
|
-
},
|
|
14838
|
-
{ skipPrompts }
|
|
14839
|
-
);
|
|
14840
|
-
}
|
|
14841
|
-
async function analyze({
|
|
14842
|
-
repo,
|
|
14843
|
-
f: scanFile,
|
|
14844
|
-
ref,
|
|
14845
|
-
apiKey,
|
|
14846
|
-
ci,
|
|
14847
|
-
commitHash,
|
|
14848
|
-
srcPath,
|
|
14849
|
-
mobbProjectName,
|
|
14850
|
-
organizationId,
|
|
14851
|
-
autoPr,
|
|
14852
|
-
createOnePr,
|
|
14853
|
-
commitDirectly,
|
|
14854
|
-
pullRequest,
|
|
14855
|
-
polling
|
|
14856
|
-
}, { skipPrompts = false } = {}) {
|
|
14857
|
-
!ci && await showWelcomeMessage(skipPrompts);
|
|
14858
|
-
await runAnalysis(
|
|
14803
|
+
const tokenInfo = await getScmTokenInfo({ gqlClient, repo: repoUrl });
|
|
14804
|
+
const scm = await createScmLib(
|
|
14859
14805
|
{
|
|
14860
|
-
|
|
14861
|
-
|
|
14862
|
-
|
|
14863
|
-
|
|
14864
|
-
ci,
|
|
14865
|
-
commitHash,
|
|
14866
|
-
mobbProjectName,
|
|
14867
|
-
srcPath,
|
|
14868
|
-
organizationId,
|
|
14869
|
-
command: "analyze",
|
|
14870
|
-
autoPr,
|
|
14871
|
-
commitDirectly,
|
|
14872
|
-
pullRequest,
|
|
14873
|
-
createOnePr,
|
|
14874
|
-
polling
|
|
14806
|
+
url: repoUrl,
|
|
14807
|
+
accessToken: tokenInfo.accessToken,
|
|
14808
|
+
scmOrg: tokenInfo.scmOrg,
|
|
14809
|
+
scmType: tokenInfo.scmLibType
|
|
14875
14810
|
},
|
|
14876
|
-
{
|
|
14811
|
+
{ propagateExceptions: true }
|
|
14877
14812
|
);
|
|
14813
|
+
const downloadUrl = await scm.getDownloadUrl(sha);
|
|
14814
|
+
const repositoryRoot = await downloadRepo({
|
|
14815
|
+
repoUrl,
|
|
14816
|
+
dirname,
|
|
14817
|
+
ci,
|
|
14818
|
+
authHeaders: scm.getAuthHeaders(),
|
|
14819
|
+
downloadUrl
|
|
14820
|
+
});
|
|
14821
|
+
const reportPath = path10.join(dirname, REPORT_DEFAULT_FILE_NAME);
|
|
14822
|
+
switch (scanner) {
|
|
14823
|
+
case "snyk":
|
|
14824
|
+
await getSnykReport(reportPath, repositoryRoot, { skipPrompts });
|
|
14825
|
+
break;
|
|
14826
|
+
case "checkmarx":
|
|
14827
|
+
if (!cxProjectName) {
|
|
14828
|
+
throw new Error("cxProjectName is required for checkmarx scanner");
|
|
14829
|
+
}
|
|
14830
|
+
await getCheckmarxReport(
|
|
14831
|
+
{
|
|
14832
|
+
reportPath,
|
|
14833
|
+
repositoryRoot,
|
|
14834
|
+
branch: reference,
|
|
14835
|
+
projectName: cxProjectName
|
|
14836
|
+
},
|
|
14837
|
+
{ skipPrompts }
|
|
14838
|
+
);
|
|
14839
|
+
break;
|
|
14840
|
+
}
|
|
14841
|
+
return reportPath;
|
|
14878
14842
|
}
|
|
14879
|
-
async function
|
|
14880
|
-
const {
|
|
14843
|
+
async function _scan(params, { skipPrompts = false } = {}) {
|
|
14844
|
+
const {
|
|
14845
|
+
dirname,
|
|
14846
|
+
repo,
|
|
14847
|
+
scanFile,
|
|
14848
|
+
apiKey,
|
|
14849
|
+
ci,
|
|
14850
|
+
srcPath,
|
|
14851
|
+
commitHash,
|
|
14852
|
+
ref,
|
|
14853
|
+
experimentalEnabled,
|
|
14854
|
+
scanner,
|
|
14855
|
+
cxProjectName,
|
|
14856
|
+
mobbProjectName,
|
|
14857
|
+
githubToken: githubActionToken,
|
|
14858
|
+
command,
|
|
14859
|
+
organizationId: userOrganizationId,
|
|
14860
|
+
autoPr,
|
|
14861
|
+
createOnePr,
|
|
14862
|
+
commitDirectly,
|
|
14863
|
+
pullRequest,
|
|
14864
|
+
polling
|
|
14865
|
+
} = params;
|
|
14866
|
+
debug21("start %s %s", dirname, repo);
|
|
14867
|
+
const { createSpinner: createSpinner5 } = Spinner2({ ci });
|
|
14868
|
+
skipPrompts = skipPrompts || ci;
|
|
14881
14869
|
const gqlClient = await getAuthenticatedGQLClient({
|
|
14882
14870
|
inputApiKey: apiKey,
|
|
14883
|
-
isSkipPrompts:
|
|
14871
|
+
isSkipPrompts: skipPrompts
|
|
14884
14872
|
});
|
|
14885
|
-
if (!
|
|
14886
|
-
throw new
|
|
14873
|
+
if (!mobbProjectName) {
|
|
14874
|
+
throw new Error("mobbProjectName is required");
|
|
14887
14875
|
}
|
|
14888
|
-
const
|
|
14889
|
-
|
|
14890
|
-
|
|
14891
|
-
token,
|
|
14892
|
-
org: organization,
|
|
14893
|
-
refreshToken
|
|
14876
|
+
const { projectId, organizationId } = await gqlClient.getLastOrgAndNamedProject({
|
|
14877
|
+
projectName: mobbProjectName,
|
|
14878
|
+
userDefinedOrganizationId: userOrganizationId
|
|
14894
14879
|
});
|
|
14895
|
-
|
|
14896
|
-
|
|
14897
|
-
|
|
14898
|
-
|
|
14899
|
-
|
|
14900
|
-
throw new CliError("Invalid SCM credentials. Please try again.");
|
|
14901
|
-
} else if (resp.updateScmToken?.__typename === "ScmAccessTokenUpdateSuccess") {
|
|
14902
|
-
console.log("Token added successfully");
|
|
14903
|
-
} else {
|
|
14904
|
-
throw new CliError("Unexpected error, failed to add token");
|
|
14880
|
+
const {
|
|
14881
|
+
uploadS3BucketInfo: { repoUploadInfo, reportUploadInfo }
|
|
14882
|
+
} = await gqlClient.uploadS3BucketInfo();
|
|
14883
|
+
if (!reportUploadInfo || !repoUploadInfo) {
|
|
14884
|
+
throw new Error("uploadS3BucketInfo is null");
|
|
14905
14885
|
}
|
|
14906
|
-
|
|
14907
|
-
|
|
14908
|
-
|
|
14909
|
-
!ci && await showWelcomeMessage(skipPrompts);
|
|
14910
|
-
const selectedScanner = scanner || await choseScanner();
|
|
14911
|
-
if (selectedScanner !== SCANNERS.Checkmarx && selectedScanner !== SCANNERS.Snyk) {
|
|
14912
|
-
throw new CliError(
|
|
14913
|
-
"Vulnerability scanning via Bugsy is available only with Snyk and Checkmarx at the moment. Additional scanners will follow soon."
|
|
14914
|
-
);
|
|
14886
|
+
let reportPath = scanFile;
|
|
14887
|
+
if (srcPath) {
|
|
14888
|
+
return await uploadExistingRepo();
|
|
14915
14889
|
}
|
|
14916
|
-
|
|
14917
|
-
|
|
14918
|
-
throw new CliError(errorMessages.missingCxProjectName);
|
|
14890
|
+
if (!repo) {
|
|
14891
|
+
throw new Error("repo is required in case srcPath is not provided");
|
|
14919
14892
|
}
|
|
14920
|
-
await
|
|
14921
|
-
|
|
14922
|
-
|
|
14923
|
-
);
|
|
14924
|
-
}
|
|
14925
|
-
|
|
14926
|
-
console.log(mobbAscii);
|
|
14927
|
-
const welcome = chalkAnimation.rainbow("\n Welcome to Bugsy\n");
|
|
14928
|
-
skipPrompts ? await sleep(100) : await sleep(2e3);
|
|
14929
|
-
welcome.stop();
|
|
14930
|
-
}
|
|
14931
|
-
var VERDICT_COLORS = {
|
|
14932
|
-
BENIGN: "green",
|
|
14933
|
-
WARNING: "yellow",
|
|
14934
|
-
SUSPICIOUS: "magenta",
|
|
14935
|
-
MALICIOUS: "red"
|
|
14936
|
-
};
|
|
14937
|
-
var SEVERITY_COLORS = {
|
|
14938
|
-
CRITICAL: "red",
|
|
14939
|
-
HIGH: "magenta",
|
|
14940
|
-
MEDIUM: "yellow",
|
|
14941
|
-
LOW: "cyan"
|
|
14942
|
-
};
|
|
14943
|
-
async function scanSkill(options) {
|
|
14944
|
-
const { url, apiKey, ci } = options;
|
|
14945
|
-
const gqlClient = await getAuthenticatedGQLClient({
|
|
14946
|
-
inputApiKey: apiKey,
|
|
14947
|
-
isSkipPrompts: ci
|
|
14893
|
+
const tokenInfo = await getScmTokenInfo({ gqlClient, repo });
|
|
14894
|
+
const validateRes = await gqlClient.validateRepoUrl({ repoUrl: repo });
|
|
14895
|
+
const isRepoAvailable = validateRes.validateRepoUrl?.__typename === "RepoValidationSuccess";
|
|
14896
|
+
const cloudScmLibType = getCloudScmLibTypeFromUrl(repo);
|
|
14897
|
+
const { authUrl: scmAuthUrl } = _getUrlForScmType({
|
|
14898
|
+
scmLibType: cloudScmLibType
|
|
14948
14899
|
});
|
|
14949
|
-
|
|
14950
|
-
|
|
14951
|
-
|
|
14952
|
-
|
|
14953
|
-
|
|
14954
|
-
|
|
14955
|
-
|
|
14956
|
-
|
|
14957
|
-
|
|
14958
|
-
|
|
14959
|
-
|
|
14960
|
-
|
|
14961
|
-
|
|
14962
|
-
|
|
14963
|
-
}
|
|
14964
|
-
console.log(`Hash: ${scan2.skillHash ?? "N/A"}`);
|
|
14965
|
-
console.log(`Findings: ${scan2.findingsCount}`);
|
|
14966
|
-
console.log(`Duration: ${scan2.scanDurationMs}ms`);
|
|
14967
|
-
if (scan2.cached) {
|
|
14968
|
-
console.log(chalk7.dim("(cached result)"));
|
|
14969
|
-
}
|
|
14970
|
-
console.log();
|
|
14971
|
-
if (scan2.findings.length > 0) {
|
|
14972
|
-
console.log(chalk7.bold("Findings:"));
|
|
14973
|
-
console.log();
|
|
14974
|
-
for (const f of scan2.findings) {
|
|
14975
|
-
const sevColor = SEVERITY_COLORS[f.severity] ?? "white";
|
|
14976
|
-
const location = [f.filePath, f.lineNumber].filter(Boolean).join(":");
|
|
14977
|
-
console.log(
|
|
14978
|
-
` ${chalk7[sevColor](f.severity)} [${f.layer}] ${f.category}${f.ruleId ? ` (${f.ruleId})` : ""}`
|
|
14979
|
-
);
|
|
14980
|
-
if (location) {
|
|
14981
|
-
console.log(` ${chalk7.dim(location)}`);
|
|
14982
|
-
}
|
|
14983
|
-
console.log(` ${f.explanation}`);
|
|
14984
|
-
if (f.evidence) {
|
|
14985
|
-
console.log(
|
|
14986
|
-
` ${String(chalk7.dim("Evidence: " + f.evidence.slice(0, 120))).replace(/\n|\r/g, "")}`
|
|
14900
|
+
if (!isRepoAvailable) {
|
|
14901
|
+
if (ci || !cloudScmLibType || !scmAuthUrl) {
|
|
14902
|
+
const errorMessage = scmAuthUrl ? `Cannot access repo ${repo}. Make sure that the repo is accessible and the SCM token configured on Mobb is correct.` : `Cannot access repo ${repo} with the provided token, please visit ${scmAuthUrl} to refresh your source control management system token`;
|
|
14903
|
+
throw new Error(errorMessage);
|
|
14904
|
+
}
|
|
14905
|
+
if (cloudScmLibType && scmAuthUrl) {
|
|
14906
|
+
await handleScmIntegration(tokenInfo.accessToken, scmAuthUrl, repo);
|
|
14907
|
+
const repoValidationResponse = await gqlClient.validateRepoUrl({
|
|
14908
|
+
repoUrl: repo
|
|
14909
|
+
});
|
|
14910
|
+
const isRepoAvailable2 = repoValidationResponse.validateRepoUrl?.__typename === "RepoValidationSuccess";
|
|
14911
|
+
if (!isRepoAvailable2) {
|
|
14912
|
+
throw new Error(
|
|
14913
|
+
`Cannot access repo ${repo} with the provided credentials: ${repoValidationResponse.validateRepoUrl?.__typename}`
|
|
14987
14914
|
);
|
|
14988
14915
|
}
|
|
14989
|
-
console.log();
|
|
14990
14916
|
}
|
|
14991
14917
|
}
|
|
14992
|
-
|
|
14993
|
-
|
|
14994
|
-
|
|
14995
|
-
|
|
14996
|
-
|
|
14997
|
-
if (scan2.verdict === "MALICIOUS" || scan2.verdict === "SUSPICIOUS") {
|
|
14998
|
-
process.exit(2);
|
|
14999
|
-
}
|
|
15000
|
-
}
|
|
15001
|
-
|
|
15002
|
-
// src/args/validation.ts
|
|
15003
|
-
import chalk8 from "chalk";
|
|
15004
|
-
import path11 from "path";
|
|
15005
|
-
import { z as z31 } from "zod";
|
|
15006
|
-
function throwRepoUrlErrorMessage({
|
|
15007
|
-
error,
|
|
15008
|
-
repoUrl,
|
|
15009
|
-
command
|
|
15010
|
-
}) {
|
|
15011
|
-
const errorMessage = error.issues[error.issues.length - 1]?.message;
|
|
15012
|
-
const formattedErrorMessage = `
|
|
15013
|
-
Error: ${chalk8.bold(
|
|
15014
|
-
repoUrl
|
|
15015
|
-
)} is ${errorMessage}
|
|
15016
|
-
Example:
|
|
15017
|
-
mobbdev ${command} -r ${chalk8.bold(
|
|
15018
|
-
"https://github.com/WebGoat/WebGoat"
|
|
15019
|
-
)}`;
|
|
15020
|
-
throw new CliError(formattedErrorMessage);
|
|
15021
|
-
}
|
|
15022
|
-
var UrlZ = z31.string({
|
|
15023
|
-
invalid_type_error: `is not a valid ${Object.values(ScmType).join("/ ")} URL`
|
|
15024
|
-
});
|
|
15025
|
-
function validateOrganizationId(organizationId) {
|
|
15026
|
-
const orgIdValidation = z31.string().uuid().nullish().safeParse(organizationId);
|
|
15027
|
-
if (!orgIdValidation.success) {
|
|
15028
|
-
throw new CliError(`organizationId: ${organizationId} is not a valid UUID`);
|
|
15029
|
-
}
|
|
15030
|
-
}
|
|
15031
|
-
function validateRepoUrl(args) {
|
|
15032
|
-
const repoSafeParseResult = UrlZ.safeParse(args.repo);
|
|
15033
|
-
const { success } = repoSafeParseResult;
|
|
15034
|
-
const [command] = args._;
|
|
15035
|
-
if (!command) {
|
|
15036
|
-
throw new CliError("Command not found");
|
|
14918
|
+
const revalidateRes = await gqlClient.validateRepoUrl({ repoUrl: repo });
|
|
14919
|
+
if (revalidateRes.validateRepoUrl?.__typename !== "RepoValidationSuccess") {
|
|
14920
|
+
throw new Error(
|
|
14921
|
+
`could not reach repo ${repo}: ${revalidateRes.validateRepoUrl?.__typename}`
|
|
14922
|
+
);
|
|
15037
14923
|
}
|
|
15038
|
-
|
|
15039
|
-
|
|
15040
|
-
|
|
15041
|
-
|
|
15042
|
-
|
|
15043
|
-
|
|
14924
|
+
const reference = ref ?? revalidateRes.validateRepoUrl.defaultBranch;
|
|
14925
|
+
const getReferenceDataRes = await gqlClient.getReferenceData({
|
|
14926
|
+
reference,
|
|
14927
|
+
repoUrl: repo
|
|
14928
|
+
});
|
|
14929
|
+
if (getReferenceDataRes.gitReference?.__typename !== "GitReferenceData") {
|
|
14930
|
+
throw new Error(
|
|
14931
|
+
`Could not get reference data for ${reference}: ${getReferenceDataRes.gitReference?.__typename}`
|
|
14932
|
+
);
|
|
15044
14933
|
}
|
|
15045
|
-
}
|
|
15046
|
-
|
|
15047
|
-
|
|
15048
|
-
if (
|
|
15049
|
-
|
|
15050
|
-
|
|
15051
|
-
|
|
15052
|
-
|
|
15053
|
-
|
|
15054
|
-
|
|
15055
|
-
|
|
15056
|
-
|
|
14934
|
+
const { sha } = getReferenceDataRes.gitReference;
|
|
14935
|
+
debug21("project id %s", projectId);
|
|
14936
|
+
debug21("default branch %s", reference);
|
|
14937
|
+
if (command === "scan") {
|
|
14938
|
+
reportPath = await getReport(
|
|
14939
|
+
{
|
|
14940
|
+
scanner: SupportedScannersZ.parse(scanner),
|
|
14941
|
+
repoUrl: repo,
|
|
14942
|
+
sha,
|
|
14943
|
+
gqlClient,
|
|
14944
|
+
cxProjectName,
|
|
14945
|
+
dirname,
|
|
14946
|
+
reference,
|
|
14947
|
+
ci
|
|
14948
|
+
},
|
|
14949
|
+
{ skipPrompts }
|
|
15057
14950
|
);
|
|
15058
14951
|
}
|
|
15059
|
-
|
|
15060
|
-
|
|
15061
|
-
|
|
15062
|
-
|
|
15063
|
-
|
|
15064
|
-
|
|
15065
|
-
|
|
15066
|
-
|
|
15067
|
-
|
|
15068
|
-
|
|
15069
|
-
|
|
15070
|
-
|
|
15071
|
-
|
|
15072
|
-
|
|
15073
|
-
),
|
|
15074
|
-
type: "string"
|
|
15075
|
-
}).option("ref", refOption).option("ch", {
|
|
15076
|
-
alias: "commit-hash",
|
|
15077
|
-
describe: chalk9.bold("Hash of the commit"),
|
|
15078
|
-
type: "string"
|
|
15079
|
-
}).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", {
|
|
15080
|
-
alias: ["pr", "pr-number", "pr-id"],
|
|
15081
|
-
describe: chalk9.bold("Number of the pull request"),
|
|
15082
|
-
type: "number",
|
|
15083
|
-
demandOption: false
|
|
15084
|
-
}).option("polling", pollingOption).example(
|
|
15085
|
-
"npx mobbdev@latest analyze -r https://github.com/WebGoat/WebGoat -f <your_vulnerability_report_path>",
|
|
15086
|
-
"analyze an existing repository"
|
|
15087
|
-
).help();
|
|
15088
|
-
}
|
|
15089
|
-
function validateAnalyzeOptions(argv) {
|
|
15090
|
-
if (argv.f && !fs12.existsSync(argv.f)) {
|
|
15091
|
-
throw new CliError(`
|
|
15092
|
-
Can't access ${chalk9.bold(argv.f)}`);
|
|
14952
|
+
const shouldScan = !reportPath;
|
|
14953
|
+
const uploadReportSpinner = createSpinner5("\u{1F4C1} Uploading Report").start();
|
|
14954
|
+
try {
|
|
14955
|
+
if (reportPath) {
|
|
14956
|
+
await uploadFile({
|
|
14957
|
+
file: reportPath,
|
|
14958
|
+
url: reportUploadInfo.url,
|
|
14959
|
+
uploadFields: JSON.parse(reportUploadInfo.uploadFieldsJSON),
|
|
14960
|
+
uploadKey: reportUploadInfo.uploadKey
|
|
14961
|
+
});
|
|
14962
|
+
}
|
|
14963
|
+
} catch (e) {
|
|
14964
|
+
uploadReportSpinner.error({ text: "\u{1F4C1} Report upload failed" });
|
|
14965
|
+
throw e;
|
|
15093
14966
|
}
|
|
15094
|
-
|
|
15095
|
-
|
|
15096
|
-
|
|
14967
|
+
await _digestReport({
|
|
14968
|
+
gqlClient,
|
|
14969
|
+
fixReportId: reportUploadInfo.fixReportId,
|
|
14970
|
+
projectId,
|
|
14971
|
+
command,
|
|
14972
|
+
ci,
|
|
14973
|
+
repoUrl: repo,
|
|
14974
|
+
sha,
|
|
14975
|
+
reference,
|
|
14976
|
+
shouldScan,
|
|
14977
|
+
polling
|
|
14978
|
+
});
|
|
14979
|
+
uploadReportSpinner.success({ text: "\u{1F4C1} Report uploaded successfully" });
|
|
14980
|
+
const mobbSpinner = createSpinner5("\u{1F575}\uFE0F\u200D\u2642\uFE0F Initiating Mobb analysis").start();
|
|
14981
|
+
const sendReportRes = await sendReport({
|
|
14982
|
+
gqlClient,
|
|
14983
|
+
spinner: mobbSpinner,
|
|
14984
|
+
submitVulnerabilityReportVariables: {
|
|
14985
|
+
fixReportId: reportUploadInfo.fixReportId,
|
|
14986
|
+
repoUrl: z31.string().parse(repo),
|
|
14987
|
+
reference,
|
|
14988
|
+
projectId,
|
|
14989
|
+
vulnerabilityReportFileName: shouldScan ? void 0 : REPORT_DEFAULT_FILE_NAME,
|
|
14990
|
+
sha,
|
|
14991
|
+
experimentalEnabled: !!experimentalEnabled,
|
|
14992
|
+
pullRequest: params.pullRequest,
|
|
14993
|
+
scanSource: _getScanSource(command, ci),
|
|
14994
|
+
scanContext: ScanContext.BUGSY
|
|
14995
|
+
},
|
|
14996
|
+
polling
|
|
14997
|
+
});
|
|
14998
|
+
if (sendReportRes.submitVulnerabilityReport.__typename !== "VulnerabilityReport") {
|
|
14999
|
+
mobbSpinner.error({ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed" });
|
|
15000
|
+
throw new Error("\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed");
|
|
15097
15001
|
}
|
|
15098
|
-
|
|
15099
|
-
|
|
15002
|
+
mobbSpinner.success({
|
|
15003
|
+
text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Generating fixes..."
|
|
15004
|
+
});
|
|
15005
|
+
if (autoPr) {
|
|
15006
|
+
await handleAutoPr({
|
|
15007
|
+
gqlClient,
|
|
15008
|
+
analysisId: reportUploadInfo.fixReportId,
|
|
15009
|
+
commitDirectly,
|
|
15010
|
+
prId: pullRequest,
|
|
15011
|
+
createSpinner: createSpinner5,
|
|
15012
|
+
createOnePr,
|
|
15013
|
+
polling
|
|
15014
|
+
});
|
|
15100
15015
|
}
|
|
15101
|
-
|
|
15102
|
-
|
|
15016
|
+
await askToOpenAnalysis();
|
|
15017
|
+
if (command === "review") {
|
|
15018
|
+
await waitForAnaysisAndReviewPr({
|
|
15019
|
+
repo,
|
|
15020
|
+
githubActionToken,
|
|
15021
|
+
analysisId: reportUploadInfo.fixReportId,
|
|
15022
|
+
scanner,
|
|
15023
|
+
gqlClient,
|
|
15024
|
+
polling
|
|
15025
|
+
});
|
|
15103
15026
|
}
|
|
15104
|
-
|
|
15105
|
-
|
|
15106
|
-
|
|
15027
|
+
return reportUploadInfo.fixReportId;
|
|
15028
|
+
async function askToOpenAnalysis() {
|
|
15029
|
+
if (!repoUploadInfo || !reportUploadInfo) {
|
|
15030
|
+
throw new Error("uploadS3BucketInfo is null");
|
|
15031
|
+
}
|
|
15032
|
+
const reportUrl = getReportUrl({
|
|
15033
|
+
organizationId,
|
|
15034
|
+
projectId,
|
|
15035
|
+
fixReportId: reportUploadInfo.fixReportId
|
|
15036
|
+
});
|
|
15037
|
+
!ci && console.log("You can access the analysis at: \n");
|
|
15038
|
+
console.log(chalk7.bold(reportUrl));
|
|
15039
|
+
!skipPrompts && await mobbAnalysisPrompt();
|
|
15040
|
+
!ci && open3(reportUrl);
|
|
15041
|
+
!ci && console.log(
|
|
15042
|
+
chalk7.bgBlue("\n\n My work here is done for now, see you soon! \u{1F575}\uFE0F\u200D\u2642\uFE0F ")
|
|
15107
15043
|
);
|
|
15108
15044
|
}
|
|
15109
|
-
|
|
15110
|
-
|
|
15111
|
-
|
|
15045
|
+
async function handleScmIntegration(oldToken, scmAuthUrl2, repoUrl) {
|
|
15046
|
+
const scmLibType = getCloudScmLibTypeFromUrl(repoUrl);
|
|
15047
|
+
const scmName = scmLibType === "GITHUB" /* GITHUB */ ? "Github" : scmLibType === "GITLAB" /* GITLAB */ ? "Gitlab" : scmLibType === "ADO" /* ADO */ ? "Azure DevOps" : "";
|
|
15048
|
+
const addScmIntegration = skipPrompts ? true : await scmIntegrationPrompt(scmName);
|
|
15049
|
+
const scmSpinner = createSpinner5(
|
|
15050
|
+
`\u{1F517} Waiting for ${scmName} integration...`
|
|
15051
|
+
).start();
|
|
15052
|
+
if (!addScmIntegration) {
|
|
15053
|
+
scmSpinner.error();
|
|
15054
|
+
throw Error(`Could not reach ${scmName} repo`);
|
|
15055
|
+
}
|
|
15056
|
+
console.log(
|
|
15057
|
+
`If the page does not open automatically, kindly access it through ${scmAuthUrl2}.`
|
|
15112
15058
|
);
|
|
15059
|
+
await open3(scmAuthUrl2);
|
|
15060
|
+
for (let i = 0; i < LOGIN_MAX_WAIT2 / LOGIN_CHECK_DELAY2; i++) {
|
|
15061
|
+
const userInfo2 = await gqlClient.getUserInfo();
|
|
15062
|
+
if (!userInfo2) {
|
|
15063
|
+
throw new CliError2("User info not found");
|
|
15064
|
+
}
|
|
15065
|
+
const scmConfigs = getFromArraySafe(userInfo2.scmConfigs);
|
|
15066
|
+
const tokenInfo2 = getScmConfig({
|
|
15067
|
+
url: repoUrl,
|
|
15068
|
+
scmConfigs,
|
|
15069
|
+
brokerHosts: getBrokerHosts(
|
|
15070
|
+
userInfo2.userOrganizationsAndUserOrganizationRoles
|
|
15071
|
+
),
|
|
15072
|
+
includeOrgTokens: false
|
|
15073
|
+
});
|
|
15074
|
+
if (tokenInfo2.accessToken && tokenInfo2.accessToken !== oldToken) {
|
|
15075
|
+
scmSpinner.success({ text: `\u{1F517} ${scmName} integration successful!` });
|
|
15076
|
+
return tokenInfo2.accessToken;
|
|
15077
|
+
}
|
|
15078
|
+
scmSpinner.spin();
|
|
15079
|
+
await sleep(LOGIN_CHECK_DELAY2);
|
|
15080
|
+
}
|
|
15081
|
+
scmSpinner.error({
|
|
15082
|
+
text: `${scmName} login timeout error`
|
|
15083
|
+
});
|
|
15084
|
+
throw new CliError2(`${scmName} login timeout`);
|
|
15113
15085
|
}
|
|
15114
|
-
|
|
15115
|
-
|
|
15116
|
-
|
|
15117
|
-
|
|
15086
|
+
async function uploadExistingRepo() {
|
|
15087
|
+
if (!repoUploadInfo || !reportUploadInfo) {
|
|
15088
|
+
throw new Error("uploadS3BucketInfo is null");
|
|
15089
|
+
}
|
|
15090
|
+
if (!srcPath) {
|
|
15091
|
+
throw new Error("src path is required");
|
|
15092
|
+
}
|
|
15093
|
+
const shouldScan2 = !reportPath;
|
|
15094
|
+
if (reportPath) {
|
|
15095
|
+
const uploadReportSpinner2 = createSpinner5("\u{1F4C1} Uploading Report").start();
|
|
15096
|
+
try {
|
|
15097
|
+
await uploadFile({
|
|
15098
|
+
file: reportPath,
|
|
15099
|
+
url: reportUploadInfo.url,
|
|
15100
|
+
uploadFields: JSON.parse(reportUploadInfo.uploadFieldsJSON),
|
|
15101
|
+
uploadKey: reportUploadInfo.uploadKey
|
|
15102
|
+
});
|
|
15103
|
+
} catch (e) {
|
|
15104
|
+
uploadReportSpinner2.error({ text: "\u{1F4C1} Report upload failed" });
|
|
15105
|
+
throw e;
|
|
15106
|
+
}
|
|
15107
|
+
uploadReportSpinner2.success({
|
|
15108
|
+
text: "\u{1F4C1} Uploading Report successful!"
|
|
15109
|
+
});
|
|
15110
|
+
}
|
|
15111
|
+
let gitInfo2 = { success: false };
|
|
15112
|
+
if (reportPath) {
|
|
15113
|
+
const vulnFiles = await _digestReport({
|
|
15114
|
+
gqlClient,
|
|
15115
|
+
fixReportId: reportUploadInfo.fixReportId,
|
|
15116
|
+
projectId,
|
|
15117
|
+
command,
|
|
15118
|
+
ci,
|
|
15119
|
+
shouldScan: shouldScan2,
|
|
15120
|
+
polling
|
|
15121
|
+
});
|
|
15122
|
+
const res = await _zipAndUploadRepo({
|
|
15123
|
+
srcPath,
|
|
15124
|
+
vulnFiles,
|
|
15125
|
+
repoUploadInfo,
|
|
15126
|
+
isIncludeAllFiles: false
|
|
15127
|
+
});
|
|
15128
|
+
gitInfo2 = res.gitInfo;
|
|
15129
|
+
} else {
|
|
15130
|
+
const res = await _zipAndUploadRepo({
|
|
15131
|
+
srcPath,
|
|
15132
|
+
vulnFiles: [],
|
|
15133
|
+
repoUploadInfo,
|
|
15134
|
+
isIncludeAllFiles: true
|
|
15135
|
+
});
|
|
15136
|
+
gitInfo2 = res.gitInfo;
|
|
15137
|
+
await _digestReport({
|
|
15138
|
+
gqlClient,
|
|
15139
|
+
fixReportId: reportUploadInfo.fixReportId,
|
|
15140
|
+
projectId,
|
|
15141
|
+
command,
|
|
15142
|
+
ci,
|
|
15143
|
+
shouldScan: shouldScan2,
|
|
15144
|
+
polling
|
|
15145
|
+
});
|
|
15146
|
+
}
|
|
15147
|
+
const mobbSpinner2 = createSpinner5("\u{1F575}\uFE0F\u200D\u2642\uFE0F Initiating Mobb analysis").start();
|
|
15148
|
+
try {
|
|
15149
|
+
await sendReport({
|
|
15150
|
+
gqlClient,
|
|
15151
|
+
spinner: mobbSpinner2,
|
|
15152
|
+
submitVulnerabilityReportVariables: {
|
|
15153
|
+
fixReportId: reportUploadInfo.fixReportId,
|
|
15154
|
+
projectId,
|
|
15155
|
+
repoUrl: repo || gitInfo2.repoUrl || getTopLevelDirName(srcPath),
|
|
15156
|
+
reference: ref || gitInfo2.reference || "no-branch",
|
|
15157
|
+
sha: commitHash || gitInfo2.hash || "0123456789abcdef",
|
|
15158
|
+
scanSource: _getScanSource(command, ci),
|
|
15159
|
+
pullRequest: params.pullRequest,
|
|
15160
|
+
experimentalEnabled: !!experimentalEnabled,
|
|
15161
|
+
scanContext: ScanContext.BUGSY
|
|
15162
|
+
},
|
|
15163
|
+
polling
|
|
15164
|
+
});
|
|
15165
|
+
if (command === "review") {
|
|
15166
|
+
await waitForAnaysisAndReviewPr({
|
|
15167
|
+
repo,
|
|
15168
|
+
githubActionToken,
|
|
15169
|
+
analysisId: reportUploadInfo.fixReportId,
|
|
15170
|
+
scanner,
|
|
15171
|
+
gqlClient,
|
|
15172
|
+
polling
|
|
15173
|
+
});
|
|
15174
|
+
}
|
|
15175
|
+
} catch (e) {
|
|
15176
|
+
mobbSpinner2.error({ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed" });
|
|
15177
|
+
throw e;
|
|
15178
|
+
}
|
|
15179
|
+
mobbSpinner2.success({
|
|
15180
|
+
text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Generating fixes..."
|
|
15181
|
+
});
|
|
15182
|
+
if (autoPr) {
|
|
15183
|
+
await handleAutoPr({
|
|
15184
|
+
gqlClient,
|
|
15185
|
+
analysisId: reportUploadInfo.fixReportId,
|
|
15186
|
+
commitDirectly,
|
|
15187
|
+
prId: pullRequest,
|
|
15188
|
+
createSpinner: createSpinner5,
|
|
15189
|
+
createOnePr,
|
|
15190
|
+
polling
|
|
15191
|
+
});
|
|
15192
|
+
}
|
|
15193
|
+
await askToOpenAnalysis();
|
|
15194
|
+
return reportUploadInfo.fixReportId;
|
|
15118
15195
|
}
|
|
15119
|
-
|
|
15120
|
-
|
|
15121
|
-
|
|
15122
|
-
|
|
15196
|
+
}
|
|
15197
|
+
async function _zipAndUploadRepo({
|
|
15198
|
+
srcPath,
|
|
15199
|
+
vulnFiles,
|
|
15200
|
+
repoUploadInfo,
|
|
15201
|
+
isIncludeAllFiles
|
|
15202
|
+
}) {
|
|
15203
|
+
const srcFileStatus = await fsPromises3.lstat(srcPath);
|
|
15204
|
+
const zippingSpinner = createSpinner4("\u{1F4E6} Zipping repo").start();
|
|
15205
|
+
let zipBuffer;
|
|
15206
|
+
let gitInfo2 = { success: false };
|
|
15207
|
+
if (srcFileStatus.isFile() && path10.extname(srcPath).toLowerCase() === ".fpr") {
|
|
15208
|
+
zipBuffer = await repackFpr(srcPath);
|
|
15209
|
+
} else {
|
|
15210
|
+
gitInfo2 = await getGitInfo(srcPath);
|
|
15211
|
+
zipBuffer = await pack(srcPath, vulnFiles, isIncludeAllFiles);
|
|
15123
15212
|
}
|
|
15124
|
-
|
|
15125
|
-
|
|
15213
|
+
zippingSpinner.success({ text: "\u{1F4E6} Zipping repo successful!" });
|
|
15214
|
+
const uploadRepoSpinner = createSpinner4("\u{1F4C1} Uploading Repo").start();
|
|
15215
|
+
try {
|
|
15216
|
+
await uploadFile({
|
|
15217
|
+
file: zipBuffer,
|
|
15218
|
+
url: repoUploadInfo.url,
|
|
15219
|
+
uploadFields: JSON.parse(repoUploadInfo.uploadFieldsJSON),
|
|
15220
|
+
uploadKey: repoUploadInfo.uploadKey
|
|
15221
|
+
});
|
|
15222
|
+
} catch (e) {
|
|
15223
|
+
uploadRepoSpinner.error({ text: "\u{1F4C1} Repo upload failed" });
|
|
15224
|
+
throw e;
|
|
15126
15225
|
}
|
|
15226
|
+
uploadRepoSpinner.success({ text: "\u{1F4C1} Uploading Repo successful!" });
|
|
15227
|
+
return { gitInfo: gitInfo2 };
|
|
15127
15228
|
}
|
|
15128
|
-
async function
|
|
15129
|
-
|
|
15130
|
-
|
|
15131
|
-
|
|
15132
|
-
|
|
15133
|
-
|
|
15134
|
-
|
|
15135
|
-
|
|
15136
|
-
|
|
15137
|
-
|
|
15138
|
-
|
|
15139
|
-
|
|
15140
|
-
|
|
15141
|
-
|
|
15142
|
-
|
|
15143
|
-
init_client_generates();
|
|
15144
|
-
init_GitService();
|
|
15145
|
-
init_urlParser2();
|
|
15146
|
-
|
|
15147
|
-
// src/utils/computerName.ts
|
|
15148
|
-
import { execSync } from "child_process";
|
|
15149
|
-
import os2 from "os";
|
|
15150
|
-
var STABLE_COMPUTER_NAME_CONFIG_KEY = "stableComputerName";
|
|
15151
|
-
var HOSTNAME_SUFFIXES = [
|
|
15152
|
-
// Cloud providers (must be first - most specific)
|
|
15153
|
-
".ec2.internal",
|
|
15154
|
-
".compute.internal",
|
|
15155
|
-
".cloudapp.net",
|
|
15156
|
-
// mDNS/Bonjour
|
|
15157
|
-
".local",
|
|
15158
|
-
".localhost",
|
|
15159
|
-
".localdomain",
|
|
15160
|
-
// Home networks
|
|
15161
|
-
".lan",
|
|
15162
|
-
".home",
|
|
15163
|
-
".homelan",
|
|
15164
|
-
// Corporate networks
|
|
15165
|
-
".corp",
|
|
15166
|
-
".internal",
|
|
15167
|
-
".intranet",
|
|
15168
|
-
".domain",
|
|
15169
|
-
".work",
|
|
15170
|
-
".office",
|
|
15171
|
-
// Container environments
|
|
15172
|
-
".docker",
|
|
15173
|
-
".kubernetes",
|
|
15174
|
-
".k8s"
|
|
15175
|
-
];
|
|
15176
|
-
function getOsSpecificComputerName() {
|
|
15177
|
-
const platform2 = os2.platform();
|
|
15229
|
+
async function _digestReport({
|
|
15230
|
+
gqlClient,
|
|
15231
|
+
fixReportId,
|
|
15232
|
+
projectId,
|
|
15233
|
+
command,
|
|
15234
|
+
ci,
|
|
15235
|
+
repoUrl,
|
|
15236
|
+
sha,
|
|
15237
|
+
reference,
|
|
15238
|
+
shouldScan,
|
|
15239
|
+
polling
|
|
15240
|
+
}) {
|
|
15241
|
+
const digestSpinner = createSpinner4(
|
|
15242
|
+
progressMassages.processingVulnerabilityReport
|
|
15243
|
+
).start();
|
|
15178
15244
|
try {
|
|
15179
|
-
|
|
15180
|
-
|
|
15181
|
-
|
|
15182
|
-
|
|
15245
|
+
const { vulnerabilityReportId } = await gqlClient.digestVulnerabilityReport(
|
|
15246
|
+
{
|
|
15247
|
+
fixReportId,
|
|
15248
|
+
projectId,
|
|
15249
|
+
scanSource: _getScanSource(command, ci),
|
|
15250
|
+
repoUrl,
|
|
15251
|
+
sha,
|
|
15252
|
+
reference,
|
|
15253
|
+
shouldScan
|
|
15254
|
+
}
|
|
15255
|
+
);
|
|
15256
|
+
const callbackStates = [
|
|
15257
|
+
"Digested" /* Digested */,
|
|
15258
|
+
"Finished" /* Finished */
|
|
15259
|
+
];
|
|
15260
|
+
const callback = (_analysisId) => digestSpinner.update({
|
|
15261
|
+
text: progressMassages.processingVulnerabilityReportSuccess
|
|
15262
|
+
});
|
|
15263
|
+
if (polling) {
|
|
15264
|
+
debug21(
|
|
15265
|
+
"[_digestReport] Using POLLING mode for analysis state updates (--polling flag enabled)"
|
|
15266
|
+
);
|
|
15267
|
+
console.log(
|
|
15268
|
+
chalk7.cyan(
|
|
15269
|
+
"\u{1F504} [Polling Mode] Using HTTP polling instead of WebSocket for status updates"
|
|
15270
|
+
)
|
|
15271
|
+
);
|
|
15272
|
+
await gqlClient.pollForAnalysisState({
|
|
15273
|
+
analysisId: fixReportId,
|
|
15274
|
+
callback,
|
|
15275
|
+
callbackStates,
|
|
15276
|
+
timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
|
|
15183
15277
|
});
|
|
15184
|
-
return result.trim();
|
|
15185
|
-
} else if (platform2 === "win32") {
|
|
15186
|
-
return process.env["COMPUTERNAME"] || null;
|
|
15187
15278
|
} else {
|
|
15188
|
-
|
|
15189
|
-
|
|
15190
|
-
|
|
15191
|
-
|
|
15192
|
-
|
|
15193
|
-
|
|
15194
|
-
|
|
15195
|
-
|
|
15196
|
-
|
|
15197
|
-
|
|
15198
|
-
|
|
15199
|
-
|
|
15279
|
+
debug21(
|
|
15280
|
+
"[_digestReport] Using WEBSOCKET mode for analysis state updates (default)"
|
|
15281
|
+
);
|
|
15282
|
+
console.log(
|
|
15283
|
+
chalk7.cyan(
|
|
15284
|
+
"\u{1F50C} [WebSocket Mode] Using WebSocket subscription for status updates"
|
|
15285
|
+
)
|
|
15286
|
+
);
|
|
15287
|
+
await gqlClient.subscribeToAnalysis({
|
|
15288
|
+
subscribeToAnalysisParams: {
|
|
15289
|
+
analysisId: fixReportId
|
|
15290
|
+
},
|
|
15291
|
+
callback,
|
|
15292
|
+
callbackStates,
|
|
15293
|
+
timeoutInMs: VUL_REPORT_DIGEST_TIMEOUT_MS
|
|
15200
15294
|
});
|
|
15201
|
-
return result.trim();
|
|
15202
15295
|
}
|
|
15203
|
-
|
|
15204
|
-
|
|
15296
|
+
const vulnFiles = await gqlClient.getVulnerabilityReportPaths(
|
|
15297
|
+
vulnerabilityReportId
|
|
15298
|
+
);
|
|
15299
|
+
digestSpinner.success({
|
|
15300
|
+
text: progressMassages.processingVulnerabilityReportSuccess
|
|
15301
|
+
});
|
|
15302
|
+
return vulnFiles;
|
|
15303
|
+
} catch (e) {
|
|
15304
|
+
const errorMessage = e instanceof ReportDigestError ? e.getDisplayMessage() : ReportDigestError.defaultMessage;
|
|
15305
|
+
digestSpinner.error({
|
|
15306
|
+
text: errorMessage
|
|
15307
|
+
});
|
|
15308
|
+
throw e;
|
|
15309
|
+
}
|
|
15310
|
+
}
|
|
15311
|
+
async function waitForAnaysisAndReviewPr({
|
|
15312
|
+
repo,
|
|
15313
|
+
githubActionToken,
|
|
15314
|
+
analysisId,
|
|
15315
|
+
scanner,
|
|
15316
|
+
gqlClient,
|
|
15317
|
+
polling
|
|
15318
|
+
}) {
|
|
15319
|
+
const params = z31.object({
|
|
15320
|
+
repo: z31.string().url(),
|
|
15321
|
+
githubActionToken: z31.string()
|
|
15322
|
+
}).parse({ repo, githubActionToken });
|
|
15323
|
+
const scm = await createScmLib(
|
|
15324
|
+
{
|
|
15325
|
+
url: params.repo,
|
|
15326
|
+
accessToken: params.githubActionToken,
|
|
15327
|
+
scmOrg: "",
|
|
15328
|
+
scmType: "GITHUB" /* GITHUB */
|
|
15329
|
+
},
|
|
15330
|
+
{
|
|
15331
|
+
propagateExceptions: true
|
|
15332
|
+
}
|
|
15333
|
+
);
|
|
15334
|
+
const callback = (analysisId2) => {
|
|
15335
|
+
return addFixCommentsForPr({
|
|
15336
|
+
analysisId: analysisId2,
|
|
15337
|
+
gqlClient,
|
|
15338
|
+
scm,
|
|
15339
|
+
scanner: z31.nativeEnum(SCANNERS).parse(scanner)
|
|
15340
|
+
});
|
|
15341
|
+
};
|
|
15342
|
+
if (polling) {
|
|
15343
|
+
debug21(
|
|
15344
|
+
"[waitForAnaysisAndReviewPr] Using POLLING mode for analysis state updates"
|
|
15345
|
+
);
|
|
15346
|
+
console.log(
|
|
15347
|
+
chalk7.cyan(
|
|
15348
|
+
"\u{1F504} [Polling Mode] Waiting for analysis completion using HTTP polling"
|
|
15349
|
+
)
|
|
15350
|
+
);
|
|
15351
|
+
await gqlClient.pollForAnalysisState({
|
|
15352
|
+
analysisId,
|
|
15353
|
+
callback,
|
|
15354
|
+
callbackStates: ["Finished" /* Finished */]
|
|
15355
|
+
});
|
|
15356
|
+
} else {
|
|
15357
|
+
debug21(
|
|
15358
|
+
"[waitForAnaysisAndReviewPr] Using WEBSOCKET mode for analysis state updates"
|
|
15359
|
+
);
|
|
15360
|
+
console.log(
|
|
15361
|
+
chalk7.cyan(
|
|
15362
|
+
"\u{1F50C} [WebSocket Mode] Waiting for analysis completion using WebSocket"
|
|
15363
|
+
)
|
|
15364
|
+
);
|
|
15365
|
+
await gqlClient.subscribeToAnalysis({
|
|
15366
|
+
subscribeToAnalysisParams: {
|
|
15367
|
+
analysisId
|
|
15368
|
+
},
|
|
15369
|
+
callback,
|
|
15370
|
+
callbackStates: ["Finished" /* Finished */]
|
|
15371
|
+
});
|
|
15205
15372
|
}
|
|
15206
15373
|
}
|
|
15207
|
-
|
|
15208
|
-
|
|
15209
|
-
|
|
15210
|
-
|
|
15211
|
-
|
|
15212
|
-
|
|
15213
|
-
|
|
15214
|
-
|
|
15374
|
+
|
|
15375
|
+
// src/commands/scan_skill_input.ts
|
|
15376
|
+
import fs11 from "fs";
|
|
15377
|
+
import path11 from "path";
|
|
15378
|
+
import AdmZip2 from "adm-zip";
|
|
15379
|
+
var LOCAL_SKILL_ZIP_DATA_URL_PREFIX = "data:application/zip;base64,";
|
|
15380
|
+
var MAX_LOCAL_SKILL_ARCHIVE_BYTES = 2 * 1024 * 1024;
|
|
15381
|
+
async function resolveSkillScanInput(skillInput) {
|
|
15382
|
+
const resolvedPath = path11.resolve(skillInput);
|
|
15383
|
+
if (!fs11.existsSync(resolvedPath)) {
|
|
15384
|
+
return skillInput;
|
|
15215
15385
|
}
|
|
15216
|
-
|
|
15386
|
+
const stat = fs11.statSync(resolvedPath);
|
|
15387
|
+
if (!stat.isDirectory()) {
|
|
15388
|
+
throw new CliError(
|
|
15389
|
+
"Local skill input must be a directory containing SKILL.md"
|
|
15390
|
+
);
|
|
15391
|
+
}
|
|
15392
|
+
return packageLocalSkillDirectory(resolvedPath);
|
|
15217
15393
|
}
|
|
15218
|
-
function
|
|
15219
|
-
const
|
|
15220
|
-
|
|
15221
|
-
|
|
15394
|
+
function packageLocalSkillDirectory(directoryPath) {
|
|
15395
|
+
const zip = new AdmZip2();
|
|
15396
|
+
const rootPath = path11.resolve(directoryPath);
|
|
15397
|
+
const filePaths = listDirectoryFiles(rootPath);
|
|
15398
|
+
let hasSkillMd = false;
|
|
15399
|
+
for (const relativePath of filePaths) {
|
|
15400
|
+
const fullPath = path11.join(rootPath, relativePath);
|
|
15401
|
+
const normalizedFullPath = path11.resolve(fullPath);
|
|
15402
|
+
if (normalizedFullPath !== rootPath && !normalizedFullPath.startsWith(rootPath + path11.sep)) {
|
|
15403
|
+
continue;
|
|
15404
|
+
}
|
|
15405
|
+
const fileContent = fs11.readFileSync(normalizedFullPath);
|
|
15406
|
+
zip.addFile(relativePath, fileContent);
|
|
15407
|
+
if (path11.basename(relativePath).toLowerCase() === "skill.md") {
|
|
15408
|
+
hasSkillMd = true;
|
|
15409
|
+
}
|
|
15222
15410
|
}
|
|
15223
|
-
|
|
15224
|
-
|
|
15411
|
+
if (!hasSkillMd) {
|
|
15412
|
+
throw new CliError("Local skill directory must contain a SKILL.md file");
|
|
15413
|
+
}
|
|
15414
|
+
const zipBuffer = zip.toBuffer();
|
|
15415
|
+
if (zipBuffer.length > MAX_LOCAL_SKILL_ARCHIVE_BYTES) {
|
|
15416
|
+
throw new CliError(
|
|
15417
|
+
`Local skill directory is too large to scan via CLI (${zipBuffer.length} bytes > ${MAX_LOCAL_SKILL_ARCHIVE_BYTES} bytes)`
|
|
15418
|
+
);
|
|
15419
|
+
}
|
|
15420
|
+
return `${LOCAL_SKILL_ZIP_DATA_URL_PREFIX}${zipBuffer.toString("base64")}`;
|
|
15225
15421
|
}
|
|
15226
|
-
function
|
|
15227
|
-
const
|
|
15228
|
-
|
|
15229
|
-
|
|
15422
|
+
function listDirectoryFiles(rootPath) {
|
|
15423
|
+
const files = [];
|
|
15424
|
+
function walk(relativeDir) {
|
|
15425
|
+
const absoluteDir = path11.join(rootPath, relativeDir);
|
|
15426
|
+
const entries = fs11.readdirSync(absoluteDir, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
|
|
15427
|
+
for (const entry of entries) {
|
|
15428
|
+
const safeEntryName = path11.basename(entry.name).replace(/\0/g, "");
|
|
15429
|
+
if (!safeEntryName) {
|
|
15430
|
+
continue;
|
|
15431
|
+
}
|
|
15432
|
+
const relativePath = relativeDir ? path11.posix.join(relativeDir, safeEntryName) : safeEntryName;
|
|
15433
|
+
const absolutePath = path11.join(rootPath, relativePath);
|
|
15434
|
+
const normalizedAbsolutePath = path11.resolve(absolutePath);
|
|
15435
|
+
if (normalizedAbsolutePath !== rootPath && !normalizedAbsolutePath.startsWith(rootPath + path11.sep)) {
|
|
15436
|
+
continue;
|
|
15437
|
+
}
|
|
15438
|
+
if (entry.isSymbolicLink()) {
|
|
15439
|
+
continue;
|
|
15440
|
+
}
|
|
15441
|
+
if (entry.isDirectory()) {
|
|
15442
|
+
walk(relativePath);
|
|
15443
|
+
continue;
|
|
15444
|
+
}
|
|
15445
|
+
if (entry.isFile()) {
|
|
15446
|
+
files.push(relativePath);
|
|
15447
|
+
}
|
|
15448
|
+
}
|
|
15230
15449
|
}
|
|
15231
|
-
|
|
15232
|
-
|
|
15233
|
-
return currentName;
|
|
15450
|
+
walk("");
|
|
15451
|
+
return files;
|
|
15234
15452
|
}
|
|
15235
15453
|
|
|
15236
|
-
// src/
|
|
15237
|
-
|
|
15238
|
-
|
|
15239
|
-
|
|
15240
|
-
|
|
15241
|
-
|
|
15242
|
-
|
|
15243
|
-
|
|
15244
|
-
|
|
15245
|
-
|
|
15246
|
-
|
|
15247
|
-
|
|
15248
|
-
|
|
15249
|
-
|
|
15250
|
-
|
|
15251
|
-
|
|
15252
|
-
|
|
15253
|
-
|
|
15254
|
-
|
|
15255
|
-
|
|
15256
|
-
|
|
15257
|
-
|
|
15258
|
-
|
|
15259
|
-
|
|
15260
|
-
|
|
15261
|
-
|
|
15262
|
-
|
|
15263
|
-
|
|
15264
|
-
|
|
15265
|
-
|
|
15266
|
-
|
|
15267
|
-
|
|
15268
|
-
|
|
15269
|
-
|
|
15270
|
-
|
|
15271
|
-
|
|
15272
|
-
|
|
15273
|
-
|
|
15274
|
-
|
|
15275
|
-
|
|
15276
|
-
|
|
15277
|
-
|
|
15278
|
-
|
|
15279
|
-
|
|
15280
|
-
|
|
15281
|
-
|
|
15282
|
-
|
|
15283
|
-
|
|
15284
|
-
|
|
15285
|
-
|
|
15286
|
-
|
|
15287
|
-
|
|
15288
|
-
|
|
15289
|
-
|
|
15290
|
-
|
|
15291
|
-
|
|
15292
|
-
|
|
15293
|
-
|
|
15294
|
-
|
|
15295
|
-
|
|
15296
|
-
|
|
15297
|
-
|
|
15298
|
-
|
|
15299
|
-
|
|
15300
|
-
|
|
15301
|
-
|
|
15302
|
-
|
|
15303
|
-
|
|
15304
|
-
|
|
15305
|
-
|
|
15306
|
-
|
|
15307
|
-
|
|
15308
|
-
|
|
15309
|
-
|
|
15310
|
-
|
|
15311
|
-
|
|
15312
|
-
|
|
15313
|
-
|
|
15314
|
-
|
|
15315
|
-
|
|
15316
|
-
|
|
15317
|
-
|
|
15318
|
-
|
|
15319
|
-
|
|
15320
|
-
|
|
15321
|
-
|
|
15322
|
-
|
|
15323
|
-
|
|
15324
|
-
|
|
15325
|
-
if (
|
|
15326
|
-
|
|
15454
|
+
// src/commands/index.ts
|
|
15455
|
+
async function review(params, { skipPrompts = true } = {}) {
|
|
15456
|
+
const {
|
|
15457
|
+
repo,
|
|
15458
|
+
f: scanFile,
|
|
15459
|
+
ref,
|
|
15460
|
+
apiKey,
|
|
15461
|
+
commitHash,
|
|
15462
|
+
mobbProjectName,
|
|
15463
|
+
pullRequest,
|
|
15464
|
+
githubToken,
|
|
15465
|
+
scanner,
|
|
15466
|
+
srcPath,
|
|
15467
|
+
polling
|
|
15468
|
+
} = params;
|
|
15469
|
+
await runAnalysis(
|
|
15470
|
+
{
|
|
15471
|
+
repo,
|
|
15472
|
+
scanFile,
|
|
15473
|
+
ref,
|
|
15474
|
+
apiKey,
|
|
15475
|
+
ci: true,
|
|
15476
|
+
commitHash,
|
|
15477
|
+
experimentalEnabled: false,
|
|
15478
|
+
mobbProjectName,
|
|
15479
|
+
pullRequest,
|
|
15480
|
+
githubToken,
|
|
15481
|
+
scanner,
|
|
15482
|
+
command: "review",
|
|
15483
|
+
srcPath,
|
|
15484
|
+
polling
|
|
15485
|
+
},
|
|
15486
|
+
{ skipPrompts }
|
|
15487
|
+
);
|
|
15488
|
+
}
|
|
15489
|
+
async function analyze({
|
|
15490
|
+
repo,
|
|
15491
|
+
f: scanFile,
|
|
15492
|
+
ref,
|
|
15493
|
+
apiKey,
|
|
15494
|
+
ci,
|
|
15495
|
+
commitHash,
|
|
15496
|
+
srcPath,
|
|
15497
|
+
mobbProjectName,
|
|
15498
|
+
organizationId,
|
|
15499
|
+
autoPr,
|
|
15500
|
+
createOnePr,
|
|
15501
|
+
commitDirectly,
|
|
15502
|
+
pullRequest,
|
|
15503
|
+
polling
|
|
15504
|
+
}, { skipPrompts = false } = {}) {
|
|
15505
|
+
!ci && await showWelcomeMessage(skipPrompts);
|
|
15506
|
+
await runAnalysis(
|
|
15507
|
+
{
|
|
15508
|
+
repo,
|
|
15509
|
+
scanFile,
|
|
15510
|
+
ref,
|
|
15511
|
+
apiKey,
|
|
15512
|
+
ci,
|
|
15513
|
+
commitHash,
|
|
15514
|
+
mobbProjectName,
|
|
15515
|
+
srcPath,
|
|
15516
|
+
organizationId,
|
|
15517
|
+
command: "analyze",
|
|
15518
|
+
autoPr,
|
|
15519
|
+
commitDirectly,
|
|
15520
|
+
pullRequest,
|
|
15521
|
+
createOnePr,
|
|
15522
|
+
polling
|
|
15523
|
+
},
|
|
15524
|
+
{ skipPrompts }
|
|
15525
|
+
);
|
|
15526
|
+
}
|
|
15527
|
+
async function addScmToken(addScmTokenOptions) {
|
|
15528
|
+
const { apiKey, token, organization, scmType, url, refreshToken, ci } = addScmTokenOptions;
|
|
15529
|
+
const gqlClient = await getAuthenticatedGQLClient({
|
|
15530
|
+
inputApiKey: apiKey,
|
|
15531
|
+
isSkipPrompts: ci
|
|
15532
|
+
});
|
|
15533
|
+
if (!scmType) {
|
|
15534
|
+
throw new CliError(errorMessages.invalidScmType);
|
|
15535
|
+
}
|
|
15536
|
+
const resp = await gqlClient.updateScmToken({
|
|
15537
|
+
scmType,
|
|
15538
|
+
url,
|
|
15539
|
+
token,
|
|
15540
|
+
org: organization,
|
|
15541
|
+
refreshToken
|
|
15542
|
+
});
|
|
15543
|
+
if (resp.updateScmToken?.__typename === "RepoUnreachableError") {
|
|
15544
|
+
throw new CliError(
|
|
15545
|
+
"Mobb could not reach the repository. Please try again. If Mobb is connected through a broker, please make sure the broker is connected."
|
|
15546
|
+
);
|
|
15547
|
+
} else if (resp.updateScmToken?.__typename === "BadScmCredentials") {
|
|
15548
|
+
throw new CliError("Invalid SCM credentials. Please try again.");
|
|
15549
|
+
} else if (resp.updateScmToken?.__typename === "ScmAccessTokenUpdateSuccess") {
|
|
15550
|
+
console.log("Token added successfully");
|
|
15551
|
+
} else {
|
|
15552
|
+
throw new CliError("Unexpected error, failed to add token");
|
|
15327
15553
|
}
|
|
15328
|
-
return str.slice(0, showStart) + "*".repeat(str.length - showStart - showEnd) + str.slice(-showEnd);
|
|
15329
15554
|
}
|
|
15330
|
-
async function
|
|
15331
|
-
const
|
|
15332
|
-
|
|
15333
|
-
|
|
15334
|
-
|
|
15335
|
-
|
|
15336
|
-
|
|
15337
|
-
|
|
15338
|
-
|
|
15339
|
-
|
|
15340
|
-
|
|
15341
|
-
|
|
15342
|
-
|
|
15343
|
-
|
|
15344
|
-
|
|
15345
|
-
|
|
15346
|
-
|
|
15347
|
-
|
|
15348
|
-
|
|
15349
|
-
|
|
15555
|
+
async function scan(scanOptions, { skipPrompts = false } = {}) {
|
|
15556
|
+
const { scanner, ci } = scanOptions;
|
|
15557
|
+
!ci && await showWelcomeMessage(skipPrompts);
|
|
15558
|
+
const selectedScanner = scanner || await choseScanner();
|
|
15559
|
+
if (selectedScanner !== SCANNERS.Checkmarx && selectedScanner !== SCANNERS.Snyk) {
|
|
15560
|
+
throw new CliError(
|
|
15561
|
+
"Vulnerability scanning via Bugsy is available only with Snyk and Checkmarx at the moment. Additional scanners will follow soon."
|
|
15562
|
+
);
|
|
15563
|
+
}
|
|
15564
|
+
selectedScanner === SCANNERS.Checkmarx && validateCheckmarxInstallation();
|
|
15565
|
+
if (selectedScanner === SCANNERS.Checkmarx && !scanOptions.cxProjectName) {
|
|
15566
|
+
throw new CliError(errorMessages.missingCxProjectName);
|
|
15567
|
+
}
|
|
15568
|
+
await runAnalysis(
|
|
15569
|
+
{ ...scanOptions, scanner: selectedScanner, command: "scan" },
|
|
15570
|
+
{ skipPrompts }
|
|
15571
|
+
);
|
|
15572
|
+
}
|
|
15573
|
+
async function showWelcomeMessage(skipPrompts = false) {
|
|
15574
|
+
console.log(mobbAscii);
|
|
15575
|
+
const welcome = chalkAnimation.rainbow("\n Welcome to Bugsy\n");
|
|
15576
|
+
skipPrompts ? await sleep(100) : await sleep(2e3);
|
|
15577
|
+
welcome.stop();
|
|
15578
|
+
}
|
|
15579
|
+
var VERDICT_COLORS = {
|
|
15580
|
+
BENIGN: "green",
|
|
15581
|
+
WARNING: "yellow",
|
|
15582
|
+
SUSPICIOUS: "magenta",
|
|
15583
|
+
MALICIOUS: "red"
|
|
15584
|
+
};
|
|
15585
|
+
var SEVERITY_COLORS = {
|
|
15586
|
+
CRITICAL: "red",
|
|
15587
|
+
HIGH: "magenta",
|
|
15588
|
+
MEDIUM: "yellow",
|
|
15589
|
+
LOW: "cyan"
|
|
15590
|
+
};
|
|
15591
|
+
async function scanSkill(options) {
|
|
15592
|
+
const { url, apiKey, ci } = options;
|
|
15593
|
+
const gqlClient = await getAuthenticatedGQLClient({
|
|
15594
|
+
inputApiKey: apiKey,
|
|
15595
|
+
isSkipPrompts: ci
|
|
15596
|
+
});
|
|
15597
|
+
console.log(chalk8.dim(`Scanning skill: ${url}`));
|
|
15598
|
+
console.log();
|
|
15599
|
+
const skillUrl = await resolveSkillScanInput(url);
|
|
15600
|
+
const result = await gqlClient.scanSkill({ skillUrl });
|
|
15601
|
+
const scan2 = result.scanSkill;
|
|
15602
|
+
const verdictColor = VERDICT_COLORS[scan2.verdict] ?? "white";
|
|
15603
|
+
console.log(
|
|
15604
|
+
chalk8.bold(`Verdict: `) + chalk8[verdictColor](
|
|
15605
|
+
scan2.verdict
|
|
15606
|
+
)
|
|
15607
|
+
);
|
|
15608
|
+
console.log(`Skill: ${scan2.skillName}`);
|
|
15609
|
+
if (scan2.skillVersion) {
|
|
15610
|
+
console.log(`Version: ${scan2.skillVersion}`);
|
|
15611
|
+
}
|
|
15612
|
+
console.log(`Hash: ${scan2.skillHash ?? "N/A"}`);
|
|
15613
|
+
console.log(`Findings: ${scan2.findingsCount}`);
|
|
15614
|
+
console.log(`Duration: ${scan2.scanDurationMs}ms`);
|
|
15615
|
+
if (scan2.cached) {
|
|
15616
|
+
console.log(chalk8.dim("(cached result)"));
|
|
15617
|
+
}
|
|
15618
|
+
console.log();
|
|
15619
|
+
if (scan2.findings.length > 0) {
|
|
15620
|
+
console.log(chalk8.bold("Findings:"));
|
|
15621
|
+
console.log();
|
|
15622
|
+
for (const f of scan2.findings) {
|
|
15623
|
+
const sevColor = SEVERITY_COLORS[f.severity] ?? "white";
|
|
15624
|
+
const location = [f.filePath, f.lineNumber].filter(Boolean).join(":");
|
|
15625
|
+
console.log(
|
|
15626
|
+
` ${chalk8[sevColor](f.severity)} [${f.layer}] ${f.category}${f.ruleId ? ` (${f.ruleId})` : ""}`
|
|
15627
|
+
);
|
|
15628
|
+
if (location) {
|
|
15629
|
+
console.log(` ${chalk8.dim(location)}`);
|
|
15350
15630
|
}
|
|
15351
|
-
}
|
|
15352
|
-
|
|
15353
|
-
|
|
15354
|
-
|
|
15355
|
-
|
|
15356
|
-
return sanitizeString(data);
|
|
15357
|
-
} else if (Array.isArray(data)) {
|
|
15358
|
-
return Promise.all(data.map((item) => sanitizeRecursive(item)));
|
|
15359
|
-
} else if (data instanceof Error) {
|
|
15360
|
-
return data;
|
|
15361
|
-
} else if (data instanceof Date) {
|
|
15362
|
-
return data;
|
|
15363
|
-
} else if (typeof data === "object" && data !== null) {
|
|
15364
|
-
const sanitized = {};
|
|
15365
|
-
const record = data;
|
|
15366
|
-
for (const key in record) {
|
|
15367
|
-
if (Object.prototype.hasOwnProperty.call(record, key)) {
|
|
15368
|
-
sanitized[key] = await sanitizeRecursive(record[key]);
|
|
15369
|
-
}
|
|
15631
|
+
console.log(` ${f.explanation}`);
|
|
15632
|
+
if (f.evidence) {
|
|
15633
|
+
console.log(
|
|
15634
|
+
` ${String(chalk8.dim("Evidence: " + f.evidence.slice(0, 120))).replace(/\n|\r/g, "")}`
|
|
15635
|
+
);
|
|
15370
15636
|
}
|
|
15371
|
-
|
|
15637
|
+
console.log();
|
|
15372
15638
|
}
|
|
15373
|
-
|
|
15374
|
-
|
|
15375
|
-
|
|
15376
|
-
|
|
15639
|
+
}
|
|
15640
|
+
if (scan2.summary) {
|
|
15641
|
+
console.log(chalk8.bold("Analysis:"));
|
|
15642
|
+
console.log(` ${scan2.summary}`);
|
|
15643
|
+
console.log();
|
|
15644
|
+
}
|
|
15645
|
+
if (scan2.verdict === "MALICIOUS" || scan2.verdict === "SUSPICIOUS") {
|
|
15646
|
+
process.exit(2);
|
|
15647
|
+
}
|
|
15377
15648
|
}
|
|
15378
15649
|
|
|
15379
|
-
// src/args/
|
|
15380
|
-
|
|
15381
|
-
|
|
15382
|
-
|
|
15383
|
-
|
|
15384
|
-
|
|
15385
|
-
|
|
15386
|
-
|
|
15387
|
-
|
|
15388
|
-
|
|
15389
|
-
|
|
15390
|
-
|
|
15391
|
-
|
|
15392
|
-
|
|
15393
|
-
|
|
15394
|
-
|
|
15395
|
-
|
|
15396
|
-
|
|
15397
|
-
|
|
15398
|
-
|
|
15399
|
-
|
|
15400
|
-
|
|
15401
|
-
"AI_THINKING",
|
|
15402
|
-
"MCP_TOOL_CALL"
|
|
15403
|
-
// MCP (Model Context Protocol) tool invocation
|
|
15404
|
-
]),
|
|
15405
|
-
attachedFiles: z32.array(
|
|
15406
|
-
z32.object({
|
|
15407
|
-
relativePath: z32.string(),
|
|
15408
|
-
startLine: z32.number().optional()
|
|
15409
|
-
})
|
|
15410
|
-
).optional(),
|
|
15411
|
-
tokens: z32.object({
|
|
15412
|
-
inputCount: z32.number(),
|
|
15413
|
-
outputCount: z32.number()
|
|
15414
|
-
}).optional(),
|
|
15415
|
-
text: z32.string().optional(),
|
|
15416
|
-
date: z32.date().optional(),
|
|
15417
|
-
tool: z32.object({
|
|
15418
|
-
name: z32.string(),
|
|
15419
|
-
parameters: z32.string(),
|
|
15420
|
-
result: z32.string(),
|
|
15421
|
-
rawArguments: z32.string().optional(),
|
|
15422
|
-
accepted: z32.boolean().optional(),
|
|
15423
|
-
// MCP-specific fields (only populated for MCP_TOOL_CALL type)
|
|
15424
|
-
mcpServer: z32.string().optional(),
|
|
15425
|
-
// MCP server name (e.g., "datadog", "mobb-mcp")
|
|
15426
|
-
mcpToolName: z32.string().optional()
|
|
15427
|
-
// MCP tool name without prefix (e.g., "scan_and_fix_vulnerabilities")
|
|
15428
|
-
}).optional()
|
|
15650
|
+
// src/args/validation.ts
|
|
15651
|
+
import chalk9 from "chalk";
|
|
15652
|
+
import path12 from "path";
|
|
15653
|
+
import { z as z32 } from "zod";
|
|
15654
|
+
function throwRepoUrlErrorMessage({
|
|
15655
|
+
error,
|
|
15656
|
+
repoUrl,
|
|
15657
|
+
command
|
|
15658
|
+
}) {
|
|
15659
|
+
const errorMessage = error.issues[error.issues.length - 1]?.message;
|
|
15660
|
+
const formattedErrorMessage = `
|
|
15661
|
+
Error: ${chalk9.bold(
|
|
15662
|
+
repoUrl
|
|
15663
|
+
)} is ${errorMessage}
|
|
15664
|
+
Example:
|
|
15665
|
+
mobbdev ${command} -r ${chalk9.bold(
|
|
15666
|
+
"https://github.com/WebGoat/WebGoat"
|
|
15667
|
+
)}`;
|
|
15668
|
+
throw new CliError(formattedErrorMessage);
|
|
15669
|
+
}
|
|
15670
|
+
var UrlZ = z32.string({
|
|
15671
|
+
invalid_type_error: `is not a valid ${Object.values(ScmType).join("/ ")} URL`
|
|
15429
15672
|
});
|
|
15430
|
-
|
|
15431
|
-
|
|
15432
|
-
|
|
15433
|
-
|
|
15434
|
-
const isRepo = await gitService.isGitRepository();
|
|
15435
|
-
if (!isRepo) {
|
|
15436
|
-
return null;
|
|
15437
|
-
}
|
|
15438
|
-
const remoteUrl = await gitService.getRemoteUrl();
|
|
15439
|
-
const parsed = parseScmURL(remoteUrl);
|
|
15440
|
-
return parsed?.scmType === "GitHub" /* GitHub */ || parsed?.scmType === "GitLab" /* GitLab */ ? remoteUrl : null;
|
|
15441
|
-
} catch {
|
|
15442
|
-
return null;
|
|
15673
|
+
function validateOrganizationId(organizationId) {
|
|
15674
|
+
const orgIdValidation = z32.string().uuid().nullish().safeParse(organizationId);
|
|
15675
|
+
if (!orgIdValidation.success) {
|
|
15676
|
+
throw new CliError(`organizationId: ${organizationId} is not a valid UUID`);
|
|
15443
15677
|
}
|
|
15444
15678
|
}
|
|
15445
|
-
function
|
|
15446
|
-
|
|
15447
|
-
|
|
15448
|
-
|
|
15449
|
-
|
|
15450
|
-
|
|
15679
|
+
function validateRepoUrl(args) {
|
|
15680
|
+
const repoSafeParseResult = UrlZ.safeParse(args.repo);
|
|
15681
|
+
const { success } = repoSafeParseResult;
|
|
15682
|
+
const [command] = args._;
|
|
15683
|
+
if (!command) {
|
|
15684
|
+
throw new CliError("Command not found");
|
|
15685
|
+
}
|
|
15686
|
+
if (!success) {
|
|
15687
|
+
throwRepoUrlErrorMessage({
|
|
15688
|
+
error: repoSafeParseResult.error,
|
|
15689
|
+
repoUrl: args.repo,
|
|
15690
|
+
command
|
|
15691
|
+
});
|
|
15451
15692
|
}
|
|
15452
|
-
return {
|
|
15453
|
-
computerName: getStableComputerName(),
|
|
15454
|
-
userName
|
|
15455
|
-
};
|
|
15456
15693
|
}
|
|
15457
|
-
|
|
15458
|
-
|
|
15459
|
-
|
|
15460
|
-
|
|
15461
|
-
|
|
15462
|
-
|
|
15463
|
-
|
|
15464
|
-
|
|
15465
|
-
|
|
15466
|
-
|
|
15467
|
-
|
|
15468
|
-
|
|
15469
|
-
|
|
15470
|
-
|
|
15694
|
+
var supportExtensions = [".json", ".xml", ".fpr", ".sarif", ".zip"];
|
|
15695
|
+
function validateReportFileFormat(reportFile) {
|
|
15696
|
+
if (!supportExtensions.includes(path12.extname(reportFile))) {
|
|
15697
|
+
throw new CliError(
|
|
15698
|
+
`
|
|
15699
|
+
${chalk9.bold(
|
|
15700
|
+
reportFile
|
|
15701
|
+
)} is not a supported file extension. Supported extensions are: ${chalk9.bold(
|
|
15702
|
+
supportExtensions.join(", ")
|
|
15703
|
+
)}
|
|
15704
|
+
`
|
|
15705
|
+
);
|
|
15706
|
+
}
|
|
15707
|
+
}
|
|
15708
|
+
|
|
15709
|
+
// src/args/commands/analyze.ts
|
|
15710
|
+
function analyzeBuilder(yargs2) {
|
|
15711
|
+
return yargs2.option("f", {
|
|
15712
|
+
alias: "scan-file",
|
|
15471
15713
|
type: "string",
|
|
15472
|
-
array: true,
|
|
15473
15714
|
describe: chalk10.bold(
|
|
15474
|
-
"
|
|
15715
|
+
"Select the vulnerability report to analyze (Checkmarx, Snyk, Fortify, CodeQL, Sonarqube, Semgrep, Datadog)"
|
|
15475
15716
|
)
|
|
15476
|
-
}).option("
|
|
15477
|
-
|
|
15478
|
-
array: true,
|
|
15479
|
-
describe: chalk10.bold("AI model name(s) (optional, one per session)")
|
|
15480
|
-
}).option("tool-name", {
|
|
15481
|
-
type: "string",
|
|
15482
|
-
array: true,
|
|
15483
|
-
describe: chalk10.bold("Tool/IDE name(s) (optional, one per session)")
|
|
15484
|
-
}).option("blame-type", {
|
|
15485
|
-
type: "string",
|
|
15486
|
-
array: true,
|
|
15487
|
-
choices: Object.values(AiBlameInferenceType),
|
|
15717
|
+
}).option("repo", repoOption).option("p", {
|
|
15718
|
+
alias: "src-path",
|
|
15488
15719
|
describe: chalk10.bold(
|
|
15489
|
-
"
|
|
15490
|
-
)
|
|
15491
|
-
|
|
15492
|
-
}
|
|
15493
|
-
|
|
15494
|
-
|
|
15495
|
-
|
|
15496
|
-
|
|
15497
|
-
|
|
15498
|
-
|
|
15499
|
-
|
|
15500
|
-
|
|
15501
|
-
|
|
15502
|
-
|
|
15503
|
-
|
|
15504
|
-
|
|
15505
|
-
let promptsUUID;
|
|
15506
|
-
let inferenceUUID;
|
|
15507
|
-
await withFile(async (promptFile) => {
|
|
15508
|
-
const promptsResult = await sanitizeDataWithCounts(args.prompts);
|
|
15509
|
-
promptsCounts = promptsResult.counts;
|
|
15510
|
-
promptsUUID = path12.basename(promptFile.path, path12.extname(promptFile.path));
|
|
15511
|
-
await fsPromises3.writeFile(
|
|
15512
|
-
promptFile.path,
|
|
15513
|
-
JSON.stringify(promptsResult.sanitizedData, null, 2),
|
|
15514
|
-
"utf-8"
|
|
15515
|
-
);
|
|
15516
|
-
uploadArgs.prompt.push(promptFile.path);
|
|
15517
|
-
await withFile(async (inferenceFile) => {
|
|
15518
|
-
const inferenceResult = await sanitizeDataWithCounts(args.inference);
|
|
15519
|
-
inferenceCounts = inferenceResult.counts;
|
|
15520
|
-
inferenceUUID = path12.basename(
|
|
15521
|
-
inferenceFile.path,
|
|
15522
|
-
path12.extname(inferenceFile.path)
|
|
15523
|
-
);
|
|
15524
|
-
await fsPromises3.writeFile(
|
|
15525
|
-
inferenceFile.path,
|
|
15526
|
-
inferenceResult.sanitizedData,
|
|
15527
|
-
"utf-8"
|
|
15528
|
-
);
|
|
15529
|
-
uploadArgs.inference.push(inferenceFile.path);
|
|
15530
|
-
uploadArgs.model.push(args.model);
|
|
15531
|
-
uploadArgs.toolName.push(args.tool);
|
|
15532
|
-
uploadArgs.aiResponseAt.push(args.responseTime);
|
|
15533
|
-
uploadArgs.blameType.push(args.blameType || "CHAT" /* Chat */);
|
|
15534
|
-
if (args.sessionId) {
|
|
15535
|
-
uploadArgs.sessionId.push(args.sessionId);
|
|
15536
|
-
}
|
|
15537
|
-
await uploadAiBlameHandler({
|
|
15538
|
-
args: uploadArgs,
|
|
15539
|
-
exitOnError: false,
|
|
15540
|
-
apiUrl: args.apiUrl,
|
|
15541
|
-
webAppUrl: args.webAppUrl,
|
|
15542
|
-
repositoryUrl: args.repositoryUrl
|
|
15543
|
-
});
|
|
15544
|
-
});
|
|
15545
|
-
});
|
|
15546
|
-
return {
|
|
15547
|
-
promptsCounts,
|
|
15548
|
-
inferenceCounts,
|
|
15549
|
-
promptsUUID,
|
|
15550
|
-
inferenceUUID
|
|
15551
|
-
};
|
|
15720
|
+
"Path to the repository folder with the source code; alternatively, you can specify the Fortify FPR file to extract source code out of it"
|
|
15721
|
+
),
|
|
15722
|
+
type: "string"
|
|
15723
|
+
}).option("ref", refOption).option("ch", {
|
|
15724
|
+
alias: "commit-hash",
|
|
15725
|
+
describe: chalk10.bold("Hash of the commit"),
|
|
15726
|
+
type: "string"
|
|
15727
|
+
}).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", {
|
|
15728
|
+
alias: ["pr", "pr-number", "pr-id"],
|
|
15729
|
+
describe: chalk10.bold("Number of the pull request"),
|
|
15730
|
+
type: "number",
|
|
15731
|
+
demandOption: false
|
|
15732
|
+
}).option("polling", pollingOption).example(
|
|
15733
|
+
"npx mobbdev@latest analyze -r https://github.com/WebGoat/WebGoat -f <your_vulnerability_report_path>",
|
|
15734
|
+
"analyze an existing repository"
|
|
15735
|
+
).help();
|
|
15552
15736
|
}
|
|
15553
|
-
|
|
15554
|
-
|
|
15555
|
-
|
|
15556
|
-
|
|
15557
|
-
apiUrl,
|
|
15558
|
-
webAppUrl,
|
|
15559
|
-
logger: logger2 = defaultLogger2
|
|
15560
|
-
} = options;
|
|
15561
|
-
const prompts = args.prompt || [];
|
|
15562
|
-
const inferences = args.inference || [];
|
|
15563
|
-
const models = args.model || [];
|
|
15564
|
-
const tools = args.toolName || args["tool-name"] || [];
|
|
15565
|
-
const responseTimes = args.aiResponseAt || args["ai-response-at"] || [];
|
|
15566
|
-
const blameTypes = args.blameType || args["blame-type"] || [];
|
|
15567
|
-
const sessionIds = args.sessionId || args["session-id"] || [];
|
|
15568
|
-
if (prompts.length !== inferences.length) {
|
|
15569
|
-
const errorMsg = "prompt and inference must have the same number of entries";
|
|
15570
|
-
logger2.error(chalk10.red(errorMsg));
|
|
15571
|
-
if (exitOnError) {
|
|
15572
|
-
process.exit(1);
|
|
15573
|
-
}
|
|
15574
|
-
throw new Error(errorMsg);
|
|
15737
|
+
function validateAnalyzeOptions(argv) {
|
|
15738
|
+
if (argv.f && !fs12.existsSync(argv.f)) {
|
|
15739
|
+
throw new CliError(`
|
|
15740
|
+
Can't access ${chalk10.bold(argv.f)}`);
|
|
15575
15741
|
}
|
|
15576
|
-
|
|
15577
|
-
|
|
15578
|
-
|
|
15579
|
-
const sessions = [];
|
|
15580
|
-
for (let i = 0; i < prompts.length; i++) {
|
|
15581
|
-
const promptPath = String(prompts[i]);
|
|
15582
|
-
const inferencePath = String(inferences[i]);
|
|
15583
|
-
try {
|
|
15584
|
-
await Promise.all([
|
|
15585
|
-
fsPromises3.access(promptPath),
|
|
15586
|
-
fsPromises3.access(inferencePath)
|
|
15587
|
-
]);
|
|
15588
|
-
} catch {
|
|
15589
|
-
const errorMsg = `File not found for session ${i + 1}`;
|
|
15590
|
-
logger2.error(chalk10.red(errorMsg));
|
|
15591
|
-
if (exitOnError) {
|
|
15592
|
-
process.exit(1);
|
|
15593
|
-
}
|
|
15594
|
-
throw new Error(errorMsg);
|
|
15595
|
-
}
|
|
15596
|
-
sessions.push({
|
|
15597
|
-
promptFileName: path12.basename(promptPath),
|
|
15598
|
-
inferenceFileName: path12.basename(inferencePath),
|
|
15599
|
-
aiResponseAt: responseTimes[i] || nowIso,
|
|
15600
|
-
model: models[i],
|
|
15601
|
-
toolName: tools[i],
|
|
15602
|
-
blameType: blameTypes[i] || "CHAT" /* Chat */,
|
|
15603
|
-
computerName,
|
|
15604
|
-
userName,
|
|
15605
|
-
sessionId: sessionIds[i],
|
|
15606
|
-
repositoryUrl
|
|
15607
|
-
});
|
|
15742
|
+
validateOrganizationId(argv.organizationId);
|
|
15743
|
+
if (!argv.srcPath && !argv.repo) {
|
|
15744
|
+
throw new CliError("You must supply either --src-path or --repo");
|
|
15608
15745
|
}
|
|
15609
|
-
|
|
15610
|
-
|
|
15611
|
-
apiUrl,
|
|
15612
|
-
webAppUrl
|
|
15613
|
-
});
|
|
15614
|
-
const initSessions = sessions.map(
|
|
15615
|
-
({ sessionId: _sessionId, ...rest }) => rest
|
|
15616
|
-
);
|
|
15617
|
-
const initRes = await authenticatedClient.uploadAIBlameInferencesInitRaw({
|
|
15618
|
-
sessions: initSessions
|
|
15619
|
-
});
|
|
15620
|
-
const uploadSessions = initRes.uploadAIBlameInferencesInit?.uploadSessions ?? [];
|
|
15621
|
-
if (uploadSessions.length !== sessions.length) {
|
|
15622
|
-
const errorMsg = "Init failed to return expected number of sessions";
|
|
15623
|
-
logger2.error(chalk10.red(errorMsg));
|
|
15624
|
-
if (exitOnError) {
|
|
15625
|
-
process.exit(1);
|
|
15626
|
-
}
|
|
15627
|
-
throw new Error(errorMsg);
|
|
15746
|
+
if (!argv.srcPath && argv.repo) {
|
|
15747
|
+
validateRepoUrl(argv);
|
|
15628
15748
|
}
|
|
15629
|
-
|
|
15630
|
-
|
|
15631
|
-
const promptPath = String(prompts[i]);
|
|
15632
|
-
const inferencePath = String(inferences[i]);
|
|
15633
|
-
await Promise.all([
|
|
15634
|
-
// Prompt
|
|
15635
|
-
uploadFile({
|
|
15636
|
-
file: promptPath,
|
|
15637
|
-
url: us.prompt.url,
|
|
15638
|
-
uploadFields: JSON.parse(us.prompt.uploadFieldsJSON),
|
|
15639
|
-
uploadKey: us.prompt.uploadKey
|
|
15640
|
-
}),
|
|
15641
|
-
// Inference
|
|
15642
|
-
uploadFile({
|
|
15643
|
-
file: inferencePath,
|
|
15644
|
-
url: us.inference.url,
|
|
15645
|
-
uploadFields: JSON.parse(us.inference.uploadFieldsJSON),
|
|
15646
|
-
uploadKey: us.inference.uploadKey
|
|
15647
|
-
})
|
|
15648
|
-
]);
|
|
15749
|
+
if (argv.ci && !argv.apiKey) {
|
|
15750
|
+
throw new CliError("--ci flag requires --api-key to be provided as well");
|
|
15649
15751
|
}
|
|
15650
|
-
|
|
15651
|
-
|
|
15652
|
-
|
|
15653
|
-
aiBlameInferenceId: us.aiBlameInferenceId,
|
|
15654
|
-
promptKey: us.prompt.uploadKey,
|
|
15655
|
-
inferenceKey: us.inference.uploadKey,
|
|
15656
|
-
aiResponseAt: s.aiResponseAt,
|
|
15657
|
-
model: s.model,
|
|
15658
|
-
toolName: s.toolName,
|
|
15659
|
-
blameType: s.blameType,
|
|
15660
|
-
computerName: s.computerName,
|
|
15661
|
-
userName: s.userName,
|
|
15662
|
-
sessionId: s.sessionId,
|
|
15663
|
-
repositoryUrl: s.repositoryUrl
|
|
15664
|
-
};
|
|
15665
|
-
});
|
|
15666
|
-
try {
|
|
15667
|
-
logger2.info(
|
|
15668
|
-
`[UPLOAD] Calling finalizeAIBlameInferencesUploadRaw with ${finalizeSessions.length} sessions`
|
|
15752
|
+
if (argv.commitDirectly && !argv["auto-pr"]) {
|
|
15753
|
+
throw new CliError(
|
|
15754
|
+
"--commit-directly flag requires --auto-pr to be provided as well"
|
|
15669
15755
|
);
|
|
15670
|
-
|
|
15671
|
-
|
|
15672
|
-
|
|
15673
|
-
|
|
15756
|
+
}
|
|
15757
|
+
if (argv["create-one-pr"] && !argv["auto-pr"]) {
|
|
15758
|
+
throw new CliError(
|
|
15759
|
+
"--create-one-pr flag requires --auto-pr to be provided as well"
|
|
15674
15760
|
);
|
|
15675
|
-
|
|
15676
|
-
|
|
15677
|
-
|
|
15678
|
-
|
|
15679
|
-
|
|
15680
|
-
|
|
15681
|
-
|
|
15682
|
-
|
|
15683
|
-
|
|
15684
|
-
|
|
15685
|
-
|
|
15686
|
-
|
|
15687
|
-
|
|
15688
|
-
}
|
|
15689
|
-
logger2.info(chalk10.green("[UPLOAD] AI Blame uploads finalized successfully"));
|
|
15690
|
-
} catch (error) {
|
|
15691
|
-
logger2.error("[UPLOAD] Finalize threw error:", error);
|
|
15692
|
-
throw error;
|
|
15761
|
+
}
|
|
15762
|
+
if (argv["create-one-pr"] && argv.commitDirectly) {
|
|
15763
|
+
throw new CliError(
|
|
15764
|
+
"--create-one-pr and --commit-directly cannot be provided at the same time"
|
|
15765
|
+
);
|
|
15766
|
+
}
|
|
15767
|
+
if (argv.pullRequest && !argv.commitDirectly) {
|
|
15768
|
+
throw new CliError(
|
|
15769
|
+
"--pull-request flag requires --commit-directly to be provided as well"
|
|
15770
|
+
);
|
|
15771
|
+
}
|
|
15772
|
+
if (argv.f) {
|
|
15773
|
+
validateReportFileFormat(argv.f);
|
|
15693
15774
|
}
|
|
15694
15775
|
}
|
|
15695
|
-
async function
|
|
15696
|
-
|
|
15776
|
+
async function analyzeHandler(args) {
|
|
15777
|
+
validateAnalyzeOptions(args);
|
|
15778
|
+
await analyze(args, { skipPrompts: args.yes });
|
|
15697
15779
|
}
|
|
15698
15780
|
|
|
15699
15781
|
// src/features/claude_code/data_collector.ts
|
|
15782
|
+
import { z as z33 } from "zod";
|
|
15700
15783
|
init_client_generates();
|
|
15701
15784
|
init_GitService();
|
|
15702
15785
|
init_urlParser2();
|
|
@@ -24636,7 +24719,7 @@ async function processChat(client, cascadeId) {
|
|
|
24636
24719
|
let repositoryUrl;
|
|
24637
24720
|
if (repoOrigin) {
|
|
24638
24721
|
const parsed = parseScmURL(repoOrigin);
|
|
24639
|
-
if (parsed?.scmType === "GitHub" /* GitHub */) {
|
|
24722
|
+
if (parsed?.scmType === "GitHub" /* GitHub */ || parsed?.scmType === "GitLab" /* GitLab */) {
|
|
24640
24723
|
repositoryUrl = parsed.canonicalUrl;
|
|
24641
24724
|
}
|
|
24642
24725
|
}
|
|
@@ -24937,13 +25020,13 @@ var parseArgs = async (args) => {
|
|
|
24937
25020
|
};
|
|
24938
25021
|
|
|
24939
25022
|
// src/index.ts
|
|
24940
|
-
var
|
|
25023
|
+
var debug22 = Debug22("mobbdev:index");
|
|
24941
25024
|
async function run() {
|
|
24942
25025
|
return parseArgs(hideBin(process.argv));
|
|
24943
25026
|
}
|
|
24944
25027
|
(async () => {
|
|
24945
25028
|
try {
|
|
24946
|
-
|
|
25029
|
+
debug22("Bugsy CLI v%s running...", packageJson.version);
|
|
24947
25030
|
await run();
|
|
24948
25031
|
process.exit(0);
|
|
24949
25032
|
} catch (err) {
|