security: show the fixed versions for each vuln

the data quality is bad, but let's see
This commit is contained in:
Christoph Reiter 2025-09-07 10:13:00 +02:00
parent 3f17254ab1
commit 4ad9115d46
8 changed files with 58 additions and 55 deletions

View File

@ -14,7 +14,7 @@ from typing import NamedTuple, Any
from collections.abc import Iterable from collections.abc import Iterable
from collections.abc import Sequence from collections.abc import Sequence
from pydantic import BaseModel from pydantic import BaseModel
from dataclasses import dataclass from dataclasses import dataclass, field
from packageurl import PackageURL from packageurl import PackageURL
from .appconfig import REPOSITORIES from .appconfig import REPOSITORIES
@ -200,6 +200,7 @@ class Vulnerability:
url: str url: str
severity: Severity severity: Severity
ignored: bool = False ignored: bool = False
unaffected_versions: list[str] = field(default_factory=list)
@property @property
def sort_key(self) -> tuple[bool, int, str, str]: def sort_key(self) -> tuple[bool, int, str, str]:

View File

@ -29,10 +29,19 @@ def parse_cdx(data: bytes) -> dict[str, list[Vulnerability]]:
for ratings in vuln["ratings"]: for ratings in vuln["ratings"]:
severity = Severity(ratings["severity"]) severity = Severity(ratings["severity"])
break break
unaffected_versions = []
for affects in vuln["affects"]:
versions = affects.get("versions", [])
for version in versions:
if version.get("status") == "unaffected" and "version" in version:
unaffected_versions.append(version["version"])
return Vulnerability( return Vulnerability(
id=vuln["id"], id=vuln["id"],
url=vuln["source"]["url"], url=vuln["source"]["url"],
severity=severity) severity=severity,
unaffected_versions=unaffected_versions)
vuln_mapping: dict[str, list[Vulnerability]] = {} vuln_mapping: dict[str, list[Vulnerability]] = {}
for vuln in cdx["vulnerabilities"]: for vuln in cdx["vulnerabilities"]:

View File

@ -1,6 +1,7 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block title %}Base Package: {{ sources[0].name if sources else '' }}{% endblock %} {% block title %}Base Package: {{ sources[0].name if sources else '' }}{% endblock %}
{% block inner_content %} {% block inner_content %}
{% from 'macros.html' import vulnerability_list %}
{% for s in sources %} {% for s in sources %}
<div class="card mb-3"> <div class="card mb-3">
@ -85,11 +86,7 @@
<dt class="col-sm-3 text-sm-end">Vulnerabilities:</dt> <dt class="col-sm-3 text-sm-end">Vulnerabilities:</dt>
<dd class="col-sm-9"> <dd class="col-sm-9">
{% if s.all_vulnerabilities %} {% if s.all_vulnerabilities %}
<ul class="list-unstyled"> {{ vulnerability_list(s) }}
{% for vuln in s.all_vulnerabilities %}
<li {% if vuln.ignored %}style="text-decoration: line-through"{% endif %}><a href="{{ vuln.url }}">{{ vuln.id }}</a> <span class="opacity-75 text-{{vulnerability_color(vuln)}}">({{ vuln.severity }})</span></li>
{% endfor %}
</ul>
{% elif not s.can_have_vulnerabilities %} {% elif not s.can_have_vulnerabilities %}
<span class="text-muted">Not enough metadata for vulnerability reporting</span> <span class="text-muted">Not enough metadata for vulnerability reporting</span>
{% else %} {% else %}

32
app/templates/macros.html Normal file
View File

