mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-29 09:14:33 +00:00
docs(spec): alertes CVE récurrentes et réelles
Le module secubox-cve-triage existe déjà et fait plus que prévu (dpkg, NVD, CVSS, EPSS réel, flux KEV CISA, triage, panel). Il lui manque une gâchette (aucun .timer), une voix (zéro notify) et un filtre anti-bruit. Ce spec livre l'appelant, pas un module. Filtre: KEV ∧ installé ∧ (exposé public ∨ rançongiciel) — un ET, pas un OU. L'exposition vient de l'inventaire secubox-profiles livré ce jour (16 modules publics sur 187). Anti-bruit: alerte sur le delta, ré-alerte sur escalade, acquittement, digest, plancher anti-tempête. CrowdSec CTI écarté: clé obligatoire, 40 requêtes/mois en gratuit, dépendance cloud pour une donnée que le KEV CISA fournit librement (1647 CVE, sans clé). XMPP chiffré honnêtement: le module jabber ne sait pas envoyer: c'est un gestionnaire Prosody. Le sink exige slixmpp (présent dans bookworm), un compte bot, un secret et un client — une tâche isolée, pas un branchement. Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
This commit is contained in:
parent
ea25a23831
commit
a33e3c0092
230
docs/superpowers/specs/2026-07-17-cve-alertes-design.md
Normal file
230
docs/superpowers/specs/2026-07-17-cve-alertes-design.md
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
# Alertes CVE récurrentes et réelles — Conception
|
||||
|
||||
**Date** : 2026-07-17
|
||||
**Statut** : conception validée, prête pour le plan d'implémentation
|
||||
**Auteur** : Gérald Kerma <devel@cybermind.fr>
|
||||
|
||||
---
|
||||
|
||||
## Objectif
|
||||
|
||||
Être **prévenu** quand un CVE réellement exploité touche un paquet installé sur un module exposé —
|
||||
récurremment, et sans bruit.
|
||||
|
||||
## Ce qui existe déjà (et qu'il ne faut PAS reconstruire)
|
||||
|
||||
`packages/secubox-cve-triage/` est un module complet, installé, avec un panel et une entrée menu :
|
||||
|
||||
| Capacité | État |
|
||||
|---|---|
|
||||
| Inventaire des paquets (dpkg/apt) | ✅ `POST /scan/packages`, `GET /packages` |
|
||||
| Recherche NVD | ✅ `POST /scan/vulnerabilities`, `GET /cves` |
|
||||
| Scoring CVSS | ✅ |
|
||||
| **EPSS** (probabilité d'exploitation) | ✅ réel — `get_epss_scores`, batché par 100, `POST /epss` |
|
||||
| **Flux KEV CISA** | ✅ `POST /feeds/kev` — `CISA_KEV_URL`, gratuit, sans clé |
|
||||
| Priorisation | ✅ `GET /prioritized` |
|
||||
| Workflow de triage | ✅ `POST /triage/{cve_id}`, `GET /triage` |
|
||||
| Panel webui | ✅ `www/cve-triage/` |
|
||||
| Service systemd | ✅ (l'API) |
|
||||
|
||||
**Ce qui manque — exactement le besoin :**
|
||||
|
||||
1. **Aucune récurrence.** Aucun `.timer` dans le paquet. Chaque scan, chaque rafraîchissement de flux
|
||||
est un `POST` manuel : la donnée périme dès que personne ne clique.
|
||||
2. **Aucune alerte.** Zéro `notify` / `webhook` / `smtp` / `ntfy` dans tout le module. Il calcule et
|
||||
se tait.
|
||||
3. **Aucun filtre anti-bruit.** `GET /cves` rend tout ; rien ne distingue « à savoir » de « agis
|
||||
maintenant ».
|
||||
|
||||
Le module est **une bibliothèque qui attend un appelant**. Ce spec livre l'appelant : une gâchette,
|
||||
un filtre, une voix.
|
||||
|
||||
## Le réel mesuré (2026-07-17)
|
||||
|
||||
- **KEV CISA** : 1647 CVE activement exploités, 1,5 Mo JSON, **sans clé API**, champs `cveID`,
|
||||
`vendorProject`, `product`, `dateAdded`, `knownRansomwareCampaignUse`, `requiredAction`.
|
||||
- **CrowdSec CTI** (l'alternative envisagée) : clé obligatoire, **40 requêtes/mois** en gratuit
|
||||
(120 en « premium free ») — une démo, pas une intégration ; le volume utile est payant. Écarté :
|
||||
dépendance cloud pour une donnée que KEV fournit librement. Ce que CrowdSec a en plus (trending,
|
||||
IP attaquantes) vient de leur flotte mondiale de capteurs et n'est pas auto-hébergeable.
|
||||
- **Inventaire profils** (`secubox-profiles`, livré ce jour) : 187 modules, dont **16 en
|
||||
`exposure=public`**. C'est le discriminant qui manquait pour dire « exposé ».
|
||||
- **`python3-slixmpp`** est dans Debian bookworm (1.8.3-1) → XMPP faisable sans pip.
|
||||
- **Aucun notifieur partagé** n'existe dans `secubox_core` : ~110 modules ne savent pas alerter.
|
||||
|
||||
---
|
||||
|
||||
## Architecture retenue
|
||||
|
||||
**Un notifieur partagé + un filtre + un timer.**
|
||||
|
||||
Alternative écartée : un notifieur interne à `cve-triage`. Plus contenu, mais garantit une réécriture
|
||||
dès qu'un deuxième module voudra alerter — et il y en aura un (WAF, sentinelle-gsm, watchdog ont tous
|
||||
de quoi alerter et se taisent). Le notifieur va dans `secubox_core` : **purement additif** (rien ne
|
||||
change tant qu'on ne l'appelle pas), donc rayon de souffle nul malgré les ~110 importateurs.
|
||||
|
||||
---
|
||||
|
||||
## ① Le filtre — ce qui rend une alerte « réelle »
|
||||
|
||||
```
|
||||
ALERTE ⟺ CVE ∈ KEV (exploité pour de vrai, pas théorique)
|
||||
∧ paquet installé sur CETTE box (pas dans l'absolu)
|
||||
∧ ( module exposé public (joignable depuis dehors)
|
||||
∨ knownRansomwareCampaignUse ) (rançongiciel : exposé ou pas, on veut savoir)
|
||||
```
|
||||
|
||||
Un **ET**, pas un OU. C'est tout le design.
|
||||
|
||||
Pourquoi chaque terme :
|
||||
|
||||
- **NVD seul** → des milliers de CVE. Bruit pur.
|
||||
- **CVSS seul** → gravité ≠ exploitation. Un 9.8 jamais exploité passe après un 7.5 exploité.
|
||||
- **KEV seul** → 1647 CVE, dont ~99 % ne concernent pas cette box.
|
||||
- **installé seul** → beaucoup de CVE théoriques sur des paquets que personne n'attaque.
|
||||
- **exposé** → vient de `secubox-profiles` (`exposure=public`, 16 modules sur 187). Un CVE sur un
|
||||
module `internal` n'a pas la même urgence.
|
||||
|
||||
Tout ce qui ne passe pas le filtre **reste un constat dans le panel**, jamais une alerte.
|
||||
|
||||
**EPSS en signal secondaire** : un CVE hors KEV mais à EPSS élevé (≥ 0,5) sur un module exposé entre
|
||||
dans le **digest**, pas dans l'alerte. C'est le « presque » — utile à voir, pas à réveiller.
|
||||
|
||||
`secubox-profiles` expose déjà `GET /api/v1/profiles/status` avec `exposure` par module ; le
|
||||
croisement se fait par le nom du paquet Debian du module. **Quand le lien paquet↔module est inconnu,
|
||||
le CVE est traité comme NON exposé pour le filtre mais SIGNALÉ comme non corrélé dans le digest** —
|
||||
l'inconnu ne doit ni mentir ni disparaître.
|
||||
|
||||
## ② L'anti-bruit — le vrai piège des alertes récurrentes
|
||||
|
||||
Un cron qui répète la même alerte chaque jour est ignoré en une semaine. Le filtre ne suffit pas ;
|
||||
il faut de l'**état** :
|
||||
|
||||
- **Alerter sur le delta**, jamais sur l'ensemble : un CVE qui *entre* dans l'ensemble alerte une
|
||||
fois.
|
||||
- **Ré-alerter seulement sur escalade** : entrée au KEV d'un CVE déjà connu, ou ajout du flag
|
||||
rançongiciel, ou un module qui passe `internal` → `public`.
|
||||
- **Le triage existe déjà** : un CVE acquitté (`POST /triage/{cve_id}`) se tait, y compris s'il
|
||||
revient dans le calcul.
|
||||
- **Digest périodique** pour tout le reste (hebdomadaire) : les « presque », les non corrélés, les
|
||||
acquittés toujours présents.
|
||||
- **Plancher anti-tempête** : si un cycle produit plus de N alertes (défaut 10), envoyer **un**
|
||||
message groupé au lieu de N. Un flux qui explose (KEV qui ajoute 200 entrées) ne doit pas noyer la
|
||||
boîte.
|
||||
|
||||
L'état des alertes déjà émises est persisté (`/var/lib/secubox/cve-triage/alerted.json`) — sans lui,
|
||||
« récurrent » veut dire « répétitif ».
|
||||
|
||||
## ③ La gâchette
|
||||
|
||||
Un `.timer` systemd — le motif du projet (`secubox-netstats.timer`, `secubox-nft-cache.timer`,
|
||||
`secubox-waf-watchdog.timer`, `secubox-frigate-diskguard.timer`).
|
||||
|
||||
```
|
||||
secubox-cve-scan.timer → quotidien, avec RandomizedDelaySec (ne pas taper NVD à heure fixe)
|
||||
→ secubox-cve-scan.service (oneshot)
|
||||
rafraîchit KEV + NVD + EPSS, rescanne dpkg, calcule le delta, émet les alertes
|
||||
```
|
||||
|
||||
`Persistent=true` : un cycle manqué (box éteinte, fibre coupée) est rattrapé au démarrage.
|
||||
|
||||
⚠️ La box tourne 118 services à load 5.4 sur 4 cœurs. Le oneshot est `Nice=10` +
|
||||
`IOSchedulingClass=idle` : un scan CVE n'a aucune raison de disputer le CPU au WAF.
|
||||
|
||||
## ④ Le notifieur partagé
|
||||
|
||||
`secubox_core/notify.py` — une fonction, N destinations :
|
||||
|
||||
```python
|
||||
notify(subject: str, body: str, *, level: str, sinks: list[str] | None = None) -> dict[str, bool]
|
||||
```
|
||||
|
||||
Sinks configurés dans `secubox.conf` (`[notify]`), chacun activable indépendamment :
|
||||
|
||||
| Sink | Chemin | État |
|
||||
|---|---|---|
|
||||
| `mail` | SMTP submission → LXC mail (587, SASL) | ✅ chemin réparé et vérifié le 2026-07-17 |
|
||||
| `panel` | écrit `/var/lib/secubox/notify/alerts.json` | ✅ trivial, la donnée existe |
|
||||
| `companion` | le module Companion lit la même API | ✅ motif établi (peertube, billets) |
|
||||
| `xmpp` | `python3-slixmpp` → compte bot Prosody | ⚠️ **à construire** (voir ⑤) |
|
||||
|
||||
Règles :
|
||||
- **Un sink qui échoue n'empêche pas les autres.** `notify` renvoie un dict par sink et journalise
|
||||
les échecs ; il ne lève jamais. Une alerte partiellement délivrée vaut mieux qu'aucune.
|
||||
- **Aucun secret dans le corps** : une alerte CVE ne contient jamais de jeton ni de mot de passe.
|
||||
- **Journal d'audit** : chaque envoi est tracé (`/var/log/secubox/audit.log`), conformément à la
|
||||
contrainte CSPN.
|
||||
|
||||
## ⑤ Le sink XMPP — chiffré honnêtement
|
||||
|
||||
Le module `secubox-jabber` **ne sait pas envoyer de message** : c'est un gestionnaire de cycle de vie
|
||||
Prosody (`install`/`start`/`stop`/`users`). Il n'y a aucun client XMPP dans le dépôt.
|
||||
|
||||
Ce que le sink exige, et qui n'existe pas :
|
||||
|
||||
1. `python3-slixmpp` en `Depends` (présent dans bookworm — pas de pip).
|
||||
2. Un **compte bot** sur Prosody — créable via le `POST /users` existant du module jabber.
|
||||
3. Ses identifiants dans `/etc/secubox/secrets/cve-xmpp` (0600 `secubox:secubox`), hors code et hors
|
||||
TOML versionné.
|
||||
4. Un client d'envoi minimal (connexion, message, déconnexion) — slixmpp est asyncio ; l'appel doit
|
||||
rester **hors de la boucle** du service appelant (`asyncio.to_thread` ou un process court), sinon
|
||||
on rejoue le blocage d'event-loop qui a déjà figé cette box.
|
||||
5. La destination (JID) configurée dans `[notify]`.
|
||||
|
||||
C'est une **tâche à part entière**, pas un branchement. Elle est isolée : si elle dérape, les trois
|
||||
autres sinks fonctionnent déjà.
|
||||
|
||||
---
|
||||
|
||||
## Surfaces
|
||||
|
||||
- **Panel** `/cve-triage/` (existe) : ajouter le bandeau d'alertes actives + l'état de fraîcheur du
|
||||
dernier cycle. Style cyan hybrid-dark (`WEBUI-PANEL-GUIDELINES.md`).
|
||||
- **Companion** : un module `cve` lisant l'API — compteur d'alertes, liste, acquittement.
|
||||
- **CLI** : `secubox-cvectl scan|alerts|ack <cve>|digest` — pour l'opérateur et le débogage.
|
||||
|
||||
Toutes exigent JWT (`Depends(require_jwt)`).
|
||||
|
||||
---
|
||||
|
||||
## Hors périmètre (YAGNI)
|
||||
|
||||
- **CrowdSec CTI** : écarté (40 requêtes/mois, dépendance cloud, données non redistribuables).
|
||||
- Corrélation avec les tentatives d'exploitation observées localement (WAF 198k menaces, DPI,
|
||||
honeypot) : séduisant, mais c'est un autre système — la donnée est là, le croisement mérite son
|
||||
spec.
|
||||
- Centralisation mesh des alertes CVE (3 nœuds) : même remarque, même renvoi.
|
||||
- Application automatique des correctifs. Une box de sécurité ne s'auto-patche pas sans décision
|
||||
humaine ; l'alerte porte la commande, l'humain l'exécute.
|
||||
|
||||
---
|
||||
|
||||
## Tests
|
||||
|
||||
- **Filtre (pur)** : KEV∧installé∧exposé alerte ; chaque terme manquant n'alerte pas ; rançongiciel
|
||||
alerte même non exposé ; paquet non corrélé à un module → non exposé + signalé.
|
||||
- **Anti-bruit (pur)** : un CVE déjà alerté ne réalerte pas ; une escalade réalerte ; un CVE acquitté
|
||||
se tait ; > N alertes → un seul message groupé.
|
||||
- **Notifieur** : un sink en échec n'empêche pas les autres ; `notify` ne lève jamais ; aucun secret
|
||||
dans le corps.
|
||||
- **Flux** : KEV/NVD/EPSS injoignables → le cycle échoue proprement et **n'émet rien** (une alerte
|
||||
fondée sur une donnée absente est pire que pas d'alerte) ; **jamais** de repli silencieux sur des
|
||||
données périmées présentées comme fraîches.
|
||||
- **Timer** : `Persistent=true` rattrape un cycle manqué.
|
||||
|
||||
Couverture ≥ 80 % (contrainte CSPN du projet).
|
||||
|
||||
---
|
||||
|
||||
## Découpage d'implémentation
|
||||
|
||||
| Phase | Contenu | Risque |
|
||||
|---|---|---|
|
||||
| 1 | `secubox_core/notify.py` + sinks `mail` et `panel` (+ tests) | faible, additif |
|
||||
| 2 | Le filtre + l'anti-bruit (fonctions pures, testables sans board) | nul |
|
||||
| 3 | Le `.timer` + le oneshot de cycle + l'état persistant | moyen (touche le réel) |
|
||||
| 4 | Panel : bandeau d'alertes + fraîcheur | faible |
|
||||
| 5 | Module Companion `cve` | faible |
|
||||
| 6 | **Sink XMPP** (slixmpp + bot + secret + client) | moyen, **isolé** |
|
||||
|
||||
Les phases 1-2 se font et se testent hors ligne. La phase 3 est la première à toucher la box.
|
||||
Loading…
Reference in New Issue
Block a user