@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 CHANGED
@@ -1,5 +1,5 @@
1
1
  # Node.js Core Utilities
2
- [![npm](https://img.shields.io/npm/v/node-core-utils.svg?style=flat-square)](https://npmjs.org/package/node-core-utils)
2
+ [![npm](https://img.shields.io/npm/v/@node-core/utils.svg?style=flat-square)](https://npmjs.org/package/@node-core/utils)
3
3
  [![Build Status](https://img.shields.io/github/actions/workflow/status/nodejs/node-core-utils/nodejs.yml?branch=main&style=flat-square)](https://github.com/nodejs/node-core-utils/workflows/Node.js%20CI/badge.svg?branch=main)
4
4
  [![codecov](https://img.shields.io/codecov/c/github/nodejs/node-core-utils.svg?style=flat-square)](https://codecov.io/gh/nodejs/node-core-utils)
5
5
  [![Known Vulnerabilities](https://snyk.io/test/github/nodejs/node-core-utils/badge.svg?style=flat-square)](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
@@ -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 create = await cli.prompt(
20
- 'Create the Next Security Release issue?',
21
- { defaultAnswer: true });
22
- if (create) {
23
- const issue = new SecurityReleaseIssue(req);
24
- const content = await issue.buildIssue(cli);
25
- const data = await req.createIssue('Next Security Release', content, {
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 SecurityReleaseIssue {
39
- constructor(req) {
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
- this.content = '';
42
- this.title = 'Next Security Release';
43
- this.affectedLines = {};
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 buildIssue(cli) {
57
- this.content = this.getSecurityIssueTemplate();
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
- this.fillTargetDate(target);
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
- return this.content;
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
- async fillReports(cli, reports) {
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
- for (const v of versions.split(',')) {
98
- if (!this.affectedLines[v]) this.affectedLines[v] = true;
99
- reportsContent += ` * ${v} - TBD\n`;
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
- this.content = this.content.replace('%REPORTS%', reportsContent);
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
- fillAffectedLines(affectedLines) {
106
- let affected = '';
107
- for (const line of affectedLines) {
108
- affected += ` * ${line} - TBD\n`;
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
- this.content =
111
- this.content.replace('%AFFECTED_LINES%', affected);
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
- fillTargetDate(date) {
115
- this.content = this.content.replace('%RELEASE_DATE%', date);
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.2.3",
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": "^1.0.2",
38
- "@node-core/caritat": "^1.2.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": "^2.1.5",
40
+ "branch-diff": "^3.0.2",
41
41
  "chalk": "^5.3.0",
42
- "changelog-maker": "^3.2.6",
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.0",
48
- "inquirer": "^9.2.11",
47
+ "ghauth": "^6.0.1",
48
+ "inquirer": "^9.2.12",
49
49
  "js-yaml": "^4.1.0",
50
- "listr2": "^7.0.2",
50
+ "listr2": "^8.0.1",
51
51
  "lodash": "^4.17.21",
52
52
  "log-symbols": "^6.0.0",
53
- "ora": "^7.0.1",
54
- "replace-in-file": "^7.0.2",
55
- "undici": "^5.27.2",
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.3",
61
- "c8": "^8.0.1",
62
- "eslint": "^8.53.0",
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.0",
65
- "eslint-plugin-n": "^16.2.0",
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
  }