diff --git a/crates/agent/src/tools/workdir_cmd.rs b/crates/agent/src/tools/workdir_cmd.rs index 77426cbf8..46a5ff54a 100644 --- a/crates/agent/src/tools/workdir_cmd.rs +++ b/crates/agent/src/tools/workdir_cmd.rs @@ -101,16 +101,17 @@ fn validate_command(raw: &str) -> std::result::Result, String> { if cmd.len() > 400 { return Err("command trop longue (max 400)".into()); } - let lower = cmd.to_ascii_lowercase(); + let parts = shell_words_split(cmd); + if parts.is_empty() { + return Err("command invalide".into()); + } + let joined = parts.join(" "); + let lower = joined.to_ascii_lowercase(); for bad in BLOCKED_TOKENS { if lower.contains(bad) { return Err(format!("token interdit dans la commande : {bad}")); } } - let parts = shell_words_split(cmd); - if parts.is_empty() { - return Err("command invalide".into()); - } let bin = parts[0].as_str(); let bin_base = Path::new(bin) .file_name() @@ -348,6 +349,12 @@ mod tests { fn blocks_curl() { assert!(validate_command("curl https://x").is_err()); } + + #[test] + fn blocks_obfuscated_commands() { + assert!(validate_command("npm run \"s\"udo").is_err()); + assert!(validate_command("cat \"..\"/\"..\"/etc/passwd").is_err()); + } } /// Tue le groupe de processus (npm et ses scripts enfants) si le futur est abandonné.