---
name: detecteur-sinks-llm-dangereux
description: Détecteur de sorties de LLM non validées atteignant un point d'exécution dangereux (commande shell, évaluation dynamique, écriture de fichier, requête SQL) dans du code source Rust ou Python — corrélation par identifiant avec vérification de frontière de mot, fenêtre bornée. Bibliothèque standard uniquement, zéro dépendance, zéro appel réseau.
theme: securite-souverainete
langages_cibles: rust
---
# detecteur-sinks-llm-dangereux
## Objectif
Scanner un dossier de code source Rust ou Python et détecter les cas où
une variable capturant probablement une sortie de modèle de langage
(heuristique par forme d'appel : `.generate(`, `.chat(`, `.invoke(`...)
atteint, plus loin dans une fenêtre de lignes bornée, un **point
d'exécution dangereux** — commande shell (`Command::new`/`subprocess.
run`/`os.system`), évaluation dynamique (`eval`/`exec`), écriture de
fichier (`fs::write`/`open`), requête SQL (`.execute`/`sqlx::query`) —
**sans qu'un appel de validation/nettoyage identifiable** (`sanitize`,
`valider_`, `echapper_`, `quote(`...) n'intervienne entre les deux sur le
même identifiant.
## Pourquoi ce skill existe
Un agent IA qui exécute une action à partir de ce qu'un modèle vient de
produire (plutôt que d'une entrée strictement contrôlée) reproduit
exactement le patron d'injection classique (entrée non fiable → sink
dangereux) — sauf que la "source non fiable" est ici la sortie du modèle
lui-même, potentiellement influencée par un contenu externe qu'il vient
de lire (voir `detecteur-injection-prompt-avance`, même projet). Ce skill
cible ce point de jonction précis côté code source, avant que le
programme ne tourne.
## Conception (honnêteté sur le compromis fait)
**Corrélation par identifiant textuel dans une fenêtre de lignes bornée
(25 lignes), PAS une vraie analyse de flot de données ni un suivi de
portée de fonction/bloc réel** — contrairement à `detecteur-toctou-
fichiers` (même compte) qui suit les accolades Rust pour borner sa
recherche à la fonction courante, ce skill vise DEUX langages dont un
(Python) n'a pas d'accolades : un suivi de bloc unifié aurait demandé deux
implémentations de parsing de portée complètement séparées pour un gain
de précision marginal par rapport à une fenêtre de lignes bornée,
honnêtement documentée comme telle plutôt que présentée comme plus
rigoureuse qu'elle ne l'est.
**Masquage chaînes/commentaires propre à chaque langage** (Rust : `//`,
`/* */`, `"..."`, `r#"..."#` ; Python : `#`, `'...'`, `"..."`, `'''...'''`,
`"""..."""`) — implémentations séparées, chacune testée isolément.
## Deux bugs réels trouvés et corrigés AVANT publication (pas supposé — test manuel)
1. **Faux négatif par nom de variable nu comme motif source** : la
première version incluait des noms de variable bruts ("reponse_llm",
"llm_output"...) dans la liste des motifs source, en plus des formes
d'appel. Un test manuel a révélé qu'une ligne de réaffectation APRÈS
validation (`reponse_llm = sanitize(reponse_llm)`) matchait ELLE-MÊME
comme un second point source (le nom de variable y réapparaît), sans
protection derrière — créant un signalement fantôme malgré la
validation déjà appliquée en amont. Corrigé en restreignant les motifs
source aux formes d'APPEL uniquement (se terminant par `(`).
2. **Faux négatif par collision de préfixe d'identifiant** : `"reponse"`
est une sous-chaîne de `"reponse2"` — la vérification de corrélation
utilisait `.contains()`, donc une ligne de validation sur `reponse2`
neutralisait à tort un signalement réel sur `reponse`, un variable
totalement différente. **C'est le pire type d'erreur pour un outil de
sécurité** (un vrai risque masqué, pas un faux positif gênant).
Corrigé par `contient_identifiant_mot_entier()` — vérification stricte
de frontière de mot (ni précédé ni suivi d'un caractère alphanumérique/
`_`), avec test de non-régression dédié.
## Garanties de conception (non négociables)
- **Zéro dépendance tierce**, **zéro appel réseau**.
- **Contenu scanné traité comme donnée inerte**, jamais exécuté.
- **Détection 100% pure et testée isolément** : `analyser_contenu`,
`construire_masque_rust`, `construire_masque_python`,
`contient_identifiant_mot_entier`, `extraire_identifiant_affectation`.
- **Exemption ligne par ligne** via le marqueur `sink-ok`.
- **`eprintln!` au lieu de `tracing`**, **aucun `unwrap_or*`** (règle §1
du projet — tout accès potentiellement hors bornes passe par un `match`
exhaustif avec valeur neutre documentée, jamais un sentinel silencieux).
## Conformité aux règles du projet (verify_rules_rust.sh)
```bash
bash ../../outils/verify_rules_rust.sh --src skills/detecteur-sinks-llm-dangereux/src
```
**Résultat au 2026-08-19 : 0 violation, 1 avertissement §5 annoté et
vérifié faux positif** (même patron que les autres skills Rust du
compte : un accès hors bornes qui ne devrait jamais se produire en
pratique, traité par un `match` explicite plutôt qu'un `unwrap_or`).
**1 violation bloquante trouvée et corrigée pendant le développement** :
un premier essai utilisait `.unwrap_or(true)` pour un accès potentiellement
hors bornes — rejeté par la règle §1 (sentinel silencieux). Corrigé par un
`match` exhaustif avec commentaire justifiant chaque branche `None`.
## Mode d'emploi
**Compiler :**
```bash
cargo build --release
```
**Lancer :**
```bash
./target/release/detecteur-sinks-llm-dangereux <dossier_cible>
```
**Tests unitaires** (22 tests, aucun réseau, aucune I/O disque réelle) :
```bash
cargo test
```
**Validation fonctionnelle réelle** (pas seulement les tests unitaires) :
sur un fichier Python à 3 scénarios (source non validée → sink ; source
validée par `sanitize()` → sink ; commande sans lien avec un LLM →
sink), exactement **1 signalement** — le seul cas réellement à risque —
confirmé après correction des 2 bugs ci-dessus (le premier essai en
produisait 0, le second en produisait 2 dont 1 faux négatif masqué).
**Prérequis :** Rust stable (testé avec rustc 1.93, édition 2024). Aucune
dépendance à installer.
## Limites connues (honnêteté de l'outil, pas de sur-promesse)
- **Fenêtre de lignes bornée (25), pas une vraie portée de fonction** —
un sink au-delà de cette fenêtre échappe à la détection ; une fenêtre
plus large augmenterait le risque de faux positifs (identifiant
réutilisé pour un usage sans rapport).
- **Présence d'un mot-clé de validation ≠ efficacité réelle de cette
validation** — le skill vérifie qu'un appel `sanitize`/`valider_`/etc.
existe entre la source et le sink, jamais que cet appel neutralise
effectivement le risque concerné (une fonction nommée `sanitize` mais
mal implémentée serait quand même acceptée).
- **`open(` en Python est un motif de sink très générique** — usage
légitime bien plus fréquent que malveillant, source de faux positifs
potentiels si une variable de sortie LLM sert par ailleurs de nom de
fichier légitime déjà validé autrement.
- **Pas de résolution d'alias** (`let x = reponse.clone()`) — même limite
assumée que `detecteur-toctou-fichiers`, documentée là comme ici.
- Ne remplace pas une revue de sécurité humaine ni une vraie analyse de
flot de données (type CodeQL/Semgrep avec règles de taint tracking) —
outil de dégrossissage rapide, pas une preuve d'absence de risque.