# Cve Veille

> Veille quotidienne CVE — récupère les nouvelles vulnérabilités critiques (CVSS≥7 OU CISA KEV) sur produits whitelist depuis NVD/MSRC/CISA, écrit max 5 fichiers dans _Inbox/raw/cve/ avec frontmatter Schema-conforme. Idempotent via seen_cves dans lazy-obsidian-state.json. Utilise WebFetch et PowerShell avec encoding UTF-8 systématique.

- Skill: `aibot88/cve-veille` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add aibot88/cve-veille`
- Raw SKILL.md: https://api.skillmd.com/api/skills/aibot88/cve-veille/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: aibot88 (https://skillmd.com/u/aibot88)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/aibot88/cve-veille

---


# Skill : cve-veille (D1)

## Objectif

Avaler les CVE des dernières 24h (ou depuis last_run) répondant aux critères de criticité, et créer une page draft par CVE retenue dans `_Inbox/raw/cve/`. Idempotent. Plafond strict de 5 CVE par run pour éviter la pollution de l'inbox.

## ⚠️ Conventions techniques (à respecter strictement)

- **Encoding UTF-8 sans BOM** pour tous les writes : `Set-Content -Encoding utf8 -NoNewline` ou `Out-File -Encoding utf8 -NoNewline`. JAMAIS d'écriture en encoding par défaut PowerShell (UTF-16 LE BOM corromprait les fichiers).
- **Lecture** : `Get-Content -Raw -Encoding utf8` pour les fichiers JSON, `-Raw` pour le markdown.
- **Append jsonl** : `Add-Content -Encoding utf8 -Path <file> -Value <line>`.
- **Atomicité** : pour modifier un fichier existant, écrire `<file>.tmp` puis `Move-Item -Force`.
- **WebFetch** pour les URLs HTTPS, `Invoke-RestMethod` pour les APIs JSON (NVD).

## Procédure

### 1. Lire l'état

Lire `99-Meta/lazy-obsidian-state.json` :

```powershell
$statePath = "D:\tommyDossier\Documents\Obsidian Vault\99-Meta\lazy-obsidian-state.json"
$state = Get-Content $statePath -Raw -Encoding utf8 | ConvertFrom-Json
$lastRun = $state.last_run.'cve-veille'
$seenCves = $state.seen_cves
```

Si `$lastRun` correspond à aujourd'hui (UTC, format `YYYY-MM-DD`) → afficher "Déjà fait aujourd'hui ($lastRun). Skip." et terminer (exit gracieux). Append une ligne `SKIP_IDEMPOTENT` au runs.jsonl.

### 2. Définir la fenêtre temporelle

- `start` = `$lastRun` ou `(Get-Date).AddDays(-1).ToUniversalTime()` si null
- `end` = `(Get-Date).ToUniversalTime()`
- Format ISO 8601 : `yyyy-MM-ddTHH:mm:ss.fffZ`

### 3. Sources à interroger

**a. NVD JSON API 2.0**
```
https://services.nvd.nist.gov/rest/json/cves/2.0?lastModStartDate=<start>&lastModEndDate=<end>
```
Pagination avec `startIndex` si > 2000 résultats (rare en 24h).

**b. MSRC RSS**
```
https://api.msrc.microsoft.com/update-guide/rss
```
Filtrer entrées `pubDate` dans la fenêtre.

**c. CISA KEV catalog**
```
https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json
```
Filtrer `vulnerabilities[].dateAdded` dans la fenêtre.

Per-source try/catch. Une source en échec n'invalide pas les autres. Logger les sources échouées dans `sources_failed[]` du runs.jsonl.

### 4. Whitelist produits

Liste configurable (modifier ici si besoin) :

```
Windows
Linux kernel
Apache HTTPD
nginx
OpenSSH
OpenSSL
Kubernetes
Docker
Microsoft Exchange
Active Directory
```

Match case-insensitive, substring sur les champs produit (CPE `criteria` pour NVD, titre/description pour MSRC/RSS).

### 5. Critères de retenue

Une CVE est retenue si **TOUTES** ces conditions :
- ID CVE non présent dans `$seenCves`
- (CVSS v3 baseScore ≥ 7.0) **OU** (présente dans CISA KEV — `vulnerabilities[].cveID`)
- Au moins un produit affecté match (case-insensitive) un item de la whitelist

### 6. Tri & plafond

```powershell
$retained | Sort-Object @{e='cvss';desc=$true}, @{e='date';desc=$true} | Select-Object -First 5
```

**Plafond strict : 5 CVE par run.** Si la fenêtre contient plus de 5 CVE éligibles, on garde les plus critiques (CVSS desc puis date desc).

### 7. Pour chaque CVE retenue : écrire le fichier

Chemin : `_Inbox\raw\cve\CVE-YYYY-NNNNN.md`

Contenu (template — substituer les `<…>`) :

```markdown
---
titre: "CVE-YYYY-NNNNN — <produit principal> <classe vuln en 3-5 mots>"
type: vulnerabilité
cluster: <cluster_inferé>
cve: [CVE-YYYY-NNNNN]
cvss_score: <float>
statut: draft
criticite: <low|medium|high|critical>
importance: standard
source_knowledge: vendor-doc
source_url: <URL NVD primaire>
tags: ["#cyber/cve", "#meta/veille-auto"]
date_maj: <YYYY-MM-DD>
---

