Skip to content

Commit 27937f8

Browse files
committed
Add vulnerability_id and subsection_for_changelog_summary with tests
1 parent 4e5e9bf commit 27937f8

File tree

4 files changed

+77
-4
lines changed

4 files changed

+77
-4
lines changed

doc/changes/unreleased.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
* #535: Added more information about Sonar's usage of ``exclusions``
77
* #596: Corrected and added more information regarding ``pyupgrade``
88

9+
## Features
10+
11+
* #595: Created class `ResolvedVulnerabilities` to track resolved vulnerabilities between versions
12+
913
## Refactoring
1014

1115
* #596: Added newline after header in versioned changelog

exasol/toolbox/util/dependencies/audit.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import tempfile
66
from dataclasses import dataclass
77
from enum import Enum
8+
from inspect import cleandoc
89
from pathlib import Path
910
from re import search
1011
from typing import (
@@ -85,7 +86,7 @@ def from_audit_entry(
8586

8687
@property
8788
def references(self) -> list[str]:
88-
return [self.id] + self.aliases
89+
return sorted([self.id] + self.aliases)
8990

9091
@property
9192
def reference_links(self) -> tuple[str, ...]:
@@ -106,6 +107,31 @@ def security_issue_entry(self) -> dict[str, str | list[str]]:
106107
"references": self.reference_links,
107108
}
108109

110+
@property
111+
def vulnerability_id(self) -> str | None:
112+
"""
113+
Ensure a consistent way of identifying a vulnerability for string generation.
114+
"""
115+
for ref in self.references:
116+
ref_upper = ref.upper()
117+
if ref_upper.startswith(VulnerabilitySource.CVE.value):
118+
return ref
119+
if ref_upper.startswith(VulnerabilitySource.GHSA.value):
120+
return ref
121+
if ref_upper.startswith(VulnerabilitySource.PYSEC.value):
122+
return ref
123+
return self.references[0]
124+
125+
@property
126+
def subsection_for_changelog_summary(self) -> str:
127+
"""
128+
Create a subsection to be included in the Summary section of a versioned changelog.
129+
"""
130+
links_join = "\n* ".join(sorted(self.reference_links))
131+
references_subsection = f"\n#### References:\n\n* {links_join}\n\n "
132+
subsection = f"### {self.vulnerability_id} in {self.coordinates}\n\n{self.description}\n{references_subsection}"
133+
return cleandoc(subsection.strip())
134+
109135

110136
def audit_poetry_files(working_directory: Path) -> str:
111137
"""

test/conftest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,12 @@ def security_issue_entry(self) -> dict[str, str | list[str] | tuple[str, ...]]:
6363
return {
6464
"name": self.package_name,
6565
"version": self.version,
66-
"refs": [self.vulnerability_id, self.cve_id],
66+
"refs": [self.cve_id, self.vulnerability_id],
6767
"description": self.description,
6868
"coordinates": f"{self.package_name}:{self.version}",
6969
"references": (
70-
f"https://github.com/advisories/{self.vulnerability_id}",
7170
f"https://nvd.nist.gov/vuln/detail/{self.cve_id}",
71+
f"https://github.com/advisories/{self.vulnerability_id}",
7272
),
7373
}
7474

@@ -80,8 +80,8 @@ def security_issue(self) -> Issue:
8080
description=self.description,
8181
coordinates=f"{self.package_name}:{self.version}",
8282
references=(
83-
f"https://github.com/advisories/{self.vulnerability_id}",
8483
f"https://nvd.nist.gov/vuln/detail/{self.cve_id}",
84+
f"https://github.com/advisories/{self.vulnerability_id}",
8585
),
8686
)
8787

test/unit/util/dependencies/audit_test.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
from inspect import cleandoc
23
from pathlib import Path
34
from subprocess import CompletedProcess
45
from unittest import mock
@@ -85,6 +86,48 @@ def test_reference_links(sample_vulnerability, reference: str, expected: list[st
8586

8687
assert result.reference_links == (expected,)
8788

89+
@pytest.mark.parametrize(
90+
"aliases,expected",
91+
(
92+
pytest.param(["A", "PYSEC", "CVE", "GHSA"], "CVE", id="CVE"),
93+
pytest.param(["A", "PYSEC", "GHSA"], "GHSA", id="GHSA"),
94+
pytest.param(["A", "PYSEC"], "PYSEC", id="PYSEC"),
95+
pytest.param(["Z", "A"], "A", id="alphabetical_case"),
96+
),
97+
)
98+
def test_vulnerability_id(self, sample_vulnerability, aliases: list[str], expected):
99+
100+
result = Vulnerability(
101+
name=sample_vulnerability.package_name,
102+
version=sample_vulnerability.version,
103+
id="DUMMY_IDENTIFIER",
104+
aliases=aliases,
105+
fix_versions=[sample_vulnerability.fix_version],
106+
description=sample_vulnerability.description,
107+
)
108+
109+
assert result.vulnerability_id == expected
110+
111+
def test_subsection_for_changelog_summary(self, sample_vulnerability):
112+
expected = cleandoc(
113+
"""
114+
### CVE-2025-27516 in jinja2:3.1.5
115+
116+
An oversight in how the Jinja sandboxed environment interacts with the
117+
`|attr` filter allows an attacker that controls the content of a template
118+
to execute arbitrary Python code.
119+
120+
#### References:
121+
122+
* https://github.com/advisories/GHSA-cpwx-vrp4-4pq7
123+
* https://nvd.nist.gov/vuln/detail/CVE-2025-27516
124+
"""
125+
)
126+
assert (
127+
sample_vulnerability.vulnerability.subsection_for_changelog_summary
128+
== expected
129+
)
130+
88131

89132
class TestAuditPoetryFiles:
90133
@staticmethod

0 commit comments

Comments
 (0)