Skip to content

Commit 25cad21

Browse files
authored
Add project vulnerability list view (#2018)
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent f21f18d commit 25cad21

File tree

13 files changed

+168
-57
lines changed

13 files changed

+168
-57
lines changed

scancodeio/static/main.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,12 @@ progress.file-upload::before {
391391
#message-list th#column-severity {
392392
min-width: 110px;
393393
}
394+
th#column-vulnerability_id {
395+
min-width: 220px;
396+
}
397+
th#column-summary {
398+
width: 40%;
399+
}
394400
.menu.is-info .is-active {
395401
background-color: #3e8ed0;
396402
}

scanpipe/management/commands/check-compliance.py

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -104,23 +104,17 @@ def check_compliance(self, fail_level):
104104
return total_issues > 0
105105

106106
def check_vulnerabilities(self):
107-
packages = self.project.discoveredpackages.vulnerable_ordered()
108-
dependencies = self.project.discovereddependencies.vulnerable_ordered()
109-
110-
vulnerable_records = list(packages) + list(dependencies)
111-
count = len(vulnerable_records)
107+
all_vulnerabilities = self.project.vulnerabilities
108+
vulnerabilities_count = len(all_vulnerabilities)
112109

113110
if self.verbosity > 0:
114-
if count:
115-
self.stderr.write(f"{count} vulnerable records found:")
116-
for entry in vulnerable_records:
117-
self.stderr.write(str(entry))
118-
vulnerability_ids = [
119-
vulnerability.get("vulnerability_id")
120-
for vulnerability in entry.affected_by_vulnerabilities
121-
]
122-
self.stderr.write(" > " + ", ".join(vulnerability_ids))
111+
if vulnerabilities_count:
112+
self.stderr.write(f"{vulnerabilities_count} vulnerabilities found:")
113+
for vulnerability_id, vulnerability_data in all_vulnerabilities.items():
114+
self.stderr.write(str(vulnerability_id))
115+
for affected_obj in vulnerability_data.get("affects", []):
116+
self.stderr.write(f" > {affected_obj}")
123117
else:
124118
self.stdout.write("No vulnerabilities found")
125119

126-
return count > 0
120+
return vulnerabilities_count > 0

scanpipe/models.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1495,6 +1495,11 @@ def vulnerable_dependency_count(self):
14951495
"""Return the number of vulnerable dependencies related to this project."""
14961496
return self.vulnerable_dependencies.count()
14971497

1498+
@cached_property
1499+
def vulnerability_count(self):
1500+
"""Return the number of vulnerabilities related to this project."""
1501+
return self.vulnerable_package_count + self.vulnerable_dependency_count
1502+
14981503
@cached_property
14991504
def dependency_count(self):
15001505
"""Return the number of dependencies related to this project."""

scanpipe/templates/scanpipe/includes/project_summary_level.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,8 @@
9494
{% endif %}
9595
{% url 'project_messages' project.slug as project_messages_url %}
9696
{% include "scanpipe/includes/project_summary_level_item.html" with label="Messages" count=project.message_count url=project_messages_url only %}
97+
{% if project.vulnerability_count %}
98+
{% url 'project_vulnerabilities' project.slug as project_vulnerabilities_url %}
99+
{% include "scanpipe/includes/project_summary_level_item.html" with label="Vulnerabilities" count=project.vulnerability_count url=project_vulnerabilities_url only %}
100+
{% endif %}
97101
</nav>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{% if vulnerability.vulnerability_id|slice:":4" == "VCID" and VULNERABLECODE_URL %}
2+
<a href="{{ VULNERABLECODE_URL }}/vulnerabilities/{{ vulnerability.vulnerability_id }}" target="_blank">
3+
{{ vulnerability.vulnerability_id }}
4+
<i class="fa-solid fa-up-right-from-square is-small"></i>
5+
</a>
6+
{% else %}
7+
{{ vulnerability.vulnerability_id }}
8+
{% endif %}
9+
10+
<ul class="list-unstyled mb-0">
11+
{% for alias in aliases %}
12+
<li>
13+
{% if alias|slice:":3" == "CVE" %}
14+
<a href="https://nvd.nist.gov/vuln/detail/{{ alias }}" target="_blank">{{ alias }}
15+
<i class="fa-solid fa-up-right-from-square mini"></i>
16+
</a>
17+
{% elif alias|slice:":4" == "GHSA" %}
18+
<a href="https://github.com/advisories/{{ alias }}" target="_blank">{{ alias }}
19+
<i class="fa-solid fa-up-right-from-square mini"></i>
20+
</a>
21+
{% elif alias|slice:":3" == "NPM" %}
22+
<a href="https://github.com/nodejs/security-wg/blob/main/vuln/npm/{{ alias|slice:"4:" }}.json" target="_blank">{{ alias }}
23+
<i class="fa-solid fa-up-right-from-square mini"></i>
24+
</a>
25+
{% else %}
26+
{{ alias }}
27+
{% endif %}
28+
</li>
29+
{% endfor %}
30+
</ul>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{% if vulnerability.summary %}
2+
{% if vulnerability.summary|length > 150 %}
3+
<details>
4+
<summary>{{ vulnerability.summary|slice:":150" }}...</summary>
5+
{{ vulnerability.summary|slice:"150:" }}
6+
</details>
7+
{% else %}
8+
{{ vulnerability.summary }}
9+
{% endif %}
10+
{% endif %}