# CVE-YYYY-NNNNN — <produit> <classe>

> [!warning] Vulnérabilité critique
> Score CVSS : <score>. CISA KEV : <oui/non>.

## Description

<Description NVD originale, traduite en français si possible, max 6 lignes.>

## Produits affectés

- <Liste CPE / versions, max 5 items>

## Source

- NVD : <URL>
- Vendor advisory : <URL si présent>
- CISA KEV : <URL si applicable>

## À investiguer

- [ ] Cluster cible définitif (parmi les 16 du Schema.md)
- [ ] Lien vers pages existantes du vault (wikilinks)
- [ ] Promotion vers `pages/{01|02|03}-…` si pertinent
```

**Écriture impérative :**
```powershell
$path = "...\_Inbox\raw\cve\CVE-2026-XXXXX.md"
Set-Content -Path $path -Value $content -Encoding utf8 -NoNewline
```

### 8. Inférence du cluster (heuristique)

| Mots-clés produit | Cluster inféré |
|---|---|
| `Active Directory`, `Kerberos`, `NTLM`, `LDAP` | `07-IAM-ActiveDirectory` |
| `Windows`, `Microsoft Exchange`, `Office` | `02-Blue-Team` |
| `Linux kernel` | `02-Blue-Team` |
| `Apache`, `nginx`, `OpenSSL` | `05-AppSec-DevSecOps` |
| `Kubernetes`, `Docker` | `06-Cloud-Security` |
| `OpenSSH` | `04-Reseau-Protocoles` |
| (autre / ambigu) | `99-Meta` |

**Mapping criticité ↔ CVSS :**
- 9.0–10.0 → `critical`
- 7.0–8.9 → `high`
- (<7.0 ne passe pas le filtre)

### 9. Append dans Fact-Check-Log.md

Pour chaque CVE retenue :

```powershell
$line = "- [veille-auto] [$cveId](_Inbox/raw/cve/$cveId.md) — CVSS $score, source NVD, ingéré $now"
Add-Content -Path "...\99-Meta\Fact-Check-Log.md" -Value $line -Encoding utf8
```

Si la section "## Veille automatique" n'existe pas dans `Fact-Check-Log.md`, la créer en append avant de logger.

### 10. Update state.json

```powershell
$state.last_run.'cve-veille' = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
foreach ($cve in $retainedIds) { $state.seen_cves += $cve }
$state | ConvertTo-Json -Depth 5 | Set-Content -Path $statePath -Encoding utf8 -NoNewline
```

Si erreurs → append dans `state.errors[]` (FIFO max 50 entrées).

### 11. Append runs.jsonl

```powershell
$runEntry = @{
    ts = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
    skill = "cve-veille"
    status = "OK"   # ou "PARTIAL", "FAIL", "SKIP_IDEMPOTENT"
    new = $retainedCount
    skipped = $skippedCount
    errors = $errorCount
    sources_failed = @()
} | ConvertTo-Json -Compress
Add-Content -Path "...\99-Meta\lazy-obsidian-runs.jsonl" -Value $runEntry -Encoding utf8
```

Statuts :
- `OK` : ≥1 retenue, 0 erreur source
- `PARTIAL` : ≥1 retenue mais ≥1 source en échec
- `FAIL` : aucune retenue ET ≥1 source en échec
- `SKIP_IDEMPOTENT` : déjà fait aujourd'hui

### 12. Output utilisateur

Affichage console final :
```
✅ cve-veille — 3 nouvelles CVE retenues, 12 filtrées hors whitelist, 0 erreurs.
   - CVE-2026-12345 (CVSS 9.8) → _Inbox/raw/cve/
   - CVE-2026-12346 (CVSS 8.1) → _Inbox/raw/cve/
   - CVE-2026-12347 (CVSS 7.5) → _Inbox/raw/cve/
```

Si SKIP : `⏭ cve-veille — déjà fait aujourd'hui (2026-04-25T09:12:33Z). Skip.`
Si FAIL : `❌ cve-veille — sources en échec : <liste>. Aucune CVE ingérée.`

## Gestion d'erreurs

| Cause | Action |
|---|---|
| NVD 5xx / timeout | Retry x2 backoff (1s, 5s). Échec → log `state.errors[]`, continuer avec MSRC/CISA |
| NVD parser fail | Écrire `_Inbox\raw\cve\_PARSE-ERROR-<date>.md` avec payload brut, log, skip |
| RSS feed down | Per-source try/catch, log `sources_failed`, autres sources continuent |
| Quota / 429 / 403 | Backoff 30s → 5min → abandon |
| `Set-Content` échoue (fichier verrouillé Obsidian) | Retry après 2s. Si échec → log + skip ce CVE spécifique |

## Limites volontaires (YAGNI)

- Pas de retry infini (max 2)
- Pas de notification externe (push, email)
- Pas de check sémantique (date plausible) — confiance NVD
- Pas de suppression de CVE déjà ingérée même si retirée du KEV
- Pas de classification fine du cluster (heuristique simple ; humain réajuste)

