Skip to content

Commit

Permalink
feat: pos-release blogpost
Browse files Browse the repository at this point in the history
  • Loading branch information
marco-ippolito committed Mar 25, 2024
1 parent b70e636 commit 725b4e2
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 10 deletions.
30 changes: 22 additions & 8 deletions components/git/security.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ const securityOptions = {
'pre-release': {
describe: 'Create the pre-release announcement',
type: 'boolean'
},
'pos-release': {
describe: 'Create the pos-release announcement',
type: 'boolean'
}
};

Expand All @@ -36,22 +40,22 @@ export function builder(yargs) {
return yargs.options(securityOptions)
.example(
'git node security --start',
'Prepare a security release of Node.js')
.example(
'Prepare a security release of Node.js'
).example(
'git node security --update-date=YYYY/MM/DD',
'Updates the target date of the security release'
)
.example(
).example(
'git node security --add-report=H1-ID',
'Fetches HackerOne report based on ID provided and adds it into vulnerabilities.json'
)
.example(
).example(
'git node security --remove-report=H1-ID',
'Removes the Hackerone report based on ID provided from vulnerabilities.json'
)
.example(
).example(
'git node security --pre-release' +
'Create the pre-release announcement on the Nodejs.org repo'
).example(
'git node security --pos-release' +
'Create the pos-release announcement on the Nodejs.org repo'
);
}

Expand All @@ -71,6 +75,9 @@ export function handler(argv) {
if (argv['remove-report']) {
return removeReport(argv);
}
if (argv['pos-release']) {
return createPosRelease(argv);
}
yargsInstance.showHelp();
}

Expand Down Expand Up @@ -105,6 +112,13 @@ async function createPreRelease() {
return preRelease.createPreRelease();
}

async function createPosRelease() {
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
const cli = new CLI(logStream);
const preRelease = new SecurityBlog(cli);
return preRelease.createPosRelease();
}

