@node-core/utils 4.2.3 → 4.3.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/lib/github/templates/next-security-release.md +5 -4
- package/lib/prepare_security.js +215 -49
- package/lib/request.js +32 -0
- package/lib/update-v8/constants.js +10 -0
- package/package.json +16 -16
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)
|
@@ -3,13 +3,14 @@
|
|
3
3
|
* [X] Open an [issue](https://github.com/nodejs-private/node-private) titled
|
4
4
|
`Next Security Release`, and put this checklist in the description.
|
5
5
|
|
6
|
-
* [ ] Get agreement on the list of vulnerabilities to be addressed
|
7
|
-
%REPORTS%
|
6
|
+
* [ ] Get agreement on the [list of vulnerabilities](%VULNERABILITIES_PR_URL%) to be addressed.
|
8
7
|
|
9
8
|
* [ ] PR release announcements in [private](https://github.com/nodejs-private/nodejs.org-private):
|
10
9
|
* [ ] pre-release: %PRE_RELEASE_PRIV%
|
11
10
|
* [ ] post-release: %POS_RELEASE_PRIV%
|
12
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)
|
13
14
|
* Ask the HackerOne reporter if they would like to be credited on the
|
14
15
|
security release blog page
|
15
16
|
|
@@ -20,10 +21,10 @@
|
|
20
21
|
|
21
22
|
## Announcement (one week in advance of the planned release)
|
22
23
|
|
23
|
-
* [ ] Verify that GitHub Actions are working as normal: <https://www.githubstatus.com/>.
|
24
|
-
|
25
24
|
* [ ] Check that all vulnerabilities are ready for release integration:
|
26
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.
|
27
28
|
* Approved
|
28
29
|
* (optional) Approved by the reporter
|
29
30
|
* Build and send the binary to the reporter according to its architecture
|
package/lib/prepare_security.js
CHANGED
@@ -2,6 +2,16 @@ import nv from '@pkgjs/nv';
|
|
2
2
|
import auth from './auth.js';
|
3
3
|
import Request from './request.js';
|
4
4
|
import fs from 'node:fs';
|
5
|
+
import { runSync } from './run.js';
|
6
|
+
import path from 'node:path';
|
7
|
+
|
8
|
+
export const PLACEHOLDERS = {
|
9
|
+
releaseDate: '%RELEASE_DATE%',
|
10
|
+
vulnerabilitiesPRURL: '%VULNERABILITIES_PR_URL%',
|
11
|
+
preReleasePrivate: '%PRE_RELEASE_PRIV%',
|
12
|
+
postReleasePrivate: '%POS_RELEASE_PRIV%',
|
13
|
+
affectedLines: '%AFFECTED_LINES%'
|
14
|
+
};
|
5
15
|
|
6
16
|
export default class SecurityReleaseSteward {
|
7
17
|
constructor(cli) {
|
@@ -16,31 +26,100 @@ export default class SecurityReleaseSteward {
|
|
16
26
|
});
|
17
27
|
|
18
28
|
const req = new Request(credentials);
|
19
|
-
const
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
owner: 'nodejs-private',
|
27
|
-
repo: 'node-private'
|
28
|
-
});
|
29
|
-
if (data.html_url) {
|
30
|
-
cli.ok('Created: ' + data.html_url);
|
31
|
-
} else {
|
32
|
-
cli.error(data);
|
33
|
-
}
|
29
|
+
const release = new PrepareSecurityRelease(req);
|
30
|
+
const releaseDate = await release.promptReleaseDate(cli);
|
31
|
+
let securityReleasePRUrl = PLACEHOLDERS.vulnerabilitiesPRURL;
|
32
|
+
|
33
|
+
const createVulnerabilitiesJSON = await release.promptVulnerabilitiesJSON(cli);
|
34
|
+
if (createVulnerabilitiesJSON) {
|
35
|
+
securityReleasePRUrl = await this.createVulnerabilitiesJSON(req, release, { cli });
|
34
36
|
}
|
37
|
+
|
38
|
+
const createIssue = await release.promptCreateRelaseIssue(cli);
|
39
|
+
|
40
|
+
if (createIssue) {
|
41
|
+
const { content } = release.buildIssue(releaseDate, securityReleasePRUrl);
|
42
|
+
await release.createIssue(content, { cli });
|
43
|
+
};
|
44
|
+
|
45
|
+
cli.ok('Done!');
|
46
|
+
}
|
47
|
+
|
48
|
+
async createVulnerabilitiesJSON(req, release, { cli }) {
|
49
|
+
// checkout on the next-security-release branch
|
50
|
+
release.checkoutOnSecurityReleaseBranch(cli);
|
51
|
+
|
52
|
+
// choose the reports to include in the security release
|
53
|
+
const reports = await release.chooseReports(cli);
|
54
|
+
|
55
|
+
// create the vulnerabilities.json file in the security-release repo
|
56
|
+
const filePath = await release.createVulnerabilitiesJSON(reports, { cli });
|
57
|
+
|
58
|
+
// review the vulnerabilities.json file
|
59
|
+
const review = await release.promptReviewVulnerabilitiesJSON(cli);
|
60
|
+
|
61
|
+
if (!review) {
|
62
|
+
cli.info(`To push the vulnerabilities.json file run:
|
63
|
+
- git add ${filePath}
|
64
|
+
- git commit -m "chore: create vulnerabilities.json for next security release"
|
65
|
+
- git push -u origin next-security-release
|
66
|
+
- open a PR on ${release.repository.owner}/${release.repository.repo}`);
|
67
|
+
return;
|
68
|
+
};
|
69
|
+
|
70
|
+
// commit and push the vulnerabilities.json file
|
71
|
+
release.commitAndPushVulnerabilitiesJSON(filePath, cli);
|
72
|
+
|
73
|
+
const createPr = await release.promptCreatePR(cli);
|
74
|
+
|
75
|
+
if (!createPr) return;
|
76
|
+
|
77
|
+
// create pr on the security-release repo
|
78
|
+
return release.createPullRequest(req, { cli });
|
35
79
|
}
|
36
80
|
}
|
37
81
|
|
38
|
-
class
|
39
|
-
|
82
|
+
class PrepareSecurityRelease {
|
83
|
+
repository = {
|
84
|
+
owner: 'nodejs-private',
|
85
|
+
repo: 'security-release'
|
86
|
+
};
|
87
|
+
|
88
|
+
title = 'Next Security Release';
|
89
|
+
nextSecurityReleaseBranch = 'next-security-release';
|
90
|
+
|
91
|
+
constructor(req, repository) {
|
40
92
|
this.req = req;
|
41
|
-
|
42
|
-
|
43
|
-
|
93
|
+
if (repository) {
|
94
|
+
this.repository = repository;
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
promptCreatePR(cli) {
|
99
|
+
return cli.prompt(
|
100
|
+
'Create the Next Security Release PR?',
|
101
|
+
{ defaultAnswer: true });
|
102
|
+
}
|
103
|
+
|
104
|
+
checkRemote(cli) {
|
105
|
+
const remote = runSync('git', ['ls-remote', '--get-url', 'origin']).trim();
|
106
|
+
const { owner, repo } = this.repository;
|
107
|
+
const securityReleaseOrigin = `https://github.com/${owner}/${repo}.git`;
|
108
|
+
|
109
|
+
if (remote !== securityReleaseOrigin) {
|
110
|
+
cli.error(`Wrong repository! It should be ${securityReleaseOrigin}`);
|
111
|
+
process.exit(1);
|
112
|
+
}
|
113
|
+
}
|
114
|
+
|
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}`);
|
44
123
|
}
|
45
124
|
|
46
125
|
getSecurityIssueTemplate() {
|
@@ -53,31 +132,58 @@ class SecurityReleaseIssue {
|
|
53
132
|
);
|
54
133
|
}
|
55
134
|
|
56
|
-
async
|
57
|
-
|
58
|
-
cli.info('Getting triaged H1 reports...');
|
59
|
-
const reports = await this.req.getTriagedReports();
|
60
|
-
await this.fillReports(cli, reports);
|
61
|
-
|
62
|
-
this.fillAffectedLines(Object.keys(this.affectedLines));
|
63
|
-
|
64
|
-
const target = await cli.prompt('Enter target date in YYYY-MM-DD format:', {
|
135
|
+
async promptReleaseDate(cli) {
|
136
|
+
return cli.prompt('Enter target release date in YYYY-MM-DD format:', {
|
65
137
|
questionType: 'input',
|
66
138
|
defaultAnswer: 'TBD'
|
67
139
|
});
|
68
|
-
|
140
|
+
}
|
141
|
+
|
142
|
+
async promptVulnerabilitiesJSON(cli) {
|
143
|
+
return cli.prompt(
|
144
|
+
'Create the vulnerabilities.json?',
|
145
|
+
{ defaultAnswer: true });
|
146
|
+
}
|
147
|
+
|
148
|
+
async promptCreateRelaseIssue(cli) {
|
149
|
+
return cli.prompt(
|
150
|
+
'Create the Next Security Release issue?',
|
151
|
+
{ defaultAnswer: true });
|
152
|
+
}
|
69
153
|
|
70
|
-
|
154
|
+
async promptReviewVulnerabilitiesJSON(cli) {
|
155
|
+
return cli.prompt(
|
156
|
+
'Please review vulnerabilities.json and press enter to proceed.',
|
157
|
+
{ defaultAnswer: true });
|
71
158
|
}
|
72
159
|
|
73
|
-
|
160
|
+
buildIssue(releaseDate, securityReleasePRUrl) {
|
161
|
+
const template = this.getSecurityIssueTemplate();
|
162
|
+
const content = template.replace(PLACEHOLDERS.releaseDate, releaseDate)
|
163
|
+
.replace(PLACEHOLDERS.vulnerabilitiesPRURL, securityReleasePRUrl);
|
164
|
+
return { releaseDate, content, securityReleasePRUrl };
|
165
|
+
}
|
166
|
+
|
167
|
+
async createIssue(content, { cli }) {
|
168
|
+
const data = await this.req.createIssue(this.title, content, this.repository);
|
169
|
+
if (data.html_url) {
|
170
|
+
cli.ok('Created: ' + data.html_url);
|
171
|
+
} else {
|
172
|
+
cli.error(data);
|
173
|
+
process.exit(1);
|
174
|
+
}
|
175
|
+
}
|
176
|
+
|
177
|
+
async chooseReports(cli) {
|
178
|
+
cli.info('Getting triaged H1 reports...');
|
179
|
+
const reports = await this.req.getTriagedReports();
|
74
180
|
const supportedVersions = (await nv('supported'))
|
75
181
|
.map((v) => v.versionName + '.x')
|
76
182
|
.join(',');
|
183
|
+
const selectedReports = [];
|
77
184
|
|
78
|
-
let reportsContent = '';
|
79
185
|
for (const report of reports.data) {
|
80
|
-
const { id, attributes: { title }, relationships: { severity } } = report;
|
186
|
+
const { id, attributes: { title, cve_ids }, relationships: { severity } } = report;
|
81
187
|
const reportLevel = severity ? severity.data.attributes.rating : 'TBD';
|
82
188
|
cli.separator();
|
83
189
|
cli.info(`Report: ${id} - ${title} (${reportLevel})`);
|
@@ -88,30 +194,90 @@ class SecurityReleaseIssue {
|
|
88
194
|
continue;
|
89
195
|
}
|
90
196
|
|
91
|
-
reportsContent +=
|
92
|
-
` * **[${id}](https://hackerone.com/bugs?subject=nodejs&report_id=${id}) - ${title} (TBD) - (${reportLevel})**\n`;
|
93
197
|
const versions = await cli.prompt('Which active release lines this report affects?', {
|
94
198
|
questionType: 'input',
|
95
199
|
defaultAnswer: supportedVersions
|
96
200
|
});
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
201
|
+
const summaryContent = await this.getSummary(id);
|
202
|
+
|
203
|
+
selectedReports.push({
|
204
|
+
id,
|
205
|
+
title,
|
206
|
+
cve_ids,
|
207
|
+
severity: reportLevel,
|
208
|
+
summary: summaryContent ?? '',
|
209
|
+
affectedVersions: versions.split(',').map((v) => v.replace('v', '').trim())
|
210
|
+
});
|
101
211
|
}
|
102
|
-
|
212
|
+
return selectedReports;
|
213
|
+
}
|
214
|
+
|
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;
|
103
222
|
}
|
104
223
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
+
async createVulnerabilitiesJSON(reports, { cli }) {
|
236
|
+
cli.separator('Creating vulnerabilities.json...');
|
237
|
+
const file = JSON.stringify({
|
238
|
+
reports
|
239
|
+
}, null, 2);
|
240
|
+
|
241
|
+
const folderPath = path.join(process.cwd(), 'security-release', 'next-security-release');
|
242
|
+
try {
|
243
|
+
await fs.accessSync(folderPath);
|
244
|
+
} catch (error) {
|
245
|
+
await fs.mkdirSync(folderPath, { recursive: true });
|
109
246
|
}
|
110
|
-
|
111
|
-
|
247
|
+
|
248
|
+
const fullPath = path.join(folderPath, 'vulnerabilities.json');
|
249
|
+
fs.writeFileSync(fullPath, file);
|
250
|
+
cli.ok(`Created ${fullPath} `);
|
251
|
+
|
252
|
+
return fullPath;
|
112
253
|
}
|
113
254
|
|
114
|
-
|
115
|
-
|
255
|
+
async createPullRequest(req, { cli }) {
|
256
|
+
const { owner, repo } = this.repository;
|
257
|
+
const response = await req.createPullRequest(
|
258
|
+
this.title,
|
259
|
+
'List of vulnerabilities to be included in the next security release',
|
260
|
+
{
|
261
|
+
owner,
|
262
|
+
repo,
|
263
|
+
base: 'main',
|
264
|
+
head: 'next-security-release'
|
265
|
+
}
|
266
|
+
|
267
|
+
);
|
268
|
+
const url = response?.html_url;
|
269
|
+
if (url) {
|
270
|
+
cli.ok('Created: ' + url);
|
271
|
+
return url;
|
272
|
+
} else {
|
273
|
+
if (response?.errors) {
|
274
|
+
for (const error of response.errors) {
|
275
|
+
cli.error(error.message);
|
276
|
+
}
|
277
|
+
} else {
|
278
|
+
cli.error(response);
|
279
|
+
}
|
280
|
+
process.exit(1);
|
281
|
+
}
|
116
282
|
}
|
117
283
|
}
|
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) {
|
@@ -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
|
];
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@node-core/utils",
|
3
|
-
"version": "4.
|
3
|
+
"version": "4.3.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
|
}
|