- Notifications
You must be signed in to change notification settings - Fork13
Calculate meta-vulnerabilities from package security advisories
License
npm/metavuln-calculator
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Calculate meta-vulnerabilities from package security advisories
This is a pretty low-level package to abstract out the parts of@npmcli/arborist that calculatemetavulnerabilities from security advisories. If you just want to get anaudit for a package tree, probably what you want to use isarborist.audit()
.
constCalculator=require('@npmcli/metavuln-calculator')// pass in any options for cacache and pacote// see those modules for option descriptionsconstcalculator=newCalculator(options)// get an advisory somehow, typically by POSTing a JSON payload like:// {"pkgname":["1.2.3","4.3.5", ...versions], ...packages}// to /-/npm/v1/security/advisories/bulk// to get a payload response like:// {// "semver": [// {// "id": 31,// "url": "https://npmjs.com/advisories/31",// "title": "Regular Expression Denial of Service",// "severity": "moderate",// "vulnerable_versions": "<4.3.2"// }// ],// ...advisories// }constarb=newAborist(options)consttree=awaitarb.loadActual()constadvisories=awaitgetBulkAdvisoryReportSomehow(tree)// then to get a comprehensive set of advisories including metavulns:constset=newSet()for(const[name,advisory]ofObject.entries(advisories)){// make sure we have the advisories loaded with latest version listsset.add(awaitcalculator.calculate(name,{advisory}))}for(constvulnofset){for(constnodeoftree.inventory.query('name',vuln.name)){// not vulnerable, just keep lookingif(!vuln.testVersion(node.version))continuefor(const{from:dep, spec}ofnode.edgesIn){constmetaAdvisory=awaitcalculator.calculate(dep.name,vuln)if(metaAdvisory.testVersion(dep.version,spec)){set.add(metaAdvisory)}}}}
TheCalculator.calculate
method returns a Promise that resolves to aAdvisory
object, filled in from the cache and updated if necessary withthe available advisory data.
Do not instantiateAdvisory
objects directly. Use thecalculate()
method to get one with appropriate data filled in.
Do not mutateAdvisory
objects. Use the supplied methods only.
name
The name of the package that this vulnerability is aboutid
The unique cache key for this vuln or metavuln. (SeeCache Keysbelow.)dependency
For metavulns, the dependency that causes this package to behave a vulnerability. For advisories, the same asname
.type
Either'advisory'
or'metavuln'
, depending on the type ofvulnerability that this object represents.url
The url for the advisory (null
for metavulns)title
The text title of the advisory or metavulnseverity
The severity level info/low/medium/high/criticalrange
The range that is vulnerableversions
The set of available versions of the packagevulnerableVersions
The set of versions that are vulnerablesource
The numeric ID of the advisory, or the cache key of thevulnerability that causes this metavulnupdated
Boolean indicating whether this vulnerability was updated sincebeing read from cache.packument
The packument object for the package that this vulnerabilityis about.
Check to see if a given version is vulnerable. Returnstrue
if theversion is vulnerable, and should be avoided.
For metavulns,dependencySpecifier
indicates the version range of thesource of the vulnerability, which the module depends on. If not provided,will attempt to read from the packument. If not provided, and unable toread from the packument, thentrue
is returned, indicating that the (notinstallable) package version should be avoided.
The cache keys are calculated by hashing together thesource
andname
fields, prefixing with the string'security-advisory:'
and the name ofthe dependency that is vulnerable.
So, a third-level metavulnerability might have a key like:
'security-advisory:foo:'+ hash(['foo', hash(['bar', hash(['baz', 123])])])
Thus, the cached entry with this key would reflect the version offoo
that is vulnerable by virtue of dependending exclusively on versions ofbar
which are vulnerable by virtue of depending exclusively on versionsofbaz
which are vulnerable by virtue of advisory ID123
.
Loading advisory data entirely from cache without hitting an npm registrysecurity advisory endpoint is not supported at this time, but technicallypossible, and likely to come in a future version of this library.
Options object is used forcacache
andpacote
calls.
name
The name of the package that the advisory is aboutsource
Advisory object from the npm security endpoint, or aAdvisory
object returned by a previous call to thecalculate()
method."Advisory" objects need to have:id
id of the advisory or Advisory objectvulnerable_versions
range of versions affectedurl
title
severity
Fetches the packument and returns a Promise that resolves to avulnerability object described above.
Will perform required I/O to fetch package metadata from registry andread from cache. Advisory information written back to cache.
Typically, dependency ranges don't change very frequently, and the mostrecent version published on a given release line is most likely to containthe fix for a given vulnerability.
So, we see things like this:
3.0.4 - not vulnerable3.0.3 - vulnerable3.0.2 - vulnerable3.0.1 - vulnerable3.0.0 - vulnerable2.3.107 - not vulnerable2.3.106 - not vulnerable2.3.105 - vulnerable... 523 more vulnerable versions ...2.0.0 - vulnerable1.1.102 - not vulnerable1.1.101 - vulnerable... 387 more vulnerable versions ...0.0.0 - vulnerable
In order to determine which versions of a package are affected by avulnerability in a dependency, this module uses the following algorithm tominimize the number of tests required by performing a binary search on eachversion set, and presuming that versionsbetween vulnerable versionswithin a given set are also vulnerable.
Sort list of available versions by SemVer precedence
Group versions into sets based on MAJOR/MINOR versions.
3.0.0 - 3.0.42.3.0 - 2.3.1072.2.0 - 2.2.432.1.0 - 2.1.4322.0.0 - 2.0.1021.1.0 - 1.1.1021.0.0 - 1.0.1570.1.0 - 0.1.1230.0.0 - 0.0.57
Test the highest and lowest in each MAJOR/MINOR set, and mark highestand lowest with known-vulnerable status. (
(s)
means "safe" and(v)
means "vulnerable".)3.0.0(v) - 3.0.4(s)2.3.0(v) - 2.3.107(s)2.2.0(v) - 2.2.43(v)2.1.0(v) - 2.1.432(v)2.0.0(v) - 2.0.102(v)1.1.0(v) - 1.1.102(s)1.0.0(v) - 1.0.157(v)0.1.0(v) - 0.1.123(v)0.0.0(v) - 0.0.57(v)
For each set of package versions:
If highest and lowest both vulnerable, assume entire set isvulnerable, and continue to next set. Ie, in the example, throw outthe following version sets:
2.2.0(v) - 2.2.43(v)2.1.0(v) - 2.1.432(v)2.0.0(v) - 2.0.102(v)1.0.0(v) - 1.0.157(v)0.1.0(v) - 0.1.123(v)0.0.0(v) - 0.0.57(v)
Test middle version MID in set, splitting into two sets.
3.0.0(v) - 3.0.2(v) - 3.0.4(s)2.3.0(v) - 2.3.54(v) - 2.3.107(s)1.1.0(v) - 1.1.51(v) - 1.1.102(s)
If any untested versions in Set(mid..highest) or Set(lowest..mid),add to list of sets to test.
3.0.0(v) - 3.0.2(v) <-- thrown out on next iteration3.0.2(v) - 3.0.4(s)2.3.0(v) - 2.3.54(v) <-- thrown out on next iteration2.3.54(v) - 2.3.107(s)1.1.0(v) - 1.1.51(v) <-- thrown out on next iteration1.1.51(v) - 1.1.102(s)
When the process finishes, all versions are either confirmed safe, orconfirmed/assumed vulnerable, and we avoid checking large sets of versionswhere vulnerabilities went unfixed.
When the dependency is inbundleDependencies
, we treat any dependentversion thatmay be vulnerable as a vulnerability. If the dependency isnot inbundleDependencies
, then we treat the dependent module as avulnerability if it canonly resolve to dependency versions that arevulnerable.
This relies on the reasonable assumption that the version of a bundleddependency will be within the stated dependency range, and accounts for thefact that we can't know ahead of time which version of a dependency may bebundled. So, we avoid versions thatmay bundle a vulnerable dependency.
For example:
Packagefoo
depends on packagebar
at the following version ranges:
foo version bar version range1.0.0 ^1.2.31.0.1 ^1.2.41.0.2 ^1.2.51.1.0 ^1.3.11.1.1 ^1.3.21.1.2 ^1.3.32.0.0 ^2.0.02.0.1 ^2.0.12.0.2 ^2.0.2
There is an advisory forbar@1.2.4 - 1.3.2
. So:
foo version vulnerable?1.0.0 if bundled (can use 1.2.3, which is not vulnerable)1.0.1 yes (must use ^1.2.4, entirely contained in vuln range)1.0.2 yes (must use ^1.2.5, entirely contained in vuln range)1.1.0 if bundled (can use 1.3.3, which is not vulnerable)1.1.1 if bundled (can use 1.3.3, which is not vulnerable)1.1.2 no (dep is outside of vuln range)2.0.0 no (dep is outside of vuln range)2.0.1 no (dep is outside of vuln range)2.0.2 no (dep is outside of vuln range)
To test a package version for metaVulnerable status, we attempt to load themanifest of the dependency, using the vulnerable version set as theavoid
versions. If we end up selecting a version that should be avoided, thenthat means that the package is vulnerable by virtue of its dependency.
About
Calculate meta-vulnerabilities from package security advisories
Topics
Resources
License
Code of conduct
Security policy
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors14
Uh oh!
There was an error while loading.Please reload this page.