Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 75 additions & 1 deletion vulnerabilities/templates/vulnerability_details.html
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,20 @@
{{ vulnerability.risk_score }}
</td>
</tr>
{% if epss_data %}
<tr>
<td class="two-col-left">
<span class="has-tooltip-multiline has-tooltip-black has-tooltip-arrow has-tooltip-text-left"
data-tooltip="The EPSS score represents the probability [0-1] of exploitation in the wild in the next 30 days. Percentile indicates the proportion of all scored vulnerabilities with the same or a lower score.">
EPSS Score
</span>
</td>
<td class="two-col-right wrap-strings">
{{ epss_data.score }}
<span class="has-text-grey ml-2">({{ epss_data.percentile }} percentile)</span>
</td>
</tr>
{% endif %}
<tr>
<td class="two-col-left"
data-tooltip="Risk expressed as a number ranging from 0 to 10. It is calculated by multiplying
Expand Down Expand Up @@ -502,7 +516,7 @@
{% endfor %}
</div>


<div class="tab-div content" data-content="epss">
{% if epss_data %}
<div class="has-text-weight-bold tab-nested-div ml-1 mb-1 mt-1">
Expand Down Expand Up @@ -541,6 +555,14 @@
{% endif %}
</tbody>
</table>

<div class="has-text-weight-bold tab-nested-div ml-1 mb-1 mt-4">
EPSS Score Trend
</div>
<div style="max-width: 800px; margin-top: 1rem;">
<canvas id="epssChart"></canvas>
</div>
<script id="epss-history-data" type="application/json">{{ epss_history_json }}</script>
{% else %}
<p>No EPSS data available for this vulnerability.</p>
{% endif %}
Expand Down Expand Up @@ -597,6 +619,58 @@
{% endif %}

<script src="{% static 'js/main.js' %}" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js" crossorigin="anonymous"></script>
<script>
(function () {
var canvas = document.getElementById('epssChart');
if (!canvas) return;

var raw = document.getElementById('epss-history-data');
if (!raw) return;

var history = JSON.parse(raw.textContent || '[]');
if (history.length === 0) return;

new Chart(canvas, {
type: 'line',
data: {
labels: history.map(function (e) { return e.date; }),
datasets: [{
label: 'EPSS Score',
data: history.map(function (e) { return e.score; }),
borderColor: '#3273dc',
backgroundColor: 'rgba(50, 115, 220, 0.08)',
pointRadius: history.length > 60 ? 2 : 4,
tension: 0.1,
fill: true,
}]
},
options: {
responsive: true,
scales: {
y: {
min: 0,
max: 1,
title: { display: true, text: 'EPSS Score (0–1)' }
},
x: {
title: { display: true, text: 'Date' }
}
},
plugins: {
tooltip: {
callbacks: {
afterLabel: function (ctx) {
var p = history[ctx.dataIndex].percentile;
return p != null ? 'Percentile: ' + p : '';
}
}
}
}
}
});
})();
</script>

<script>
function goToTab(tabName) {
Expand Down
27 changes: 22 additions & 5 deletions vulnerabilities/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
# See https://aboutcode.org for more information about nexB OSS projects.
#
import json
import logging

from cvss.exceptions import CVSS2MalformedError
Expand Down Expand Up @@ -386,14 +387,29 @@ def get_context_data(self, **kwargs):
):
logging.error(f"CVSSMalformedError for {severity.scoring_elements}")

epss_severity = vulnerability.severities.filter(scoring_system="epss").first()
epss_severities = vulnerability.severities.filter(
scoring_system="epss"
).order_by("published_at")

epss_data = None
if epss_severity:
epss_history_json = "[]"
if epss_severities.exists():
latest = epss_severities.last()
epss_data = {
"percentile": epss_severity.scoring_elements,
"score": epss_severity.value,
"published_at": epss_severity.published_at,
"percentile": latest.scoring_elements,
"score": latest.value,
"published_at": latest.published_at,
}
epss_history_json = json.dumps(
[
{
"date": e.published_at.strftime("%Y-%m-%d") if e.published_at else None,
"score": float(e.value) if e.value else None,
"percentile": float(e.scoring_elements) if e.scoring_elements else None,
}
for e in epss_severities
]
)

context.update(
{
Expand All @@ -407,6 +423,7 @@ def get_context_data(self, **kwargs):
"status": vulnerability.get_status_label,
"history": vulnerability.history,
"epss_data": epss_data,
"epss_history_json": epss_history_json,
}
)
return context
Expand Down