mobbdev 0.0.61 → 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 +1676 -945
- 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,836 +805,608 @@ 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
|
-
}
|
|
641
|
-
|
|
642
|
-
// src/features/analysis/prompts.ts
|
|
643
|
-
import inquirer from "inquirer";
|
|
644
|
-
import { createSpinner } from "nanospinner";
|
|
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;
|
|
671
974
|
}
|
|
672
|
-
async function
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
await keypress();
|
|
677
|
-
checkmarxConfigreSpinner.success();
|
|
975
|
+
async function getGithubUsername(accessToken) {
|
|
976
|
+
const oktoKit = getOktoKit({ githubAuthToken: accessToken });
|
|
977
|
+
const res = await oktoKit.rest.users.getAuthenticated();
|
|
978
|
+
return res.data.login;
|
|
678
979
|
}
|
|
679
|
-
async function
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
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;
|
|
996
|
+
}
|
|
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
|
-
import { z as z3 } from "zod";
|
|
955
|
-
function removeTrailingSlash(str) {
|
|
956
|
-
return str.trim().replace(/\/+$/, "");
|
|
1238
|
+
// src/features/analysis/scm/github/github-v2.ts
|
|
1239
|
+
function postPrComment(client, params) {
|
|
1240
|
+
return client.request(POST_COMMENT_PATH, params);
|
|
957
1241
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
});
|
|
961
|
-
var { GITHUB_API_TOKEN } = EnvVariablesZod.parse(process.env);
|
|
962
|
-
var GetBlameDocument = `
|
|
963
|
-
query GetBlame(
|
|
964
|
-
$owner: String!
|
|
965
|
-
$repo: String!
|
|
966
|
-
$ref: String!
|
|
967
|
-
$path: String!
|
|
968
|
-
) {
|
|
969
|
-
repository(name: $repo, owner: $owner) {
|
|
970
|
-
# branch name
|
|
971
|
-
object(expression: $ref) {
|
|
972
|
-
# cast Target to a Commit
|
|
973
|
-
... on Commit {
|
|
974
|
-
# full repo-relative path to blame file
|
|
975
|
-
blame(path: $path) {
|
|
976
|
-
ranges {
|
|
977
|
-
commit {
|
|
978
|
-
author {
|
|
979
|
-
user {
|
|
980
|
-
name
|
|
981
|
-
login
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
authoredDate
|
|
985
|
-
}
|
|
986
|
-
startingLine
|
|
987
|
-
endingLine
|
|
988
|
-
age
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
`;
|
|
997
|
-
var githubUrlRegex = /^http[s]?:\/\/[^/\s]+\/([^/.\s]+\/[^/.\s]+)(\.git)?(\/)?$/i;
|
|
998
|
-
function getOktoKit(options) {
|
|
999
|
-
const token = options?.githubAuthToken ?? GITHUB_API_TOKEN ?? "";
|
|
1000
|
-
return new Octokit({ auth: token });
|
|
1242
|
+
function updatePrComment(client, params) {
|
|
1243
|
+
return client.request(UPDATE_COMMENT_PATH, params);
|
|
1001
1244
|
}
|
|
1002
|
-
|
|
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
|
+
}
|
|
1251
|
+
|
|
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();
|
|
1003
1261
|
try {
|
|
1004
|
-
const
|
|
1005
|
-
if (
|
|
1006
|
-
|
|
1007
|
-
}
|
|
1008
|
-
if (url) {
|
|
1009
|
-
const { owner, repo } = parseOwnerAndRepo(url);
|
|
1010
|
-
await oktoKit.rest.repos.get({ repo, owner });
|
|
1262
|
+
const res = await git.raw(["check-ref-format", "--branch", branchName]);
|
|
1263
|
+
if (res) {
|
|
1264
|
+
return true;
|
|
1011
1265
|
}
|
|
1266
|
+
return false;
|
|
1012
1267
|
} catch (e) {
|
|
1013
|
-
|
|
1014
|
-
const code = error.status || error.statusCode || error.response?.status || error.response?.statusCode || error.response?.code;
|
|
1015
|
-
if (code === 401 || code === 403) {
|
|
1016
|
-
throw new InvalidAccessTokenError(`invalid github access token`);
|
|
1017
|
-
}
|
|
1018
|
-
if (code === 404) {
|
|
1019
|
-
throw new InvalidRepoUrlError(`invalid github repo Url ${url}`);
|
|
1020
|
-
}
|
|
1021
|
-
throw e;
|
|
1268
|
+
return false;
|
|
1022
1269
|
}
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1270
|
+
};
|
|
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"
|
|
1285
|
+
};
|
|
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()
|
|
1326
|
+
});
|
|
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;
|
|
1043
1333
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
async function getGithubPullRequestStatus(accessToken, repoUrl, prNumber) {
|
|
1047
|
-
const { owner, repo } = parseOwnerAndRepo(repoUrl);
|
|
1048
|
-
const oktoKit = getOktoKit({ githubAuthToken: accessToken });
|
|
1049
|
-
const res = await oktoKit.rest.pulls.get({
|
|
1050
|
-
owner,
|
|
1051
|
-
repo,
|
|
1052
|
-
pull_number: prNumber
|
|
1053
|
-
});
|
|
1054
|
-
if (res.data.merged) {
|
|
1055
|
-
return "merged";
|
|
1334
|
+
if (url.toLowerCase().startsWith("https://gitlab.com/")) {
|
|
1335
|
+
return "GITLAB" /* GITLAB */;
|
|
1056
1336
|
}
|
|
1057
|
-
if (
|
|
1058
|
-
return "
|
|
1337
|
+
if (url.toLowerCase().startsWith("https://github.com/")) {
|
|
1338
|
+
return "GITHUB" /* GITHUB */;
|
|
1059
1339
|
}
|
|
1060
|
-
return
|
|
1340
|
+
return void 0;
|
|
1061
1341
|
}
|
|
1062
|
-
async function
|
|
1063
|
-
|
|
1064
|
-
|
|
1342
|
+
async function scmCanReachRepo({
|
|
1343
|
+
repoUrl,
|
|
1344
|
+
githubToken,
|
|
1345
|
+
gitlabToken
|
|
1346
|
+
}) {
|
|
1065
1347
|
try {
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
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
|
|
1070
1353
|
});
|
|
1071
|
-
return
|
|
1354
|
+
return true;
|
|
1072
1355
|
} catch (e) {
|
|
1073
1356
|
return false;
|
|
1074
1357
|
}
|
|
1075
1358
|
}
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
const githubRepos = await getRepos(oktoKit);
|
|
1080
|
-
return githubRepos.map(
|
|
1081
|
-
(repo) => {
|
|
1082
|
-
const repoLanguages = [];
|
|
1083
|
-
if (repo.language) {
|
|
1084
|
-
repoLanguages.push(repo.language);
|
|
1085
|
-
}
|
|
1086
|
-
return {
|
|
1087
|
-
repoName: repo.name,
|
|
1088
|
-
repoUrl: repo.html_url,
|
|
1089
|
-
repoOwner: repo.owner.login,
|
|
1090
|
-
repoLanguages,
|
|
1091
|
-
repoIsPublic: !repo.private,
|
|
1092
|
-
repoUpdatedAt: repo.updated_at
|
|
1093
|
-
};
|
|
1094
|
-
}
|
|
1095
|
-
);
|
|
1096
|
-
} catch (e) {
|
|
1097
|
-
if (e instanceof RequestError && e.status === 401) {
|
|
1098
|
-
return [];
|
|
1099
|
-
}
|
|
1100
|
-
if (e instanceof RequestError && e.status === 404) {
|
|
1101
|
-
return [];
|
|
1102
|
-
}
|
|
1103
|
-
throw e;
|
|
1359
|
+
var InvalidRepoUrlError = class extends Error {
|
|
1360
|
+
constructor(m) {
|
|
1361
|
+
super(m);
|
|
1104
1362
|
}
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
"
|
|
1136
|
-
|
|
1363
|
+
};
|
|
1364
|
+
var InvalidAccessTokenError = class extends Error {
|
|
1365
|
+
constructor(m) {
|
|
1366
|
+
super(m);
|
|
1367
|
+
}
|
|
1368
|
+
};
|
|
1369
|
+
var InvalidUrlPatternError = class extends Error {
|
|
1370
|
+
constructor(m) {
|
|
1371
|
+
super(m);
|
|
1372
|
+
}
|
|
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");
|
|
1137
1395
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
getBranch({ owner, repo, branch: ref }, oktoKit).then((result) => ({
|
|
1153
|
-
date: result.data.commit.commit.committer?.date ? new Date(result.data.commit.commit.committer?.date) : void 0,
|
|
1154
|
-
type: "BRANCH" /* BRANCH */,
|
|
1155
|
-
sha: result.data.commit.sha
|
|
1156
|
-
})),
|
|
1157
|
-
getCommit({ commitSha: ref, repo, owner }, oktoKit).then((commit) => ({
|
|
1158
|
-
date: new Date(commit.data.committer.date),
|
|
1159
|
-
type: "COMMIT" /* COMMIT */,
|
|
1160
|
-
sha: commit.data.sha
|
|
1161
|
-
})),
|
|
1162
|
-
getTagDate({ owner, repo, tag: ref }, oktoKit).then((data) => ({
|
|
1163
|
-
date: new Date(data.date),
|
|
1164
|
-
type: "TAG" /* TAG */,
|
|
1165
|
-
sha: data.sha
|
|
1166
|
-
}))
|
|
1167
|
-
]);
|
|
1168
|
-
return res;
|
|
1169
|
-
} catch (e) {
|
|
1170
|
-
if (e instanceof AggregateError) {
|
|
1171
|
-
throw new RefNotFoundError(`ref: ${ref} does not exist`);
|
|
1172
|
-
}
|
|
1173
|
-
throw e;
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
async function getBranch({ branch, owner, repo }, oktoKit) {
|
|
1177
|
-
return oktoKit.rest.repos.getBranch({
|
|
1178
|
-
branch,
|
|
1179
|
-
owner,
|
|
1180
|
-
repo
|
|
1181
|
-
});
|
|
1182
|
-
}
|
|
1183
|
-
async function getTagDate({ tag, owner, repo }, oktoKit) {
|
|
1184
|
-
const refResponse = await oktoKit.rest.git.getRef({
|
|
1185
|
-
ref: `tags/${tag}`,
|
|
1186
|
-
owner,
|
|
1187
|
-
repo
|
|
1188
|
-
});
|
|
1189
|
-
const tagSha = refResponse.data.object.sha;
|
|
1190
|
-
if (refResponse.data.object.type === "commit") {
|
|
1191
|
-
const res2 = await oktoKit.rest.git.getCommit({
|
|
1192
|
-
commit_sha: tagSha,
|
|
1193
|
-
owner,
|
|
1194
|
-
repo
|
|
1195
|
-
});
|
|
1196
|
-
return {
|
|
1197
|
-
date: res2.data.committer.date,
|
|
1198
|
-
sha: res2.data.sha
|
|
1199
|
-
};
|
|
1200
|
-
}
|
|
1201
|
-
const res = await oktoKit.rest.git.getTag({
|
|
1202
|
-
tag_sha: tagSha,
|
|
1203
|
-
owner,
|
|
1204
|
-
repo
|
|
1205
|
-
});
|
|
1206
|
-
return {
|
|
1207
|
-
date: res.data.tagger.date,
|
|
1208
|
-
sha: res.data.sha
|
|
1209
|
-
};
|
|
1210
|
-
}
|
|
1211
|
-
async function getCommit({
|
|
1212
|
-
commitSha,
|
|
1213
|
-
owner,
|
|
1214
|
-
repo
|
|
1215
|
-
}, oktoKit) {
|
|
1216
|
-
return oktoKit.rest.git.getCommit({
|
|
1217
|
-
repo,
|
|
1218
|
-
owner,
|
|
1219
|
-
commit_sha: commitSha
|
|
1220
|
-
});
|
|
1221
|
-
}
|
|
1222
|
-
function parseOwnerAndRepo(gitHubUrl) {
|
|
1223
|
-
gitHubUrl = removeTrailingSlash(gitHubUrl);
|
|
1224
|
-
if (!githubUrlRegex.test(gitHubUrl)) {
|
|
1225
|
-
throw new InvalidUrlPatternError(`invalid github repo Url ${gitHubUrl}`);
|
|
1226
|
-
}
|
|
1227
|
-
const groups = gitHubUrl.split(githubUrlRegex).filter((res) => res);
|
|
1228
|
-
const ownerAndRepo = groups[0]?.split("/");
|
|
1229
|
-
const owner = ownerAndRepo?.at(0);
|
|
1230
|
-
const repo = ownerAndRepo?.at(1);
|
|
1231
|
-
if (!owner || !repo) {
|
|
1232
|
-
throw new InvalidUrlPatternError(`invalid github repo Url ${gitHubUrl}`);
|
|
1233
|
-
}
|
|
1234
|
-
return { owner, repo };
|
|
1235
|
-
}
|
|
1236
|
-
async function queryGithubGraphql(query, variables, options) {
|
|
1237
|
-
const token = options?.githubAuthToken ?? GITHUB_API_TOKEN ?? "";
|
|
1238
|
-
const parameters = variables ?? {};
|
|
1239
|
-
const authorizationHeader = {
|
|
1240
|
-
headers: {
|
|
1241
|
-
authorization: `bearer ${token}`
|
|
1242
|
-
}
|
|
1243
|
-
};
|
|
1244
|
-
try {
|
|
1245
|
-
const oktoKit = getOktoKit(options);
|
|
1246
|
-
const res = await oktoKit.graphql(query, {
|
|
1247
|
-
...parameters,
|
|
1248
|
-
...authorizationHeader
|
|
1249
|
-
});
|
|
1250
|
-
return res;
|
|
1251
|
-
} catch (e) {
|
|
1252
|
-
if (e instanceof RequestError) {
|
|
1253
|
-
return null;
|
|
1254
|
-
}
|
|
1255
|
-
throw e;
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
async function getGithubBlameRanges({ ref, gitHubUrl, path: path8 }, options) {
|
|
1259
|
-
const { owner, repo } = parseOwnerAndRepo(gitHubUrl);
|
|
1260
|
-
const variables = {
|
|
1261
|
-
owner,
|
|
1262
|
-
repo,
|
|
1263
|
-
path: path8,
|
|
1264
|
-
ref
|
|
1265
|
-
};
|
|
1266
|
-
const res = await queryGithubGraphql(
|
|
1267
|
-
GetBlameDocument,
|
|
1268
|
-
variables,
|
|
1269
|
-
options
|
|
1270
|
-
);
|
|
1271
|
-
if (!res?.repository?.object?.blame?.ranges) {
|
|
1272
|
-
return [];
|
|
1273
|
-
}
|
|
1274
|
-
return res.repository.object.blame.ranges.map((range) => ({
|
|
1275
|
-
startingLine: range.startingLine,
|
|
1276
|
-
endingLine: range.endingLine,
|
|
1277
|
-
email: range.commit.author.user.email,
|
|
1278
|
-
name: range.commit.author.user.name,
|
|
1279
|
-
login: range.commit.author.user.login
|
|
1280
|
-
}));
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
// src/features/analysis/scm/scmSubmit.ts
|
|
1284
|
-
import fs2 from "node:fs/promises";
|
|
1285
|
-
import os from "os";
|
|
1286
|
-
import path5 from "path";
|
|
1287
|
-
import { simpleGit as simpleGit2 } from "simple-git";
|
|
1288
|
-
import { z as z4 } from "zod";
|
|
1289
|
-
var isValidBranchName = async (branchName) => {
|
|
1290
|
-
const git = simpleGit2();
|
|
1291
|
-
try {
|
|
1292
|
-
const res = await git.raw(["check-ref-format", "--branch", branchName]);
|
|
1293
|
-
if (res) {
|
|
1294
|
-
return true;
|
|
1295
|
-
}
|
|
1296
|
-
return false;
|
|
1297
|
-
} catch (e) {
|
|
1298
|
-
return false;
|
|
1299
|
-
}
|
|
1300
|
-
};
|
|
1301
|
-
var SubmitFixesMessageZ = z4.object({
|
|
1302
|
-
submitFixRequestId: z4.string().uuid(),
|
|
1303
|
-
fixes: z4.array(
|
|
1304
|
-
z4.object({
|
|
1305
|
-
fixId: z4.string().uuid(),
|
|
1306
|
-
diff: z4.string()
|
|
1307
|
-
})
|
|
1308
|
-
),
|
|
1309
|
-
branchName: z4.string(),
|
|
1310
|
-
commitHash: z4.string(),
|
|
1311
|
-
targetBranch: z4.string(),
|
|
1312
|
-
repoUrl: z4.string()
|
|
1313
|
-
});
|
|
1314
|
-
var FixResponseArrayZ = z4.array(
|
|
1315
|
-
z4.object({
|
|
1316
|
-
fixId: z4.string().uuid()
|
|
1317
|
-
})
|
|
1318
|
-
);
|
|
1319
|
-
var SubmitFixesResponseMessageZ = z4.object({
|
|
1320
|
-
submitFixRequestId: z4.string().uuid(),
|
|
1321
|
-
submitBranches: z4.array(
|
|
1322
|
-
z4.object({
|
|
1323
|
-
branchName: z4.string(),
|
|
1324
|
-
fixes: FixResponseArrayZ
|
|
1325
|
-
})
|
|
1326
|
-
),
|
|
1327
|
-
error: z4.object({
|
|
1328
|
-
type: z4.enum([
|
|
1329
|
-
"InitialRepoAccessError",
|
|
1330
|
-
"PushBranchError",
|
|
1331
|
-
"UnknownError"
|
|
1332
|
-
]),
|
|
1333
|
-
info: z4.object({
|
|
1334
|
-
message: z4.string(),
|
|
1335
|
-
pushBranchName: z4.string().optional()
|
|
1336
|
-
})
|
|
1337
|
-
}).optional()
|
|
1338
|
-
});
|
|
1339
|
-
|
|
1340
|
-
// src/features/analysis/scm/scm.ts
|
|
1341
|
-
function getScmLibTypeFromUrl(url) {
|
|
1342
|
-
if (!url) {
|
|
1343
|
-
return void 0;
|
|
1344
|
-
}
|
|
1345
|
-
if (url.toLowerCase().startsWith("https://gitlab.com/")) {
|
|
1346
|
-
return "GITLAB" /* GITLAB */;
|
|
1347
|
-
}
|
|
1348
|
-
if (url.toLowerCase().startsWith("https://github.com/")) {
|
|
1349
|
-
return "GITHUB" /* GITHUB */;
|
|
1350
|
-
}
|
|
1351
|
-
return void 0;
|
|
1352
|
-
}
|
|
1353
|
-
async function scmCanReachRepo({
|
|
1354
|
-
repoUrl,
|
|
1355
|
-
githubToken,
|
|
1356
|
-
gitlabToken
|
|
1357
|
-
}) {
|
|
1358
|
-
try {
|
|
1359
|
-
const scmLibType = getScmLibTypeFromUrl(repoUrl);
|
|
1360
|
-
await SCMLib.init({
|
|
1361
|
-
url: repoUrl,
|
|
1362
|
-
accessToken: scmLibType === "GITHUB" /* GITHUB */ ? githubToken : scmLibType === "GITLAB" /* GITLAB */ ? gitlabToken : "",
|
|
1363
|
-
scmType: scmLibType
|
|
1364
|
-
});
|
|
1365
|
-
return true;
|
|
1366
|
-
} catch (e) {
|
|
1367
|
-
return false;
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
var InvalidRepoUrlError = class extends Error {
|
|
1371
|
-
constructor(m) {
|
|
1372
|
-
super(m);
|
|
1373
|
-
}
|
|
1374
|
-
};
|
|
1375
|
-
var InvalidAccessTokenError = class extends Error {
|
|
1376
|
-
constructor(m) {
|
|
1377
|
-
super(m);
|
|
1378
|
-
}
|
|
1379
|
-
};
|
|
1380
|
-
var InvalidUrlPatternError = class extends Error {
|
|
1381
|
-
constructor(m) {
|
|
1382
|
-
super(m);
|
|
1383
|
-
}
|
|
1384
|
-
};
|
|
1385
|
-
var RefNotFoundError = class extends Error {
|
|
1386
|
-
constructor(m) {
|
|
1387
|
-
super(m);
|
|
1388
|
-
}
|
|
1389
|
-
};
|
|
1390
|
-
var RepoNoTokenAccessError = class extends Error {
|
|
1391
|
-
constructor(m) {
|
|
1392
|
-
super(m);
|
|
1393
|
-
}
|
|
1394
|
-
};
|
|
1395
|
-
var SCMLib = class {
|
|
1396
|
-
constructor(url, accessToken) {
|
|
1397
|
-
__publicField(this, "url");
|
|
1398
|
-
__publicField(this, "accessToken");
|
|
1399
|
-
this.accessToken = accessToken;
|
|
1400
|
-
this.url = url;
|
|
1401
|
-
}
|
|
1402
|
-
async getUrlWithCredentials() {
|
|
1403
|
-
if (!this.url) {
|
|
1404
|
-
console.error("no url for getUrlWithCredentials()");
|
|
1405
|
-
throw new Error("no url");
|
|
1406
|
-
}
|
|
1407
|
-
const trimmedUrl = this.url.trim().replace(/\/$/, "");
|
|
1408
|
-
if (!this.accessToken) {
|
|
1409
|
-
return trimmedUrl;
|
|
1410
|
-
}
|
|
1411
|
-
const username = await this._getUsernameForAuthUrl();
|
|
1412
|
-
const is_http = trimmedUrl.toLowerCase().startsWith("http://");
|
|
1413
|
-
const is_https = trimmedUrl.toLowerCase().startsWith("https://");
|
|
1414
|
-
if (is_http) {
|
|
1415
|
-
return `http://${username}:${this.accessToken}@${trimmedUrl.toLowerCase().replace("http://", "")}`;
|
|
1416
|
-
} else if (is_https) {
|
|
1417
|
-
return `https://${username}:${this.accessToken}@${trimmedUrl.toLowerCase().replace("https://", "")}`;
|
|
1418
|
-
} else {
|
|
1419
|
-
console.error(`invalid scm url ${trimmedUrl}`);
|
|
1420
|
-
throw new Error(`invalid scm url ${trimmedUrl}`);
|
|
1396
|
+
const trimmedUrl = this.url.trim().replace(/\/$/, "");
|
|
1397
|
+
if (!this.accessToken) {
|
|
1398
|
+
return trimmedUrl;
|
|
1399
|
+
}
|
|
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}`);
|
|
1421
1410
|
}
|
|
1422
1411
|
}
|
|
1423
1412
|
getAccessToken() {
|
|
@@ -1609,6 +1598,11 @@ var GitlabSCMLib = class extends SCMLib {
|
|
|
1609
1598
|
}
|
|
1610
1599
|
};
|
|
1611
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
|
+
}
|
|
1612
1606
|
async createSubmitRequest(targetBranchName, sourceBranchName, title, body) {
|
|
1613
1607
|
if (!this.accessToken || !this.url) {
|
|
1614
1608
|
console.error("no access token or no url");
|
|
@@ -1628,6 +1622,55 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
1628
1622
|
async validateParams() {
|
|
1629
1623
|
return githubValidateParams(this.url, this.accessToken);
|
|
1630
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
|
+
}
|
|
1631
1674
|
async getRepoList() {
|
|
1632
1675
|
if (!this.accessToken) {
|
|
1633
1676
|
console.error("no access token");
|
|
@@ -1801,7 +1844,6 @@ var EnvVariablesZod2 = z5.object({
|
|
|
1801
1844
|
GITLAB_API_TOKEN: z5.string().optional()
|
|
1802
1845
|
});
|
|
1803
1846
|
var { GITLAB_API_TOKEN } = EnvVariablesZod2.parse(process.env);
|
|
1804
|
-
var gitlabUrlRegex = /^http[s]?:\/\/[^/\s]+\/(([^/.\s]+[/])+)([^/.\s]+)(\.git)?(\/)?$/i;
|
|
1805
1847
|
function getGitBeaker(options) {
|
|
1806
1848
|
const token = options?.gitlabAuthToken ?? GITLAB_API_TOKEN ?? "";
|
|
1807
1849
|
if (token?.startsWith("glpat-") || token === "") {
|
|
@@ -1934,121 +1976,709 @@ async function getGitlabBranchList({
|
|
|
1934
1976
|
} catch (e) {
|
|
1935
1977
|
return [];
|
|
1936
1978
|
}
|
|
1937
|
-
}
|
|
1938
|
-
async function createMergeRequest(options) {
|
|
1939
|
-
const { projectPath } = parseOwnerAndRepo2(options.repoUrl);
|
|
1940
|
-
const api = getGitBeaker({ gitlabAuthToken: options.accessToken });
|
|
1941
|
-
const res = await api.MergeRequests.create(
|
|
1942
|
-
projectPath,
|
|
1943
|
-
options.sourceBranchName,
|
|
1944
|
-
options.targetBranchName,
|
|
1945
|
-
options.title,
|
|
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);
|
|
2446
|
+
}
|
|
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);
|
|
2453
|
+
}
|
|
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}`));
|
|
2466
|
+
}
|
|
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);
|
|
2472
|
+
}
|
|
2473
|
+
childProcess.on("exit", (code) => {
|
|
2474
|
+
debug10(`${name} exit code ${code}`);
|
|
2475
|
+
resolve({ message: out, code });
|
|
2476
|
+
});
|
|
2477
|
+
childProcess.on("error", (err) => {
|
|
2478
|
+
debug10(`${name} error %o`, err);
|
|
2479
|
+
reject(err);
|
|
2480
|
+
});
|
|
2481
|
+
});
|
|
2482
|
+
}
|
|
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";
|
|
2496
|
+
try {
|
|
2497
|
+
return require2.resolve(`.bin/${cxFileName}`);
|
|
2498
|
+
} catch (e) {
|
|
2499
|
+
throw new CliError(cxOperatingSystemSupportMessage);
|
|
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());
|
|
2530
|
+
}
|
|
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 }
|
|
2536
|
+
);
|
|
2537
|
+
}
|
|
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();
|
|
2549
|
+
}
|
|
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],
|
|
1946
2563
|
{
|
|
1947
|
-
|
|
2564
|
+
display: true
|
|
1948
2565
|
}
|
|
1949
2566
|
);
|
|
1950
|
-
|
|
1951
|
-
}
|
|
1952
|
-
|
|
1953
|
-
const api = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
|
|
1954
|
-
const { projectPath } = parseOwnerAndRepo2(repoUrl);
|
|
1955
|
-
const project = await api.Projects.show(projectPath);
|
|
1956
|
-
if (!project.default_branch) {
|
|
1957
|
-
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();
|
|
1958
2570
|
}
|
|
1959
|
-
|
|
2571
|
+
await createSpinner2("\u{1F50D} Checkmarx Scan completed").start().success();
|
|
2572
|
+
return true;
|
|
1960
2573
|
}
|
|
1961
|
-
async function
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
type: "BRANCH" /* BRANCH */,
|
|
1970
|
-
date: res.commit.committed_date ? new Date(res.commit.committed_date) : void 0
|
|
1971
|
-
};
|
|
1972
|
-
})(),
|
|
1973
|
-
(async () => {
|
|
1974
|
-
const res = await api.Commits.show(projectPath, ref);
|
|
1975
|
-
return {
|
|
1976
|
-
sha: res.id,
|
|
1977
|
-
type: "COMMIT" /* COMMIT */,
|
|
1978
|
-
date: res.committed_date ? new Date(res.committed_date) : void 0
|
|
1979
|
-
};
|
|
1980
|
-
})(),
|
|
1981
|
-
(async () => {
|
|
1982
|
-
const res = await api.Tags.show(projectPath, ref);
|
|
1983
|
-
return {
|
|
1984
|
-
sha: res.commit.id,
|
|
1985
|
-
type: "TAG" /* TAG */,
|
|
1986
|
-
date: res.commit.committed_date ? new Date(res.commit.committed_date) : void 0
|
|
1987
|
-
};
|
|
1988
|
-
})()
|
|
1989
|
-
]);
|
|
1990
|
-
const [branchRes, commitRes, tagRes] = results;
|
|
1991
|
-
if (tagRes.status === "fulfilled") {
|
|
1992
|
-
return tagRes.value;
|
|
1993
|
-
}
|
|
1994
|
-
if (branchRes.status === "fulfilled") {
|
|
1995
|
-
return branchRes.value;
|
|
1996
|
-
}
|
|
1997
|
-
if (commitRes.status === "fulfilled") {
|
|
1998
|
-
return commitRes.value;
|
|
1999
|
-
}
|
|
2000
|
-
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
|
+
);
|
|
2001
2582
|
}
|
|
2002
|
-
function
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
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
|
+
}
|
|
2006
2601
|
}
|
|
2007
|
-
|
|
2008
|
-
const owner = groups[0]?.split("/")[0];
|
|
2009
|
-
const repo = groups[2];
|
|
2010
|
-
const projectPath = `${groups[0]}${repo}`;
|
|
2011
|
-
return { owner, repo, projectPath };
|
|
2602
|
+
await createSpinner2("\u{1F513} Checkmarx configured successfully!").start().success();
|
|
2012
2603
|
}
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
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();
|
|
2022
2634
|
}
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
}
|
|
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;
|
|
2032
2667
|
}
|
|
2033
|
-
var GitlabAuthResultZ = z5.object({
|
|
2034
|
-
access_token: z5.string(),
|
|
2035
|
-
token_type: z5.string(),
|
|
2036
|
-
refresh_token: z5.string()
|
|
2037
|
-
});
|
|
2038
2668
|
|
|
2039
2669
|
// src/features/analysis/upload-file.ts
|
|
2040
|
-
import
|
|
2670
|
+
import Debug9 from "debug";
|
|
2041
2671
|
import fetch2, { File, fileFrom, FormData } from "node-fetch";
|
|
2042
|
-
var
|
|
2672
|
+
var debug8 = Debug9("mobbdev:upload-file");
|
|
2043
2673
|
async function uploadFile({
|
|
2044
2674
|
file,
|
|
2045
2675
|
url,
|
|
2046
2676
|
uploadKey,
|
|
2047
2677
|
uploadFields
|
|
2048
2678
|
}) {
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2679
|
+
debug8("upload file start %s", url);
|
|
2680
|
+
debug8("upload fields %o", uploadFields);
|
|
2681
|
+
debug8("upload key %s", uploadKey);
|
|
2052
2682
|
const form = new FormData();
|
|
2053
2683
|
Object.entries(uploadFields).forEach(([key, value]) => {
|
|
2054
2684
|
form.append(key, value);
|
|
@@ -2057,10 +2687,10 @@ async function uploadFile({
|
|
|
2057
2687
|
form.append("key", uploadKey);
|
|
2058
2688
|
}
|
|
2059
2689
|
if (typeof file === "string") {
|
|
2060
|
-
|
|
2690
|
+
debug8("upload file from path %s", file);
|
|
2061
2691
|
form.append("file", await fileFrom(file));
|
|
2062
2692
|
} else {
|
|
2063
|
-
|
|
2693
|
+
debug8("upload file from buffer");
|
|
2064
2694
|
form.append("file", new File([file], "file"));
|
|
2065
2695
|
}
|
|
2066
2696
|
const response = await fetch2(url, {
|
|
@@ -2068,10 +2698,10 @@ async function uploadFile({
|
|
|
2068
2698
|
body: form
|
|
2069
2699
|
});
|
|
2070
2700
|
if (!response.ok) {
|
|
2071
|
-
|
|
2701
|
+
debug8("error from S3 %s %s", response.body, response.status);
|
|
2072
2702
|
throw new Error(`Failed to upload the file: ${response.status}`);
|
|
2073
2703
|
}
|
|
2074
|
-
|
|
2704
|
+
debug8("upload file done");
|
|
2075
2705
|
}
|
|
2076
2706
|
|
|
2077
2707
|
// src/features/analysis/index.ts
|
|
@@ -2088,7 +2718,7 @@ async function downloadRepo({
|
|
|
2088
2718
|
}) {
|
|
2089
2719
|
const { createSpinner: createSpinner4 } = Spinner2({ ci });
|
|
2090
2720
|
const repoSpinner = createSpinner4("\u{1F4BE} Downloading Repo").start();
|
|
2091
|
-
|
|
2721
|
+
debug9("download repo %s %s %s", repoUrl, dirname);
|
|
2092
2722
|
const zipFilePath = path6.join(dirname, "repo.zip");
|
|
2093
2723
|
const response = await fetch3(downloadUrl, {
|
|
2094
2724
|
method: "GET",
|
|
@@ -2097,7 +2727,7 @@ async function downloadRepo({
|
|
|
2097
2727
|
}
|
|
2098
2728
|
});
|
|
2099
2729
|
if (!response.ok) {
|
|
2100
|
-
|
|
2730
|
+
debug9("SCM zipball request failed %s %s", response.body, response.status);
|
|
2101
2731
|
repoSpinner.error({ text: "\u{1F4BE} Repo download failed" });
|
|
2102
2732
|
throw new Error(`Can't access ${chalk4.bold(repoUrl)}`);
|
|
2103
2733
|
}
|
|
@@ -2111,7 +2741,7 @@ async function downloadRepo({
|
|
|
2111
2741
|
if (!repoRoot) {
|
|
2112
2742
|
throw new Error("Repo root not found");
|
|
2113
2743
|
}
|
|
2114
|
-
|
|
2744
|
+
debug9("repo root %s", repoRoot);
|
|
2115
2745
|
repoSpinner.success({ text: "\u{1F4BE} Repo downloaded successfully" });
|
|
2116
2746
|
return path6.join(dirname, repoRoot);
|
|
2117
2747
|
}
|
|
@@ -2120,7 +2750,7 @@ var LOGIN_CHECK_DELAY = 5 * 1e3;
|
|
|
2120
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(
|
|
2121
2751
|
"press any key to continue"
|
|
2122
2752
|
)};`;
|
|
2123
|
-
var tmpObj =
|
|
2753
|
+
var tmpObj = tmp2.dirSync({
|
|
2124
2754
|
unsafeCleanup: true
|
|
2125
2755
|
});
|
|
2126
2756
|
var getReportUrl = ({
|
|
@@ -2128,7 +2758,7 @@ var getReportUrl = ({
|
|
|
2128
2758
|
projectId,
|
|
2129
2759
|
fixReportId
|
|
2130
2760
|
}) => `${WEB_APP_URL}/organization/${organizationId}/project/${projectId}/report/${fixReportId}`;
|
|
2131
|
-
var
|
|
2761
|
+
var debug9 = Debug10("mobbdev:index");
|
|
2132
2762
|
var packageJson = JSON.parse(
|
|
2133
2763
|
fs3.readFileSync(path6.join(getDirName2(), "../package.json"), "utf8")
|
|
2134
2764
|
);
|
|
@@ -2138,7 +2768,7 @@ if (!semver.satisfies(process.version, packageJson.engines.node)) {
|
|
|
2138
2768
|
);
|
|
2139
2769
|
}
|
|
2140
2770
|
var config2 = new Configstore(packageJson.name, { apiToken: "" });
|
|
2141
|
-
|
|
2771
|
+
debug9("config %o", config2);
|
|
2142
2772
|
async function runAnalysis(params, options) {
|
|
2143
2773
|
try {
|
|
2144
2774
|
await _scan(
|
|
@@ -2152,20 +2782,23 @@ async function runAnalysis(params, options) {
|
|
|
2152
2782
|
tmpObj.removeCallback();
|
|
2153
2783
|
}
|
|
2154
2784
|
}
|
|
2155
|
-
async function _scan({
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
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);
|
|
2169
2802
|
const { createSpinner: createSpinner4 } = Spinner2({ ci });
|
|
2170
2803
|
skipPrompts = skipPrompts || ci;
|
|
2171
2804
|
let gqlClient = new GQLClient({
|
|
@@ -2221,9 +2854,9 @@ async function _scan({
|
|
|
2221
2854
|
});
|
|
2222
2855
|
const reference = ref ?? await scm.getRepoDefaultBranch();
|
|
2223
2856
|
const { sha } = await scm.getReferenceData(reference);
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2857
|
+
debug9("org id %s", organizationId);
|
|
2858
|
+
debug9("project id %s", projectId);
|
|
2859
|
+
debug9("default branch %s", reference);
|
|
2227
2860
|
const repositoryRoot = await downloadRepo({
|
|
2228
2861
|
repoUrl: repo,
|
|
2229
2862
|
dirname,
|
|
@@ -2231,8 +2864,8 @@ async function _scan({
|
|
|
2231
2864
|
authHeaders: scm.getAuthHeaders(),
|
|
2232
2865
|
downloadUrl: scm.getDownloadUrl(sha)
|
|
2233
2866
|
});
|
|
2234
|
-
if (
|
|
2235
|
-
reportPath = await getReport(scanner);
|
|
2867
|
+
if (command === "scan") {
|
|
2868
|
+
reportPath = await getReport(SupportedScannersZ.parse(scanner));
|
|
2236
2869
|
}
|
|
2237
2870
|
if (!reportPath) {
|
|
2238
2871
|
throw new Error("reportPath is null");
|
|
@@ -2251,23 +2884,43 @@ async function _scan({
|
|
|
2251
2884
|
}
|
|
2252
2885
|
uploadReportSpinner.success({ text: "\u{1F4C1} Report uploaded successfully" });
|
|
2253
2886
|
const mobbSpinner = createSpinner4("\u{1F575}\uFE0F\u200D\u2642\uFE0F Initiating Mobb analysis").start();
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
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
|
+
);
|
|
2266
2899
|
}
|
|
2267
2900
|
mobbSpinner.success({
|
|
2268
2901
|
text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Generating fixes..."
|
|
2269
2902
|
});
|
|
2270
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
|
+
}
|
|
2271
2924
|
async function getReport(scanner2) {
|
|
2272
2925
|
const reportPath2 = path6.join(dirname, "report.json");
|
|
2273
2926
|
switch (scanner2) {
|
|
@@ -2298,7 +2951,6 @@ async function _scan({
|
|
|
2298
2951
|
fixReportId: reportUploadInfo.fixReportId
|
|
2299
2952
|
});
|
|
2300
2953
|
!ci && console.log("You can access the analysis at: \n");
|
|
2301
|
-
console.log(chalk4.bold(reportUrl));
|
|
2302
2954
|
!skipPrompts && await mobbAnalysisPrompt();
|
|
2303
2955
|
!ci && open2(reportUrl);
|
|
2304
2956
|
!ci && console.log(
|
|
@@ -2343,9 +2995,9 @@ async function _scan({
|
|
|
2343
2995
|
});
|
|
2344
2996
|
loginSpinner.spin();
|
|
2345
2997
|
if (encryptedApiToken) {
|
|
2346
|
-
|
|
2998
|
+
debug9("encrypted API token received %s", encryptedApiToken);
|
|
2347
2999
|
newApiToken = crypto.privateDecrypt(privateKey, Buffer.from(encryptedApiToken, "base64")).toString("utf-8");
|
|
2348
|
-
|
|
3000
|
+
debug9("API token decrypted");
|
|
2349
3001
|
break;
|
|
2350
3002
|
}
|
|
2351
3003
|
await sleep(LOGIN_CHECK_DELAY);
|
|
@@ -2358,7 +3010,7 @@ async function _scan({
|
|
|
2358
3010
|
}
|
|
2359
3011
|
gqlClient = new GQLClient({ apiKey: newApiToken });
|
|
2360
3012
|
if (await gqlClient.verifyToken()) {
|
|
2361
|
-
|
|
3013
|
+
debug9("set api token %s", newApiToken);
|
|
2362
3014
|
config2.set("apiToken", newApiToken);
|
|
2363
3015
|
loginSpinner.success({ text: "\u{1F513} Login to Mobb successful!" });
|
|
2364
3016
|
} else {
|
|
@@ -2482,6 +3134,35 @@ async function _scan({
|
|
|
2482
3134
|
|
|
2483
3135
|
// src/commands/index.ts
|
|
2484
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
|
+
}
|
|
2485
3166
|
async function analyze({
|
|
2486
3167
|
repo,
|
|
2487
3168
|
f: scanFile,
|
|
@@ -2502,7 +3183,8 @@ async function analyze({
|
|
|
2502
3183
|
ci,
|
|
2503
3184
|
commitHash,
|
|
2504
3185
|
mobbProjectName,
|
|
2505
|
-
srcPath
|
|
3186
|
+
srcPath,
|
|
3187
|
+
command: "analyze"
|
|
2506
3188
|
},
|
|
2507
3189
|
{ skipPrompts }
|
|
2508
3190
|
);
|
|
@@ -2521,7 +3203,7 @@ async function scan(scanOptions, { skipPrompts = false } = {}) {
|
|
|
2521
3203
|
throw new CliError(errorMessages.missingCxProjectName);
|
|
2522
3204
|
}
|
|
2523
3205
|
await runAnalysis(
|
|
2524
|
-
{ ...scanOptions, scanner: selectedScanner },
|
|
3206
|
+
{ ...scanOptions, scanner: selectedScanner, command: "scan" },
|
|
2525
3207
|
{ skipPrompts }
|
|
2526
3208
|
);
|
|
2527
3209
|
}
|
|
@@ -2587,7 +3269,7 @@ var commitHashOption = {
|
|
|
2587
3269
|
// src/args/validation.ts
|
|
2588
3270
|
import chalk6 from "chalk";
|
|
2589
3271
|
import path7 from "path";
|
|
2590
|
-
import { z as
|
|
3272
|
+
import { z as z9 } from "zod";
|
|
2591
3273
|
function throwRepoUrlErrorMessage({
|
|
2592
3274
|
error,
|
|
2593
3275
|
repoUrl,
|
|
@@ -2604,10 +3286,9 @@ Example:
|
|
|
2604
3286
|
)}`;
|
|
2605
3287
|
throw new CliError(formattedErrorMessage);
|
|
2606
3288
|
}
|
|
2607
|
-
var
|
|
2608
|
-
var UrlZ = z6.string({
|
|
3289
|
+
var UrlZ = z9.string({
|
|
2609
3290
|
invalid_type_error: "is not a valid GitHub / GitLab URL"
|
|
2610
|
-
}).
|
|
3291
|
+
}).refine((data) => !!parseScmURL(data), {
|
|
2611
3292
|
message: "is not a valid GitHub / GitLab URL"
|
|
2612
3293
|
});
|
|
2613
3294
|
function validateRepoUrl(args) {
|
|
@@ -2685,6 +3366,49 @@ async function analyzeHandler(args) {
|
|
|
2685
3366
|
await analyze(args, { skipPrompts: args.yes });
|
|
2686
3367
|
}
|
|
2687
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
|
+
|
|
2688
3412
|
// src/args/commands/scan.ts
|
|
2689
3413
|
function scanBuilder(args) {
|
|
2690
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(
|
|
@@ -2713,32 +3437,39 @@ async function scanHandler(args) {
|
|
|
2713
3437
|
var parseArgs = async (args) => {
|
|
2714
3438
|
const yargsInstance = yargs(args);
|
|
2715
3439
|
return yargsInstance.updateStrings({
|
|
2716
|
-
"Commands:":
|
|
2717
|
-
"Options:":
|
|
2718
|
-
"Examples:":
|
|
2719
|
-
"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")
|
|
2720
3444
|
}).usage(
|
|
2721
|
-
`${
|
|
3445
|
+
`${chalk9.bold(
|
|
2722
3446
|
"\n Bugsy - Trusted, Automatic Vulnerability Fixer \u{1F575}\uFE0F\u200D\u2642\uFE0F\n\n"
|
|
2723
|
-
)} ${
|
|
2724
|
-
$0 ${
|
|
3447
|
+
)} ${chalk9.yellow.underline.bold("Usage:")}
|
|
3448
|
+
$0 ${chalk9.green(
|
|
2725
3449
|
"<command>"
|
|
2726
|
-
)} ${
|
|
3450
|
+
)} ${chalk9.dim("[options]")}
|
|
2727
3451
|
`
|
|
2728
3452
|
).version(false).command(
|
|
2729
|
-
|
|
2730
|
-
|
|
3453
|
+
mobbCliCommand.scan,
|
|
3454
|
+
chalk9.bold(
|
|
2731
3455
|
"Scan your code for vulnerabilities, get automated fixes right away."
|
|
2732
3456
|
),
|
|
2733
3457
|
scanBuilder,
|
|
2734
3458
|
scanHandler
|
|
2735
3459
|
).command(
|
|
2736
|
-
|
|
2737
|
-
|
|
3460
|
+
mobbCliCommand.analyze,
|
|
3461
|
+
chalk9.bold(
|
|
2738
3462
|
"Provide a vulnerability report and relevant code repository, get automated fixes right away."
|
|
2739
3463
|
),
|
|
2740
3464
|
analyzeBuilder,
|
|
2741
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
|
|
2742
3473
|
).example(
|
|
2743
3474
|
"$0 scan -r https://github.com/WebGoat/WebGoat",
|
|
2744
3475
|
"Scan an existing repository"
|
|
@@ -2747,7 +3478,7 @@ var parseArgs = async (args) => {
|
|
|
2747
3478
|
handler() {
|
|
2748
3479
|
yargsInstance.showHelp();
|
|
2749
3480
|
}
|
|
2750
|
-
}).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();
|
|
2751
3482
|
};
|
|
2752
3483
|
|
|
2753
3484
|
// src/index.ts
|