DependencyTrack / DependencyTrack/dependency-track

NVD mirror ignores CVSS metric type and picks CNA/ADP rating over NIST Primary rating

Open
#7,192 1 comment 0 reactions 0 assignees View on GitHub
defect in triage
Dominant language
Java
Stars
4.2k
Forks
811
Avg merge
8h 39m
Merged PRs (30d)
237

Description

### Current Behavior

Since upgrading from Dependency-Track 4.x to 5.0.3, CVEs that carry multiple CVSS v3.1 metrics in the NVD (NIST Primary + CNA/ADP Secondary ratings) frequently end up with a Secondary rating stored in Dependency-Track instead of NIST's Primary rating. Which rating wins depends on the order of the `cvssMetricV31[]` array returned by the NVD API, so it appears random across CVEs.

### Example 1: CVE-2023-2976 (wrong)

NVD API `cvssMetricV31[]` order: `Google CNA (5.5)`, `nvd@nist.gov Primary (7.1)`, `CISA-ADP (5.5)`

- NIST Primary: 7.1 HIGH, `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N`
- Google CNA (Secondary): 5.5 MEDIUM, `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N`
- CISA-ADP (Secondary): 5.5 MEDIUM, same vector as Google

Dependency-Track stores: **5.5 / `.../C:H/I:N/A:N`** (the Google CNA rating), attributed to source NVD.

### Example 2: CVE-2026-42581 (correct, but only by coincidence)

NVD API `cvssMetricV31[]` order: `security-advisories@github.com (5.8)`, `nvd@nist.gov Primary (9.8)`, `redhat-SADP (7.2)`

Dependency-Track stores: **9.8** (correct).

## Root Cause

`vuln-data-source/nvd/src/main/java/org/dependencytrack/vulndatasource/nvd/ModelConverter.java` (in 5.0.x: `NvdToCyclonedxParser` in the mirror-service) converts every entry of `cvssMetricV31[]` into a CycloneDX rating and assigns its source via `determineMetricSource()`:

```java
if ("nvd@nist.gov".equals(source)) {
return SOURCE_NVD;
} else if ("security-advisories@github.com".equals(source)) {
return SOURCE_GITHUB;
}
// Fall back to NVD if we don't recognize the source.
return SOURCE_NVD;
```

Every unrecognized source (Google, CISA-ADP, redhat-SADP, VulnCheck, any other CNA) is labelled as source `NVD`. The metric `type` (`Primary` / `Secondary`) is not considered at all.

`BovModelConverter.compareRatings()` (5.0.x: `ModelConverterCdxToVuln.compareRatings()`) then prefers ratings whose source equals the vulnerability source (`NVD`). Since all non-GitHub ratings are now labelled `NVD` and share the same method, they tie, the stable sort keeps NVD API order, and the first entry wins.

This explains both examples: for CVE-2023-2976 the Google CNA rating is first in the array and is mislabelled as NVD, so it beats NIST. For CVE-2026-42581 the first entry is GitHub, which is recognized and demoted, so NIST's rating is the first `NVD`-labelled one.

The behaviour regressed compared to 4.x. In `dependency-track` (`4.14.x`), `parser/nvd/api20/ModelConverter.java` sorts the metrics by type before picking the first one:

```java
metrics.getCvssMetricV31().sort(comparingInt(metric -> metric.getType().ordinal()));
```

That sort was not carried over to the v5 converter.

## Proposed Fix

Either (or both):

1. Sort `cvssMetricV2/V30/V31/V40` by `type` (Primary first) before converting, as 4.x does, so the Primary rating is first among ties.
2. Only map `nvd@nist.gov` to source `NVD` in `determineMetricSource()`, and give all other sources a distinct source name (e.g. the raw source string), so `compareRatings()` can demote them. Optionally carry the `type` into the rating so the comparator can prefer Primary explicitly.

Longer term it would be useful to persist all ratings per source (CycloneDX `ratings[]`), but that is a larger change.

## Environment

- Dependency-Track version: 5.0.3 (hyades mirror-service v0.6.0)
- Vulnerability sources: NVD (REST API mirror), Trivy analyzer
- The code path is unchanged on `main` (5.7.0-alpha), so newer versions are affected as well.

### Steps to Reproduce

1. Run Dependency-Track 5.0.3 with NVD mirroring via the NVD REST API enabled.
2. Let the mirror import CVE-2023-2976.
3. `GET /api/v1/vulnerability/source/NVD/vuln/CVE-2023-2976` and compare `cvssV3Vector` / `cvssV3BaseScore` with the NIST Primary metric from `https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=CVE-2023-2976`.

### Expected Behavior

When the NVD provides a `Primary` (NIST) CVSS metric, that metric should be the one stored in Dependency-Track. Secondary ratings should only be used as a fallback when no Primary rating exists.

### Dependency-Track Version

5.x

### Browser

Google Chrome

### Checklist

- [x] I have read and understand the [contributing guidelines](https://github.com/DependencyTrack/dependency-track/blob/main/CONTRIBUTING.md#filing-issues)
- [x] I have checked the [existing issues](https://github.com/DependencyTrack/dependency-track/issues) for whether this defect was already reported

Contributor guide

Open the contributing guide

Research direction

Start with vuln-data-source/nvd/src/main/java/org/dependencytrack/vulndatasource/nvd/ModelConverter.java and trace how cvssMetricV2/V30/V31/V40 entries are converted and sourced. Compare the v5 behavior with the 4.x sorting in parser/nvd/api20/ModelConverter.java, then reproduce CVE-2023-2976 through the NVD API path. Done means a Primary rating is selected before Secondary ratings, with Secondary used only when no Primary exists.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
backend, security
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
75/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.