@@ -3,14 +3,21 @@ const { request } = require('https')
3
3
const fs = require ( 'fs' )
4
4
const path = require ( 'path' )
5
5
const satisfies = require ( 'semver/functions/satisfies' )
6
- const nv = require ( '@pkgjs/nv' )
7
6
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
+ }
14
21
15
22
async function readLocal ( file ) {
16
23
return require ( file )
@@ -23,26 +30,23 @@ function debug (msg) {
23
30
}
24
31
25
32
function 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
+ }
29
38
}
30
39
}
31
40
32
- function updateLastETag ( etag ) {
33
- lastETagValue = etag
34
- fs . writeFileSync ( ETagFile , lastETagValue )
35
- }
36
-
37
- async function fetchCoreIndex ( ) {
41
+ async function fetchJson ( obj ) {
38
42
await new Promise ( ( resolve ) => {
39
- request ( CORE_RAW_URL , ( res ) => {
43
+ request ( obj . url , ( res ) => {
40
44
if ( res . statusCode !== 200 ) {
41
45
console . error ( `Request to Github returned http status ${ res . statusCode } . Aborting...` )
42
46
process . nextTick ( ( ) => { process . exit ( 1 ) } )
43
47
}
44
48
45
- const fileStream = fs . createWriteStream ( coreLocalFile )
49
+ const fileStream = fs . createWriteStream ( obj . jsonFile )
46
50
res . pipe ( fileStream )
47
51
48
52
fileStream . on ( 'finish' , ( ) => {
@@ -51,20 +55,20 @@ async function fetchCoreIndex () {
51
55
} )
52
56
53
57
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...` )
55
59
process . nextTick ( ( ) => { process . exit ( 1 ) } )
56
60
} )
57
61
} ) . on ( 'error' , ( err ) => {
58
62
console . error ( `Request to Github returned error ${ err . message } . Aborting...` )
59
63
process . nextTick ( ( ) => { process . exit ( 1 ) } )
60
64
} ) . end ( )
61
65
} )
62
- return readLocal ( coreLocalFile )
66
+ return readLocal ( obj . jsonFile )
63
67
}
64
68
65
- async function getCoreIndex ( ) {
69
+ async function getJson ( obj ) {
66
70
return new Promise ( ( resolve ) => {
67
- request ( CORE_RAW_URL , { method : 'HEAD' } , ( res ) => {
71
+ request ( obj . url , { method : 'HEAD' } , ( res ) => {
68
72
if ( res . statusCode !== 200 ) {
69
73
console . error ( `Request to Github returned http status ${ res . statusCode } . Aborting...` )
70
74
process . nextTick ( ( ) => { process . exit ( 1 ) } )
@@ -73,13 +77,14 @@ async function getCoreIndex () {
73
77
res . on ( 'data' , ( ) => { } )
74
78
75
79
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 )
78
83
debug ( 'Creating local core.json' )
79
- resolve ( fetchCoreIndex ( ) )
84
+ resolve ( fetchJson ( obj ) )
80
85
} 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 ) )
83
88
}
84
89
} ) . on ( 'error' , ( err ) => {
85
90
console . error ( `Request to Github returned error ${ err . message } . Aborting...` )
@@ -94,6 +99,7 @@ const checkPlatform = platform => {
94
99
throw new Error ( `platform ${ platform } is not valid. Please use ${ availablePlatforms . join ( ',' ) } .` )
95
100
}
96
101
}
102
+
97
103
const isSystemAffected = ( platform , affectedEnvironments ) => {
98
104
// No platform specified (legacy mode)
99
105
if ( ! platform || ! Array . isArray ( affectedEnvironments ) ) {
@@ -127,15 +133,17 @@ function getVulnerabilityList (currentVersion, data, platform) {
127
133
128
134
async function cli ( currentVersion , platform ) {
129
135
checkPlatform ( platform )
136
+
130
137
const isEOL = await isNodeEOL ( currentVersion )
131
138
if ( isEOL ) {
132
139
console . error ( danger )
133
140
console . error ( `${ currentVersion } is end-of-life. There are high chances of being vulnerable. Please upgrade it.` )
134
141
process . exit ( 1 )
135
142
}
136
143
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
+
139
147
if ( list . length ) {
140
148
console . error ( danger )
141
149
console . error ( vulnerableWarning + '\n' )
@@ -146,25 +154,40 @@ async function cli (currentVersion, platform) {
146
154
}
147
155
}
148
156
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
+
149
173
/**
150
174
* @param {string } version
151
175
* @returns {Promise<boolean> } true if the version is end-of-life
152
176
*/
153
177
async function isNodeEOL ( version ) {
154
- const myVersionInfo = await nv ( version )
178
+ const myVersionInfo = await getVersionInfo ( version )
179
+
155
180
if ( ! myVersionInfo ) {
156
- // i.e. isNodeEOL('abcd')
181
+ // i.e. isNodeEOL('abcd') or isNodeEOL('lts') or isNodeEOL('99')
157
182
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 ) {
162
184
// We got a record, but..
163
185
// v0.12.18 etc does not have an EOL date, which probably means too old.
164
186
return true
165
187
}
188
+
166
189
const now = new Date ( )
167
- const end = new Date ( myVersionInfo [ 0 ] . end )
190
+ const end = new Date ( myVersionInfo . end )
168
191
return now > end
169
192
}
170
193
@@ -175,7 +198,7 @@ async function isNodeVulnerable (version, platform) {
175
198
return true
176
199
}
177
200
178
- const coreIndex = await getCoreIndex ( )
201
+ const coreIndex = await getJson ( STORE . security )
179
202
const list = getVulnerabilityList ( version , coreIndex , platform )
180
203
return list . length > 0
181
204
}
0 commit comments