Skip to content

Commit 59fefcd

Browse files
authored
Replace @pkg/nv with https.request call (#27)
* Replace @pkg/nv with https.request call * Update to throw error for v25 since v24 is available * Remove @pkgjs/nv dependency
1 parent 453e164 commit 59fefcd

File tree

5 files changed

+79
-454
lines changed

5 files changed

+79
-454
lines changed

.gitignore

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,5 +123,7 @@ out
123123
tags
124124
tags.*
125125

126-
.etag
127-
core.json
126+
schedule.etag
127+
schedule.json
128+
security.etag
129+
security.json

is-vulnerable.js

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,21 @@ const { request } = require('https')
33
const fs = require('fs')
44
const path = require('path')
55
const 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

1522
async function readLocal (file) {
1623
return require(file)
@@ -23,26 +30,23 @@ function debug (msg) {
2330
}
2431

2532
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+
}
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+
97103
const 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

128134
async 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
*/
153177
async 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

Comments
 (0)