--- title: Security CVE Watch url: https://blog.guigpap.com/fr/workflows/security-cve-watch/ url_md: https://blog.guigpap.com/fr/workflows/security-cve-watch.md category: automation date: '2026-05-04' maturite: production techno: - docker - n8n - telegram application: - monitoring - operations --- # Security CVE Watch > Veille CVE quotidienne sur les images Docker du VPS via Trivy, dédup persistante et alertes Telegram ## 1. Quoi ? — Définition et contexte **Security CVE Watch** est un workflow N8N qui scanne quotidiennement les images Docker du VPS à la recherche de vulnérabilités connues (CVE), dédoublonne les détections et n'envoie une alerte que pour les nouveautés ou les escalades de sévérité. > **Note - CVE et Trivy** > > Une **CVE** (Common Vulnerabilities and Exposures) est un identifiant standard pour une faille de sécurité publique, accompagné d'une sévérité (LOW/MEDIUM/HIGH/CRITICAL) et d'un CVSS score. **Trivy** est un scanner open-source qui interroge plusieurs bases (NVD, Red Hat, Alpine, Debian) et compare les paquets installés dans une image aux versions vulnérables connues. ### Composants | Workflow | ID | Nodes | Rôle | |----------|------|-------|------| | **CVE Watch (parent)** | `5mHMlcg2HqEa7pWJ` | 18 | Trigger + boucle images + agrégation + notification | | **CVE Scanner (SW-1)** | `Tz40sFr3CMrw7X81` | 6 | Lance `trivy image` via SSH sur une image | | **CVE Processor (SW-2)** | `JCyc4Jkev8fQ1mwL` | 9 | Dédup, classification, comparaison historique | ### Architecture visuelle ```mermaid flowchart TD subgraph Triggers["Déclencheurs"] direction TB Daily["Cron daily 06:00"] Weekly["Cron weekly Monday 09:00"] DIUN["DIUN webhook · nouvelle image"] end subgraph Parent["CVE Watch · 18 nodes"] direction TB GetImages["Get Docker Images · docker images"] LoopImages["Loop Over Images"] Aggregate["Aggregate Findings"] Notify["Notification Hub"] end subgraph Scanner["CVE Scanner · 6 nodes"] direction TB SSH["SSH Local VPS"] Trivy["docker exec trivy image <img>"] ParseJSON["Parse JSON output"] end subgraph Processor["CVE Processor · 9 nodes"] direction TB Dedup["Lookup cve_notifications"] Classify["Filter CRITICAL/HIGH"] Persist["Insert/Update DT"] end subgraph DT["Data Tables"] direction TB CVENotif["cve_notifications · Tfng3e4kUL0Ya4Lm"] ScanHist["cve_scan_history · elUlUt9HzqSSCr7L"] end Triggers --> Parent GetImages --> LoopImages --> Scanner Scanner --> Processor Processor <--> CVENotif Processor --> Aggregate --> Notify Parent --> ScanHist ``` ### Périmètre couvert Une dizaine d'images de la stack VPS sont scannées : `n8nio/n8n`, `odoo:18`, `caddy:2-alpine`, `postgres:16`, `postgres:17`, `redis:7-alpine`, `qdrant/qdrant`, `crowdsecurity/crowdsec`, `prom/prometheus`, `grafana/grafana`. La liste est dérivée de `docker images --format` au runtime, donc les ajouts de stacks sont automatiquement intégrés au prochain scan. --- ## 2. Pourquoi ? — Enjeux et motivations ### Le problème sans veille active Les CVE publiées sur les images Docker ne déclenchent rien automatiquement. Une vulnérabilité critique sur `postgres:16` peut traîner des semaines avant d'être remarquée si personne ne consulte explicitement l'avis CVE ou ne lit le changelog du registre. | Symptôme sans CVE Watch | Cause | Impact | |--------------------------|-------|--------| | **Vulnérabilité non détectée** | Pas de scan systématique | Fenêtre d'exploitation prolongée | | **Faux positifs en boucle** | Pas de dédup | Bruit, désensibilisation aux alertes | | **Déclenchement lent** | Surveillance manuelle | Aucun mécanisme de relance | | **Pas de traçabilité** | Pas d'historique de scan | Impossible de prouver un état | ### Pourquoi Trivy plutôt que Snyk ou Docker Scout ? | Critère | Trivy | Snyk | Docker Scout | |---------|-------|------|--------------| | **Open source** | Oui (Apache 2.0) | Non | Non | | **Self-hosted** | Oui | Partiel | Non | | **Sources CVE** | NVD, Red Hat, Alpine, Debian, Ubuntu | NVD, Snyk DB | NVD, Snyk DB | | **Auth requise** | Non | Token + quota | Docker Hub login | | **Scan offline** | Oui (DB locale) | Non | Non | | **Format JSON** | Stable, documenté | Stable | Stable | Le choix de Trivy s'aligne avec la philosophie self-hosted du VPS : pas de dépendance à un SaaS, pas de quota d'API, pas de fuite de la liste des images vers un tiers. ### Trois déclencheurs complémentaires | Trigger | Fréquence | Cas d'usage | |---------|-----------|-------------| | **Cron daily 06:00** | Tous les jours avant la journée | Détecter les CVE publiées la veille | | **Cron weekly Monday 09:00** | Une fois par semaine | Récap hebdomadaire avec tendances | | **DIUN webhook** | À chaque pull d'image | Scanner immédiatement une nouvelle version | Le couplage avec [DIUN](/fr/infrastructure/notify-stack/) ferme la boucle : quand DIUN détecte une nouvelle image disponible, le scan CVE est déclenché avant l'approbation Telegram, ce qui permet de refuser un update qui introduirait une régression de sécurité. --- ## 3. Comment ? — Mise en œuvre technique ### Le parcours d'un scan **1. Inventaire des images** — Le parent appelle `docker images --format '{{.Repository}}:{{.Tag}}'` via SSH local pour obtenir la liste exacte des images chargées sur le VPS au moment du scan. **2. Boucle par image** — Pour chacune, le parent appelle le sous-workflow CVE Scanner via Execute Workflow. **3. Scan Trivy** — Le Scanner se connecte en SSH au VPS et lance `docker exec trivy image --format json --severity CRITICAL,HIGH `. Le container `trivy` est pré-installé et son cache de DB CVE est monté en volume pour éviter de re-télécharger 150 MB à chaque scan. **4. Parsing du résultat** — Le Scanner parse le JSON Trivy et retourne un tableau normalisé `{cve_id, severity, package, installed, fixed, title, cvss}`. **5. Dédup et classification** — Le CVE Processor compare chaque finding avec la table `cve_notifications`. Trois cas : | Cas | Action | |-----|--------| | **CVE inconnue** | Insert avec `status=new`, ajoute à la liste à notifier | | **CVE déjà notifiée** | Skip (sauf escalade de sévérité) | | **CVE marquée fixée** | Vérifier que le fix est bien appliqué, sinon ré-ouvrir | **6. Agrégation** — Le parent regroupe les nouvelles findings par image et par sévérité. **7. Notification** — Si au moins une CRITICAL est présente, le parent envoie une **alerte immédiate** au [Notification Hub](/fr/workflows/notification-hub/). Sinon, il attend le passage hebdomadaire pour un digest. > **Caution - Trivy DB freshness** > > La base CVE de Trivy est mise à jour automatiquement à chaque exécution (option `--cache-dir`). Sans cache partagé, chaque scan re-téléchargerait la DB. Le container `trivy` du VPS monte un volume persistant `/root/.cache/trivy` qui mutualise le cache entre tous les scans. ### Configuration des Data Tables #### `cve_notifications` (`Tfng3e4kUL0Ya4Lm`) | Colonne | Type | Description | |---------|------|-------------| | `cve_id` | string | `CVE-YYYY-NNNNN` | | `image` | string | `repo:tag` | | `severity` | string | CRITICAL / HIGH / MEDIUM / LOW | | `package` | string | Paquet vulnérable | | `installed_version` | string | Version actuelle | | `fixed_version` | string | Version qui corrige (ou null) | | `first_seen` | datetime | Première détection | | `notified_at` | datetime | Date de la dernière notification | | `status` | string | `new` / `notified` / `fixed` / `ignored` | #### `cve_scan_history` (`elUlUt9HzqSSCr7L`) | Colonne | Type | Description | |---------|------|-------------| | `scan_date` | datetime | Date du run | | `images_scanned` | number | Nombre d'images analysées | | `critical_count` | number | CVE CRITICAL détectées | | `high_count` | number | CVE HIGH détectées | | `report_sent` | boolean | Notification envoyée ? | ### Format des notifications **Alerte immédiate (CRITICAL)** envoyée via le Notification Hub : ``` 🚨 CVE CRITIQUE DÉTECTÉE Image: postgres:16 CVE: CVE-2026-12345 Severity: CRITICAL (CVSS 9.8) Package: libpq Installed: 16.2 → Fixed in: 16.3 Stacks affectés: - n8n-stack (n8n-postgres) - odoo-stack (odoo-db) [🔄 Update maintenant] [📖 Détails CVE] [⏰ Reporter] ``` **Digest hebdomadaire** envoyé chaque lundi 09:00 : ``` 📊 CVE Watch · Semaine 18 12 scans effectués 0 CRITICAL · 3 HIGH · 8 MEDIUM (ignorées) FIXES APPLIQUÉS: ✅ postgres:16.2 → 16.3 (CVE-2026-12345) ✅ caddy:2.8.4 → 2.8.5 (CVE-2026-12346) EN ATTENTE: ⏳ redis:7.2.3 → 7.2.4 (planifié dimanche) ``` ### Intégration avec DIUN Quand [DIUN](/fr/infrastructure/notify-stack/) détecte qu'une nouvelle version d'image est disponible, son webhook déclenche aussi CVE Watch sur cette image spécifique. Le résultat est intégré au message d'approbation Telegram : | Cas | Bouton de l'approval | |-----|----------------------| | **Nouvelle version sans CVE** | `[Update]` (vert) | | **Nouvelle version + CVE résolue** | `[Update fixe CVE-XXX]` (vert) | | **Nouvelle version + nouvelles CVE** | `[Update malgré CVE]` (orange) | L'utilisateur prend la décision en pleine conscience du risque. --- ## 4. Et si ? — Perspectives et limites ### Limites actuelles | Limite | Impact | Mitigation | |--------|--------|------------| | **Lag des feeds CVE** | Une CVE publiée hier peut ne pas être encore dans la DB Trivy | Couplage avec abonnement RSS NVD à terme | | **Dépendances applicatives non scannées** | Trivy scan les paquets OS, pas les dépendances `package.json`/`requirements.txt` à l'intérieur de l'image | Couvrir avec un workflow GitHub Dependabot séparé | | **Pas de scoring contextuel** | Un CVSS 9.8 sur un paquet inutilisé reste affiché comme CRITICAL | Whitelist par paquet via `error_handling_config`-like (à concevoir) | | **Faux positifs sur images musl** | Trivy classe parfois des CVE Debian sur des images Alpine (musl) | Ignorer via filtre `--ignore-policy` Trivy | ### Scénarios d'évolution **Si la fréquence quotidienne devient insuffisante** : - Passer à un scan toutes les heures pour les images CRITICAL - Abonner CVE Watch à un flux NVD quasi-temps-réel - Coupler avec un webhook Trivy serveur (mode `--server`) **Si le bruit de notifications devient gênant** : - Ajouter une whitelist par CVE acceptée (avec justification) - Regrouper les CVE par paquet plutôt que par CVE - Ne notifier que si CVSS ≥ 9.0 en mode strict **Si le besoin de conformité émerge** : - Conserver les rapports JSON dans un bucket d'audit - Générer un PDF mensuel signé - Exposer une API read-only pour un dashboard externe ### Métriques pertinentes | Métrique | Source | Interprétation | |----------|--------|----------------| | `cve_critical_open` | `cve_notifications WHERE status IN ('new','notified') AND severity='CRITICAL'` | Doit rester à 0 | | `cve_mttr` | Δ entre `first_seen` et `fixed_at` | Mean Time To Resolution | | `scan_failures` | `cve_scan_history WHERE images_scanned=0` | Indicateur de panne du scanner | --- ## Pages liées ### Infrastructure - [Security Stack](/fr/infrastructure/security-stack/) — Caddy, CrowdSec, Trivy - [Notify Stack](/fr/infrastructure/notify-stack/) — DIUN qui déclenche les scans à chaud - [Architecture VPS](/fr/infrastructure/architecture-vps/) — Vue d'ensemble ### Workflows - [Notification Hub](/fr/workflows/notification-hub/) — Routage des alertes CVE - [Docker Updates](/fr/workflows/docker-updates/) — Approval workflow couplé aux scans CVE - [Monitoring Digests](/fr/workflows/monitoring-digests/) — Digest hebdomadaire incluant les CVE ### Référence - [Glossaire](/fr/reference/glossary/) — CVE, CVSS, Trivy ## Metadonnees agent - Cet article est issu du blog GuiGPaP Lab. - Contexte global du blog: https://blog.guigpap.com/llms.txt - Contact auteur: https://odoo.guigpap.com/mon-cv - Licence: CC-BY-SA 4.0