@node-core/utils 4.2.3 → 4.4.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 +1 -1
- package/components/git/security.js +100 -4
- package/lib/github/templates/security-pre-release.md +30 -0
- package/lib/prepare_security.js +215 -61
- package/lib/request.js +32 -0
- package/lib/security-announcement.js +83 -0
- package/lib/security-release/security-release.js +109 -0
- package/lib/security_blog.js +182 -0
- package/lib/update-v8/constants.js +10 -0
- package/lib/update_security_release.js +138 -0
- package/package.json +16 -16
- package/lib/github/templates/next-security-release.md +0 -97
package/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Node.js Core Utilities
|
2
|
-
[](https://npmjs.org/package/@node-core/utils)
|
3
3
|
[](https://github.com/nodejs/node-core-utils/workflows/Node.js%20CI/badge.svg?branch=main)
|
4
4
|
[](https://codecov.io/gh/nodejs/node-core-utils)
|
5
5
|
[](https://snyk.io/test/github/nodejs/node-core-utils)
|
@@ -1,5 +1,8 @@
|
|
1
1
|
import CLI from '../../lib/cli.js';
|
2
2
|
import SecurityReleaseSteward from '../../lib/prepare_security.js';
|
3
|
+
import UpdateSecurityRelease from '../../lib/update_security_release.js';
|
4
|
+
import SecurityBlog from '../../lib/security_blog.js';
|
5
|
+
import SecurityAnnouncement from '../../lib/security-announcement.js';
|
3
6
|
|
4
7
|
export const command = 'security [options]';
|
5
8
|
export const describe = 'Manage an in-progress security release or start a new one.';
|
@@ -8,6 +11,26 @@ const securityOptions = {
|
|
8
11
|
start: {
|
9
12
|
describe: 'Start security release process',
|
10
13
|
type: 'boolean'
|
14
|
+
},
|
15
|
+
'update-date': {
|
16
|
+
describe: 'Updates the target date of the security release',
|
17
|
+
type: 'string'
|
18
|
+
},
|
19
|
+
'add-report': {
|
20
|
+
describe: 'Extracts data from HackerOne report and adds it into vulnerabilities.json',
|
21
|
+
type: 'string'
|
22
|
+
},
|
23
|
+
'remove-report': {
|
24
|
+
describe: 'Removes a report from vulnerabilities.json',
|
25
|
+
type: 'string'
|
26
|
+
},
|
27
|
+
'pre-release': {
|
28
|
+
describe: 'Create the pre-release announcement',
|
29
|
+
type: 'boolean'
|
30
|
+
},
|
31
|
+
'notify-pre-release': {
|
32
|
+
describe: 'Notify the community about the security release',
|
33
|
+
type: 'boolean'
|
11
34
|
}
|
12
35
|
};
|
13
36
|
|
@@ -15,21 +38,94 @@ let yargsInstance;
|
|
15
38
|
|
16
39
|
export function builder(yargs) {
|
17
40
|
yargsInstance = yargs;
|
18
|
-
return yargs.options(securityOptions)
|
19
|
-
|
20
|
-
|
41
|
+
return yargs.options(securityOptions)
|
42
|
+
.example(
|
43
|
+
'git node security --start',
|
44
|
+
'Prepare a security release of Node.js')
|
45
|
+
.example(
|
46
|
+
'git node security --update-date=YYYY/MM/DD',
|
47
|
+
'Updates the target date of the security release'
|
48
|
+
)
|
49
|
+
.example(
|
50
|
+
'git node security --add-report=H1-ID',
|
51
|
+
'Fetches HackerOne report based on ID provided and adds it into vulnerabilities.json'
|
52
|
+
)
|
53
|
+
.example(
|
54
|
+
'git node security --remove-report=H1-ID',
|
55
|
+
'Removes the Hackerone report based on ID provided from vulnerabilities.json'
|
56
|
+
)
|
57
|
+
.example(
|
58
|
+
'git node security --pre-release' +
|
59
|
+
'Create the pre-release announcement on the Nodejs.org repo'
|
60
|
+
).example(
|
61
|
+
'git node security --notify-pre-release' +
|
62
|
+
'Notifies the community about the security release'
|
63
|
+
);
|
21
64
|
}
|
22
65
|
|
23
66
|
export function handler(argv) {
|
24
67
|
if (argv.start) {
|
25
68
|
return startSecurityRelease(argv);
|
26
69
|
}
|
70
|
+
if (argv['update-date']) {
|
71
|
+
return updateReleaseDate(argv);
|
72
|
+
}
|
73
|
+
if (argv['pre-release']) {
|
74
|
+
return createPreRelease(argv);
|
75
|
+
}
|
76
|
+
if (argv['add-report']) {
|
77
|
+
return addReport(argv);
|
78
|
+
}
|
79
|
+
if (argv['remove-report']) {
|
80
|
+
return removeReport(argv);
|
81
|
+
}
|
82
|
+
if (argv['notify-pre-release']) {
|
83
|
+
return notifyPreRelease(argv);
|
84
|
+
}
|
27
85
|
yargsInstance.showHelp();
|
28
86
|
}
|
29
87
|
|
30
|
-
async function
|
88
|
+
async function removeReport(argv) {
|
89
|
+
const reportId = argv['remove-report'];
|
90
|
+
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
91
|
+
const cli = new CLI(logStream);
|
92
|
+
const update = new UpdateSecurityRelease(cli);
|
93
|
+
return update.removeReport(reportId);
|
94
|
+
}
|
95
|
+
|
96
|
+
async function addReport(argv) {
|
97
|
+
const reportId = argv['add-report'];
|
98
|
+
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
99
|
+
const cli = new CLI(logStream);
|
100
|
+
const update = new UpdateSecurityRelease(cli);
|
101
|
+
return update.addReport(reportId);
|
102
|
+
}
|
103
|
+
|
104
|
+
async function updateReleaseDate(argv) {
|
105
|
+
const releaseDate = argv['update-date'];
|
106
|
+
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
107
|
+
const cli = new CLI(logStream);
|
108
|
+
const update = new UpdateSecurityRelease(cli);
|
109
|
+
return update.updateReleaseDate(releaseDate);
|
110
|
+
}
|
111
|
+
|
112
|
+
async function createPreRelease() {
|
113
|
+
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
114
|
+
const cli = new CLI(logStream);
|
115
|
+
const preRelease = new SecurityBlog(cli);
|
116
|
+
return preRelease.createPreRelease();
|
117
|
+
}
|
118
|
+
|
119
|
+
async function startSecurityRelease() {
|
31
120
|
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
32
121
|
const cli = new CLI(logStream);
|
33
122
|
const release = new SecurityReleaseSteward(cli);
|
34
123
|
return release.start();
|
35
124
|
}
|
125
|
+
|
126
|
+
async function notifyPreRelease() {
|
127
|
+
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
128
|
+
const cli = new CLI(logStream);
|
129
|
+
const preRelease = new SecurityAnnouncement(cli);
|
130
|
+
return preRelease.notifyPreRelease();
|
131
|
+
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
---
|
2
|
+
date: %ANNOUNCEMENT_DATE%
|
3
|
+
category: vulnerability
|
4
|
+
title: %RELEASE_DATE% Security Releases
|
5
|
+
slug: %SLUG%
|
6
|
+
layout: blog-post
|
7
|
+
author: The Node.js Project
|
8
|
+
---
|
9
|
+
|
10
|
+
# Summary
|
11
|
+
|
12
|
+
The Node.js project will release new versions of the %AFFECTED_VERSIONS%
|
13
|
+
releases lines on or shortly after, %RELEASE_DATE% in order to address:
|
14
|
+
|
15
|
+
%VULNERABILITIES%
|
16
|
+
%OPENSSL_UPDATES%
|
17
|
+
## Impact
|
18
|
+
|
19
|
+
%IMPACT%
|
20
|
+
|
21
|
+
## Release timing
|
22
|
+
|
23
|
+
Releases will be available on, or shortly after, %RELEASE_DATE%.
|
24
|
+
|
25
|
+
## Contact and future updates
|
26
|
+
|
27
|
+
The current Node.js security policy can be found at https://nodejs.org/en/security/.
|
28
|
+
Please follow the process outlined in https://github.com/nodejs/node/blob/master/SECURITY.md if you wish to report a vulnerability in Node.js.
|
29
|
+
|
30
|
+
Subscribe to the low-volume announcement-only nodejs-sec mailing list at https://groups.google.com/forum/#!forum/nodejs-sec to stay up to date on security vulnerabilities and security-related releases of Node.js and the projects maintained in the nodejs GitHub organization.
|
package/lib/prepare_security.js
CHANGED
@@ -1,9 +1,21 @@
|
|
1
1
|
import nv from '@pkgjs/nv';
|
2
|
+
import fs from 'node:fs';
|
3
|
+
import path from 'node:path';
|
2
4
|
import auth from './auth.js';
|
3
5
|
import Request from './request.js';
|
4
|
-
import
|
6
|
+
import {
|
7
|
+
NEXT_SECURITY_RELEASE_BRANCH,
|
8
|
+
NEXT_SECURITY_RELEASE_FOLDER,
|
9
|
+
NEXT_SECURITY_RELEASE_REPOSITORY,
|
10
|
+
PLACEHOLDERS,
|
11
|
+
checkoutOnSecurityReleaseBranch,
|
12
|
+
commitAndPushVulnerabilitiesJSON,
|
13
|
+
getSummary,
|
14
|
+
validateDate
|
15
|
+
} from './security-release/security-release.js';
|
5
16
|
|
6
17
|
export default class SecurityReleaseSteward {
|
18
|
+
repository = NEXT_SECURITY_RELEASE_REPOSITORY;
|
7
19
|
constructor(cli) {
|
8
20
|
this.cli = cli;
|
9
21
|
}
|
@@ -16,71 +28,171 @@ export default class SecurityReleaseSteward {
|
|
16
28
|
});
|
17
29
|
|
18
30
|
const req = new Request(credentials);
|
19
|
-
const
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
repo: 'node-private'
|
28
|
-
});
|
29
|
-
if (data.html_url) {
|
30
|
-
cli.ok('Created: ' + data.html_url);
|
31
|
-
} else {
|
32
|
-
cli.error(data);
|
33
|
-
}
|
31
|
+
const release = new PrepareSecurityRelease(req);
|
32
|
+
const releaseDate = await release.promptReleaseDate(cli);
|
33
|
+
validateDate(releaseDate);
|
34
|
+
const createVulnerabilitiesJSON = await release.promptVulnerabilitiesJSON(cli);
|
35
|
+
|
36
|
+
let securityReleasePRUrl;
|
37
|
+
if (createVulnerabilitiesJSON) {
|
38
|
+
securityReleasePRUrl = await this.createVulnerabilitiesJSON(req, release, { cli });
|
34
39
|
}
|
40
|
+
|
41
|
+
const createIssue = await release.promptCreateRelaseIssue(cli);
|
42
|
+
|
43
|
+
if (createIssue) {
|
44
|
+
const content = await release.buildIssue(releaseDate, securityReleasePRUrl);
|
45
|
+
await release.createIssue(content, { cli });
|
46
|
+
};
|
47
|
+
|
48
|
+
cli.ok('Done!');
|
49
|
+
}
|
50
|
+
|
51
|
+
async createVulnerabilitiesJSON(req, release, { cli }) {
|
52
|
+
// checkout on the next-security-release branch
|
53
|
+
checkoutOnSecurityReleaseBranch(cli, this.repository);
|
54
|
+
|
55
|
+
// choose the reports to include in the security release
|
56
|
+
const reports = await release.chooseReports(cli);
|
57
|
+
|
58
|
+
// create the vulnerabilities.json file in the security-release repo
|
59
|
+
const filePath = await release.createVulnerabilitiesJSON(reports, { cli });
|
60
|
+
|
61
|
+
// review the vulnerabilities.json file
|
62
|
+
const review = await release.promptReviewVulnerabilitiesJSON(cli);
|
63
|
+
|
64
|
+
if (!review) {
|
65
|
+
cli.info(`To push the vulnerabilities.json file run:
|
66
|
+
- git add ${filePath}
|
67
|
+
- git commit -m "chore: create vulnerabilities.json for next security release"
|
68
|
+
- git push -u origin ${NEXT_SECURITY_RELEASE_BRANCH}
|
69
|
+
- open a PR on ${release.repository.owner}/${release.repository.repo}`);
|
70
|
+
return;
|
71
|
+
};
|
72
|
+
|
73
|
+
// commit and push the vulnerabilities.json file
|
74
|
+
const commitMessage = 'chore: create vulnerabilities.json for next security release';
|
75
|
+
commitAndPushVulnerabilitiesJSON(filePath, commitMessage, { cli, repository: this.repository });
|
76
|
+
|
77
|
+
const createPr = await release.promptCreatePR(cli);
|
78
|
+
|
79
|
+
if (!createPr) return;
|
80
|
+
|
81
|
+
// create pr on the security-release repo
|
82
|
+
return release.createPullRequest(req, { cli });
|
35
83
|
}
|
36
84
|
}
|
37
85
|
|
38
|
-
class
|
39
|
-
|
86
|
+
class PrepareSecurityRelease {
|
87
|
+
repository = NEXT_SECURITY_RELEASE_REPOSITORY;
|
88
|
+
title = 'Next Security Release';
|
89
|
+
|
90
|
+
constructor(req, repository) {
|
40
91
|
this.req = req;
|
41
|
-
|
42
|
-
|
43
|
-
|
92
|
+
if (repository) {
|
93
|
+
this.repository = repository;
|
94
|
+
}
|
44
95
|
}
|
45
96
|
|
46
|
-
|
47
|
-
return
|
48
|
-
|
49
|
-
|
50
|
-
import.meta.url
|
51
|
-
),
|
52
|
-
'utf-8'
|
53
|
-
);
|
97
|
+
promptCreatePR(cli) {
|
98
|
+
return cli.prompt(
|
99
|
+
'Create the Next Security Release PR?',
|
100
|
+
{ defaultAnswer: true });
|
54
101
|
}
|
55
102
|
|
56
|
-
async
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
103
|
+
async getSecurityIssueTemplate() {
|
104
|
+
const url = 'https://raw.githubusercontent.com/nodejs/node/main/doc/contributing/security-release-process.md';
|
105
|
+
try {
|
106
|
+
// fetch document from nodejs/node main so we dont need to keep a copy
|
107
|
+
const response = await fetch(url);
|
108
|
+
const body = await response.text();
|
109
|
+
// remove everything before the Planning section
|
110
|
+
const index = body.indexOf('## Planning');
|
111
|
+
if (index !== -1) {
|
112
|
+
return body.substring(index);
|
113
|
+
}
|
114
|
+
return body;
|
115
|
+
} catch (error) {
|
116
|
+
this.cli.error(`Could not retrieve the security issue template from ${url}`);
|
117
|
+
}
|
118
|
+
}
|
63
119
|
|
64
|
-
|
120
|
+
async promptReleaseDate(cli) {
|
121
|
+
const nextWeekDate = new Date();
|
122
|
+
nextWeekDate.setDate(nextWeekDate.getDate() + 7);
|
123
|
+
// Format the date as YYYY/MM/DD
|
124
|
+
const formattedDate = nextWeekDate.toISOString().slice(0, 10).replace(/-/g, '/');
|
125
|
+
return cli.prompt('Enter target release date in YYYY/MM/DD format:', {
|
65
126
|
questionType: 'input',
|
66
|
-
defaultAnswer:
|
127
|
+
defaultAnswer: formattedDate
|
67
128
|
});
|
68
|
-
|
129
|
+
}
|
130
|
+
|
131
|
+
async promptVulnerabilitiesJSON(cli) {
|
132
|
+
return cli.prompt(
|
133
|
+
'Create the vulnerabilities.json?',
|
134
|
+
{ defaultAnswer: true });
|
135
|
+
}
|
69
136
|
|
70
|
-
|
137
|
+
async promptCreateRelaseIssue(cli) {
|
138
|
+
return cli.prompt(
|
139
|
+
'Create the Next Security Release issue?',
|
140
|
+
{ defaultAnswer: true });
|
71
141
|
}
|
72
142
|
|
73
|
-
async
|
143
|
+
async promptReviewVulnerabilitiesJSON(cli) {
|
144
|
+
return cli.prompt(
|
145
|
+
'Please review vulnerabilities.json and press enter to proceed.',
|
146
|
+
{ defaultAnswer: true });
|
147
|
+
}
|
148
|
+
|
149
|
+
async buildIssue(releaseDate, securityReleasePRUrl = PLACEHOLDERS.vulnerabilitiesPRURL) {
|
150
|
+
const template = await this.getSecurityIssueTemplate();
|
151
|
+
const content = template.replace(PLACEHOLDERS.releaseDate, releaseDate)
|
152
|
+
.replace(PLACEHOLDERS.vulnerabilitiesPRURL, securityReleasePRUrl);
|
153
|
+
return content;
|
154
|
+
}
|
155
|
+
|
156
|
+
async createIssue(content, { cli }) {
|
157
|
+
const data = await this.req.createIssue(this.title, content, this.repository);
|
158
|
+
if (data.html_url) {
|
159
|
+
cli.ok(`Created: ${data.html_url}`);
|
160
|
+
} else {
|
161
|
+
cli.error(data);
|
162
|
+
process.exit(1);
|
163
|
+
}
|
164
|
+
}
|
165
|
+
|
166
|
+
async chooseReports(cli) {
|
167
|
+
cli.info('Getting triaged H1 reports...');
|
168
|
+
const reports = await this.req.getTriagedReports();
|
74
169
|
const supportedVersions = (await nv('supported'))
|
75
|
-
.map((v) => v.versionName
|
170
|
+
.map((v) => `${v.versionName}.x`)
|
76
171
|
.join(',');
|
172
|
+
const selectedReports = [];
|
77
173
|
|
78
|
-
let reportsContent = '';
|
79
174
|
for (const report of reports.data) {
|
80
|
-
const {
|
81
|
-
|
175
|
+
const {
|
176
|
+
id, attributes: { title, cve_ids, created_at },
|
177
|
+
relationships: { severity, weakness, reporter }
|
178
|
+
} = report;
|
179
|
+
const link = `https://hackerone.com/reports/${id}`;
|
180
|
+
let reportSeverity = {
|
181
|
+
rating: '',
|
182
|
+
cvss_vector_string: '',
|
183
|
+
weakness_id: ''
|
184
|
+
};
|
185
|
+
if (severity?.data?.attributes?.cvss_vector_string) {
|
186
|
+
const { cvss_vector_string, rating } = severity.data.attributes;
|
187
|
+
reportSeverity = {
|
188
|
+
cvss_vector_string,
|
189
|
+
rating,
|
190
|
+
weakness_id: weakness?.data?.id
|
191
|
+
};
|
192
|
+
}
|
193
|
+
|
82
194
|
cli.separator();
|
83
|
-
cli.info(`Report: ${
|
195
|
+
cli.info(`Report: ${link} - ${title} (${reportSeverity?.rating})`);
|
84
196
|
const include = await cli.prompt(
|
85
197
|
'Would you like to include this report to the next security release?',
|
86
198
|
{ defaultAnswer: true });
|
@@ -88,30 +200,72 @@ class SecurityReleaseIssue {
|
|
88
200
|
continue;
|
89
201
|
}
|
90
202
|
|
91
|
-
reportsContent +=
|
92
|
-
` * **[${id}](https://hackerone.com/bugs?subject=nodejs&report_id=${id}) - ${title} (TBD) - (${reportLevel})**\n`;
|
93
203
|
const versions = await cli.prompt('Which active release lines this report affects?', {
|
94
204
|
questionType: 'input',
|
95
205
|
defaultAnswer: supportedVersions
|
96
206
|
});
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
207
|
+
const summaryContent = await getSummary(id, this.req);
|
208
|
+
|
209
|
+
selectedReports.push({
|
210
|
+
id,
|
211
|
+
title,
|
212
|
+
cve_ids,
|
213
|
+
severity: reportSeverity,
|
214
|
+
summary: summaryContent ?? '',
|
215
|
+
affectedVersions: versions.split(',').map((v) => v.replace('v', '').trim()),
|
216
|
+
link,
|
217
|
+
reporter: reporter.data.attributes.username,
|
218
|
+
created_at // when we request CVE we need to input vulnerability_discovered_at
|
219
|
+
});
|
101
220
|
}
|
102
|
-
|
221
|
+
return selectedReports;
|
103
222
|
}
|
104
223
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
224
|
+
async createVulnerabilitiesJSON(reports, { cli }) {
|
225
|
+
cli.separator('Creating vulnerabilities.json...');
|
226
|
+
const file = JSON.stringify({
|
227
|
+
reports
|
228
|
+
}, null, 2);
|
229
|
+
|
230
|
+
const folderPath = path.join(process.cwd(), NEXT_SECURITY_RELEASE_FOLDER);
|
231
|
+
try {
|
232
|
+
await fs.accessSync(folderPath);
|
233
|
+
} catch (error) {
|
234
|
+
await fs.mkdirSync(folderPath, { recursive: true });
|
109
235
|
}
|
110
|
-
|
111
|
-
|
236
|
+
|
237
|
+
const fullPath = path.join(folderPath, 'vulnerabilities.json');
|
238
|
+
fs.writeFileSync(fullPath, file);
|
239
|
+
cli.ok(`Created ${fullPath} `);
|
240
|
+
|
241
|
+
return fullPath;
|
112
242
|
}
|
113
243
|
|
114
|
-
|
115
|
-
|
244
|
+
async createPullRequest(req, { cli }) {
|
245
|
+
const { owner, repo } = this.repository;
|
246
|
+
const response = await req.createPullRequest(
|
247
|
+
this.title,
|
248
|
+
'List of vulnerabilities to be included in the next security release',
|
249
|
+
{
|
250
|
+
owner,
|
251
|
+
repo,
|
252
|
+
base: 'main',
|
253
|
+
head: 'next-security-release'
|
254
|
+
}
|
255
|
+
|
256
|
+
);
|
257
|
+
const url = response?.html_url;
|
258
|
+
if (url) {
|
259
|
+
cli.ok(`Created: ${url}`);
|
260
|
+
return url;
|
261
|
+
}
|
262
|
+
if (response?.errors) {
|
263
|
+
for (const error of response.errors) {
|
264
|
+
cli.error(error.message);
|
265
|
+
}
|
266
|
+
} else {
|
267
|
+
cli.error(response);
|
268
|
+
}
|
269
|
+
process.exit(1);
|
116
270
|
}
|
117
271
|
}
|
package/lib/request.js
CHANGED
@@ -77,6 +77,25 @@ export default class Request {
|
|
77
77
|
return this.json(url, options);
|
78
78
|
}
|
79
79
|
|
80
|
+
async createPullRequest(title, body, { owner, repo, head, base }) {
|
81
|
+
const url = `https://api.github.com/repos/${owner}/${repo}/pulls`;
|
82
|
+
const options = {
|
83
|
+
method: 'POST',
|
84
|
+
headers: {
|
85
|
+
Authorization: `Basic ${this.credentials.github}`,
|
86
|
+
'User-Agent': 'node-core-utils',
|
87
|
+
Accept: 'application/vnd.github+json'
|
88
|
+
},
|
89
|
+
body: JSON.stringify({
|
90
|
+
title,
|
91
|
+
body,
|
92
|
+
head,
|
93
|
+
base
|
94
|
+
})
|
95
|
+
};
|
96
|
+
return this.json(url, options);
|
97
|
+
}
|
98
|
+
|
80
99
|
async gql(name, variables, path) {
|
81
100
|
const query = this.loadQuery(name);
|
82
101
|
if (path) {
|
@@ -113,6 +132,19 @@ export default class Request {
|
|
113
132
|
return this.json(url, options);
|
114
133
|
}
|
115
134
|
|
135
|
+
async getReport(reportId) {
|
136
|
+
const url = `https://api.hackerone.com/v1/reports/${reportId}`;
|
137
|
+
const options = {
|
138
|
+
method: 'GET',
|
139
|
+
headers: {
|
140
|
+
Authorization: `Basic ${this.credentials.h1}`,
|
141
|
+
'User-Agent': 'node-core-utils',
|
142
|
+
Accept: 'application/json'
|
143
|
+
}
|
144
|
+
};
|
145
|
+
return this.json(url, options);
|
146
|
+
}
|
147
|
+
|
116
148
|
// This is for github v4 API queries, for other types of queries
|
117
149
|
// use .text or .json
|
118
150
|
async query(query, variables) {
|
@@ -0,0 +1,83 @@
|
|
1
|
+
import {
|
2
|
+
NEXT_SECURITY_RELEASE_REPOSITORY,
|
3
|
+
checkoutOnSecurityReleaseBranch,
|
4
|
+
getVulnerabilitiesJSON,
|
5
|
+
validateDate,
|
6
|
+
formatDateToYYYYMMDD
|
7
|
+
} from './security-release/security-release.js';
|
8
|
+
import auth from './auth.js';
|
9
|
+
import Request from './request.js';
|
10
|
+
|
11
|
+
export default class SecurityAnnouncement {
|
12
|
+
repository = NEXT_SECURITY_RELEASE_REPOSITORY;
|
13
|
+
req;
|
14
|
+
constructor(cli) {
|
15
|
+
this.cli = cli;
|
16
|
+
}
|
17
|
+
|
18
|
+
async notifyPreRelease() {
|
19
|
+
const { cli } = this;
|
20
|
+
|
21
|
+
const credentials = await auth({
|
22
|
+
github: true,
|
23
|
+
h1: true
|
24
|
+
});
|
25
|
+
|
26
|
+
this.req = new Request(credentials);
|
27
|
+
|
28
|
+
// checkout on security release branch
|
29
|
+
checkoutOnSecurityReleaseBranch(cli, this.repository);
|
30
|
+
// read vulnerabilities JSON file
|
31
|
+
const content = getVulnerabilitiesJSON(cli);
|
32
|
+
// validate the release date read from vulnerabilities JSON
|
33
|
+
if (!content.releaseDate) {
|
34
|
+
cli.error('Release date is not set in vulnerabilities.json,' +
|
35
|
+
' run `git node security --update-date=YYYY/MM/DD` to set the release date.');
|
36
|
+
process.exit(1);
|
37
|
+
}
|
38
|
+
|
39
|
+
validateDate(content.releaseDate);
|
40
|
+
const releaseDate = new Date(content.releaseDate);
|
41
|
+
|
42
|
+
await Promise.all([this.createDockerNodeIssue(releaseDate),
|
43
|
+
this.createBuildWGIssue(releaseDate)]);
|
44
|
+
}
|
45
|
+
|
46
|
+
async createBuildWGIssue(releaseDate) {
|
47
|
+
const repository = {
|
48
|
+
owner: 'nodejs',
|
49
|
+
repo: 'build'
|
50
|
+
};
|
51
|
+
|
52
|
+
const { title, content } = this.createPreleaseAnnouncementIssue(releaseDate, 'build');
|
53
|
+
await this.createIssue(title, content, repository);
|
54
|
+
}
|
55
|
+
|
56
|
+
createPreleaseAnnouncementIssue(releaseDate, team) {
|
57
|
+
const title = `[NEXT-SECURITY-RELEASE] Heads up on upcoming Node.js\
|
58
|
+
security release ${formatDateToYYYYMMDD(releaseDate)}`;
|
59
|
+
const content = 'As per security release workflow,' +
|
60
|
+
` creating issue to give the ${team} team a heads up.`;
|
61
|
+
return { title, content };
|
62
|
+
}
|
63
|
+
|
64
|
+
async createDockerNodeIssue(releaseDate) {
|
65
|
+
const repository = {
|
66
|
+
owner: 'nodejs',
|
67
|
+
repo: 'docker-node'
|
68
|
+
};
|
69
|
+
|
70
|
+
const { title, content } = this.createPreleaseAnnouncementIssue(releaseDate, 'docker');
|
71
|
+
await this.createIssue(title, content, repository);
|
72
|
+
}
|
73
|
+
|
74
|
+
async createIssue(title, content, repository) {
|
75
|
+
const data = await this.req.createIssue(title, content, repository);
|
76
|
+
if (data.html_url) {
|
77
|
+
this.cli.ok(`Created: ${data.html_url}`);
|
78
|
+
} else {
|
79
|
+
this.cli.error(data);
|
80
|
+
process.exit(1);
|
81
|
+
}
|
82
|
+
}
|
83
|
+
}
|
@@ -0,0 +1,109 @@
|
|
1
|
+
import { runSync } from '../run.js';
|
2
|
+
import nv from '@pkgjs/nv';
|
3
|
+
import fs from 'node:fs';
|
4
|
+
import path from 'node:path';
|
5
|
+
|
6
|
+
export const NEXT_SECURITY_RELEASE_BRANCH = 'next-security-release';
|
7
|
+
export const NEXT_SECURITY_RELEASE_FOLDER = 'security-release/next-security-release';
|
8
|
+
|
9
|
+
export const NEXT_SECURITY_RELEASE_REPOSITORY = {
|
10
|
+
owner: 'nodejs-private',
|
11
|
+
repo: 'security-release'
|
12
|
+
};
|
13
|
+
|
14
|
+
export const PLACEHOLDERS = {
|
15
|
+
releaseDate: '%RELEASE_DATE%',
|
16
|
+
vulnerabilitiesPRURL: '%VULNERABILITIES_PR_URL%',
|
17
|
+
preReleasePrivate: '%PRE_RELEASE_PRIV%',
|
18
|
+
postReleasePrivate: '%POS_RELEASE_PRIV%',
|
19
|
+
affectedLines: '%AFFECTED_LINES%',
|
20
|
+
annoucementDate: '%ANNOUNCEMENT_DATE%',
|
21
|
+
slug: '%SLUG%',
|
22
|
+
affectedVersions: '%AFFECTED_VERSIONS%',
|
23
|
+
openSSLUpdate: '%OPENSSL_UPDATES%',
|
24
|
+
impact: '%IMPACT%',
|
25
|
+
vulnerabilities: '%VULNERABILITIES%'
|
26
|
+
};
|
27
|
+
|
28
|
+
export function checkRemote(cli, repository) {
|
29
|
+
const remote = runSync('git', ['ls-remote', '--get-url', 'origin']).trim();
|
30
|
+
const { owner, repo } = repository;
|
31
|
+
const securityReleaseOrigin = [
|
32
|
+
`https://github.com/${owner}/${repo}.git`,
|
33
|
+
`git@github.com:${owner}/${repo}.git`
|
34
|
+
];
|
35
|
+
|
36
|
+
if (!securityReleaseOrigin.includes(remote)) {
|
37
|
+
cli.error(`Wrong repository! It should be ${securityReleaseOrigin}`);
|
38
|
+
process.exit(1);
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
export function checkoutOnSecurityReleaseBranch(cli, repository) {
|
43
|
+
checkRemote(cli, repository);
|
44
|
+
const currentBranch = runSync('git', ['branch', '--show-current']).trim();
|
45
|
+
cli.info(`Current branch: ${currentBranch} `);
|
46
|
+
|
47
|
+
if (currentBranch !== NEXT_SECURITY_RELEASE_BRANCH) {
|
48
|
+
runSync('git', ['checkout', '-B', NEXT_SECURITY_RELEASE_BRANCH]);
|
49
|
+
cli.ok(`Checkout on branch: ${NEXT_SECURITY_RELEASE_BRANCH} `);
|
50
|
+
};
|
51
|
+
}
|
52
|
+
|
53
|
+
export function commitAndPushVulnerabilitiesJSON(filePath, commitMessage, { cli, repository }) {
|
54
|
+
checkRemote(cli, repository);
|
55
|
+
|
56
|
+
if (Array.isArray(filePath)) {
|
57
|
+
for (const path of filePath) {
|
58
|
+
runSync('git', ['add', path]);
|
59
|
+
}
|
60
|
+
} else {
|
61
|
+
runSync('git', ['add', filePath]);
|
62
|
+
}
|
63
|
+
|
64
|
+
runSync('git', ['commit', '-m', commitMessage]);
|
65
|
+
runSync('git', ['push', '-u', 'origin', NEXT_SECURITY_RELEASE_BRANCH]);
|
66
|
+
cli.ok(`Pushed commit: ${commitMessage} to ${NEXT_SECURITY_RELEASE_BRANCH}`);
|
67
|
+
}
|
68
|
+
|
69
|
+
export async function getSupportedVersions() {
|
70
|
+
const supportedVersions = (await nv('supported'))
|
71
|
+
.map((v) => `${v.versionName}.x`)
|
72
|
+
.join(',');
|
73
|
+
return supportedVersions;
|
74
|
+
}
|
75
|
+
|
76
|
+
export async function getSummary(reportId, req) {
|
77
|
+
const { data } = await req.getReport(reportId);
|
78
|
+
const summaryList = data?.relationships?.summaries?.data;
|
79
|
+
if (!summaryList?.length) return;
|
80
|
+
const summaries = summaryList.filter((summary) => summary?.attributes?.category === 'team');
|
81
|
+
if (!summaries?.length) return;
|
82
|
+
return summaries?.[0].attributes?.content;
|
83
|
+
}
|
84
|
+
|
85
|
+
export function getVulnerabilitiesJSON(cli) {
|
86
|
+
const vulnerabilitiesJSONPath = path.join(process.cwd(),
|
87
|
+
NEXT_SECURITY_RELEASE_FOLDER, 'vulnerabilities.json');
|
88
|
+
cli.startSpinner(`Reading vulnerabilities.json from ${vulnerabilitiesJSONPath}..`);
|
89
|
+
const file = JSON.parse(fs.readFileSync(vulnerabilitiesJSONPath, 'utf-8'));
|
90
|
+
cli.stopSpinner(`Done reading vulnerabilities.json from ${vulnerabilitiesJSONPath}`);
|
91
|
+
return file;
|
92
|
+
}
|
93
|
+
|
94
|
+
export function validateDate(releaseDate) {
|
95
|
+
const value = new Date(releaseDate).valueOf();
|
96
|
+
if (Number.isNaN(value) || value < 0) {
|
97
|
+
throw new Error('Invalid date format');
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
export function formatDateToYYYYMMDD(date) {
|
102
|
+
// Get year, month, and day
|
103
|
+
const year = date.getFullYear();
|
104
|
+
const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are zero-based
|
105
|
+
const day = String(date.getDate()).padStart(2, '0');
|
106
|
+
|
107
|
+
// Concatenate year, month, and day with slashes
|
108
|
+
return `${year}/${month}/${day}`;
|
109
|
+
}
|
@@ -0,0 +1,182 @@
|
|
1
|
+
import fs from 'node:fs';
|
2
|
+
import path from 'node:path';
|
3
|
+
import _ from 'lodash';
|
4
|
+
import {
|
5
|
+
PLACEHOLDERS,
|
6
|
+
getVulnerabilitiesJSON,
|
7
|
+
checkoutOnSecurityReleaseBranch,
|
8
|
+
NEXT_SECURITY_RELEASE_REPOSITORY,
|
9
|
+
validateDate
|
10
|
+
} from './security-release/security-release.js';
|
11
|
+
|
12
|
+
export default class SecurityBlog {
|
13
|
+
repository = NEXT_SECURITY_RELEASE_REPOSITORY;
|
14
|
+
constructor(cli) {
|
15
|
+
this.cli = cli;
|
16
|
+
}
|
17
|
+
|
18
|
+
async createPreRelease() {
|
19
|
+
const { cli } = this;
|
20
|
+
|
21
|
+
// checkout on security release branch
|
22
|
+
checkoutOnSecurityReleaseBranch(cli, this.repository);
|
23
|
+
|
24
|
+
// read vulnerabilities JSON file
|
25
|
+
const content = getVulnerabilitiesJSON(cli);
|
26
|
+
// validate the release date read from vulnerabilities JSON
|
27
|
+
if (!content.releaseDate) {
|
28
|
+
cli.error('Release date is not set in vulnerabilities.json,' +
|
29
|
+
' run `git node security --update-date=YYYY/MM/DD` to set the release date.');
|
30
|
+
process.exit(1);
|
31
|
+
}
|
32
|
+
|
33
|
+
validateDate(content.releaseDate);
|
34
|
+
const releaseDate = new Date(content.releaseDate);
|
35
|
+
|
36
|
+
const template = this.getSecurityPreReleaseTemplate();
|
37
|
+
const data = {
|
38
|
+
annoucementDate: await this.getAnnouncementDate(cli),
|
39
|
+
releaseDate: this.formatReleaseDate(releaseDate),
|
40
|
+
affectedVersions: this.getAffectedVersions(content),
|
41
|
+
vulnerabilities: this.getVulnerabilities(content),
|
42
|
+
slug: this.getSlug(releaseDate),
|
43
|
+
impact: this.getImpact(content),
|
44
|
+
openSSLUpdate: await this.promptOpenSSLUpdate(cli)
|
45
|
+
};
|
46
|
+
const month = releaseDate.toLocaleString('en-US', { month: 'long' }).toLowerCase();
|
47
|
+
const year = releaseDate.getFullYear();
|
48
|
+
const fileName = `${month}-${year}-security-releases.md`;
|
49
|
+
const preRelease = this.buildPreRelease(template, data);
|
50
|
+
const file = path.join(process.cwd(), fileName);
|
51
|
+
fs.writeFileSync(file, preRelease);
|
52
|
+
cli.ok(`Pre-release announcement file created at ${file}`);
|
53
|
+
}
|
54
|
+
|
55
|
+
promptOpenSSLUpdate(cli) {
|
56
|
+
return cli.prompt('Does this security release containt OpenSSL updates?', {
|
57
|
+
defaultAnswer: true
|
58
|
+
});
|
59
|
+
}
|
60
|
+
|
61
|
+
formatReleaseDate(releaseDate) {
|
62
|
+
const options = {
|
63
|
+
weekday: 'long',
|
64
|
+
month: 'long',
|
65
|
+
day: 'numeric',
|
66
|
+
year: 'numeric'
|
67
|
+
};
|
68
|
+
return releaseDate.toLocaleDateString('en-US', options);
|
69
|
+
}
|
70
|
+
|
71
|
+
buildPreRelease(template, data) {
|
72
|
+
const {
|
73
|
+
annoucementDate,
|
74
|
+
releaseDate,
|
75
|
+
affectedVersions,
|
76
|
+
vulnerabilities,
|
77
|
+
slug,
|
78
|
+
impact,
|
79
|
+
openSSLUpdate
|
80
|
+
} = data;
|
81
|
+
return template.replaceAll(PLACEHOLDERS.annoucementDate, annoucementDate)
|
82
|
+
.replaceAll(PLACEHOLDERS.slug, slug)
|
83
|
+
.replaceAll(PLACEHOLDERS.affectedVersions, affectedVersions)
|
84
|
+
.replaceAll(PLACEHOLDERS.vulnerabilities, vulnerabilities)
|
85
|
+
.replaceAll(PLACEHOLDERS.releaseDate, releaseDate)
|
86
|
+
.replaceAll(PLACEHOLDERS.impact, impact)
|
87
|
+
.replaceAll(PLACEHOLDERS.openSSLUpdate, this.getOpenSSLUpdateTemplate(openSSLUpdate));
|
88
|
+
}
|
89
|
+
|
90
|
+
getOpenSSLUpdateTemplate(openSSLUpdate) {
|
91
|
+
if (openSSLUpdate) {
|
92
|
+
return '\n## OpenSSL Security updates\n\n' +
|
93
|
+
'This security release includes OpenSSL security updates\n';
|
94
|
+
}
|
95
|
+
return '';
|
96
|
+
}
|
97
|
+
|
98
|
+
getSlug(releaseDate) {
|
99
|
+
const month = releaseDate.toLocaleString('en-US', { month: 'long' });
|
100
|
+
const year = releaseDate.getFullYear();
|
101
|
+
return `${month.toLocaleLowerCase()}-${year}-security-releases`;
|
102
|
+
}
|
103
|
+
|
104
|
+
async getAnnouncementDate(cli) {
|
105
|
+
try {
|
106
|
+
const date = await this.promptAnnouncementDate(cli);
|
107
|
+
validateDate(date);
|
108
|
+
return new Date(date).toISOString();
|
109
|
+
} catch (error) {
|
110
|
+
return PLACEHOLDERS.annoucementDate;
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
promptAnnouncementDate(cli) {
|
115
|
+
const today = new Date().toISOString().substring(0, 10).replace(/-/g, '/');
|
116
|
+
return cli.prompt('When is the security release going to be announced? ' +
|
117
|
+
'Enter in YYYY/MM/DD format:', {
|
118
|
+
questionType: 'input',
|
119
|
+
defaultAnswer: today
|
120
|
+
});
|
121
|
+
}
|
122
|
+
|
123
|
+
getImpact(content) {
|
124
|
+
const impact = content.reports.reduce((acc, report) => {
|
125
|
+
for (const affectedVersion of report.affectedVersions) {
|
126
|
+
if (acc[affectedVersion]) {
|
127
|
+
acc[affectedVersion].push(report);
|
128
|
+
} else {
|
129
|
+
acc[affectedVersion] = [report];
|
130
|
+
}
|
131
|
+
}
|
132
|
+
return acc;
|
133
|
+
}, {});
|
134
|
+
|
135
|
+
const impactText = [];
|
136
|
+
for (const [key, value] of Object.entries(impact)) {
|
137
|
+
const groupedByRating = Object.values(_.groupBy(value, 'severity.rating'))
|
138
|
+
.map(severity => {
|
139
|
+
if (!severity[0]?.severity?.rating) {
|
140
|
+
this.cli.error(`severity.rating not found for the report ${severity[0].id}. \
|
141
|
+
Please add it manually before continuing.`);
|
142
|
+
process.exit(1);
|
143
|
+
}
|
144
|
+
const firstSeverityRating = severity[0].severity.rating.toLocaleLowerCase();
|
145
|
+
return `${severity.length} ${firstSeverityRating} severity issues`;
|
146
|
+
}).join(', ');
|
147
|
+
|
148
|
+
impactText.push(`The ${key} release line of Node.js is vulnerable to ${groupedByRating}.`);
|
149
|
+
}
|
150
|
+
|
151
|
+
return impactText.join('\n');
|
152
|
+
}
|
153
|
+
|
154
|
+
getVulnerabilities(content) {
|
155
|
+
const grouped = _.groupBy(content.reports, 'severity.rating');
|
156
|
+
const text = [];
|
157
|
+
for (const [key, value] of Object.entries(grouped)) {
|
158
|
+
text.push(`- ${value.length} ${key.toLocaleLowerCase()} severity issues.`);
|
159
|
+
}
|
160
|
+
return text.join('\n');
|
161
|
+
}
|
162
|
+
|
163
|
+
getAffectedVersions(content) {
|
164
|
+
const affectedVersions = new Set();
|
165
|
+
for (const report of Object.values(content.reports)) {
|
166
|
+
for (const affectedVersion of report.affectedVersions) {
|
167
|
+
affectedVersions.add(affectedVersion);
|
168
|
+
}
|
169
|
+
}
|
170
|
+
return Array.from(affectedVersions).join(', ');
|
171
|
+
}
|
172
|
+
|
173
|
+
getSecurityPreReleaseTemplate() {
|
174
|
+
return fs.readFileSync(
|
175
|
+
new URL(
|
176
|
+
'./github/templates/security-pre-release.md',
|
177
|
+
import.meta.url
|
178
|
+
),
|
179
|
+
'utf-8'
|
180
|
+
);
|
181
|
+
}
|
182
|
+
}
|
@@ -34,6 +34,10 @@ const abseilIgnore = `!/third_party/abseil-cpp
|
|
34
34
|
/third_party/abseil-cpp/.github
|
35
35
|
/third_party/abseil-cpp/ci`;
|
36
36
|
|
37
|
+
const fp16Ignore = `!/third_party/fp16
|
38
|
+
/third_party/fp16/src/*
|
39
|
+
!/third_party/fp16/src/include`;
|
40
|
+
|
37
41
|
export const v8Deps = [
|
38
42
|
{
|
39
43
|
name: 'trace_event',
|
@@ -92,5 +96,11 @@ export const v8Deps = [
|
|
92
96
|
repo: 'third_party/abseil-cpp',
|
93
97
|
gitignore: abseilIgnore,
|
94
98
|
since: 121
|
99
|
+
},
|
100
|
+
{
|
101
|
+
name: 'fp16',
|
102
|
+
repo: 'third_party/fp16/src',
|
103
|
+
gitignore: fp16Ignore,
|
104
|
+
since: 124
|
95
105
|
}
|
96
106
|
];
|
@@ -0,0 +1,138 @@
|
|
1
|
+
import {
|
2
|
+
NEXT_SECURITY_RELEASE_FOLDER,
|
3
|
+
NEXT_SECURITY_RELEASE_REPOSITORY,
|
4
|
+
checkoutOnSecurityReleaseBranch,
|
5
|
+
commitAndPushVulnerabilitiesJSON,
|
6
|
+
getSupportedVersions,
|
7
|
+
getSummary,
|
8
|
+
validateDate
|
9
|
+
} from './security-release/security-release.js';
|
10
|
+
import fs from 'node:fs';
|
11
|
+
import path from 'node:path';
|
12
|
+
import auth from './auth.js';
|
13
|
+
import Request from './request.js';
|
14
|
+
|
15
|
+
export default class UpdateSecurityRelease {
|
16
|
+
repository = NEXT_SECURITY_RELEASE_REPOSITORY;
|
17
|
+
constructor(cli) {
|
18
|
+
this.cli = cli;
|
19
|
+
}
|
20
|
+
|
21
|
+
async updateReleaseDate(releaseDate) {
|
22
|
+
const { cli } = this;
|
23
|
+
|
24
|
+
try {
|
25
|
+
validateDate(releaseDate);
|
26
|
+
} catch (error) {
|
27
|
+
cli.error('Invalid date format. Please use the format yyyy/mm/dd.');
|
28
|
+
process.exit(1);
|
29
|
+
}
|
30
|
+
|
31
|
+
// checkout on the next-security-release branch
|
32
|
+
checkoutOnSecurityReleaseBranch(cli, this.repository);
|
33
|
+
|
34
|
+
// update the release date in the vulnerabilities.json file
|
35
|
+
const updatedVulnerabilitiesFiles = await this.updateVulnerabilitiesJSON(releaseDate, { cli });
|
36
|
+
|
37
|
+
const commitMessage = `chore: update the release date to ${releaseDate}`;
|
38
|
+
commitAndPushVulnerabilitiesJSON(updatedVulnerabilitiesFiles,
|
39
|
+
commitMessage, { cli, repository: this.repository });
|
40
|
+
cli.ok('Done!');
|
41
|
+
}
|
42
|
+
|
43
|
+
readVulnerabilitiesJSON(vulnerabilitiesJSONPath) {
|
44
|
+
const exists = fs.existsSync(vulnerabilitiesJSONPath);
|
45
|
+
|
46
|
+
if (!exists) {
|
47
|
+
this.cli.error(`The file vulnerabilities.json does not exist at ${vulnerabilitiesJSONPath}`);
|
48
|
+
process.exit(1);
|
49
|
+
}
|
50
|
+
|
51
|
+
return JSON.parse(fs.readFileSync(vulnerabilitiesJSONPath, 'utf8'));
|
52
|
+
}
|
53
|
+
|
54
|
+
getVulnerabilitiesJSONPath() {
|
55
|
+
return path.join(process.cwd(),
|
56
|
+
NEXT_SECURITY_RELEASE_FOLDER, 'vulnerabilities.json');
|
57
|
+
}
|
58
|
+
|
59
|
+
async updateVulnerabilitiesJSON(releaseDate) {
|
60
|
+
const vulnerabilitiesJSONPath = this.getVulnerabilitiesJSONPath();
|
61
|
+
const content = this.readVulnerabilitiesJSON(vulnerabilitiesJSONPath);
|
62
|
+
content.releaseDate = releaseDate;
|
63
|
+
|
64
|
+
fs.writeFileSync(vulnerabilitiesJSONPath, JSON.stringify(content, null, 2));
|
65
|
+
|
66
|
+
this.cli.ok(`Updated the release date in vulnerabilities.json: ${releaseDate}`);
|
67
|
+
return [vulnerabilitiesJSONPath];
|
68
|
+
}
|
69
|
+
|
70
|
+
async addReport(reportId) {
|
71
|
+
const { cli } = this;
|
72
|
+
const credentials = await auth({
|
73
|
+
github: true,
|
74
|
+
h1: true
|
75
|
+
});
|
76
|
+
|
77
|
+
const req = new Request(credentials);
|
78
|
+
// checkout on the next-security-release branch
|
79
|
+
checkoutOnSecurityReleaseBranch(cli, this.repository);
|
80
|
+
|
81
|
+
// get h1 report
|
82
|
+
const { data: report } = await req.getReport(reportId);
|
83
|
+
const { id, attributes: { title, cve_ids }, relationships: { severity, reporter } } = report;
|
84
|
+
// if severity is not set on h1, set it to TBD
|
85
|
+
const reportLevel = severity ? severity.data.attributes.rating : 'TBD';
|
86
|
+
|
87
|
+
// get the affected versions
|
88
|
+
const supportedVersions = await getSupportedVersions();
|
89
|
+
const versions = await cli.prompt('Which active release lines this report affects?', {
|
90
|
+
questionType: 'input',
|
91
|
+
defaultAnswer: supportedVersions
|
92
|
+
});
|
93
|
+
|
94
|
+
// get the team summary from h1 report
|
95
|
+
const summaryContent = await getSummary(id, req);
|
96
|
+
|
97
|
+
const entry = {
|
98
|
+
id,
|
99
|
+
title,
|
100
|
+
cve_ids,
|
101
|
+
severity: reportLevel,
|
102
|
+
summary: summaryContent ?? '',
|
103
|
+
affectedVersions: versions.split(',').map((v) => v.replace('v', '').trim()),
|
104
|
+
reporter: reporter.data.attributes.username
|
105
|
+
};
|
106
|
+
|
107
|
+
const vulnerabilitiesJSONPath = this.getVulnerabilitiesJSONPath();
|
108
|
+
const content = this.readVulnerabilitiesJSON(vulnerabilitiesJSONPath);
|
109
|
+
content.reports.push(entry);
|
110
|
+
fs.writeFileSync(vulnerabilitiesJSONPath, JSON.stringify(content, null, 2));
|
111
|
+
this.cli.ok(`Updated vulnerabilities.json with the report: ${id}`);
|
112
|
+
const commitMessage = `chore: added report ${id} to vulnerabilities.json`;
|
113
|
+
commitAndPushVulnerabilitiesJSON(vulnerabilitiesJSONPath,
|
114
|
+
commitMessage, { cli, repository: this.repository });
|
115
|
+
cli.ok('Done!');
|
116
|
+
}
|
117
|
+
|
118
|
+
removeReport(reportId) {
|
119
|
+
const { cli } = this;
|
120
|
+
// checkout on the next-security-release branch
|
121
|
+
checkoutOnSecurityReleaseBranch(cli, this.repository);
|
122
|
+
const vulnerabilitiesJSONPath = this.getVulnerabilitiesJSONPath();
|
123
|
+
const content = this.readVulnerabilitiesJSON(vulnerabilitiesJSONPath);
|
124
|
+
const found = content.reports.some((report) => report.id === reportId);
|
125
|
+
if (!found) {
|
126
|
+
cli.error(`Report with id ${reportId} not found in vulnerabilities.json`);
|
127
|
+
process.exit(1);
|
128
|
+
}
|
129
|
+
content.reports = content.reports.filter((report) => report.id !== reportId);
|
130
|
+
fs.writeFileSync(vulnerabilitiesJSONPath, JSON.stringify(content, null, 2));
|
131
|
+
this.cli.ok(`Updated vulnerabilities.json with the report: ${reportId}`);
|
132
|
+
|
133
|
+
const commitMessage = `chore: remove report ${reportId} from vulnerabilities.json`;
|
134
|
+
commitAndPushVulnerabilitiesJSON(vulnerabilitiesJSONPath,
|
135
|
+
commitMessage, { cli, repository: this.repository });
|
136
|
+
cli.ok('Done!');
|
137
|
+
}
|
138
|
+
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@node-core/utils",
|
3
|
-
"version": "4.
|
3
|
+
"version": "4.4.0",
|
4
4
|
"description": "Utilities for Node.js core collaborators",
|
5
5
|
"type": "module",
|
6
6
|
"engines": {
|
@@ -34,35 +34,35 @@
|
|
34
34
|
],
|
35
35
|
"license": "MIT",
|
36
36
|
"dependencies": {
|
37
|
-
"@listr2/prompt-adapter-enquirer": "^
|
38
|
-
"@node-core/caritat": "^1.
|
37
|
+
"@listr2/prompt-adapter-enquirer": "^2.0.1",
|
38
|
+
"@node-core/caritat": "^1.3.0",
|
39
39
|
"@pkgjs/nv": "^0.2.2",
|
40
|
-
"branch-diff": "^
|
40
|
+
"branch-diff": "^3.0.2",
|
41
41
|
"chalk": "^5.3.0",
|
42
|
-
"changelog-maker": "^
|
42
|
+
"changelog-maker": "^4.0.1",
|
43
43
|
"cheerio": "^1.0.0-rc.12",
|
44
44
|
"clipboardy": "^4.0.0",
|
45
45
|
"core-validate-commit": "^4.0.0",
|
46
46
|
"figures": "^6.0.1",
|
47
|
-
"ghauth": "^6.0.
|
48
|
-
"inquirer": "^9.2.
|
47
|
+
"ghauth": "^6.0.1",
|
48
|
+
"inquirer": "^9.2.12",
|
49
49
|
"js-yaml": "^4.1.0",
|
50
|
-
"listr2": "^
|
50
|
+
"listr2": "^8.0.1",
|
51
51
|
"lodash": "^4.17.21",
|
52
52
|
"log-symbols": "^6.0.0",
|
53
|
-
"ora": "^
|
54
|
-
"replace-in-file": "^7.0
|
55
|
-
"undici": "^
|
53
|
+
"ora": "^8.0.1",
|
54
|
+
"replace-in-file": "^7.1.0",
|
55
|
+
"undici": "^6.3.0",
|
56
56
|
"which": "^4.0.0",
|
57
57
|
"yargs": "^17.7.2"
|
58
58
|
},
|
59
59
|
"devDependencies": {
|
60
|
-
"@reporters/github": "^1.5.
|
61
|
-
"c8": "^
|
62
|
-
"eslint": "^8.
|
60
|
+
"@reporters/github": "^1.5.4",
|
61
|
+
"c8": "^9.0.0",
|
62
|
+
"eslint": "^8.56.0",
|
63
63
|
"eslint-config-standard": "^17.1.0",
|
64
|
-
"eslint-plugin-import": "^2.29.
|
65
|
-
"eslint-plugin-n": "^16.
|
64
|
+
"eslint-plugin-import": "^2.29.1",
|
65
|
+
"eslint-plugin-n": "^16.6.1",
|
66
66
|
"eslint-plugin-promise": "^6.1.1",
|
67
67
|
"sinon": "^17.0.1"
|
68
68
|
}
|
@@ -1,97 +0,0 @@
|
|
1
|
-
## Planning
|
2
|
-
|
3
|
-
* [X] Open an [issue](https://github.com/nodejs-private/node-private) titled
|
4
|
-
`Next Security Release`, and put this checklist in the description.
|
5
|
-
|
6
|
-
* [ ] Get agreement on the list of vulnerabilities to be addressed:
|
7
|
-
%REPORTS%
|
8
|
-
|
9
|
-
* [ ] PR release announcements in [private](https://github.com/nodejs-private/nodejs.org-private):
|
10
|
-
* [ ] pre-release: %PRE_RELEASE_PRIV%
|
11
|
-
* [ ] post-release: %POS_RELEASE_PRIV%
|
12
|
-
* List vulnerabilities in order of descending severity
|
13
|
-
* Ask the HackerOne reporter if they would like to be credited on the
|
14
|
-
security release blog page
|
15
|
-
|
16
|
-
* [ ] Get agreement on the planned date for the release: %RELEASE_DATE%
|
17
|
-
|
18
|
-
* [ ] Get release team volunteers for all affected lines:
|
19
|
-
%AFFECTED_LINES%
|
20
|
-
|
21
|
-
## Announcement (one week in advance of the planned release)
|
22
|
-
|
23
|
-
* [ ] Verify that GitHub Actions are working as normal: <https://www.githubstatus.com/>.
|
24
|
-
|
25
|
-
* [ ] Check that all vulnerabilities are ready for release integration:
|
26
|
-
* PRs against all affected release lines or cherry-pick clean
|
27
|
-
* Approved
|
28
|
-
* (optional) Approved by the reporter
|
29
|
-
* Build and send the binary to the reporter according to its architecture
|
30
|
-
and ask for a review. This step is important to avoid insufficient fixes
|
31
|
-
between Security Releases.
|
32
|
-
* Have CVEs
|
33
|
-
* Make sure that dependent libraries have CVEs for their issues. We should
|
34
|
-
only create CVEs for vulnerabilities in Node.js itself. This is to avoid
|
35
|
-
having duplicate CVEs for the same vulnerability.
|
36
|
-
* Described in the pre/post announcements
|
37
|
-
|
38
|
-
* [ ] Pre-release announcement to nodejs.org blog: TBD
|
39
|
-
(Re-PR the pre-approved branch from nodejs-private/nodejs.org-private to
|
40
|
-
nodejs/nodejs.org)
|
41
|
-
|
42
|
-
* [ ] Pre-release announcement [email](https://groups.google.com/forum/#!forum/nodejs-sec): TBD
|
43
|
-
* Subject: `Node.js security updates for all active release lines, Month Year`
|
44
|
-
|
45
|
-
* [ ] CC `oss-security@lists.openwall.com` on pre-release
|
46
|
-
* [ ] Forward the email you receive to `oss-security@lists.openwall.com`.
|
47
|
-
|
48
|
-
* [ ] Create a new issue in [nodejs/tweet](https://github.com/nodejs/tweet/issues)
|
49
|
-
|
50
|
-
* [ ] Request releaser(s) to start integrating the PRs to be released.
|
51
|
-
|
52
|
-
* [ ] Notify [docker-node](https://github.com/nodejs/docker-node/issues) of upcoming security release date: TBD
|
53
|
-
|
54
|
-
* [ ] Notify build-wg of upcoming security release date by opening an issue
|
55
|
-
in [nodejs/build](https://github.com/nodejs/build/issues) to request WG members are available to fix any CI issues: TBD
|
56
|
-
|
57
|
-
## Release day
|
58
|
-
|
59
|
-
* [ ] [Lock CI](https://github.com/nodejs/build/blob/HEAD/doc/jenkins-guide.md#before-the-release)
|
60
|
-
|
61
|
-
* [ ] The releaser(s) run the release process to completion.
|
62
|
-
|
63
|
-
* [ ] [Unlock CI](https://github.com/nodejs/build/blob/HEAD/doc/jenkins-guide.md#after-the-release)
|
64
|
-
|
65
|
-
* [ ] Post-release announcement to Nodejs.org blog:
|
66
|
-
* (Re-PR the pre-approved branch from nodejs-private/nodejs.org-private to
|
67
|
-
nodejs/nodejs.org)
|
68
|
-
|
69
|
-
* [ ] Post-release announcement in reply email: TBD
|
70
|
-
|
71
|
-
* [ ] Notify `#nodejs-social` about the release.
|
72
|
-
|
73
|
-
* [ ] Comment in [docker-node][] issue that release is ready for integration.
|
74
|
-
The docker-node team will build and release docker image updates.
|
75
|
-
|
76
|
-
* [ ] For every H1 report resolved:
|
77
|
-
* Close as Resolved
|
78
|
-
* Request Disclosure
|
79
|
-
* Request publication of H1 CVE requests
|
80
|
-
* (Check that the "Version Fixed" field in the CVE is correct, and provide
|
81
|
-
links to the release blogs in the "Public Reference" section)
|
82
|
-
|
83
|
-
* [ ] PR machine-readable JSON descriptions of the vulnerabilities to the
|
84
|
-
[core](https://github.com/nodejs/security-wg/tree/HEAD/vuln/core)
|
85
|
-
vulnerability DB.
|
86
|
-
* For each vulnerability add a `#.json` file, one can copy an existing
|
87
|
-
[json](https://github.com/nodejs/security-wg/blob/0d82062d917cb9ddab88f910559469b2b13812bf/vuln/core/78.json)
|
88
|
-
file, and increment the latest created file number and use that as the name
|
89
|
-
of the new file to be added. For example, `79.json`.
|
90
|
-
|
91
|
-
* [ ] Close this issue
|
92
|
-
|
93
|
-
* [ ] Make sure the PRs for the vulnerabilities are closed.
|
94
|
-
|
95
|
-
* [ ] PR in that you stewarded the release in
|
96
|
-
[Security release stewards](https://github.com/nodejs/node/blob/HEAD/doc/contributing/security-release-process.md#security-release-stewards).
|
97
|
-
If necessary add the next rotation of the steward rotation.
|