Skip to content

Commit 5e07124

Browse files
committed
update: drop number of vulnerabilities on --pre-release
When we announce a security release, we typically say we'll be fixing X High, X Medium, and so on. That policy was set before the AI era, when reports weren't as frequent. Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com>
1 parent 37cc6f0 commit 5e07124

3 files changed

Lines changed: 132 additions & 22 deletions

File tree

lib/security-release/security-release.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,20 @@ export const NEXT_SECURITY_RELEASE_REPOSITORY = {
1111
repo: 'security-release'
1212
};
1313

14+
const SEVERITY_RANK = {
15+
critical: 0,
16+
high: 1,
17+
medium: 2,
18+
low: 3
19+
};
20+
21+
const SEVERITY_LABEL = {
22+
critical: 'CRITICAL',
23+
high: 'HIGH',
24+
medium: 'MEDIUM',
25+
low: 'LOW'
26+
};
27+
1428
export const PLACEHOLDERS = {
1529
releaseDate: '%RELEASE_DATE%',
1630
vulnerabilitiesPRURL: '%VULNERABILITIES_PR_URL%',
@@ -130,6 +144,26 @@ export function formatDateToYYYYMMDD(date) {
130144
return `${year}/${month}/${day}`;
131145
}
132146

147+
export function getHighestSeverity(reports) {
148+
let highestSeverity = '';
149+
150+
for (const report of reports) {
151+
const rating = report.severity.rating.toLowerCase();
152+
const currentRank = SEVERITY_RANK[rating] ?? Number.MAX_SAFE_INTEGER;
153+
const highestRank = SEVERITY_RANK[highestSeverity] ?? Number.MAX_SAFE_INTEGER;
154+
155+
if (!highestSeverity || currentRank < highestRank) {
156+
highestSeverity = rating;
157+
}
158+
}
159+
160+
return SEVERITY_LABEL[highestSeverity] ?? highestSeverity.toUpperCase();
161+
}
162+
163+
export function getHighestSeverityAnnouncement(reports) {
164+
return `The highest severity issue fixed in this release is ${getHighestSeverity(reports)}.`;
165+
}
166+
133167
export function promptDependencies(cli) {
134168
return cli.prompt('Enter the link to the dependency update PR (leave empty to exit): ', {
135169
defaultAnswer: '',

lib/security_blog.js

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import fs from 'node:fs';
22
import path from 'node:path';
3-
import _ from 'lodash';
43
import nv from '@pkgjs/nv';
54
import {
65
PLACEHOLDERS,
76
checkoutOnSecurityReleaseBranch,
87
validateDate,
98
SecurityRelease,
109
commitAndPushVulnerabilitiesJSON,
10+
getHighestSeverity,
11+
getHighestSeverityAnnouncement,
1112
} from './security-release/security-release.js';
1213
import auth from './auth.js';
1314
import Request from './request.js';
@@ -323,6 +324,11 @@ export default class SecurityBlog extends SecurityRelease {
323324
getImpact(content) {
324325
const impact = new Map();
325326
for (const report of content.reports) {
327+
if (!report.severity?.rating) {
328+
this.cli.error(`severity.rating not found for report ${report.id}.`);
329+
process.exit(1);
330+
}
331+
326332
for (const version of report.affectedVersions) {
327333
if (!impact.has(version)) impact.set(version, []);
328334
impact.get(version).push(report);
@@ -332,35 +338,22 @@ export default class SecurityBlog extends SecurityRelease {
332338
const result = Array.from(impact.entries())
333339
.sort(([a], [b]) => b.localeCompare(a)) // DESC
334340
.map(([version, reports]) => {
335-
const severityCount = new Map();
336-
337-
for (const report of reports) {
338-
const rating = report.severity.rating?.toLowerCase();
339-
if (!rating) {
340-
this.cli.error(`severity.rating not found for report ${report.id}.`);
341-
process.exit(1);
342-
}
343-
severityCount.set(rating, (severityCount.get(rating) || 0) + 1);
344-
}
345-
346-
const groupedByRating = Array.from(severityCount.entries())
347-
.map(([rating, count]) => `${count} ${rating} severity issues`)
348-
.join(', ');
349-
350-
return `The ${version} release line of Node.js is vulnerable to ${groupedByRating}.`;
341+
return `The highest severity issue fixed in the ${version} release line is ` +
342+
`${getHighestSeverity(reports)}.`;
351343
})
352344
.join('\n');
353345

354346
return result;
355347
}
356348

357349
getVulnerabilities(content) {
358-
const grouped = _.groupBy(content.reports, 'severity.rating');
359-
const text = [];
360-
for (const [key, value] of Object.entries(grouped)) {
361-
text.push(`- ${value.length} ${key.toLocaleLowerCase()} severity issues.`);
350+
for (const report of content.reports) {
351+
if (!report.severity?.rating) {
352+
this.cli.error(`severity.rating not found for report ${report.id}.`);
353+
process.exit(1);
354+
}
362355
}
363-
return text.join('\n');
356+
return getHighestSeverityAnnouncement(content.reports);
364357
}
365358

366359
getSecurityPreReleaseTemplate() {

test/unit/security_release.test.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { describe, it } from 'node:test';
2+
import assert from 'node:assert';
3+
4+
import SecurityBlog from '../../lib/security_blog.js';
5+
import {
6+
getHighestSeverity,
7+
getHighestSeverityAnnouncement
8+
} from '../../lib/security-release/security-release.js';
9+
10+
const cli = {
11+
error() {}
12+
};
13+
14+
function report(id, rating, affectedVersions = ['24.x']) {
15+
return {
16+
id,
17+
severity: { rating },
18+
affectedVersions
19+
};
20+
}
21+
22+
describe('security_release: severity announcement', () => {
23+
it('uses the highest severity across reports', () => {
24+
const reports = [
25+
report(1, 'low'),
26+
report(2, 'medium'),
27+
report(3, 'high')
28+
];
29+
30+
assert.strictEqual(getHighestSeverity(reports), 'HIGH');
31+
assert.strictEqual(
32+
getHighestSeverityAnnouncement(reports),
33+
'The highest severity issue fixed in this release is HIGH.'
34+
);
35+
});
36+
37+
it('uses medium severity wording', () => {
38+
const reports = [
39+
report(1, 'low'),
40+
report(2, 'medium')
41+
];
42+
43+
assert.strictEqual(getHighestSeverity(reports), 'MEDIUM');
44+
assert.strictEqual(
45+
getHighestSeverityAnnouncement(reports),
46+
'The highest severity issue fixed in this release is MEDIUM.'
47+
);
48+
});
49+
});
50+
51+
describe('security_blog: pre-release severity wording', () => {
52+
it('does not include severity counts in the summary', () => {
53+
const blog = new SecurityBlog(cli);
54+
const content = {
55+
reports: [
56+
report(1, 'low'),
57+
report(2, 'medium')
58+
]
59+
};
60+
61+
assert.strictEqual(
62+
blog.getVulnerabilities(content),
63+
'The highest severity issue fixed in this release is MEDIUM.'
64+
);
65+
});
66+
67+
it('uses the highest severity per release line in impact text', () => {
68+
const blog = new SecurityBlog(cli);
69+
const content = {
70+
reports: [
71+
report(1, 'low', ['22.x', '20.x']),
72+
report(2, 'medium', ['22.x']),
73+
report(3, 'high', ['20.x'])
74+
]
75+
};
76+
77+
assert.strictEqual(
78+
blog.getImpact(content),
79+
'The highest severity issue fixed in the 22.x release line is MEDIUM.\n' +
80+
'The highest severity issue fixed in the 20.x release line is HIGH.'
81+
);
82+
});
83+
});

0 commit comments

Comments
 (0)