Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions lib/security-release/security-release.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ export const NEXT_SECURITY_RELEASE_REPOSITORY = {
repo: 'security-release'
};

const SEVERITY_RANK = {
critical: 0,
high: 1,
medium: 2,
low: 3
};

const SEVERITY_LABEL = {
critical: 'CRITICAL',
high: 'HIGH',
medium: 'MEDIUM',
low: 'LOW'
Comment thread
aduh95 marked this conversation as resolved.
Outdated
};

export const PLACEHOLDERS = {
releaseDate: '%RELEASE_DATE%',
vulnerabilitiesPRURL: '%VULNERABILITIES_PR_URL%',
Expand Down Expand Up @@ -130,6 +144,26 @@ export function formatDateToYYYYMMDD(date) {
return `${year}/${month}/${day}`;
}

export function getHighestSeverity(reports) {
let highestSeverity = '';

for (const report of reports) {
const rating = report.severity.rating.toLowerCase();
Comment thread
aduh95 marked this conversation as resolved.
Outdated
const currentRank = SEVERITY_RANK[rating] ?? Number.MAX_SAFE_INTEGER;
const highestRank = SEVERITY_RANK[highestSeverity] ?? Number.MAX_SAFE_INTEGER;

if (!highestSeverity || currentRank < highestRank) {
highestSeverity = rating;
}
}

return SEVERITY_LABEL[highestSeverity] ?? highestSeverity.toUpperCase();
}

export function getHighestSeverityAnnouncement(reports) {
return `The highest severity issue fixed in this release is ${getHighestSeverity(reports)}.`;
}

export function promptDependencies(cli) {
return cli.prompt('Enter the link to the dependency update PR (leave empty to exit): ', {
defaultAnswer: '',
Expand Down
37 changes: 15 additions & 22 deletions lib/security_blog.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import fs from 'node:fs';
import path from 'node:path';
import _ from 'lodash';
import nv from '@pkgjs/nv';
import {
PLACEHOLDERS,
checkoutOnSecurityReleaseBranch,
validateDate,
SecurityRelease,
commitAndPushVulnerabilitiesJSON,
getHighestSeverity,
getHighestSeverityAnnouncement,
} from './security-release/security-release.js';
import auth from './auth.js';
import Request from './request.js';
Expand Down Expand Up @@ -323,6 +324,11 @@ export default class SecurityBlog extends SecurityRelease {
getImpact(content) {
const impact = new Map();
for (const report of content.reports) {
if (!report.severity?.rating) {
this.cli.error(`severity.rating not found for report ${report.id}.`);
process.exit(1);
}

for (const version of report.affectedVersions) {
if (!impact.has(version)) impact.set(version, []);
impact.get(version).push(report);
Expand All @@ -332,35 +338,22 @@ export default class SecurityBlog extends SecurityRelease {
const result = Array.from(impact.entries())
.sort(([a], [b]) => b.localeCompare(a)) // DESC
.map(([version, reports]) => {
const severityCount = new Map();

for (const report of reports) {
const rating = report.severity.rating?.toLowerCase();
if (!rating) {
this.cli.error(`severity.rating not found for report ${report.id}.`);
process.exit(1);
}
severityCount.set(rating, (severityCount.get(rating) || 0) + 1);
}

const groupedByRating = Array.from(severityCount.entries())
.map(([rating, count]) => `${count} ${rating} severity issues`)
.join(', ');

return `The ${version} release line of Node.js is vulnerable to ${groupedByRating}.`;
return `The highest severity issue fixed in the ${version} release line is ` +
`${getHighestSeverity(reports)}.`;
})
.join('\n');

return result;
}

getVulnerabilities(content) {
const grouped = _.groupBy(content.reports, 'severity.rating');
const text = [];
for (const [key, value] of Object.entries(grouped)) {
text.push(`- ${value.length} ${key.toLocaleLowerCase()} severity issues.`);
for (const report of content.reports) {
if (!report.severity?.rating) {
this.cli.error(`severity.rating not found for report ${report.id}.`);
process.exit(1);
}
}
return text.join('\n');
return getHighestSeverityAnnouncement(content.reports);
}
Comment thread
RafaelGSS marked this conversation as resolved.

getSecurityPreReleaseTemplate() {
Expand Down
83 changes: 83 additions & 0 deletions test/unit/security_release.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { describe, it } from 'node:test';
import assert from 'node:assert';

import SecurityBlog from '../../lib/security_blog.js';
import {
getHighestSeverity,
getHighestSeverityAnnouncement
} from '../../lib/security-release/security-release.js';

const cli = {
error() {}
};

function report(id, rating, affectedVersions = ['24.x']) {
return {
id,
severity: { rating },
affectedVersions
};
}

describe('security_release: severity announcement', () => {
it('uses the highest severity across reports', () => {
const reports = [
report(1, 'low'),
report(2, 'medium'),
report(3, 'high')
];

assert.strictEqual(getHighestSeverity(reports), 'HIGH');
assert.strictEqual(
getHighestSeverityAnnouncement(reports),
'The highest severity issue fixed in this release is HIGH.'
);
});

it('uses medium severity wording', () => {
const reports = [
report(1, 'low'),
report(2, 'medium')
];

assert.strictEqual(getHighestSeverity(reports), 'MEDIUM');
assert.strictEqual(
getHighestSeverityAnnouncement(reports),
'The highest severity issue fixed in this release is MEDIUM.'
);
});
});

describe('security_blog: pre-release severity wording', () => {
it('does not include severity counts in the summary', () => {
const blog = new SecurityBlog(cli);
const content = {
reports: [
report(1, 'low'),
report(2, 'medium')
]
};

assert.strictEqual(
blog.getVulnerabilities(content),
'The highest severity issue fixed in this release is MEDIUM.'
);
});

it('uses the highest severity per release line in impact text', () => {
const blog = new SecurityBlog(cli);
const content = {
reports: [
report(1, 'low', ['22.x', '20.x']),
report(2, 'medium', ['22.x']),
report(3, 'high', ['20.x'])
]
};

assert.strictEqual(
blog.getImpact(content),
'The highest severity issue fixed in the 22.x release line is MEDIUM.\n' +
'The highest severity issue fixed in the 20.x release line is HIGH.'
);
});
});
Loading