Skip to content

Commit 2ca5acf

Browse files
raboofterriko
andauthored
chore: align vex output with CycloneDX schema (#2337)
Generate vex output that matches the bom-1.4.schema.json from CycloneDX. Since vex formats are still in flux we can deviate from the CycloneDX schema if needed, but so far the differences seemed mostly accidental and it seems nice to be consistent. Co-authored-by: Terri Oda <[email protected]>
1 parent 45eb708 commit 2ca5acf

File tree

5 files changed

+171
-38
lines changed

5 files changed

+171
-38
lines changed

cve_bin_tool/input_engine.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,14 @@ def validate_product(self, product: str) -> bool:
8282
return re.search(cpe_regex, product) is not None
8383

8484
def input_vex(self) -> None:
85+
def strip_remark(detail) -> str:
86+
detail = re.sub("^" + Remarks.NewFound.name + "(: )?", "", detail)
87+
detail = re.sub("^" + Remarks.Unexplored.name + "(: )?", "", detail)
88+
detail = re.sub("^" + Remarks.Confirmed.name + "(: )?", "", detail)
89+
detail = re.sub("^" + Remarks.Mitigated.name + "(: )?", "", detail)
90+
detail = re.sub("^" + Remarks.Ignored.name + "(: )?", "", detail)
91+
return detail
92+
8593
analysis_state = {
8694
"under_review": Remarks.Unexplored,
8795
"in_triage": Remarks.Unexplored,
@@ -98,11 +106,11 @@ def input_vex(self) -> None:
98106
state = Remarks.Unexplored
99107
if analysis_status in analysis_state:
100108
state = analysis_state[analysis_status]
101-
comments = vulnerability["analysis"]["detail"]
109+
comments = strip_remark(vulnerability["analysis"]["detail"])
102110
severity = None
103111
if "ratings" in vulnerability:
104112
for rating in vulnerability["ratings"]:
105-
severity = rating["severity"]
113+
severity = rating["severity"].upper()
106114
for affect in vulnerability["affects"]:
107115
ref = affect["ref"]
108116
version = None

cve_bin_tool/output_engine/__init__.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -631,18 +631,18 @@ def output_cves(self, outfile, output_type="console"):
631631

632632
def generate_vex(self, all_cve_data: dict[ProductInfo, CVEData], filename: str):
633633
analysis_state = {
634-
Remarks.NewFound: "under_review",
635-
Remarks.Unexplored: "under_review",
634+
Remarks.NewFound: "in_triage",
635+
Remarks.Unexplored: "in_triage",
636636
Remarks.Confirmed: "exploitable",
637637
Remarks.Mitigated: "not_affected",
638638
Remarks.Ignored: "not_affected",
639639
}
640640
response_state = {
641-
Remarks.NewFound: "Outstanding",
642-
Remarks.Unexplored: "Not defined",
643-
Remarks.Confirmed: "Upgrade required",
644-
Remarks.Mitigated: "Resolved",
645-
Remarks.Ignored: "No impact",
641+
Remarks.NewFound: [],
642+
Remarks.Unexplored: [],
643+
Remarks.Confirmed: ["update"],
644+
Remarks.Mitigated: [],
645+
Remarks.Ignored: [],
646646
}
647647
# Generate VEX file
648648
vex_output = {"bomFormat": "CycloneDX", "specVersion": "1.4", "version": 1}
@@ -676,8 +676,8 @@ def generate_vex(self, all_cve_data: dict[ProductInfo, CVEData], filename: str):
676676
"name": "NVD",
677677
"url": "https://nvd.nist.gov/vuln-metrics/cvss/" + url,
678678
},
679-
"score": str(cve.score),
680-
"severity": cve.severity,
679+
"score": cve.score,
680+
"severity": cve.severity.lower(),
681681
"method": "CVSSv" + str(cve.cvss_version),
682682
"vector": cve.cvss_vector,
683683
}
@@ -690,11 +690,15 @@ def generate_vex(self, all_cve_data: dict[ProductInfo, CVEData], filename: str):
690690
vulnerability["created"] = "NOT_KNOWN"
691691
vulnerability["published"] = "NOT_KNOWN"
692692
vulnerability["updated"] = "NOT_KNOWN"
693+
detail = (
694+
cve.remarks.name + ": " + cve.comments
695+
if cve.comments
696+
else cve.remarks.name
697+
)
693698
analysis = {
694699
"state": analysis_state[cve.remarks],
695700
"response": response_state[cve.remarks],
696-
"justification": "",
697-
"detail": cve.comments,
701+
"detail": detail,
698702
}
699703
vulnerability["analysis"] = analysis
700704
bom_urn = "NOTKNOWN"

