Skip to content

Commit 7724bae

Browse files
gerrod3cursoragent
andcommitted
Scope JSON vulnerabilities to the scanned repository version
Filter OSV reports by repository version so another index cannot show vulns until it is scanned. Move the Warehouse trim helper out of Django-backed utils so unit tests collect without loading apps. Assisted By: Cursor Grok 4.6 Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 5b93bae commit 7724bae

4 files changed

Lines changed: 72 additions & 48 deletions

File tree

pulp_python/app/osv.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from packaging.version import InvalidVersion, Version
2+
3+
4+
def _osv_fixed_in(vuln):
5+
"""Extract PEP 440 fixed versions from an OSV vulnerability record."""
6+
fixed = []
7+
seen = set()
8+
for affected in vuln.get("affected") or []:
9+
for range_ in affected.get("ranges") or []:
10+
for event in range_.get("events") or []:
11+
if "fixed" not in event:
12+
continue
13+
version = event["fixed"]
14+
if version in seen:
15+
continue
16+
try:
17+
Version(version)
18+
except InvalidVersion:
19+
continue
20+
seen.add(version)
21+
fixed.append(version)
22+
return fixed
23+
24+
25+
def osv_to_pypi_vulnerabilities(vulns):
26+
"""Trim OSV vulnerability records to the Warehouse JSON API shape."""
27+
seen = {}
28+
for vuln in vulns or []:
29+
vuln_id = vuln.get("id")
30+
if not vuln_id or vuln_id in seen:
31+
continue
32+
seen[vuln_id] = {
33+
"id": vuln_id,
34+
"source": "osv",
35+
"link": f"https://osv.dev/vulnerability/{vuln_id}",
36+
"aliases": vuln.get("aliases") or [],
37+
"details": vuln.get("details"),
38+
"summary": vuln.get("summary"),
39+
"fixed_in": _osv_fixed_in(vuln),
40+
"withdrawn": vuln.get("withdrawn"),
41+
}
42+
return list(seen.values())

pulp_python/app/utils.py

Lines changed: 13 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
from jinja2 import Template
1818
from packaging.requirements import Requirement
1919
from packaging.utils import canonicalize_name
20-
from packaging.version import InvalidVersion, Version, parse
20+
from packaging.version import InvalidVersion, parse
2121
from pypi_simple import ACCEPT_JSON_PREFERRED, ProjectPage
2222

2323
from pulpcore.plugin.exceptions import TimeoutException
2424
from pulpcore.plugin.models import Artifact, Remote, VulnerabilityReport
2525
from pulpcore.plugin.util import get_domain
2626

27+
from pulp_python.app.osv import osv_to_pypi_vulnerabilities
28+
2729
log = logging.getLogger(__name__)
2830

2931

@@ -408,56 +410,20 @@ def python_content_to_json(
408410
full_metadata["info"] = python_content_to_info(latest_content[0])
409411
full_metadata["releases"] = python_content_to_releases(all_content, base_path, domain)
410412
full_metadata["urls"] = python_content_to_urls(latest_content, base_path, domain)
411-
full_metadata["vulnerabilities"] = _vulnerabilities_for_content(latest_content)
413+
full_metadata["vulnerabilities"] = _vulnerabilities_for_content(
414+
latest_content, repository_version
415+
)
412416
return full_metadata
413417

414418

415-
def _osv_fixed_in(vuln):
416-
"""Extract PEP 440 fixed versions from an OSV vulnerability record."""
417-
fixed = []
418-
seen = set()
419-
for affected in vuln.get("affected") or []:
420-
for range_ in affected.get("ranges") or []:
421-
for event in range_.get("events") or []:
422-
if "fixed" not in event:
423-
continue
424-
version = event["fixed"]
425-
if version in seen:
426-
continue
427-
try:
428-
Version(version)
429-
except InvalidVersion:
430-
continue
431-
seen.add(version)
432-
fixed.append(version)
433-
return fixed
434-
435-
436-
def osv_to_pypi_vulnerabilities(vulns):
437-
"""Trim OSV vulnerability records to the Warehouse JSON API shape."""
438-
seen = {}
439-
for vuln in vulns or []:
440-
vuln_id = vuln.get("id")
441-
if not vuln_id or vuln_id in seen:
442-
continue
443-
seen[vuln_id] = {
444-
"id": vuln_id,
445-
"source": "osv",
446-
"link": f"https://osv.dev/vulnerability/{vuln_id}",
447-
"aliases": vuln.get("aliases") or [],
448-
"details": vuln.get("details"),
449-
"summary": vuln.get("summary"),
450-
"fixed_in": _osv_fixed_in(vuln),
451-
"withdrawn": vuln.get("withdrawn"),
452-
}
453-
return list(seen.values())
454-
455-
456-
def _vulnerabilities_for_content(contents):
457-
"""Load VulnerabilityReports for content units and trim them to Warehouse shape."""
458-
if not contents:
419+
def _vulnerabilities_for_content(contents, repository_version=None):
420+
"""Load VulnerabilityReports scanned for this repository version and trim to Warehouse shape."""
421+
if not contents or repository_version is None:
459422
return []
460-
reports = VulnerabilityReport.objects.filter(content_id__in=[c.pk for c in contents])
423+
reports = VulnerabilityReport.objects.filter(
424+
content_id__in=[c.pk for c in contents],
425+
repo_versions=repository_version,
426+
)
461427
merged = []
462428
for vulns in reports.values_list("vulns", flat=True):
463429
if vulns:

pulp_python/tests/functional/api/test_pypi_json_vulnerabilities.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def test_pypi_json_vulnerabilities_from_manual_scan(
5959
python_bindings,
6060
python_remote_factory,
6161
python_repo,
62+
python_repo_factory,
6263
python_distribution_factory,
6364
monitor_task,
6465
):
@@ -94,11 +95,26 @@ def test_pypi_json_vulnerabilities_from_manual_scan(
9495
repository_version=repo.latest_version_href,
9596
)
9697
assert packages.count >= 2
98+
hrefs = []
9799
for content in packages.results:
98100
assert content.vuln_report is not None
99101
report = pulpcore_bindings.VulnReportApi.read(content.vuln_report)
100102
assert report.vulns
101103
assert "affected" in report.vulns[0]
104+
hrefs.append(content.pulp_href)
105+
106+
other = python_repo_factory()
107+
monitor_task(
108+
python_bindings.RepositoriesPythonApi.modify(
109+
other.pulp_href, {"add_content_units": hrefs}
110+
).task
111+
)
112+
other = python_bindings.RepositoriesPythonApi.read(other.pulp_href)
113+
other_distro = python_distribution_factory(repository=other)
114+
other_json = requests.get(
115+
urljoin(_index_url(other_distro, bindings_cfg), f"pypi/{name}/json")
116+
).json()
117+
assert other_json["vulnerabilities"] == []
102118

103119

104120
@pytest.mark.parallel

pulp_python/tests/unit/test_vulnerabilities.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from pulp_python.app.utils import osv_to_pypi_vulnerabilities
1+
from pulp_python.app.osv import osv_to_pypi_vulnerabilities
22

33

44
def test_osv_to_pypi_maps_fixed_versions_and_skips_git_shas():

0 commit comments

Comments
 (0)