mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-28 21:17:36 +00:00
The generator only rejected a candidate when re.escape(path) exactly equaled a stored pattern key, so it never matched the live cve_2024 category's hand-written regexes (literal hyphens, \d classes) — the dedup feature was inert against real WAF rules. Now a candidate is covered when any existing pattern, treated as a regex (as the WAF engine does), matches the raw path; invalid stored regexes are skipped, never raised. Also harden existing_patterns() against valid-but-non-object JSON (e.g. "[]"/"null") which previously raised AttributeError on data.get(...), breaking the read-only preview/dry-run best-effort contract. Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
84 lines
3.1 KiB
Python
84 lines
3.1 KiB
Python
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
|
# Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
|
# Source-Disclosed License — All rights reserved except as expressly granted.
|
|
# See LICENCE-CMSD-1.0.md for terms.
|
|
|
|
"""
|
|
SecuBox-Deb :: cve-triage — orchestration du générateur
|
|
CyberMind — https://cybermind.fr
|
|
|
|
Parcourt le sous-ensemble Nuclei vendored, applique KEV + extraction + les deux
|
|
barrières de présence, retourne (retenus, rejets-avec-raison). N'écrit rien :
|
|
la CLI décide (dry-run par défaut).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
from .extract import Candidate, Rejection, extract, is_kev
|
|
from .presence import is_appliance, should_generate
|
|
|
|
|
|
def _covered_by(path: str, existing: dict[str, str]) -> str | None:
|
|
"""Nom de la catégorie dont un pattern (traité comme regex, ce que fait le
|
|
moteur WAF) matche `path` — donc la sonde detect y serait redondante. Un
|
|
pattern stocké qui n'est pas une regex valide ne peut pas matcher : on le
|
|
saute (jamais d'erreur). None si aucune couverture."""
|
|
for pattern, category in existing.items():
|
|
try:
|
|
if re.search(pattern, path):
|
|
return category
|
|
except re.error:
|
|
continue
|
|
return None
|
|
|
|
|
|
def generate(subset_dir: Path, present: set[str], present_complete: bool,
|
|
*, existing: dict[str, str] | None = None
|
|
) -> tuple[list[Candidate], list[tuple[str, str]]]:
|
|
"""(retenus, [(fichier, raison de rejet)]). `existing` mappe pattern-string
|
|
-> catégorie (voir emit.existing_patterns) : un candidat dont le pattern y
|
|
figure déjà est rejeté (redondance ; une catégorie bloquante l'emporterait
|
|
sur notre detect). None/{} => aucune dédup."""
|
|
existing = existing or {}
|
|
kept: list[Candidate] = []
|
|
rejected: list[tuple[str, str]] = []
|
|
|
|
for p in sorted(Path(subset_dir).glob("*.yaml")):
|
|
try:
|
|
tmpl = yaml.safe_load(p.read_text(encoding="utf-8"))
|
|
except (OSError, yaml.YAMLError) as exc:
|
|
rejected.append((p.name, f"unparseable: {exc}"))
|
|
continue
|
|
if not isinstance(tmpl, dict):
|
|
rejected.append((p.name, "not a template mapping"))
|
|
continue
|
|
|
|
if not is_kev(tmpl):
|
|
rejected.append((p.name, "not KEV (not known-exploited)"))
|
|
continue
|
|
|
|
result = extract(tmpl)
|
|
if isinstance(result, Rejection):
|
|
rejected.append((p.name, result.reason))
|
|
continue
|
|
|
|
if not should_generate(result, present, present_complete):
|
|
why = ("inventory incomplete → present" if not present_complete
|
|
else "not an appliance family" if not is_appliance(result)
|
|
else "product is present on this box")
|
|
rejected.append((p.name, why))
|
|
continue
|
|
|
|
covered_by = _covered_by(result.path, existing)
|
|
if covered_by is not None:
|
|
rejected.append((p.name, f"already covered by {covered_by}"))
|
|
continue
|
|
|
|
kept.append(result)
|
|
|
|
return kept, rejected
|