@@ -3,14 +3,21 @@ const { request } = require('https')
33const fs = require ( 'fs' )
44const path = require ( 'path' )
55const satisfies = require ( 'semver/functions/satisfies' )
6- const nv = require ( '@pkgjs/nv' )
76
8- const CORE_RAW_URL = 'https://raw.githubusercontent.com/nodejs/security-wg/main/vuln/core/index.json'
9-
10- let lastETagValue
11-
12- const coreLocalFile = path . join ( __dirname , 'core.json' )
13- const ETagFile = path . join ( __dirname , '.etag' )
7+ const STORE = {
8+ security : {
9+ url : 'https://raw.githubusercontent.com/nodejs/security-wg/main/vuln/core/index.json' ,
10+ jsonFile : path . join ( __dirname , 'security.json' ) ,
11+ etagFile : path . join ( __dirname , 'security.etag' ) ,
12+ etagValue : ''
13+ } ,
14+ schedule : {
15+ url : 'https://raw.githubusercontent.com/nodejs/Release/main/schedule.json' ,
16+ jsonFile : path . join ( __dirname , 'schedule.json' ) ,
17+ etagFile : path . join ( __dirname , 'schedule.etag' ) ,
18+ etagValue : ''
19+ }
20+ }
1421
1522async function readLocal ( file ) {
1623 return require ( file )
@@ -23,26 +30,23 @@ function debug (msg) {
2330}
2431
2532function loadETag ( ) {
26- if ( fs . existsSync ( ETagFile ) ) {
27- debug ( 'Loading local ETag' )
28- lastETagValue = fs . readFileSync ( ETagFile ) . toString ( )
33+ for ( const [ key , obj ] of Object . entries ( STORE ) ) {
34+ if ( fs . existsSync ( obj . etagFile ) ) {
35+ debug ( `Loading local ETag for '${ key } '` )
36+ obj . etagValue = fs . readFileSync ( obj . etagFile ) . toString ( )
37+ }
2938 }
3039}
3140
32- function updateLastETag ( etag ) {
33- lastETagValue = etag
34- fs . writeFileSync ( ETagFile , lastETagValue )
35- }
36-
37- async function fetchCoreIndex ( ) {
41+ async function fetchJson ( obj ) {
3842 await new Promise ( ( resolve ) => {
39- request ( CORE_RAW_URL , ( res ) => {
43+ request ( obj . url , ( res ) => {
4044 if ( res . statusCode !== 200 ) {
4145 console . error ( `Request to Github returned http status ${ res . statusCode } . Aborting...` )
4246 process . nextTick ( ( ) => { process . exit ( 1 ) } )
4347 }
4448
45- const fileStream = fs . createWriteStream ( coreLocalFile )
49+ const fileStream = fs . createWriteStream ( obj . jsonFile )
4650 res . pipe ( fileStream )
4751
4852 fileStream . on ( 'finish' , ( ) => {
@@ -51,20 +55,20 @@ async function fetchCoreIndex () {
5155 } )
5256
5357 fileStream . on ( 'error' , ( err ) => {
54- console . error ( `Error ${ err . message } while writing to '${ coreLocalFile } '. Aborting...` )
58+ console . error ( `Error ${ err . message } while writing to '${ obj . jsonFile } '. Aborting...` )
5559 process . nextTick ( ( ) => { process . exit ( 1 ) } )
5660 } )
5761 } ) . on ( 'error' , ( err ) => {
5862 console . error ( `Request to Github returned error ${ err . message } . Aborting...` )
5963 process . nextTick ( ( ) => { process . exit ( 1 ) } )
6064 } ) . end ( )
6165 } )
62- return readLocal ( coreLocalFile )
66+ return readLocal ( obj . jsonFile )
6367}
6468
65- async function getCoreIndex ( ) {
69+ async function getJson ( obj ) {
6670 return new Promise ( ( resolve ) => {
67- request ( CORE_RAW_URL , { method : 'HEAD' } , ( res ) => {
71+ request ( obj . url , { method : 'HEAD' } , ( res ) => {
6872 if ( res . statusCode !== 200 ) {
6973 console . error ( `Request to Github returned http status ${ res . statusCode } . Aborting...` )
7074 process . nextTick ( ( ) => { process . exit ( 1 ) } )
@@ -73,13 +77,14 @@ async function getCoreIndex () {
7377 res . on ( 'data' , ( ) => { } )
7478
7579 const { etag } = res . headers
76- if ( ! lastETagValue || lastETagValue !== etag || ! fs . existsSync ( coreLocalFile ) ) {
77- updateLastETag ( etag )
80+ if ( ! obj . etagValue || obj . eTagValue !== etag || ! fs . existsSync ( obj . jsonFile ) ) {
81+ obj . etagValue = etag
82+ fs . writeFileSync ( obj . etagFile , etag )
7883 debug ( 'Creating local core.json' )
79- resolve ( fetchCoreIndex ( ) )
84+ resolve ( fetchJson ( obj ) )
8085 } else {
81- debug ( `No updates from upstream. Getting a cached version: ${ coreLocalFile } ` )
82- resolve ( readLocal ( coreLocalFile ) )
86+ debug ( `No updates from upstream. Getting a cached version: ${ obj . jsonFile } ` )
87+ resolve ( readLocal ( obj . jsonFile ) )
8388 }
8489 } ) . on ( 'error' , ( err ) => {
8590 console . error ( `Request to Github returned error ${ err . message } . Aborting...` )
@@ -94,6 +99,7 @@ const checkPlatform = platform => {
9499 throw new Error ( `platform ${ platform } is not valid. Please use ${ availablePlatforms . join ( ',' ) } .` )
95100 }
96101}
102+
97103const isSystemAffected = ( platform , affectedEnvironments ) => {
98104 // No platform specified (legacy mode)
99105 if ( ! platform || ! Array . isArray ( affectedEnvironments ) ) {
@@ -127,15 +133,17 @@ function getVulnerabilityList (currentVersion, data, platform) {
127133
128134async function cli ( currentVersion , platform ) {
129135 checkPlatform ( platform )
136+
130137 const isEOL = await isNodeEOL ( currentVersion )
131138 if ( isEOL ) {
132139 console . error ( danger )
133140 console . error ( `${ currentVersion } is end-of-life. There are high chances of being vulnerable. Please upgrade it.` )
134141 process . exit ( 1 )
135142 }
136143
137- const coreIndex = await getCoreIndex ( )
138- const list = getVulnerabilityList ( currentVersion , coreIndex , platform )
144+ const securityJson = await getJson ( STORE . security )
145+ const list = getVulnerabilityList ( currentVersion , securityJson , platform )
146+
139147 if ( list . length ) {
140148 console . error ( danger )
141149 console . error ( vulnerableWarning + '\n' )
@@ -146,25 +154,40 @@ async function cli (currentVersion, platform) {
146154 }
147155}
148156
157+ async function getVersionInfo ( version ) {
158+ const scheduleJson = await getJson ( STORE . schedule )
159+
160+ if ( scheduleJson [ version . toLowerCase ( ) ] ) {
161+ return scheduleJson [ version . toLowerCase ( ) ]
162+ }
163+
164+ for ( const [ key , value ] of Object . entries ( scheduleJson ) ) {
165+ if ( satisfies ( version , key ) ) {
166+ return value
167+ }
168+ }
169+
170+ return null
171+ }
172+
149173/**
150174 * @param {string } version
151175 * @returns {Promise<boolean> } true if the version is end-of-life
152176 */
153177async function isNodeEOL ( version ) {
154- const myVersionInfo = await nv ( version )
178+ const myVersionInfo = await getVersionInfo ( version )
179+
155180 if ( ! myVersionInfo ) {
156- // i.e. isNodeEOL('abcd')
181+ // i.e. isNodeEOL('abcd') or isNodeEOL('lts') or isNodeEOL('99')
157182 throw Error ( `Could not fetch version information for ${ version } ` )
158- } else if ( myVersionInfo . length !== 1 ) {
159- // i.e. isNodeEOL('lts') or isNodeEOL('99')
160- throw Error ( `Did not get exactly one version record for ${ version } ` )
161- } else if ( ! myVersionInfo [ 0 ] . end ) {
183+ } else if ( ! myVersionInfo . end ) {
162184 // We got a record, but..
163185 // v0.12.18 etc does not have an EOL date, which probably means too old.
164186 return true
165187 }
188+
166189 const now = new Date ( )
167- const end = new Date ( myVersionInfo [ 0 ] . end )
190+ const end = new Date ( myVersionInfo . end )
168191 return now > end
169192}
170193
@@ -175,7 +198,7 @@ async function isNodeVulnerable (version, platform) {
175198 return true
176199 }
177200
178- const coreIndex = await getCoreIndex ( )
201+ const coreIndex = await getJson ( STORE . security )
179202 const list = getVulnerabilityList ( version , coreIndex , platform )
180203 return list . length > 0
181204}
0 commit comments