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
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{% load i18n %}
<ul class="dropdown-menu fw-normal">
{% if vulnerability.aliases %}
<li><h6 class="dropdown-header small">{% trans "Aliases" %}</h6></li>
{% for alias in vulnerability.aliases %}
<li>
{% if alias|slice:':3' == 'CVE' %}
<a class="dropdown-item small" href="https://nvd.nist.gov/vuln/detail/{{ alias }}" target="_blank">{{ alias }} <i class="fa-solid fa-up-right-from-square mini"></i></a>
{% elif alias|slice:':4' == 'GHSA' %}
<a class="dropdown-item small" href="https://github.com/advisories/{{ alias }}" target="_blank">{{ alias }} <i class="fa-solid fa-up-right-from-square mini"></i></a>
{% elif alias|slice:':3' == 'NPM' %}
<a class="dropdown-item small" href="https://github.com/nodejs/security-wg/blob/main/vuln/npm/{{ alias|slice:"4:" }}.json" target="_blank">{{ alias }} <i class="fa-solid fa-up-right-from-square mini"></i></a>
{% else %}
<span class="dropdown-item-text small">{{ alias }}</span>
{% endif %}
</li>
{% endfor %}
{% endif %}
{% if vulnerability.highest_ssvc_decision %}
{% if vulnerability.aliases %}
<li><hr class="dropdown-divider"></li>
{% endif %}
<li><span class="dropdown-item-text small"><strong>SSVC:</strong> {{ vulnerability.highest_ssvc_decision }}</span></li>
{% endif %}
</ul>

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@
{{ vulnerability.advisory_id }}
{% endif %}
</strong>
<div class="mt-1">
{% include 'vulnerabilities/includes/curation_badges.html' with vulnerability=vulnerability vulnerablecode_todos_url=values.vulnerablecode_todos_url only %}
</div>
<div class="mt-2">
{% include 'component_catalog/includes/vulnerability_aliases.html' with aliases=vulnerability.aliases only %}
</div>
Expand Down
15 changes: 15 additions & 0 deletions component_catalog/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
from dejacode_toolkit.vulnerablecode import get_plain_purls
from dje.copier import copy_object
from dje.models import Dataspace
from dje.models import DataspaceConfiguration
from dje.models import ExternalReference
from dje.models import ExternalSource
from dje.models import History
Expand Down Expand Up @@ -3040,6 +3041,20 @@ def test_package_details_view_tab_vulnerabilities_fixed_by_packages(self):
self.assertContains(response, "idna@9.9.9")
self.assertContains(response, "package_url=pkg:pypi/idna@9.9.9")

def test_package_details_view_tab_vulnerabilities_curation_badges(self):
DataspaceConfiguration.objects.create(
dataspace=self.dataspace, vulnerablecode_url="https://vcio/"
)
self.vulnerability1.is_curation = True
self.vulnerability1.todo_count = 2
self.vulnerability1.save()

self.client.login(username=self.super_user.username, password="secret")
response = self.client.get(self.package1.details_url)
self.assertContains(response, '<i class="fas fa-check me-1"></i>Curated')
expected = f'href="https://vcio/advisories/todos/?search={self.vulnerability1.advisory_id}"'
self.assertContains(response, expected)

def test_vulnerablecode_get_plain_purls(self):
purls = get_plain_purls(packages=[])
self.assertEqual([], purls)
Expand Down
2 changes: 2 additions & 0 deletions component_catalog/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
from dejacode_toolkit.scancodeio import ScanStatus
from dejacode_toolkit.scancodeio import get_package_download_url
from dejacode_toolkit.scancodeio import get_scan_results_as_file_url
from dejacode_toolkit.vulnerablecode import VulnerableCode
from dje import tasks
from dje.client_data import add_client_data
from dje.models import DejacodeUser
Expand Down Expand Up @@ -272,6 +273,7 @@ def tab_vulnerabilities(self):

context = {
"vulnerabilities": vulnerabilities,
"vulnerablecode_todos_url": VulnerableCode(self.object.dataspace).advisory_todos_url,
}