@ -0,0 +1,32 @@
{% macro vulnerability_color(vuln) %}
{%- if vuln.severity|string == "critical" -%}
danger
{%- elif vuln.severity|string == "high" -%}
warning
{%- else -%}
secondary
{%- endif -%}
{% endmacro %}
{% macro vulnerability_tooltip(s) %}
<span class="mytooltip-onclick">
<span role="button" class="text-{{vulnerability_color(s.worst_active_vulnerability)}}"></span>
<template class="mytooltip-content">
{{ vulnerability_list(s) }}
</template>
</span>
{% endmacro %}
{% macro vulnerability_list(s) %}
<ul class="list-unstyled">
{% for vuln in s.all_vulnerabilities %}
<li {% if vuln.ignored %}style="text-decoration: line-through"{% endif %}>
<a href="{{ vuln.url }}">{{ vuln.id }}</a>
<span class="opacity-75 text-{{vulnerability_color(vuln)}}">({{ vuln.severity }})</span>
{% if not vuln.ignored and vuln.unaffected_versions %}
<br><span>(fixed in {{ vuln.unaffected_versions|join(', ') }})</span>
{% endif %}
</li>
{% endfor %}
</ul>
{% endmacro %}

View File

@ -1,6 +1,7 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block title %}Outdated Packages{% endblock %} {% block title %}Outdated Packages{% endblock %}
{% block inner_content %} {% block inner_content %}
{% from 'macros.html' import vulnerability_tooltip %}
<div class="card mb-3"> <div class="card mb-3">
<div class="card-header"> <div class="card-header">
@ -64,20 +65,11 @@
<td class="text-version">{{ myver }}{% if gitver %} <span class="text-muted small align-text-bottom ps-1">({{ gitver }} in git)</span>{% endif %}</td> <td class="text-version">{{ myver }}{% if gitver %} <span class="text-muted small align-text-bottom ps-1">({{ gitver }} in git)</span>{% endif %}</td>
<td></td> <td></td>
<td class="text-version"><a href="{{ url }}">{{ ver }}</a></td> <td class="text-version"><a href="{{ url }}">{{ ver }}</a></td>
<td>
{% if s.active_vulnerabilities %} {% if s.active_vulnerabilities %}
<td class="mytooltip-onclick"> {{ vulnerability_tooltip(s) }}
<span role="button" class="text-{{vulnerability_color(s.worst_active_vulnerability)}}"></span>
<template class="mytooltip-content">
<ul class="list-unstyled">
{% for vuln in s.all_vulnerabilities %}
<li {% if vuln.ignored %}style="text-decoration: line-through"{% endif %}><a href="{{ vuln.url }}">{{ vuln.id }}</a> <span class="opacity-75 text-{{vulnerability_color(vuln)}}">({{ vuln.severity }})</span></li>
{% endfor %}
</ul>
</template>
</td>
{% else %}
<td></td>
{% endif %} {% endif %}
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@ -87,16 +79,7 @@
{% for s in missing %} {% for s in missing %}
<a href="{{ url_for('base', base_name=s.name) }}">{{ s.realname }}</a> <a href="{{ url_for('base', base_name=s.name) }}">{{ s.realname }}</a>
{%- if s.active_vulnerabilities %} {%- if s.active_vulnerabilities %}
<span class="mytooltip-onclick"> {{ vulnerability_tooltip(s) }}
<span role="button" class="text-{{vulnerability_color(s.worst_active_vulnerability)}}"></span>
<template class="mytooltip-content">
<ul class="list-unstyled">
{% for vuln in s.all_vulnerabilities %}
<li {% if vuln.ignored %}style="text-decoration: line-through"{% endif %}><a href="{{ vuln.url }}">{{ vuln.id }}</a> <span class="opacity-75 text-{{vulnerability_color(vuln)}}">({{ vuln.severity }})</span></li>
{% endfor %}
</ul>
</template>
</span>
{% endif -%} {% endif -%}
{{ ", " if not loop.last else '' }} {{ ", " if not loop.last else '' }}
{% endfor %} {% endfor %}

View File

