From 575a75b628bbb2609da54eddaacf6f581a370b1d Mon Sep 17 00:00:00 2001 From: Sebastian Schuberth Date: Fri, 6 Sep 2024 11:55:19 +0200 Subject: [PATCH] chore(osv): Improve mapping from OSV to ORT vulnerability references The original comment and implementation was misleadning by saying "only one representation is actually possible currently" as it left unlcear whether that refer to a limitation in the OSV or ORT data model. In any case, it is possible to maintain all OSV `severity` data by creating ORT vulnerability references for all combinations of severities and references. Do that to not lose any information about possible alternative severity type / score pairs. Signed-off-by: Sebastian Schuberth --- plugins/advisors/osv/src/main/kotlin/Osv.kt | 65 +++++++++++---------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/plugins/advisors/osv/src/main/kotlin/Osv.kt b/plugins/advisors/osv/src/main/kotlin/Osv.kt index d3508d9463bf3..e2427c09bb95b 100644 --- a/plugins/advisors/osv/src/main/kotlin/Osv.kt +++ b/plugins/advisors/osv/src/main/kotlin/Osv.kt @@ -182,43 +182,48 @@ private fun createRequest(pkg: Package): VulnerabilitiesForPackageRequest? { } private fun Vulnerability.toOrtVulnerability(): org.ossreviewtoolkit.model.vulnerabilities.Vulnerability { - // OSV uses a list in order to support multiple representations of the severity using different scoring systems. - // However, only one representation is actually possible currently, because the enum 'Severity.Type' contains just a - // single element / scoring system. So, picking first severity is fine, in particular because ORT only supports a - // single severity representation. - val (scoringSystem, severity) = severity.firstOrNull()?.let { + // The ORT and OSV vulnerability data models are different in that ORT uses a severity for each reference (assuming + // that different references could use different severities), whereas OSV manages severities and references on the + // same level, which means it is not possible to identify whether a reference belongs to a specific severity. + // To map between these different model, simply use the "cartesian product" to create an ORT reference for each + // combination of an OSV severity and reference. + val ortReferences = mutableListOf() + + severity.map { it.type.name to it.score - } ?: (null to null) - - val references = references.mapNotNull { reference -> - val url = reference.url.trim().let { if (it.startsWith("://")) "https$it" else it } - - url.toUri().onFailure { - logger.debug { "Could not parse reference URL for vulnerability '$id': ${it.collectMessages()}." } - }.map { - // Use the 'severity' property of the unspecified 'databaseSpecific' object. - // See also https://github.com/google/osv.dev/issues/484. - val specificSeverity = databaseSpecific?.get("severity") - - // Note that the CVSS Calculator does not support CVSS 4.0 yet: - // https://github.com/stevespringett/cvss-calculator/issues/78 - val baseScore = runCatching { - Cvss.fromVector(severity)?.calculateScore()?.baseScore?.toFloat() - }.onFailure { - logger.debug { "Unable to parse CVSS vector '$severity': ${it.collectMessages()}." } + }.ifEmpty { + listOf(null to null) + }.forEach { (scoringSystem, severity) -> + references.mapNotNullTo(ortReferences) { reference -> + val url = reference.url.trim().let { if (it.startsWith("://")) "https$it" else it } + + url.toUri().onFailure { + logger.debug { "Could not parse reference URL for vulnerability '$id': ${it.collectMessages()}." } + }.map { + // Use the 'severity' property of the unspecified 'databaseSpecific' object. + // See also https://github.com/google/osv.dev/issues/484. + val specificSeverity = databaseSpecific?.get("severity") + + // Note that the CVSS Calculator does not support CVSS 4.0 yet: + // https://github.com/stevespringett/cvss-calculator/issues/78 + val baseScore = runCatching { + Cvss.fromVector(severity)?.calculateScore()?.baseScore?.toFloat() + }.onFailure { + logger.debug { "Unable to parse CVSS vector '$severity': ${it.collectMessages()}." } + }.getOrNull() + + val severityRating = (specificSeverity as? JsonPrimitive)?.contentOrNull + ?: VulnerabilityReference.getSeverityRating(scoringSystem, baseScore) + + VulnerabilityReference(it, scoringSystem, severityRating, baseScore, severity) }.getOrNull() - - val severityRating = (specificSeverity as? JsonPrimitive)?.contentOrNull - ?: VulnerabilityReference.getSeverityRating(scoringSystem, baseScore) - - VulnerabilityReference(it, scoringSystem, severityRating, baseScore, severity) - }.getOrNull() + } } return org.ossreviewtoolkit.model.vulnerabilities.Vulnerability( id = id, summary = summary, description = details, - references = references + references = ortReferences ) }