return {
Expand Down
56 changes: 28 additions & 28 deletions dejacode/static/css/dejacode_bootstrap.css
Original file line number Diff line number Diff line change
Expand Up @@ -413,43 +413,43 @@ table.vulnerabilities-table .column-summary {
}

/* -- Vulnerability tab -- */
#tab_vulnerabilities .column-advisory_uid {
width: 240px;
#tab_vulnerabilities .vulnerabilities-collapse-toggle.collapsed .fa-chevron-down {
transform: rotate(-90deg);
}
#tab_vulnerabilities .column-affected_packages {
min-width: 310px;
width: 310px;
#tab_vulnerabilities .vulnerabilities-card:has(> .collapse:not(.show)) > .card-header {
border-bottom: 0;
border-radius: var(--bs-card-inner-border-radius);
}
#tab_vulnerabilities .column-triage_action {
min-width: 165px;
}
#tab_vulnerabilities .column-action {
width: 30px;
}
#tab_vulnerabilities .column-exploitability {
width: 140px;
/* Fixed layout keeps the columns aligned across the package cards */
#tab_vulnerabilities .product-vulnerabilities-table {
table-layout: fixed;
}
#tab_vulnerabilities .column-weighted_severity {
width: 105px;
#tab_vulnerabilities .product-vulnerabilities-table thead tr th {
font-size: 0.8125rem;
font-weight: 600;
color: var(--bs-secondary-color);
}
#tab_vulnerabilities .column-risk_score,
#tab_vulnerabilities .column-weighted_risk_score{
width: 80px;
#tab_vulnerabilities .product-vulnerabilities-table .column-advisory_uid {
width: 25%;
}
#tab_vulnerabilities .column-summary {
width: 300px;
#tab_vulnerabilities .vulnerability-summary {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden;
}
#tab_vulnerabilities .column-vulnerability_analyses__state {
min-width: 100px;
#tab_vulnerabilities .product-vulnerabilities-table .column-risk_score {
width: 50px;
}
#tab_vulnerabilities .column-vulnerability_analyses__justification {
min-width: 130px;
#tab_vulnerabilities .product-vulnerabilities-table .column-exploitability {
width: 120px;
}
#tab_vulnerabilities .column-vulnerability_analyses__responses {
min-width: 120px;
#tab_vulnerabilities .product-vulnerabilities-table .column-triage_action {
width: 20%;
}
#tab_vulnerabilities .column-vulnerability_analyses__is_reachable {
width: 80px;
#tab_vulnerabilities .product-vulnerabilities-table .column-action {
width: 56px;
}
/* -- Vulnerability analysis modal -- */
#vulnerability-analysis-modal #div_id_responses .form-check {
Expand Down
7 changes: 7 additions & 0 deletions dejacode_toolkit/tests/test_vulnerablecode.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ class VulnerableCodenTestCase(TestCase):
def setUp(self):
self.service = VulnerableCode(make_dataspace())

def test_advisory_todos_url(self):
expected = "https://public.vulnerablecode.io/advisories/todos/"
self.assertEqual(expected, self.service.advisory_todos_url)

with patch.object(VulnerableCode, "is_configured", return_value=False):
self.assertIsNone(self.service.advisory_todos_url)

def test_get_session_retry_configuration(self):
session = self.service.get_session()
adapter = session.get_adapter("https://example.com")
Expand Down
6 changes: 6 additions & 0 deletions dejacode_toolkit/vulnerablecode.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ def get_session(self):
session.mount("http://", adapter)
return session

@property
def advisory_todos_url(self):
"""Return the URL of the advisory curation ToDos list, when the service is configured."""
if self.is_configured():
return f"{self.service_url.rstrip('/')}/advisories/todos/"

def get_vulnerabilities_by_purl(
self,
purl,
Expand Down
2 changes: 1 addition & 1 deletion dje/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -966,7 +966,7 @@ def update_from_data(self, user, data, override=False, override_unknown=False):

current_value = getattr(self, field_name, None)
update_conditions = [
not current_value,
not current_value and current_value != value,
current_value != value and override,
current_value == "unknown" and override_unknown,
]
Expand Down
12 changes: 7 additions & 5 deletions dje/templates/tabs/pagination.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% load humanize %}
<div class="row align-items-end">
<div class="row gx-2 align-items-start">
<div class="col mb-2">
<ul class="nav nav-pills">
<li class="nav-item">
Expand All @@ -11,7 +11,7 @@
<div class="h6 mt-2 mb-0 smaller">
{% if page_obj.paginator.count != total_count %}
{{ page_obj.paginator.count|intcomma }} of
<a href="#" hx-get="{{ request.path }}?all=true#{{ tab_id }}" hx-target="{{ tab_id_html }}">
<a href="{{ object.get_absolute_url }}#{{ tab_id }}">
{{ total_count }} results
</a>
{% else %}
Expand All @@ -23,9 +23,11 @@
{% include extra_nav_item_template %}
{% endif %}
</ul>
<div class="mt-1">
{% include 'includes/filters_breadcrumbs.html' with filterset=filterset fragment=tab_id only %}
</div>
{% if not hide_filters_breadcrumbs %}
<div class="mt-1">
{% include 'includes/filters_breadcrumbs.html' with filterset=filterset fragment=tab_id only %}
</div>
{% endif %}
</div>
<div class="col-auto">
{% include 'pagination/object_list_pagination.html' with hx_target=tab_id_html %}
Expand Down
25 changes: 25 additions & 0 deletions dje/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from django.contrib.admin.widgets import AdminTextInputWidget
from django.forms import widgets
from django.forms.utils import flatatt
from django.utils.html import escape
from django.utils.html import format_html
from django.utils.html import mark_safe
from django.utils.http import urlencode
Expand Down Expand Up @@ -100,6 +101,30 @@ def __init__(self, attrs=None, choices=(), *args, **kwargs):
self.right_align = True


class LabeledDropDownWidget(DropDownWidget):
"""Render as a button displaying the filter label and the selected choice label."""

