Skip to content

Commit c21bf1f

Browse files
authored
Merge pull request #1679 from aboutcode-org/1650-ghost-cant-fix
Do not report ghost package as a fix for vulnerability
2 parents dc2b367 + 650df4c commit c21bf1f

File tree

5 files changed

+254
-8
lines changed

5 files changed

+254
-8
lines changed

vulnerabilities/api.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@
2222
from rest_framework import viewsets
2323
from rest_framework.decorators import action
2424
from rest_framework.response import Response
25-
from rest_framework.reverse import reverse
2625
from rest_framework.throttling import AnonRateThrottle
27-
from rest_framework.throttling import UserRateThrottle
2826

2927
from vulnerabilities.models import Alias
3028
from vulnerabilities.models import Exploit
@@ -369,6 +367,10 @@ def get_fixing_vulnerabilities(self, package) -> dict:
369367
"""
370368
Return a mapping of vulnerabilities fixed in the given `package`.
371369
"""
370+
# Ghost package should not fix any vulnerability.
371+
if package.is_ghost:
372+
return []
373+
372374
return self.get_vulnerabilities_for_a_package(package=package, fix=True)
373375

374376
def get_affected_vulnerabilities(self, package) -> dict:
@@ -643,7 +645,10 @@ def get_fixed_packages_qs(self):
643645
"""
644646
return (
645647
self.get_packages_qs()
646-
.filter(fixingpackagerelatedvulnerability__isnull=False)
648+
.filter(
649+
fixingpackagerelatedvulnerability__isnull=False,
650+
is_ghost=False,
651+
)
647652
.with_is_vulnerable()
648653
)
649654

@@ -669,6 +674,9 @@ def get_queryset(self):
669674
.get_queryset()
670675
.prefetch_related(
671676
"weaknesses",
677+
"references",
678+
"exploits",
679+
"severities",
672680
Prefetch(
673681
"fixed_by_packages",
674682
queryset=self.get_fixed_packages_qs(),

vulnerabilities/api_v2.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,9 @@ def get_affected_by_vulnerabilities(self, obj):
198198
return [vuln.vulnerability_id for vuln in obj.affected_by_vulnerabilities.all()]
199199

200200
def get_fixing_vulnerabilities(self, obj):
201+
# Ghost package should not fix any vulnerability.
202+
if obj.is_ghost:
203+
return []
201204
return [vuln.vulnerability_id for vuln in obj.fixing_vulnerabilities.all()]
202205

203206

vulnerabilities/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ def only_vulnerable(self):
632632
return self._vulnerable(True)
633633

634634
def only_non_vulnerable(self):
635-
return self._vulnerable(False)
635+
return self._vulnerable(False).filter(is_ghost=False)
636636

637637
def _vulnerable(self, vulnerable=True):
638638
"""

vulnerabilities/tests/test_api.py

Lines changed: 219 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
import json
1111
import os
12-
from collections import OrderedDict
1312
from urllib.parse import quote
1413

1514
from django.test import TestCase
@@ -31,7 +30,6 @@
3130
from vulnerabilities.models import VulnerabilitySeverity
3231
from vulnerabilities.models import Weakness
3332
from vulnerabilities.severity_systems import EPSS
34-
from vulnerabilities.tests import util_tests
3533

3634
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
3735
TEST_DATA = os.path.join(BASE_DIR, "test_data")
@@ -355,6 +353,55 @@ def test_api_with_single_vulnerability_with_filters(self):
355353
"weighted_severity": None,
356354
}
357355

356+
def test_api_with_single_vulnerability_no_ghost_fix(self):
357+
self.pkg2.is_ghost = True
358+
self.pkg1.is_ghost = True
359+
self.pkg2.save()
360+
self.pkg1.save()
361+
362+
response = self.csrf_client.get(
363+
f"/api/vulnerabilities/{self.vulnerability.id}", format="json"
364+
).data
365+
366+
expected = {
367+
"url": f"http://testserver/api/vulnerabilities/{self.vulnerability.id}",
368+
"vulnerability_id": self.vulnerability.vulnerability_id,
369+
"summary": "test",
370+
"severity_range_score": None,
371+
"aliases": [],
372+
"resource_url": f"http://testserver/vulnerabilities/{self.vulnerability.vulnerability_id}",
373+
"fixed_packages": [],
374+
"affected_packages": [],
375+
"references": [
376+
{
377+
"reference_url": "https://.com",
378+
"reference_id": "",
379+
"reference_type": "",
380+
"scores": [
381+
{
382+
"value": "0.526",
383+
"scoring_system": "epss",
384+
"scoring_elements": ".0016",
385+
}
386+
],
387+
"url": "https://.com",
388+
}
389+
],
390+
"weaknesses": [
391+
{
392+
"cwe_id": 119,
393+
"name": "Improper Restriction of Operations within the Bounds of a Memory Buffer",
394+
"description": "The product performs operations on a memory buffer, but it can read from or write to a memory location that is outside of the intended boundary of the buffer.",
395+
},
396+
],
397+
"exploits": [],
398+
"risk_score": None,
399+
"exploitability": None,
400+
"weighted_severity": None,
401+
}
402+
403+
assert expected == response
404+
358405

