Skip to content

Commit 5608838

Browse files
committed
Add support for vulnerabilities in load_sbom pipeline #1729
Signed-off-by: tdruez <[email protected]>
1 parent a403344 commit 5608838

File tree

4 files changed

+160
-20
lines changed

4 files changed

+160
-20
lines changed

scanpipe/pipes/cyclonedx.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,12 @@ def is_cyclonedx_bom(input_location):
150150
return False
151151

152152

153-
def cyclonedx_component_to_package_data(cdx_component, dependencies=None):
153+
def cyclonedx_component_to_package_data(
154+
cdx_component, dependencies=None, vulnerabilities=None
155+
):
154156
"""Return package_data from CycloneDX component."""
155157
dependencies = dependencies or {}
158+
vulnerabilities = vulnerabilities or {}
156159
extra_data = {}
157160

158161
# Store the original bom_ref and dependencies for future processing.
@@ -175,13 +178,24 @@ def cyclonedx_component_to_package_data(cdx_component, dependencies=None):
175178
nested_purls = [component.bom_ref.value for component in nested_components]
176179
extra_data["nestedComponents"] = sorted(nested_purls)
177180

181+
affected_by_vulnerabilities = []
182+
if affected_by := vulnerabilities.get(bom_ref):
183+
for cdx_vulnerability in affected_by:
184+
affected_by_vulnerabilities.append(
185+
{
186+
"vulnerability_id": str(cdx_vulnerability.id),
187+
"summary": cdx_vulnerability.description,
188+
}
189+
)
190+
178191
package_data = {
179192
"name": cdx_component.name,
180193
"extracted_license_statement": declared_license,
181194
"copyright": cdx_component.copyright,
182195
"version": cdx_component.version,
183196
"description": cdx_component.description,
184197
"extra_data": extra_data,
198+
"affected_by_vulnerabilities": affected_by_vulnerabilities,
185199
**package_url_dict,
186200
**get_checksums(cdx_component),
187201
**get_properties_data(cdx_component),
@@ -216,7 +230,6 @@ def delete_ignored_root_properties(cyclonedx_document_json):
216230
"services",
217231
"externalReferences",
218232
"compositions",
219-
"vulnerabilities",
220233
"annotations",
221234
"formulation",
222235
"declarations",
@@ -324,7 +337,12 @@ def resolve_cyclonedx_packages(input_location):
324337
if depends_on := [str(dep.ref) for dep in entry.dependencies]:
325338
dependencies[str(entry.ref)].extend(depends_on)
326339

340+
vulnerabilities = defaultdict(list)
341+
for vulnerability in cyclonedx_bom.vulnerabilities:
342+
for affected_target in vulnerability.affects:
343+
vulnerabilities[str(affected_target.ref)].append(vulnerability)
344+
327345
return [
328-
cyclonedx_component_to_package_data(component, dependencies)
346+
cyclonedx_component_to_package_data(component, dependencies, vulnerabilities)
329347
for component in components
330348
]
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
{
2+
"$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
3+
"bomFormat": "CycloneDX",
4+
"specVersion": "1.6",
5+
"serialNumber": "urn:uuid:f9350ed3-ac98-4d15-90ef-62b39dc55d5a",
6+
"version": 1,
7+
"metadata": {
8+
"timestamp": "2025-08-18T05:43:10+00:00",
9+
"component": {
10+
"bom-ref": "pkg:oci/python@sha256%3A0de818129b26ed8f46fd772f540c80e277b67a28229531a1ba0fdacfaed19bcb?arch=amd64&repository_url=index.docker.io%2Flibrary%2Fpython",
11+
"type": "container",
12+
"name": "python:3.13.0-slim",
13+
"purl": "pkg:oci/python@sha256%3A0de818129b26ed8f46fd772f540c80e277b67a28229531a1ba0fdacfaed19bcb?arch=amd64&repository_url=index.docker.io%2Flibrary%2Fpython"
14+
}
15+
},
16+
"components": [
17+
{
18+
"bom-ref": "pkg:deb/debian/[email protected]%2Bdfsg-1.2%2Bdeb12u1?arch=amd64&distro=debian-12.8",
19+
"type": "library",
20+
"supplier": {
21+
"name": "Janos Lenart <[email protected]>"
22+
},
23+
"name": "tar",
24+
"version": "1.34+dfsg-1.2+deb12u1",
25+
"purl": "pkg:deb/debian/[email protected]%2Bdfsg-1.2%2Bdeb12u1?arch=amd64&distro=debian-12.8"
26+
}
27+
],
28+
"vulnerabilities": [
29+
{
30+
"id": "CVE-2005-2541",
31+
"source": {
32+
"name": "debian",
33+
"url": "https://salsa.debian.org/security-tracker-team/security-tracker"
34+
},
35+
"ratings": [
36+
{
37+
"source": {
38+
"name": "debian"
39+
},
40+
"severity": "low"
41+
},
42+
{
43+
"source": {
44+
"name": "nvd"
45+
},
46+
"score": 10,
47+
"severity": "high",
48+
"method": "CVSSv2",
49+
"vector": "AV:N/AC:L/Au:N/C:C/I:C/A:C"
50+
},
51+
{
52+
"source": {
53+
"name": "redhat"
54+
},
55+
"score": 7,
56+
"severity": "medium",
57+
"method": "CVSSv31",
58+
"vector": "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H"
59+
}
60+
],
61+
"description": "Tar 1.15.1 does not properly warn the user when...",
62+
"advisories": [
63+
{
64+
"url": "https://avd.aquasec.com/nvd/cve-2005-2541"
65+
},
66+
{
67+
"url": "http://marc.info/?l=bugtraq&m=112327628230258&w=2"
68+
},
69+
{
70+
"url": "https://access.redhat.com/security/cve/CVE-2005-2541"
71+
},
72+
{
73+
"url": "https://lists.apache.org/thread.html/rc713534b10f9daeee2e0990239fa407e2118e4aa9e88a7041177497c%40%3Cissues.guacamole.apache.org%3E"
74+
},
75+
{
76+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2005-2541"
77+
},
78+
{
79+
"url": "https://www.cve.org/CVERecord?id=CVE-2005-2541"
80+
}
81+
],
82+
"published": "2005-08-10T04:00:00+00:00",
83+
"updated": "2025-04-03T01:03:51+00:00",
84+
"affects": [
85+
{
86+
"ref": "pkg:deb/debian/[email protected]%2Bdfsg-1.2%2Bdeb12u1?arch=amd64&distro=debian-12.8",
87+
"versions": [
88+
{
89+
"version": "1.34+dfsg-1.2+deb12u1",
90+
"status": "affected"
91+
}
92+
]
93+
}
94+
]
95+
}
96+
]
97+
}

scanpipe/tests/pipes/test_cyclonedx.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,20 @@ def test_scanpipe_cyclonedx_resolve_cyclonedx_packages_dependencies(self):
248248
]
249249
self.assertEqual(expected_depends_on, extra_data["depends_on"])
250250

