2023-08-15 09:31:15 +00:00
// TODO #22198
2024-08-19 13:15:27 +00:00
import type { Ecosystem , Osv } from '@renovatebot/osv-offline' ;
import { OsvOffline } from '@renovatebot/osv-offline' ;
2023-02-10 10:13:47 +00:00
import is from '@sindresorhus/is' ;
import type { CvssScore } from 'vuln-vects' ;
import { parseCvssVector } from 'vuln-vects' ;
2022-04-12 16:13:20 +00:00
import { getManagerConfig , mergeChildConfig } from '../../../config' ;
import type { PackageRule , RenovateConfig } from '../../../config/types' ;
import { logger } from '../../../logger' ;
2023-07-28 08:09:41 +00:00
import { getDefaultVersioning } from '../../../modules/datasource/common' ;
2022-04-12 16:13:20 +00:00
import type {
PackageDependency ,
2023-02-20 14:58:49 +00:00
PackageFile ,
2022-04-12 16:13:20 +00:00
} from '../../../modules/manager/types' ;
2024-08-19 13:15:27 +00:00
import type { VersioningApi } from '../../../modules/versioning' ;
import { get as getVersioning } from '../../../modules/versioning' ;
2024-02-16 16:13:49 +00:00
import { findGithubToken } from '../../../util/check-token' ;
2024-01-26 20:21:53 +00:00
import { find } from '../../../util/host-rules' ;
2023-02-10 10:13:47 +00:00
import { sanitizeMarkdown } from '../../../util/markdown' ;
2022-09-06 10:36:51 +00:00
import * as p from '../../../util/promises' ;
2023-02-10 10:13:47 +00:00
import { regEx } from '../../../util/regex' ;
2023-05-16 15:38:23 +00:00
import { titleCase } from '../../../util/string' ;
2023-05-11 15:00:20 +00:00
import type {
DependencyVulnerabilities ,
SeverityDetails ,
Vulnerability ,
} from './types' ;
2022-04-12 16:13:20 +00:00
export class Vulnerabilities {
private osvOffline : OsvOffline | undefined ;
2023-02-10 10:13:47 +00:00
private static readonly datasourceEcosystemMap : Record <
2022-04-12 16:13:20 +00:00
string ,
Ecosystem | undefined
> = {
2023-02-10 10:13:47 +00:00
crate : 'crates.io' ,
go : 'Go' ,
hex : 'Hex' ,
2022-04-12 16:13:20 +00:00
maven : 'Maven' ,
npm : 'npm' ,
nuget : 'NuGet' ,
2023-02-10 10:13:47 +00:00
packagist : 'Packagist' ,
pypi : 'PyPI' ,
rubygems : 'RubyGems' ,
2022-04-12 16:13:20 +00:00
} ;
private constructor ( ) { }
private async initialize ( ) : Promise < void > {
2024-01-26 20:21:53 +00:00
// hard-coded logic to use authentication for github.com based on the githubToken for api.github.com
2024-02-16 16:13:49 +00:00
const token = findGithubToken (
find ( {
hostType : 'github' ,
url : 'https://api.github.com/' ,
} ) ,
) ;
2024-01-26 20:21:53 +00:00
2024-02-16 16:13:49 +00:00
this . osvOffline = await OsvOffline . create ( token ) ;
2022-04-12 16:13:20 +00:00
}
static async create ( ) : Promise < Vulnerabilities > {
const instance = new Vulnerabilities ( ) ;
await instance . initialize ( ) ;
return instance ;
}
2023-04-12 20:34:13 +00:00
async appendVulnerabilityPackageRules (
2022-04-12 16:13:20 +00:00
config : RenovateConfig ,
2023-11-07 15:50:29 +00:00
packageFiles : Record < string , PackageFile [ ] > ,
2022-04-12 16:13:20 +00:00
) : Promise < void > {
2023-04-18 09:07:36 +00:00
const dependencyVulnerabilities = await this . fetchDependencyVulnerabilities (
config ,
2023-11-07 15:50:29 +00:00
packageFiles ,
2023-04-18 09:07:36 +00:00
) ;
config . packageRules ? ? = [ ] ;
for ( const {
vulnerabilities ,
versioningApi ,
} of dependencyVulnerabilities ) {
const groupPackageRules : PackageRule [ ] = [ ] ;
for ( const vulnerability of vulnerabilities ) {
const rule = this . vulnerabilityToPackageRules ( vulnerability ) ;
if ( is . nullOrUndefined ( rule ) ) {
continue ;
}
groupPackageRules . push ( rule ) ;
}
this . sortByFixedVersion ( groupPackageRules , versioningApi ) ;
config . packageRules . push ( . . . groupPackageRules ) ;
}
}
async fetchVulnerabilities (
config : RenovateConfig ,
2023-11-07 15:50:29 +00:00
packageFiles : Record < string , PackageFile [ ] > ,
2023-04-18 09:07:36 +00:00
) : Promise < Vulnerability [ ] > {
const groups = await this . fetchDependencyVulnerabilities (
config ,
2023-11-07 15:50:29 +00:00
packageFiles ,
2023-04-18 09:07:36 +00:00
) ;
return groups . flatMap ( ( group ) = > group . vulnerabilities ) ;
}
private async fetchDependencyVulnerabilities (
config : RenovateConfig ,
2023-11-07 15:50:29 +00:00
packageFiles : Record < string , PackageFile [ ] > ,
2023-04-18 09:07:36 +00:00
) : Promise < DependencyVulnerabilities [ ] > {
2023-02-10 10:13:47 +00:00
const managers = Object . keys ( packageFiles ) ;
2022-04-12 16:13:20 +00:00
const allManagerJobs = managers . map ( ( manager ) = >
2023-11-07 15:50:29 +00:00
this . fetchManagerVulnerabilities ( config , packageFiles , manager ) ,
2022-04-12 16:13:20 +00:00
) ;
2023-04-18 09:07:36 +00:00
return ( await Promise . all ( allManagerJobs ) ) . flat ( ) ;
2022-04-12 16:13:20 +00:00
}
private async fetchManagerVulnerabilities (
config : RenovateConfig ,
2023-02-20 14:58:49 +00:00
packageFiles : Record < string , PackageFile [ ] > ,
2023-11-07 15:50:29 +00:00
manager : string ,
2023-04-18 09:07:36 +00:00
) : Promise < DependencyVulnerabilities [ ] > {
2022-04-12 16:13:20 +00:00
const managerConfig = getManagerConfig ( config , manager ) ;
const queue = packageFiles [ manager ] . map (
2023-04-18 09:07:36 +00:00
( pFile ) = > ( ) : Promise < DependencyVulnerabilities [ ] > = >
2023-11-07 15:50:29 +00:00
this . fetchManagerPackageFileVulnerabilities ( managerConfig , pFile ) ,
2022-04-12 16:13:20 +00:00
) ;
logger . trace (
{ manager , queueLength : queue.length } ,
2023-11-07 15:50:29 +00:00
'fetchManagerVulnerabilities starting' ,
2022-04-12 16:13:20 +00:00
) ;
2023-04-18 09:07:36 +00:00
const result = ( await p . all ( queue ) ) . flat ( ) ;
logger . trace ( { manager } , 'fetchManagerVulnerabilities finished' ) ;
return result ;
2022-04-12 16:13:20 +00:00
}
2023-02-10 10:13:47 +00:00
private async fetchManagerPackageFileVulnerabilities (
2022-04-12 16:13:20 +00:00
managerConfig : RenovateConfig ,
2023-11-07 15:50:29 +00:00
pFile : PackageFile ,
2023-04-18 09:07:36 +00:00
) : Promise < DependencyVulnerabilities [ ] > {
2022-04-12 16:13:20 +00:00
const { packageFile } = pFile ;
const packageFileConfig = mergeChildConfig ( managerConfig , pFile ) ;
const { manager } = packageFileConfig ;
const queue = pFile . deps . map (
2023-04-18 09:07:36 +00:00
( dep ) = > ( ) : Promise < DependencyVulnerabilities | null > = >
2023-11-07 15:50:29 +00:00
this . fetchDependencyVulnerability ( packageFileConfig , dep ) ,
2022-04-12 16:13:20 +00:00
) ;
logger . trace (
{ manager , packageFile , queueLength : queue.length } ,
2023-11-07 15:50:29 +00:00
'fetchManagerPackageFileVulnerabilities starting with concurrency' ,
2022-04-12 16:13:20 +00:00
) ;
2023-04-18 09:07:36 +00:00
const result = await p . all ( queue ) ;
2023-02-10 10:13:47 +00:00
logger . trace (
{ packageFile } ,
2023-11-07 15:50:29 +00:00
'fetchManagerPackageFileVulnerabilities finished' ,
2023-02-10 10:13:47 +00:00
) ;
2023-04-18 09:07:36 +00:00
return result . filter ( is . truthy ) ;
2022-04-12 16:13:20 +00:00
}
2023-04-18 09:07:36 +00:00
private async fetchDependencyVulnerability (
2023-02-20 14:58:49 +00:00
packageFileConfig : RenovateConfig & PackageFile ,
2023-11-07 15:50:29 +00:00
dep : PackageDependency ,
2023-04-18 09:07:36 +00:00
) : Promise < DependencyVulnerabilities | null > {
2023-02-10 10:13:47 +00:00
const ecosystem = Vulnerabilities . datasourceEcosystemMap [ dep . datasource ! ] ;
if ( ! ecosystem ) {
logger . trace ( ` Cannot map datasource ${ dep . datasource ! } to OSV ecosystem ` ) ;
2023-04-18 09:07:36 +00:00
return null ;
2023-02-10 10:13:47 +00:00
}
let packageName = dep . packageName ? ? dep . depName ! ;
if ( ecosystem === 'PyPI' ) {
// https://peps.python.org/pep-0503/#normalized-names
packageName = packageName . toLowerCase ( ) . replace ( regEx ( /[_.-]+/g ) , '-' ) ;
}
try {
2023-04-18 09:07:36 +00:00
const osvVulnerabilities = await this . osvOffline ? . getVulnerabilities (
2023-02-10 10:13:47 +00:00
ecosystem ,
2023-11-07 15:50:29 +00:00
packageName ,
2023-02-10 10:13:47 +00:00
) ;
if (
2023-04-18 09:07:36 +00:00
is . nullOrUndefined ( osvVulnerabilities ) ||
is . emptyArray ( osvVulnerabilities )
2023-02-10 10:13:47 +00:00
) {
logger . trace (
2023-11-07 15:50:29 +00:00
` No vulnerabilities found in OSV database for ${ packageName } ` ,
2023-02-10 10:13:47 +00:00
) ;
2023-04-18 09:07:36 +00:00
return null ;
2023-02-10 10:13:47 +00:00
}
const depVersion =
dep . lockedVersion ? ? dep . currentVersion ? ? dep . currentValue ! ;
const versioning = dep . versioning ? ? getDefaultVersioning ( dep . datasource ) ;
const versioningApi = getVersioning ( versioning ) ;
if ( ! versioningApi . isVersion ( depVersion ) ) {
logger . debug (
2023-11-07 15:50:29 +00:00
` Skipping vulnerability lookup for package ${ packageName } due to unsupported version ${ depVersion } ` ,
2023-02-10 10:13:47 +00:00
) ;
2023-04-18 09:07:36 +00:00
return null ;
2023-02-10 10:13:47 +00:00
}
2023-04-18 09:07:36 +00:00
const vulnerabilities : Vulnerability [ ] = [ ] ;
for ( const osvVulnerability of osvVulnerabilities ) {
if ( osvVulnerability . withdrawn ) {
logger . trace (
2023-11-07 15:50:29 +00:00
` Skipping withdrawn vulnerability ${ osvVulnerability . id } ` ,
2023-04-18 09:07:36 +00:00
) ;
2023-04-03 04:39:48 +00:00
continue ;
}
2023-04-18 09:07:36 +00:00
for ( const affected of osvVulnerability . affected ? ? [ ] ) {
2023-02-10 10:13:47 +00:00
const isVulnerable = this . isPackageVulnerable (
ecosystem ,
packageName ,
depVersion ,
affected ,
2023-11-07 15:50:29 +00:00
versioningApi ,
2023-02-10 10:13:47 +00:00
) ;
if ( ! isVulnerable ) {
continue ;
}
logger . debug (
2023-11-07 15:50:29 +00:00
` Vulnerability ${ osvVulnerability . id } affects ${ packageName } ${ depVersion } ` ,
2023-02-10 10:13:47 +00:00
) ;
const fixedVersion = this . getFixedVersion (
ecosystem ,
depVersion ,
affected ,
2023-11-07 15:50:29 +00:00
versioningApi ,
2023-02-10 10:13:47 +00:00
) ;
2023-04-18 09:07:36 +00:00
vulnerabilities . push ( {
2023-02-10 10:13:47 +00:00
packageName ,
2023-04-18 09:07:36 +00:00
vulnerability : osvVulnerability ,
affected ,
2023-02-10 10:13:47 +00:00
depVersion ,
fixedVersion ,
2023-04-18 09:07:36 +00:00
datasource : dep.datasource ! ,
packageFileConfig ,
} ) ;
2023-02-10 10:13:47 +00:00
}
}
2023-04-18 09:07:36 +00:00
return { vulnerabilities , versioningApi } ;
2023-02-10 10:13:47 +00:00
} catch ( err ) {
2023-02-20 09:43:40 +00:00
logger . warn (
2023-02-10 10:13:47 +00:00
{ err } ,
2023-11-07 15:50:29 +00:00
` Error fetching vulnerability information for ${ packageName } ` ,
2023-02-10 10:13:47 +00:00
) ;
2023-04-18 09:07:36 +00:00
return null ;
2023-02-10 10:13:47 +00:00
}
}
private sortByFixedVersion (
packageRules : PackageRule [ ] ,
2023-11-07 15:50:29 +00:00
versioningApi : VersioningApi ,
2023-02-10 10:13:47 +00:00
) : void {
const versionsCleaned : Record < string , string > = { } ;
for ( const rule of packageRules ) {
const version = rule . allowedVersions as string ;
2024-02-16 17:22:37 +00:00
versionsCleaned [ version ] = version . replace ( regEx ( /[(),=> ]+/g ) , '' ) ;
2023-02-10 10:13:47 +00:00
}
packageRules . sort ( ( a , b ) = >
versioningApi . sortVersions (
versionsCleaned [ a . allowedVersions as string ] ,
2023-11-07 15:50:29 +00:00
versionsCleaned [ b . allowedVersions as string ] ,
) ,
2022-04-12 16:13:20 +00:00
) ;
2023-02-10 10:13:47 +00:00
}
// https://ossf.github.io/osv-schema/#affectedrangesevents-fields
private sortEvents (
events : Osv.Event [ ] ,
2023-11-07 15:50:29 +00:00
versioningApi : VersioningApi ,
2023-02-10 10:13:47 +00:00
) : Osv . Event [ ] {
const sortedCopy : Osv.Event [ ] = [ ] ;
let zeroEvent : Osv.Event | null = null ;
for ( const event of events ) {
if ( event . introduced === '0' ) {
zeroEvent = event ;
2023-02-20 09:43:40 +00:00
} else if ( versioningApi . isVersion ( Object . values ( event ) [ 0 ] ) ) {
sortedCopy . push ( event ) ;
} else {
logger . debug ( { event } , 'Skipping OSV event with invalid version' ) ;
2023-02-10 10:13:47 +00:00
}
}
sortedCopy . sort ( ( a , b ) = >
// no pre-processing, as there are only very few values to sort
2023-11-07 15:50:29 +00:00
versioningApi . sortVersions ( Object . values ( a ) [ 0 ] , Object . values ( b ) [ 0 ] ) ,
2023-02-10 10:13:47 +00:00
) ;
if ( zeroEvent ) {
sortedCopy . unshift ( zeroEvent ) ;
}
return sortedCopy ;
}
private isPackageAffected (
ecosystem : Ecosystem ,
packageName : string ,
2023-11-07 15:50:29 +00:00
affected : Osv.Affected ,
2023-02-10 10:13:47 +00:00
) : boolean {
return (
affected . package ? . name === packageName &&
affected . package ? . ecosystem === ecosystem
) ;
}
private includedInVersions (
depVersion : string ,
2023-11-07 15:50:29 +00:00
affected : Osv.Affected ,
2023-02-10 10:13:47 +00:00
) : boolean {
return ! ! affected . versions ? . includes ( depVersion ) ;
}
private includedInRanges (
depVersion : string ,
affected : Osv.Affected ,
2023-11-07 15:50:29 +00:00
versioningApi : VersioningApi ,
2023-02-10 10:13:47 +00:00
) : boolean {
for ( const range of affected . ranges ? ? [ ] ) {
if ( range . type === 'GIT' ) {
continue ;
}
let vulnerable = false ;
for ( const event of this . sortEvents ( range . events , versioningApi ) ) {
if (
is . nonEmptyString ( event . introduced ) &&
( event . introduced === '0' ||
this . isVersionGtOrEq ( depVersion , event . introduced , versioningApi ) )
) {
vulnerable = true ;
} else if (
is . nonEmptyString ( event . fixed ) &&
this . isVersionGtOrEq ( depVersion , event . fixed , versioningApi )
) {
vulnerable = false ;
} else if (
is . nonEmptyString ( event . last_affected ) &&
this . isVersionGt ( depVersion , event . last_affected , versioningApi )
) {
vulnerable = false ;
}
}
if ( vulnerable ) {
return true ;
}
}
return false ;
}
// https://ossf.github.io/osv-schema/#evaluation
private isPackageVulnerable (
ecosystem : Ecosystem ,
packageName : string ,
depVersion : string ,
affected : Osv.Affected ,
2023-11-07 15:50:29 +00:00
versioningApi : VersioningApi ,
2023-02-10 10:13:47 +00:00
) : boolean {
return (
this . isPackageAffected ( ecosystem , packageName , affected ) &&
( this . includedInVersions ( depVersion , affected ) ||
this . includedInRanges ( depVersion , affected , versioningApi ) )
) ;
}
private getFixedVersion (
ecosystem : Ecosystem ,
depVersion : string ,
affected : Osv.Affected ,
2023-11-07 15:50:29 +00:00
versioningApi : VersioningApi ,
2023-02-10 10:13:47 +00:00
) : string | null {
const fixedVersions : string [ ] = [ ] ;
const lastAffectedVersions : string [ ] = [ ] ;
for ( const range of affected . ranges ? ? [ ] ) {
if ( range . type === 'GIT' ) {
continue ;
}
for ( const event of range . events ) {
2023-02-20 09:43:40 +00:00
if (
is . nonEmptyString ( event . fixed ) &&
versioningApi . isVersion ( event . fixed )
) {
2023-02-10 10:13:47 +00:00
fixedVersions . push ( event . fixed ) ;
2023-02-20 09:43:40 +00:00
} else if (
is . nonEmptyString ( event . last_affected ) &&
versioningApi . isVersion ( event . last_affected )
) {
2023-02-10 10:13:47 +00:00
lastAffectedVersions . push ( event . last_affected ) ;
}
}
}
fixedVersions . sort ( ( a , b ) = > versioningApi . sortVersions ( a , b ) ) ;
const fixedVersion = fixedVersions . find ( ( version ) = >
2023-11-07 15:50:29 +00:00
this . isVersionGt ( version , depVersion , versioningApi ) ,
2023-02-10 10:13:47 +00:00
) ;
if ( fixedVersion ) {
2024-06-15 06:27:04 +00:00
return this . getFixedVersionByEcosystem ( fixedVersion , ecosystem ) ;
2023-02-10 10:13:47 +00:00
}
lastAffectedVersions . sort ( ( a , b ) = > versioningApi . sortVersions ( a , b ) ) ;
const lastAffected = lastAffectedVersions . find ( ( version ) = >
2023-11-07 15:50:29 +00:00
this . isVersionGtOrEq ( version , depVersion , versioningApi ) ,
2023-02-10 10:13:47 +00:00
) ;
if ( lastAffected ) {
2024-02-16 17:22:37 +00:00
return this . getLastAffectedByEcosystem ( lastAffected , ecosystem ) ;
2023-02-10 10:13:47 +00:00
}
return null ;
}
2024-06-15 06:27:04 +00:00
private getFixedVersionByEcosystem (
fixedVersion : string ,
ecosystem : Ecosystem ,
) : string {
2024-08-31 12:53:22 +00:00
if ( ecosystem === 'Maven' || ecosystem === 'NuGet' ) {
2024-06-15 06:27:04 +00:00
return ` [ ${ fixedVersion } ,) ` ;
}
// crates.io, Go, Hex, npm, RubyGems, PyPI
return ` >= ${ fixedVersion } ` ;
}
2024-02-16 17:22:37 +00:00
private getLastAffectedByEcosystem (
lastAffected : string ,
ecosystem : Ecosystem ,
) : string {
if ( ecosystem === 'Maven' ) {
return ` ( ${ lastAffected } ,) ` ;
}
// crates.io, Go, Hex, npm, RubyGems, PyPI
return ` > ${ lastAffected } ` ;
}
2023-02-10 10:13:47 +00:00
private isVersionGt (
version : string ,
other : string ,
2023-11-07 15:50:29 +00:00
versioningApi : VersioningApi ,
2023-02-10 10:13:47 +00:00
) : boolean {
return (
versioningApi . isVersion ( version ) &&
versioningApi . isVersion ( other ) &&
versioningApi . isGreaterThan ( version , other )
) ;
}
private isVersionGtOrEq (
version : string ,
other : string ,
2023-11-07 15:50:29 +00:00
versioningApi : VersioningApi ,
2023-02-10 10:13:47 +00:00
) : boolean {
return (
versioningApi . isVersion ( version ) &&
versioningApi . isVersion ( other ) &&
( versioningApi . equals ( version , other ) ||
versioningApi . isGreaterThan ( version , other ) )
2022-04-12 16:13:20 +00:00
) ;
}
2023-04-18 09:07:36 +00:00
private vulnerabilityToPackageRules ( vul : Vulnerability ) : PackageRule | null {
const {
vulnerability ,
affected ,
packageName ,
depVersion ,
fixedVersion ,
datasource ,
packageFileConfig ,
} = vul ;
if ( is . nullOrUndefined ( fixedVersion ) ) {
logger . info (
2023-11-07 15:50:29 +00:00
` No fixed version available for vulnerability ${ vulnerability . id } in ${ packageName } ${ depVersion } ` ,
2023-04-18 09:07:36 +00:00
) ;
return null ;
}
logger . debug (
2023-11-07 15:50:29 +00:00
` Setting allowed version ${ fixedVersion } to fix vulnerability ${ vulnerability . id } in ${ packageName } ${ depVersion } ` ,
2023-04-18 09:07:36 +00:00
) ;
2023-05-11 15:00:20 +00:00
const severityDetails = this . extractSeverityDetails (
vulnerability ,
2023-11-07 15:50:29 +00:00
affected ,
2023-05-11 15:00:20 +00:00
) ;
2023-02-10 10:13:47 +00:00
return {
2023-04-18 09:07:36 +00:00
matchDatasources : [ datasource ] ,
2023-02-10 10:13:47 +00:00
matchPackageNames : [ packageName ] ,
matchCurrentVersion : depVersion ,
allowedVersions : fixedVersion ,
isVulnerabilityAlert : true ,
2023-05-16 15:38:23 +00:00
vulnerabilitySeverity : severityDetails.severityLevel ,
2023-02-13 18:00:36 +00:00
prBodyNotes : this.generatePrBodyNotes ( vulnerability , affected ) ,
2023-02-10 10:13:47 +00:00
force : {
. . . packageFileConfig . vulnerabilityAlerts ,
} ,
} ;
}
private evaluateCvssVector ( vector : string ) : [ string , string ] {
try {
const parsedCvss : CvssScore = parseCvssVector ( vector ) ;
2023-05-16 15:38:23 +00:00
const severityLevel = parsedCvss . cvss3OverallSeverityText ;
2023-02-10 10:13:47 +00:00
return [ parsedCvss . baseScore . toFixed ( 1 ) , severityLevel ] ;
2024-08-14 10:33:02 +00:00
} catch {
2023-02-10 10:13:47 +00:00
logger . debug ( ` Error processing CVSS vector ${ vector } ` ) ;
}
return [ '' , '' ] ;
}
2023-02-13 18:00:36 +00:00
private generatePrBodyNotes (
vulnerability : Osv.Vulnerability ,
2023-11-07 15:50:29 +00:00
affected : Osv.Affected ,
2023-02-13 18:00:36 +00:00
) : string [ ] {
2023-02-10 10:13:47 +00:00
let aliases = [ vulnerability . id ] . concat ( vulnerability . aliases ? ? [ ] ) . sort ( ) ;
aliases = aliases . map ( ( id ) = > {
if ( id . startsWith ( 'CVE-' ) ) {
return ` [ ${ id } ](https://nvd.nist.gov/vuln/detail/ ${ id } ) ` ;
} else if ( id . startsWith ( 'GHSA-' ) ) {
return ` [ ${ id } ](https://github.com/advisories/ ${ id } ) ` ;
} else if ( id . startsWith ( 'GO-' ) ) {
return ` [ ${ id } ](https://pkg.go.dev/vuln/ ${ id } ) ` ;
} else if ( id . startsWith ( 'RUSTSEC-' ) ) {
return ` [ ${ id } ](https://rustsec.org/advisories/ ${ id } .html) ` ;
}
return id ;
} ) ;
let content = '\n\n---\n\n### ' ;
content += vulnerability . summary ? ` ${ vulnerability . summary } \ n ` : '' ;
content += ` ${ aliases . join ( ' / ' ) } \ n ` ;
content += ` \ n<details> \ n<summary>More information</summary> \ n ` ;
2023-02-13 18:00:36 +00:00
const details = vulnerability . details ? . replace (
regEx ( /^#{1,4} /gm ) ,
2023-11-07 15:50:29 +00:00
'##### ' ,
2023-02-13 18:00:36 +00:00
) ;
content += ` #### Details \ n ${ details ? ? 'No details.' } \ n ` ;
content += '#### Severity\n' ;
2023-05-11 15:00:20 +00:00
const severityDetails = this . extractSeverityDetails (
vulnerability ,
2023-11-07 15:50:29 +00:00
affected ,
2023-05-11 15:00:20 +00:00
) ;
if ( severityDetails . cvssVector ) {
content += ` - CVSS Score: ${ severityDetails . score } \ n ` ;
content += ` - Vector String: \` ${ severityDetails . cvssVector } \` \ n ` ;
2023-02-10 10:13:47 +00:00
} else {
2023-05-16 15:38:23 +00:00
content += ` ${ titleCase ( severityDetails . severityLevel ) } \ n ` ;
2023-02-10 10:13:47 +00:00
}
2023-02-13 18:00:36 +00:00
content += ` \ n#### References \ n ${
2023-02-10 10:13:47 +00:00
vulnerability . references
? . map ( ( ref ) = > {
return ` - [ ${ ref . url } ]( ${ ref . url } ) ` ;
2022-04-12 16:13:20 +00:00
} )
2023-02-10 10:13:47 +00:00
. join ( '\n' ) ? ? 'No references.'
} ` ;
let attribution = '' ;
if ( vulnerability . id . startsWith ( 'GHSA-' ) ) {
attribution = ` and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)) ` ;
} else if ( vulnerability . id . startsWith ( 'GO-' ) ) {
attribution = ` and the [Go Vulnerability Database](https://github.com/golang/vulndb) ([CC-BY 4.0](https://github.com/golang/vulndb#license)) ` ;
} else if ( vulnerability . id . startsWith ( 'PYSEC-' ) ) {
attribution = ` and the [PyPI Advisory Database](https://github.com/pypa/advisory-database) ([CC-BY 4.0](https://github.com/pypa/advisory-database/blob/main/LICENSE)) ` ;
} else if ( vulnerability . id . startsWith ( 'RUSTSEC-' ) ) {
attribution = ` and the [Rust Advisory Database](https://github.com/RustSec/advisory-db) ([CC0 1.0](https://github.com/rustsec/advisory-db/blob/main/LICENSE.txt)) ` ;
}
content += ` \ n \ nThis data is provided by [OSV](https://osv.dev/vulnerability/ ${ vulnerability . id } ) ${ attribution } . \ n ` ;
content += ` </details> ` ;
return [ sanitizeMarkdown ( content ) ] ;
2022-04-12 16:13:20 +00:00
}
2023-05-11 15:00:20 +00:00
private extractSeverityDetails (
vulnerability : Osv.Vulnerability ,
2023-11-07 15:50:29 +00:00
affected : Osv.Affected ,
2023-05-11 15:00:20 +00:00
) : SeverityDetails {
2023-05-16 15:38:23 +00:00
let severityLevel = 'UNKNOWN' ;
2023-05-11 15:00:20 +00:00
let score = 'Unknown' ;
const cvssVector =
vulnerability . severity ? . find ( ( e ) = > e . type === 'CVSS_V3' ) ? . score ? ?
vulnerability . severity ? . [ 0 ] ? . score ? ?
( affected . database_specific ? . cvss as string ) ; // RUSTSEC
if ( cvssVector ) {
const [ baseScore , severity ] = this . evaluateCvssVector ( cvssVector ) ;
2023-05-16 15:38:23 +00:00
severityLevel = severity . toUpperCase ( ) ;
score = baseScore
? ` ${ baseScore } / 10 ( ${ titleCase ( severityLevel ) } ) `
: 'Unknown' ;
2023-05-11 15:00:20 +00:00
} else if (
vulnerability . id . startsWith ( 'GHSA-' ) &&
vulnerability . database_specific ? . severity
) {
const severity = vulnerability . database_specific . severity as string ;
2023-05-16 15:38:23 +00:00
severityLevel = severity . toUpperCase ( ) ;
2023-05-11 15:00:20 +00:00
}
return {
cvssVector ,
score ,
severityLevel ,
} ;
}
2022-04-12 16:13:20 +00:00
}