dropdown_template = """
<div class="dropdown">
<button type="button" class="btn btn-sm btn-outline-dark dropdown-toggle {active}"
data-bs-toggle="dropdown" aria-expanded="false" aria-label="{label} filter">
<span class="opacity-75">{label}:</span> {link_content}
</button>
{menu}
</div>
"""

def get_selected_label(self, value):
if not value:
return _("All")
choice_labels = {str(choice_value): label for choice_value, label in self.choices}
return choice_labels.get(str(value), value)

def render(self, name, value, attrs=None, renderer=None, choices=()):
self.link_content = escape(self.get_selected_label(value))
return super().render(name, value, attrs, renderer, choices)


class DropDownAsListWidget(DropDownRightWidget):
dropdown_template = """
<li class="dropdown {active}">
Expand Down
21 changes: 13 additions & 8 deletions docs/howto-4-product-vulnerability-analysis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,26 @@ To begin analyzing vulnerabilities for a Product:
3. Click on the :guilabel:`Vulnerabilities` tab to view all vulnerabilities affecting
the Product.

- The tab lists vulnerabilities associated with all packages linked to the Product.
- Use filters and sorting options to prioritize specific vulnerabilities based on
criteria such as **risk score**, **exploitability**, **severity**, or
**exploitability**.
- The tab lists vulnerabilities associated with all packages linked to the Product,
grouped by package.
- Use filters to prioritize specific vulnerabilities based on criteria such as
**risk**, **recommendation**, **analysis**, or **reachability**.

.. image:: images/howto-4-product-vulnerability-analysis/vulnerability-row.jpg

2. Reviewing Vulnerabilities
----------------------------

The Product :guilabel:`Vulnerabilities` tab provides a detailed row for each
vulnerability, enabling in-depth review and understanding of its potential impact.
The Product :guilabel:`Vulnerabilities` tab provides a card for each affected package,
with a detailed row for each vulnerability, enabling in-depth review and understanding
of its potential impact.

.. image:: images/howto-4-product-vulnerability-analysis/vulnerabilities-tab.jpg

Each entry includes the vulnerability ID, its aliases, severity, exploitability, and
risk score, along with links to the affected packages.
Each card header includes a link to the affected package, its risk, and a summary of
its vulnerabilities, known exploits, and analysis progress. Each entry includes the
vulnerability ID, its summary, exploitability, and risk score. The aliases are
available from the info icon next to the vulnerability ID.

.. seealso::
Refer to :ref:`reference_vulnerability_management` for a complete description of
Expand All @@ -53,6 +56,8 @@ DejaCode enables teams to conduct a thorough analysis of each vulnerability:
- Open the :guilabel:`Vulnerabilities` tab in the Product view.
- Use the **"Edit Analysis"** link on the right side of each row to open the
**"Vulnerability Analysis"** form.
- Use the **"Apply analysis preset"** link, left of the **"Edit Analysis"** link, to
apply an Analysis Preset to a vulnerability not analyzed yet.

.. image:: images/howto-4-product-vulnerability-analysis/analysis-form.jpg

Expand Down
2 changes: 1 addition & 1 deletion docs/howto-7-vulnerability-triage-configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ A ruleset has no effect until it is assigned to one or more products.

1. Open a product detail page.
2. Navigate to the :guilabel:`Vulnerabilities` tab.
3. Click :guilabel:`Manage Triage Rulesets` in the triage panel header.
3. Click :guilabel:`Manage triage rules` in the tab's toolbar.
4. In the modal that opens, select the rulesets you want to assign to this product.
5. Click :guilabel:`Save`.

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/images/tutorial-4-vulnerabilities/vulnerabilities-tab.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions docs/reference-vulnerability-management.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ its user interface. Below are the key areas where vulnerabilities are displayed:
Refer to the **API documentation** from the :guilabel:`Tools` menu for detailed
guidance on endpoints and usage.

In the Global Vulnerabilities List and the :guilabel:`Vulnerabilities` tabs, a
**Curated** badge is displayed next to the ID of a curation advisory. Click the badge to
list the advisories curated by this curation advisory. A **ToDos** badge displays the
number of open curation ToDos reported by VulnerableCode for the advisory. Click the
badge to curate the advisory in VulnerableCode.

These features ensure that vulnerability information is seamlessly integrated into the
DejaCode platform, making it easier to assess, prioritize, and manage risks across your
software products.
Expand Down Expand Up @@ -156,6 +162,17 @@ accurate assessment and management. Below is a description of the key fields:
by the exploitability score, capped at 10.
**A higher risk score indicates a greater potential threat.**

- **is_curation**:
Indicates whether this advisory is a **curation advisory**, containing data reviewed
manually.

- **curating_advisories**:
A list of URLs of the advisories curated by this curation advisory.

- **todo_count**:
The number of open curation **ToDos** (data issues such as conflicting affected
packages) reported by VulnerableCode for this advisory.

These fields collectively provide a comprehensive view of each vulnerability,
supporting informed decision-making in the context of vulnerability management.

Expand Down
Loading
Loading