@node-core/utils 4.3.0 → 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/components/git/security.js +100 -4
- package/lib/github/templates/security-pre-release.md +30 -0
- package/lib/prepare_security.js +82 -94
- 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_security_release.js +138 -0
- package/package.json +1 -1
- package/lib/github/templates/next-security-release.md +0 -98
@@ -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,19 +1,21 @@
|
|
1
1
|
import nv from '@pkgjs/nv';
|
2
|
-
import auth from './auth.js';
|
3
|
-
import Request from './request.js';
|
4
2
|
import fs from 'node:fs';
|
5
|
-
import { runSync } from './run.js';
|
6
3
|
import path from 'node:path';
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
4
|
+
import auth from './auth.js';
|
5
|
+
import Request from './request.js';
|
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';
|
15
16
|
|
16
17
|
export default class SecurityReleaseSteward {
|
18
|
+
repository = NEXT_SECURITY_RELEASE_REPOSITORY;
|
17
19
|
constructor(cli) {
|
18
20
|
this.cli = cli;
|
19
21
|
}
|
@@ -28,9 +30,10 @@ export default class SecurityReleaseSteward {
|
|
28
30
|
const req = new Request(credentials);
|
29
31
|
const release = new PrepareSecurityRelease(req);
|
30
32
|
const releaseDate = await release.promptReleaseDate(cli);
|
31
|
-
|
32
|
-
|
33
|
+
validateDate(releaseDate);
|
33
34
|
const createVulnerabilitiesJSON = await release.promptVulnerabilitiesJSON(cli);
|
35
|
+
|
36
|
+
let securityReleasePRUrl;
|
34
37
|
if (createVulnerabilitiesJSON) {
|
35
38
|
securityReleasePRUrl = await this.createVulnerabilitiesJSON(req, release, { cli });
|
36
39
|
}
|
@@ -38,7 +41,7 @@ export default class SecurityReleaseSteward {
|
|
38
41
|
const createIssue = await release.promptCreateRelaseIssue(cli);
|
39
42
|
|
40
43
|
if (createIssue) {
|
41
|
-
const
|
44
|
+
const content = await release.buildIssue(releaseDate, securityReleasePRUrl);
|
42
45
|
await release.createIssue(content, { cli });
|
43
46
|
};
|
44
47
|
|
@@ -47,7 +50,7 @@ export default class SecurityReleaseSteward {
|
|
47
50
|
|
48
51
|
async createVulnerabilitiesJSON(req, release, { cli }) {
|
49
52
|
// checkout on the next-security-release branch
|
50
|
-
|
53
|
+
checkoutOnSecurityReleaseBranch(cli, this.repository);
|
51
54
|
|
52
55
|
// choose the reports to include in the security release
|
53
56
|
const reports = await release.chooseReports(cli);
|
@@ -62,13 +65,14 @@ export default class SecurityReleaseSteward {
|
|
62
65
|
cli.info(`To push the vulnerabilities.json file run:
|
63
66
|
- git add ${filePath}
|
64
67
|
- git commit -m "chore: create vulnerabilities.json for next security release"
|
65
|
-
- git push -u origin
|
68
|
+
- git push -u origin ${NEXT_SECURITY_RELEASE_BRANCH}
|
66
69
|
- open a PR on ${release.repository.owner}/${release.repository.repo}`);
|
67
70
|
return;
|
68
71
|
};
|
69
72
|
|
70
73
|
// commit and push the vulnerabilities.json file
|
71
|
-
|
74
|
+
const commitMessage = 'chore: create vulnerabilities.json for next security release';
|
75
|
+
commitAndPushVulnerabilitiesJSON(filePath, commitMessage, { cli, repository: this.repository });
|
72
76
|
|
73
77
|
const createPr = await release.promptCreatePR(cli);
|
74
78
|
|
@@ -80,13 +84,8 @@ export default class SecurityReleaseSteward {
|
|
80
84
|
}
|
81
85
|
|
82
86
|
class PrepareSecurityRelease {
|
83
|
-
repository =
|
84
|
-
owner: 'nodejs-private',
|
85
|
-
repo: 'security-release'
|
86
|
-
};
|
87
|
-
|
87
|
+
repository = NEXT_SECURITY_RELEASE_REPOSITORY;
|
88
88
|
title = 'Next Security Release';
|
89
|
-
nextSecurityReleaseBranch = 'next-security-release';
|
90
89
|
|
91
90
|
constructor(req, repository) {
|
92
91
|
this.req = req;
|
@@ -101,41 +100,31 @@ class PrepareSecurityRelease {
|
|
101
100
|
{ defaultAnswer: true });
|
102
101
|
}
|
103
102
|
|
104
|
-
|
105
|
-
const
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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}`);
|
112
117
|
}
|
113
118
|
}
|
114
119
|
|
115
|
-
commitAndPushVulnerabilitiesJSON(filePath, cli) {
|
116
|
-
this.checkRemote(cli);
|
117
|
-
|
118
|
-
runSync('git', ['add', filePath]);
|
119
|
-
const commitMessage = 'chore: create vulnerabilities.json for next security release';
|
120
|
-
runSync('git', ['commit', '-m', commitMessage]);
|
121
|
-
runSync('git', ['push', '-u', 'origin', 'next-security-release']);
|
122
|
-
cli.ok(`Pushed commit: ${commitMessage} to ${this.nextSecurityReleaseBranch}`);
|
123
|
-
}
|
124
|
-
|
125
|
-
getSecurityIssueTemplate() {
|
126
|
-
return fs.readFileSync(
|
127
|
-
new URL(
|
128
|
-
'./github/templates/next-security-release.md',
|
129
|
-
import.meta.url
|
130
|
-
),
|
131
|
-
'utf-8'
|
132
|
-
);
|
133
|
-
}
|
134
|
-
|
135
120
|
async promptReleaseDate(cli) {
|
136
|
-
|
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:', {
|
137
126
|
questionType: 'input',
|
138
|
-
defaultAnswer:
|
127
|
+
defaultAnswer: formattedDate
|
139
128
|
});
|
140
129
|
}
|
141
130
|
|
@@ -157,17 +146,17 @@ class PrepareSecurityRelease {
|
|
157
146
|
{ defaultAnswer: true });
|
158
147
|
}
|
159
148
|
|
160
|
-
buildIssue(releaseDate, securityReleasePRUrl) {
|
161
|
-
const template = this.getSecurityIssueTemplate();
|
149
|
+
async buildIssue(releaseDate, securityReleasePRUrl = PLACEHOLDERS.vulnerabilitiesPRURL) {
|
150
|
+
const template = await this.getSecurityIssueTemplate();
|
162
151
|
const content = template.replace(PLACEHOLDERS.releaseDate, releaseDate)
|
163
152
|
.replace(PLACEHOLDERS.vulnerabilitiesPRURL, securityReleasePRUrl);
|
164
|
-
return
|
153
|
+
return content;
|
165
154
|
}
|
166
155
|
|
167
156
|
async createIssue(content, { cli }) {
|
168
157
|
const data = await this.req.createIssue(this.title, content, this.repository);
|
169
158
|
if (data.html_url) {
|
170
|
-
cli.ok(
|
159
|
+
cli.ok(`Created: ${data.html_url}`);
|
171
160
|
} else {
|
172
161
|
cli.error(data);
|
173
162
|
process.exit(1);
|
@@ -178,15 +167,32 @@ class PrepareSecurityRelease {
|
|
178
167
|
cli.info('Getting triaged H1 reports...');
|
179
168
|
const reports = await this.req.getTriagedReports();
|
180
169
|
const supportedVersions = (await nv('supported'))
|
181
|
-
.map((v) => v.versionName
|
170
|
+
.map((v) => `${v.versionName}.x`)
|
182
171
|
.join(',');
|
183
172
|
const selectedReports = [];
|
184
173
|
|
185
174
|
for (const report of reports.data) {
|
186
|
-
const {
|
187
|
-
|
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
|
+
|
188
194
|
cli.separator();
|
189
|
-
cli.info(`Report: ${
|
195
|
+
cli.info(`Report: ${link} - ${title} (${reportSeverity?.rating})`);
|
190
196
|
const include = await cli.prompt(
|
191
197
|
'Would you like to include this report to the next security release?',
|
192
198
|
{ defaultAnswer: true });
|
@@ -198,47 +204,30 @@ class PrepareSecurityRelease {
|
|
198
204
|
questionType: 'input',
|
199
205
|
defaultAnswer: supportedVersions
|
200
206
|
});
|
201
|
-
const summaryContent = await
|
207
|
+
const summaryContent = await getSummary(id, this.req);
|
202
208
|
|
203
209
|
selectedReports.push({
|
204
210
|
id,
|
205
211
|
title,
|
206
212
|
cve_ids,
|
207
|
-
severity:
|
213
|
+
severity: reportSeverity,
|
208
214
|
summary: summaryContent ?? '',
|
209
|
-
affectedVersions: versions.split(',').map((v) => v.replace('v', '').trim())
|
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
|
210
219
|
});
|
211
220
|
}
|
212
221
|
return selectedReports;
|
213
222
|
}
|
214
223
|
|
215
|
-
async getSummary(reportId) {
|
216
|
-
const { data } = await this.req.getReport(reportId);
|
217
|
-
const summaryList = data?.relationships?.summaries?.data;
|
218
|
-
if (!summaryList?.length) return;
|
219
|
-
const summaries = summaryList.filter((summary) => summary?.attributes?.category === 'team');
|
220
|
-
if (!summaries?.length) return;
|
221
|
-
return summaries?.[0].attributes?.content;
|
222
|
-
}
|
223
|
-
|
224
|
-
checkoutOnSecurityReleaseBranch(cli) {
|
225
|
-
this.checkRemote(cli);
|
226
|
-
const currentBranch = runSync('git', ['branch', '--show-current']).trim();
|
227
|
-
cli.info(`Current branch: ${currentBranch} `);
|
228
|
-
|
229
|
-
if (currentBranch !== this.nextSecurityReleaseBranch) {
|
230
|
-
runSync('git', ['checkout', '-B', this.nextSecurityReleaseBranch]);
|
231
|
-
cli.ok(`Checkout on branch: ${this.nextSecurityReleaseBranch} `);
|
232
|
-
};
|
233
|
-
}
|
234
|
-
|
235
224
|
async createVulnerabilitiesJSON(reports, { cli }) {
|
236
225
|
cli.separator('Creating vulnerabilities.json...');
|
237
226
|
const file = JSON.stringify({
|
238
227
|
reports
|
239
228
|
}, null, 2);
|
240
229
|
|
241
|
-
const folderPath = path.join(process.cwd(),
|
230
|
+
const folderPath = path.join(process.cwd(), NEXT_SECURITY_RELEASE_FOLDER);
|
242
231
|
try {
|
243
232
|
await fs.accessSync(folderPath);
|
244
233
|
} catch (error) {
|
@@ -267,17 +256,16 @@ class PrepareSecurityRelease {
|
|
267
256
|
);
|
268
257
|
const url = response?.html_url;
|
269
258
|
if (url) {
|
270
|
-
cli.ok(
|
259
|
+
cli.ok(`Created: ${url}`);
|
271
260
|
return url;
|
272
|
-
}
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
}
|
277
|
-
} else {
|
278
|
-
cli.error(response);
|
261
|
+
}
|
262
|
+
if (response?.errors) {
|
263
|
+
for (const error of response.errors) {
|
264
|
+
cli.error(error.message);
|
279
265
|
}
|
280
|
-
|
266
|
+
} else {
|
267
|
+
cli.error(response);
|
281
268
|
}
|
269
|
+
process.exit(1);
|
282
270
|
}
|
283
271
|
}
|
@@ -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
|
+
}
|
@@ -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,98 +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](%VULNERABILITIES_PR_URL%) to be addressed.
|
7
|
-
|
8
|
-
* [ ] PR release announcements in [private](https://github.com/nodejs-private/nodejs.org-private):
|
9
|
-
* [ ] pre-release: %PRE_RELEASE_PRIV%
|
10
|
-
* [ ] post-release: %POS_RELEASE_PRIV%
|
11
|
-
* List vulnerabilities in order of descending severity
|
12
|
-
* Use the "summary" feature in HackerOne to sync post-release content
|
13
|
-
and CVE requests. Example [2038134](https://hackerone.com/bugs?subject=nodejs\&report_id=2038134)
|
14
|
-
* Ask the HackerOne reporter if they would like to be credited on the
|
15
|
-
security release blog page
|
16
|
-
|
17
|
-
* [ ] Get agreement on the planned date for the release: %RELEASE_DATE%
|
18
|
-
|
19
|
-
* [ ] Get release team volunteers for all affected lines:
|
20
|
-
%AFFECTED_LINES%
|
21
|
-
|
22
|
-
## Announcement (one week in advance of the planned release)
|
23
|
-
|
24
|
-
* [ ] Check that all vulnerabilities are ready for release integration:
|
25
|
-
* PRs against all affected release lines or cherry-pick clean
|
26
|
-
* PRs with breaking changes have a
|
27
|
-
[--security-revert](#Adding-a-security-revert-option) option if possible.
|
28
|
-
* Approved
|
29
|
-
* (optional) Approved by the reporter
|
30
|
-
* Build and send the binary to the reporter according to its architecture
|
31
|
-
and ask for a review. This step is important to avoid insufficient fixes
|
32
|
-
between Security Releases.
|
33
|
-
* Have CVEs
|
34
|
-
* Make sure that dependent libraries have CVEs for their issues. We should
|
35
|
-
only create CVEs for vulnerabilities in Node.js itself. This is to avoid
|
36
|
-
having duplicate CVEs for the same vulnerability.
|
37
|
-
* Described in the pre/post announcements
|
38
|
-
|
39
|
-
* [ ] Pre-release announcement to nodejs.org blog: TBD
|
40
|
-
(Re-PR the pre-approved branch from nodejs-private/nodejs.org-private to
|
41
|
-
nodejs/nodejs.org)
|
42
|
-
|
43
|
-
* [ ] Pre-release announcement [email](https://groups.google.com/forum/#!forum/nodejs-sec): TBD
|
44
|
-
* Subject: `Node.js security updates for all active release lines, Month Year`
|
45
|
-
|
46
|
-
* [ ] CC `oss-security@lists.openwall.com` on pre-release
|
47
|
-
* [ ] Forward the email you receive to `oss-security@lists.openwall.com`.
|
48
|
-
|
49
|
-
* [ ] Create a new issue in [nodejs/tweet](https://github.com/nodejs/tweet/issues)
|
50
|
-
|
51
|
-
* [ ] Request releaser(s) to start integrating the PRs to be released.
|
52
|
-
|
53
|
-
* [ ] Notify [docker-node](https://github.com/nodejs/docker-node/issues) of upcoming security release date: TBD
|
54
|
-
|
55
|
-
* [ ] Notify build-wg of upcoming security release date by opening an issue
|
56
|
-
in [nodejs/build](https://github.com/nodejs/build/issues) to request WG members are available to fix any CI issues: TBD
|
57
|
-
|
58
|
-
## Release day
|
59
|
-
|
60
|
-
* [ ] [Lock CI](https://github.com/nodejs/build/blob/HEAD/doc/jenkins-guide.md#before-the-release)
|
61
|
-
|
62
|
-
* [ ] The releaser(s) run the release process to completion.
|
63
|
-
|
64
|
-
* [ ] [Unlock CI](https://github.com/nodejs/build/blob/HEAD/doc/jenkins-guide.md#after-the-release)
|
65
|
-
|
66
|
-
* [ ] Post-release announcement to Nodejs.org blog:
|
67
|
-
* (Re-PR the pre-approved branch from nodejs-private/nodejs.org-private to
|
68
|
-
nodejs/nodejs.org)
|
69
|
-
|
70
|
-
* [ ] Post-release announcement in reply email: TBD
|
71
|
-
|
72
|
-
* [ ] Notify `#nodejs-social` about the release.
|
73
|
-
|
74
|
-
* [ ] Comment in [docker-node][] issue that release is ready for integration.
|
75
|
-
The docker-node team will build and release docker image updates.
|
76
|
-
|
77
|
-
* [ ] For every H1 report resolved:
|
78
|
-
* Close as Resolved
|
79
|
-
* Request Disclosure
|
80
|
-
* Request publication of H1 CVE requests
|
81
|
-
* (Check that the "Version Fixed" field in the CVE is correct, and provide
|
82
|
-
links to the release blogs in the "Public Reference" section)
|
83
|
-
|
84
|
-
* [ ] PR machine-readable JSON descriptions of the vulnerabilities to the
|
85
|
-
[core](https://github.com/nodejs/security-wg/tree/HEAD/vuln/core)
|
86
|
-
vulnerability DB.
|
87
|
-
* For each vulnerability add a `#.json` file, one can copy an existing
|
88
|
-
[json](https://github.com/nodejs/security-wg/blob/0d82062d917cb9ddab88f910559469b2b13812bf/vuln/core/78.json)
|
89
|
-
file, and increment the latest created file number and use that as the name
|
90
|
-
of the new file to be added. For example, `79.json`.
|
91
|
-
|
92
|
-
* [ ] Close this issue
|
93
|
-
|
94
|
-
* [ ] Make sure the PRs for the vulnerabilities are closed.
|
95
|
-
|
96
|
-
* [ ] PR in that you stewarded the release in
|
97
|
-
[Security release stewards](https://github.com/nodejs/node/blob/HEAD/doc/contributing/security-release-process.md#security-release-stewards).
|
98
|
-
If necessary add the next rotation of the steward rotation.
|