@node-core/utils 5.1.0 → 5.2.1
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 +18 -0
- package/lib/github/templates/security-pre-release.md +4 -0
- package/lib/prepare_security.js +1 -2
- package/lib/request.js +3 -2
- package/lib/security-release/security-release.js +33 -17
- package/lib/update_security_release.js +39 -1
- package/lib/voting_session.js +13 -3
- package/package.json +1 -1
@@ -12,6 +12,10 @@ const securityOptions = {
|
|
12
12
|
describe: 'Start security release process',
|
13
13
|
type: 'boolean'
|
14
14
|
},
|
15
|
+
sync: {
|
16
|
+
describe: 'Synchronize an ongoing security release with HackerOne',
|
17
|
+
type: 'boolean'
|
18
|
+
},
|
15
19
|
'update-date': {
|
16
20
|
describe: 'Updates the target date of the security release',
|
17
21
|
type: 'string'
|
@@ -46,6 +50,10 @@ export function builder(yargs) {
|
|
46
50
|
.example(
|
47
51
|
'git node security --start',
|
48
52
|
'Prepare a security release of Node.js')
|
53
|
+
.example(
|
54
|
+
'git node security --sync',
|
55
|
+
'Synchronize an ongoing security release with HackerOne'
|
56
|
+
)
|
49
57
|
.example(
|
50
58
|
'git node security --update-date=YYYY/MM/DD',
|
51
59
|
'Updates the target date of the security release'
|
@@ -76,6 +84,9 @@ export function handler(argv) {
|
|
76
84
|
if (argv.start) {
|
77
85
|
return startSecurityRelease(argv);
|
78
86
|
}
|
87
|
+
if (argv.sync) {
|
88
|
+
return syncSecurityRelease(argv);
|
89
|
+
}
|
79
90
|
if (argv['update-date']) {
|
80
91
|
return updateReleaseDate(argv);
|
81
92
|
}
|
@@ -142,6 +153,13 @@ async function startSecurityRelease(argv) {
|
|
142
153
|
return release.start();
|
143
154
|
}
|
144
155
|
|
156
|
+
async function syncSecurityRelease(argv) {
|
157
|
+
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
158
|
+
const cli = new CLI(logStream);
|
159
|
+
const release = new UpdateSecurityRelease(cli);
|
160
|
+
return release.sync();
|
161
|
+
}
|
162
|
+
|
145
163
|
async function notifyPreRelease() {
|
146
164
|
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
|
147
165
|
const cli = new CLI(logStream);
|
@@ -18,6 +18,10 @@ releases lines on or shortly after, %RELEASE_DATE% in order to address:
|
|
18
18
|
|
19
19
|
%IMPACT%
|
20
20
|
|
21
|
+
It's important to note that End-of-Life versions are always affected when a security release occurs.
|
22
|
+
To ensure your system's security, please use an up-to-date version as outlined in our
|
23
|
+
[Release Schedule](https://github.com/nodejs/release#release-schedule).
|
24
|
+
|
21
25
|
## Release timing
|
22
26
|
|
23
27
|
Releases will be available on, or shortly after, %RELEASE_DATE%.
|
package/lib/prepare_security.js
CHANGED
@@ -248,8 +248,7 @@ export default class PrepareSecurityRelease {
|
|
248
248
|
});
|
249
249
|
|
250
250
|
try {
|
251
|
-
const
|
252
|
-
const res = await this.req.getPullRequest(prUrl);
|
251
|
+
const res = await this.req.getPullRequest(dep);
|
253
252
|
const { html_url, title } = res;
|
254
253
|
deps.push({
|
255
254
|
name,
|
package/lib/request.js
CHANGED
@@ -77,7 +77,8 @@ export default class Request {
|
|
77
77
|
return this.json(url, options);
|
78
78
|
}
|
79
79
|
|
80
|
-
async getPullRequest(
|
80
|
+
async getPullRequest(fullUrl) {
|
81
|
+
const prUrl = fullUrl.replace('https://github.com/', 'https://api.github.com/repos/').replace('pull', 'pulls');
|
81
82
|
const options = {
|
82
83
|
method: 'GET',
|
83
84
|
headers: {
|
@@ -86,7 +87,7 @@ export default class Request {
|
|
86
87
|
Accept: 'application/vnd.github+json'
|
87
88
|
}
|
88
89
|
};
|
89
|
-
return this.json(
|
90
|
+
return this.json(prUrl, options);
|
90
91
|
}
|
91
92
|
|
92
93
|
async createPullRequest(title, body, { owner, repo, head, base }) {
|
@@ -87,9 +87,8 @@ export async function getSupportedVersions() {
|
|
87
87
|
return supportedVersions;
|
88
88
|
}
|
89
89
|
|
90
|
-
export
|
91
|
-
const
|
92
|
-
const summaryList = data?.relationships?.summaries?.data;
|
90
|
+
export function getSummary(report) {
|
91
|
+
const summaryList = report?.relationships?.summaries?.data;
|
93
92
|
if (!summaryList?.length) return;
|
94
93
|
const summaries = summaryList.filter((summary) => summary?.attributes?.category === 'team');
|
95
94
|
if (!summaries?.length) return;
|
@@ -139,17 +138,25 @@ export async function createIssue(title, content, repository, { cli, req }) {
|
|
139
138
|
}
|
140
139
|
}
|
141
140
|
|
142
|
-
export
|
141
|
+
export function getReportSeverity(report) {
|
143
142
|
const {
|
144
|
-
|
145
|
-
relationships: { severity, weakness, reporter }
|
143
|
+
relationships: { severity, weakness }
|
146
144
|
} = report;
|
147
|
-
const link = `https://hackerone.com/reports/${id}`;
|
148
145
|
const reportSeverity = {
|
149
146
|
rating: severity?.data?.attributes?.rating || '',
|
150
147
|
cvss_vector_string: severity?.data?.attributes?.cvss_vector_string || '',
|
151
148
|
weakness_id: weakness?.data?.id || ''
|
152
149
|
};
|
150
|
+
return reportSeverity;
|
151
|
+
}
|
152
|
+
|
153
|
+
export async function pickReport(report, { cli, req }) {
|
154
|
+
const {
|
155
|
+
id, attributes: { title, cve_ids },
|
156
|
+
relationships: { reporter, custom_field_values }
|
157
|
+
} = report;
|
158
|
+
const link = `https://hackerone.com/reports/${id}`;
|
159
|
+
const reportSeverity = getReportSeverity(report);
|
153
160
|
|
154
161
|
cli.separator();
|
155
162
|
cli.info(`Report: ${link} - ${title} (${reportSeverity?.rating})`);
|
@@ -165,19 +172,27 @@ export async function pickReport(report, { cli, req }) {
|
|
165
172
|
defaultAnswer: await getSupportedVersions()
|
166
173
|
});
|
167
174
|
|
168
|
-
let
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
});
|
173
|
-
|
174
|
-
if (!patchAuthors) {
|
175
|
-
patchAuthors = [];
|
175
|
+
let prURL = '';
|
176
|
+
let patchAuthors = [];
|
177
|
+
if (custom_field_values.data.length) {
|
178
|
+
prURL = custom_field_values.data[0].attributes.value;
|
179
|
+
const { user } = await req.getPullRequest(prURL);
|
180
|
+
patchAuthors = [user.login];
|
176
181
|
} else {
|
177
|
-
patchAuthors =
|
182
|
+
patchAuthors = await cli.prompt(
|
183
|
+
'Add github username of the authors of the patch (split by comma if multiple)', {
|
184
|
+
questionType: 'input',
|
185
|
+
defaultAnswer: ''
|
186
|
+
});
|
187
|
+
|
188
|
+
if (!patchAuthors) {
|
189
|
+
patchAuthors = [];
|
190
|
+
} else {
|
191
|
+
patchAuthors = patchAuthors.split(',').map((p) => p.trim());
|
192
|
+
}
|
178
193
|
}
|
179
194
|
|
180
|
-
const summaryContent =
|
195
|
+
const summaryContent = getSummary(report);
|
181
196
|
|
182
197
|
return {
|
183
198
|
id,
|
@@ -186,6 +201,7 @@ export async function pickReport(report, { cli, req }) {
|
|
186
201
|
severity: reportSeverity,
|
187
202
|
summary: summaryContent ?? '',
|
188
203
|
patchAuthors,
|
204
|
+
prURL,
|
189
205
|
affectedVersions: versions.split(',').map((v) => v.replace('v', '').trim()),
|
190
206
|
link,
|
191
207
|
reporter: reporter.data.attributes.username
|
@@ -2,9 +2,12 @@ import {
|
|
2
2
|
NEXT_SECURITY_RELEASE_FOLDER,
|
3
3
|
NEXT_SECURITY_RELEASE_REPOSITORY,
|
4
4
|
checkoutOnSecurityReleaseBranch,
|
5
|
+
checkRemote,
|
5
6
|
commitAndPushVulnerabilitiesJSON,
|
6
7
|
validateDate,
|
7
|
-
pickReport
|
8
|
+
pickReport,
|
9
|
+
getReportSeverity,
|
10
|
+
getSummary
|
8
11
|
} from './security-release/security-release.js';
|
9
12
|
import fs from 'node:fs';
|
10
13
|
import path from 'node:path';
|
@@ -18,6 +21,41 @@ export default class UpdateSecurityRelease {
|
|
18
21
|
this.cli = cli;
|
19
22
|
}
|
20
23
|
|
24
|
+
async sync() {
|
25
|
+
checkRemote(this.cli, this.repository);
|
26
|
+
|
27
|
+
const vulnerabilitiesJSONPath = this.getVulnerabilitiesJSONPath();
|
28
|
+
const content = this.readVulnerabilitiesJSON(vulnerabilitiesJSONPath);
|
29
|
+
const credentials = await auth({
|
30
|
+
github: true,
|
31
|
+
h1: true
|
32
|
+
});
|
33
|
+
const req = new Request(credentials);
|
34
|
+
for (let i = 0; i < content.reports.length; ++i) {
|
35
|
+
const report = content.reports[i];
|
36
|
+
const { data } = await req.getReport(report.id);
|
37
|
+
const reportSeverity = getReportSeverity(data);
|
38
|
+
const summaryContent = getSummary(data);
|
39
|
+
const link = `https://hackerone.com/reports/${report.id}`;
|
40
|
+
let prURL = report.prURL;
|
41
|
+
if (data.relationships.custom_field_values.data.length) {
|
42
|
+
prURL = data.relationships.custom_field_values.data[0].attributes.value;
|
43
|
+
}
|
44
|
+
|
45
|
+
content.reports[i] = {
|
46
|
+
...report,
|
47
|
+
title: data.attributes.title,
|
48
|
+
cveIds: data.attributes.cve_ids,
|
49
|
+
severity: reportSeverity,
|
50
|
+
summary: summaryContent ?? report.summary,
|
51
|
+
link,
|
52
|
+
prURL
|
53
|
+
};
|
54
|
+
}
|
55
|
+
fs.writeFileSync(vulnerabilitiesJSONPath, JSON.stringify(content, null, 2));
|
56
|
+
this.cli.ok('Synced vulnerabilities.json with HackerOne');
|
57
|
+
}
|
58
|
+
|
21
59
|
async updateReleaseDate(releaseDate) {
|
22
60
|
const { cli } = this;
|
23
61
|
|
package/lib/voting_session.js
CHANGED
@@ -114,7 +114,7 @@ export default class VotingSession extends Session {
|
|
114
114
|
const body = 'I would like to close this vote, and for this effect, I\'m revealing my ' +
|
115
115
|
`key part:\n\n${'```'}\n${keyPart}\n${'```'}\n`;
|
116
116
|
if (this.postComment) {
|
117
|
-
const { html_url } = await this.req.json(`https://api.github.com/repos/${this.owner}/${this.repo}/issues/${this.prid}/comments`, {
|
117
|
+
const { message, html_url } = await this.req.json(`https://api.github.com/repos/${this.owner}/${this.repo}/issues/${this.prid}/comments`, {
|
118
118
|
agent: this.req.proxyAgent,
|
119
119
|
method: 'POST',
|
120
120
|
headers: {
|
@@ -124,13 +124,23 @@ export default class VotingSession extends Session {
|
|
124
124
|
},
|
125
125
|
body: JSON.stringify({ body })
|
126
126
|
});
|
127
|
-
|
128
|
-
|
127
|
+
if (html_url) {
|
128
|
+
this.cli.log(`Comment posted at: ${html_url}`);
|
129
|
+
return;
|
130
|
+
} else {
|
131
|
+
this.cli.warn(message);
|
132
|
+
this.cli.error('Failed to post comment');
|
133
|
+
}
|
134
|
+
}
|
135
|
+
if (isGhAvailable()) {
|
129
136
|
this.cli.log('\nRun the following command to post the comment:\n');
|
130
137
|
this.cli.log(
|
131
138
|
`gh pr comment ${this.prid} --repo ${this.owner}/${this.repo} ` +
|
132
139
|
`--body-file - <<'EOF'\n${body}\nEOF`
|
133
140
|
);
|
141
|
+
} else {
|
142
|
+
this.cli.log('\nPost the following comment on the PR thread:\n');
|
143
|
+
this.cli.log(body);
|
134
144
|
}
|
135
145
|
}
|
136
146
|
}
|