359406
def set_as_affected_by(package, vulnerability):
360407
"""
@@ -743,6 +790,176 @@ def test_api_with_ignorning_qualifiers(self):
743790
== "pkg:maven/com.fasterxml.jackson.core/[email protected]"
744791
)
745792

793+
def test_api_with_ghost_package_no_fixing_vulnerabilities(self):
794+
self.pkg_2_13_1.is_ghost = True
795+
self.pkg_2_13_1.save()
796+
797+
response = self.csrf_client.get(f"/api/packages/{self.pkg_2_13_1.id}", format="json").data
798+
799+
expected = {
800+
"url": "http://testserver/api/packages/{0}".format(self.pkg_2_13_1.id),
801+
"purl": "pkg:maven/com.fasterxml.jackson.core/[email protected]",
802+
"type": "maven",
803+
"namespace": "com.fasterxml.jackson.core",
804+
"name": "jackson-databind",
805+
"version": "2.13.1",
806+
"qualifiers": {},
807+
"subpath": "",
808+
"is_vulnerable": True,
809+
"next_non_vulnerable_version": "2.14.0-rc1",
810+
"latest_non_vulnerable_version": "2.14.0-rc1",
811+
"affected_by_vulnerabilities": [
812+
{
813+
"url": "http://testserver/api/vulnerabilities/{0}".format(self.vul1.id),
814+
"vulnerability_id": "VCID-vul1-vul1-vul1",
815+
"summary": "This is VCID-vul1-vul1-vul1",
816+
"references": [
817+
{
818+
"reference_url": "https://example.com",
819+
"reference_id": "CVE-xxx-xxx",
820+
"reference_type": "advisory",
821+
"scores": [
822+
{
823+
"value": "0.526",
824+
"scoring_system": "epss",
825+
"scoring_elements": ".0016",
826+
}
827+
],
828+
"url": "https://example.com",
829+
}
830+
],
831+
"fixed_packages": [
832+
{
833+
"url": "http://testserver/api/packages/{0}".format(self.pkg_2_13_2.id),
834+
"purl": "pkg:maven/com.fasterxml.jackson.core/[email protected]",
835+
"is_vulnerable": True,
836+
"affected_by_vulnerabilities": [
837+
{"vulnerability": "VCID-vul2-vul2-vul2"}
838+
],
839+
"resource_url": "http://testserver/packages/pkg:maven/com.fasterxml.jackson.core/[email protected]",
840+
}
841+
],
842+
"aliases": ["CVE-2020-36518", "GHSA-57j2-w4cx-62h2"],
843+
"risk_score": None,
844+
"exploitability": None,
845+
"weighted_severity": None,
846+
"resource_url": "http://testserver/vulnerabilities/VCID-vul1-vul1-vul1",
847+
}
848+
],
849+
"fixing_vulnerabilities": [],
850+
"risk_score": None,
851+
"resource_url": "http://testserver/packages/pkg:maven/com.fasterxml.jackson.core/[email protected]",
852+
}
853+
854+
assert response == expected
855+
856+
def test_api_with_ghost_package_no_next_latest_non_vulnerabilities(self):
857+
self.pkg_2_14_0_rc1.is_ghost = True
858+
self.pkg_2_14_0_rc1.save()
859+
860+
response = self.csrf_client.get(f"/api/packages/{self.pkg_2_13_1.id}", format="json").data
861+
862+
expected = {
863+
"url": "http://testserver/api/packages/{0}".format(self.pkg_2_13_1.id),
864+
"purl": "pkg:maven/com.fasterxml.jackson.core/[email protected]",
865+
"type": "maven",
866+
"namespace": "com.fasterxml.jackson.core",
867+
"name": "jackson-databind",
868+
"version": "2.13.1",
869+
"qualifiers": {},
870+
"subpath": "",
871+
"is_vulnerable": True,
872+
"next_non_vulnerable_version": None,
873+
"latest_non_vulnerable_version": None,
874+
"affected_by_vulnerabilities": [
875+
{
876+
"url": "http://testserver/api/vulnerabilities/{0}".format(self.vul1.id),
877+
"vulnerability_id": "VCID-vul1-vul1-vul1",
878+
"summary": "This is VCID-vul1-vul1-vul1",
879+
"references": [
880+
{
881+
"reference_url": "https://example.com",
882+
"reference_id": "CVE-xxx-xxx",
883+
"reference_type": "advisory",
884+
"scores": [
885+
{
886+
"value": "0.526",
887+
"scoring_system": "epss",
888+
"scoring_elements": ".0016",
889+
}
890+
],
891+
"url": "https://example.com",
892+
}
893+
],
894+
"fixed_packages": [
895+
{
896+
"url": "http://testserver/api/packages/{0}".format(self.pkg_2_13_2.id),
897+
"purl": "pkg:maven/com.fasterxml.jackson.core/[email protected]",
898+
"is_vulnerable": True,
899+
"affected_by_vulnerabilities": [
900+
{"vulnerability": "VCID-vul2-vul2-vul2"}
901+
],
902+
"resource_url": "http://testserver/packages/pkg:maven/com.fasterxml.jackson.core/[email protected]",
903+
}
904+
],
905+
"aliases": ["CVE-2020-36518", "GHSA-57j2-w4cx-62h2"],
906+
"risk_score": None,
907+
"exploitability": None,
908+
"weighted_severity": None,
909+
"resource_url": "http://testserver/vulnerabilities/VCID-vul1-vul1-vul1",
910+
}
911+
],
912+
"fixing_vulnerabilities": [
913+
{
914+
"url": "http://testserver/api/vulnerabilities/{0}".format(self.vul3.id),
915+
"vulnerability_id": "VCID-vul3-vul3-vul3",
916+
"summary": "This is VCID-vul3-vul3-vul3",
917+
"references": [
918+
{
919+
"reference_url": "https://example.com",
920+
"reference_id": "CVE-xxx-xxx",
921+
"reference_type": "advisory",
922+
"scores": [
923+
{
924+
"value": "0.526",
925+
"scoring_system": "epss",
926+
"scoring_elements": ".0016",
927+
}
928+
],
929+
"url": "https://example.com",
930+
}
931+
],
932+
"fixed_packages": [
933+
{
934+
"url": "http://testserver/api/packages/{0}".format(self.pkg_2_12_6.id),
935+
"purl": "pkg:maven/com.fasterxml.jackson.core/[email protected]",
936+
"is_vulnerable": False,
937+
"affected_by_vulnerabilities": [],
938+
"resource_url": "http://testserver/packages/pkg:maven/com.fasterxml.jackson.core/[email protected]",
939+
},
940+
{
941+
"url": "http://testserver/api/packages/{0}".format(self.pkg_2_13_1.id),
942+
"purl": "pkg:maven/com.fasterxml.jackson.core/[email protected]",
943+
"is_vulnerable": True,
944+
"affected_by_vulnerabilities": [
945+
{"vulnerability": "VCID-vul1-vul1-vul1"}
946+
],
947+
"resource_url": "http://testserver/packages/pkg:maven/com.fasterxml.jackson.core/[email protected]",
948+
},
949+
],
950+
"aliases": ["CVE-2021-46877", "GHSA-3x8x-79m2-3w2w"],
951+
"risk_score": None,
952+
"exploitability": None,
953+
"weighted_severity": None,
954+
"resource_url": "http://testserver/vulnerabilities/VCID-vul3-vul3-vul3",
955+
}
956+
],
957+
"risk_score": None,
958+
"resource_url": "http://testserver/packages/pkg:maven/com.fasterxml.jackson.core/[email protected]",
959+
}
960+
961+
assert response == expected
962+
746963

747964
class CPEApi(TestCase):
748965
def setUp(self):

vulnerabilities/views.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,10 @@ def get_context_data(self, **kwargs):
119119
package = self.object
120120
context["package"] = package
121121
context["affected_by_vulnerabilities"] = package.affected_by.order_by("vulnerability_id")
122-
context["fixing_vulnerabilities"] = package.fixing.order_by("vulnerability_id")
122+
# Ghost package should not fix any vulnerability.
123+
context["fixing_vulnerabilities"] = (
124+
None if package.is_ghost else package.fixing.order_by("vulnerability_id")
125+
)
123126
context["package_search_form"] = PackageSearchForm(self.request.GET)
124127
context["fixed_package_details"] = package.fixed_package_details
125128

@@ -153,7 +156,17 @@ class VulnerabilityDetails(DetailView):
153156
slug_field = "vulnerability_id"
154157

155158
def get_queryset(self):
156-
return super().get_queryset().prefetch_related("references", "aliases", "weaknesses")
159+
return (
160+
super()
161+
.get_queryset()
162+
.prefetch_related(
163+
"references",
164+
"aliases",
165+
"weaknesses",
166+
"severities",
167+
"exploits",
168+
)
169+
)
157170

158171
def get_context_data(self, **kwargs):
159172
context = super().get_context_data(**kwargs)
@@ -193,6 +206,11 @@ def get_context_data(self, **kwargs):
193206
affected_fixed_by_matches["affected_package"] = sorted_affected_package
194207
matched_fixed_by_packages = []
195208
for fixed_by_package in sorted_fixed_by_packages:
209+
210+
# Ghost Package can't fix vulnerability.
211+
if fixed_by_package.is_ghost:
212+
continue
213+
196214
sorted_affected_version_class = get_purl_version_class(sorted_affected_package)
197215
fixed_by_version_class = get_purl_version_class(fixed_by_package)
198216
if (

0 commit comments

Comments
 (0)