node/deps/npm/node_modules/@npmcli/arborist/lib/vuln.js
Myles Borins 2e54524955
deps: update npm to 7.0.0-rc.3
PR-URL: https://github.com/nodejs/node/pull/35474
Reviewed-By: Ruy Adorno <ruyadorno@github.com>
Reviewed-By: Ujjwal Sharma <ryzokuken@disroot.org>
Reviewed-By: Ben Coe <bencoe@gmail.com>
Reviewed-By: Geoffrey Booth <webmaster@geoffreybooth.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Shelley Vohr <codebytere@gmail.com>
Reviewed-By: Guy Bedford <guybedford@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
2020-10-07 09:59:49 -04:00

190 lines
5.4 KiB
JavaScript

// An object representing a vulnerability either as the result of an
// advisory or due to the package in question depending exclusively on
// vulnerable versions of a dep.
//
// - name: package name
// - range: Set of vulnerable versions
// - nodes: Set of nodes affected
// - effects: Set of vulns triggered by this one
// - advisories: Set of advisories (including metavulns) causing this vuln.
// All of the entries in via are vulnerability objects returned by
// @npmcli/metavuln-calculator
// - via: dependency vulns which cause this one
const {satisfies, simplifyRange} = require('semver')
const semverOpt = { loose: true, includePrerelease: true }
const npa = require('npm-package-arg')
const _range = Symbol('_range')
const _simpleRange = Symbol('_simpleRange')
const _fixAvailable = Symbol('_fixAvailable')
const severities = new Map([
['info', 0],
['low', 1],
['moderate', 2],
['high', 3],
['critical', 4],
[null, -1],
])
for (const [name, val] of severities.entries())
severities.set(val, name)
class Vuln {
constructor ({ name, advisory }) {
this.name = name
this.via = new Set()
this.advisories = new Set()
this.severity = null
this.effects = new Set()
this.topNodes = new Set()
this[_range] = null
this[_simpleRange] = null
this.nodes = new Set()
// assume a fix is available unless it hits a top node
// that locks it in place, setting this to false or {isSemVerMajor, version}.
this[_fixAvailable] = true
this.addAdvisory(advisory)
this.packument = advisory.packument
this.versions = advisory.versions
}
get fixAvailable () {
return this[_fixAvailable]
}
set fixAvailable (f) {
this[_fixAvailable] = f
// if there's a fix available for this at the top level, it means that
// it will also fix the vulns that led to it being there. to get there,
// we set the vias to the most "strict" of fix availables.
// - false: no fix is available
// - {name, version, isSemVerMajor} fix requires -f, is semver major
// - {name, version} fix requires -f, not semver major
// - true: fix does not require -f
for (const v of this.via) {
// don't blow up on loops
if (v.fixAvailable === f)
continue
if (f === false)
v.fixAvailable = f
else if (v.fixAvailable === true)
v.fixAvailable = f
else if (typeof f === 'object' && (
typeof v.fixAvailable !== 'object' || !v.fixAvailable.isSemVerMajor))
v.fixAvailable = f
}
}
testSpec (spec) {
const specObj = npa(spec)
if (!specObj.registry)
return true
for (const v of this.versions) {
if (satisfies(v, spec) && !satisfies(v, this.range, semverOpt))
return false
}
return true
}
toJSON () {
// sort so that they're always in a consistent order
return {
name: this.name,
severity: this.severity,
// just loop over the advisories, since via is only Vuln objects,
// and calculated advisories have all the info we need
via: [...this.advisories].map(v => v.type === 'metavuln' ? v.dependency : {
...v,
versions: undefined,
vulnerableVersions: undefined,
id: undefined,
}).sort((a, b) =>
String(a.source || a).localeCompare(String(b.source || b))),
effects: [...this.effects].map(v => v.name)
.sort(/* istanbul ignore next */(a, b) => a.localeCompare(b)),
range: this.simpleRange,
nodes: [...this.nodes].map(n => n.location)
.sort(/* istanbul ignore next */(a, b) => a.localeCompare(b)),
fixAvailable: this[_fixAvailable],
}
}
addVia (v) {
this.via.add(v)
v.effects.add(this)
// call the setter since we might add vias _after_ setting fixAvailable
this.fixAvailable = this.fixAvailable
}
deleteVia (v) {
this.via.delete(v)
v.effects.delete(this)
}
deleteAdvisory (advisory) {
this.advisories.delete(advisory)
// make sure we have the max severity of all the vulns causing this one
this.severity = null
this[_range] = null
this[_simpleRange] = null
// refresh severity
for (const advisory of this.advisories)
this.addAdvisory(advisory)
// remove any effects that are no longer relevant
const vias = new Set([...this.advisories].map(a => a.dependency))
for (const via of this.via) {
if (!vias.has(via.name))
this.deleteVia(via)
}
}
addAdvisory (advisory) {
this.advisories.add(advisory)
const sev = severities.get(advisory.severity)
this[_range] = null
this[_simpleRange] = null
if (sev > severities.get(this.severity))
this.severity = advisory.severity
}
get range () {
return this[_range] ||
(this[_range] = [...this.advisories].map(v => v.range).join(' || '))
}
get simpleRange () {
if (this[_simpleRange] && this[_simpleRange] === this[_range])
return this[_simpleRange]
const versions = [...this.advisories][0].versions
const range = this.range
const simple = simplifyRange(versions, range, semverOpt)
return this[_simpleRange] = this[_range] = simple
}
isVulnerable (node) {
if (this.nodes.has(node))
return true
const { version } = node.package
if (!version)
return false
for (const v of this.advisories) {
if (v.testVersion(version)) {
this.nodes.add(node)
return true
}
}
return false
}
}
module.exports = Vuln