From 13d826ecd2322bc78b74719a954f9a1568640d39 Mon Sep 17 00:00:00 2001 From: enzo Date: Wed, 13 May 2026 04:20:14 +0200 Subject: [PATCH 1/3] feat(cve): add severity and vector columns to CVE list - Parse [remote|local] and [severity] from debsecan output - Display Severity and Vector columns in CVEListScreen - Severity values: unimportant, low, medium, high, critical --- full_updater/backend/scanner.py | 11 +++++++++-- full_updater/ui/detail_screens.py | 6 ++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/full_updater/backend/scanner.py b/full_updater/backend/scanner.py index 4d2d768..2ae08ad 100644 --- a/full_updater/backend/scanner.py +++ b/full_updater/backend/scanner.py @@ -226,9 +226,16 @@ def scan_cve(target: Target) -> tuple[bool, list[dict[str, str]], str]: cves = [] for line in stdout.splitlines(): - m = re.match(r"(CVE-\d{4}-\d+)\s+(\S+)", line) + # Format: CVE-XXXX-XXXX package [remote|local] [severity] - description + m = re.match(r"(CVE-\d{4}-\d+)\s+(\S+)(?:\s+\[(remote|local)\])?(?:\s+\[(unimportant|low|medium|high|critical)\])?", line) if m: - cves.append({"id": m.group(1), "package": m.group(2), "url": f"https://security-tracker.debian.org/tracker/{m.group(1)}"}) + cves.append({ + "id": m.group(1), + "package": m.group(2), + "vector": m.group(3) or "?", + "severity": m.group(4) or "?", + "url": f"https://security-tracker.debian.org/tracker/{m.group(1)}" + }) return True, cves, "" diff --git a/full_updater/ui/detail_screens.py b/full_updater/ui/detail_screens.py index 78b31b4..5e95e10 100644 --- a/full_updater/ui/detail_screens.py +++ b/full_updater/ui/detail_screens.py @@ -90,15 +90,17 @@ class CVEListScreen(Screen): with Horizontal(id="toolbar"): yield Button("⬅ Retour", id="cve-back", variant="default") table = DataTable(id="cve-table") - table.add_columns("CVE-ID", "Paquet", "Corrigeable", "Lien") + table.add_columns("CVE-ID", "Paquet", "Severite", "Vecteur", "Corrigeable", "Lien") table.cursor_type = "row" for i, cve in enumerate(self.cves): cve_id = cve.get("id", "?") pkg = cve.get("package", "?") url = cve.get("url", "") + severity = cve.get("severity", "?") + vector = cve.get("vector", "?") fixable = "🟢 Oui" if cve.get("fixable") else "🔴 Non" self.urls[i] = url - table.add_row(cve_id, pkg, fixable, url) + table.add_row(cve_id, pkg, severity, vector, fixable, url) yield table def on_data_table_row_selected(self, event: DataTable.RowSelected): From 9107009370de3b4f2827a3d2849f1ed0234c3e34 Mon Sep 17 00:00:00 2001 From: enzo Date: Wed, 13 May 2026 04:23:45 +0200 Subject: [PATCH 2/3] fix(cve): robust parsing of [remote|local] and [severity] flags - Use re.findall to extract all bracketed flags instead of positional regex - Fixes issue where optional groups were not captured correctly --- full_updater/backend/scanner.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/full_updater/backend/scanner.py b/full_updater/backend/scanner.py index 2ae08ad..9e317bb 100644 --- a/full_updater/backend/scanner.py +++ b/full_updater/backend/scanner.py @@ -227,13 +227,22 @@ def scan_cve(target: Target) -> tuple[bool, list[dict[str, str]], str]: cves = [] for line in stdout.splitlines(): # Format: CVE-XXXX-XXXX package [remote|local] [severity] - description - m = re.match(r"(CVE-\d{4}-\d+)\s+(\S+)(?:\s+\[(remote|local)\])?(?:\s+\[(unimportant|low|medium|high|critical)\])?", line) + m = re.match(r"(CVE-\d{4}-\d+)\s+(\S+)", line) if m: + # Extraire tous les flags entre crochets + flags = re.findall(r"\[(\w+)\]", line) + vector = "?" + severity = "?" + for f in flags: + if f in ("remote", "local"): + vector = f + elif f in ("unimportant", "low", "medium", "high", "critical"): + severity = f cves.append({ "id": m.group(1), "package": m.group(2), - "vector": m.group(3) or "?", - "severity": m.group(4) or "?", + "vector": vector, + "severity": severity, "url": f"https://security-tracker.debian.org/tracker/{m.group(1)}" }) return True, cves, "" From 11ada690a121c3759b334a66cc692a37dd46de9a Mon Sep 17 00:00:00 2001 From: enzo Date: Wed, 13 May 2026 04:33:05 +0200 Subject: [PATCH 3/3] fix(cve): enrich CVEs with severity/vector from Debian Security Tracker HTML - debsecan default format has no [remote|local] or [severity] flags - Now fetches severity and vector from security-tracker.debian.org HTML - scan_cve uses default format (no --format report) - enrich_cves replaces filter_actionable_cves, adds severity+vector --- full_updater/backend/scanner.py | 58 +++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/full_updater/backend/scanner.py b/full_updater/backend/scanner.py index 9e317bb..f00185a 100644 --- a/full_updater/backend/scanner.py +++ b/full_updater/backend/scanner.py @@ -41,6 +41,29 @@ def _fetch_cve_html(cve_id: str) -> str: return "" +def _enrich_cve(cve_id: str) -> dict[str, str]: + """Récupère la sévérité et le vecteur d'attaque depuis le HTML Debian Security Tracker.""" + html = _fetch_cve_html(cve_id) + if not html: + return {"severity": "?", "vector": "?"} + + result = {"severity": "?", "vector": "?"} + + # Extraction de la sévérité depuis le bloc "Vulnerable and fixed packages" + # Recherche du motif: remote ou local + vector_match = re.search(r']*>\s*(remote|local)\s*', html, re.IGNORECASE) + if vector_match: + result["vector"] = vector_match.group(1).lower() + + # Extraction de la sévérité depuis les notes ou le tableau + # Format courant: [low], [medium], [high], [critical] dans les notes + severity_match = re.search(r'\b(unimportant|low|medium|high|critical)\b', html, re.IGNORECASE) + if severity_match: + result["severity"] = severity_match.group(1).lower() + + return result + + def _is_cve_actionable(cve_id: str, suite: str = "bookworm") -> bool: """Retourne True si la CVE est marquée 'fixed' pour le suite donné dans le HTML.""" html = _fetch_cve_html(cve_id) @@ -54,16 +77,20 @@ def _is_cve_actionable(cve_id: str, suite: str = "bookworm") -> bool: return bool(pattern.search(html)) -def filter_actionable_cves(cves: list[dict]) -> tuple[list[dict], int]: - """Filtre la liste des CVE pour ajouter un flag 'fixable'. - Retourne (cve_avec_flag, nombre_actionnables).""" +def enrich_cves(cves: list[dict]) -> tuple[list[dict], int]: + """Enrichit les CVEs avec fixable, severity, vector via l'API Debian. + Retourne (cve_enrichies, nombre_actionnables).""" if not cves: return [], 0 def check(cve: dict) -> dict: try: - is_fixable = _is_cve_actionable(cve["id"]) - cve["fixable"] = is_fixable + # Vérifie si corrigeable + cve["fixable"] = _is_cve_actionable(cve["id"]) + # Enrichit avec severity et vector + enriched = _enrich_cve(cve["id"]) + cve["severity"] = enriched["severity"] + cve["vector"] = enriched["vector"] return cve except Exception: cve["fixable"] = False @@ -219,30 +246,21 @@ def scan_apt(target: Target) -> tuple[bool, list[dict[str, str]], str]: def scan_cve(target: Target) -> tuple[bool, list[dict[str, str]], str]: - cmd = ["debsecan", "--format", "report", "--suite", "bookworm"] if target.is_host else ["pct", "exec", target.target_id, "--", "debsecan", "--format", "report", "--suite", "bookworm"] + # Format par défaut (pas de --format) retourne: CVE-ID package + cmd = ["debsecan", "--suite", "bookworm"] if target.is_host else ["pct", "exec", target.target_id, "--", "debsecan", "--suite", "bookworm"] ok, stdout, stderr = run_cmd(cmd, timeout=120) if not ok: return False, [], stderr or stdout cves = [] for line in stdout.splitlines(): - # Format: CVE-XXXX-XXXX package [remote|local] [severity] - description m = re.match(r"(CVE-\d{4}-\d+)\s+(\S+)", line) if m: - # Extraire tous les flags entre crochets - flags = re.findall(r"\[(\w+)\]", line) - vector = "?" - severity = "?" - for f in flags: - if f in ("remote", "local"): - vector = f - elif f in ("unimportant", "low", "medium", "high", "critical"): - severity = f cves.append({ "id": m.group(1), "package": m.group(2), - "vector": vector, - "severity": severity, + "vector": "?", + "severity": "?", "url": f"https://security-tracker.debian.org/tracker/{m.group(1)}" }) return True, cves, "" @@ -273,9 +291,9 @@ def scan_target(target: Target, progress_cb: Callable) -> ScanResult: if not cve_ok: result.error = cve_err - # Filtrer les CVE actionnables via l'API Debian + # Enrichir les CVE via l'API Debian (severity, vector, fixable) if cve_list: - all_cves, actionable_count = filter_actionable_cves(cve_list) + all_cves, actionable_count = enrich_cves(cve_list) result.cve_list = all_cves result.cve_count = actionable_count result.cve_total = len(all_cves)