@ -1,6 +1,7 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block title %}Package: {{ packages[0][1].name if packages else '' }}{% endblock %} {% block title %}Package: {{ packages[0][1].name if packages else '' }}{% endblock %}
{% block inner_content %} {% block inner_content %}
{% from 'macros.html' import vulnerability_list %}
{% for s, p in packages %} {% for s, p in packages %}
<div class="card mb-3"> <div class="card mb-3">
@ -79,11 +80,7 @@
{% if s.all_vulnerabilities %} {% if s.all_vulnerabilities %}
<dt class="col-sm-3 text-sm-end">Vulnerabilities:</dt> <dt class="col-sm-3 text-sm-end">Vulnerabilities:</dt>
<dd class="col-sm-9"> <dd class="col-sm-9">
<ul class="list-unstyled"> {{ vulnerability_list(s) }}
{% for vuln in s.all_vulnerabilities %}
<li {% if vuln.ignored %}style="text-decoration: line-through"{% endif %}><a href="{{ vuln.url }}">{{ vuln.id }}</a> <span class="opacity-75 text-{{vulnerability_color(vuln)}}">({{ vuln.severity }})</span></li>
{% endfor %}
</ul>
</dd> </dd>
{% endif%} {% endif%}

View File

@ -1,6 +1,7 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block title %}Security {% endblock %} {% block title %}Security {% endblock %}
{% block inner_content %} {% block inner_content %}
{% from 'macros.html' import vulnerability_tooltip %}
<div class="card mb-3"> <div class="card mb-3">
<div class="card-header"> <div class="card-header">
@ -35,15 +36,8 @@
<td><a href="{{ url_for('base', base_name=s.name) }}">{{ s.name }}</a></td> <td><a href="{{ url_for('base', base_name=s.name) }}">{{ s.name }}</a></td>
<td class="text-version">{{ s.version }}{% if s.version != s.git_version %} <span class="text-muted small align-text-bottom ps-1">({{ s.git_version }} in git)</span>{% endif %}</td> <td class="text-version">{{ s.version }}{% if s.version != s.git_version %} <span class="text-muted small align-text-bottom ps-1">({{ s.git_version }} in git)</span>{% endif %}</td>
<td class="text-version">{{ s.upstream_version if s.is_outdated_in_git else '' }}</td> <td class="text-version">{{ s.upstream_version if s.is_outdated_in_git else '' }}</td>
<td class="mytooltip-onclick"> <td>
<span role="button" class="text-{{vulnerability_color(s.worst_active_vulnerability)}}"></span> {{ vulnerability_tooltip(s) }}
<template class="mytooltip-content">
<ul class="list-unstyled">
{% for vuln in s.all_vulnerabilities %}
<li {% if vuln.ignored %}style="text-decoration: line-through"{% endif %}><a href="{{ vuln.url }}">{{ vuln.id }}</a> <span class="opacity-75 text-{{vulnerability_color(vuln)}}">({{ vuln.severity }})</span></li>
{% endfor %}
</ul>
</template>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -21,7 +21,7 @@ from fastapi_etag import Etag
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi_etag import add_exception_handler as add_etag_exception_handler from fastapi_etag import add_exception_handler as add_etag_exception_handler
from .appstate import state, get_repositories, Package, Source, DepType, SrcInfoPackage, get_base_group_name, Vulnerability, Severity, PackageKey, find_packages from .appstate import state, get_repositories, Package, Source, DepType, SrcInfoPackage, get_base_group_name, Vulnerability, PackageKey, find_packages
from .utils import extract_upstream_version, version_is_newer_than from .utils import extract_upstream_version, version_is_newer_than
router = APIRouter(default_response_class=HTMLResponse) router = APIRouter(default_response_class=HTMLResponse)
@ -92,16 +92,6 @@ def update_timestamp(request: Request) -> float:
return state.last_update return state.last_update
@context_function("vulnerability_color")
def vulnerability_color(request: Request, vuln: Vulnerability) -> str:
if vuln.severity == Severity.CRITICAL:
return "danger"
elif vuln.severity == Severity.HIGH:
return "warning"
else:
return "secondary"
@context_function("package_url") @context_function("package_url")
def package_url(request: Request, package: Package, name: str | None = None) -> str: def package_url(request: Request, package: Package, name: str | None = None) -> str:
res: str = "" res: str = ""