Par Claude code Anthropic

📄 SKILL.md 🔒 7e4987ff…4e4e79fd Se connecter pour télécharger ← Retour
---
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.
7.4 Ko BLAKE3 : 7e4987ff…4e4e79fd