251+
def test_scanpipe_cyclonedx_resolve_cyclonedx_packages_vulnerabilities(self):
252+
input_location = self.data / "python-3.13.0-vulnerabilities.cdx.json"
253+
packages = cyclonedx.resolve_cyclonedx_packages(input_location)
254+
self.assertEqual(1, len(packages))
255+
256+
affected_by = packages[0]["affected_by_vulnerabilities"]
257+
expected = [
258+
{
259+
"vulnerability_id": "CVE-2005-2541",
260+
"summary": "Tar 1.15.1 does not properly warn the user when...",
261+
}
262+
]
263+
self.assertEqual(expected, affected_by)
264+
251265
def test_scanpipe_cyclonedx_resolve_cyclonedx_packages_pre_validation(self):
252266
# This SBOM includes multiple deserialization issues that are "fixed"
253267
# by the pre-validation cleanup.

scanpipe/tests/test_pipelines.py

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,11 +1388,11 @@ def test_scanpipe_fetch_scores_pipeline_integration(self, mock_is_available):
13881388
"scoring_tool_documentation_url": "https://github.com/[trunc...]",
13891389
"score_date": "2025-07-24T18:50:16Z",
13901390
}
1391-
with mock.patch("scorecode.ossf_scorecard.fetch_scorecard_info") as fetch:
1391+
with mock.patch("scorecode.ossf_scorecard.fetch_scorecard") as fetch:
13921392
fetch.return_value = PackageScore(**package_score_data)
1393-
exitcode, out = pipeline.execute()
1394-
1393+
exitcode, out = pipeline.execute()
13951394
self.assertEqual(0, exitcode, msg=out)
1395+
13961396
package1.refresh_from_db()
13971397
scorecard_entry = package1.scores.filter(scoring_tool="ossf-scorecard").first()
13981398
self.assertIsNotNone(scorecard_entry)
@@ -1617,6 +1617,31 @@ def test_scanpipe_load_sbom_pipeline_cyclonedx_with_dependencies_integration(sel
16171617
dependency = project1.discovereddependencies.all()[0]
16181618
self.assertEqual("bom.1.4.json", str(dependency.datafile_resource))
16191619

1620+
def test_scanpipe_load_sbom_pipeline_cyclonedx_with_vulnerabilities(self):
1621+
pipeline_name = "load_sbom"
1622+
project1 = make_project()
1623+
1624+
input_location = (
1625+
self.data / "cyclonedx" / "python-3.13.0-vulnerabilities.cdx.json"
1626+
)
1627+
project1.copy_input_from(input_location)
1628+
1629+
run = project1.add_pipeline(pipeline_name)
1630+
pipeline = run.make_pipeline_instance()
1631+
1632+
exitcode, out = pipeline.execute()
1633+
self.assertEqual(0, exitcode, msg=out)
1634+
1635+
self.assertEqual(1, project1.discoveredpackages.count())
1636+
package = project1.discoveredpackages.get()
1637+
expected = [
1638+
{
1639+
"vulnerability_id": "CVE-2005-2541",
1640+
"summary": "Tar 1.15.1 does not properly warn the user when...",
1641+
}
1642+
]
1643+
self.assertEqual(expected, package.affected_by_vulnerabilities)
1644+
16201645
@mock.patch("scanpipe.pipes.purldb.request_post")
16211646
@mock.patch("uuid.uuid4")
16221647
def test_scanpipe_deploy_to_develop_pipeline_integration(
@@ -1747,20 +1772,6 @@ def test_scanpipe_deploy_to_develop_pipeline_with_about_file(
17471772
)
17481773
self.assertIn(expected, message.description)
17491774

1750-
def test_scanpipe_deploy_to_develop_pipeline_without_selected_groups(self):
1751-
pipeline_name = "map_deploy_to_develop"
1752-
project1 = make_project(name="Analysis")
1753-
1754-
data_dir = self.data / "d2d" / "about_files"
1755-
project1.copy_input_from(data_dir / "from-with-about-file.zip")
1756-
project1.copy_input_from(data_dir / "to-with-jar.zip")
1757-
1758-
run = project1.add_pipeline(pipeline_name=pipeline_name)
1759-
pipeline = run.make_pipeline_instance()
1760-
1761-
exitcode, out = pipeline.execute()
1762-
self.assertEqual(0, exitcode, msg=out)
1763-
17641775
@mock.patch("scanpipe.pipes.purldb.request_post")
17651776
@mock.patch("scanpipe.pipes.purldb.is_available")
17661777
def test_scanpipe_populate_purldb_pipeline_integration(

0 commit comments

Comments
 (0)