feat(cve): show all CVEs with fixable indicator in list screen
All checks were successful
Build and Release .deb / build-deb (push) Successful in 21s

- filter_actionable_cves now marks all CVEs with 'fixable' boolean
- cve_list in cache contains ALL CVEs (not just actionable ones)
- CVEListScreen adds 'Corrigeable' column with 🟢/🔴 indicator
- Sidebar counter still shows only actionable CVEs (cve_count)
This commit is contained in:
enzo 2026-05-13 02:35:15 +02:00
parent e22f416500
commit 86eda73eb9
2 changed files with 37 additions and 20 deletions

View file

@ -51,33 +51,34 @@ def _is_cve_actionable(cve_id: str, suite: str = "bookworm") -> bool:
def filter_actionable_cves(cves: list[dict]) -> tuple[list[dict], int]: def filter_actionable_cves(cves: list[dict]) -> tuple[list[dict], int]:
"""Filtre la liste des CVE pour ne garder que les actionnables. """Filtre la liste des CVE pour ajouter un flag 'fixable'.
Retourne (cve_actionnables, cve_total).""" Retourne (cve_avec_flag, nombre_actionnables)."""
if not cves: if not cves:
return [], 0 return [], 0
total = len(cves) def check(cve: dict) -> dict:
def check(cve: dict) -> dict | None:
try: try:
if _is_cve_actionable(cve["id"]): is_fixable = _is_cve_actionable(cve["id"])
return cve cve["fixable"] = is_fixable
return cve
except Exception: except Exception:
pass cve["fixable"] = False
return None return cve
actionable = [] all_cves = []
actionable_count = 0
with ThreadPoolExecutor(max_workers=10) as executor: with ThreadPoolExecutor(max_workers=10) as executor:
futures = [executor.submit(check, cve) for cve in cves] futures = [executor.submit(check, cve) for cve in cves]
for future in futures: for future in futures:
try: try:
result = future.result() cve = future.result()
if result: all_cves.append(cve)
actionable.append(result) if cve.get("fixable"):
actionable_count += 1
except Exception: except Exception:
pass pass
return actionable, total return all_cves, actionable_count
@dataclass @dataclass
@ -218,10 +219,10 @@ def scan_target(target: Target, progress_cb: Callable) -> ScanResult:
# Filtrer les CVE actionnables via l'API Debian # Filtrer les CVE actionnables via l'API Debian
if cve_list: if cve_list:
actionable, total = filter_actionable_cves(cve_list) all_cves, actionable_count = filter_actionable_cves(cve_list)
result.cve_list = actionable result.cve_list = all_cves
result.cve_count = len(actionable) result.cve_count = actionable_count
result.cve_total = total result.cve_total = len(all_cves)
else: else:
result.cve_list = [] result.cve_list = []
result.cve_count = 0 result.cve_count = 0

View file

@ -9,6 +9,21 @@ except Exception:
PYPERCLIP_OK = False PYPERCLIP_OK = False
SCREEN_CSS = """
align: left top;
padding: 1 2;
#toolbar {
height: auto;
dock: top;
margin-bottom: 1;
}
DataTable {
height: 1fr;
border: solid $primary;
}
"""
class PackageListScreen(Screen): class PackageListScreen(Screen):
BINDINGS = [("b", "back", "Retour")] BINDINGS = [("b", "back", "Retour")]
DEFAULT_CSS = """ DEFAULT_CSS = """
@ -75,14 +90,15 @@ class CVEListScreen(Screen):
with Horizontal(id="toolbar"): with Horizontal(id="toolbar"):
yield Button("⬅ Retour", id="cve-back", variant="default") yield Button("⬅ Retour", id="cve-back", variant="default")
table = DataTable(id="cve-table") table = DataTable(id="cve-table")
table.add_columns("CVE-ID", "Paquet", "Lien") table.add_columns("CVE-ID", "Paquet", "Corrigeable", "Lien")
table.cursor_type = "row" table.cursor_type = "row"
for i, cve in enumerate(self.cves): for i, cve in enumerate(self.cves):
cve_id = cve.get("id", "?") cve_id = cve.get("id", "?")
pkg = cve.get("package", "?") pkg = cve.get("package", "?")
url = cve.get("url", "") url = cve.get("url", "")
fixable = "🟢 Oui" if cve.get("fixable") else "🔴 Non"
self.urls[i] = url self.urls[i] = url
table.add_row(cve_id, pkg, url) table.add_row(cve_id, pkg, fixable, url)
yield table yield table
def on_data_table_row_selected(self, event: DataTable.RowSelected): def on_data_table_row_selected(self, event: DataTable.RowSelected):