diff --git a/components/git/security.js b/components/git/security.js index 300f33c6..9e53569a 100644 --- a/components/git/security.js +++ b/components/git/security.js @@ -12,6 +12,10 @@ const securityOptions = { describe: 'Start security release process', type: 'boolean' }, + sync: { + describe: 'Synchronize an ongoing security release with HackerOne', + type: 'boolean' + }, 'update-date': { describe: 'Updates the target date of the security release', type: 'string' @@ -46,6 +50,10 @@ export function builder(yargs) { .example( 'git node security --start', 'Prepare a security release of Node.js') + .example( + 'git node security --sync', + 'Synchronize an ongoing security release with HackerOne' + ) .example( 'git node security --update-date=YYYY/MM/DD', 'Updates the target date of the security release' @@ -76,6 +84,9 @@ export function handler(argv) { if (argv.start) { return startSecurityRelease(argv); } + if (argv.sync) { + return syncSecurityRelease(argv); + } if (argv['update-date']) { return updateReleaseDate(argv); } @@ -142,6 +153,13 @@ async function startSecurityRelease(argv) { return release.start(); } +async function syncSecurityRelease(argv) { + const logStream = process.stdout.isTTY ? process.stdout : process.stderr; + const cli = new CLI(logStream); + const release = new UpdateSecurityRelease(cli); + return release.sync(); +} + async function notifyPreRelease() { const logStream = process.stdout.isTTY ? process.stdout : process.stderr; const cli = new CLI(logStream); diff --git a/lib/security-release/security-release.js b/lib/security-release/security-release.js index 1cbae023..08aac85a 100644 --- a/lib/security-release/security-release.js +++ b/lib/security-release/security-release.js @@ -87,8 +87,8 @@ export async function getSupportedVersions() { return supportedVersions; } -export async function getSummary(reportId, req) { - const { data } = await req.getReport(reportId); +export function getSummary(report) { + const { data } = report; const summaryList = data?.relationships?.summaries?.data; if (!summaryList?.length) return; const summaries = summaryList.filter((summary) => summary?.attributes?.category === 'team'); @@ -139,17 +139,25 @@ export async function createIssue(title, content, repository, { cli, req }) { } } -export async function pickReport(report, { cli, req }) { +export function getReportSeverity(report) { const { - id, attributes: { title, cve_ids }, - relationships: { severity, weakness, reporter, custom_field_values } + relationships: { severity, weakness } } = report; - const link = `https://hackerone.com/reports/${id}`; const reportSeverity = { rating: severity?.data?.attributes?.rating || '', cvss_vector_string: severity?.data?.attributes?.cvss_vector_string || '', weakness_id: weakness?.data?.id || '' }; + return reportSeverity; +} + +export async function pickReport(report, { cli, req }) { + const { + id, attributes: { title, cve_ids }, + relationships: { reporter, custom_field_values } + } = report; + const link = `https://hackerone.com/reports/${id}`; + const reportSeverity = getReportSeverity(report); cli.separator(); cli.info(`Report: ${link} - ${title} (${reportSeverity?.rating})`); @@ -185,7 +193,7 @@ export async function pickReport(report, { cli, req }) { } } - const summaryContent = await getSummary(id, req); + const summaryContent = getSummary(report); return { id, diff --git a/lib/update_security_release.js b/lib/update_security_release.js index 977464f8..ae7b7069 100644 --- a/lib/update_security_release.js +++ b/lib/update_security_release.js @@ -2,9 +2,12 @@ import { NEXT_SECURITY_RELEASE_FOLDER, NEXT_SECURITY_RELEASE_REPOSITORY, checkoutOnSecurityReleaseBranch, + checkRemote, commitAndPushVulnerabilitiesJSON, validateDate, - pickReport + pickReport, + getReportSeverity, + getSummary } from './security-release/security-release.js'; import fs from 'node:fs'; import path from 'node:path'; @@ -18,6 +21,41 @@ export default class UpdateSecurityRelease { this.cli = cli; } + async sync() { + checkRemote(this.cli, this.repository); + + const vulnerabilitiesJSONPath = this.getVulnerabilitiesJSONPath(); + const content = this.readVulnerabilitiesJSON(vulnerabilitiesJSONPath); + const credentials = await auth({ + github: true, + h1: true + }); + const req = new Request(credentials); + for (let i = 0; i < content.reports.length; ++i) { + let report = content.reports[i]; + const { data } = await req.getReport(report.id); + const reportSeverity = getReportSeverity(data); + const summaryContent = getSummary(data); + const link = `https://hackerone.com/reports/${report.id}`; + let prURL = report.prURL; + if (data.relationships.custom_field_values.data.length) { + prURL = data.relationships.custom_field_values.data[0].attributes.value; + } + + report = { + ...report, + title: data.attributes.title, + cveIds: data.attributes.cve_ids, + severity: reportSeverity, + summary: summaryContent ?? report.summary, + link, + prURL + }; + } + fs.writeFileSync(vulnerabilitiesJSONPath, JSON.stringify(content, null, 2)); + this.cli.ok('Synced vulnerabilities.json with HackerOne'); + } + async updateReleaseDate(releaseDate) { const { cli } = this;