test/test_input_engine.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ def test_valid_file(self, filepath, parsed_data):
148148
"filepath, parsed_data",
149149
(
150150
(str(VEX_PATH / "test_triage.vex"), VEX_TRIAGE_DATA),
151+
(str(VEX_PATH / "test_triage_cyclonedx.vex"), VEX_TRIAGE_DATA),
151152
(str(VEX_PATH / "bad.vex"), defaultdict(dict)),
152153
),
153154
)

test/test_output_engine.py

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@
1313
from datetime import datetime
1414
from pathlib import Path
1515

16+
import requests
17+
from jsonschema import validate
1618
from rich.console import Console
1719

1820
from cve_bin_tool.output_engine import OutputEngine, output_csv, output_json, output_pdf
1921
from cve_bin_tool.output_engine.console import output_console
2022
from cve_bin_tool.output_engine.util import format_output
2123
from cve_bin_tool.util import CVE, CVEData, ProductInfo, VersionInfo
2224

25+
VEX_SCHEMA = "https://raw.githubusercontent.com/CycloneDX/specification/master/schema/bom-1.4.schema.json"
26+
2327

2428
class TestOutputEngine(unittest.TestCase):
2529
"""Test the OutputEngine class functions"""
@@ -616,8 +620,8 @@ class TestOutputEngine(unittest.TestCase):
616620
"name": "NVD",
617621
"url": "https://nvd.nist.gov/vuln-metrics/cvss/v2-calculator?name=CVE-1234-1234&vector=C:H&version=2.0",
618622
},
619-
"score": "4.2",
620-
"severity": "MEDIUM",
623+
"score": 4.2,
624+
"severity": "medium",
621625
"method": "CVSSv2",
622626
"vector": "C:H",
623627
}
@@ -630,10 +634,9 @@ class TestOutputEngine(unittest.TestCase):
630634
"published": "NOT_KNOWN",
631635
"updated": "NOT_KNOWN",
632636
"analysis": {
633-
"state": "under_review",
634-
"response": "Outstanding",
635-
"justification": "",
636-
"detail": "",
637+
"state": "in_triage",
638+
"response": [],
639+
"detail": "NewFound",
637640
},
638641
"affects": [{"ref": "urn:cdx:NOTKNOWN/1#product0-1.0"}],
639642
},
@@ -649,8 +652,8 @@ class TestOutputEngine(unittest.TestCase):
649652
"name": "NVD",
650653
"url": "https://nvd.nist.gov/vuln-metrics/cvss/v2-calculator?name=CVE-1234-1234&vector=CVSS2.0/C:H&version=2.0",
651654
},
652-
"score": "1.2",
653-
"severity": "LOW",
655+
"score": 1.2,
656+
"severity": "low",
654657
"method": "CVSSv2",
655658
"vector": "CVSS2.0/C:H",
656659
}
@@ -663,10 +666,9 @@ class TestOutputEngine(unittest.TestCase):
663666
"published": "NOT_KNOWN",
664667
"updated": "NOT_KNOWN",
665668
"analysis": {
666-
"state": "under_review",
667-
"response": "Outstanding",
668-
"justification": "",
669-
"detail": "",
669+
"state": "in_triage",
670+
"response": [],
671+
"detail": "NewFound",
670672
},
671673
"affects": [{"ref": "urn:cdx:NOTKNOWN/1#product0-1.0"}],
672674
},
@@ -682,8 +684,8 @@ class TestOutputEngine(unittest.TestCase):
682684
"name": "NVD",
683685
"url": "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?name=CVE-1234-1234&vector=CVSS3.0/C:H/I:L/A:M&version=3.1",
684686
},
685-
"score": "2.5",
686-
"severity": "LOW",
687+
"score": 2.5,
688+
"severity": "low",
687689
"method": "CVSSv3",
688690
"vector": "CVSS3.0/C:H/I:L/A:M",
689691
}
@@ -696,10 +698,9 @@ class TestOutputEngine(unittest.TestCase):
696698
"published": "NOT_KNOWN",
697699
"updated": "NOT_KNOWN",
698700
"analysis": {
699-
"state": "under_review",
700-
"response": "Outstanding",
701-
"justification": "",
702-
"detail": "",
701+
"state": "in_triage",
702+
"response": [],
703+
"detail": "NewFound",
703704
},
704705
"affects": [{"ref": "urn:cdx:NOTKNOWN/1#product0-2.8.6"}],
705706
},
@@ -715,8 +716,8 @@ class TestOutputEngine(unittest.TestCase):
715716
"name": "NVD",
716717
"url": "https://nvd.nist.gov/vuln-metrics/cvss/v2-calculator?name=CVE-1234-1234&vector=C:H/I:L/A:M&version=2.0",
717718
},
718-
"score": "7.5",
719-
"severity": "HIGH",
719+
"score": 7.5,
720+
"severity": "high",
720721
"method": "CVSSv2",
721722
"vector": "C:H/I:L/A:M",
722723
}
@@ -729,10 +730,9 @@ class TestOutputEngine(unittest.TestCase):
729730
"published": "NOT_KNOWN",
730731
"updated": "NOT_KNOWN",
731732
"analysis": {
732-
"state": "under_review",
733-
"response": "Outstanding",
734-
"justification": "",
735-
"detail": "",
733+
"state": "in_triage",
734+
"response": [],
735+
"detail": "NewFound",
736736
},
737737
"affects": [{"ref": "urn:cdx:NOTKNOWN/1#product1-3.2.1.0"}],
738738
},
@@ -783,7 +783,10 @@ def test_output_vex(self):
783783
"""Test creating VEX formatted file"""
784784
self.output_engine.generate_vex(self.MOCK_OUTPUT, "test.vex")
785785
with open("test.vex") as f:
786-
self.assertEqual(json.load(f), self.VEX_FORMATTED_OUTPUT[0])
786+
vex_json = json.load(f)
787+
SCHEMA = requests.get(VEX_SCHEMA).json()
788+
validate(vex_json, SCHEMA)
789+
self.assertEqual(vex_json, self.VEX_FORMATTED_OUTPUT[0])
787790
Path("test.vex").unlink()
788791

