mobbdev 0.0.9 → 0.0.11
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/.env +3 -2
- package/index.mjs +1 -1
- package/package.json +6 -6
- package/src/constants.mjs +2 -2
- package/src/github.mjs +19 -26
- package/src/gql.mjs +58 -22
- package/src/index.mjs +17 -3
- package/src/upload-file.mjs +7 -5
package/.env
CHANGED
package/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mobbdev",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "Automated secure code remediation tool",
|
|
5
|
-
"main": "index.
|
|
5
|
+
"main": "index.mjs",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"lint": "prettier --check . && eslint **/*.mjs",
|
|
8
8
|
"lint:fix": "prettier --write . && eslint --fix **/*.mjs",
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
"configstore": "6.0.0",
|
|
20
20
|
"dotenv": "16.0.3",
|
|
21
21
|
"extract-zip": "2.0.1",
|
|
22
|
-
"
|
|
23
|
-
"got": "12.6.0",
|
|
22
|
+
"node-fetch": "3.3.1",
|
|
24
23
|
"open": "8.4.2",
|
|
24
|
+
"semver": "7.5.0",
|
|
25
25
|
"snyk": "1.1118.0",
|
|
26
26
|
"tmp": "0.2.1",
|
|
27
27
|
"zod": "3.21.4"
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
"prettier": "2.8.4"
|
|
34
34
|
},
|
|
35
35
|
"engines": {
|
|
36
|
-
"node": ">=
|
|
36
|
+
"node": ">=12.20.0"
|
|
37
37
|
},
|
|
38
|
+
"type": "module",
|
|
38
39
|
"files": [
|
|
39
40
|
"bin",
|
|
40
41
|
"src",
|
|
41
|
-
"index.mjs",
|
|
42
42
|
".env",
|
|
43
43
|
"README.md",
|
|
44
44
|
"LICENSE",
|
package/src/constants.mjs
CHANGED
|
@@ -9,7 +9,7 @@ dotenv.config({ path: path.join(__dirname, '../.env') });
|
|
|
9
9
|
const envVariablesSchema = z
|
|
10
10
|
.object({
|
|
11
11
|
WEB_LOGIN_URL: z.string(),
|
|
12
|
-
|
|
12
|
+
WEB_APP_URL: z.string(),
|
|
13
13
|
API_URL: z.string(),
|
|
14
14
|
})
|
|
15
15
|
.required();
|
|
@@ -17,5 +17,5 @@ const envVariablesSchema = z
|
|
|
17
17
|
const envVariables = envVariablesSchema.parse(process.env);
|
|
18
18
|
|
|
19
19
|
export const WEB_LOGIN_URL = envVariables.WEB_LOGIN_URL;
|
|
20
|
-
export const
|
|
20
|
+
export const WEB_APP_URL = envVariables.WEB_APP_URL;
|
|
21
21
|
export const API_URL = envVariables.API_URL;
|
package/src/github.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import got from 'got';
|
|
2
1
|
import fs from 'node:fs';
|
|
3
|
-
import { promisify } from 'node:util';
|
|
4
2
|
import stream from 'node:stream';
|
|
5
|
-
import extract from 'extract-zip';
|
|
6
3
|
import path from 'node:path';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import fetch from 'node-fetch';
|
|
6
|
+
import extract from 'extract-zip';
|
|
7
7
|
|
|
8
8
|
const pipeline = promisify(stream.pipeline);
|
|
9
9
|
|
|
@@ -18,44 +18,37 @@ export async function getDefaultBranch(repoUrl) {
|
|
|
18
18
|
slug = slug.substring(0, slug.length - '.git'.length);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return repoInfo.default_branch;
|
|
31
|
-
} catch (e) {
|
|
21
|
+
const response = await fetch(`https://api.github.com/repos/${slug}`, {
|
|
22
|
+
method: 'GET',
|
|
23
|
+
headers: {
|
|
24
|
+
Accept: 'application/vnd.github+json',
|
|
25
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (!response.ok) {
|
|
32
30
|
throw new Error(
|
|
33
31
|
`Can't get default branch, make sure the repository is public: ${repoUrl}.`
|
|
34
32
|
);
|
|
35
33
|
}
|
|
34
|
+
|
|
35
|
+
const repoInfo = await response.json();
|
|
36
|
+
|
|
37
|
+
return repoInfo.default_branch;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export async function downloadRepo(repoUrl, reference, dirname) {
|
|
39
41
|
const zipFilePath = path.join(dirname, 'repo.zip');
|
|
40
|
-
const
|
|
42
|
+
const response = await fetch(`${repoUrl}/zipball/${reference}`);
|
|
41
43
|
const fileWriterStream = fs.createWriteStream(zipFilePath);
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
if (transferred > 0) {
|
|
45
|
-
console.log(
|
|
46
|
-
`Progress: ${transferred} (${Math.round(percent * 100)}%) ...`
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
await pipeline(downloadStream, fileWriterStream);
|
|
45
|
+
await pipeline(response.body, fileWriterStream);
|
|
52
46
|
await extract(zipFilePath, { dir: dirname });
|
|
53
47
|
|
|
54
48
|
const repoRoot = fs
|
|
55
49
|
.readdirSync(dirname, { withFileTypes: true })
|
|
56
50
|
.filter((dirent) => dirent.isDirectory())
|
|
57
|
-
.map((dirent) => dirent.name)
|
|
58
|
-
.at(0);
|
|
51
|
+
.map((dirent) => dirent.name)[0];
|
|
59
52
|
|
|
60
53
|
return path.join(dirname, repoRoot);
|
|
61
54
|
}
|
package/src/gql.mjs
CHANGED
|
@@ -1,19 +1,29 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
2
|
import { API_URL } from './constants.mjs';
|
|
3
3
|
|
|
4
4
|
const ME = `
|
|
5
|
-
query
|
|
6
|
-
|
|
5
|
+
query Me {
|
|
6
|
+
user {
|
|
7
|
+
id
|
|
7
8
|
email
|
|
8
|
-
|
|
9
|
+
userOrganizations {
|
|
10
|
+
organization {
|
|
11
|
+
id
|
|
12
|
+
projects {
|
|
13
|
+
id
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
9
17
|
}
|
|
10
18
|
}
|
|
11
19
|
`;
|
|
12
20
|
|
|
13
21
|
const CREATE_COMMUNITY_USER = `
|
|
14
22
|
mutation CreateCommunityUser {
|
|
15
|
-
|
|
16
|
-
|
|
23
|
+
initOrganizationAndProject {
|
|
24
|
+
userId
|
|
25
|
+
projectId
|
|
26
|
+
organizationId
|
|
17
27
|
}
|
|
18
28
|
}
|
|
19
29
|
`;
|
|
@@ -34,13 +44,13 @@ mutation uploadS3BucketInfo($fileName: String!) {
|
|
|
34
44
|
`;
|
|
35
45
|
|
|
36
46
|
const SUBMIT_VULNERABILITY_REPORT = `
|
|
37
|
-
mutation SubmitVulnerabilityReport($vulnerabilityReportFileName: String!, $fixReportId: String!, $repoUrl: String!, $reference: String!) {
|
|
47
|
+
mutation SubmitVulnerabilityReport($vulnerabilityReportFileName: String!, $fixReportId: String!, $repoUrl: String!, $reference: String!, $projectId: String!) {
|
|
38
48
|
submitVulnerabilityReport(
|
|
39
49
|
fixReportId: $fixReportId
|
|
40
50
|
repoUrl: $repoUrl
|
|
41
51
|
reference: $reference
|
|
42
52
|
vulnerabilityReportFileName: $vulnerabilityReportFileName
|
|
43
|
-
|
|
53
|
+
projectId: $projectId
|
|
44
54
|
) {
|
|
45
55
|
__typename
|
|
46
56
|
}
|
|
@@ -48,40 +58,65 @@ mutation SubmitVulnerabilityReport($vulnerabilityReportFileName: String!, $fixRe
|
|
|
48
58
|
`;
|
|
49
59
|
|
|
50
60
|
export class GQLClient {
|
|
51
|
-
#token;
|
|
52
|
-
|
|
53
61
|
constructor(token) {
|
|
54
|
-
this
|
|
62
|
+
this._token = token;
|
|
63
|
+
this._projectId = undefined;
|
|
64
|
+
this._organizationId = undefined;
|
|
55
65
|
}
|
|
56
66
|
|
|
57
|
-
async
|
|
58
|
-
const response = await
|
|
67
|
+
async _apiCall(query, variables = {}) {
|
|
68
|
+
const response = await fetch(API_URL, {
|
|
59
69
|
method: 'POST',
|
|
60
70
|
headers: {
|
|
61
|
-
authorization: `Bearer ${this
|
|
71
|
+
authorization: `Bearer ${this._token}`,
|
|
62
72
|
},
|
|
63
73
|
body: JSON.stringify({
|
|
64
74
|
query,
|
|
65
75
|
variables,
|
|
66
76
|
}),
|
|
67
|
-
})
|
|
77
|
+
});
|
|
68
78
|
|
|
69
|
-
if (response.
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
throw new Error(`API call failed: ${response.status}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const data = await response.json();
|
|
84
|
+
|
|
85
|
+
if (data.errors) {
|
|
70
86
|
throw new Error(`API error: ${response.errors[0].message}`);
|
|
71
87
|
}
|
|
72
88
|
|
|
73
|
-
if (!
|
|
89
|
+
if (!data.data) {
|
|
74
90
|
throw new Error('No data returned for the API query.');
|
|
75
91
|
}
|
|
76
92
|
|
|
77
|
-
return
|
|
93
|
+
return data.data;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
getOrganizationId() {
|
|
97
|
+
return this._organizationId;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
getProjectId() {
|
|
101
|
+
return this._projectId;
|
|
78
102
|
}
|
|
79
103
|
|
|
80
104
|
async verifyToken() {
|
|
81
105
|
await this.createCommunityUser();
|
|
82
106
|
|
|
83
107
|
try {
|
|
84
|
-
await this
|
|
108
|
+
const userInfo = await this._apiCall(ME);
|
|
109
|
+
const {
|
|
110
|
+
user: [{ userOrganizations }],
|
|
111
|
+
} = userInfo;
|
|
112
|
+
const [
|
|
113
|
+
{
|
|
114
|
+
organization: { id: organizationId, projects },
|
|
115
|
+
},
|
|
116
|
+
] = userOrganizations;
|
|
117
|
+
const [{ id: projectId }] = projects;
|
|
118
|
+
this._projectId = projectId;
|
|
119
|
+
this._organizationId = organizationId;
|
|
85
120
|
} catch (e) {
|
|
86
121
|
return false;
|
|
87
122
|
}
|
|
@@ -90,14 +125,14 @@ export class GQLClient {
|
|
|
90
125
|
|
|
91
126
|
async createCommunityUser() {
|
|
92
127
|
try {
|
|
93
|
-
await this
|
|
128
|
+
await this._apiCall(CREATE_COMMUNITY_USER);
|
|
94
129
|
} catch (e) {
|
|
95
130
|
// Ignore errors
|
|
96
131
|
}
|
|
97
132
|
}
|
|
98
133
|
|
|
99
134
|
async uploadS3BucketInfo() {
|
|
100
|
-
const data = await this
|
|
135
|
+
const data = await this._apiCall(UPLOAD_S3_BUCKET_INFO, {
|
|
101
136
|
fileName: 'report.json',
|
|
102
137
|
});
|
|
103
138
|
|
|
@@ -112,11 +147,12 @@ export class GQLClient {
|
|
|
112
147
|
}
|
|
113
148
|
|
|
114
149
|
async submitVulnerabilityReport(fixReportId, repoUrl, reference) {
|
|
115
|
-
await this
|
|
150
|
+
await this._apiCall(SUBMIT_VULNERABILITY_REPORT, {
|
|
116
151
|
fixReportId,
|
|
117
152
|
repoUrl,
|
|
118
153
|
reference,
|
|
119
154
|
vulnerabilityReportFileName: 'report.json',
|
|
155
|
+
projectId: this._projectId,
|
|
120
156
|
});
|
|
121
157
|
}
|
|
122
158
|
}
|
package/src/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import semver from 'semver';
|
|
4
5
|
import open from 'open';
|
|
5
6
|
import Configstore from 'configstore';
|
|
6
7
|
import { GQLClient } from './gql.mjs';
|
|
@@ -8,12 +9,20 @@ import { webLogin } from './web-login.mjs';
|
|
|
8
9
|
import { downloadRepo, getDefaultBranch } from './github.mjs';
|
|
9
10
|
import { getSnykReport } from './snyk.mjs';
|
|
10
11
|
import { uploadFile } from './upload-file.mjs';
|
|
11
|
-
import {
|
|
12
|
+
import { WEB_APP_URL } from './constants.mjs';
|
|
12
13
|
|
|
13
14
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
15
|
const packageJson = JSON.parse(
|
|
15
16
|
fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8')
|
|
16
17
|
);
|
|
18
|
+
|
|
19
|
+
if (!semver.satisfies(process.version, packageJson.engines.node)) {
|
|
20
|
+
console.error(
|
|
21
|
+
`${packageJson.name} requires node version ${packageJson.engines.node}, but running ${process.version}.`
|
|
22
|
+
);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
17
26
|
const config = new Configstore(packageJson.name, { token: '' });
|
|
18
27
|
|
|
19
28
|
export async function main(dirname, repoUrl) {
|
|
@@ -66,12 +75,17 @@ export async function main(dirname, repoUrl) {
|
|
|
66
75
|
reference
|
|
67
76
|
);
|
|
68
77
|
|
|
69
|
-
|
|
78
|
+
const results = ((report.runs || [])[0] || {}).results || [];
|
|
79
|
+
if (results.length === 0) {
|
|
70
80
|
console.log('Snyk has not found any vulnerabilities — nothing to fix.');
|
|
71
81
|
} else {
|
|
72
82
|
console.log(
|
|
73
83
|
'You will be redirected to our report page, please wait until the analysis is finished and enjoy your fixes.'
|
|
74
84
|
);
|
|
75
|
-
|
|
85
|
+
const projectId = gqlClient.getProjectId();
|
|
86
|
+
const organizationId = gqlClient.getOrganizationId();
|
|
87
|
+
await open(
|
|
88
|
+
`${WEB_APP_URL}/organization/${organizationId}/project/${projectId}/report/${uploadData.fixReportId}`
|
|
89
|
+
);
|
|
76
90
|
}
|
|
77
91
|
}
|
package/src/upload-file.mjs
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import FormData from '
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import got from 'got';
|
|
1
|
+
import fetch, { FormData, fileFrom } from 'node-fetch';
|
|
4
2
|
|
|
5
3
|
export async function uploadFile(reportPath, url, uploadKey, uploadFields) {
|
|
6
4
|
const form = new FormData();
|
|
@@ -10,10 +8,14 @@ export async function uploadFile(reportPath, url, uploadKey, uploadFields) {
|
|
|
10
8
|
}
|
|
11
9
|
|
|
12
10
|
form.append('key', uploadKey);
|
|
13
|
-
form.append('file',
|
|
11
|
+
form.append('file', await fileFrom(reportPath));
|
|
14
12
|
|
|
15
|
-
await
|
|
13
|
+
const response = await fetch(url, {
|
|
16
14
|
method: 'POST',
|
|
17
15
|
body: form,
|
|
18
16
|
});
|
|
17
|
+
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
throw new Error(`Failed to upload the report: ${response.status}`);
|
|
20
|
+
}
|
|
19
21
|
}
|