async function startSecurityRelease() {
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
const cli = new CLI(logStream);
Expand Down
38 changes: 38 additions & 0 deletions lib/github/templates/security-pos-release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
date: %ANNOUNCEMENT_DATE%
category: vulnerability
title: %RELEASE_DATE% Security Releases
slug: %SLUG%
layout: blog-post.hbs
author: %AUTHOR%
---

## Security releases available

Updates are now available for the %AFFECTED_VERSIONS% Node.js release lines for the
following issues.
%DEPENDENCY_UPDATES%
%OPENSSL_UPDATES%
%REPORTS%
---

# Summary

The Node.js project will release new versions of the %AFFECTED_VERSIONS%
releases lines on or shortly after, %RELEASE_DATE% in order to address:

%VULNERABILITIES%

## Impact

%IMPACT%

## Release timing

Releases will be available on, or shortly after, %RELEASE_DATE%.

## Contact and future updates

The current Node.js security policy can be found at <https://nodejs.org/en/security/>. Please follow the process outlined in <https://github.com/nodejs/node/blob/master/SECURITY.md> if you wish to report a vulnerability in Node.js.

Subscribe to the low-volume announcement-only nodejs-sec mailing list at <https://groups.google.com/forum/#!forum/nodejs-sec> to stay up to date on security vulnerabilities and security-related releases of Node.js and the projects maintained in the nodejs GitHub organization.
5 changes: 4 additions & 1 deletion lib/security-release/security-release.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ export const PLACEHOLDERS = {
affectedVersions: '%AFFECTED_VERSIONS%',
openSSLUpdate: '%OPENSSL_UPDATES%',
impact: '%IMPACT%',
vulnerabilities: '%VULNERABILITIES%'
vulnerabilities: '%VULNERABILITIES%',
reports: '%REPORTS%',
author: '%AUTHOR%',
dependencyUpdates: '%DEPENDENCY_UPDATES%'
};

export function checkRemote(cli, repository) {
Expand Down
131 changes: 130 additions & 1 deletion lib/security_blog.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,64 @@ export default class SecurityBlog {
cli.ok(`Pre-release announcement file created at ${file}`);
}

async createPosRelease() {
const { cli } = this;

// checkout on security release branch
checkoutOnSecurityReleaseBranch(cli, this.repository);

// read vulnerabilities JSON file
const content = getVulnerabilitiesJSON(cli);
if (!content.releaseDate) {
cli.error('Release date is not set in vulnerabilities.json,' +
' run `git node security --update-date=YYYY/MM/DD` to set the release date.');
process.exit(1);
}

validateDate(content.releaseDate);
const releaseDate = new Date(content.releaseDate);
const template = this.getSecurityPosReleaseTemplate();
const data = {
annoucementDate: await this.getAnnouncementDate(cli),
releaseDate: this.formatReleaseDate(releaseDate),
affectedVersions: this.getAffectedVersions(content),
vulnerabilities: this.getVulnerabilities(content),
slug: this.getSlug(releaseDate),
impact: this.getImpact(content),
openSSLUpdate: await this.promptOpenSSLUpdate(cli),
author: await this.promptAuthor(cli),
reports: content.reports,
dependencyUpdates: await this.promptDependencyUpdates(cli)
};
const month = releaseDate.toLocaleString('en-US', { month: 'long' }).toLowerCase();
const year = releaseDate.getFullYear();
const fileName = `${month}-${year}-security-releases.md`;
const posRelease = await this.buildPosRelease(template, data);
const file = path.join(process.cwd(), fileName);
fs.writeFileSync(file, posRelease);
cli.ok(`Pos-release announcement file created at ${file}`);
}

promptDependencyUpdates(cli) {
return cli.prompt('Does this security release contain dependency updates?', {
defaultAnswer: true
});
}

promptOpenSSLUpdate(cli) {
return cli.prompt('Does this security release containt OpenSSL updates?', {
defaultAnswer: true
});
}

promptAuthor(cli) {
return cli.prompt('Who is the author of this security release? If multiple' +
' use & as separator', {
questionType: 'input',
defaultAnswer: PLACEHOLDERS.author
});
}

formatReleaseDate(releaseDate) {
const options = {
weekday: 'long',
Expand Down Expand Up @@ -95,6 +147,73 @@ export default class SecurityBlog {
return '';
}

async buildPosRelease(template, data) {
const {
annoucementDate,
releaseDate,
affectedVersions,
vulnerabilities,
slug,
impact,
openSSLUpdate,
author,
reports,
dependencyUpdates
} = data;
return template.replaceAll(PLACEHOLDERS.annoucementDate, annoucementDate)
.replaceAll(PLACEHOLDERS.slug, slug)
.replaceAll(PLACEHOLDERS.affectedVersions, affectedVersions)
.replaceAll(PLACEHOLDERS.vulnerabilities, vulnerabilities)
.replaceAll(PLACEHOLDERS.releaseDate, releaseDate)
.replaceAll(PLACEHOLDERS.impact, impact)
.replaceAll(PLACEHOLDERS.openSSLUpdate, this.getOpenSSLUpdateTemplate(openSSLUpdate))
.replaceAll(PLACEHOLDERS.author, author)
.replaceAll(PLACEHOLDERS.reports, await this.getReportsTemplate(reports))
.replaceAll(PLACEHOLDERS.dependencyUpdates,
await this.getDependencyUpdatesTemplate(dependencyUpdates));
}

async getReportsTemplate(reports) {
let template = '';
for (const report of reports) {
let cveId = report.cve_ids.join(', ');
if (!cveId) {
cveId = await this.cli.prompt(`What is the CVE ID for vulnerability https://hackerone.com/reports/${report.id} ${report.title}?`, {
questionType: 'input',
defaultAnswer: 'TBD'
});
}
template += `\n## ${report.title} (${cveId}) - (${report.severity.rating})\n\n`;
template += `${report.summary}\n\n`;
const releaseLines = report.affectedVersions.join(', ');
template += `Impact:\n\n- This vulnerability affects all users\
in active release lines: ${releaseLines}\n\n`;
let contributor = report.contributor;
if (!contributor) {
contributor = await this.cli.prompt(`Who fixed vulnerability https://hackerone.com/reports/${report.id} ${report.title}? If multiple use & as separator`, {
questionType: 'input',
defaultAnswer: 'TBD'
});
}
template += `Thank you, to ${report.reporter} for reporting this vulnerability\
and thank you ${contributor} for fixing it.\n`;
}
return template;
}

async getDependencyUpdatesTemplate(dependencyUpdates) {
if (!dependencyUpdates) return '';
const template = 'This security release includes the following dependency' +
' updates to address public vulnerabilities:\n';
return template;
}

getOpenSSLUpdateTemplate(openSSLUpdate) {

Check failure on line 211 in lib/security_blog.js

View workflow job for this annotation

GitHub Actions / Lint using ESLint

Duplicate name 'getOpenSSLUpdateTemplate'
if (!openSSLUpdate) return '';
return '## OpenSSL Security updates\n\n' +
'This security release includes OpenSSL security updates';
}

getSlug(releaseDate) {
const month = releaseDate.toLocaleString('en-US', { month: 'long' });
const year = releaseDate.getFullYear();
Expand Down Expand Up @@ -149,7 +268,7 @@ export default class SecurityBlog {
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.`);
text.push(`- ${value.length} ${key.toLocaleLowerCase()} severity issues.`);
}
return text.join('\n');
}
Expand All @@ -173,4 +292,14 @@ export default class SecurityBlog {
'utf-8'
);
}

getSecurityPosReleaseTemplate() {
return fs.readFileSync(
new URL(
'./github/templates/security-pos-release.md',
import.meta.url
),
'utf-8'
);
}
}

0 comments on commit 725b4e2

Please sign in to comment.