Skip to content

Commit a9db6a9

Browse files
authored
Merge pull request #104 from RedHatProductSecurity/add-x-generator-value
Add default x_generator value into CVE records
2 parents 7641978 + f6ac1fa commit a9db6a9

File tree

3 files changed

+76
-0
lines changed

3 files changed

+76
-0
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ Additional options that have an accompanying environment variable include:
117117
before a request is sent to CVE Services. Truthy values for the environment variable are:
118118
`1`, `t`, `yes`.
119119

120+
* `CVE_GENERATOR`: override the default value of `cvelib x.y.z` that is injected into the
121+
`x_generator` field of every published or updated CVE record. If you'd prefer to omit setting
122+
the field entirely, set the value to `-` (dash character; `export CVE_GENERATOR=-`). Existing
123+
`x_generator` values are not overwritten.
124+
120125
### Command Autocompletion
121126

122127
Autocompletion of subcommands is supported for the following shells:

cvelib/cve_api.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import os
23
from datetime import datetime
34
from enum import Enum
45
from pathlib import Path
@@ -8,6 +9,8 @@
89
import requests
910
from jsonschema import Draft7Validator
1011

12+
from cvelib import __version__
13+
1114
SCHEMA_DIR = Path(__file__).parent / "schemas"
1215

1316

@@ -178,10 +181,33 @@ def _add_provider_metadata(self, cve_json: dict) -> dict:
178181
cve_json["providerMetadata"] = {"orgId": org_id}
179182
return cve_json
180183

184+
@staticmethod
185+
def _add_generator(cve_json: dict) -> dict:
186+
"""Add the x_generator field to the CVE record if defined.
187+
188+
Determine and inject the value of the x_generator field into all created/updated CVE
189+
records to identify this library as the tool that was used to do so. Override this value
190+
via the CVE_GENERATOR env var; set this env var to the value of "-" to omit adding this
191+
field into the CVE record. Existing x_generator values are not overridden.
192+
"""
193+
generator = os.getenv("CVE_GENERATOR")
194+
195+
# Skip adding the x_generator field if undesired or already present
196+
if generator == "-" or "x_generator" in cve_json:
197+
return cve_json
198+
199+
# If no custom value is specified, use cvelib
200+
if generator is None:
201+
generator = f"cvelib {__version__}"
202+
203+
cve_json["x_generator"] = {"engine": generator}
204+
return cve_json
205+
181206
def publish(self, cve_id: str, cve_json: dict, validate: bool = True) -> dict:
182207
"""Publish a CVE from a JSON object representing the CNA container data."""
183208
cve_json = self._extract_cna_container(cve_json)
184209
cve_json = self._add_provider_metadata(cve_json)
210+
cve_json = self._add_generator(cve_json)
185211
if validate:
186212
CveRecord.validate(cve_json, CveRecord.Schemas.CNA_PUBLISHED)
187213

@@ -194,6 +220,7 @@ def update_published(self, cve_id: str, cve_json: dict, validate: bool = True) -
194220
"""Update a published CVE record from a JSON object representing the CNA container data."""
195221
cve_json = self._extract_cna_container(cve_json)
196222
cve_json = self._add_provider_metadata(cve_json)
223+
cve_json = self._add_generator(cve_json)
197224
if validate:
198225
CveRecord.validate(cve_json, CveRecord.Schemas.CNA_PUBLISHED)
199226

@@ -206,6 +233,7 @@ def publish_adp(self, cve_id: str, cve_json: dict, validate: bool = True) -> dic
206233
"""Add or update an ADP container from a JSON object representing the ADP container data."""
207234
cve_json = self._extract_adp_container(cve_json)
208235
cve_json = self._add_provider_metadata(cve_json)
236+
cve_json = self._add_generator(cve_json)
209237
if validate:
210238
CveRecord.validate(cve_json, CveRecord.Schemas.ADP)
211239

@@ -218,6 +246,7 @@ def reject(self, cve_id: str, cve_json: dict, validate: bool = True) -> dict:
218246
"""Reject a CVE from a JSON object representing the CNA container data."""
219247
cve_json = self._extract_cna_container(cve_json)
220248
cve_json = self._add_provider_metadata(cve_json)
249+
cve_json = self._add_generator(cve_json)
221250
if validate:
222251
CveRecord.validate(cve_json, CveRecord.Schemas.CNA_REJECTED)
223252

@@ -230,6 +259,7 @@ def update_rejected(self, cve_id: str, cve_json: dict, validate: bool = True) ->
230259
"""Update a rejected CVE record from a JSON object representing the CNA container data."""
231260
cve_json = self._extract_cna_container(cve_json)
232261
cve_json = self._add_provider_metadata(cve_json)
262+
cve_json = self._add_generator(cve_json)
233263
if validate:
234264
CveRecord.validate(cve_json, CveRecord.Schemas.CNA_REJECTED)
235265

tests/test_cve_api.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import json
2+
import os
23
import pickle
34
from pathlib import Path
5+
from unittest import mock
46

57
import pytest
68

9+
from cvelib import __version__
710
from cvelib.cve_api import CveApi, CveRecord, CveRecordValidationError
811

912

@@ -53,3 +56,41 @@ def test_cve_record_validation_error_is_picklable():
5356
# Errors do not survive pickling because they include jsonschema-specific objects that
5457
# are not picklable.
5558
assert unpickled_exc.errors is None
59+
60+
61+
class TestGeneratorMetadata:
62+
63+
@pytest.fixture
64+
def sample_cve_json(self):
65+
with open(Path(__file__).parent / "data/CVEv5_basic-example.json") as record_file:
66+
return json.load(record_file)
67+
68+
def test_add_generator_default(self, sample_cve_json):
69+
cve_json = sample_cve_json.copy()
70+
result = CveApi._add_generator(cve_json)
71+
assert "x_generator" in result
72+
assert result["x_generator"]["engine"] == f"cvelib {__version__}"
73+
74+
def test_add_generator_custom(self, sample_cve_json):
75+
custom_generator = "awesome_cve_cli 1.2.3"
76+
with mock.patch.dict(os.environ, {"CVE_GENERATOR": custom_generator}):
77+
cve_json = sample_cve_json.copy()
78+
result = CveApi._add_generator(cve_json)
79+
assert "x_generator" in result
80+
assert result["x_generator"]["engine"] == custom_generator
81+
82+
def test_generator_not_added(self, sample_cve_json):
83+
with mock.patch.dict(os.environ, {"CVE_GENERATOR": "-"}):
84+
cve_json = sample_cve_json.copy()
85+
result = CveApi._add_generator(cve_json)
86+
assert "x_generator" not in result
87+
88+
def test_generator_not_overridden(self, sample_cve_json):
89+
cve_json = sample_cve_json.copy()
90+
original_value = {"engine": "cve_cli 9.8.7"}
91+
cve_json["x_generator"] = original_value
92+
93+
with mock.patch.dict(os.environ, {"CVE_GENERATOR": "should_not_be_used"}):
94+
result = CveApi._add_generator(cve_json)
95+
assert "x_generator" in result
96+
assert result["x_generator"] == original_value

0 commit comments

Comments
 (0)