789792
@unittest.skipUnless(

test/vex/test_triage_cyclonedx.vex

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
{
2+
"bomFormat": "CycloneDX",
3+
"specVersion": "1.4",
4+
"version": 1,
5+
"vulnerabilities": [
6+
{
7+
"id": "CVE-2018-19664",
8+
"source": {
9+
"name": "NVD",
10+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2018-19664"
11+
},
12+
"ratings": [
13+
{
14+
"source": {
15+
"name": "NVD",
16+
"url": "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?name=CVE-2018-19664&vector=CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H&version=3.1"
17+
},
18+
"score": 10,
19+
"severity": "critical",
20+
"method": "CVSSv3",
21+
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H"
22+
}
23+
],
24+
"cwes": [],
25+
"description": "Some description from the CVE",
26+
"recommendation": "",
27+
"advisories": [],
28+
"created": "NOT_KNOWN",
29+
"published": "NOT_KNOWN",
30+
"updated": "NOT_KNOWN",
31+
"analysis": {
32+
"state": "exploitable",
33+
"response": [ ],
34+
"detail": "NewFound: High priority need to resolve fast"
35+
},
36+
"affects": [
37+
{
38+
"ref": "urn:cdx:NOTKNOWN/1#libjpeg-turbo-2.0.1"
39+
}
40+
]
41+
},
42+
{
43+
"id": "CVE-2021-1234",
44+
"source": {
45+
"name": "NVD",
46+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-1234"
47+
},
48+
"ratings": [
49+
{
50+
"source": {
51+
"name": "NVD",
52+
"url": "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?name=CVE-2021-1234&vector=CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H&version=3.1"
53+
},
54+
"score": 7.5,
55+
"severity": "high",
56+
"method": "CVSSv3",
57+
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H"
58+
}
59+
],
60+
"cwes": [],
61+
"description": "Some description from the CVE",
62+
"recommendation": "",
63+
"advisories": [],
64+
"created": "NOT_KNOWN",
65+
"published": "NOT_KNOWN",
66+
"updated": "NOT_KNOWN",
67+
"analysis": {
68+
"state": "in_triage",
69+
"response": [ ],
70+
"detail": "NewFound"
71+
},
72+
"affects": [
73+
{
74+
"ref": "urn:cdx:NOTKNOWN/1#glibc-2.33"
75+
}
76+
]
77+
},
78+
{
79+
"id": "CVE-2099-9999",
80+
"source": {
81+
"name": "NVD",
82+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2099-9999"
83+
},
84+
"ratings": [
85+
{
86+
"source": {
87+
"name": "NVD",
88+
"url": "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?name=CVE-2021-1234&vector=CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H&version=3.1"
89+
},
90+
"score": 7.5,
91+
"severity": "high",
92+
"method": "CVSSv3",
93+
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H"
94+
}
95+
],
96+
"cwes": [],
97+
"description": "Some description from the CVE",
98+
"recommendation": "",
99+
"advisories": [],
100+
"created": "NOT_KNOWN",
101+
"published": "NOT_KNOWN",
102+
"updated": "NOT_KNOWN",
103+
"analysis": {
104+
"state": "not_affected",
105+
"response": [ ],
106+
"detail": "NewFound"
107+
},
108+
"affects": [
109+
{
110+
"ref": "log4j-2.14"
111+
}
112+
]
113+
}
114+
]
115+
}
116+
117+

0 commit comments

Comments
 (0)