Skip to content

ROX-28353: use NVD 2.0 JSON feeds #2015

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ jobs:
contains(github.event.pull_request.labels.*.name, 'generate-dumps-on-pr')
env:
NVD_API_KEY: ${{ secrets.NVD_API_KEY }}
ROX_LEGACY_NVD_LOADER: true
ROX_NVD_FEED_LOADER: true
runs-on: ubuntu-latest
needs:
- pre-build-updater
Expand Down
35 changes: 13 additions & 22 deletions e2etests/vuln_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,23 +142,16 @@ func TestStackroxVulnImages(t *testing.T) {
image: "quay.io/rhacs-eng/qa:rails-cve-2016-2098",
expectedFeatures: []feature{
{"rails", "4.2.5.1", []expectedVuln{
{name: "CVE-2016-2098"},
{name: "CVE-2016-6316"},
{name: "CVE-2016-6317"},
{name: "CVE-2018-16476", fixedBy: "4.2.11"},
{name: "CVE-2019-5418", fixedBy: "4.2.11.1"},
{name: "CVE-2019-5419", fixedBy: "4.2.11.1"},
{name: "CVE-2019-5420", fixedBy: "5.2.2.1"},
},
},
},
unexpectedVulns: []feature{
{"rails", "4.2.5.1", []expectedVuln{
// These three vulns should exist, but NVD set these to deferred.
// Placing them here until they are no longer deferred.
{name: "CVE-2016-2098"},
{name: "CVE-2016-6316"},
{name: "CVE-2016-6317"},
},
},
},
},
{
// docker.io/1and1internet/ubuntu-16-customerssh:latest
Expand All @@ -184,6 +177,10 @@ func TestStackroxVulnImages(t *testing.T) {
{name: "CVE-2019-10086", fixedBy: ""},
},
},
{"commons_fileupload", "1.3.2", []expectedVuln{
{name: "CVE-2016-1000031", fixedBy: ""},
},
},
{"guava", "18.0", []expectedVuln{
{name: "CVE-2018-10237", fixedBy: "24.1.1"},
},
Expand All @@ -197,12 +194,6 @@ func TestStackroxVulnImages(t *testing.T) {
{name: "CVE-2015-2512"},
},
},
{"commons_fileupload", "1.3.2", []expectedVuln{
// This vuln should exist, but NVD set it to deferred.
// Placing it here until they are no longer deferred.
{name: "CVE-2016-1000031", fixedBy: ""},
},
},
},
},
{
Expand All @@ -218,17 +209,17 @@ func TestStackroxVulnImages(t *testing.T) {
{
// docker.io/library/cassandra:latest
image: "quay.io/rhacs-eng/qa:cassandra",
expectedFeatures: []feature{
{"logback", "1.1.3", []expectedVuln{
{name: "CVE-2017-5929", fixedBy: ""},
},
},
},
unexpectedVulns: []feature{
{"slingshot", "0.10.3", []expectedVuln{
{name: "CVE-2015-5711"},
},
},
{"logback", "1.1.3", []expectedVuln{
// This vuln should exist, but NVD set it to deferred.
// Placing it here until they are no longer deferred.
{name: "CVE-2017-5929", fixedBy: ""},
},
},
},
},
{
Expand Down
6 changes: 3 additions & 3 deletions pkg/env/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ var (
// MaxGrpcConcurrentStreams configures the maximum number of HTTP/2 streams to use with gRPC
MaxGrpcConcurrentStreams = RegisterIntegerSetting("ROX_GRPC_MAX_CONCURRENT_STREAMS", DefaultMaxGrpcConcurrentStreams)

// LegacyNVDLoader when true will cause the loader to pull NVD data using
// the NVD Legacy Data Feeds, if false will pull from the NVD 2.0 API.
LegacyNVDLoader = RegisterBooleanSetting("ROX_LEGACY_NVD_LOADER", false)
// NVDFeedLoader when true will cause the loader to pull NVD data using
// the NVD 2.0 Data Feeds. If false, the loader will pull from the NVD 2.0 API.
NVDFeedLoader = RegisterBooleanSetting("ROX_NVD_FEED_LOADER", false)

// RHLineage when true will cause all parent layers (a.k.a lineage) to be considered when
// storing scan results for RHEL image layers.
Expand Down
25 changes: 17 additions & 8 deletions pkg/vulnloader/nvdloader/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const (
jsonTimeFormat = "2006-01-02T15:04Z"
)

func toJSON(vulns []*apischema.CVEAPIJSON20DefCVEItem) ([]*jsonschema.NVDCVEFeedJSON10DefCVEItem, error) {
func toJSON10(vulns []*apischema.CVEAPIJSON20DefCVEItem) ([]*jsonschema.NVDCVEFeedJSON10DefCVEItem, error) {
if vulns == nil {
return nil, nil
}
Expand All @@ -31,6 +31,11 @@ func toJSON(vulns []*apischema.CVEAPIJSON20DefCVEItem) ([]*jsonschema.NVDCVEFeed
continue
}

// Ignore rejected vulnerabilities.
if strings.EqualFold(vuln.CVE.VulnStatus, "Rejected") {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dcaravel @BradLugo I added this conditional after noticing the 2.0 feeds include rejected vulns. They will lack a lot of fields, but just to be safe, I opted to explicitly ignore it

Copy link
Contributor

@BradLugo BradLugo Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I vaguely recall there being some contention with us supporting rejected vulns in the past, and ignoring them as the solution, so your change tracks 👍
Is this covered by a test? If not, should it be?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't add any because they didn't exist before, but that's a bad reason to not add tests. Just added one which covers this rejected case and some basic stuff

continue
}

cve := vuln.CVE

modifiedTime, err := toTime(cve.LastModified)
Expand Down Expand Up @@ -152,14 +157,18 @@ func toBaseMetricV2(metrics []*apischema.CVEAPIJSON20CVSSV2) *jsonschema.NVDCVEF
}

func toBaseMetricV3(metrics30 []*apischema.CVEAPIJSON20CVSSV30, metrics31 []*apischema.CVEAPIJSON20CVSSV31) *jsonschema.NVDCVEFeedJSON10DefImpactBaseMetricV3 {
switch {
case len(metrics31) != 0:
return toBaseMetricV31(metrics31)
case len(metrics30) != 0:
return toBaseMetricV30(metrics30)
default:
return nil
// Prefer CVSS 3.1.
baseMetric := toBaseMetricV31(metrics31)
if baseMetric != nil {
return baseMetric
}

baseMetric = toBaseMetricV30(metrics30)
if baseMetric != nil {
return baseMetric
}

return nil
}

func toBaseMetricV31(metrics []*apischema.CVEAPIJSON20CVSSV31) *jsonschema.NVDCVEFeedJSON10DefImpactBaseMetricV3 {
Expand Down
130 changes: 130 additions & 0 deletions pkg/vulnloader/nvdloader/convert_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package nvdloader

import (
"os"
"testing"

jsonschema "github.com/facebookincubator/nvdtools/cvefeed/nvd/schema"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestToJSON10(t *testing.T) {
f, err := os.Open("testdata/nvdcve-2.0-2025.json")
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, f.Close())
})

cveAPI, err := parseReader(f)
require.NoError(t, err)

cveFeed, err := toJSON10(cveAPI.Vulnerabilities)
assert.NoError(t, err)

expected := []*jsonschema.NVDCVEFeedJSON10DefCVEItem{
{
CVE: &jsonschema.CVEJSON40{
CVEDataMeta: &jsonschema.CVEJSON40CVEDataMeta{
ID: "CVE-2025-0168",
},
Description: &jsonschema.CVEJSON40Description{
DescriptionData: []*jsonschema.CVEJSON40LangString{
{
Lang: "en",
Value: "A vulnerability classified as critical has been found in code-projects Job Recruitment 1.0. This affects an unknown part of the file /_parse/_feedback_system.php. The manipulation of the argument person leads to sql injection. It is possible to initiate the attack remotely. The exploit has been disclosed to the public and may be used.",
},
},
},
},
Configurations: &jsonschema.NVDCVEFeedJSON10DefConfigurations{
Nodes: []*jsonschema.NVDCVEFeedJSON10DefNode{
{
CPEMatch: []*jsonschema.NVDCVEFeedJSON10DefCPEMatch{
{
Cpe23Uri: "cpe:2.3:a:anisha:job_recruitment:1.0:*:*:*:*:*:*:*",
Vulnerable: true,
},
},
Operator: "OR",
},
},
},
Impact: &jsonschema.NVDCVEFeedJSON10DefImpact{
BaseMetricV3: &jsonschema.NVDCVEFeedJSON10DefImpactBaseMetricV3{
CVSSV3: &jsonschema.CVSSV30{
AttackComplexity: "LOW",
AttackVector: "NETWORK",
AvailabilityImpact: "NONE",
BaseScore: 7.5,
BaseSeverity: "HIGH",
ConfidentialityImpact: "HIGH",
IntegrityImpact: "NONE",
PrivilegesRequired: "NONE",
Scope: "UNCHANGED",
UserInteraction: "NONE",
VectorString: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
Version: "3.1",
},
ExploitabilityScore: 3.9,
ImpactScore: 3.6,
},
},
LastModifiedDate: "2025-02-25T21:26Z",
PublishedDate: "2025-01-01T14:15Z",
},
{
CVE: &jsonschema.CVEJSON40{
CVEDataMeta: &jsonschema.CVEJSON40CVEDataMeta{
ID: "CVE-2025-22144",
},
Description: &jsonschema.CVEJSON40Description{
DescriptionData: []*jsonschema.CVEJSON40LangString{
{
Lang: "en",
Value: "NamelessMC is a free, easy to use & powerful website software for Minecraft servers. A user with admincp.core.emails or admincp.users.edit permissions can validate users and an attacker can reset their password. When the account is successfully approved by email the reset code is NULL, but when the account is manually validated by a user with admincp.core.emails or admincp.users.edit permissions then the reset_code will no longer be NULL but empty. An attacker can request http://localhost/nameless/index.php?route=/forgot_password/&c= and reset the password. As a result an attacker may compromise another users password and take over their account. This issue has been addressed in release version 2.1.3 and all users are advised to upgrade. There are no known workarounds for this vulnerability.",
},
},
},
},
Configurations: &jsonschema.NVDCVEFeedJSON10DefConfigurations{
Nodes: []*jsonschema.NVDCVEFeedJSON10DefNode{
{
CPEMatch: []*jsonschema.NVDCVEFeedJSON10DefCPEMatch{
{
Cpe23Uri: "cpe:2.3:a:namelessmc:nameless:*:*:*:*:*:*:*:*",
VersionEndExcluding: "2.1.3",
Vulnerable: true,
},
},
Operator: "OR",
},
},
},
Impact: &jsonschema.NVDCVEFeedJSON10DefImpact{
BaseMetricV3: &jsonschema.NVDCVEFeedJSON10DefImpactBaseMetricV3{
CVSSV3: &jsonschema.CVSSV30{
AttackComplexity: "LOW",
AttackVector: "NETWORK",
AvailabilityImpact: "HIGH",
BaseScore: 9.8,
BaseSeverity: "CRITICAL",
ConfidentialityImpact: "HIGH",
IntegrityImpact: "HIGH",
PrivilegesRequired: "NONE",
Scope: "UNCHANGED",
UserInteraction: "NONE",
VectorString: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
Version: "3.1",
},
ExploitabilityScore: 3.9,
ImpactScore: 5.9,
},
},
LastModifiedDate: "2025-05-13T15:42Z",
PublishedDate: "2025-01-13T20:15Z",
},
}

assert.ElementsMatch(t, expected, cveFeed)
}
Loading