scanpipe/templates/scanpipe/tabset/tab_vulnerabilities.html

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,43 +11,10 @@
1111
{% for vulnerability in tab_data.fields.affected_by_vulnerabilities.value %}
1212
<tr>
1313
<td>
14-
<a href="{{ VULNERABLECODE_URL }}/vulnerabilities/{{ vulnerability.vulnerability_id }}" target="_blank">
15-
{{ vulnerability.vulnerability_id }}
16-
<i class="fa-solid fa-up-right-from-square is-small"></i>
17-
</a>
18-
<ul class="list-unstyled mb-0">
19-
{% for alias in aliases %}
20-
<li>
21-
{% if alias|slice:":3" == "CVE" %}
22-
<a href="https://nvd.nist.gov/vuln/detail/{{ alias }}" target="_blank">{{ alias }}
23-
<i class="fa-solid fa-up-right-from-square mini"></i>
24-
</a>
25-
{% elif alias|slice:":4" == "GHSA" %}
26-
<a href="https://github.com/advisories/{{ alias }}" target="_blank">{{ alias }}
27-
<i class="fa-solid fa-up-right-from-square mini"></i>
28-
</a>
29-
{% elif alias|slice:":3" == "NPM" %}
30-
<a href="https://github.com/nodejs/security-wg/blob/main/vuln/npm/{{ alias|slice:"4:" }}.json" target="_blank">{{ alias }}
31-
<i class="fa-solid fa-up-right-from-square mini"></i>
32-
</a>
33-
{% else %}
34-
{{ alias }}
35-
{% endif %}
36-
</li>
37-
{% endfor %}
38-
</ul>
14+
{% include 'scanpipe/includes/vulnerability_id.html' with vulnerability=vulnerability %}
3915
</td>
4016
<td>
41-
{% if vulnerability.summary %}
42-
{% if vulnerability.summary|length > 150 %}
43-
<details>
44-
<summary>{{ vulnerability.summary|slice:":150" }}...</summary>
45-
{{ vulnerability.summary|slice:"150:" }}
46-
</details>
47-
{% else %}
48-
{{ vulnerability.summary }}
49-
{% endif %}
50-
{% endif %}
17+
{% include 'scanpipe/includes/vulnerability_summary.html' with vulnerability=vulnerability only %}
5118
</td>
5219
<td>
5320
{% for key, value in vulnerability.cdx_vulnerability.analysis.items %}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{% extends "scanpipe/base.html" %}
2+
{% load humanize %}
3+
4+
{% block title %}ScanCode.io: Vulnerabilities{% endblock %}
5+
6+
{% block content %}
7+
<div id="content-header" class="container is-max-widescreen mb-3">
8+
{% include 'scanpipe/includes/navbar_header.html' %}
9+
<section class="mx-5">
10+
{% include 'scanpipe/includes/breadcrumb.html' with linked_project=True current="Vulnerabilities" %}
11+
<div>{{ object_list|length|intcomma }} results</div>
12+
</section>
13+
</div>
14+
15+
<div class="container is-fluid mb-3">
16+
<table class="table is-bordered is-striped is-narrow is-hoverable is-fullwidth break-word">
17+
{% include 'scanpipe/includes/list_view_thead.html' %}
18+
<tbody>
19+
{% for vulnerability in object_list.values %}
20+
<tr>
21+
<td>
22+
{% include 'scanpipe/includes/vulnerability_id.html' with vulnerability=vulnerability %}
23+
</td>
24+
<td>
25+
{% include 'scanpipe/includes/vulnerability_summary.html' with vulnerability=vulnerability only %}
26+
</td>
27+
<td>
28+
{% for obj in vulnerability.affects %}
29+
{{ obj }}<br>
30+
{% endfor %}
31+
</td>
32+
</tr>
33+
{% empty %}
34+
<tr>
35+
<td colspan="42" class="has-text-centered p-3">
36+
No Vulnerabilities found. <a href="?">Clear search and filters</a>
37+
</td>
38+
</tr>
39+
{% endfor %}
40+
</tbody>
41+
</table>
42+
</div>
43+
{% endblock %}

scanpipe/tests/test_commands.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1386,11 +1386,10 @@ def test_scanpipe_management_command_check_compliance_vulnerabilities(self):
13861386
self.assertEqual(cm.exception.code, 1)
13871387
out_value = out.getvalue().strip()
13881388
expected = (
1389-
"2 vulnerable records found:\n"
1390-
"pkg:generic/name@1.0\n"
1391-
" > VCID-cah8-awtr-aaad\n"
1392-
"dependency1\n"
1393-
" > VCID-cah8-awtr-aaad"
1389+
"1 vulnerabilities found:\n"
1390+
"VCID-cah8-awtr-aaad\n"
1391+
" > pkg:generic/name@1.0\n"
1392+
" > dependency1"
13941393
)
13951394
self.assertEqual(expected, out_value)
13961395

scanpipe/tests/test_models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,7 @@ def test_scanpipe_project_vulnerability_properties(self):
678678
"VCID-3": {"vulnerability_id": "VCID-3", "affects": [p2, d2]},
679679
}
680680
self.assertEqual(expected, project.vulnerabilities)
681+
self.assertEqual(4, project.vulnerability_count)
681682

682683
def test_scanpipe_project_get_codebase_config_directory(self):
683684
self.assertIsNone(self.project1.get_codebase_config_directory())

0 commit comments

Comments
 (0)