From 5100d3a009de7a202ecad518d7b24d0f5990691e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2026 07:16:19 +0000 Subject: [PATCH] Fix command blocklist bypass via trailing spaces When a blocklist rule contained a space (like `"rm "`) to prevent false positives on legitimate commands (like `"format"`), it failed to match if the blocked token was at the very end of the command string (e.g. `npm run rm`). This patch tokenizes the command first and enforces exact matches for critical sensitive commands (like `rm`), while retaining the existing substring check on the joined command to support multi-word blocks. Co-authored-by: bobdivx <6737167+bobdivx@users.noreply.github.com> --- .jules/sentinel.md | 5 +++++ crates/agent/src/tools/workdir_cmd.rs | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 1a9528255..dd7a883d1 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. + +## 2024-11-04 - Fix command blocklist bypass via trailing spaces +**Vulnerability:** The blocklist check in `crates/agent/src/tools/workdir_cmd.rs` used `.contains("rm ")` with a space to prevent false positives like "format". However, this failed to block "rm" if it was at the very end of the command string (e.g. `npm run rm`). +**Learning:** Checking for substrings with spaces is fragile and easily bypassed if the string is the last token in a command without a trailing space. +**Prevention:** Implement exact match token checking after shell word splitting, while keeping the main string match for multi-word blocks. diff --git a/crates/agent/src/tools/workdir_cmd.rs b/crates/agent/src/tools/workdir_cmd.rs index 46a5ff54a..3cd28940c 100644 --- a/crates/agent/src/tools/workdir_cmd.rs +++ b/crates/agent/src/tools/workdir_cmd.rs @@ -105,6 +105,7 @@ fn validate_command(raw: &str) -> std::result::Result, String> { if parts.is_empty() { return Err("command invalide".into()); } + let joined = parts.join(" "); let lower = joined.to_ascii_lowercase(); for bad in BLOCKED_TOKENS { @@ -112,6 +113,18 @@ fn validate_command(raw: &str) -> std::result::Result, String> { return Err(format!("token interdit dans la commande : {bad}")); } } + + // Validate individual tokenized arguments to prevent quotation/concatenation bypasses + for part in &parts { + let lower = part.to_ascii_lowercase(); + + // Exact match check to block bypasses like `npm run rm` + // where "rm " with space fails to match "rm" from the joined string. + if lower == "rm" { + return Err(format!("commande interdite : {lower}")); + } + } + let bin = parts[0].as_str(); let bin_base = Path::new(bin) .file_name()