From 67e3c30be284edc77ce38d8fd1dbac64546ddb6e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 11 Nov 2026 07:01:45 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITICAL]?= =?UTF-8?q?=20Fix=20command=20parser=20bypass=20allowing=20exact=20'rm'=20?= =?UTF-8?q?usage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚨 Severity: CRITICAL 💡 Vulnerability: The `validate_command` check searched for the blocked token `"rm "` (with trailing space). It failed to match exact invocations of `rm` at the end of the line, such as `npm run rm`. 🎯 Impact: Arbitrary deletion of repository files using obfuscated or trailing calls to rm in package management scripts. 🔧 Fix: Added exact token matching iteration over the split words, verifying if any single token equals `"rm"`. ✅ Verification: Added tests explicitly asserting that `npm run rm` fails validation, run `cargo test -p devforge-agent`. Co-authored-by: bobdivx <6737167+bobdivx@users.noreply.github.com> --- .jules/sentinel.md | 5 +++++ crates/agent/src/tools/workdir_cmd.rs | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 1a9528255..9683797cd 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -17,3 +17,8 @@ **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. + +## 2026-11-11 - [Bypass Command Parsing - rm] +**Vulnerability:** The `validate_command` function in `crates/agent/src/tools/workdir_cmd.rs` used a trailing space in the `BLOCKED_TOKENS` list for `rm ` and `rm\t` to prevent matching prefixes like `arm` or `chmod`. This logic, however, did not block exact uses of `rm` at the end of a command string, such as `npm run rm`. It also relied primarily on the joined command string without checking the individual parsed tokens for exact matches of these sensitive binaries. +**Learning:** Checking for substrings against joined command strings can be bypassed if the restricted token happens at the end of the string where a space is not appended, or if the tokenizer fails to match exact words. +**Prevention:** Validation for string tokens should run not just against the joined command but also check for exact token matches over the tokenized arguments (e.g. iterate over tokens and verify `token.to_ascii_lowercase() == "rm"`). diff --git a/crates/agent/src/tools/workdir_cmd.rs b/crates/agent/src/tools/workdir_cmd.rs index 46a5ff54a..714a8e584 100644 --- a/crates/agent/src/tools/workdir_cmd.rs +++ b/crates/agent/src/tools/workdir_cmd.rs @@ -112,6 +112,12 @@ 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" { + return Err(format!("token interdit dans la commande : rm")); + } + } let bin = parts[0].as_str(); let bin_base = Path::new(bin) .file_name() @@ -354,6 +360,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()); } }