mobbdev 0.0.62 → 0.0.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +1865 -1177
- package/package.json +7 -1
package/dist/index.mjs
CHANGED
|
@@ -12,8 +12,15 @@ var __publicField = (obj, key, value) => {
|
|
|
12
12
|
// src/index.ts
|
|
13
13
|
import { hideBin } from "yargs/helpers";
|
|
14
14
|
|
|
15
|
+
// src/types.ts
|
|
16
|
+
var mobbCliCommand = {
|
|
17
|
+
scan: "scan",
|
|
18
|
+
analyze: "analyze",
|
|
19
|
+
review: "review"
|
|
20
|
+
};
|
|
21
|
+
|
|
15
22
|
// src/args/yargs.ts
|
|
16
|
-
import
|
|
23
|
+
import chalk9 from "chalk";
|
|
17
24
|
import yargs from "yargs/yargs";
|
|
18
25
|
|
|
19
26
|
// src/args/commands/analyze.ts
|
|
@@ -35,6 +42,7 @@ var SCANNERS = {
|
|
|
35
42
|
Fortify: "fortify",
|
|
36
43
|
Snyk: "snyk"
|
|
37
44
|
};
|
|
45
|
+
var SupportedScannersZ = z.enum([SCANNERS.Checkmarx, SCANNERS.Snyk]);
|
|
38
46
|
var envVariablesSchema = z.object({
|
|
39
47
|
WEB_APP_URL: z.string(),
|
|
40
48
|
API_URL: z.string()
|
|
@@ -146,14 +154,16 @@ var CliError = class extends Error {
|
|
|
146
154
|
};
|
|
147
155
|
|
|
148
156
|
// src/features/analysis/index.ts
|
|
157
|
+
import { Octokit as Octokit3 } from "@octokit/core";
|
|
149
158
|
import chalk4 from "chalk";
|
|
150
159
|
import Configstore from "configstore";
|
|
151
|
-
import
|
|
160
|
+
import Debug10 from "debug";
|
|
152
161
|
import extract from "extract-zip";
|
|
153
162
|
import fetch3 from "node-fetch";
|
|
154
163
|
import open2 from "open";
|
|
155
164
|
import semver from "semver";
|
|
156
|
-
import
|
|
165
|
+
import tmp2 from "tmp";
|
|
166
|
+
import { z as z8 } from "zod";
|
|
157
167
|
|
|
158
168
|
// src/features/analysis/git.ts
|
|
159
169
|
import Debug2 from "debug";
|
|
@@ -265,16 +275,22 @@ var SUBMIT_VULNERABILITY_REPORT = gql`
|
|
|
265
275
|
$projectId: String!
|
|
266
276
|
$sha: String
|
|
267
277
|
$vulnerabilityReportFileName: String
|
|
278
|
+
$pullRequest: Int
|
|
268
279
|
) {
|
|
269
280
|
submitVulnerabilityReport(
|
|
270
281
|
fixReportId: $fixReportId
|
|
271
282
|
repoUrl: $repoUrl
|
|
272
283
|
reference: $reference
|
|
273
284
|
sha: $sha
|
|
285
|
+
pullRequest: $pullRequest
|
|
274
286
|
projectId: $projectId
|
|
275
287
|
vulnerabilityReportFileName: $vulnerabilityReportFileName
|
|
276
288
|
) {
|
|
277
289
|
__typename
|
|
290
|
+
... on VulnerabilityReport {
|
|
291
|
+
vulnerabilityReportId
|
|
292
|
+
fixReportId
|
|
293
|
+
}
|
|
278
294
|
}
|
|
279
295
|
}
|
|
280
296
|
`;
|
|
@@ -352,6 +368,136 @@ var GET_VULNERABILITY_REPORT_PATHS = gql2`
|
|
|
352
368
|
}
|
|
353
369
|
}
|
|
354
370
|
`;
|
|
371
|
+
var SUBSCRIBE_TO_ANALYSIS = gql2`
|
|
372
|
+
subscription getAnalysis($analysisId: uuid!) {
|
|
373
|
+
analysis: fixReport_by_pk(id: $analysisId) {
|
|
374
|
+
id
|
|
375
|
+
state
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
`;
|
|
379
|
+
var GET_ANALYSIS = gql2`
|
|
380
|
+
query getAnalsyis($analysisId: uuid!) {
|
|
381
|
+
analysis: fixReport_by_pk(id: $analysisId) {
|
|
382
|
+
id
|
|
383
|
+
state
|
|
384
|
+
repo {
|
|
385
|
+
commitSha
|
|
386
|
+
pullRequest
|
|
387
|
+
}
|
|
388
|
+
fixes {
|
|
389
|
+
id
|
|
390
|
+
issueType
|
|
391
|
+
vulnerabilityReportIssues {
|
|
392
|
+
issueLanguage
|
|
393
|
+
state
|
|
394
|
+
issueType
|
|
395
|
+
vendorIssueId
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
vulnerabilityReport {
|
|
399
|
+
projectId
|
|
400
|
+
project {
|
|
401
|
+
organizationId
|
|
402
|
+
}
|
|
403
|
+
file {
|
|
404
|
+
signedFile {
|
|
405
|
+
url
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
`;
|
|
412
|
+
var GET_FIX = gql2`
|
|
413
|
+
query getFix($fixId: uuid!) {
|
|
414
|
+
fix_by_pk(id: $fixId) {
|
|
415
|
+
patchAndQuestions {
|
|
416
|
+
patch
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
`;
|
|
421
|
+
|
|
422
|
+
// src/features/analysis/graphql/subscirbe.ts
|
|
423
|
+
import { createClient } from "graphql-ws";
|
|
424
|
+
import WebSocket from "ws";
|
|
425
|
+
var SUBSCRIPTION_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
426
|
+
function createWSClient(options) {
|
|
427
|
+
return createClient({
|
|
428
|
+
url: options.url,
|
|
429
|
+
webSocketImpl: options.websocket || WebSocket,
|
|
430
|
+
connectionParams: () => {
|
|
431
|
+
return {
|
|
432
|
+
headers: {
|
|
433
|
+
[API_KEY_HEADER_NAME]: options.apiKey
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
function subscribe(query, variables, callback, wsClientOptions) {
|
|
440
|
+
return new Promise((resolve, reject) => {
|
|
441
|
+
let timer = null;
|
|
442
|
+
const { timeoutInMs = SUBSCRIPTION_TIMEOUT_MS } = wsClientOptions;
|
|
443
|
+
const client = createWSClient({
|
|
444
|
+
...wsClientOptions,
|
|
445
|
+
websocket: WebSocket,
|
|
446
|
+
url: API_URL.replace("http", "ws")
|
|
447
|
+
});
|
|
448
|
+
const unsubscribe = client.subscribe(
|
|
449
|
+
{ query, variables },
|
|
450
|
+
{
|
|
451
|
+
next: (data) => {
|
|
452
|
+
function callbackResolve(data2) {
|
|
453
|
+
unsubscribe();
|
|
454
|
+
if (timer) {
|
|
455
|
+
clearTimeout(timer);
|
|
456
|
+
}
|
|
457
|
+
resolve(data2);
|
|
458
|
+
}
|
|
459
|
+
function callbackReject(data2) {
|
|
460
|
+
unsubscribe();
|
|
461
|
+
if (timer) {
|
|
462
|
+
clearTimeout(timer);
|
|
463
|
+
}
|
|
464
|
+
reject(data2);
|
|
465
|
+
}
|
|
466
|
+
if (!data.data) {
|
|
467
|
+
reject(
|
|
468
|
+
new Error(
|
|
469
|
+
`Broken data object from graphQL subscribe: ${JSON.stringify(
|
|
470
|
+
data
|
|
471
|
+
)} for query: ${query}`
|
|
472
|
+
)
|
|
473
|
+
);
|
|
474
|
+
} else {
|
|
475
|
+
callback(callbackResolve, callbackReject, data.data);
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
error: (error) => {
|
|
479
|
+
if (timer) {
|
|
480
|
+
clearTimeout(timer);
|
|
481
|
+
}
|
|
482
|
+
reject(error);
|
|
483
|
+
},
|
|
484
|
+
complete: () => {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
);
|
|
489
|
+
if (typeof timeoutInMs === "number") {
|
|
490
|
+
timer = setTimeout(() => {
|
|
491
|
+
unsubscribe();
|
|
492
|
+
reject(
|
|
493
|
+
new Error(
|
|
494
|
+
`Timeout expired for graphQL subscribe query: ${query} with timeout: ${timeoutInMs}`
|
|
495
|
+
)
|
|
496
|
+
);
|
|
497
|
+
}, timeoutInMs);
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
}
|
|
355
501
|
|
|
356
502
|
// src/features/analysis/graphql/types.ts
|
|
357
503
|
import { z as z2 } from "zod";
|
|
@@ -421,9 +567,25 @@ var DigestVulnerabilityReportZ = z2.object({
|
|
|
421
567
|
vulnerabilityReportId: z2.string()
|
|
422
568
|
})
|
|
423
569
|
});
|
|
570
|
+
var AnalysisStateZ = z2.enum([
|
|
571
|
+
"Created",
|
|
572
|
+
"Deleted",
|
|
573
|
+
"Digested",
|
|
574
|
+
"Expired",
|
|
575
|
+
"Failed",
|
|
576
|
+
"Finished",
|
|
577
|
+
"Initialized",
|
|
578
|
+
"Requested"
|
|
579
|
+
]);
|
|
424
580
|
var GetFixReportZ = z2.object({
|
|
425
581
|
fixReport_by_pk: z2.object({
|
|
426
|
-
state:
|
|
582
|
+
state: AnalysisStateZ
|
|
583
|
+
})
|
|
584
|
+
});
|
|
585
|
+
var GetFixReportSubscriptionZ = z2.object({
|
|
586
|
+
analysis: z2.object({
|
|
587
|
+
id: z2.string(),
|
|
588
|
+
state: AnalysisStateZ
|
|
427
589
|
})
|
|
428
590
|
});
|
|
429
591
|
var GetVulnerabilityReportPathsZ = z2.object({
|
|
@@ -433,6 +595,55 @@ var GetVulnerabilityReportPathsZ = z2.object({
|
|
|
433
595
|
})
|
|
434
596
|
)
|
|
435
597
|
});
|
|
598
|
+
var CreateUpdateFixReportMutationZ = z2.object({
|
|
599
|
+
submitVulnerabilityReport: z2.object({
|
|
600
|
+
__typename: z2.literal("VulnerabilityReport"),
|
|
601
|
+
vulnerabilityReportId: z2.string(),
|
|
602
|
+
fixReportId: z2.string()
|
|
603
|
+
})
|
|
604
|
+
});
|
|
605
|
+
var GetAnalysisQueryZ = z2.object({
|
|
606
|
+
analysis: z2.object({
|
|
607
|
+
id: z2.string(),
|
|
608
|
+
state: z2.string(),
|
|
609
|
+
repo: z2.object({
|
|
610
|
+
commitSha: z2.string(),
|
|
611
|
+
pullRequest: z2.number()
|
|
612
|
+
}),
|
|
613
|
+
fixes: z2.array(
|
|
614
|
+
z2.object({
|
|
615
|
+
id: z2.string(),
|
|
616
|
+
issueType: z2.string(),
|
|
617
|
+
vulnerabilityReportIssues: z2.array(
|
|
618
|
+
z2.object({
|
|
619
|
+
issueLanguage: z2.string(),
|
|
620
|
+
state: z2.string(),
|
|
621
|
+
issueType: z2.string(),
|
|
622
|
+
vendorIssueId: z2.string()
|
|
623
|
+
})
|
|
624
|
+
)
|
|
625
|
+
})
|
|
626
|
+
),
|
|
627
|
+
vulnerabilityReport: z2.object({
|
|
628
|
+
projectId: z2.string(),
|
|
629
|
+
project: z2.object({
|
|
630
|
+
organizationId: z2.string()
|
|
631
|
+
}),
|
|
632
|
+
file: z2.object({
|
|
633
|
+
signedFile: z2.object({
|
|
634
|
+
url: z2.string()
|
|
635
|
+
})
|
|
636
|
+
})
|
|
637
|
+
})
|
|
638
|
+
})
|
|
639
|
+
});
|
|
640
|
+
var GetFixQueryZ = z2.object({
|
|
641
|
+
fix_by_pk: z2.object({
|
|
642
|
+
patchAndQuestions: z2.object({
|
|
643
|
+
patch: z2.string()
|
|
644
|
+
})
|
|
645
|
+
})
|
|
646
|
+
});
|
|
436
647
|
|
|
437
648
|
// src/features/analysis/graphql/gql.ts
|
|
438
649
|
var debug3 = Debug3("mobbdev:gql");
|
|
@@ -441,7 +652,9 @@ var REPORT_STATE_CHECK_DELAY = 5 * 1e3;
|
|
|
441
652
|
var GQLClient = class {
|
|
442
653
|
constructor(args) {
|
|
443
654
|
__publicField(this, "_client");
|
|
655
|
+
__publicField(this, "_apiKey");
|
|
444
656
|
const { apiKey } = args;
|
|
657
|
+
this._apiKey = apiKey;
|
|
445
658
|
debug3(`init with apiKey ${apiKey}`);
|
|
446
659
|
this._client = new GraphQLClient(API_URL, {
|
|
447
660
|
headers: { [API_KEY_HEADER_NAME]: apiKey || "" },
|
|
@@ -539,22 +752,26 @@ var GQLClient = class {
|
|
|
539
752
|
);
|
|
540
753
|
return DigestVulnerabilityReportZ.parse(res).digestVulnerabilityReport;
|
|
541
754
|
}
|
|
542
|
-
async submitVulnerabilityReport({
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
755
|
+
async submitVulnerabilityReport(params) {
|
|
756
|
+
const {
|
|
757
|
+
fixReportId,
|
|
758
|
+
repoUrl,
|
|
759
|
+
reference,
|
|
760
|
+
projectId,
|
|
761
|
+
sha,
|
|
762
|
+
vulnerabilityReportFileName,
|
|
763
|
+
pullRequest
|
|
764
|
+
} = params;
|
|
765
|
+
const res = await this._client.request(SUBMIT_VULNERABILITY_REPORT, {
|
|
551
766
|
fixReportId,
|
|
552
767
|
repoUrl,
|
|
553
768
|
reference,
|
|
554
769
|
vulnerabilityReportFileName,
|
|
555
770
|
projectId,
|
|
771
|
+
pullRequest,
|
|
556
772
|
sha: sha || ""
|
|
557
773
|
});
|
|
774
|
+
return CreateUpdateFixReportMutationZ.parse(res);
|
|
558
775
|
}
|
|
559
776
|
async getFixReportState(fixReportId) {
|
|
560
777
|
const res = await this._client.request(
|
|
@@ -588,957 +805,682 @@ var GQLClient = class {
|
|
|
588
805
|
res
|
|
589
806
|
).vulnerability_report_path.map((p) => p.path);
|
|
590
807
|
}
|
|
808
|
+
async subscribeToAnalysis(params, callback) {
|
|
809
|
+
return subscribe(
|
|
810
|
+
SUBSCRIBE_TO_ANALYSIS,
|
|
811
|
+
params,
|
|
812
|
+
async (resolve, reject, data) => {
|
|
813
|
+
if (data.analysis.state === "Failed") {
|
|
814
|
+
reject(data);
|
|
815
|
+
throw new Error(`Analysis failed with id: ${data.analysis.id}`);
|
|
816
|
+
}
|
|
817
|
+
if (data.analysis?.state === "Finished") {
|
|
818
|
+
await callback(data.analysis.id);
|
|
819
|
+
resolve(data);
|
|
820
|
+
}
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
apiKey: this._apiKey
|
|
824
|
+
}
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
async getAnalysis(analysisId) {
|
|
828
|
+
const res = await this._client.request(GET_ANALYSIS, {
|
|
829
|
+
analysisId
|
|
830
|
+
});
|
|
831
|
+
return GetAnalysisQueryZ.parse(res);
|
|
832
|
+
}
|
|
833
|
+
async getFix(fixId) {
|
|
834
|
+
const res = await this._client.request(GET_FIX, {
|
|
835
|
+
fixId
|
|
836
|
+
});
|
|
837
|
+
return GetFixQueryZ.parse(res);
|
|
838
|
+
}
|
|
591
839
|
};
|
|
592
840
|
|
|
593
|
-
// src/features/analysis/
|
|
594
|
-
import fs from "node:fs";
|
|
595
|
-
import path3 from "node:path";
|
|
596
|
-
import AdmZip from "adm-zip";
|
|
841
|
+
// src/features/analysis/handle_finished_analysis.ts
|
|
597
842
|
import Debug4 from "debug";
|
|
598
|
-
import {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
843
|
+
import { z as z7 } from "zod";
|
|
844
|
+
|
|
845
|
+
// src/features/analysis/scm/gitlab.ts
|
|
846
|
+
import querystring from "node:querystring";
|
|
847
|
+
import { Gitlab } from "@gitbeaker/rest";
|
|
848
|
+
import { z as z5 } from "zod";
|
|
849
|
+
|
|
850
|
+
// src/features/analysis/scm/scm.ts
|
|
851
|
+
import { Octokit as Octokit2 } from "@octokit/core";
|
|
852
|
+
|
|
853
|
+
// src/features/analysis/scm/github/github.ts
|
|
854
|
+
import { RequestError } from "@octokit/request-error";
|
|
855
|
+
import { Octokit } from "octokit";
|
|
856
|
+
import { z as z3 } from "zod";
|
|
857
|
+
|
|
858
|
+
// src/features/analysis/scm/urlParser.ts
|
|
859
|
+
var pathnameParsingMap = {
|
|
860
|
+
"gitlab.com": (pathname) => {
|
|
861
|
+
if (pathname.length < 2)
|
|
862
|
+
return null;
|
|
863
|
+
return {
|
|
864
|
+
organization: pathname[0],
|
|
865
|
+
repoName: pathname[pathname.length - 1]
|
|
866
|
+
};
|
|
867
|
+
},
|
|
868
|
+
"github.com": (pathname) => {
|
|
869
|
+
if (pathname.length !== 2)
|
|
870
|
+
return null;
|
|
871
|
+
return {
|
|
872
|
+
organization: pathname[0],
|
|
873
|
+
repoName: pathname[1]
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
};
|
|
877
|
+
var NAME_REGEX = /[a-z0-9\-_.+]+/i;
|
|
878
|
+
var parseScmURL = (scmURL) => {
|
|
879
|
+
try {
|
|
880
|
+
const url = new URL(scmURL);
|
|
881
|
+
const hostname = url.hostname.toLowerCase();
|
|
882
|
+
if (!(hostname in pathnameParsingMap))
|
|
883
|
+
return null;
|
|
884
|
+
const projectPath = url.pathname.substring(1).replace(/.git$/i, "");
|
|
885
|
+
const repo = pathnameParsingMap[hostname](
|
|
886
|
+
projectPath.split("/")
|
|
887
|
+
);
|
|
888
|
+
if (!repo)
|
|
889
|
+
return null;
|
|
890
|
+
const { organization, repoName } = repo;
|
|
891
|
+
if (!organization || !repoName)
|
|
892
|
+
return null;
|
|
893
|
+
if (!organization.match(NAME_REGEX) || !repoName.match(NAME_REGEX))
|
|
894
|
+
return null;
|
|
895
|
+
return {
|
|
896
|
+
hostname: url.hostname,
|
|
897
|
+
organization,
|
|
898
|
+
projectPath,
|
|
899
|
+
repoName
|
|
900
|
+
};
|
|
901
|
+
} catch (e) {
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
// src/features/analysis/scm/github/github.ts
|
|
907
|
+
function removeTrailingSlash(str) {
|
|
908
|
+
return str.trim().replace(/\/+$/, "");
|
|
606
909
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
910
|
+
var EnvVariablesZod = z3.object({
|
|
911
|
+
GITHUB_API_TOKEN: z3.string().optional()
|
|
912
|
+
});
|
|
913
|
+
var { GITHUB_API_TOKEN } = EnvVariablesZod.parse(process.env);
|
|
914
|
+
var GetBlameDocument = `
|
|
915
|
+
query GetBlame(
|
|
916
|
+
$owner: String!
|
|
917
|
+
$repo: String!
|
|
918
|
+
$ref: String!
|
|
919
|
+
$path: String!
|
|
920
|
+
) {
|
|
921
|
+
repository(name: $repo, owner: $owner) {
|
|
922
|
+
# branch name
|
|
923
|
+
object(expression: $ref) {
|
|
924
|
+
# cast Target to a Commit
|
|
925
|
+
... on Commit {
|
|
926
|
+
# full repo-relative path to blame file
|
|
927
|
+
blame(path: $path) {
|
|
928
|
+
ranges {
|
|
929
|
+
commit {
|
|
930
|
+
author {
|
|
931
|
+
user {
|
|
932
|
+
name
|
|
933
|
+
login
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
authoredDate
|
|
937
|
+
}
|
|
938
|
+
startingLine
|
|
939
|
+
endingLine
|
|
940
|
+
age
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
`;
|
|
949
|
+
function getOktoKit(options) {
|
|
950
|
+
const token = options?.githubAuthToken ?? GITHUB_API_TOKEN ?? "";
|
|
951
|
+
return new Octokit({ auth: token });
|
|
952
|
+
}
|
|
953
|
+
async function githubValidateParams(url, accessToken) {
|
|
954
|
+
try {
|
|
955
|
+
const oktoKit = getOktoKit({ githubAuthToken: accessToken });
|
|
956
|
+
if (accessToken) {
|
|
957
|
+
await oktoKit.rest.users.getAuthenticated();
|
|
626
958
|
}
|
|
627
|
-
if (
|
|
628
|
-
|
|
629
|
-
|
|
959
|
+
if (url) {
|
|
960
|
+
const { owner, repo } = parseOwnerAndRepo(url);
|
|
961
|
+
await oktoKit.rest.repos.get({ repo, owner });
|
|
630
962
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
963
|
+
} catch (e) {
|
|
964
|
+
const error = e;
|
|
965
|
+
const code = error.status || error.statusCode || error.response?.status || error.response?.statusCode || error.response?.code;
|
|
966
|
+
if (code === 401 || code === 403) {
|
|
967
|
+
throw new InvalidAccessTokenError(`invalid github access token`);
|
|
635
968
|
}
|
|
636
|
-
|
|
969
|
+
if (code === 404) {
|
|
970
|
+
throw new InvalidRepoUrlError(`invalid github repo Url ${url}`);
|
|
971
|
+
}
|
|
972
|
+
throw e;
|
|
637
973
|
}
|
|
638
|
-
debug4("get zip file buffer");
|
|
639
|
-
return zip.toBuffer();
|
|
640
974
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
var scannerChoices = [
|
|
646
|
-
{ name: "Snyk", value: SCANNERS.Snyk },
|
|
647
|
-
{ name: "Checkmarx", value: SCANNERS.Checkmarx },
|
|
648
|
-
{ name: "Codeql", value: SCANNERS.Codeql },
|
|
649
|
-
{ name: "Fortify", value: SCANNERS.Fortify }
|
|
650
|
-
];
|
|
651
|
-
async function choseScanner() {
|
|
652
|
-
const { scanner } = await inquirer.prompt({
|
|
653
|
-
name: "scanner",
|
|
654
|
-
message: "Choose a scanner you wish to use to scan your code",
|
|
655
|
-
type: "list",
|
|
656
|
-
choices: scannerChoices
|
|
657
|
-
});
|
|
658
|
-
return scanner;
|
|
659
|
-
}
|
|
660
|
-
async function tryCheckmarxConfiguarationAgain() {
|
|
661
|
-
console.log(
|
|
662
|
-
"\u{1F513} Oops, seems like checkmarx does not accept the current configuration"
|
|
663
|
-
);
|
|
664
|
-
const { confirmCheckmarxRetryConfigrations } = await inquirer.prompt({
|
|
665
|
-
name: "confirmCheckmarxRetryConfigrations",
|
|
666
|
-
type: "confirm",
|
|
667
|
-
message: "Would like to try to configure them again? ",
|
|
668
|
-
default: true
|
|
669
|
-
});
|
|
670
|
-
return confirmCheckmarxRetryConfigrations;
|
|
975
|
+
async function getGithubUsername(accessToken) {
|
|
976
|
+
const oktoKit = getOktoKit({ githubAuthToken: accessToken });
|
|
977
|
+
const res = await oktoKit.rest.users.getAuthenticated();
|
|
978
|
+
return res.data.login;
|
|
671
979
|
}
|
|
672
|
-
async function
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
980
|
+
async function getGithubIsUserCollaborator(username, accessToken, repoUrl) {
|
|
981
|
+
try {
|
|
982
|
+
const { owner, repo } = parseOwnerAndRepo(repoUrl);
|
|
983
|
+
const oktoKit = getOktoKit({ githubAuthToken: accessToken });
|
|
984
|
+
const res = await oktoKit.rest.repos.checkCollaborator({
|
|
985
|
+
owner,
|
|
986
|
+
repo,
|
|
987
|
+
username
|
|
988
|
+
});
|
|
989
|
+
if (res.status === 204) {
|
|
990
|
+
return true;
|
|
991
|
+
}
|
|
992
|
+
} catch (e) {
|
|
993
|
+
return false;
|
|
994
|
+
}
|
|
995
|
+
return false;
|
|
678
996
|
}
|
|
679
|
-
async function
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
997
|
+
async function getGithubPullRequestStatus(accessToken, repoUrl, prNumber) {
|
|
998
|
+
const { owner, repo } = parseOwnerAndRepo(repoUrl);
|
|
999
|
+
const oktoKit = getOktoKit({ githubAuthToken: accessToken });
|
|
1000
|
+
const res = await oktoKit.rest.pulls.get({
|
|
1001
|
+
owner,
|
|
1002
|
+
repo,
|
|
1003
|
+
pull_number: prNumber
|
|
685
1004
|
});
|
|
686
|
-
|
|
1005
|
+
if (res.data.merged) {
|
|
1006
|
+
return "merged";
|
|
1007
|
+
}
|
|
1008
|
+
if (res.data.draft) {
|
|
1009
|
+
return "draft";
|
|
1010
|
+
}
|
|
1011
|
+
return res.data.state;
|
|
687
1012
|
}
|
|
688
|
-
async function
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
1013
|
+
async function getGithubIsRemoteBranch(accessToken, repoUrl, branch) {
|
|
1014
|
+
const { owner, repo } = parseOwnerAndRepo(repoUrl);
|
|
1015
|
+
const oktoKit = getOktoKit({ githubAuthToken: accessToken });
|
|
1016
|
+
try {
|
|
1017
|
+
const res = await oktoKit.rest.repos.getBranch({
|
|
1018
|
+
owner,
|
|
1019
|
+
repo,
|
|
1020
|
+
branch
|
|
1021
|
+
});
|
|
1022
|
+
return branch === res.data.name;
|
|
1023
|
+
} catch (e) {
|
|
1024
|
+
return false;
|
|
1025
|
+
}
|
|
693
1026
|
}
|
|
694
|
-
async function
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
1027
|
+
async function getGithubRepoList(accessToken) {
|
|
1028
|
+
const oktoKit = getOktoKit({ githubAuthToken: accessToken });
|
|
1029
|
+
try {
|
|
1030
|
+
const githubRepos = await getRepos(oktoKit);
|
|
1031
|
+
return githubRepos.map(
|
|
1032
|
+
(repo) => {
|
|
1033
|
+
const repoLanguages = [];
|
|
1034
|
+
if (repo.language) {
|
|
1035
|
+
repoLanguages.push(repo.language);
|
|
1036
|
+
}
|
|
1037
|
+
return {
|
|
1038
|
+
repoName: repo.name,
|
|
1039
|
+
repoUrl: repo.html_url,
|
|
1040
|
+
repoOwner: repo.owner.login,
|
|
1041
|
+
repoLanguages,
|
|
1042
|
+
repoIsPublic: !repo.private,
|
|
1043
|
+
repoUpdatedAt: repo.updated_at
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
);
|
|
1047
|
+
} catch (e) {
|
|
1048
|
+
if (e instanceof RequestError && e.status === 401) {
|
|
1049
|
+
return [];
|
|
1050
|
+
}
|
|
1051
|
+
if (e instanceof RequestError && e.status === 404) {
|
|
1052
|
+
return [];
|
|
1053
|
+
}
|
|
1054
|
+
throw e;
|
|
1055
|
+
}
|
|
702
1056
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
// src/utils/child_process.ts
|
|
712
|
-
import cp from "node:child_process";
|
|
713
|
-
import Debug5 from "debug";
|
|
714
|
-
import * as process2 from "process";
|
|
715
|
-
import supportsColor from "supports-color";
|
|
716
|
-
var { stdout: stdout2 } = supportsColor;
|
|
717
|
-
function createFork({ args, processPath, name }, options) {
|
|
718
|
-
const child = cp.fork(processPath, args, {
|
|
719
|
-
stdio: ["inherit", "pipe", "pipe", "ipc"],
|
|
720
|
-
env: { FORCE_COLOR: stdout2 ? "1" : "0" }
|
|
1057
|
+
async function getGithubBranchList(accessToken, repoUrl) {
|
|
1058
|
+
const { owner, repo } = parseOwnerAndRepo(repoUrl);
|
|
1059
|
+
const oktoKit = getOktoKit({ githubAuthToken: accessToken });
|
|
1060
|
+
const res = await oktoKit.rest.repos.listBranches({
|
|
1061
|
+
owner,
|
|
1062
|
+
repo,
|
|
1063
|
+
per_page: 1e3,
|
|
1064
|
+
page: 1
|
|
721
1065
|
});
|
|
722
|
-
return
|
|
1066
|
+
return res.data.map((branch) => branch.name);
|
|
723
1067
|
}
|
|
724
|
-
function
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
|
|
1068
|
+
async function createPullRequest(options) {
|
|
1069
|
+
const { owner, repo } = parseOwnerAndRepo(options.repoUrl);
|
|
1070
|
+
const oktoKit = getOktoKit({ githubAuthToken: options.accessToken });
|
|
1071
|
+
const res = await oktoKit.rest.pulls.create({
|
|
1072
|
+
owner,
|
|
1073
|
+
repo,
|
|
1074
|
+
title: options.title,
|
|
1075
|
+
body: options.body,
|
|
1076
|
+
head: options.sourceBranchName,
|
|
1077
|
+
base: options.targetBranchName,
|
|
1078
|
+
draft: false,
|
|
1079
|
+
maintainer_can_modify: true
|
|
728
1080
|
});
|
|
729
|
-
return
|
|
1081
|
+
return res.data.number;
|
|
730
1082
|
}
|
|
731
|
-
function
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
const onData = (chunk) => {
|
|
737
|
-
debug9(`chunk received from ${name} std ${chunk}`);
|
|
738
|
-
out += chunk;
|
|
739
|
-
};
|
|
740
|
-
if (!childProcess || !childProcess?.stdout || !childProcess?.stderr) {
|
|
741
|
-
debug9(`unable to fork ${name}`);
|
|
742
|
-
reject(new Error(`unable to fork ${name}`));
|
|
743
|
-
}
|
|
744
|
-
childProcess.stdout?.on("data", onData);
|
|
745
|
-
childProcess.stderr?.on("data", onData);
|
|
746
|
-
if (display) {
|
|
747
|
-
childProcess.stdout?.pipe(process2.stdout);
|
|
748
|
-
childProcess.stderr?.pipe(process2.stderr);
|
|
1083
|
+
async function getRepos(oktoKit) {
|
|
1084
|
+
const res = await oktoKit.request("GET /user/repos?sort=updated", {
|
|
1085
|
+
headers: {
|
|
1086
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
1087
|
+
per_page: 100
|
|
749
1088
|
}
|
|
750
|
-
childProcess.on("exit", (code) => {
|
|
751
|
-
debug9(`${name} exit code ${code}`);
|
|
752
|
-
resolve({ message: out, code });
|
|
753
|
-
});
|
|
754
|
-
childProcess.on("error", (err) => {
|
|
755
|
-
debug9(`${name} error %o`, err);
|
|
756
|
-
reject(err);
|
|
757
|
-
});
|
|
758
1089
|
});
|
|
1090
|
+
return res.data;
|
|
759
1091
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
var debug5 = Debug6("mobbdev:checkmarx");
|
|
769
|
-
var require2 = createRequire(import.meta.url);
|
|
770
|
-
var getCheckmarxPath = () => {
|
|
771
|
-
const os3 = type();
|
|
772
|
-
const cxFileName = os3 === "Windows_NT" ? "cx.exe" : "cx";
|
|
1092
|
+
async function getGithubRepoDefaultBranch(repoUrl, options) {
|
|
1093
|
+
const oktoKit = getOktoKit(options);
|
|
1094
|
+
const { owner, repo } = parseOwnerAndRepo(repoUrl);
|
|
1095
|
+
return (await oktoKit.rest.repos.get({ repo, owner })).data.default_branch;
|
|
1096
|
+
}
|
|
1097
|
+
async function getGithubReferenceData({ ref, gitHubUrl }, options) {
|
|
1098
|
+
const { owner, repo } = parseOwnerAndRepo(gitHubUrl);
|
|
1099
|
+
let res;
|
|
773
1100
|
try {
|
|
774
|
-
|
|
1101
|
+
const oktoKit = getOktoKit(options);
|
|
1102
|
+
res = await Promise.any([
|
|
1103
|
+
getBranch({ owner, repo, branch: ref }, oktoKit).then((result) => ({
|
|
1104
|
+
date: result.data.commit.commit.committer?.date ? new Date(result.data.commit.commit.committer?.date) : void 0,
|
|
1105
|
+
type: "BRANCH" /* BRANCH */,
|
|
1106
|
+
sha: result.data.commit.sha
|
|
1107
|
+
})),
|
|
1108
|
+
getCommit({ commitSha: ref, repo, owner }, oktoKit).then((commit) => ({
|
|
1109
|
+
date: new Date(commit.data.committer.date),
|
|
1110
|
+
type: "COMMIT" /* COMMIT */,
|
|
1111
|
+
sha: commit.data.sha
|
|
1112
|
+
})),
|
|
1113
|
+
getTagDate({ owner, repo, tag: ref }, oktoKit).then((data) => ({
|
|
1114
|
+
date: new Date(data.date),
|
|
1115
|
+
type: "TAG" /* TAG */,
|
|
1116
|
+
sha: data.sha
|
|
1117
|
+
}))
|
|
1118
|
+
]);
|
|
1119
|
+
return res;
|
|
775
1120
|
} catch (e) {
|
|
776
|
-
|
|
1121
|
+
if (e instanceof AggregateError) {
|
|
1122
|
+
throw new RefNotFoundError(`ref: ${ref} does not exist`);
|
|
1123
|
+
}
|
|
1124
|
+
throw e;
|
|
777
1125
|
}
|
|
778
|
-
};
|
|
779
|
-
var getCheckmarxCommandArgs = ({
|
|
780
|
-
repoPath,
|
|
781
|
-
branch,
|
|
782
|
-
fileName,
|
|
783
|
-
filePath,
|
|
784
|
-
projectName
|
|
785
|
-
}) => [
|
|
786
|
-
"--project-name",
|
|
787
|
-
projectName,
|
|
788
|
-
"-s",
|
|
789
|
-
repoPath,
|
|
790
|
-
"--branch",
|
|
791
|
-
branch,
|
|
792
|
-
"--scan-types",
|
|
793
|
-
"sast",
|
|
794
|
-
"--output-path",
|
|
795
|
-
filePath,
|
|
796
|
-
"--output-name",
|
|
797
|
-
fileName,
|
|
798
|
-
"--report-format",
|
|
799
|
-
"json"
|
|
800
|
-
];
|
|
801
|
-
var VALIDATE_COMMAND = ["auth", "validate"];
|
|
802
|
-
var CONFIGURE_COMMAND = ["configure"];
|
|
803
|
-
var SCAN_COMMAND = ["scan", "create"];
|
|
804
|
-
var CHECKMARX_SUCCESS_CODE = 0;
|
|
805
|
-
function validateCheckmarxInstallation() {
|
|
806
|
-
existsSync(getCheckmarxPath());
|
|
807
1126
|
}
|
|
808
|
-
async function
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
);
|
|
1127
|
+
async function getBranch({ branch, owner, repo }, oktoKit) {
|
|
1128
|
+
return oktoKit.rest.repos.getBranch({
|
|
1129
|
+
branch,
|
|
1130
|
+
owner,
|
|
1131
|
+
repo
|
|
1132
|
+
});
|
|
814
1133
|
}
|
|
815
|
-
async function
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
1134
|
+
async function getTagDate({ tag, owner, repo }, oktoKit) {
|
|
1135
|
+
const refResponse = await oktoKit.rest.git.getRef({
|
|
1136
|
+
ref: `tags/${tag}`,
|
|
1137
|
+
owner,
|
|
1138
|
+
repo
|
|
819
1139
|
});
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
1140
|
+
const tagSha = refResponse.data.object.sha;
|
|
1141
|
+
if (refResponse.data.object.type === "commit") {
|
|
1142
|
+
const res2 = await oktoKit.rest.git.getCommit({
|
|
1143
|
+
commit_sha: tagSha,
|
|
1144
|
+
owner,
|
|
1145
|
+
repo
|
|
1146
|
+
});
|
|
1147
|
+
return {
|
|
1148
|
+
date: res2.data.committer.date,
|
|
1149
|
+
sha: res2.data.sha
|
|
1150
|
+
};
|
|
826
1151
|
}
|
|
827
|
-
const
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
repoPath: repositoryRoot,
|
|
832
|
-
branch,
|
|
833
|
-
filePath,
|
|
834
|
-
fileName,
|
|
835
|
-
projectName
|
|
1152
|
+
const res = await oktoKit.rest.git.getTag({
|
|
1153
|
+
tag_sha: tagSha,
|
|
1154
|
+
owner,
|
|
1155
|
+
repo
|
|
836
1156
|
});
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
display: true
|
|
842
|
-
}
|
|
843
|
-
);
|
|
844
|
-
if (scanCode !== CHECKMARX_SUCCESS_CODE) {
|
|
845
|
-
createSpinner2("\u{1F50D} Something went wrong with the checkmarx scan").start().error();
|
|
846
|
-
throw new CliError();
|
|
847
|
-
}
|
|
848
|
-
await createSpinner2("\u{1F50D} Checkmarx Scan completed").start().success();
|
|
849
|
-
return true;
|
|
850
|
-
}
|
|
851
|
-
async function throwCheckmarxConfigError() {
|
|
852
|
-
await createSpinner2("\u{1F513} Checkmarx is not configued correctly").start().error();
|
|
853
|
-
throw new CliError(
|
|
854
|
-
`Checkmarx is not configued correctly
|
|
855
|
-
you can configure it by using the ${chalk2.bold(
|
|
856
|
-
"cx configure"
|
|
857
|
-
)} command`
|
|
858
|
-
);
|
|
1157
|
+
return {
|
|
1158
|
+
date: res.data.tagger.date,
|
|
1159
|
+
sha: res.data.sha
|
|
1160
|
+
};
|
|
859
1161
|
}
|
|
860
|
-
async function
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
1162
|
+
async function getCommit({
|
|
1163
|
+
commitSha,
|
|
1164
|
+
owner,
|
|
1165
|
+
repo
|
|
1166
|
+
}, oktoKit) {
|
|
1167
|
+
return oktoKit.rest.git.getCommit({
|
|
1168
|
+
repo,
|
|
1169
|
+
owner,
|
|
1170
|
+
commit_sha: commitSha
|
|
869
1171
|
});
|
|
870
|
-
if (loginCode !== CHECKMARX_SUCCESS_CODE) {
|
|
871
|
-
const tryAgain = await tryCheckmarxConfiguarationAgain();
|
|
872
|
-
if (!tryAgain) {
|
|
873
|
-
await throwCheckmarxConfigError();
|
|
874
|
-
}
|
|
875
|
-
if (await tryCheckmarxConfiguarationAgain()) {
|
|
876
|
-
validateCheckamxCredentials();
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
await createSpinner2("\u{1F513} Checkmarx configured successfully!").start().success();
|
|
880
1172
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
debug6("snyk executable path %s", SNYK_PATH);
|
|
893
|
-
async function forkSnyk(args, { display }) {
|
|
894
|
-
debug6("fork snyk with args %o %s", args, display);
|
|
895
|
-
return createFork(
|
|
896
|
-
{ args, processPath: SNYK_PATH, name: "checkmarx" },
|
|
897
|
-
{ display }
|
|
898
|
-
);
|
|
1173
|
+
function parseOwnerAndRepo(gitHubUrl) {
|
|
1174
|
+
gitHubUrl = removeTrailingSlash(gitHubUrl);
|
|
1175
|
+
const parsingResult = parseScmURL(gitHubUrl);
|
|
1176
|
+
if (!parsingResult || parsingResult.hostname !== "github.com") {
|
|
1177
|
+
throw new InvalidUrlPatternError(`invalid github repo Url ${gitHubUrl}`);
|
|
1178
|
+
}
|
|
1179
|
+
const { organization, repoName } = parsingResult;
|
|
1180
|
+
if (!organization || !repoName) {
|
|
1181
|
+
throw new InvalidUrlPatternError(`invalid github repo Url ${gitHubUrl}`);
|
|
1182
|
+
}
|
|
1183
|
+
return { owner: organization, repo: repoName };
|
|
899
1184
|
}
|
|
900
|
-
async function
|
|
901
|
-
|
|
902
|
-
const
|
|
903
|
-
const
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
if (!skipPrompts) {
|
|
907
|
-
snykLoginSpinner.update({
|
|
908
|
-
text: "\u{1F513} Login to Snyk is required, press any key to continue"
|
|
909
|
-
});
|
|
910
|
-
await keypress();
|
|
1185
|
+
async function queryGithubGraphql(query, variables, options) {
|
|
1186
|
+
const token = options?.githubAuthToken ?? GITHUB_API_TOKEN ?? "";
|
|
1187
|
+
const parameters = variables ?? {};
|
|
1188
|
+
const authorizationHeader = {
|
|
1189
|
+
headers: {
|
|
1190
|
+
authorization: `bearer ${token}`
|
|
911
1191
|
}
|
|
912
|
-
|
|
913
|
-
|
|
1192
|
+
};
|
|
1193
|
+
try {
|
|
1194
|
+
const oktoKit = getOktoKit(options);
|
|
1195
|
+
const res = await oktoKit.graphql(query, {
|
|
1196
|
+
...parameters,
|
|
1197
|
+
...authorizationHeader
|
|
914
1198
|
});
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1199
|
+
return res;
|
|
1200
|
+
} catch (e) {
|
|
1201
|
+
if (e instanceof RequestError) {
|
|
1202
|
+
return null;
|
|
1203
|
+
}
|
|
1204
|
+
throw e;
|
|
918
1205
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1206
|
+
}
|
|
1207
|
+
async function getGithubBlameRanges({ ref, gitHubUrl, path: path8 }, options) {
|
|
1208
|
+
const { owner, repo } = parseOwnerAndRepo(gitHubUrl);
|
|
1209
|
+
const variables = {
|
|
1210
|
+
owner,
|
|
1211
|
+
repo,
|
|
1212
|
+
path: path8,
|
|
1213
|
+
ref
|
|
1214
|
+
};
|
|
1215
|
+
const res = await queryGithubGraphql(
|
|
1216
|
+
GetBlameDocument,
|
|
1217
|
+
variables,
|
|
1218
|
+
options
|
|
923
1219
|
);
|
|
924
|
-
if (
|
|
925
|
-
|
|
926
|
-
)) {
|
|
927
|
-
debug6("snyk code is not enabled %s", scanOutput);
|
|
928
|
-
snykSpinner.error({ text: "\u{1F50D} Snyk configuration needed" });
|
|
929
|
-
const answer = await snykArticlePrompt();
|
|
930
|
-
debug6("answer %s", answer);
|
|
931
|
-
if (answer) {
|
|
932
|
-
debug6("opening the browser");
|
|
933
|
-
await open(SNYK_ARTICLE_URL);
|
|
934
|
-
}
|
|
935
|
-
console.log(
|
|
936
|
-
chalk3.bgBlue(
|
|
937
|
-
"\nPlease enable Snyk Code in your Snyk account and try again."
|
|
938
|
-
)
|
|
939
|
-
);
|
|
940
|
-
throw Error("snyk is not enbabled");
|
|
1220
|
+
if (!res?.repository?.object?.blame?.ranges) {
|
|
1221
|
+
return [];
|
|
941
1222
|
}
|
|
942
|
-
|
|
943
|
-
|
|
1223
|
+
return res.repository.object.blame.ranges.map((range) => ({
|
|
1224
|
+
startingLine: range.startingLine,
|
|
1225
|
+
endingLine: range.endingLine,
|
|
1226
|
+
email: range.commit.author.user.email,
|
|
1227
|
+
name: range.commit.author.user.name,
|
|
1228
|
+
login: range.commit.author.user.login
|
|
1229
|
+
}));
|
|
944
1230
|
}
|
|
945
1231
|
|
|
946
|
-
// src/features/analysis/scm/
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1232
|
+
// src/features/analysis/scm/github/consts.ts
|
|
1233
|
+
var POST_COMMENT_PATH = "POST /repos/{owner}/{repo}/pulls/{pull_number}/comments";
|
|
1234
|
+
var DELETE_COMMENT_PATH = "DELETE /repos/{owner}/{repo}/pulls/comments/{comment_id}";
|
|
1235
|
+
var UPDATE_COMMENT_PATH = "PATCH /repos/{owner}/{repo}/pulls/comments/{comment_id}";
|
|
1236
|
+
var GET_PR_COMMENTS_PATH = "GET /repos/{owner}/{repo}/pulls/comments";
|
|
950
1237
|
|
|
951
|
-
// src/features/analysis/scm/github.ts
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1238
|
+
// src/features/analysis/scm/github/github-v2.ts
|
|
1239
|
+
function postPrComment(client, params) {
|
|
1240
|
+
return client.request(POST_COMMENT_PATH, params);
|
|
1241
|
+
}
|
|
1242
|
+
function updatePrComment(client, params) {
|
|
1243
|
+
return client.request(UPDATE_COMMENT_PATH, params);
|
|
1244
|
+
}
|
|
1245
|
+
function getPrComments(client, params) {
|
|
1246
|
+
return client.request(GET_PR_COMMENTS_PATH, params);
|
|
1247
|
+
}
|
|
1248
|
+
function deleteComment(client, params) {
|
|
1249
|
+
return client.request(DELETE_COMMENT_PATH, params);
|
|
1250
|
+
}
|
|
955
1251
|
|
|
956
|
-
// src/features/analysis/scm/
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
if (
|
|
968
|
-
return
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1252
|
+
// src/features/analysis/scm/scmSubmit.ts
|
|
1253
|
+
import fs from "node:fs/promises";
|
|
1254
|
+
import os from "os";
|
|
1255
|
+
import path3 from "path";
|
|
1256
|
+
import { simpleGit as simpleGit2 } from "simple-git";
|
|
1257
|
+
import tmp from "tmp";
|
|
1258
|
+
import { z as z4 } from "zod";
|
|
1259
|
+
var isValidBranchName = async (branchName) => {
|
|
1260
|
+
const git = simpleGit2();
|
|
1261
|
+
try {
|
|
1262
|
+
const res = await git.raw(["check-ref-format", "--branch", branchName]);
|
|
1263
|
+
if (res) {
|
|
1264
|
+
return true;
|
|
1265
|
+
}
|
|
1266
|
+
return false;
|
|
1267
|
+
} catch (e) {
|
|
1268
|
+
return false;
|
|
973
1269
|
}
|
|
974
1270
|
};
|
|
975
|
-
var
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
if (!organization || !repoName)
|
|
990
|
-
return null;
|
|
991
|
-
if (!organization.match(NAME_REGEX) || !repoName.match(NAME_REGEX))
|
|
992
|
-
return null;
|
|
993
|
-
return {
|
|
994
|
-
hostname: url.hostname,
|
|
995
|
-
organization,
|
|
996
|
-
projectPath,
|
|
997
|
-
repoName
|
|
998
|
-
};
|
|
999
|
-
} catch (e) {
|
|
1000
|
-
return null;
|
|
1001
|
-
}
|
|
1271
|
+
var BaseSubmitToScmMessageZ = z4.object({
|
|
1272
|
+
submitFixRequestId: z4.string().uuid(),
|
|
1273
|
+
fixes: z4.array(
|
|
1274
|
+
z4.object({
|
|
1275
|
+
fixId: z4.string().uuid(),
|
|
1276
|
+
diff: z4.string()
|
|
1277
|
+
})
|
|
1278
|
+
),
|
|
1279
|
+
commitHash: z4.string(),
|
|
1280
|
+
repoUrl: z4.string()
|
|
1281
|
+
});
|
|
1282
|
+
var submitToScmMessageType = {
|
|
1283
|
+
commitToSameBranch: "commitToSameBranch",
|
|
1284
|
+
submitFixesForDifferentBranch: "submitFixesForDifferentBranch"
|
|
1002
1285
|
};
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1286
|
+
var CommitToSameBranchParamsZ = BaseSubmitToScmMessageZ.merge(
|
|
1287
|
+
z4.object({
|
|
1288
|
+
type: z4.literal(submitToScmMessageType.commitToSameBranch),
|
|
1289
|
+
branch: z4.string()
|
|
1290
|
+
})
|
|
1291
|
+
);
|
|
1292
|
+
var SubmitFixesToDifferentBranchParamsZ = z4.object({
|
|
1293
|
+
type: z4.literal(submitToScmMessageType.submitFixesForDifferentBranch),
|
|
1294
|
+
submitBranch: z4.string(),
|
|
1295
|
+
baseBranch: z4.string()
|
|
1296
|
+
}).merge(BaseSubmitToScmMessageZ);
|
|
1297
|
+
var SubmitFixesMessageZ = z4.union([
|
|
1298
|
+
CommitToSameBranchParamsZ,
|
|
1299
|
+
SubmitFixesToDifferentBranchParamsZ
|
|
1300
|
+
]);
|
|
1301
|
+
var FixResponseArrayZ = z4.array(
|
|
1302
|
+
z4.object({
|
|
1303
|
+
fixId: z4.string().uuid()
|
|
1304
|
+
})
|
|
1305
|
+
);
|
|
1306
|
+
var SubmitFixesResponseMessageZ = z4.object({
|
|
1307
|
+
type: z4.nativeEnum(submitToScmMessageType),
|
|
1308
|
+
submitFixRequestId: z4.string().uuid(),
|
|
1309
|
+
submitBranches: z4.array(
|
|
1310
|
+
z4.object({
|
|
1311
|
+
branchName: z4.string(),
|
|
1312
|
+
fixes: FixResponseArrayZ
|
|
1313
|
+
})
|
|
1314
|
+
),
|
|
1315
|
+
error: z4.object({
|
|
1316
|
+
type: z4.enum([
|
|
1317
|
+
"InitialRepoAccessError",
|
|
1318
|
+
"PushBranchError",
|
|
1319
|
+
"UnknownError"
|
|
1320
|
+
]),
|
|
1321
|
+
info: z4.object({
|
|
1322
|
+
message: z4.string(),
|
|
1323
|
+
pushBranchName: z4.string().optional()
|
|
1324
|
+
})
|
|
1325
|
+
}).optional()
|
|
1010
1326
|
});
|
|
1011
|
-
var {
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
$path: String!
|
|
1018
|
-
) {
|
|
1019
|
-
repository(name: $repo, owner: $owner) {
|
|
1020
|
-
# branch name
|
|
1021
|
-
object(expression: $ref) {
|
|
1022
|
-
# cast Target to a Commit
|
|
1023
|
-
... on Commit {
|
|
1024
|
-
# full repo-relative path to blame file
|
|
1025
|
-
blame(path: $path) {
|
|
1026
|
-
ranges {
|
|
1027
|
-
commit {
|
|
1028
|
-
author {
|
|
1029
|
-
user {
|
|
1030
|
-
name
|
|
1031
|
-
login
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
authoredDate
|
|
1035
|
-
}
|
|
1036
|
-
startingLine
|
|
1037
|
-
endingLine
|
|
1038
|
-
age
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
`;
|
|
1047
|
-
function getOktoKit(options) {
|
|
1048
|
-
const token = options?.githubAuthToken ?? GITHUB_API_TOKEN ?? "";
|
|
1049
|
-
return new Octokit({ auth: token });
|
|
1050
|
-
}
|
|
1051
|
-
async function githubValidateParams(url, accessToken) {
|
|
1052
|
-
try {
|
|
1053
|
-
const oktoKit = getOktoKit({ githubAuthToken: accessToken });
|
|
1054
|
-
if (accessToken) {
|
|
1055
|
-
await oktoKit.rest.users.getAuthenticated();
|
|
1056
|
-
}
|
|
1057
|
-
if (url) {
|
|
1058
|
-
const { owner, repo } = parseOwnerAndRepo(url);
|
|
1059
|
-
await oktoKit.rest.repos.get({ repo, owner });
|
|
1060
|
-
}
|
|
1061
|
-
} catch (e) {
|
|
1062
|
-
const error = e;
|
|
1063
|
-
const code = error.status || error.statusCode || error.response?.status || error.response?.statusCode || error.response?.code;
|
|
1064
|
-
if (code === 401 || code === 403) {
|
|
1065
|
-
throw new InvalidAccessTokenError(`invalid github access token`);
|
|
1066
|
-
}
|
|
1067
|
-
if (code === 404) {
|
|
1068
|
-
throw new InvalidRepoUrlError(`invalid github repo Url ${url}`);
|
|
1069
|
-
}
|
|
1070
|
-
throw e;
|
|
1327
|
+
var FixesZ = z4.array(z4.object({ fixId: z4.string(), diff: z4.string() })).nonempty();
|
|
1328
|
+
|
|
1329
|
+
// src/features/analysis/scm/scm.ts
|
|
1330
|
+
function getScmLibTypeFromUrl(url) {
|
|
1331
|
+
if (!url) {
|
|
1332
|
+
return void 0;
|
|
1071
1333
|
}
|
|
1334
|
+
if (url.toLowerCase().startsWith("https://gitlab.com/")) {
|
|
1335
|
+
return "GITLAB" /* GITLAB */;
|
|
1336
|
+
}
|
|
1337
|
+
if (url.toLowerCase().startsWith("https://github.com/")) {
|
|
1338
|
+
return "GITHUB" /* GITHUB */;
|
|
1339
|
+
}
|
|
1340
|
+
return void 0;
|
|
1072
1341
|
}
|
|
1073
|
-
async function
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
}
|
|
1078
|
-
async function getGithubIsUserCollaborator(username, accessToken, repoUrl) {
|
|
1342
|
+
async function scmCanReachRepo({
|
|
1343
|
+
repoUrl,
|
|
1344
|
+
githubToken,
|
|
1345
|
+
gitlabToken
|
|
1346
|
+
}) {
|
|
1079
1347
|
try {
|
|
1080
|
-
const
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
username
|
|
1348
|
+
const scmLibType = getScmLibTypeFromUrl(repoUrl);
|
|
1349
|
+
await SCMLib.init({
|
|
1350
|
+
url: repoUrl,
|
|
1351
|
+
accessToken: scmLibType === "GITHUB" /* GITHUB */ ? githubToken : scmLibType === "GITLAB" /* GITLAB */ ? gitlabToken : "",
|
|
1352
|
+
scmType: scmLibType
|
|
1086
1353
|
});
|
|
1087
|
-
|
|
1088
|
-
return true;
|
|
1089
|
-
}
|
|
1354
|
+
return true;
|
|
1090
1355
|
} catch (e) {
|
|
1091
1356
|
return false;
|
|
1092
1357
|
}
|
|
1093
|
-
return false;
|
|
1094
1358
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
const res = await oktoKit.rest.pulls.get({
|
|
1099
|
-
owner,
|
|
1100
|
-
repo,
|
|
1101
|
-
pull_number: prNumber
|
|
1102
|
-
});
|
|
1103
|
-
if (res.data.merged) {
|
|
1104
|
-
return "merged";
|
|
1359
|
+
var InvalidRepoUrlError = class extends Error {
|
|
1360
|
+
constructor(m) {
|
|
1361
|
+
super(m);
|
|
1105
1362
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1363
|
+
};
|
|
1364
|
+
var InvalidAccessTokenError = class extends Error {
|
|
1365
|
+
constructor(m) {
|
|
1366
|
+
super(m);
|
|
1108
1367
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
const oktoKit = getOktoKit({ githubAuthToken: accessToken });
|
|
1114
|
-
try {
|
|
1115
|
-
const res = await oktoKit.rest.repos.getBranch({
|
|
1116
|
-
owner,
|
|
1117
|
-
repo,
|
|
1118
|
-
branch
|
|
1119
|
-
});
|
|
1120
|
-
return branch === res.data.name;
|
|
1121
|
-
} catch (e) {
|
|
1122
|
-
return false;
|
|
1368
|
+
};
|
|
1369
|
+
var InvalidUrlPatternError = class extends Error {
|
|
1370
|
+
constructor(m) {
|
|
1371
|
+
super(m);
|
|
1123
1372
|
}
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
if (e instanceof RequestError && e.status === 401) {
|
|
1147
|
-
return [];
|
|
1373
|
+
};
|
|
1374
|
+
var RefNotFoundError = class extends Error {
|
|
1375
|
+
constructor(m) {
|
|
1376
|
+
super(m);
|
|
1377
|
+
}
|
|
1378
|
+
};
|
|
1379
|
+
var RepoNoTokenAccessError = class extends Error {
|
|
1380
|
+
constructor(m) {
|
|
1381
|
+
super(m);
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
var SCMLib = class {
|
|
1385
|
+
constructor(url, accessToken) {
|
|
1386
|
+
__publicField(this, "url");
|
|
1387
|
+
__publicField(this, "accessToken");
|
|
1388
|
+
this.accessToken = accessToken;
|
|
1389
|
+
this.url = url;
|
|
1390
|
+
}
|
|
1391
|
+
async getUrlWithCredentials() {
|
|
1392
|
+
if (!this.url) {
|
|
1393
|
+
console.error("no url for getUrlWithCredentials()");
|
|
1394
|
+
throw new Error("no url");
|
|
1148
1395
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1396
|
+
const trimmedUrl = this.url.trim().replace(/\/$/, "");
|
|
1397
|
+
if (!this.accessToken) {
|
|
1398
|
+
return trimmedUrl;
|
|
1151
1399
|
}
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
page: 1
|
|
1163
|
-
});
|
|
1164
|
-
return res.data.map((branch) => branch.name);
|
|
1165
|
-
}
|
|
1166
|
-
async function createPullRequest(options) {
|
|
1167
|
-
const { owner, repo } = parseOwnerAndRepo(options.repoUrl);
|
|
1168
|
-
const oktoKit = getOktoKit({ githubAuthToken: options.accessToken });
|
|
1169
|
-
const res = await oktoKit.rest.pulls.create({
|
|
1170
|
-
owner,
|
|
1171
|
-
repo,
|
|
1172
|
-
title: options.title,
|
|
1173
|
-
body: options.body,
|
|
1174
|
-
head: options.sourceBranchName,
|
|
1175
|
-
base: options.targetBranchName,
|
|
1176
|
-
draft: false,
|
|
1177
|
-
maintainer_can_modify: true
|
|
1178
|
-
});
|
|
1179
|
-
return res.data.number;
|
|
1180
|
-
}
|
|
1181
|
-
async function getRepos(oktoKit) {
|
|
1182
|
-
const res = await oktoKit.request("GET /user/repos?sort=updated", {
|
|
1183
|
-
headers: {
|
|
1184
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
1185
|
-
per_page: 100
|
|
1186
|
-
}
|
|
1187
|
-
});
|
|
1188
|
-
return res.data;
|
|
1189
|
-
}
|
|
1190
|
-
async function getGithubRepoDefaultBranch(repoUrl, options) {
|
|
1191
|
-
const oktoKit = getOktoKit(options);
|
|
1192
|
-
const { owner, repo } = parseOwnerAndRepo(repoUrl);
|
|
1193
|
-
return (await oktoKit.rest.repos.get({ repo, owner })).data.default_branch;
|
|
1194
|
-
}
|
|
1195
|
-
async function getGithubReferenceData({ ref, gitHubUrl }, options) {
|
|
1196
|
-
const { owner, repo } = parseOwnerAndRepo(gitHubUrl);
|
|
1197
|
-
let res;
|
|
1198
|
-
try {
|
|
1199
|
-
const oktoKit = getOktoKit(options);
|
|
1200
|
-
res = await Promise.any([
|
|
1201
|
-
getBranch({ owner, repo, branch: ref }, oktoKit).then((result) => ({
|
|
1202
|
-
date: result.data.commit.commit.committer?.date ? new Date(result.data.commit.commit.committer?.date) : void 0,
|
|
1203
|
-
type: "BRANCH" /* BRANCH */,
|
|
1204
|
-
sha: result.data.commit.sha
|
|
1205
|
-
})),
|
|
1206
|
-
getCommit({ commitSha: ref, repo, owner }, oktoKit).then((commit) => ({
|
|
1207
|
-
date: new Date(commit.data.committer.date),
|
|
1208
|
-
type: "COMMIT" /* COMMIT */,
|
|
1209
|
-
sha: commit.data.sha
|
|
1210
|
-
})),
|
|
1211
|
-
getTagDate({ owner, repo, tag: ref }, oktoKit).then((data) => ({
|
|
1212
|
-
date: new Date(data.date),
|
|
1213
|
-
type: "TAG" /* TAG */,
|
|
1214
|
-
sha: data.sha
|
|
1215
|
-
}))
|
|
1216
|
-
]);
|
|
1217
|
-
return res;
|
|
1218
|
-
} catch (e) {
|
|
1219
|
-
if (e instanceof AggregateError) {
|
|
1220
|
-
throw new RefNotFoundError(`ref: ${ref} does not exist`);
|
|
1400
|
+
const username = await this._getUsernameForAuthUrl();
|
|
1401
|
+
const is_http = trimmedUrl.toLowerCase().startsWith("http://");
|
|
1402
|
+
const is_https = trimmedUrl.toLowerCase().startsWith("https://");
|
|
1403
|
+
if (is_http) {
|
|
1404
|
+
return `http://${username}:${this.accessToken}@${trimmedUrl.toLowerCase().replace("http://", "")}`;
|
|
1405
|
+
} else if (is_https) {
|
|
1406
|
+
return `https://${username}:${this.accessToken}@${trimmedUrl.toLowerCase().replace("https://", "")}`;
|
|
1407
|
+
} else {
|
|
1408
|
+
console.error(`invalid scm url ${trimmedUrl}`);
|
|
1409
|
+
throw new Error(`invalid scm url ${trimmedUrl}`);
|
|
1221
1410
|
}
|
|
1222
|
-
throw e;
|
|
1223
1411
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
return oktoKit.rest.repos.getBranch({
|
|
1227
|
-
branch,
|
|
1228
|
-
owner,
|
|
1229
|
-
repo
|
|
1230
|
-
});
|
|
1231
|
-
}
|
|
1232
|
-
async function getTagDate({ tag, owner, repo }, oktoKit) {
|
|
1233
|
-
const refResponse = await oktoKit.rest.git.getRef({
|
|
1234
|
-
ref: `tags/${tag}`,
|
|
1235
|
-
owner,
|
|
1236
|
-
repo
|
|
1237
|
-
});
|
|
1238
|
-
const tagSha = refResponse.data.object.sha;
|
|
1239
|
-
if (refResponse.data.object.type === "commit") {
|
|
1240
|
-
const res2 = await oktoKit.rest.git.getCommit({
|
|
1241
|
-
commit_sha: tagSha,
|
|
1242
|
-
owner,
|
|
1243
|
-
repo
|
|
1244
|
-
});
|
|
1245
|
-
return {
|
|
1246
|
-
date: res2.data.committer.date,
|
|
1247
|
-
sha: res2.data.sha
|
|
1248
|
-
};
|
|
1412
|
+
getAccessToken() {
|
|
1413
|
+
return this.accessToken || "";
|
|
1249
1414
|
}
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
owner,
|
|
1253
|
-
repo
|
|
1254
|
-
});
|
|
1255
|
-
return {
|
|
1256
|
-
date: res.data.tagger.date,
|
|
1257
|
-
sha: res.data.sha
|
|
1258
|
-
};
|
|
1259
|
-
}
|
|
1260
|
-
async function getCommit({
|
|
1261
|
-
commitSha,
|
|
1262
|
-
owner,
|
|
1263
|
-
repo
|
|
1264
|
-
}, oktoKit) {
|
|
1265
|
-
return oktoKit.rest.git.getCommit({
|
|
1266
|
-
repo,
|
|
1267
|
-
owner,
|
|
1268
|
-
commit_sha: commitSha
|
|
1269
|
-
});
|
|
1270
|
-
}
|
|
1271
|
-
function parseOwnerAndRepo(gitHubUrl) {
|
|
1272
|
-
gitHubUrl = removeTrailingSlash(gitHubUrl);
|
|
1273
|
-
const parsingResult = parseScmURL(gitHubUrl);
|
|
1274
|
-
if (!parsingResult || parsingResult.hostname !== "github.com") {
|
|
1275
|
-
throw new InvalidUrlPatternError(`invalid github repo Url ${gitHubUrl}`);
|
|
1415
|
+
getUrl() {
|
|
1416
|
+
return this.url;
|
|
1276
1417
|
}
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1418
|
+
getName() {
|
|
1419
|
+
if (!this.url) {
|
|
1420
|
+
return "";
|
|
1421
|
+
}
|
|
1422
|
+
return this.url.split("/").at(-1) || "";
|
|
1280
1423
|
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1424
|
+
static async getIsValidBranchName(branchName) {
|
|
1425
|
+
return isValidBranchName(branchName);
|
|
1426
|
+
}
|
|
1427
|
+
static async init({
|
|
1428
|
+
url,
|
|
1429
|
+
accessToken,
|
|
1430
|
+
scmType
|
|
1431
|
+
}) {
|
|
1432
|
+
let trimmedUrl = void 0;
|
|
1433
|
+
if (url) {
|
|
1434
|
+
trimmedUrl = url.trim().replace(/\/$/, "");
|
|
1289
1435
|
}
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1436
|
+
try {
|
|
1437
|
+
if ("GITHUB" /* GITHUB */ === scmType) {
|
|
1438
|
+
const scm = new GithubSCMLib(trimmedUrl, accessToken);
|
|
1439
|
+
await scm.validateParams();
|
|
1440
|
+
return scm;
|
|
1441
|
+
}
|
|
1442
|
+
if ("GITLAB" /* GITLAB */ === scmType) {
|
|
1443
|
+
const scm = new GitlabSCMLib(trimmedUrl, accessToken);
|
|
1444
|
+
await scm.validateParams();
|
|
1445
|
+
return scm;
|
|
1446
|
+
}
|
|
1447
|
+
} catch (e) {
|
|
1448
|
+
if (e instanceof InvalidRepoUrlError && url) {
|
|
1449
|
+
throw new RepoNoTokenAccessError("no access to repo");
|
|
1450
|
+
}
|
|
1301
1451
|
}
|
|
1302
|
-
|
|
1452
|
+
return new StubSCMLib(trimmedUrl);
|
|
1303
1453
|
}
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1454
|
+
};
|
|
1455
|
+
var GitlabSCMLib = class extends SCMLib {
|
|
1456
|
+
async createSubmitRequest(targetBranchName, sourceBranchName, title, body) {
|
|
1457
|
+
if (!this.accessToken || !this.url) {
|
|
1458
|
+
console.error("no access token or no url");
|
|
1459
|
+
throw new Error("no access token or no url");
|
|
1460
|
+
}
|
|
1461
|
+
return String(
|
|
1462
|
+
await createMergeRequest({
|
|
1463
|
+
title,
|
|
1464
|
+
body,
|
|
1465
|
+
targetBranchName,
|
|
1466
|
+
sourceBranchName,
|
|
1467
|
+
repoUrl: this.url,
|
|
1468
|
+
accessToken: this.accessToken
|
|
1469
|
+
})
|
|
1470
|
+
);
|
|
1320
1471
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
import fs2 from "node:fs/promises";
|
|
1332
|
-
import os from "os";
|
|
1333
|
-
import path5 from "path";
|
|
1334
|
-
import { simpleGit as simpleGit2 } from "simple-git";
|
|
1335
|
-
import { z as z4 } from "zod";
|
|
1336
|
-
var isValidBranchName = async (branchName) => {
|
|
1337
|
-
const git = simpleGit2();
|
|
1338
|
-
try {
|
|
1339
|
-
const res = await git.raw(["check-ref-format", "--branch", branchName]);
|
|
1340
|
-
if (res) {
|
|
1341
|
-
return true;
|
|
1472
|
+
async validateParams() {
|
|
1473
|
+
return gitlabValidateParams({
|
|
1474
|
+
url: this.url,
|
|
1475
|
+
accessToken: this.accessToken
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
async getRepoList() {
|
|
1479
|
+
if (!this.accessToken) {
|
|
1480
|
+
console.error("no access token");
|
|
1481
|
+
throw new Error("no access token");
|
|
1342
1482
|
}
|
|
1343
|
-
return
|
|
1344
|
-
} catch (e) {
|
|
1345
|
-
return false;
|
|
1346
|
-
}
|
|
1347
|
-
};
|
|
1348
|
-
var SubmitFixesMessageZ = z4.object({
|
|
1349
|
-
submitFixRequestId: z4.string().uuid(),
|
|
1350
|
-
fixes: z4.array(
|
|
1351
|
-
z4.object({
|
|
1352
|
-
fixId: z4.string().uuid(),
|
|
1353
|
-
diff: z4.string()
|
|
1354
|
-
})
|
|
1355
|
-
),
|
|
1356
|
-
branchName: z4.string(),
|
|
1357
|
-
commitHash: z4.string(),
|
|
1358
|
-
targetBranch: z4.string(),
|
|
1359
|
-
repoUrl: z4.string()
|
|
1360
|
-
});
|
|
1361
|
-
var FixResponseArrayZ = z4.array(
|
|
1362
|
-
z4.object({
|
|
1363
|
-
fixId: z4.string().uuid()
|
|
1364
|
-
})
|
|
1365
|
-
);
|
|
1366
|
-
var SubmitFixesResponseMessageZ = z4.object({
|
|
1367
|
-
submitFixRequestId: z4.string().uuid(),
|
|
1368
|
-
submitBranches: z4.array(
|
|
1369
|
-
z4.object({
|
|
1370
|
-
branchName: z4.string(),
|
|
1371
|
-
fixes: FixResponseArrayZ
|
|
1372
|
-
})
|
|
1373
|
-
),
|
|
1374
|
-
error: z4.object({
|
|
1375
|
-
type: z4.enum([
|
|
1376
|
-
"InitialRepoAccessError",
|
|
1377
|
-
"PushBranchError",
|
|
1378
|
-
"UnknownError"
|
|
1379
|
-
]),
|
|
1380
|
-
info: z4.object({
|
|
1381
|
-
message: z4.string(),
|
|
1382
|
-
pushBranchName: z4.string().optional()
|
|
1383
|
-
})
|
|
1384
|
-
}).optional()
|
|
1385
|
-
});
|
|
1386
|
-
|
|
1387
|
-
// src/features/analysis/scm/scm.ts
|
|
1388
|
-
function getScmLibTypeFromUrl(url) {
|
|
1389
|
-
if (!url) {
|
|
1390
|
-
return void 0;
|
|
1391
|
-
}
|
|
1392
|
-
if (url.toLowerCase().startsWith("https://gitlab.com/")) {
|
|
1393
|
-
return "GITLAB" /* GITLAB */;
|
|
1394
|
-
}
|
|
1395
|
-
if (url.toLowerCase().startsWith("https://github.com/")) {
|
|
1396
|
-
return "GITHUB" /* GITHUB */;
|
|
1397
|
-
}
|
|
1398
|
-
return void 0;
|
|
1399
|
-
}
|
|
1400
|
-
async function scmCanReachRepo({
|
|
1401
|
-
repoUrl,
|
|
1402
|
-
githubToken,
|
|
1403
|
-
gitlabToken
|
|
1404
|
-
}) {
|
|
1405
|
-
try {
|
|
1406
|
-
const scmLibType = getScmLibTypeFromUrl(repoUrl);
|
|
1407
|
-
await SCMLib.init({
|
|
1408
|
-
url: repoUrl,
|
|
1409
|
-
accessToken: scmLibType === "GITHUB" /* GITHUB */ ? githubToken : scmLibType === "GITLAB" /* GITLAB */ ? gitlabToken : "",
|
|
1410
|
-
scmType: scmLibType
|
|
1411
|
-
});
|
|
1412
|
-
return true;
|
|
1413
|
-
} catch (e) {
|
|
1414
|
-
return false;
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
var InvalidRepoUrlError = class extends Error {
|
|
1418
|
-
constructor(m) {
|
|
1419
|
-
super(m);
|
|
1420
|
-
}
|
|
1421
|
-
};
|
|
1422
|
-
var InvalidAccessTokenError = class extends Error {
|
|
1423
|
-
constructor(m) {
|
|
1424
|
-
super(m);
|
|
1425
|
-
}
|
|
1426
|
-
};
|
|
1427
|
-
var InvalidUrlPatternError = class extends Error {
|
|
1428
|
-
constructor(m) {
|
|
1429
|
-
super(m);
|
|
1430
|
-
}
|
|
1431
|
-
};
|
|
1432
|
-
var RefNotFoundError = class extends Error {
|
|
1433
|
-
constructor(m) {
|
|
1434
|
-
super(m);
|
|
1435
|
-
}
|
|
1436
|
-
};
|
|
1437
|
-
var RepoNoTokenAccessError = class extends Error {
|
|
1438
|
-
constructor(m) {
|
|
1439
|
-
super(m);
|
|
1440
|
-
}
|
|
1441
|
-
};
|
|
1442
|
-
var SCMLib = class {
|
|
1443
|
-
constructor(url, accessToken) {
|
|
1444
|
-
__publicField(this, "url");
|
|
1445
|
-
__publicField(this, "accessToken");
|
|
1446
|
-
this.accessToken = accessToken;
|
|
1447
|
-
this.url = url;
|
|
1448
|
-
}
|
|
1449
|
-
async getUrlWithCredentials() {
|
|
1450
|
-
if (!this.url) {
|
|
1451
|
-
console.error("no url for getUrlWithCredentials()");
|
|
1452
|
-
throw new Error("no url");
|
|
1453
|
-
}
|
|
1454
|
-
const trimmedUrl = this.url.trim().replace(/\/$/, "");
|
|
1455
|
-
if (!this.accessToken) {
|
|
1456
|
-
return trimmedUrl;
|
|
1457
|
-
}
|
|
1458
|
-
const username = await this._getUsernameForAuthUrl();
|
|
1459
|
-
const is_http = trimmedUrl.toLowerCase().startsWith("http://");
|
|
1460
|
-
const is_https = trimmedUrl.toLowerCase().startsWith("https://");
|
|
1461
|
-
if (is_http) {
|
|
1462
|
-
return `http://${username}:${this.accessToken}@${trimmedUrl.toLowerCase().replace("http://", "")}`;
|
|
1463
|
-
} else if (is_https) {
|
|
1464
|
-
return `https://${username}:${this.accessToken}@${trimmedUrl.toLowerCase().replace("https://", "")}`;
|
|
1465
|
-
} else {
|
|
1466
|
-
console.error(`invalid scm url ${trimmedUrl}`);
|
|
1467
|
-
throw new Error(`invalid scm url ${trimmedUrl}`);
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
getAccessToken() {
|
|
1471
|
-
return this.accessToken || "";
|
|
1472
|
-
}
|
|
1473
|
-
getUrl() {
|
|
1474
|
-
return this.url;
|
|
1475
|
-
}
|
|
1476
|
-
getName() {
|
|
1477
|
-
if (!this.url) {
|
|
1478
|
-
return "";
|
|
1479
|
-
}
|
|
1480
|
-
return this.url.split("/").at(-1) || "";
|
|
1481
|
-
}
|
|
1482
|
-
static async getIsValidBranchName(branchName) {
|
|
1483
|
-
return isValidBranchName(branchName);
|
|
1484
|
-
}
|
|
1485
|
-
static async init({
|
|
1486
|
-
url,
|
|
1487
|
-
accessToken,
|
|
1488
|
-
scmType
|
|
1489
|
-
}) {
|
|
1490
|
-
let trimmedUrl = void 0;
|
|
1491
|
-
if (url) {
|
|
1492
|
-
trimmedUrl = url.trim().replace(/\/$/, "");
|
|
1493
|
-
}
|
|
1494
|
-
try {
|
|
1495
|
-
if ("GITHUB" /* GITHUB */ === scmType) {
|
|
1496
|
-
const scm = new GithubSCMLib(trimmedUrl, accessToken);
|
|
1497
|
-
await scm.validateParams();
|
|
1498
|
-
return scm;
|
|
1499
|
-
}
|
|
1500
|
-
if ("GITLAB" /* GITLAB */ === scmType) {
|
|
1501
|
-
const scm = new GitlabSCMLib(trimmedUrl, accessToken);
|
|
1502
|
-
await scm.validateParams();
|
|
1503
|
-
return scm;
|
|
1504
|
-
}
|
|
1505
|
-
} catch (e) {
|
|
1506
|
-
if (e instanceof InvalidRepoUrlError && url) {
|
|
1507
|
-
throw new RepoNoTokenAccessError("no access to repo");
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
return new StubSCMLib(trimmedUrl);
|
|
1511
|
-
}
|
|
1512
|
-
};
|
|
1513
|
-
var GitlabSCMLib = class extends SCMLib {
|
|
1514
|
-
async createSubmitRequest(targetBranchName, sourceBranchName, title, body) {
|
|
1515
|
-
if (!this.accessToken || !this.url) {
|
|
1516
|
-
console.error("no access token or no url");
|
|
1517
|
-
throw new Error("no access token or no url");
|
|
1518
|
-
}
|
|
1519
|
-
return String(
|
|
1520
|
-
await createMergeRequest({
|
|
1521
|
-
title,
|
|
1522
|
-
body,
|
|
1523
|
-
targetBranchName,
|
|
1524
|
-
sourceBranchName,
|
|
1525
|
-
repoUrl: this.url,
|
|
1526
|
-
accessToken: this.accessToken
|
|
1527
|
-
})
|
|
1528
|
-
);
|
|
1529
|
-
}
|
|
1530
|
-
async validateParams() {
|
|
1531
|
-
return gitlabValidateParams({
|
|
1532
|
-
url: this.url,
|
|
1533
|
-
accessToken: this.accessToken
|
|
1534
|
-
});
|
|
1535
|
-
}
|
|
1536
|
-
async getRepoList() {
|
|
1537
|
-
if (!this.accessToken) {
|
|
1538
|
-
console.error("no access token");
|
|
1539
|
-
throw new Error("no access token");
|
|
1540
|
-
}
|
|
1541
|
-
return getGitlabRepoList(this.accessToken);
|
|
1483
|
+
return getGitlabRepoList(this.accessToken);
|
|
1542
1484
|
}
|
|
1543
1485
|
async getBranchList() {
|
|
1544
1486
|
if (!this.accessToken || !this.url) {
|
|
@@ -1656,6 +1598,11 @@ var GitlabSCMLib = class extends SCMLib {
|
|
|
1656
1598
|
}
|
|
1657
1599
|
};
|
|
1658
1600
|
var GithubSCMLib = class extends SCMLib {
|
|
1601
|
+
constructor(url, accessToken) {
|
|
1602
|
+
super(url, accessToken);
|
|
1603
|
+
__publicField(this, "oktokit");
|
|
1604
|
+
this.oktokit = new Octokit2({ auth: accessToken });
|
|
1605
|
+
}
|
|
1659
1606
|
async createSubmitRequest(targetBranchName, sourceBranchName, title, body) {
|
|
1660
1607
|
if (!this.accessToken || !this.url) {
|
|
1661
1608
|
console.error("no access token or no url");
|
|
@@ -1675,6 +1622,55 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
1675
1622
|
async validateParams() {
|
|
1676
1623
|
return githubValidateParams(this.url, this.accessToken);
|
|
1677
1624
|
}
|
|
1625
|
+
async postPrComment(params, _oktokit) {
|
|
1626
|
+
if (!_oktokit && !this.accessToken || !this.url) {
|
|
1627
|
+
throw new Error("cannot post on PR without access token or url");
|
|
1628
|
+
}
|
|
1629
|
+
const oktokit = _oktokit || this.oktokit;
|
|
1630
|
+
const { owner, repo } = parseOwnerAndRepo(this.url);
|
|
1631
|
+
return postPrComment(oktokit, {
|
|
1632
|
+
...params,
|
|
1633
|
+
owner,
|
|
1634
|
+
repo
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
async updatePrComment(params, _oktokit) {
|
|
1638
|
+
if (!_oktokit && !this.accessToken || !this.url) {
|
|
1639
|
+
throw new Error("cannot update on PR without access token or url");
|
|
1640
|
+
}
|
|
1641
|
+
const oktokit = _oktokit || this.oktokit;
|
|
1642
|
+
const { owner, repo } = parseOwnerAndRepo(this.url);
|
|
1643
|
+
return updatePrComment(oktokit, {
|
|
1644
|
+
...params,
|
|
1645
|
+
owner,
|
|
1646
|
+
repo
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
async deleteComment(params, _oktokit) {
|
|
1650
|
+
if (!_oktokit && !this.accessToken || !this.url) {
|
|
1651
|
+
throw new Error("cannot delete comment without access token or url");
|
|
1652
|
+
}
|
|
1653
|
+
const oktokit = _oktokit || this.oktokit;
|
|
1654
|
+
const { owner, repo } = parseOwnerAndRepo(this.url);
|
|
1655
|
+
return deleteComment(oktokit, {
|
|
1656
|
+
...params,
|
|
1657
|
+
owner,
|
|
1658
|
+
repo
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
async getPrComments(params, _oktokit) {
|
|
1662
|
+
if (!_oktokit && !this.accessToken || !this.url) {
|
|
1663
|
+
throw new Error("cannot get Pr Comments without access token or url");
|
|
1664
|
+
}
|
|
1665
|
+
const oktokit = _oktokit || this.oktokit;
|
|
1666
|
+
const { owner, repo } = parseOwnerAndRepo(this.url);
|
|
1667
|
+
return getPrComments(oktokit, {
|
|
1668
|
+
per_page: 100,
|
|
1669
|
+
...params,
|
|
1670
|
+
owner,
|
|
1671
|
+
repo
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1678
1674
|
async getRepoList() {
|
|
1679
1675
|
if (!this.accessToken) {
|
|
1680
1676
|
console.error("no access token");
|
|
@@ -1840,259 +1836,849 @@ var StubSCMLib = class extends SCMLib {
|
|
|
1840
1836
|
}
|
|
1841
1837
|
};
|
|
1842
1838
|
|
|
1843
|
-
// src/features/analysis/scm/gitlab.ts
|
|
1844
|
-
function removeTrailingSlash2(str) {
|
|
1845
|
-
return str.trim().replace(/\/+$/, "");
|
|
1839
|
+
// src/features/analysis/scm/gitlab.ts
|
|
1840
|
+
function removeTrailingSlash2(str) {
|
|
1841
|
+
return str.trim().replace(/\/+$/, "");
|
|
1842
|
+
}
|
|
1843
|
+
var EnvVariablesZod2 = z5.object({
|
|
1844
|
+
GITLAB_API_TOKEN: z5.string().optional()
|
|
1845
|
+
});
|
|
1846
|
+
var { GITLAB_API_TOKEN } = EnvVariablesZod2.parse(process.env);
|
|
1847
|
+
function getGitBeaker(options) {
|
|
1848
|
+
const token = options?.gitlabAuthToken ?? GITLAB_API_TOKEN ?? "";
|
|
1849
|
+
if (token?.startsWith("glpat-") || token === "") {
|
|
1850
|
+
return new Gitlab({ token });
|
|
1851
|
+
}
|
|
1852
|
+
return new Gitlab({ oauthToken: token });
|
|
1853
|
+
}
|
|
1854
|
+
async function gitlabValidateParams({
|
|
1855
|
+
url,
|
|
1856
|
+
accessToken
|
|
1857
|
+
}) {
|
|
1858
|
+
try {
|
|
1859
|
+
const api = getGitBeaker({ gitlabAuthToken: accessToken });
|
|
1860
|
+
if (accessToken) {
|
|
1861
|
+
await api.Users.showCurrentUser();
|
|
1862
|
+
}
|
|
1863
|
+
if (url) {
|
|
1864
|
+
const { projectPath } = parseOwnerAndRepo2(url);
|
|
1865
|
+
await api.Projects.show(projectPath);
|
|
1866
|
+
}
|
|
1867
|
+
} catch (e) {
|
|
1868
|
+
const error = e;
|
|
1869
|
+
const code = error.code || error.status || error.statusCode || error.response?.status || error.response?.statusCode || error.response?.code;
|
|
1870
|
+
const description = error.description || `${e}`;
|
|
1871
|
+
if (code === 401 || code === 403 || description.includes("401") || description.includes("403")) {
|
|
1872
|
+
throw new InvalidAccessTokenError(`invalid gitlab access token`);
|
|
1873
|
+
}
|
|
1874
|
+
if (code === 404 || description.includes("404") || description.includes("Not Found")) {
|
|
1875
|
+
throw new InvalidRepoUrlError(`invalid gitlab repo Url ${url}`);
|
|
1876
|
+
}
|
|
1877
|
+
throw e;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
async function getGitlabUsername(accessToken) {
|
|
1881
|
+
const api = getGitBeaker({ gitlabAuthToken: accessToken });
|
|
1882
|
+
const res = await api.Users.showCurrentUser();
|
|
1883
|
+
return res.username;
|
|
1884
|
+
}
|
|
1885
|
+
async function getGitlabIsUserCollaborator({
|
|
1886
|
+
username,
|
|
1887
|
+
accessToken,
|
|
1888
|
+
repoUrl
|
|
1889
|
+
}) {
|
|
1890
|
+
try {
|
|
1891
|
+
const { projectPath } = parseOwnerAndRepo2(repoUrl);
|
|
1892
|
+
const api = getGitBeaker({ gitlabAuthToken: accessToken });
|
|
1893
|
+
const res = await api.Projects.show(projectPath);
|
|
1894
|
+
const members = await api.ProjectMembers.all(res.id, {
|
|
1895
|
+
includeInherited: true
|
|
1896
|
+
});
|
|
1897
|
+
return !!members.find((member) => member.username === username);
|
|
1898
|
+
} catch (e) {
|
|
1899
|
+
return false;
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
async function getGitlabMergeRequestStatus({
|
|
1903
|
+
accessToken,
|
|
1904
|
+
repoUrl,
|
|
1905
|
+
mrNumber
|
|
1906
|
+
}) {
|
|
1907
|
+
const { projectPath } = parseOwnerAndRepo2(repoUrl);
|
|
1908
|
+
const api = getGitBeaker({ gitlabAuthToken: accessToken });
|
|
1909
|
+
const res = await api.MergeRequests.show(projectPath, mrNumber);
|
|
1910
|
+
switch (res.state) {
|
|
1911
|
+
case "merged" /* merged */:
|
|
1912
|
+
case "opened" /* opened */:
|
|
1913
|
+
case "closed" /* closed */:
|
|
1914
|
+
return res.state;
|
|
1915
|
+
default:
|
|
1916
|
+
throw new Error(`unknown merge request state ${res.state}`);
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
async function getGitlabIsRemoteBranch({
|
|
1920
|
+
accessToken,
|
|
1921
|
+
repoUrl,
|
|
1922
|
+
branch
|
|
1923
|
+
}) {
|
|
1924
|
+
const { projectPath } = parseOwnerAndRepo2(repoUrl);
|
|
1925
|
+
const api = getGitBeaker({ gitlabAuthToken: accessToken });
|
|
1926
|
+
try {
|
|
1927
|
+
const res = await api.Branches.show(projectPath, branch);
|
|
1928
|
+
return res.name === branch;
|
|
1929
|
+
} catch (e) {
|
|
1930
|
+
return false;
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
async function getGitlabRepoList(accessToken) {
|
|
1934
|
+
const api = getGitBeaker({ gitlabAuthToken: accessToken });
|
|
1935
|
+
const res = await api.Projects.all({
|
|
1936
|
+
membership: true,
|
|
1937
|
+
//TODO: a bug in the sorting mechanism of this api call
|
|
1938
|
+
//disallows us to sort by updated_at in descending order
|
|
1939
|
+
//so we have to sort by updated_at in ascending order.
|
|
1940
|
+
//We can wait for the bug to be fixed or call the api
|
|
1941
|
+
//directly with fetch()
|
|
1942
|
+
sort: "asc",
|
|
1943
|
+
orderBy: "updated_at",
|
|
1944
|
+
perPage: 100
|
|
1945
|
+
});
|
|
1946
|
+
return Promise.all(
|
|
1947
|
+
res.map(async (project) => {
|
|
1948
|
+
const proj = await api.Projects.show(project.id);
|
|
1949
|
+
const owner = proj.namespace.name;
|
|
1950
|
+
const repoLanguages = await api.Projects.showLanguages(project.id);
|
|
1951
|
+
return {
|
|
1952
|
+
repoName: project.path,
|
|
1953
|
+
repoUrl: project.web_url,
|
|
1954
|
+
repoOwner: owner,
|
|
1955
|
+
repoLanguages: Object.keys(repoLanguages),
|
|
1956
|
+
repoIsPublic: project.visibility === "public",
|
|
1957
|
+
repoUpdatedAt: project.last_activity_at
|
|
1958
|
+
};
|
|
1959
|
+
})
|
|
1960
|
+
);
|
|
1961
|
+
}
|
|
1962
|
+
async function getGitlabBranchList({
|
|
1963
|
+
accessToken,
|
|
1964
|
+
repoUrl
|
|
1965
|
+
}) {
|
|
1966
|
+
const { projectPath } = parseOwnerAndRepo2(repoUrl);
|
|
1967
|
+
const api = getGitBeaker({ gitlabAuthToken: accessToken });
|
|
1968
|
+
try {
|
|
1969
|
+
const res = await api.Branches.all(projectPath, {
|
|
1970
|
+
perPage: 100,
|
|
1971
|
+
pagination: "keyset",
|
|
1972
|
+
orderBy: "updated_at",
|
|
1973
|
+
sort: "dec"
|
|
1974
|
+
});
|
|
1975
|
+
return res.map((branch) => branch.name);
|
|
1976
|
+
} catch (e) {
|
|
1977
|
+
return [];
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
async function createMergeRequest(options) {
|
|
1981
|
+
const { projectPath } = parseOwnerAndRepo2(options.repoUrl);
|
|
1982
|
+
const api = getGitBeaker({ gitlabAuthToken: options.accessToken });
|
|
1983
|
+
const res = await api.MergeRequests.create(
|
|
1984
|
+
projectPath,
|
|
1985
|
+
options.sourceBranchName,
|
|
1986
|
+
options.targetBranchName,
|
|
1987
|
+
options.title,
|
|
1988
|
+
{
|
|
1989
|
+
description: options.body
|
|
1990
|
+
}
|
|
1991
|
+
);
|
|
1992
|
+
return res.iid;
|
|
1993
|
+
}
|
|
1994
|
+
async function getGitlabRepoDefaultBranch(repoUrl, options) {
|
|
1995
|
+
const api = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
|
|
1996
|
+
const { projectPath } = parseOwnerAndRepo2(repoUrl);
|
|
1997
|
+
const project = await api.Projects.show(projectPath);
|
|
1998
|
+
if (!project.default_branch) {
|
|
1999
|
+
throw new Error("no default branch");
|
|
2000
|
+
}
|
|
2001
|
+
return project.default_branch;
|
|
2002
|
+
}
|
|
2003
|
+
async function getGitlabReferenceData({ ref, gitlabUrl }, options) {
|
|
2004
|
+
const { projectPath } = parseOwnerAndRepo2(gitlabUrl);
|
|
2005
|
+
const api = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
|
|
2006
|
+
const results = await Promise.allSettled([
|
|
2007
|
+
(async () => {
|
|
2008
|
+
const res = await api.Branches.show(projectPath, ref);
|
|
2009
|
+
return {
|
|
2010
|
+
sha: res.commit.id,
|
|
2011
|
+
type: "BRANCH" /* BRANCH */,
|
|
2012
|
+
date: res.commit.committed_date ? new Date(res.commit.committed_date) : void 0
|
|
2013
|
+
};
|
|
2014
|
+
})(),
|
|
2015
|
+
(async () => {
|
|
2016
|
+
const res = await api.Commits.show(projectPath, ref);
|
|
2017
|
+
return {
|
|
2018
|
+
sha: res.id,
|
|
2019
|
+
type: "COMMIT" /* COMMIT */,
|
|
2020
|
+
date: res.committed_date ? new Date(res.committed_date) : void 0
|
|
2021
|
+
};
|
|
2022
|
+
})(),
|
|
2023
|
+
(async () => {
|
|
2024
|
+
const res = await api.Tags.show(projectPath, ref);
|
|
2025
|
+
return {
|
|
2026
|
+
sha: res.commit.id,
|
|
2027
|
+
type: "TAG" /* TAG */,
|
|
2028
|
+
date: res.commit.committed_date ? new Date(res.commit.committed_date) : void 0
|
|
2029
|
+
};
|
|
2030
|
+
})()
|
|
2031
|
+
]);
|
|
2032
|
+
const [branchRes, commitRes, tagRes] = results;
|
|
2033
|
+
if (tagRes.status === "fulfilled") {
|
|
2034
|
+
return tagRes.value;
|
|
2035
|
+
}
|
|
2036
|
+
if (branchRes.status === "fulfilled") {
|
|
2037
|
+
return branchRes.value;
|
|
2038
|
+
}
|
|
2039
|
+
if (commitRes.status === "fulfilled") {
|
|
2040
|
+
return commitRes.value;
|
|
2041
|
+
}
|
|
2042
|
+
throw new RefNotFoundError(`ref: ${ref} does not exist`);
|
|
2043
|
+
}
|
|
2044
|
+
function parseOwnerAndRepo2(gitlabUrl) {
|
|
2045
|
+
gitlabUrl = removeTrailingSlash2(gitlabUrl);
|
|
2046
|
+
const parsingResult = parseScmURL(gitlabUrl);
|
|
2047
|
+
if (!parsingResult || parsingResult.hostname !== "gitlab.com") {
|
|
2048
|
+
throw new InvalidUrlPatternError(`invalid gitlab repo Url ${gitlabUrl}`);
|
|
2049
|
+
}
|
|
2050
|
+
const { organization, repoName, projectPath } = parsingResult;
|
|
2051
|
+
return { owner: organization, repo: repoName, projectPath };
|
|
2052
|
+
}
|
|
2053
|
+
async function getGitlabBlameRanges({ ref, gitlabUrl, path: path8 }, options) {
|
|
2054
|
+
const { projectPath } = parseOwnerAndRepo2(gitlabUrl);
|
|
2055
|
+
const api = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
|
|
2056
|
+
const resp = await api.RepositoryFiles.allFileBlames(projectPath, path8, ref);
|
|
2057
|
+
let lineNumber = 1;
|
|
2058
|
+
return resp.filter((range) => range.lines).map((range) => {
|
|
2059
|
+
const oldLineNumber = lineNumber;
|
|
2060
|
+
if (!range.lines) {
|
|
2061
|
+
throw new Error("range.lines should not be undefined");
|
|
2062
|
+
}
|
|
2063
|
+
lineNumber += range.lines.length;
|
|
2064
|
+
return {
|
|
2065
|
+
startingLine: oldLineNumber,
|
|
2066
|
+
endingLine: lineNumber - 1,
|
|
2067
|
+
login: range.commit.author_email,
|
|
2068
|
+
email: range.commit.author_email,
|
|
2069
|
+
name: range.commit.author_name
|
|
2070
|
+
};
|
|
2071
|
+
});
|
|
2072
|
+
}
|
|
2073
|
+
var GitlabAuthResultZ = z5.object({
|
|
2074
|
+
access_token: z5.string(),
|
|
2075
|
+
token_type: z5.string(),
|
|
2076
|
+
refresh_token: z5.string()
|
|
2077
|
+
});
|
|
2078
|
+
|
|
2079
|
+
// src/features/analysis/types.ts
|
|
2080
|
+
import { z as z6 } from "zod";
|
|
2081
|
+
var VulReportLocationZ = z6.object({
|
|
2082
|
+
physicalLocation: z6.object({
|
|
2083
|
+
artifactLocation: z6.object({
|
|
2084
|
+
uri: z6.string(),
|
|
2085
|
+
uriBaseId: z6.string(),
|
|
2086
|
+
index: z6.number()
|
|
2087
|
+
}),
|
|
2088
|
+
region: z6.object({
|
|
2089
|
+
startLine: z6.number(),
|
|
2090
|
+
startColumn: z6.number(),
|
|
2091
|
+
endColumn: z6.number()
|
|
2092
|
+
})
|
|
2093
|
+
})
|
|
2094
|
+
});
|
|
2095
|
+
|
|
2096
|
+
// src/features/analysis/utils/get_issue_type.ts
|
|
2097
|
+
var getIssueType = (issueType) => {
|
|
2098
|
+
switch (issueType) {
|
|
2099
|
+
case "SQL_Injection" /* SqlInjection */:
|
|
2100
|
+
return "SQL Injection";
|
|
2101
|
+
case "CMDi_relative_path_command" /* CmDiRelativePathCommand */:
|
|
2102
|
+
return "Relative Path Command Injection";
|
|
2103
|
+
case "CMDi" /* CmDi */:
|
|
2104
|
+
return "Command Injection";
|
|
2105
|
+
case "XXE" /* Xxe */:
|
|
2106
|
+
return "XXE";
|
|
2107
|
+
case "XSS" /* Xss */:
|
|
2108
|
+
return "XSS";
|
|
2109
|
+
case "PT" /* Pt */:
|
|
2110
|
+
return "Path Traversal";
|
|
2111
|
+
case "INSECURE_RANDOMNESS" /* InsecureRandomness */:
|
|
2112
|
+
return "Insecure Randomness";
|
|
2113
|
+
case "SSRF" /* Ssrf */:
|
|
2114
|
+
return "Server Side Request Forgery";
|
|
2115
|
+
case "TYPE_CONFUSION" /* TypeConfusion */:
|
|
2116
|
+
return "Type Confusion";
|
|
2117
|
+
case "REGEX_INJECTION" /* RegexInjection */:
|
|
2118
|
+
return "Regular Expression Injection";
|
|
2119
|
+
case "INCOMPLETE_URL_SANITIZATION" /* IncompleteUrlSanitization */:
|
|
2120
|
+
return "Incomplete URL Sanitization";
|
|
2121
|
+
case "LOG_FORGING" /* LogForging */:
|
|
2122
|
+
return "Log Forging";
|
|
2123
|
+
case "MISSING_CHECK_AGAINST_NULL" /* MissingCheckAgainstNull */:
|
|
2124
|
+
return "Missing Check against Null";
|
|
2125
|
+
case "PASSWORD_IN_COMMENT" /* PasswordInComment */:
|
|
2126
|
+
return "Password in Comment";
|
|
2127
|
+
case "OVERLY_BROAD_CATCH" /* OverlyBroadCatch */:
|
|
2128
|
+
return "Poor Error Handling: Overly Broad Catch";
|
|
2129
|
+
case "USE_OF_SYSTEM_OUTPUT_STREAM" /* UseOfSystemOutputStream */:
|
|
2130
|
+
return "Use of System.out/System.err";
|
|
2131
|
+
case "DANGEROUS_FUNCTION_OVERFLOW" /* DangerousFunctionOverflow */:
|
|
2132
|
+
return "Use of dangerous function";
|
|
2133
|
+
case "DOS_STRING_BUILDER" /* DosStringBuilder */:
|
|
2134
|
+
return "Denial of Service: StringBuilder";
|
|
2135
|
+
case "OPEN_REDIRECT" /* OpenRedirect */:
|
|
2136
|
+
return "Open Redirect";
|
|
2137
|
+
case "WEAK_XML_SCHEMA_UNBOUNDED_OCCURRENCES" /* WeakXmlSchemaUnboundedOccurrences */:
|
|
2138
|
+
return "Weak XML Schema: Unbounded Occurrences";
|
|
2139
|
+
case "SYSTEM_INFORMATION_LEAK" /* SystemInformationLeak */:
|
|
2140
|
+
return "System Information Leak";
|
|
2141
|
+
case "HTTP_RESPONSE_SPLITTING" /* HttpResponseSplitting */:
|
|
2142
|
+
return "HTTP response splitting";
|
|
2143
|
+
case "HTTP_ONLY_COOKIE" /* HttpOnlyCookie */:
|
|
2144
|
+
return "Cookie is not HttpOnly";
|
|
2145
|
+
case "INSECURE_COOKIE" /* InsecureCookie */:
|
|
2146
|
+
return "Insecure Cookie";
|
|
2147
|
+
default: {
|
|
2148
|
+
return issueType ? issueType.replaceAll("_", " ") : "Other";
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
};
|
|
2152
|
+
|
|
2153
|
+
// src/features/analysis/utils/index.ts
|
|
2154
|
+
function getFixUrl({
|
|
2155
|
+
fixId,
|
|
2156
|
+
projectId,
|
|
2157
|
+
organizationId,
|
|
2158
|
+
analysisId
|
|
2159
|
+
}) {
|
|
2160
|
+
return `${WEB_APP_URL}/organization/${organizationId}/project/${projectId}/report/${analysisId}/fix/${fixId}`;
|
|
2161
|
+
}
|
|
2162
|
+
function getCommitUrl({
|
|
2163
|
+
fixId,
|
|
2164
|
+
projectId,
|
|
2165
|
+
organizationId,
|
|
2166
|
+
analysisId,
|
|
2167
|
+
redirectUrl
|
|
2168
|
+
}) {
|
|
2169
|
+
return `${getFixUrl({
|
|
2170
|
+
fixId,
|
|
2171
|
+
projectId,
|
|
2172
|
+
organizationId,
|
|
2173
|
+
analysisId
|
|
2174
|
+
})}/commit?redirect_url=${encodeURIComponent(redirectUrl)}`;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
// src/features/analysis/handle_finished_analysis.ts
|
|
2178
|
+
var debug4 = Debug4("mobbdev:handle-finished-analysis");
|
|
2179
|
+
var MOBB_ICON_IMG = "";
|
|
2180
|
+
var COMMIT_FIX_SVG = `https://felt-laptop-20190711103614-deployment.s3.us-east-1.amazonaws.com/commit-button.svg`;
|
|
2181
|
+
var commitFixButton = (commitUrl) => `<a href="${commitUrl}"><img src=${COMMIT_FIX_SVG}></a>`;
|
|
2182
|
+
function scannerToFriendlyString(scanner) {
|
|
2183
|
+
switch (scanner) {
|
|
2184
|
+
case "checkmarx":
|
|
2185
|
+
return "Checkmarx";
|
|
2186
|
+
case "codeql":
|
|
2187
|
+
return "CodeQL";
|
|
2188
|
+
case "fortify":
|
|
2189
|
+
return "Fortify";
|
|
2190
|
+
case "snyk":
|
|
2191
|
+
return "Snyk";
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
async function handleFinishedAnalysis({
|
|
2195
|
+
analysisId,
|
|
2196
|
+
scm: _scm,
|
|
2197
|
+
gqlClient,
|
|
2198
|
+
githubActionOctokit,
|
|
2199
|
+
scanner
|
|
2200
|
+
}) {
|
|
2201
|
+
if (_scm instanceof GithubSCMLib === false) {
|
|
2202
|
+
return;
|
|
2203
|
+
}
|
|
2204
|
+
const scm = _scm;
|
|
2205
|
+
const res = await gqlClient.getAnalysis(analysisId);
|
|
2206
|
+
const comments = await scm.getPrComments({}, githubActionOctokit);
|
|
2207
|
+
await Promise.all(
|
|
2208
|
+
comments.data.filter((comment) => {
|
|
2209
|
+
return comment.body.includes("fix by Mobb is ready");
|
|
2210
|
+
}).map((comment) => {
|
|
2211
|
+
try {
|
|
2212
|
+
return scm.deleteComment(
|
|
2213
|
+
{ comment_id: comment.id },
|
|
2214
|
+
githubActionOctokit
|
|
2215
|
+
);
|
|
2216
|
+
} catch (e) {
|
|
2217
|
+
debug4("delete comment failed %s", e);
|
|
2218
|
+
return Promise.resolve();
|
|
2219
|
+
}
|
|
2220
|
+
})
|
|
2221
|
+
);
|
|
2222
|
+
const {
|
|
2223
|
+
vulnerabilityReport: {
|
|
2224
|
+
file: {
|
|
2225
|
+
signedFile: { url: vulReportUrl }
|
|
2226
|
+
}
|
|
2227
|
+
},
|
|
2228
|
+
repo: { commitSha, pullRequest }
|
|
2229
|
+
} = res.analysis;
|
|
2230
|
+
const {
|
|
2231
|
+
projectId,
|
|
2232
|
+
project: { organizationId }
|
|
2233
|
+
} = res.analysis.vulnerabilityReport;
|
|
2234
|
+
const vulReportRes = await fetch(vulReportUrl);
|
|
2235
|
+
const vulReport = await vulReportRes.json();
|
|
2236
|
+
return Promise.all(
|
|
2237
|
+
res.analysis.fixes.map((fix) => {
|
|
2238
|
+
const [vulnerabilityReportIssue] = fix.vulnerabilityReportIssues;
|
|
2239
|
+
const issueIndex = parseInt(
|
|
2240
|
+
z7.string().parse(vulnerabilityReportIssue?.vendorIssueId)
|
|
2241
|
+
);
|
|
2242
|
+
const results = vulReport.runs[0]?.results || [];
|
|
2243
|
+
const ruleId = results[issueIndex]?.ruleId;
|
|
2244
|
+
const location = VulReportLocationZ.parse(
|
|
2245
|
+
results[issueIndex]?.locations[0]
|
|
2246
|
+
);
|
|
2247
|
+
const { uri: filePath } = location.physicalLocation.artifactLocation;
|
|
2248
|
+
const { startLine, startColumn, endColumn } = location.physicalLocation.region;
|
|
2249
|
+
const fixLocation = {
|
|
2250
|
+
filePath,
|
|
2251
|
+
startLine,
|
|
2252
|
+
startColumn,
|
|
2253
|
+
endColumn,
|
|
2254
|
+
ruleId
|
|
2255
|
+
};
|
|
2256
|
+
return {
|
|
2257
|
+
fix,
|
|
2258
|
+
fixLocation
|
|
2259
|
+
};
|
|
2260
|
+
}).map(async ({ fix, fixLocation }) => {
|
|
2261
|
+
const { filePath, startLine } = fixLocation;
|
|
2262
|
+
const getFixContent = await gqlClient.getFix(fix.id);
|
|
2263
|
+
const {
|
|
2264
|
+
fix_by_pk: {
|
|
2265
|
+
patchAndQuestions: { patch }
|
|
2266
|
+
}
|
|
2267
|
+
} = getFixContent;
|
|
2268
|
+
const commentRes = await scm.postPrComment(
|
|
2269
|
+
{
|
|
2270
|
+
body: "empty",
|
|
2271
|
+
pull_number: pullRequest,
|
|
2272
|
+
commit_id: commitSha,
|
|
2273
|
+
path: filePath,
|
|
2274
|
+
line: startLine
|
|
2275
|
+
},
|
|
2276
|
+
githubActionOctokit
|
|
2277
|
+
);
|
|
2278
|
+
const commitUrl = getCommitUrl({
|
|
2279
|
+
fixId: fix.id,
|
|
2280
|
+
projectId,
|
|
2281
|
+
analysisId,
|
|
2282
|
+
organizationId,
|
|
2283
|
+
redirectUrl: commentRes.data.html_url
|
|
2284
|
+
});
|
|
2285
|
+
const fixUrl = getFixUrl({
|
|
2286
|
+
fixId: fix.id,
|
|
2287
|
+
projectId,
|
|
2288
|
+
analysisId,
|
|
2289
|
+
organizationId
|
|
2290
|
+
});
|
|
2291
|
+
const scanerString = scannerToFriendlyString(scanner);
|
|
2292
|
+
const issueType = getIssueType(fix.issueType);
|
|
2293
|
+
const title = `# ${MOBB_ICON_IMG} ${issueType} fix by Mobb is ready`;
|
|
2294
|
+
const subTitle = `### Apply the following code change to fix ${issueType} issue detected by ${scanerString}:`;
|
|
2295
|
+
const diff = `\`\`\`diff
|
|
2296
|
+
${patch}
|
|
2297
|
+
\`\`\``;
|
|
2298
|
+
const fixPageLink = `[Learn more and fine tune the fix](${fixUrl})`;
|
|
2299
|
+
await scm.updatePrComment(
|
|
2300
|
+
{
|
|
2301
|
+
body: `${title}
|
|
2302
|
+
${subTitle}
|
|
2303
|
+
${diff}
|
|
2304
|
+
${commitFixButton(
|
|
2305
|
+
commitUrl
|
|
2306
|
+
)}
|
|
2307
|
+
${fixPageLink}`,
|
|
2308
|
+
comment_id: commentRes.data.id
|
|
2309
|
+
},
|
|
2310
|
+
githubActionOctokit
|
|
2311
|
+
);
|
|
2312
|
+
})
|
|
2313
|
+
);
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
// src/features/analysis/pack.ts
|
|
2317
|
+
import fs2 from "node:fs";
|
|
2318
|
+
import path4 from "node:path";
|
|
2319
|
+
import AdmZip from "adm-zip";
|
|
2320
|
+
import Debug5 from "debug";
|
|
2321
|
+
import { globby } from "globby";
|
|
2322
|
+
import { isBinary } from "istextorbinary";
|
|
2323
|
+
var debug5 = Debug5("mobbdev:pack");
|
|
2324
|
+
var MAX_FILE_SIZE = 1024 * 1024 * 5;
|
|
2325
|
+
function endsWithAny(str, suffixes) {
|
|
2326
|
+
return suffixes.some(function(suffix) {
|
|
2327
|
+
return str.endsWith(suffix);
|
|
2328
|
+
});
|
|
2329
|
+
}
|
|
2330
|
+
async function pack(srcDirPath, vulnFiles) {
|
|
2331
|
+
debug5("pack folder %s", srcDirPath);
|
|
2332
|
+
const filepaths = await globby("**", {
|
|
2333
|
+
gitignore: true,
|
|
2334
|
+
onlyFiles: true,
|
|
2335
|
+
cwd: srcDirPath,
|
|
2336
|
+
followSymbolicLinks: false
|
|
2337
|
+
});
|
|
2338
|
+
debug5("files found %d", filepaths.length);
|
|
2339
|
+
const zip = new AdmZip();
|
|
2340
|
+
debug5("compressing files");
|
|
2341
|
+
for (const filepath of filepaths) {
|
|
2342
|
+
const absFilepath = path4.join(srcDirPath, filepath.toString());
|
|
2343
|
+
if (!endsWithAny(
|
|
2344
|
+
absFilepath.toString().replaceAll(path4.win32.sep, path4.posix.sep),
|
|
2345
|
+
vulnFiles
|
|
2346
|
+
)) {
|
|
2347
|
+
debug5("ignoring %s because it is not a vulnerability file", filepath);
|
|
2348
|
+
continue;
|
|
2349
|
+
}
|
|
2350
|
+
if (fs2.lstatSync(absFilepath).size > MAX_FILE_SIZE) {
|
|
2351
|
+
debug5("ignoring %s because the size is > 5MB", filepath);
|
|
2352
|
+
continue;
|
|
2353
|
+
}
|
|
2354
|
+
const data = fs2.readFileSync(absFilepath);
|
|
2355
|
+
if (isBinary(null, data)) {
|
|
2356
|
+
debug5("ignoring %s because is seems to be a binary file", filepath);
|
|
2357
|
+
continue;
|
|
2358
|
+
}
|
|
2359
|
+
zip.addFile(filepath.toString(), data);
|
|
2360
|
+
}
|
|
2361
|
+
debug5("get zip file buffer");
|
|
2362
|
+
return zip.toBuffer();
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
// src/features/analysis/prompts.ts
|
|
2366
|
+
import inquirer from "inquirer";
|
|
2367
|
+
import { createSpinner } from "nanospinner";
|
|
2368
|
+
var scannerChoices = [
|
|
2369
|
+
{ name: "Snyk", value: SCANNERS.Snyk },
|
|
2370
|
+
{ name: "Checkmarx", value: SCANNERS.Checkmarx },
|
|
2371
|
+
{ name: "Codeql", value: SCANNERS.Codeql },
|
|
2372
|
+
{ name: "Fortify", value: SCANNERS.Fortify }
|
|
2373
|
+
];
|
|
2374
|
+
async function choseScanner() {
|
|
2375
|
+
const { scanner } = await inquirer.prompt({
|
|
2376
|
+
name: "scanner",
|
|
2377
|
+
message: "Choose a scanner you wish to use to scan your code",
|
|
2378
|
+
type: "list",
|
|
2379
|
+
choices: scannerChoices
|
|
2380
|
+
});
|
|
2381
|
+
return scanner;
|
|
2382
|
+
}
|
|
2383
|
+
async function tryCheckmarxConfiguarationAgain() {
|
|
2384
|
+
console.log(
|
|
2385
|
+
"\u{1F513} Oops, seems like checkmarx does not accept the current configuration"
|
|
2386
|
+
);
|
|
2387
|
+
const { confirmCheckmarxRetryConfigrations } = await inquirer.prompt({
|
|
2388
|
+
name: "confirmCheckmarxRetryConfigrations",
|
|
2389
|
+
type: "confirm",
|
|
2390
|
+
message: "Would like to try to configure them again? ",
|
|
2391
|
+
default: true
|
|
2392
|
+
});
|
|
2393
|
+
return confirmCheckmarxRetryConfigrations;
|
|
2394
|
+
}
|
|
2395
|
+
async function startCheckmarxConfigationPrompt() {
|
|
2396
|
+
const checkmarxConfigreSpinner = createSpinner(
|
|
2397
|
+
"\u{1F513} Checkmarx needs to be configured before we start, press any key to continue"
|
|
2398
|
+
).start();
|
|
2399
|
+
await keypress();
|
|
2400
|
+
checkmarxConfigreSpinner.success();
|
|
2401
|
+
}
|
|
2402
|
+
async function scmIntegrationPrompt(scmName) {
|
|
2403
|
+
const answers = await inquirer.prompt({
|
|
2404
|
+
name: "scmConfirm",
|
|
2405
|
+
type: "confirm",
|
|
2406
|
+
message: `It seems we don't have access to the repo, do you want to grant access to your ${scmName} account?`,
|
|
2407
|
+
default: true
|
|
2408
|
+
});
|
|
2409
|
+
return answers.scmConfirm;
|
|
2410
|
+
}
|
|
2411
|
+
async function mobbAnalysisPrompt() {
|
|
2412
|
+
const spinner = createSpinner().start();
|
|
2413
|
+
spinner.update({ text: "Hit any key to view available fixes" });
|
|
2414
|
+
await keypress();
|
|
2415
|
+
return spinner.success();
|
|
2416
|
+
}
|
|
2417
|
+
async function snykArticlePrompt() {
|
|
2418
|
+
const { snykArticleConfirm } = await inquirer.prompt({
|
|
2419
|
+
name: "snykArticleConfirm",
|
|
2420
|
+
type: "confirm",
|
|
2421
|
+
message: "Do you want to be taken to the relevant Snyk's online article?",
|
|
2422
|
+
default: true
|
|
2423
|
+
});
|
|
2424
|
+
return snykArticleConfirm;
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
// src/features/analysis/scanners/checkmarx.ts
|
|
2428
|
+
import { createRequire } from "node:module";
|
|
2429
|
+
|
|
2430
|
+
// src/post_install/constants.mjs
|
|
2431
|
+
var cxOperatingSystemSupportMessage = `Your operating system does not support checkmarx.
|
|
2432
|
+
You can see the list of supported operating systems here: https://github.com/Checkmarx/ast-cli#releases`;
|
|
2433
|
+
|
|
2434
|
+
// src/utils/child_process.ts
|
|
2435
|
+
import cp from "node:child_process";
|
|
2436
|
+
import Debug6 from "debug";
|
|
2437
|
+
import * as process2 from "process";
|
|
2438
|
+
import supportsColor from "supports-color";
|
|
2439
|
+
var { stdout: stdout2 } = supportsColor;
|
|
2440
|
+
function createFork({ args, processPath, name }, options) {
|
|
2441
|
+
const child = cp.fork(processPath, args, {
|
|
2442
|
+
stdio: ["inherit", "pipe", "pipe", "ipc"],
|
|
2443
|
+
env: { FORCE_COLOR: stdout2 ? "1" : "0" }
|
|
2444
|
+
});
|
|
2445
|
+
return createChildProcess({ childProcess: child, name }, options);
|
|
1846
2446
|
}
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
if (token?.startsWith("glpat-") || token === "") {
|
|
1854
|
-
return new Gitlab({ token });
|
|
1855
|
-
}
|
|
1856
|
-
return new Gitlab({ oauthToken: token });
|
|
2447
|
+
function createSpwan({ args, processPath, name }, options) {
|
|
2448
|
+
const child = cp.spawn(processPath, args, {
|
|
2449
|
+
stdio: ["inherit", "pipe", "pipe", "ipc"],
|
|
2450
|
+
env: { FORCE_COLOR: stdout2 ? "1" : "0" }
|
|
2451
|
+
});
|
|
2452
|
+
return createChildProcess({ childProcess: child, name }, options);
|
|
1857
2453
|
}
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
const
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
}
|
|
1867
|
-
if (
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
}
|
|
1871
|
-
} catch (e) {
|
|
1872
|
-
const error = e;
|
|
1873
|
-
const code = error.code || error.status || error.statusCode || error.response?.status || error.response?.statusCode || error.response?.code;
|
|
1874
|
-
const description = error.description || `${e}`;
|
|
1875
|
-
if (code === 401 || code === 403 || description.includes("401") || description.includes("403")) {
|
|
1876
|
-
throw new InvalidAccessTokenError(`invalid gitlab access token`);
|
|
2454
|
+
function createChildProcess({ childProcess, name }, options) {
|
|
2455
|
+
const debug10 = Debug6(`mobbdev:${name}`);
|
|
2456
|
+
const { display } = options;
|
|
2457
|
+
return new Promise((resolve, reject) => {
|
|
2458
|
+
let out = "";
|
|
2459
|
+
const onData = (chunk) => {
|
|
2460
|
+
debug10(`chunk received from ${name} std ${chunk}`);
|
|
2461
|
+
out += chunk;
|
|
2462
|
+
};
|
|
2463
|
+
if (!childProcess || !childProcess?.stdout || !childProcess?.stderr) {
|
|
2464
|
+
debug10(`unable to fork ${name}`);
|
|
2465
|
+
reject(new Error(`unable to fork ${name}`));
|
|
1877
2466
|
}
|
|
1878
|
-
|
|
1879
|
-
|
|
2467
|
+
childProcess.stdout?.on("data", onData);
|
|
2468
|
+
childProcess.stderr?.on("data", onData);
|
|
2469
|
+
if (display) {
|
|
2470
|
+
childProcess.stdout?.pipe(process2.stdout);
|
|
2471
|
+
childProcess.stderr?.pipe(process2.stderr);
|
|
1880
2472
|
}
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
}
|
|
1884
|
-
async function getGitlabUsername(accessToken) {
|
|
1885
|
-
const api = getGitBeaker({ gitlabAuthToken: accessToken });
|
|
1886
|
-
const res = await api.Users.showCurrentUser();
|
|
1887
|
-
return res.username;
|
|
1888
|
-
}
|
|
1889
|
-
async function getGitlabIsUserCollaborator({
|
|
1890
|
-
username,
|
|
1891
|
-
accessToken,
|
|
1892
|
-
repoUrl
|
|
1893
|
-
}) {
|
|
1894
|
-
try {
|
|
1895
|
-
const { projectPath } = parseOwnerAndRepo2(repoUrl);
|
|
1896
|
-
const api = getGitBeaker({ gitlabAuthToken: accessToken });
|
|
1897
|
-
const res = await api.Projects.show(projectPath);
|
|
1898
|
-
const members = await api.ProjectMembers.all(res.id, {
|
|
1899
|
-
includeInherited: true
|
|
2473
|
+
childProcess.on("exit", (code) => {
|
|
2474
|
+
debug10(`${name} exit code ${code}`);
|
|
2475
|
+
resolve({ message: out, code });
|
|
1900
2476
|
});
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
}
|
|
1906
|
-
async function getGitlabMergeRequestStatus({
|
|
1907
|
-
accessToken,
|
|
1908
|
-
repoUrl,
|
|
1909
|
-
mrNumber
|
|
1910
|
-
}) {
|
|
1911
|
-
const { projectPath } = parseOwnerAndRepo2(repoUrl);
|
|
1912
|
-
const api = getGitBeaker({ gitlabAuthToken: accessToken });
|
|
1913
|
-
const res = await api.MergeRequests.show(projectPath, mrNumber);
|
|
1914
|
-
switch (res.state) {
|
|
1915
|
-
case "merged" /* merged */:
|
|
1916
|
-
case "opened" /* opened */:
|
|
1917
|
-
case "closed" /* closed */:
|
|
1918
|
-
return res.state;
|
|
1919
|
-
default:
|
|
1920
|
-
throw new Error(`unknown merge request state ${res.state}`);
|
|
1921
|
-
}
|
|
2477
|
+
childProcess.on("error", (err) => {
|
|
2478
|
+
debug10(`${name} error %o`, err);
|
|
2479
|
+
reject(err);
|
|
2480
|
+
});
|
|
2481
|
+
});
|
|
1922
2482
|
}
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
|
|
2483
|
+
|
|
2484
|
+
// src/features/analysis/scanners/checkmarx.ts
|
|
2485
|
+
import chalk2 from "chalk";
|
|
2486
|
+
import Debug7 from "debug";
|
|
2487
|
+
import { existsSync } from "fs";
|
|
2488
|
+
import { createSpinner as createSpinner2 } from "nanospinner";
|
|
2489
|
+
import { type } from "os";
|
|
2490
|
+
import path5 from "path";
|
|
2491
|
+
var debug6 = Debug7("mobbdev:checkmarx");
|
|
2492
|
+
var require2 = createRequire(import.meta.url);
|
|
2493
|
+
var getCheckmarxPath = () => {
|
|
2494
|
+
const os3 = type();
|
|
2495
|
+
const cxFileName = os3 === "Windows_NT" ? "cx.exe" : "cx";
|
|
1930
2496
|
try {
|
|
1931
|
-
|
|
1932
|
-
return res.name === branch;
|
|
2497
|
+
return require2.resolve(`.bin/${cxFileName}`);
|
|
1933
2498
|
} catch (e) {
|
|
1934
|
-
|
|
2499
|
+
throw new CliError(cxOperatingSystemSupportMessage);
|
|
1935
2500
|
}
|
|
2501
|
+
};
|
|
2502
|
+
var getCheckmarxCommandArgs = ({
|
|
2503
|
+
repoPath,
|
|
2504
|
+
branch,
|
|
2505
|
+
fileName,
|
|
2506
|
+
filePath,
|
|
2507
|
+
projectName
|
|
2508
|
+
}) => [
|
|
2509
|
+
"--project-name",
|
|
2510
|
+
projectName,
|
|
2511
|
+
"-s",
|
|
2512
|
+
repoPath,
|
|
2513
|
+
"--branch",
|
|
2514
|
+
branch,
|
|
2515
|
+
"--scan-types",
|
|
2516
|
+
"sast",
|
|
2517
|
+
"--output-path",
|
|
2518
|
+
filePath,
|
|
2519
|
+
"--output-name",
|
|
2520
|
+
fileName,
|
|
2521
|
+
"--report-format",
|
|
2522
|
+
"json"
|
|
2523
|
+
];
|
|
2524
|
+
var VALIDATE_COMMAND = ["auth", "validate"];
|
|
2525
|
+
var CONFIGURE_COMMAND = ["configure"];
|
|
2526
|
+
var SCAN_COMMAND = ["scan", "create"];
|
|
2527
|
+
var CHECKMARX_SUCCESS_CODE = 0;
|
|
2528
|
+
function validateCheckmarxInstallation() {
|
|
2529
|
+
existsSync(getCheckmarxPath());
|
|
1936
2530
|
}
|
|
1937
|
-
async function
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
//disallows us to sort by updated_at in descending order
|
|
1943
|
-
//so we have to sort by updated_at in ascending order.
|
|
1944
|
-
//We can wait for the bug to be fixed or call the api
|
|
1945
|
-
//directly with fetch()
|
|
1946
|
-
sort: "asc",
|
|
1947
|
-
orderBy: "updated_at",
|
|
1948
|
-
perPage: 100
|
|
1949
|
-
});
|
|
1950
|
-
return Promise.all(
|
|
1951
|
-
res.map(async (project) => {
|
|
1952
|
-
const proj = await api.Projects.show(project.id);
|
|
1953
|
-
const owner = proj.namespace.name;
|
|
1954
|
-
const repoLanguages = await api.Projects.showLanguages(project.id);
|
|
1955
|
-
return {
|
|
1956
|
-
repoName: project.path,
|
|
1957
|
-
repoUrl: project.web_url,
|
|
1958
|
-
repoOwner: owner,
|
|
1959
|
-
repoLanguages: Object.keys(repoLanguages),
|
|
1960
|
-
repoIsPublic: project.visibility === "public",
|
|
1961
|
-
repoUpdatedAt: project.last_activity_at
|
|
1962
|
-
};
|
|
1963
|
-
})
|
|
2531
|
+
async function forkCheckmarx(args, { display }) {
|
|
2532
|
+
debug6("fork checkmarx with args %o %s", args.join(" "), display);
|
|
2533
|
+
return createSpwan(
|
|
2534
|
+
{ args, processPath: getCheckmarxPath(), name: "checkmarx" },
|
|
2535
|
+
{ display }
|
|
1964
2536
|
);
|
|
1965
2537
|
}
|
|
1966
|
-
async function
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
sort: "dec"
|
|
1978
|
-
});
|
|
1979
|
-
return res.map((branch) => branch.name);
|
|
1980
|
-
} catch (e) {
|
|
1981
|
-
return [];
|
|
2538
|
+
async function getCheckmarxReport({ reportPath, repositoryRoot, branch, projectName }, { skipPrompts = false }) {
|
|
2539
|
+
debug6("get checkmarx report start %s %s", reportPath, repositoryRoot);
|
|
2540
|
+
const { code: loginCode } = await forkCheckmarx(VALIDATE_COMMAND, {
|
|
2541
|
+
display: false
|
|
2542
|
+
});
|
|
2543
|
+
if (loginCode !== CHECKMARX_SUCCESS_CODE) {
|
|
2544
|
+
if (skipPrompts) {
|
|
2545
|
+
await throwCheckmarxConfigError();
|
|
2546
|
+
}
|
|
2547
|
+
await startCheckmarxConfigationPrompt();
|
|
2548
|
+
await validateCheckamxCredentials();
|
|
1982
2549
|
}
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
const
|
|
1986
|
-
const
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
2550
|
+
const extension = path5.extname(reportPath);
|
|
2551
|
+
const filePath = path5.dirname(reportPath);
|
|
2552
|
+
const fileName = path5.basename(reportPath, extension);
|
|
2553
|
+
const checkmarxCommandArgs = getCheckmarxCommandArgs({
|
|
2554
|
+
repoPath: repositoryRoot,
|
|
2555
|
+
branch,
|
|
2556
|
+
filePath,
|
|
2557
|
+
fileName,
|
|
2558
|
+
projectName
|
|
2559
|
+
});
|
|
2560
|
+
console.log("\u280B \u{1F50D} Initiating Checkmarx Scan ");
|
|
2561
|
+
const { code: scanCode } = await forkCheckmarx(
|
|
2562
|
+
[...SCAN_COMMAND, ...checkmarxCommandArgs],
|
|
1992
2563
|
{
|
|
1993
|
-
|
|
2564
|
+
display: true
|
|
1994
2565
|
}
|
|
1995
2566
|
);
|
|
1996
|
-
|
|
1997
|
-
}
|
|
1998
|
-
|
|
1999
|
-
const api = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
|
|
2000
|
-
const { projectPath } = parseOwnerAndRepo2(repoUrl);
|
|
2001
|
-
const project = await api.Projects.show(projectPath);
|
|
2002
|
-
if (!project.default_branch) {
|
|
2003
|
-
throw new Error("no default branch");
|
|
2567
|
+
if (scanCode !== CHECKMARX_SUCCESS_CODE) {
|
|
2568
|
+
createSpinner2("\u{1F50D} Something went wrong with the checkmarx scan").start().error();
|
|
2569
|
+
throw new CliError();
|
|
2004
2570
|
}
|
|
2005
|
-
|
|
2571
|
+
await createSpinner2("\u{1F50D} Checkmarx Scan completed").start().success();
|
|
2572
|
+
return true;
|
|
2006
2573
|
}
|
|
2007
|
-
async function
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
type: "BRANCH" /* BRANCH */,
|
|
2016
|
-
date: res.commit.committed_date ? new Date(res.commit.committed_date) : void 0
|
|
2017
|
-
};
|
|
2018
|
-
})(),
|
|
2019
|
-
(async () => {
|
|
2020
|
-
const res = await api.Commits.show(projectPath, ref);
|
|
2021
|
-
return {
|
|
2022
|
-
sha: res.id,
|
|
2023
|
-
type: "COMMIT" /* COMMIT */,
|
|
2024
|
-
date: res.committed_date ? new Date(res.committed_date) : void 0
|
|
2025
|
-
};
|
|
2026
|
-
})(),
|
|
2027
|
-
(async () => {
|
|
2028
|
-
const res = await api.Tags.show(projectPath, ref);
|
|
2029
|
-
return {
|
|
2030
|
-
sha: res.commit.id,
|
|
2031
|
-
type: "TAG" /* TAG */,
|
|
2032
|
-
date: res.commit.committed_date ? new Date(res.commit.committed_date) : void 0
|
|
2033
|
-
};
|
|
2034
|
-
})()
|
|
2035
|
-
]);
|
|
2036
|
-
const [branchRes, commitRes, tagRes] = results;
|
|
2037
|
-
if (tagRes.status === "fulfilled") {
|
|
2038
|
-
return tagRes.value;
|
|
2039
|
-
}
|
|
2040
|
-
if (branchRes.status === "fulfilled") {
|
|
2041
|
-
return branchRes.value;
|
|
2042
|
-
}
|
|
2043
|
-
if (commitRes.status === "fulfilled") {
|
|
2044
|
-
return commitRes.value;
|
|
2045
|
-
}
|
|
2046
|
-
throw new RefNotFoundError(`ref: ${ref} does not exist`);
|
|
2574
|
+
async function throwCheckmarxConfigError() {
|
|
2575
|
+
await createSpinner2("\u{1F513} Checkmarx is not configued correctly").start().error();
|
|
2576
|
+
throw new CliError(
|
|
2577
|
+
`Checkmarx is not configued correctly
|
|
2578
|
+
you can configure it by using the ${chalk2.bold(
|
|
2579
|
+
"cx configure"
|
|
2580
|
+
)} command`
|
|
2581
|
+
);
|
|
2047
2582
|
}
|
|
2048
|
-
function
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2583
|
+
async function validateCheckamxCredentials() {
|
|
2584
|
+
console.log(`
|
|
2585
|
+
Here's a suggestion for checkmarx configuation:
|
|
2586
|
+
${chalk2.bold("AST Base URI:")} https://ast.checkmarx.net
|
|
2587
|
+
${chalk2.bold("AST Base Auth URI (IAM):")} https://iam.checkmarx.net
|
|
2588
|
+
`);
|
|
2589
|
+
await forkCheckmarx(CONFIGURE_COMMAND, { display: true });
|
|
2590
|
+
const { code: loginCode } = await forkCheckmarx(VALIDATE_COMMAND, {
|
|
2591
|
+
display: false
|
|
2592
|
+
});
|
|
2593
|
+
if (loginCode !== CHECKMARX_SUCCESS_CODE) {
|
|
2594
|
+
const tryAgain = await tryCheckmarxConfiguarationAgain();
|
|
2595
|
+
if (!tryAgain) {
|
|
2596
|
+
await throwCheckmarxConfigError();
|
|
2597
|
+
}
|
|
2598
|
+
if (await tryCheckmarxConfiguarationAgain()) {
|
|
2599
|
+
validateCheckamxCredentials();
|
|
2600
|
+
}
|
|
2053
2601
|
}
|
|
2054
|
-
|
|
2055
|
-
return { owner: organization, repo: repoName, projectPath };
|
|
2602
|
+
await createSpinner2("\u{1F513} Checkmarx configured successfully!").start().success();
|
|
2056
2603
|
}
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2604
|
+
|
|
2605
|
+
// src/features/analysis/scanners/snyk.ts
|
|
2606
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
2607
|
+
import chalk3 from "chalk";
|
|
2608
|
+
import Debug8 from "debug";
|
|
2609
|
+
import { createSpinner as createSpinner3 } from "nanospinner";
|
|
2610
|
+
import open from "open";
|
|
2611
|
+
var debug7 = Debug8("mobbdev:snyk");
|
|
2612
|
+
var require3 = createRequire2(import.meta.url);
|
|
2613
|
+
var SNYK_PATH = require3.resolve("snyk/bin/snyk");
|
|
2614
|
+
var SNYK_ARTICLE_URL = "https://docs.snyk.io/scan-application-code/snyk-code/getting-started-with-snyk-code/activating-snyk-code-using-the-web-ui/step-1-enabling-the-snyk-code-option";
|
|
2615
|
+
debug7("snyk executable path %s", SNYK_PATH);
|
|
2616
|
+
async function forkSnyk(args, { display }) {
|
|
2617
|
+
debug7("fork snyk with args %o %s", args, display);
|
|
2618
|
+
return createFork(
|
|
2619
|
+
{ args, processPath: SNYK_PATH, name: "checkmarx" },
|
|
2620
|
+
{ display }
|
|
2621
|
+
);
|
|
2622
|
+
}
|
|
2623
|
+
async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
|
|
2624
|
+
debug7("get snyk report start %s %s", reportPath, repoRoot);
|
|
2625
|
+
const config3 = await forkSnyk(["config"], { display: false });
|
|
2626
|
+
const { message: configMessage } = config3;
|
|
2627
|
+
if (!configMessage.includes("api: ")) {
|
|
2628
|
+
const snykLoginSpinner = createSpinner3().start();
|
|
2629
|
+
if (!skipPrompts) {
|
|
2630
|
+
snykLoginSpinner.update({
|
|
2631
|
+
text: "\u{1F513} Login to Snyk is required, press any key to continue"
|
|
2632
|
+
});
|
|
2633
|
+
await keypress();
|
|
2066
2634
|
}
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
}
|
|
2635
|
+
snykLoginSpinner.update({
|
|
2636
|
+
text: "\u{1F513} Waiting for Snyk login to complete"
|
|
2637
|
+
});
|
|
2638
|
+
debug7("no token in the config %s", config3);
|
|
2639
|
+
await forkSnyk(["auth"], { display: true });
|
|
2640
|
+
snykLoginSpinner.success({ text: "\u{1F513} Login to Snyk Successful" });
|
|
2641
|
+
}
|
|
2642
|
+
const snykSpinner = createSpinner3("\u{1F50D} Scanning your repo with Snyk ").start();
|
|
2643
|
+
const { message: scanOutput } = await forkSnyk(
|
|
2644
|
+
["code", "test", `--sarif-file-output=${reportPath}`, repoRoot],
|
|
2645
|
+
{ display: true }
|
|
2646
|
+
);
|
|
2647
|
+
if (scanOutput.includes(
|
|
2648
|
+
"Snyk Code is not supported for org: enable in Settings > Snyk Code"
|
|
2649
|
+
)) {
|
|
2650
|
+
debug7("snyk code is not enabled %s", scanOutput);
|
|
2651
|
+
snykSpinner.error({ text: "\u{1F50D} Snyk configuration needed" });
|
|
2652
|
+
const answer = await snykArticlePrompt();
|
|
2653
|
+
debug7("answer %s", answer);
|
|
2654
|
+
if (answer) {
|
|
2655
|
+
debug7("opening the browser");
|
|
2656
|
+
await open(SNYK_ARTICLE_URL);
|
|
2657
|
+
}
|
|
2658
|
+
console.log(
|
|
2659
|
+
chalk3.bgBlue(
|
|
2660
|
+
"\nPlease enable Snyk Code in your Snyk account and try again."
|
|
2661
|
+
)
|
|
2662
|
+
);
|
|
2663
|
+
throw Error("snyk is not enbabled");
|
|
2664
|
+
}
|
|
2665
|
+
snykSpinner.success({ text: "\u{1F50D} Snyk code scan completed" });
|
|
2666
|
+
return true;
|
|
2076
2667
|
}
|
|
2077
|
-
var GitlabAuthResultZ = z5.object({
|
|
2078
|
-
access_token: z5.string(),
|
|
2079
|
-
token_type: z5.string(),
|
|
2080
|
-
refresh_token: z5.string()
|
|
2081
|
-
});
|
|
2082
2668
|
|
|
2083
2669
|
// src/features/analysis/upload-file.ts
|
|
2084
|
-
import
|
|
2670
|
+
import Debug9 from "debug";
|
|
2085
2671
|
import fetch2, { File, fileFrom, FormData } from "node-fetch";
|
|
2086
|
-
var
|
|
2672
|
+
var debug8 = Debug9("mobbdev:upload-file");
|
|
2087
2673
|
async function uploadFile({
|
|
2088
2674
|
file,
|
|
2089
2675
|
url,
|
|
2090
2676
|
uploadKey,
|
|
2091
2677
|
uploadFields
|
|
2092
2678
|
}) {
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2679
|
+
debug8("upload file start %s", url);
|
|
2680
|
+
debug8("upload fields %o", uploadFields);
|
|
2681
|
+
debug8("upload key %s", uploadKey);
|
|
2096
2682
|
const form = new FormData();
|
|
2097
2683
|
Object.entries(uploadFields).forEach(([key, value]) => {
|
|
2098
2684
|
form.append(key, value);
|
|
@@ -2101,10 +2687,10 @@ async function uploadFile({
|
|
|
2101
2687
|
form.append("key", uploadKey);
|
|
2102
2688
|
}
|
|
2103
2689
|
if (typeof file === "string") {
|
|
2104
|
-
|
|
2690
|
+
debug8("upload file from path %s", file);
|
|
2105
2691
|
form.append("file", await fileFrom(file));
|
|
2106
2692
|
} else {
|
|
2107
|
-
|
|
2693
|
+
debug8("upload file from buffer");
|
|
2108
2694
|
form.append("file", new File([file], "file"));
|
|
2109
2695
|
}
|
|
2110
2696
|
const response = await fetch2(url, {
|
|
@@ -2112,10 +2698,10 @@ async function uploadFile({
|
|
|
2112
2698
|
body: form
|
|
2113
2699
|
});
|
|
2114
2700
|
if (!response.ok) {
|
|
2115
|
-
|
|
2701
|
+
debug8("error from S3 %s %s", response.body, response.status);
|
|
2116
2702
|
throw new Error(`Failed to upload the file: ${response.status}`);
|
|
2117
2703
|
}
|
|
2118
|
-
|
|
2704
|
+
debug8("upload file done");
|
|
2119
2705
|
}
|
|
2120
2706
|
|
|
2121
2707
|
// src/features/analysis/index.ts
|
|
@@ -2132,7 +2718,7 @@ async function downloadRepo({
|
|
|
2132
2718
|
}) {
|
|
2133
2719
|
const { createSpinner: createSpinner4 } = Spinner2({ ci });
|
|
2134
2720
|
const repoSpinner = createSpinner4("\u{1F4BE} Downloading Repo").start();
|
|
2135
|
-
|
|
2721
|
+
debug9("download repo %s %s %s", repoUrl, dirname);
|
|
2136
2722
|
const zipFilePath = path6.join(dirname, "repo.zip");
|
|
2137
2723
|
const response = await fetch3(downloadUrl, {
|
|
2138
2724
|
method: "GET",
|
|
@@ -2141,7 +2727,7 @@ async function downloadRepo({
|
|
|
2141
2727
|
}
|
|
2142
2728
|
});
|
|
2143
2729
|
if (!response.ok) {
|
|
2144
|
-
|
|
2730
|
+
debug9("SCM zipball request failed %s %s", response.body, response.status);
|
|
2145
2731
|
repoSpinner.error({ text: "\u{1F4BE} Repo download failed" });
|
|
2146
2732
|
throw new Error(`Can't access ${chalk4.bold(repoUrl)}`);
|
|
2147
2733
|
}
|
|
@@ -2155,7 +2741,7 @@ async function downloadRepo({
|
|
|
2155
2741
|
if (!repoRoot) {
|
|
2156
2742
|
throw new Error("Repo root not found");
|
|
2157
2743
|
}
|
|
2158
|
-
|
|
2744
|
+
debug9("repo root %s", repoRoot);
|
|
2159
2745
|
repoSpinner.success({ text: "\u{1F4BE} Repo downloaded successfully" });
|
|
2160
2746
|
return path6.join(dirname, repoRoot);
|
|
2161
2747
|
}
|
|
@@ -2164,7 +2750,7 @@ var LOGIN_CHECK_DELAY = 5 * 1e3;
|
|
|
2164
2750
|
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(
|
|
2165
2751
|
"press any key to continue"
|
|
2166
2752
|
)};`;
|
|
2167
|
-
var tmpObj =
|
|
2753
|
+
var tmpObj = tmp2.dirSync({
|
|
2168
2754
|
unsafeCleanup: true
|
|
2169
2755
|
});
|
|
2170
2756
|
var getReportUrl = ({
|
|
@@ -2172,7 +2758,7 @@ var getReportUrl = ({
|
|
|
2172
2758
|
projectId,
|
|
2173
2759
|
fixReportId
|
|
2174
2760
|
}) => `${WEB_APP_URL}/organization/${organizationId}/project/${projectId}/report/${fixReportId}`;
|
|
2175
|
-
var
|
|
2761
|
+
var debug9 = Debug10("mobbdev:index");
|
|
2176
2762
|
var packageJson = JSON.parse(
|
|
2177
2763
|
fs3.readFileSync(path6.join(getDirName2(), "../package.json"), "utf8")
|
|
2178
2764
|
);
|
|
@@ -2182,7 +2768,7 @@ if (!semver.satisfies(process.version, packageJson.engines.node)) {
|
|
|
2182
2768
|
);
|
|
2183
2769
|
}
|
|
2184
2770
|
var config2 = new Configstore(packageJson.name, { apiToken: "" });
|
|
2185
|
-
|
|
2771
|
+
debug9("config %o", config2);
|
|
2186
2772
|
async function runAnalysis(params, options) {
|
|
2187
2773
|
try {
|
|
2188
2774
|
await _scan(
|
|
@@ -2196,20 +2782,23 @@ async function runAnalysis(params, options) {
|
|
|
2196
2782
|
tmpObj.removeCallback();
|
|
2197
2783
|
}
|
|
2198
2784
|
}
|
|
2199
|
-
async function _scan({
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2785
|
+
async function _scan(params, { skipPrompts = false } = {}) {
|
|
2786
|
+
const {
|
|
2787
|
+
dirname,
|
|
2788
|
+
repo,
|
|
2789
|
+
scanFile,
|
|
2790
|
+
apiKey,
|
|
2791
|
+
ci,
|
|
2792
|
+
srcPath,
|
|
2793
|
+
commitHash,
|
|
2794
|
+
ref,
|
|
2795
|
+
scanner,
|
|
2796
|
+
cxProjectName,
|
|
2797
|
+
mobbProjectName,
|
|
2798
|
+
githubToken: githubActionToken,
|
|
2799
|
+
command
|
|
2800
|
+
} = params;
|
|
2801
|
+
debug9("start %s %s", dirname, repo);
|
|
2213
2802
|
const { createSpinner: createSpinner4 } = Spinner2({ ci });
|
|
2214
2803
|
skipPrompts = skipPrompts || ci;
|
|
2215
2804
|
let gqlClient = new GQLClient({
|
|
@@ -2265,9 +2854,9 @@ async function _scan({
|
|
|
2265
2854
|
});
|
|
2266
2855
|
const reference = ref ?? await scm.getRepoDefaultBranch();
|
|
2267
2856
|
const { sha } = await scm.getReferenceData(reference);
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2857
|
+
debug9("org id %s", organizationId);
|
|
2858
|
+
debug9("project id %s", projectId);
|
|
2859
|
+
debug9("default branch %s", reference);
|
|
2271
2860
|
const repositoryRoot = await downloadRepo({
|
|
2272
2861
|
repoUrl: repo,
|
|
2273
2862
|
dirname,
|
|
@@ -2275,8 +2864,8 @@ async function _scan({
|
|
|
2275
2864
|
authHeaders: scm.getAuthHeaders(),
|
|
2276
2865
|
downloadUrl: scm.getDownloadUrl(sha)
|
|
2277
2866
|
});
|
|
2278
|
-
if (
|
|
2279
|
-
reportPath = await getReport(scanner);
|
|
2867
|
+
if (command === "scan") {
|
|
2868
|
+
reportPath = await getReport(SupportedScannersZ.parse(scanner));
|
|
2280
2869
|
}
|
|
2281
2870
|
if (!reportPath) {
|
|
2282
2871
|
throw new Error("reportPath is null");
|
|
@@ -2295,23 +2884,43 @@ async function _scan({
|
|
|
2295
2884
|
}
|
|
2296
2885
|
uploadReportSpinner.success({ text: "\u{1F4C1} Report uploaded successfully" });
|
|
2297
2886
|
const mobbSpinner = createSpinner4("\u{1F575}\uFE0F\u200D\u2642\uFE0F Initiating Mobb analysis").start();
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2887
|
+
const sendReportRes = await sendReport();
|
|
2888
|
+
if (command === "review") {
|
|
2889
|
+
await gqlClient.subscribeToAnalysis(
|
|
2890
|
+
{ analysisId: sendReportRes.submitVulnerabilityReport.fixReportId },
|
|
2891
|
+
(analysisId) => handleFinishedAnalysis({
|
|
2892
|
+
analysisId,
|
|
2893
|
+
gqlClient,
|
|
2894
|
+
scm,
|
|
2895
|
+
githubActionOctokit: new Octokit3({ auth: githubActionToken }),
|
|
2896
|
+
scanner: z8.nativeEnum(SCANNERS).parse(scanner)
|
|
2897
|
+
})
|
|
2898
|
+
);
|
|
2310
2899
|
}
|
|
2311
2900
|
mobbSpinner.success({
|
|
2312
2901
|
text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Generating fixes..."
|
|
2313
2902
|
});
|
|
2314
2903
|
await askToOpenAnalysis();
|
|
2904
|
+
async function sendReport() {
|
|
2905
|
+
try {
|
|
2906
|
+
const sumbitRes = await gqlClient.submitVulnerabilityReport({
|
|
2907
|
+
fixReportId: reportUploadInfo.fixReportId,
|
|
2908
|
+
repoUrl: z8.string().parse(repo),
|
|
2909
|
+
reference,
|
|
2910
|
+
projectId,
|
|
2911
|
+
vulnerabilityReportFileName: "report.json",
|
|
2912
|
+
sha,
|
|
2913
|
+
pullRequest: params.pullRequest
|
|
2914
|
+
});
|
|
2915
|
+
if (sumbitRes.submitVulnerabilityReport.__typename !== "VulnerabilityReport") {
|
|
2916
|
+
throw new Error("\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed");
|
|
2917
|
+
}
|
|
2918
|
+
return sumbitRes;
|
|
2919
|
+
} catch (e) {
|
|
2920
|
+
mobbSpinner.error({ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed" });
|
|
2921
|
+
throw e;
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2315
2924
|
async function getReport(scanner2) {
|
|
2316
2925
|
const reportPath2 = path6.join(dirname, "report.json");
|
|
2317
2926
|
switch (scanner2) {
|
|
@@ -2342,7 +2951,6 @@ async function _scan({
|
|
|
2342
2951
|
fixReportId: reportUploadInfo.fixReportId
|
|
2343
2952
|
});
|
|
2344
2953
|
!ci && console.log("You can access the analysis at: \n");
|
|
2345
|
-
console.log(chalk4.bold(reportUrl));
|
|
2346
2954
|
!skipPrompts && await mobbAnalysisPrompt();
|
|
2347
2955
|
!ci && open2(reportUrl);
|
|
2348
2956
|
!ci && console.log(
|
|
@@ -2387,9 +2995,9 @@ async function _scan({
|
|
|
2387
2995
|
});
|
|
2388
2996
|
loginSpinner.spin();
|
|
2389
2997
|
if (encryptedApiToken) {
|
|
2390
|
-
|
|
2998
|
+
debug9("encrypted API token received %s", encryptedApiToken);
|
|
2391
2999
|
newApiToken = crypto.privateDecrypt(privateKey, Buffer.from(encryptedApiToken, "base64")).toString("utf-8");
|
|
2392
|
-
|
|
3000
|
+
debug9("API token decrypted");
|
|
2393
3001
|
break;
|
|
2394
3002
|
}
|
|
2395
3003
|
await sleep(LOGIN_CHECK_DELAY);
|
|
@@ -2402,7 +3010,7 @@ async function _scan({
|
|
|
2402
3010
|
}
|
|
2403
3011
|
gqlClient = new GQLClient({ apiKey: newApiToken });
|
|
2404
3012
|
if (await gqlClient.verifyToken()) {
|
|
2405
|
-
|
|
3013
|
+
debug9("set api token %s", newApiToken);
|
|
2406
3014
|
config2.set("apiToken", newApiToken);
|
|
2407
3015
|
loginSpinner.success({ text: "\u{1F513} Login to Mobb successful!" });
|
|
2408
3016
|
} else {
|
|
@@ -2526,6 +3134,35 @@ async function _scan({
|
|
|
2526
3134
|
|
|
2527
3135
|
// src/commands/index.ts
|
|
2528
3136
|
import chalkAnimation from "chalk-animation";
|
|
3137
|
+
async function review(params, { skipPrompts = true } = {}) {
|
|
3138
|
+
const {
|
|
3139
|
+
repo,
|
|
3140
|
+
f: scanFile,
|
|
3141
|
+
ref,
|
|
3142
|
+
apiKey,
|
|
3143
|
+
commitHash,
|
|
3144
|
+
mobbProjectName,
|
|
3145
|
+
pullRequest,
|
|
3146
|
+
githubToken,
|
|
3147
|
+
scanner
|
|
3148
|
+
} = params;
|
|
3149
|
+
await runAnalysis(
|
|
3150
|
+
{
|
|
3151
|
+
repo,
|
|
3152
|
+
scanFile,
|
|
3153
|
+
ref,
|
|
3154
|
+
apiKey,
|
|
3155
|
+
ci: true,
|
|
3156
|
+
commitHash,
|
|
3157
|
+
mobbProjectName,
|
|
3158
|
+
pullRequest,
|
|
3159
|
+
githubToken,
|
|
3160
|
+
scanner,
|
|
3161
|
+
command: "review"
|
|
3162
|
+
},
|
|
3163
|
+
{ skipPrompts }
|
|
3164
|
+
);
|
|
3165
|
+
}
|
|
2529
3166
|
async function analyze({
|
|
2530
3167
|
repo,
|
|
2531
3168
|
f: scanFile,
|
|
@@ -2546,7 +3183,8 @@ async function analyze({
|
|
|
2546
3183
|
ci,
|
|
2547
3184
|
commitHash,
|
|
2548
3185
|
mobbProjectName,
|
|
2549
|
-
srcPath
|
|
3186
|
+
srcPath,
|
|
3187
|
+
command: "analyze"
|
|
2550
3188
|
},
|
|
2551
3189
|
{ skipPrompts }
|
|
2552
3190
|
);
|
|
@@ -2565,7 +3203,7 @@ async function scan(scanOptions, { skipPrompts = false } = {}) {
|
|
|
2565
3203
|
throw new CliError(errorMessages.missingCxProjectName);
|
|
2566
3204
|
}
|
|
2567
3205
|
await runAnalysis(
|
|
2568
|
-
{ ...scanOptions, scanner: selectedScanner },
|
|
3206
|
+
{ ...scanOptions, scanner: selectedScanner, command: "scan" },
|
|
2569
3207
|
{ skipPrompts }
|
|
2570
3208
|
);
|
|
2571
3209
|
}
|
|
@@ -2631,7 +3269,7 @@ var commitHashOption = {
|
|
|
2631
3269
|
// src/args/validation.ts
|
|
2632
3270
|
import chalk6 from "chalk";
|
|
2633
3271
|
import path7 from "path";
|
|
2634
|
-
import { z as
|
|
3272
|
+
import { z as z9 } from "zod";
|
|
2635
3273
|
function throwRepoUrlErrorMessage({
|
|
2636
3274
|
error,
|
|
2637
3275
|
repoUrl,
|
|
@@ -2648,7 +3286,7 @@ Example:
|
|
|
2648
3286
|
)}`;
|
|
2649
3287
|
throw new CliError(formattedErrorMessage);
|
|
2650
3288
|
}
|
|
2651
|
-
var UrlZ =
|
|
3289
|
+
var UrlZ = z9.string({
|
|
2652
3290
|
invalid_type_error: "is not a valid GitHub / GitLab URL"
|
|
2653
3291
|
}).refine((data) => !!parseScmURL(data), {
|
|
2654
3292
|
message: "is not a valid GitHub / GitLab URL"
|
|
@@ -2728,6 +3366,49 @@ async function analyzeHandler(args) {
|
|
|
2728
3366
|
await analyze(args, { skipPrompts: args.yes });
|
|
2729
3367
|
}
|
|
2730
3368
|
|
|
3369
|
+
// src/args/commands/review.ts
|
|
3370
|
+
import fs5 from "node:fs";
|
|
3371
|
+
import chalk8 from "chalk";
|
|
3372
|
+
function reviewBuilder(yargs2) {
|
|
3373
|
+
return yargs2.option("f", {
|
|
3374
|
+
alias: "scan-file",
|
|
3375
|
+
demandOption: true,
|
|
3376
|
+
type: "string",
|
|
3377
|
+
describe: chalk8.bold(
|
|
3378
|
+
"Select the vulnerability report to analyze (Checkmarx, Snyk, Fortify, CodeQL)"
|
|
3379
|
+
)
|
|
3380
|
+
}).option("repo", { ...repoOption, demandOption: true }).option("scanner", { ...scannerOptions, demandOption: true }).option("ref", { ...refOption, demandOption: true }).option("ch", {
|
|
3381
|
+
alias: "commit-hash",
|
|
3382
|
+
describe: chalk8.bold("Hash of the commit"),
|
|
3383
|
+
type: "string",
|
|
3384
|
+
demandOption: true
|
|
3385
|
+
}).option("mobb-project-name", mobbProjectNameOption).option("api-key", { ...apiKeyOption, demandOption: true }).option("commit-hash", { ...commitHashOption, demandOption: true }).option("github-token", {
|
|
3386
|
+
describe: chalk8.bold("Github action token"),
|
|
3387
|
+
type: "string",
|
|
3388
|
+
demandOption: true
|
|
3389
|
+
}).option("pull-request", {
|
|
3390
|
+
alias: "pr",
|
|
3391
|
+
describe: chalk8.bold("Number of the pull request"),
|
|
3392
|
+
type: "number",
|
|
3393
|
+
demandOption: true
|
|
3394
|
+
}).example(
|
|
3395
|
+
"$0 review -r https://github.com/WebGoat/WebGoat -f <your_vulirabitliy_report_path> --ch <pr_last_commit> --pr <pr_number> --ref <pr_branch_name> --api-key <api_key>",
|
|
3396
|
+
"add fixes to your pr"
|
|
3397
|
+
).help();
|
|
3398
|
+
}
|
|
3399
|
+
function validateReviewOptions(argv) {
|
|
3400
|
+
if (!fs5.existsSync(argv.f)) {
|
|
3401
|
+
throw new CliError(`
|
|
3402
|
+
Can't access ${chalk8.bold(argv.f)}`);
|
|
3403
|
+
}
|
|
3404
|
+
validateRepoUrl(argv);
|
|
3405
|
+
validateReportFileFormat(argv.f);
|
|
3406
|
+
}
|
|
3407
|
+
async function reviewHandler(args) {
|
|
3408
|
+
validateReviewOptions(args);
|
|
3409
|
+
await review(args, { skipPrompts: true });
|
|
3410
|
+
}
|
|
3411
|
+
|
|
2731
3412
|
// src/args/commands/scan.ts
|
|
2732
3413
|
function scanBuilder(args) {
|
|
2733
3414
|
return args.coerce("scanner", (arg) => arg.toLowerCase()).option("repo", repoOption).option("ref", refOption).option("scanner", scannerOptions).option("mobb-project-name", mobbProjectNameOption).option("y", yesOption).option("ci", ciOption).option("api-key", apiKeyOption).option("cx-project-name", projectNameOption).example(
|
|
@@ -2756,32 +3437,39 @@ async function scanHandler(args) {
|
|
|
2756
3437
|
var parseArgs = async (args) => {
|
|
2757
3438
|
const yargsInstance = yargs(args);
|
|
2758
3439
|
return yargsInstance.updateStrings({
|
|
2759
|
-
"Commands:":
|
|
2760
|
-
"Options:":
|
|
2761
|
-
"Examples:":
|
|
2762
|
-
"Show help":
|
|
3440
|
+
"Commands:": chalk9.yellow.underline.bold("Commands:"),
|
|
3441
|
+
"Options:": chalk9.yellow.underline.bold("Options:"),
|
|
3442
|
+
"Examples:": chalk9.yellow.underline.bold("Examples:"),
|
|
3443
|
+
"Show help": chalk9.bold("Show help")
|
|
2763
3444
|
}).usage(
|
|
2764
|
-
`${
|
|
3445
|
+
`${chalk9.bold(
|
|
2765
3446
|
"\n Bugsy - Trusted, Automatic Vulnerability Fixer \u{1F575}\uFE0F\u200D\u2642\uFE0F\n\n"
|
|
2766
|
-
)} ${
|
|
2767
|
-
$0 ${
|
|
3447
|
+
)} ${chalk9.yellow.underline.bold("Usage:")}
|
|
3448
|
+
$0 ${chalk9.green(
|
|
2768
3449
|
"<command>"
|
|
2769
|
-
)} ${
|
|
3450
|
+
)} ${chalk9.dim("[options]")}
|
|
2770
3451
|
`
|
|
2771
3452
|
).version(false).command(
|
|
2772
|
-
|
|
2773
|
-
|
|
3453
|
+
mobbCliCommand.scan,
|
|
3454
|
+
chalk9.bold(
|
|
2774
3455
|
"Scan your code for vulnerabilities, get automated fixes right away."
|
|
2775
3456
|
),
|
|
2776
3457
|
scanBuilder,
|
|
2777
3458
|
scanHandler
|
|
2778
3459
|
).command(
|
|
2779
|
-
|
|
2780
|
-
|
|
3460
|
+
mobbCliCommand.analyze,
|
|
3461
|
+
chalk9.bold(
|
|
2781
3462
|
"Provide a vulnerability report and relevant code repository, get automated fixes right away."
|
|
2782
3463
|
),
|
|
2783
3464
|
analyzeBuilder,
|
|
2784
3465
|
analyzeHandler
|
|
3466
|
+
).command(
|
|
3467
|
+
mobbCliCommand.review,
|
|
3468
|
+
chalk9.bold(
|
|
3469
|
+
"(beta) Mobb will review your github pull requests and provide comments with fixes "
|
|
3470
|
+
),
|
|
3471
|
+
reviewBuilder,
|
|
3472
|
+
reviewHandler
|
|
2785
3473
|
).example(
|
|
2786
3474
|
"$0 scan -r https://github.com/WebGoat/WebGoat",
|
|
2787
3475
|
"Scan an existing repository"
|
|
@@ -2790,7 +3478,7 @@ var parseArgs = async (args) => {
|
|
|
2790
3478
|
handler() {
|
|
2791
3479
|
yargsInstance.showHelp();
|
|
2792
3480
|
}
|
|
2793
|
-
}).strictOptions().help("h").alias("h", "help").epilog(
|
|
3481
|
+
}).strictOptions().help("h").alias("h", "help").epilog(chalk9.bgBlue("Made with \u2764\uFE0F by Mobb")).showHelpOnFail(true).wrap(Math.min(120, yargsInstance.terminalWidth())).parse();
|
|
2794
3482
|
};
|
|
2795
3483
|
|
|
2796
3484
|
// src/index.ts
|