@node-core/utils 6.4.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -5
- package/bin/ncu-ci.js +3 -19
- package/components/git/benchmark.js +112 -0
- package/components/git/land.js +3 -3
- package/components/git/metadata.js +34 -1
- package/components/git/release.js +3 -5
- package/components/git/security.js +258 -27
- package/components/git/v8.js +6 -3
- package/components/metadata.js +75 -7
- package/lib/auth.js +3 -4
- package/lib/backport_session.js +3 -3
- package/lib/benchmark.js +243 -0
- package/lib/cherry_pick.js +32 -11
- package/lib/ci/failure_aggregator.js +16 -20
- package/lib/ci/run_ci.js +1 -1
- package/lib/cli.js +25 -1
- package/lib/config.js +7 -7
- package/lib/file.js +5 -5
- package/lib/landing_session.js +65 -24
- package/lib/links.js +4 -4
- package/lib/pr_checker.js +163 -31
- package/lib/pr_data.js +1 -1
- package/lib/pr_summary.js +1 -1
- package/lib/prepare_release.js +270 -85
- package/lib/prepare_security.js +312 -46
- package/lib/promote_release.js +19 -16
- package/lib/queries/ListSecurityReleasePRs.gql +19 -0
- package/lib/queries/PR.gql +4 -1
- package/lib/request.js +41 -0
- package/lib/run.js +5 -5
- package/lib/security-announcement.js +10 -5
- package/lib/security-release/security-release.js +160 -20
- package/lib/security_blog.js +130 -65
- package/lib/session.js +4 -3
- package/lib/team_info.js +1 -1
- package/lib/update-v8/applyNodeChanges.js +1 -3
- package/lib/update-v8/backport.js +6 -6
- package/lib/update-v8/commitUpdate.js +3 -6
- package/lib/update-v8/common.js +30 -6
- package/lib/update-v8/constants.js +5 -0
- package/lib/update-v8/deps.js +3 -5
- package/lib/update-v8/index.js +14 -5
- package/lib/update-v8/majorUpdate.js +5 -4
- package/lib/update-v8/minorUpdate.js +4 -4
- package/lib/update-v8/updateCrates.js +192 -0
- package/lib/update-v8/updateV8Clone.js +1 -1
- package/lib/update-v8/updateVersionNumbers.js +3 -6
- package/lib/update-v8/util.js +3 -3
- package/lib/update_security_release.js +347 -105
- package/lib/utils.js +5 -5
- package/lib/verbosity.js +3 -3
- package/lib/wpt/index.js +1 -23
- package/package.json +18 -22
- package/bin/get-metadata.js +0 -11
- package/lib/wpt/templates/README.md +0 -16
- package/npm-shrinkwrap.json +0 -20380
|
@@ -4,11 +4,13 @@ import {
|
|
|
4
4
|
commitAndPushVulnerabilitiesJSON,
|
|
5
5
|
validateDate,
|
|
6
6
|
pickReport,
|
|
7
|
+
getAffectedVersionLines,
|
|
7
8
|
getReportSeverity,
|
|
8
9
|
getSummary,
|
|
10
|
+
confirmSecurityStep,
|
|
11
|
+
writeSecurityFile,
|
|
9
12
|
SecurityRelease
|
|
10
13
|
} from './security-release/security-release.js';
|
|
11
|
-
import fs from 'node:fs';
|
|
12
14
|
import auth from './auth.js';
|
|
13
15
|
import Request from './request.js';
|
|
14
16
|
import nv from '@pkgjs/nv';
|
|
@@ -16,7 +18,7 @@ import semver from 'semver';
|
|
|
16
18
|
|
|
17
19
|
export default class UpdateSecurityRelease extends SecurityRelease {
|
|
18
20
|
async sync() {
|
|
19
|
-
checkRemote(this.cli, this.repository);
|
|
21
|
+
await checkRemote(this.cli, this.repository);
|
|
20
22
|
|
|
21
23
|
const content = this.readVulnerabilitiesJSON();
|
|
22
24
|
const credentials = await auth({
|
|
@@ -46,10 +48,15 @@ export default class UpdateSecurityRelease extends SecurityRelease {
|
|
|
46
48
|
};
|
|
47
49
|
}
|
|
48
50
|
const vulnerabilitiesJSONPath = this.getVulnerabilitiesJSONPath();
|
|
49
|
-
|
|
51
|
+
await writeSecurityFile(
|
|
52
|
+
this.cli,
|
|
53
|
+
vulnerabilitiesJSONPath,
|
|
54
|
+
JSON.stringify(content, null, 2),
|
|
55
|
+
'This writes synchronized HackerOne report data to vulnerabilities.json.'
|
|
56
|
+
);
|
|
50
57
|
|
|
51
58
|
const commitMessage = 'chore: git node security --sync';
|
|
52
|
-
commitAndPushVulnerabilitiesJSON([vulnerabilitiesJSONPath],
|
|
59
|
+
await commitAndPushVulnerabilitiesJSON([vulnerabilitiesJSONPath],
|
|
53
60
|
commitMessage, { cli: this.cli, repository: this.repository });
|
|
54
61
|
this.cli.ok('Synced vulnerabilities.json with HackerOne');
|
|
55
62
|
}
|
|
@@ -59,19 +66,19 @@ export default class UpdateSecurityRelease extends SecurityRelease {
|
|
|
59
66
|
|
|
60
67
|
try {
|
|
61
68
|
validateDate(releaseDate);
|
|
62
|
-
} catch
|
|
69
|
+
} catch {
|
|
63
70
|
cli.error('Invalid date format. Please use the format yyyy/mm/dd.');
|
|
64
71
|
process.exit(1);
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
// checkout on the next-security-release branch
|
|
68
|
-
checkoutOnSecurityReleaseBranch(cli, this.repository);
|
|
75
|
+
await checkoutOnSecurityReleaseBranch(cli, this.repository);
|
|
69
76
|
|
|
70
77
|
// update the release date in the vulnerabilities.json file
|
|
71
78
|
const updatedVulnerabilitiesFiles = await this.updateJSONReleaseDate(releaseDate, { cli });
|
|
72
79
|
|
|
73
80
|
const commitMessage = `chore: update the release date to ${releaseDate}`;
|
|
74
|
-
commitAndPushVulnerabilitiesJSON(updatedVulnerabilitiesFiles,
|
|
81
|
+
await commitAndPushVulnerabilitiesJSON(updatedVulnerabilitiesFiles,
|
|
75
82
|
commitMessage, { cli, repository: this.repository });
|
|
76
83
|
cli.ok('Done!');
|
|
77
84
|
}
|
|
@@ -81,7 +88,12 @@ export default class UpdateSecurityRelease extends SecurityRelease {
|
|
|
81
88
|
const content = this.readVulnerabilitiesJSON(vulnerabilitiesJSONPath);
|
|
82
89
|
content.releaseDate = releaseDate;
|
|
83
90
|
|
|
84
|
-
|
|
91
|
+
await writeSecurityFile(
|
|
92
|
+
this.cli,
|
|
93
|
+
vulnerabilitiesJSONPath,
|
|
94
|
+
JSON.stringify(content, null, 2),
|
|
95
|
+
`This sets releaseDate to ${releaseDate} in vulnerabilities.json.`
|
|
96
|
+
);
|
|
85
97
|
|
|
86
98
|
this.cli.ok(`Updated the release date in vulnerabilities.json: ${releaseDate}`);
|
|
87
99
|
return [vulnerabilitiesJSONPath];
|
|
@@ -95,7 +107,7 @@ export default class UpdateSecurityRelease extends SecurityRelease {
|
|
|
95
107
|
|
|
96
108
|
const req = new Request(credentials);
|
|
97
109
|
// checkout on the next-security-release branch
|
|
98
|
-
checkoutOnSecurityReleaseBranch(this.cli, this.repository);
|
|
110
|
+
await checkoutOnSecurityReleaseBranch(this.cli, this.repository);
|
|
99
111
|
|
|
100
112
|
// get h1 report
|
|
101
113
|
const { data: report } = await req.getReport(reportId);
|
|
@@ -104,18 +116,23 @@ export default class UpdateSecurityRelease extends SecurityRelease {
|
|
|
104
116
|
const vulnerabilitiesJSONPath = this.getVulnerabilitiesJSONPath();
|
|
105
117
|
const content = this.readVulnerabilitiesJSON(vulnerabilitiesJSONPath);
|
|
106
118
|
content.reports.push(entry);
|
|
107
|
-
|
|
119
|
+
await writeSecurityFile(
|
|
120
|
+
this.cli,
|
|
121
|
+
vulnerabilitiesJSONPath,
|
|
122
|
+
JSON.stringify(content, null, 2),
|
|
123
|
+
`This appends HackerOne report ${entry.id} to vulnerabilities.json.`
|
|
124
|
+
);
|
|
108
125
|
this.cli.ok(`Updated vulnerabilities.json with the report: ${entry.id}`);
|
|
109
126
|
const commitMessage = `chore: added report ${entry.id} to vulnerabilities.json`;
|
|
110
|
-
commitAndPushVulnerabilitiesJSON(vulnerabilitiesJSONPath,
|
|
127
|
+
await commitAndPushVulnerabilitiesJSON(vulnerabilitiesJSONPath,
|
|
111
128
|
commitMessage, { cli: this.cli, repository: this.repository });
|
|
112
129
|
this.cli.ok('Done!');
|
|
113
130
|
}
|
|
114
131
|
|
|
115
|
-
removeReport(reportId) {
|
|
132
|
+
async removeReport(reportId) {
|
|
116
133
|
const { cli } = this;
|
|
117
134
|
// checkout on the next-security-release branch
|
|
118
|
-
checkoutOnSecurityReleaseBranch(cli, this.repository);
|
|
135
|
+
await checkoutOnSecurityReleaseBranch(cli, this.repository);
|
|
119
136
|
const vulnerabilitiesJSONPath = this.getVulnerabilitiesJSONPath();
|
|
120
137
|
const content = this.readVulnerabilitiesJSON(vulnerabilitiesJSONPath);
|
|
121
138
|
const found = content.reports.some((report) => report.id === reportId);
|
|
@@ -124,11 +141,16 @@ export default class UpdateSecurityRelease extends SecurityRelease {
|
|
|
124
141
|
process.exit(1);
|
|
125
142
|
}
|
|
126
143
|
content.reports = content.reports.filter((report) => report.id !== reportId);
|
|
127
|
-
|
|
144
|
+
await writeSecurityFile(
|
|
145
|
+
this.cli,
|
|
146
|
+
vulnerabilitiesJSONPath,
|
|
147
|
+
JSON.stringify(content, null, 2),
|
|
148
|
+
`This removes HackerOne report ${reportId} from vulnerabilities.json.`
|
|
149
|
+
);
|
|
128
150
|
this.cli.ok(`Updated vulnerabilities.json with the report: ${reportId}`);
|
|
129
151
|
|
|
130
152
|
const commitMessage = `chore: remove report ${reportId} from vulnerabilities.json`;
|
|
131
|
-
commitAndPushVulnerabilitiesJSON(vulnerabilitiesJSONPath,
|
|
153
|
+
await commitAndPushVulnerabilitiesJSON(vulnerabilitiesJSONPath,
|
|
132
154
|
commitMessage, { cli, repository: this.repository });
|
|
133
155
|
cli.ok('Done!');
|
|
134
156
|
}
|
|
@@ -169,6 +191,16 @@ export default class UpdateSecurityRelease extends SecurityRelease {
|
|
|
169
191
|
}
|
|
170
192
|
|
|
171
193
|
async updateHackonerReportCve(req, report) {
|
|
194
|
+
const { id, cveIds } = report;
|
|
195
|
+
await confirmSecurityStep(
|
|
196
|
+
this.cli,
|
|
197
|
+
`update HackerOne report \`${id}\` with CVEs ${cveIds}`,
|
|
198
|
+
'This writes the assigned CVE IDs back to the HackerOne report.'
|
|
199
|
+
);
|
|
200
|
+
return this.updateHackonerReportCveWithoutConfirmation(req, report);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async updateHackonerReportCveWithoutConfirmation(req, report) {
|
|
172
204
|
const { id, cveIds } = report;
|
|
173
205
|
this.cli.startSpinner(`Updating report ${id} with CVEs ${cveIds}..`);
|
|
174
206
|
const body = {
|
|
@@ -179,90 +211,286 @@ export default class UpdateSecurityRelease extends SecurityRelease {
|
|
|
179
211
|
}
|
|
180
212
|
}
|
|
181
213
|
};
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
214
|
+
try {
|
|
215
|
+
const response = await req.updateReportCVE(id, body);
|
|
216
|
+
if (response.errors) {
|
|
217
|
+
this.cli.stopSpinner(
|
|
218
|
+
`Error updating report ${id}`,
|
|
219
|
+
this.cli.SPINNER_STATUS.FAILED
|
|
220
|
+
);
|
|
221
|
+
this.cli.error(`Error updating report ${id}`);
|
|
222
|
+
this.cli.error(JSON.stringify(response.errors, null, 2));
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
this.cli.stopSpinner(`Done updating report ${id} with CVEs ${cveIds}..`);
|
|
226
|
+
return true;
|
|
227
|
+
} catch (error) {
|
|
228
|
+
this.cli.stopSpinner(
|
|
229
|
+
`Error updating report ${id}`,
|
|
230
|
+
this.cli.SPINNER_STATUS.FAILED
|
|
231
|
+
);
|
|
232
|
+
this.cli.error(error);
|
|
233
|
+
return false;
|
|
186
234
|
}
|
|
187
|
-
this.cli.stopSpinner(`Done updating report ${id} with CVEs ${cveIds}..`);
|
|
188
235
|
}
|
|
189
236
|
|
|
190
237
|
async promptCVECreation(req, reports, programId, content) {
|
|
238
|
+
const reportsWithoutCVE = reports.filter((report) => !report.cveIds?.length);
|
|
239
|
+
const requestAll = await this.promptRequestAllCVEs(reportsWithoutCVE);
|
|
240
|
+
|
|
241
|
+
if (requestAll) {
|
|
242
|
+
await confirmSecurityStep(
|
|
243
|
+
this.cli,
|
|
244
|
+
`request CVEs for ${reportsWithoutCVE.length} HackerOne reports`,
|
|
245
|
+
'This submits CVE requests to HackerOne for every report without a CVE.'
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
191
249
|
const supportedVersions = (await nv('supported'));
|
|
192
250
|
const eolVersions = (await nv('eol'));
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
251
|
+
const versionCache = new Map();
|
|
252
|
+
const successfulReports = await this.collectSuccessfulCVERequests({
|
|
253
|
+
req,
|
|
254
|
+
reports,
|
|
255
|
+
programId,
|
|
256
|
+
supportedVersions,
|
|
257
|
+
eolVersions,
|
|
258
|
+
versionCache,
|
|
259
|
+
requestAll
|
|
260
|
+
});
|
|
199
261
|
|
|
200
|
-
|
|
262
|
+
if (!successfulReports.length) {
|
|
263
|
+
this.cli.warn('No CVE requests succeeded.');
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
201
266
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
267
|
+
await this.applySuccessfulCVEUpdates(req, content, successfulReports);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
showCVERequestSummary(reports) {
|
|
271
|
+
this.cli.info(`Reports selected for CVE requests (${reports.length}):`);
|
|
272
|
+
for (const { id, severity, title } of reports) {
|
|
273
|
+
const rating = severity?.rating || 'unknown';
|
|
274
|
+
this.cli.info(`- ${id} [${rating}] ${title}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
promptRequestAllCVEs(reportsWithoutCVE) {
|
|
279
|
+
if (reportsWithoutCVE.length <= 1) return false;
|
|
280
|
+
this.showCVERequestSummary(reportsWithoutCVE);
|
|
281
|
+
return this.cli.prompt(
|
|
282
|
+
'Request CVEs for all reports without prompting for each report?',
|
|
283
|
+
{ defaultAnswer: false }
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async collectSuccessfulCVERequests({
|
|
288
|
+
req,
|
|
289
|
+
reports,
|
|
290
|
+
programId,
|
|
291
|
+
supportedVersions,
|
|
292
|
+
eolVersions,
|
|
293
|
+
versionCache,
|
|
294
|
+
requestAll
|
|
295
|
+
}) {
|
|
296
|
+
const successfulReports = [];
|
|
297
|
+
try {
|
|
298
|
+
for (const report of reports) {
|
|
299
|
+
const requested = await this.requestReportCVE({
|
|
300
|
+
req,
|
|
301
|
+
report,
|
|
302
|
+
programId,
|
|
303
|
+
supportedVersions,
|
|
304
|
+
eolVersions,
|
|
305
|
+
versionCache,
|
|
306
|
+
requestAll
|
|
307
|
+
});
|
|
308
|
+
if (requested) successfulReports.push(report);
|
|
309
|
+
}
|
|
310
|
+
} catch (error) {
|
|
311
|
+
if (!this.isPromptInterrupted(error)) throw error;
|
|
312
|
+
this.cli.warn('CVE request interrupted. Finalizing successful requests.');
|
|
313
|
+
}
|
|
314
|
+
return successfulReports;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
isPromptInterrupted(error) {
|
|
318
|
+
return error?.name === 'ExitPromptError' ||
|
|
319
|
+
error?.name === 'AbortPromptError' ||
|
|
320
|
+
error?.message?.includes('User force closed');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async requestReportCVE({
|
|
324
|
+
req,
|
|
325
|
+
report,
|
|
326
|
+
programId,
|
|
327
|
+
supportedVersions,
|
|
328
|
+
eolVersions,
|
|
329
|
+
versionCache,
|
|
330
|
+
requestAll
|
|
331
|
+
}) {
|
|
332
|
+
const { id, summary, title, affectedVersions, cveIds, link } = report;
|
|
333
|
+
const affectedVersionLines = getAffectedVersionLines(affectedVersions);
|
|
334
|
+
// skip if already has a CVE
|
|
335
|
+
// risky because the CVE associated might be
|
|
336
|
+
// mentioned in the report and not requested by Node
|
|
337
|
+
if (cveIds?.length) return false;
|
|
338
|
+
|
|
339
|
+
let severity = report.severity;
|
|
340
|
+
|
|
341
|
+
if (!severity.cvss_vector_string || !severity.weakness_id) {
|
|
342
|
+
try {
|
|
343
|
+
const h1Report = await req.getReport(id);
|
|
344
|
+
if (!h1Report.data.relationships.severity?.data.attributes.cvss_vector_string) {
|
|
345
|
+
throw new Error('No severity found');
|
|
217
346
|
}
|
|
347
|
+
severity = {
|
|
348
|
+
weakness_id: h1Report.data.relationships.weakness?.data.id,
|
|
349
|
+
cvss_vector_string:
|
|
350
|
+
h1Report.data.relationships.severity?.data.attributes.cvss_vector_string,
|
|
351
|
+
rating: h1Report.data.relationships.severity?.data.attributes.rating
|
|
352
|
+
};
|
|
353
|
+
} catch {
|
|
354
|
+
this.cli.error(`Couldnt not retrieve severity from report ${id}, skipping...`);
|
|
355
|
+
return false;
|
|
218
356
|
}
|
|
357
|
+
}
|
|
219
358
|
|
|
220
|
-
|
|
359
|
+
const { cvss_vector_string, weakness_id } = severity;
|
|
221
360
|
|
|
222
|
-
|
|
223
|
-
|
|
361
|
+
const create = requestAll || await this.cli.prompt(
|
|
362
|
+
`Request a CVE for: \n
|
|
224
363
|
Title: ${title}\n
|
|
225
364
|
Link: ${link}\n
|
|
226
|
-
Affected versions: ${
|
|
365
|
+
Affected versions: ${affectedVersionLines.join(', ')}\n
|
|
227
366
|
Vector: ${cvss_vector_string}\n
|
|
228
367
|
Summary: ${summary}\n`,
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
368
|
+
{ defaultAnswer: true });
|
|
369
|
+
|
|
370
|
+
if (!create) return false;
|
|
371
|
+
|
|
372
|
+
const { h1AffectedVersions, patchedVersions } =
|
|
373
|
+
await this.calculateVersions(
|
|
374
|
+
affectedVersionLines,
|
|
375
|
+
supportedVersions,
|
|
376
|
+
eolVersions,
|
|
377
|
+
versionCache
|
|
378
|
+
);
|
|
379
|
+
const body = {
|
|
380
|
+
data: {
|
|
381
|
+
type: 'cve-request',
|
|
382
|
+
attributes: {
|
|
383
|
+
team_handle: 'nodejs-team',
|
|
384
|
+
versions: h1AffectedVersions,
|
|
385
|
+
metrics: [
|
|
386
|
+
{
|
|
387
|
+
vectorString: cvss_vector_string
|
|
388
|
+
}
|
|
389
|
+
],
|
|
390
|
+
auto_submit_on_publicly_disclosing_report: true,
|
|
391
|
+
references: ['https://nodejs.org/en/blog/vulnerability'],
|
|
392
|
+
report_id: report.id,
|
|
393
|
+
weakness_id: Number(weakness_id),
|
|
394
|
+
description: report.summary,
|
|
395
|
+
vulnerability_discovered_at: new Date().toISOString()
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
if (!requestAll) {
|
|
400
|
+
await confirmSecurityStep(
|
|
401
|
+
this.cli,
|
|
402
|
+
`request CVE for HackerOne report \`${report.id}\``,
|
|
403
|
+
'This submits a CVE request to HackerOne for the selected report.'
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
this.cli.startSpinner(`Requesting CVE for report ${id}...`);
|
|
407
|
+
let response;
|
|
408
|
+
try {
|
|
409
|
+
response = await req.requestCVE(programId, body);
|
|
410
|
+
} catch (error) {
|
|
411
|
+
this.cli.stopSpinner(
|
|
412
|
+
`Error requesting CVE for report ${id}`,
|
|
413
|
+
this.cli.SPINNER_STATUS.FAILED
|
|
414
|
+
);
|
|
415
|
+
this.cli.error(error);
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
if (response.errors) {
|
|
419
|
+
this.cli.stopSpinner(
|
|
420
|
+
`Error requesting CVE for report ${id}`,
|
|
421
|
+
this.cli.SPINNER_STATUS.FAILED
|
|
422
|
+
);
|
|
423
|
+
this.cli.error(`Error requesting CVE for report ${id}`);
|
|
424
|
+
this.cli.error(JSON.stringify(response.errors, null, 2));
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
this.cli.stopSpinner(`Requested CVE for report ${id}`);
|
|
428
|
+
const { cve_identifier } = response.data.attributes;
|
|
429
|
+
report.cveIds = [cve_identifier];
|
|
430
|
+
report.patchedVersions = patchedVersions;
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async applySuccessfulCVEUpdates(req, content, successfulReports) {
|
|
435
|
+
this.cli.info('CVE requests succeeded:');
|
|
436
|
+
for (const { id, cveIds } of successfulReports) {
|
|
437
|
+
this.cli.info(`- ${id}: ${cveIds.join(', ')}`);
|
|
438
|
+
}
|
|
439
|
+
const updateVulnerabilitiesJSON = await this.cli.prompt(
|
|
440
|
+
'Update vulnerabilities.json with the successful CVE requests?',
|
|
441
|
+
{ defaultAnswer: true }
|
|
442
|
+
);
|
|
443
|
+
if (!updateVulnerabilitiesJSON) {
|
|
444
|
+
this.cli.warn(
|
|
445
|
+
'Skipping HackerOne updates because vulnerabilities.json was not updated.'
|
|
446
|
+
);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const vulnerabilitiesUpdated = await this.updateVulnerabilitiesJSON(content);
|
|
451
|
+
if (!vulnerabilitiesUpdated) {
|
|
452
|
+
this.cli.warn('Skipping HackerOne updates because vulnerabilities.json update failed.');
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const updateHackerOne = await this.cli.prompt(
|
|
457
|
+
'Update HackerOne reports with the successful CVE IDs?',
|
|
458
|
+
{ defaultAnswer: true }
|
|
459
|
+
);
|
|
460
|
+
if (updateHackerOne) {
|
|
461
|
+
await confirmSecurityStep(
|
|
462
|
+
this.cli,
|
|
463
|
+
`update ${successfulReports.length} HackerOne reports with CVE IDs`,
|
|
464
|
+
'This writes the assigned CVE IDs back to all successful HackerOne reports.'
|
|
465
|
+
);
|
|
466
|
+
const h1Succeeded = [];
|
|
467
|
+
const h1Failed = [];
|
|
468
|
+
for (const report of successfulReports) {
|
|
469
|
+
try {
|
|
470
|
+
const updated = await this.updateHackonerReportCveWithoutConfirmation(req, report);
|
|
471
|
+
if (updated) {
|
|
472
|
+
h1Succeeded.push(report);
|
|
473
|
+
} else {
|
|
474
|
+
h1Failed.push(report);
|
|
252
475
|
}
|
|
476
|
+
} catch (error) {
|
|
477
|
+
this.cli.error(error);
|
|
478
|
+
h1Failed.push(report);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (h1Succeeded.length) {
|
|
483
|
+
this.cli.info('HackerOne reports updated:');
|
|
484
|
+
for (const { id, cveIds } of h1Succeeded) {
|
|
485
|
+
this.cli.info(`- ${id}: ${cveIds.join(', ')}`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (h1Failed.length) {
|
|
489
|
+
this.cli.warn('HackerOne reports not updated:');
|
|
490
|
+
for (const { id, cveIds } of h1Failed) {
|
|
491
|
+
this.cli.warn(`- ${id}: ${cveIds.join(', ')}`);
|
|
253
492
|
}
|
|
254
|
-
};
|
|
255
|
-
const response = await req.requestCVE(programId, body);
|
|
256
|
-
if (response.errors) {
|
|
257
|
-
this.cli.error(`Error requesting CVE for report ${id}`);
|
|
258
|
-
this.cli.error(JSON.stringify(response.errors, null, 2));
|
|
259
|
-
continue;
|
|
260
493
|
}
|
|
261
|
-
const { cve_identifier } = response.data.attributes;
|
|
262
|
-
report.cveIds = [cve_identifier];
|
|
263
|
-
report.patchedVersions = patchedVersions;
|
|
264
|
-
this.updateVulnerabilitiesJSON(content);
|
|
265
|
-
await this.updateHackonerReportCve(req, report);
|
|
266
494
|
}
|
|
267
495
|
}
|
|
268
496
|
|
|
@@ -277,39 +505,53 @@ Summary: ${summary}\n`,
|
|
|
277
505
|
}
|
|
278
506
|
}
|
|
279
507
|
|
|
280
|
-
async calculateVersions(
|
|
508
|
+
async calculateVersions(
|
|
509
|
+
affectedVersions,
|
|
510
|
+
supportedVersions,
|
|
511
|
+
eolVersions,
|
|
512
|
+
versionCache = new Map()
|
|
513
|
+
) {
|
|
514
|
+
affectedVersions = getAffectedVersionLines(affectedVersions);
|
|
281
515
|
const h1AffectedVersions = [];
|
|
282
516
|
const patchedVersions = [];
|
|
283
517
|
let isPatchRelease = true;
|
|
284
518
|
for (const affectedVersion of affectedVersions) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
519
|
+
if (!versionCache.has(affectedVersion)) {
|
|
520
|
+
const affectedMajor = affectedVersion.split('.')[0];
|
|
521
|
+
const latest = supportedVersions.find((v) => v.major === Number(affectedMajor)).version;
|
|
522
|
+
const version = await this.cli.prompt(
|
|
523
|
+
`What is the affected version (<=) for release line ${affectedVersion}?`,
|
|
524
|
+
{ questionType: 'input', defaultAnswer: latest });
|
|
525
|
+
|
|
526
|
+
const nextPatchVersion = semver.inc(version, 'patch');
|
|
527
|
+
const nextMinorVersion = semver.inc(version, 'minor');
|
|
528
|
+
const patchedVersion = await this.cli.promptRadio(
|
|
529
|
+
`What is the patched version (>=) for release line ${affectedVersion}?`,
|
|
530
|
+
[nextPatchVersion, nextMinorVersion],
|
|
531
|
+
{
|
|
532
|
+
defaultAnswer: isPatchRelease ? nextPatchVersion : nextMinorVersion
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
if (patchedVersion !== nextPatchVersion) {
|
|
536
|
+
isPatchRelease = false; // is a minor release
|
|
537
|
+
}
|
|
299
538
|
|
|
300
|
-
|
|
301
|
-
|
|
539
|
+
versionCache.set(affectedVersion, {
|
|
540
|
+
patchedVersion,
|
|
541
|
+
h1AffectedVersion: {
|
|
542
|
+
vendor: 'nodejs',
|
|
543
|
+
product: 'node',
|
|
544
|
+
func: '<=',
|
|
545
|
+
version,
|
|
546
|
+
versionType: 'semver',
|
|
547
|
+
affected: true
|
|
548
|
+
}
|
|
549
|
+
});
|
|
302
550
|
}
|
|
303
551
|
|
|
552
|
+
const { h1AffectedVersion, patchedVersion } = versionCache.get(affectedVersion);
|
|
304
553
|
patchedVersions.push(patchedVersion);
|
|
305
|
-
h1AffectedVersions.push(
|
|
306
|
-
vendor: 'nodejs',
|
|
307
|
-
product: 'node',
|
|
308
|
-
func: '<=',
|
|
309
|
-
version,
|
|
310
|
-
versionType: 'semver',
|
|
311
|
-
affected: true
|
|
312
|
-
});
|
|
554
|
+
h1AffectedVersions.push(h1AffectedVersion);
|
|
313
555
|
}
|
|
314
556
|
|
|
315
557
|
// All EOL versions are affected since they no longer receive security patches
|
package/lib/utils.js
CHANGED
|
@@ -6,12 +6,12 @@ import { forceRunAsync } from './run.js';
|
|
|
6
6
|
export function ascending(a, b) {
|
|
7
7
|
if (a === b) return 0;
|
|
8
8
|
return a < b ? -1 : 1;
|
|
9
|
-
}
|
|
9
|
+
}
|
|
10
10
|
|
|
11
11
|
export function descending(a, b) {
|
|
12
12
|
if (a === b) return 0;
|
|
13
13
|
return a > b ? -1 : 1;
|
|
14
|
-
}
|
|
14
|
+
}
|
|
15
15
|
|
|
16
16
|
export function flatten(arr) {
|
|
17
17
|
let result = [];
|
|
@@ -27,7 +27,7 @@ export function flatten(arr) {
|
|
|
27
27
|
|
|
28
28
|
export function shortSha(sha) {
|
|
29
29
|
return sha.slice(0, 12);
|
|
30
|
-
}
|
|
30
|
+
}
|
|
31
31
|
|
|
32
32
|
let isGhAvailableCache;
|
|
33
33
|
export function isGhAvailable() {
|
|
@@ -35,7 +35,7 @@ export function isGhAvailable() {
|
|
|
35
35
|
isGhAvailableCache = which.sync('gh', { nothrow: true }) !== null;
|
|
36
36
|
}
|
|
37
37
|
return isGhAvailableCache;
|
|
38
|
-
}
|
|
38
|
+
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Returns the user's preferred text editor command.
|
|
@@ -65,4 +65,4 @@ export async function getEditor(options = {}) {
|
|
|
65
65
|
|
|
66
66
|
return process.env.VISUAL || process.env.EDITOR ||
|
|
67
67
|
(platform() === 'win32' ? 'notepad.exe' : 'vi');
|
|
68
|
-
}
|
|
68
|
+
}
|
package/lib/verbosity.js
CHANGED
|
@@ -11,7 +11,7 @@ export let verbosity = VERBOSITY.NONE;
|
|
|
11
11
|
|
|
12
12
|
export function isDebugVerbosity() {
|
|
13
13
|
return verbosity === VERBOSITY.DEBUG;
|
|
14
|
-
}
|
|
14
|
+
}
|
|
15
15
|
|
|
16
16
|
export function setVerbosityFromEnv() {
|
|
17
17
|
const env = (process.env.NCU_VERBOSITY || '').toUpperCase();
|
|
@@ -21,9 +21,9 @@ export function setVerbosityFromEnv() {
|
|
|
21
21
|
if (!isDebugVerbosity()) {
|
|
22
22
|
Error.stackTraceLimit = 0;
|
|
23
23
|
}
|
|
24
|
-
}
|
|
24
|
+
}
|
|
25
25
|
|
|
26
26
|
export function debuglog(...args) {
|
|
27
27
|
// Prepend a line break in case it's logged while the spinner is running
|
|
28
28
|
console.error(chalk.green(util.format('\n[DEBUG]', ...args)));
|
|
29
|
-
}
|
|
29
|
+
}
|