-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
115 lines (101 loc) · 3.49 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#!/usr/bin/env node
const { request, stream, setGlobalDispatcher, Agent } = require('undici')
const EE = require('events')
const fs = require('fs')
const path = require('path')
const { setTimeout: delay } = require('node:timers/promises')
const core = require('@actions/core')
setGlobalDispatcher(new Agent({ connections: 1 }))
const CORE_RAW_URL = 'https://raw.githubusercontent.com/nodejs/security-wg/main/vuln/core/index.json'
const NVD_CVE_SEARCH = 'https://services.nvd.nist.gov/rest/json/cves/2.0?cveId='
let lastETagValue
const coreLocalFile = path.join(__dirname, 'core.json')
const ETagFile = path.join(__dirname, '.etag')
if (!process.env.API_KEY) {
console.error('Please provide an API_KEY or the rate limit will be reached.')
process.exit(1)
}
async function readLocal (file) {
return require(file)
}
function loadETag () {
if (fs.existsSync(ETagFile)) {
core.debug('Loading local ETag')
lastETagValue = fs.readFileSync(ETagFile).toString()
}
}
function updateLastETag (etag) {
lastETagValue = etag
fs.writeFileSync(ETagFile, lastETagValue)
}
async function fetchCoreIndex () {
const abortRequest = new EE()
await stream(CORE_RAW_URL, { signal: abortRequest }, ({ statusCode }) => {
if (statusCode !== 200) {
core.error('Request to Github failed. Aborting...')
abortRequest.emit('abort')
process.nextTick(() => { process.exit(1) })
}
return fs.createWriteStream(coreLocalFile, { flags: 'w', autoClose: true })
})
return readLocal(coreLocalFile)
}
async function getCoreIndex () {
const { headers } = await request(CORE_RAW_URL, { method: 'HEAD' })
if (!lastETagValue || lastETagValue !== headers.etag || !fs.existsSync(coreLocalFile)) {
updateLastETag(headers.etag)
core.debug('Creating local core.json')
return fetchCoreIndex()
} else {
core.debug(`No updates from upstream. Getting a cached version: ${coreLocalFile}`)
return readLocal(coreLocalFile)
}
}
async function isCVEPublished (cveId) {
core.debug('Searching... ' + NVD_CVE_SEARCH + cveId)
const { body, statusCode } = await request(NVD_CVE_SEARCH + cveId, {
method: 'GET',
headers: {
apiKey: process.env.API_KEY
}
})
if (statusCode !== 200) {
core.warning(`Request to ${cveId} failed with status code: ${statusCode}`)
// Return true to avoid false positives
return true
}
try {
const json = await body.json()
return json.totalResults !== 0
} catch (e) {
core.error(`Error while JSON parsing ${cveId}. ${await body.text()}`)
// Return true to avoid false positives
return true
}
}
async function main () {
loadETag()
const coreIndex = await getCoreIndex()
// TODO(rafaelgss): we can use a "local" cache to avoid requesting NVD CVEs
// already checked, since true states will never change
let idx = 66 // starting on 2020 CVEs
const lastIdx = +Object.keys(coreIndex)[Object.keys(coreIndex).length -1]
const unpublishedCVEs = []
while (idx < lastIdx) {
core.debug(`Getting: ${idx}`)
// We publish a single CVE for each report
const cve = coreIndex[idx].cve[0]
const published = await isCVEPublished(cve)
if (!published) {
unpublishedCVEs.push({ cveID: cve })
core.warning(`⚠️ ${cve} wasn't published.`)
}
// delay greater than 0.6s is required by NVD API
// 10s is to guarantee rate limit won't be reached
await delay(10000)
++idx
}
core.debug(`Settings: CVEs={"include": [${unpublishedCVEs}]}`)
core.setOutput('CVEs', { include: unpublishedCVEs })
}
main()