@@ -11,6 +11,7 @@ import fs from 'node:fs';
1111import path from 'node:path' ;
1212import auth from './auth.js' ;
1313import Request from './request.js' ;
14+ import nv from '@pkgjs/nv' ;
1415
1516export default class UpdateSecurityRelease {
1617 repository = NEXT_SECURITY_RELEASE_REPOSITORY ;
@@ -32,7 +33,7 @@ export default class UpdateSecurityRelease {
3233 checkoutOnSecurityReleaseBranch ( cli , this . repository ) ;
3334
3435 // update the release date in the vulnerabilities.json file
35- const updatedVulnerabilitiesFiles = await this . updateVulnerabilitiesJSON ( releaseDate , { cli } ) ;
36+ const updatedVulnerabilitiesFiles = await this . updateJSONReleaseDate ( releaseDate , { cli } ) ;
3637
3738 const commitMessage = `chore: update the release date to ${ releaseDate } ` ;
3839 commitAndPushVulnerabilitiesJSON ( updatedVulnerabilitiesFiles ,
@@ -56,7 +57,7 @@ export default class UpdateSecurityRelease {
5657 NEXT_SECURITY_RELEASE_FOLDER , 'vulnerabilities.json' ) ;
5758 }
5859
59- async updateVulnerabilitiesJSON ( releaseDate ) {
60+ async updateJSONReleaseDate ( releaseDate ) {
6061 const vulnerabilitiesJSONPath = this . getVulnerabilitiesJSONPath ( ) ;
6162 const content = this . readVulnerabilitiesJSON ( vulnerabilitiesJSONPath ) ;
6263 content . releaseDate = releaseDate ;
@@ -80,9 +81,16 @@ export default class UpdateSecurityRelease {
8081
8182 // get h1 report
8283 const { data : report } = await req . getReport ( reportId ) ;
83- const { id, attributes : { title, cve_ids } , relationships : { severity, reporter } } = report ;
84- // if severity is not set on h1, set it to TBD
85- const reportLevel = severity ? severity . data . attributes . rating : 'TBD' ;
84+ const {
85+ id, attributes : { title, cve_ids } ,
86+ relationships : { severity, reporter, weakness }
87+ } = report ;
88+
89+ const reportSeverity = {
90+ rating : severity ?. data ?. attributes ?. rating || '' ,
91+ cvss_vector_string : severity ?. data ?. attributes ?. cvss_vector_string || '' ,
92+ weakness_id : weakness ?. data ?. id || ''
93+ } ;
8694
8795 // get the affected versions
8896 const supportedVersions = await getSupportedVersions ( ) ;
@@ -97,8 +105,9 @@ export default class UpdateSecurityRelease {
97105 const entry = {
98106 id,
99107 title,
100- cve_ids,
101- severity : reportLevel ,
108+ link : `https://hackerone.com/reports/${ id } ` ,
109+ cveIds : cve_ids ,
110+ severity : reportSeverity ,
102111 summary : summaryContent ?? '' ,
103112 affectedVersions : versions . split ( ',' ) . map ( ( v ) => v . replace ( 'v' , '' ) . trim ( ) ) ,
104113 reporter : reporter . data . attributes . username
@@ -135,4 +144,163 @@ export default class UpdateSecurityRelease {
135144 commitMessage , { cli, repository : this . repository } ) ;
136145 cli . ok ( 'Done!' ) ;
137146 }
147+
148+ async requestCVEs ( ) {
149+ const credentials = await auth ( {
150+ github : true ,
151+ h1 : true
152+ } ) ;
153+ const vulnerabilitiesJSONPath = this . getVulnerabilitiesJSONPath ( ) ;
154+ const content = this . readVulnerabilitiesJSON ( vulnerabilitiesJSONPath ) ;
155+ const { reports } = content ;
156+ const req = new Request ( credentials ) ;
157+ const programId = await this . getNodeProgramId ( req ) ;
158+ const cves = await this . promptCVECreation ( req , reports , programId ) ;
159+ this . assignCVEtoReport ( cves , reports ) ;
160+ this . updateVulnerabilitiesJSON ( content , vulnerabilitiesJSONPath ) ;
161+ this . updateHackonerReportCve ( req , reports ) ;
162+ }
163+
164+ assignCVEtoReport ( cves , reports ) {
165+ for ( const cve of cves ) {
166+ const report = reports . find ( report => report . id === cve . reportId ) ;
167+ report . cveIds = [ cve . cve_identifier ] ;
168+ }
169+ }
170+
171+ async updateHackonerReportCve ( req , reports ) {
172+ for ( const report of reports ) {
173+ const { id, cveIds } = report ;
174+ this . cli . startSpinner ( `Updating report ${ id } with CVEs ${ cveIds } ..` ) ;
175+ const body = {
176+ data : {
177+ type : 'report-cves' ,
178+ attributes : {
179+ cve_ids : cveIds
180+ }
181+ }
182+ } ;
183+ const response = await req . updateReportCVE ( id , body ) ;
184+ if ( response . errors ) {
185+ this . cli . error ( `Error updating report ${ id } ` ) ;
186+ this . cli . error ( JSON . stringify ( response . errors , null , 2 ) ) ;
187+ }
188+ this . cli . stopSpinner ( `Done updating report ${ id } with CVEs ${ cveIds } ..` ) ;
189+ }
190+ }
191+
192+ updateVulnerabilitiesJSON ( content , vulnerabilitiesJSONPath ) {
193+ this . cli . startSpinner ( `Updating vulnerabilities.json from\
194+ ${ vulnerabilitiesJSONPath } ..` ) ;
195+ const filePath = path . resolve ( vulnerabilitiesJSONPath ) ;
196+ fs . writeFileSync ( filePath , JSON . stringify ( content , null , 2 ) ) ;
197+ // push the changes to the repository
198+ commitAndPushVulnerabilitiesJSON ( filePath ,
199+ 'chore: updated vulnerabilities.json with CVEs' ,
200+ { cli : this . cli , repository : this . repository } ) ;
201+ this . cli . stopSpinner ( `Done updating vulnerabilities.json from ${ filePath } ` ) ;
202+ }
203+
204+ async promptCVECreation ( req , reports , programId ) {
205+ const supportedVersions = ( await nv ( 'supported' ) ) ;
206+ const cves = [ ] ;
207+ for ( const report of reports ) {
208+ const { id, summary, title, affectedVersions, cveIds, link } = report ;
209+ // skip if already has a CVE
210+ // risky because the CVE associated might be
211+ // mentioned in the report and not requested by Node
212+ if ( cveIds ?. length ) continue ;
213+
214+ let severity = report . severity ;
215+
216+ if ( ! severity . cvss_vector_string || ! severity . weakness_id ) {
217+ try {
218+ const h1Report = await req . getReport ( id ) ;
219+ if ( ! h1Report . data . relationships . severity ?. data . attributes . cvss_vector_string ) {
220+ throw new Error ( 'No severity found' ) ;
221+ }
222+ severity = {
223+ weakness_id : h1Report . data . relationships . weakness ?. data . id ,
224+ cvss_vector_string :
225+ h1Report . data . relationships . severity ?. data . attributes . cvss_vector_string ,
226+ rating : h1Report . data . relationships . severity ?. data . attributes . rating
227+ } ;
228+ } catch ( error ) {
229+ this . cli . error ( `Couldnt not retrieve severity from report ${ id } , skipping...` ) ;
230+ continue ;
231+ }
232+ }
233+
234+ const { cvss_vector_string, weakness_id } = severity ;
235+
236+ const create = await this . cli . prompt (
237+ `Request a CVE for: \n
238+ Title: ${ title } \n
239+ Link: ${ link } \n
240+ Affected versions: ${ affectedVersions . join ( ', ' ) } \n
241+ Vector: ${ cvss_vector_string } \n
242+ Summary: ${ summary } \n` ,
243+ { defaultAnswer : true } ) ;
244+
245+ if ( ! create ) continue ;
246+
247+ const body = {
248+ data : {
249+ type : 'cve-request' ,
250+ attributes : {
251+ team_handle : 'nodejs-team' ,
252+ versions : await this . formatAffected ( affectedVersions , supportedVersions ) ,
253+ metrics : [
254+ {
255+ vectorString : cvss_vector_string
256+ }
257+ ] ,
258+ weakness_id : Number ( weakness_id ) ,
259+ description : title ,
260+ vulnerability_discovered_at : new Date ( ) . toISOString ( )
261+ }
262+ }
263+ } ;
264+ const { data } = await req . requestCVE ( programId , body ) ;
265+ if ( data . errors ) {
266+ this . cli . error ( `Error requesting CVE for report ${ id } ` ) ;
267+ this . cli . error ( JSON . stringify ( data . errors , null , 2 ) ) ;
268+ continue ;
269+ }
270+ const { cve_identifier } = data . attributes ;
271+ cves . push ( { cve_identifier, reportId : id } ) ;
272+ }
273+ return cves ;
274+ }
275+
276+ async getNodeProgramId ( req ) {
277+ const programs = await req . getPrograms ( ) ;
278+ const { data } = programs ;
279+ for ( const program of data ) {
280+ const { attributes } = program ;
281+ if ( attributes . handle === 'nodejs' ) {
282+ return program . id ;
283+ }
284+ }
285+ }
286+
287+ async formatAffected ( affectedVersions , supportedVersions ) {
288+ const result = [ ] ;
289+ for ( const affectedVersion of affectedVersions ) {
290+ const major = affectedVersion . split ( '.' ) [ 0 ] ;
291+ const latest = supportedVersions . find ( ( v ) => v . major === Number ( major ) ) . version ;
292+ const version = await this . cli . prompt (
293+ `What is the affected version (<=) for release line ${ affectedVersion } ?` ,
294+ { questionType : 'input' , defaultAnswer : latest } ) ;
295+ result . push ( {
296+ vendor : 'nodejs' ,
297+ product : 'node' ,
298+ func : '<=' ,
299+ version,
300+ versionType : 'semver' ,
301+ affected : true
302+ } ) ;
303+ }
304+ return result ;
305+ }
138306}
0 commit comments