From 40a65d5f3a5590dfc6ec78b700d52dd1487dcfcb Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 8 Nov 2026 06:54:22 +0000 Subject: [PATCH] fix(agent): prevent exact token match bypass in workdir command validation Previously, `validate_command` only checked if a padded token (e.g., `rm `) was in the joined command string. This allowed malicious payloads like `npm run rm` where the exact token had no trailing space to bypass the check. This commit adds a loop to iterate through every parsed token and verifies exact matches against sensitive binaries ("rm", "sudo", "chmod", "chown", "curl", "wget", "ssh", "scp"). Co-authored-by: bobdivx <6737167+bobdivx@users.noreply.github.com> --- .jules/sentinel.md | 4 ++++ crates/agent/src/tools/workdir_cmd.rs | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 1a9528255..ade312db4 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -17,3 +17,7 @@ **Vulnerability:** The `verify_github_sig` function in `apps/server/src/infra_routes.rs` verified GitHub webhook signatures using standard string comparison `expected.eq_ignore_ascii_case(hex_sig)`. **Learning:** String comparison functions exit early as soon as a character mismatch is found. In a cryptographic context, this variable response time leaks information and allows an attacker to perform a timing attack to forge a valid HMAC signature by guessing it byte by byte. **Prevention:** Always use a constant-time comparison library (like `hmac`'s `verify_slice()`) to compare cryptographic hashes, secrets, and signatures to prevent side-channel timing attacks. Avoid custom implementations of standard cryptographic algorithms. +## 2024-05-18 - Exact Token Matching in Command Validation +**Vulnerability:** Command validation in `workdir_cmd.rs` relied on joined strings for multi-word blocked phrases (e.g., `rm `), which allowed an attacker to bypass it by placing the sensitive token at the end of the command string (e.g., `npm run rm`). +**Learning:** Checking a joined string for an exact blocked token padding (like `rm `) is insufficient as it fails to catch exact matches that appear as the last argument where no trailing space exists. Relying solely on tokenized matches also breaks multi-word blocked phrases. Both checks must be implemented simultaneously for robustness. +**Prevention:** Always verify both the joined command string against multi-word blocked phrases AND individual tokenized arguments for exact matches of sensitive binaries. diff --git a/crates/agent/src/tools/workdir_cmd.rs b/crates/agent/src/tools/workdir_cmd.rs index 46a5ff54a..0b5c06144 100644 --- a/crates/agent/src/tools/workdir_cmd.rs +++ b/crates/agent/src/tools/workdir_cmd.rs @@ -112,6 +112,20 @@ fn validate_command(raw: &str) -> std::result::Result, String> { return Err(format!("token interdit dans la commande : {bad}")); } } + for part in &parts { + let p_lower = part.to_ascii_lowercase(); + if p_lower == "rm" + || p_lower == "sudo" + || p_lower == "chmod" + || p_lower == "chown" + || p_lower == "curl" + || p_lower == "wget" + || p_lower == "ssh" + || p_lower == "scp" + { + return Err(format!("token exact interdit dans la commande : {}", part)); + } + } let bin = parts[0].as_str(); let bin_base = Path::new(bin) .file_name() @@ -354,6 +368,7 @@ mod tests { fn blocks_obfuscated_commands() { assert!(validate_command("npm run \"s\"udo").is_err()); assert!(validate_command("cat \"..\"/\"..\"/etc/passwd").is_err()); + assert!(validate_command("npm run rm").is_err()); } }