diff --git a/components/git/security.js b/components/git/security.js index 345034c6..dc857610 100644 --- a/components/git/security.js +++ b/components/git/security.js @@ -13,6 +13,10 @@ const securityOptions = { 'update-date': { describe: 'Updates the target date of the security release', type: 'string' + }, + 'add-report': { + describe: 'Extracts data from HackerOne report and adds it into vulnerabilities.json', + type: 'string' } }; @@ -26,6 +30,10 @@ export function builder(yargs) { .example( 'git node security --update-date=31/12/2023', 'Updates the target date of the security release' + ) + .example( + 'git node security --add-report=H1-ID', + 'Fetches HackerOne report based on ID provided and adds it into vulnerabilities.json' ); } @@ -36,9 +44,20 @@ export function handler(argv) { if (argv['update-date']) { return updateReleaseDate(argv); } + if (argv['add-report']) { + return addReport(argv); + } yargsInstance.showHelp(); } +async function addReport(argv) { + const reportId = argv['add-report']; + const logStream = process.stdout.isTTY ? process.stdout : process.stderr; + const cli = new CLI(logStream); + const update = new UpdateSecurityRelease(cli); + return update.addReport(reportId); +} + async function updateReleaseDate(argv) { const releaseDate = argv['update-date']; const logStream = process.stdout.isTTY ? process.stdout : process.stderr; diff --git a/lib/prepare_security.js b/lib/prepare_security.js index 04fad2ad..72afdaa0 100644 --- a/lib/prepare_security.js +++ b/lib/prepare_security.js @@ -9,7 +9,8 @@ import { NEXT_SECURITY_RELEASE_REPOSITORY, PLACEHOLDERS, checkoutOnSecurityReleaseBranch, - commitAndPushVulnerabilitiesJSON + commitAndPushVulnerabilitiesJSON, + getSummary } from './security-release/security-release.js'; export default class SecurityReleaseSteward { @@ -175,7 +176,7 @@ class PrepareSecurityRelease { questionType: 'input', defaultAnswer: supportedVersions }); - const summaryContent = await this.getSummary(id); + const summaryContent = await getSummary(id, this.req); selectedReports.push({ id, @@ -190,15 +191,6 @@ class PrepareSecurityRelease { return selectedReports; } - async getSummary(reportId) { - const { data } = await this.req.getReport(reportId); - const summaryList = data?.relationships?.summaries?.data; - if (!summaryList?.length) return; - const summaries = summaryList.filter((summary) => summary?.attributes?.category === 'team'); - if (!summaries?.length) return; - return summaries?.[0].attributes?.content; - } - async createVulnerabilitiesJSON(reports, { cli }) { cli.separator('Creating vulnerabilities.json...'); const file = JSON.stringify({ diff --git a/lib/security-release/security-release.js b/lib/security-release/security-release.js index 20f3da1f..974c0905 100644 --- a/lib/security-release/security-release.js +++ b/lib/security-release/security-release.js @@ -1,4 +1,5 @@ import { runSync } from '../run.js'; +import nv from '@pkgjs/nv'; export const NEXT_SECURITY_RELEASE_BRANCH = 'next-security-release'; export const NEXT_SECURITY_RELEASE_FOLDER = 'security-release/next-security-release'; @@ -56,3 +57,19 @@ export function commitAndPushVulnerabilitiesJSON(filePath, commitMessage, { cli, runSync('git', ['push', '-u', 'origin', NEXT_SECURITY_RELEASE_BRANCH]); cli.ok(`Pushed commit: ${commitMessage} to ${NEXT_SECURITY_RELEASE_BRANCH}`); } + +export async function getSupportedVersions() { + const supportedVersions = (await nv('supported')) + .map((v) => `${v.versionName}.x`) + .join(','); + return supportedVersions; +} + +export async function getSummary(reportId, req) { + const { data } = await req.getReport(reportId); + const summaryList = data?.relationships?.summaries?.data; + if (!summaryList?.length) return; + const summaries = summaryList.filter((summary) => summary?.attributes?.category === 'team'); + if (!summaries?.length) return; + return summaries?.[0].attributes?.content; +} diff --git a/lib/update_security_release.js b/lib/update_security_release.js index 096859d7..1a7185fe 100644 --- a/lib/update_security_release.js +++ b/lib/update_security_release.js @@ -2,10 +2,14 @@ import { NEXT_SECURITY_RELEASE_FOLDER, NEXT_SECURITY_RELEASE_REPOSITORY, checkoutOnSecurityReleaseBranch, - commitAndPushVulnerabilitiesJSON + commitAndPushVulnerabilitiesJSON, + getSupportedVersions, + getSummary } from './security-release/security-release.js'; import fs from 'node:fs'; import path from 'node:path'; +import auth from './auth.js'; +import Request from './request.js'; export default class UpdateSecurityRelease { repository = NEXT_SECURITY_RELEASE_REPOSITORY; @@ -39,10 +43,7 @@ export default class UpdateSecurityRelease { cli.ok('Done!'); } - async updateVulnerabilitiesJSON(releaseDate) { - const vulnerabilitiesJSONPath = path.join(process.cwd(), - NEXT_SECURITY_RELEASE_FOLDER, 'vulnerabilities.json'); - + readVulnerabilitiesJSON(vulnerabilitiesJSONPath) { const exists = fs.existsSync(vulnerabilitiesJSONPath); if (!exists) { @@ -50,7 +51,17 @@ export default class UpdateSecurityRelease { process.exit(1); } - const content = JSON.parse(fs.readFileSync(vulnerabilitiesJSONPath, 'utf8')); + return JSON.parse(fs.readFileSync(vulnerabilitiesJSONPath, 'utf8')); + } + + getVulnerabilitiesJSONPath() { + return path.join(process.cwd(), + NEXT_SECURITY_RELEASE_FOLDER, 'vulnerabilities.json'); + } + + async updateVulnerabilitiesJSON(releaseDate) { + const vulnerabilitiesJSONPath = this.getVulnerabilitiesJSONPath(); + const content = this.readVulnerabilitiesJSON(vulnerabilitiesJSONPath); content.releaseDate = releaseDate; fs.writeFileSync(vulnerabilitiesJSONPath, JSON.stringify(content, null, 2)); @@ -58,4 +69,48 @@ export default class UpdateSecurityRelease { this.cli.ok(`Updated the release date in vulnerabilities.json: ${releaseDate}`); return [vulnerabilitiesJSONPath]; } + + async addReport(reportId) { + const { cli } = this; + const credentials = await auth({ + github: true, + h1: true + }); + + const req = new Request(credentials); + // checkout on the next-security-release branch + checkoutOnSecurityReleaseBranch(cli, this.repository); + + // get h1 report + const { data: report } = await req.getReport(reportId); + const { id, attributes: { title, cve_ids }, relationships: { severity, reporter } } = report; + // if severity is not set on h1, set it to TBD + const reportLevel = severity ? severity.data.attributes.rating : 'TBD'; + + // get the affected versions + const supportedVersions = await getSupportedVersions(); + const versions = await cli.prompt('Which active release lines this report affects?', { + questionType: 'input', + defaultAnswer: supportedVersions + }); + + // get the team summary from h1 report + const summaryContent = await getSummary(id, req); + + const entry = { + id, + title, + cve_ids, + severity: reportLevel, + summary: summaryContent ?? '', + affectedVersions: versions.split(',').map((v) => v.replace('v', '').trim()), + reporter: reporter.data.attributes.username + }; + + const vulnerabilitiesJSONPath = this.getVulnerabilitiesJSONPath(); + const content = this.readVulnerabilitiesJSON(vulnerabilitiesJSONPath); + content.reports.push(entry); + fs.writeFileSync(vulnerabilitiesJSONPath, JSON.stringify(content, null, 2)); + this.cli.ok(`Updated vulnerabilities.json with the report: ${id}`); + } }