From 477bb89f10d571f09ac77a1d364b0ed276fb981c Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 4 Feb 2026 15:52:14 -0500 Subject: [PATCH] Expand hardcoded agent terminal security rules (#48399) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expands the hardcoded security rules that block dangerous `rm` commands in the agent terminal tool. ### New blocked patterns - `rm -rf $HOME` / `rm -rf $HOME/` / `rm -rf ${HOME}` / `rm -rf ${HOME}/` - `rm -rf .` / `rm -rf ./` - `rm -rf ..` / `rm -rf ../` - `rm -rf ~/` (previously only `rm -rf ~` was blocked) ### Flag handling improvements - Simplified the flag character class from `[rRfF]` to `[rf]` since the regex is already compiled with case-insensitive mode — less confusing, same behavior. - Added tests verifying that reversed flags (`-fr`), uppercase (`RM -RF`), split flags (`-r -f`), and chained commands all get caught. ### Safe commands still allowed Paths like `rm -rf ./build`, `rm -rf ~/Documents`, `rm -rf $HOME/Documents`, `rm -rf ../some_dir`, and `rm -rf .hidden_dir` are **not** blocked. Release Notes: - Auto-block a wider range of agent terminal commands, e.g. `rm -rf $HOME` in addition to `rm -rf ~` --- crates/agent/src/tool_permissions.rs | 71 ++++++++++++++++++++++++++-- crates/settings_content/src/agent.rs | 5 +- 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/crates/agent/src/tool_permissions.rs b/crates/agent/src/tool_permissions.rs index 215e979f798addb339f6b5929adea4500fb60e40..62742622bf909455dfd8998a87393fca0e08c5ec 100644 --- a/crates/agent/src/tool_permissions.rs +++ b/crates/agent/src/tool_permissions.rs @@ -17,12 +17,22 @@ pub struct HardcodedSecurityRules { pub static HARDCODED_SECURITY_RULES: LazyLock = LazyLock::new(|| { HardcodedSecurityRules { + // Case-insensitive; `(-[rf]+\s+)*` handles `-rf`, `-fr`, `-RF`, `-r -f`, etc. terminal_deny: vec![ // Recursive deletion of root - "rm -rf /" or "rm -rf / " - CompiledRegex::new(r"rm\s+(-[rRfF]+\s+)*/\s*$", false) + CompiledRegex::new(r"rm\s+(-[rf]+\s+)*/\s*$", false) .expect("hardcoded regex should compile"), - // Recursive deletion of home - "rm -rf ~" (but not ~/subdir) - CompiledRegex::new(r"rm\s+(-[rRfF]+\s+)*~\s*$", false) + // Recursive deletion of home - "rm -rf ~" or "rm -rf ~/" (but not ~/subdir) + CompiledRegex::new(r"rm\s+(-[rf]+\s+)*~/?\s*$", false) + .expect("hardcoded regex should compile"), + // Recursive deletion of home via $HOME - "rm -rf $HOME" or "rm -rf ${HOME}" + CompiledRegex::new(r"rm\s+(-[rf]+\s+)*(\$HOME|\$\{HOME\})/?\s*$", false) + .expect("hardcoded regex should compile"), + // Recursive deletion of current directory - "rm -rf ." or "rm -rf ./" + CompiledRegex::new(r"rm\s+(-[rf]+\s+)*\./?\s*$", false) + .expect("hardcoded regex should compile"), + // Recursive deletion of parent directory - "rm -rf .." or "rm -rf ../" + CompiledRegex::new(r"rm\s+(-[rf]+\s+)*\.\./?\s*$", false) .expect("hardcoded regex should compile"), ], } @@ -1065,14 +1075,44 @@ mod tests { #[test] fn hardcoded_blocks_rm_rf_root() { - // rm -rf / should be blocked by hardcoded rules t("rm -rf /").is_deny(); + t("rm -fr /").is_deny(); + t("rm -RF /").is_deny(); + t("rm -FR /").is_deny(); + t("rm -r -f /").is_deny(); + t("rm -f -r /").is_deny(); + t("RM -RF /").is_deny(); } #[test] fn hardcoded_blocks_rm_rf_home() { - // rm -rf ~ should be blocked by hardcoded rules t("rm -rf ~").is_deny(); + t("rm -fr ~").is_deny(); + t("rm -rf ~/").is_deny(); + t("rm -rf $HOME").is_deny(); + t("rm -fr $HOME").is_deny(); + t("rm -rf $HOME/").is_deny(); + t("rm -rf ${HOME}").is_deny(); + t("rm -rf ${HOME}/").is_deny(); + t("rm -RF $HOME").is_deny(); + t("rm -FR ${HOME}/").is_deny(); + t("rm -R -F ${HOME}/").is_deny(); + t("RM -RF ~").is_deny(); + } + + #[test] + fn hardcoded_blocks_rm_rf_dot() { + t("rm -rf .").is_deny(); + t("rm -fr .").is_deny(); + t("rm -rf ./").is_deny(); + t("rm -rf ..").is_deny(); + t("rm -fr ..").is_deny(); + t("rm -rf ../").is_deny(); + t("rm -RF .").is_deny(); + t("rm -FR ../").is_deny(); + t("rm -R -F ../").is_deny(); + t("RM -RF .").is_deny(); + t("RM -RF ..").is_deny(); } #[test] @@ -1080,12 +1120,18 @@ mod tests { // Even with always_allow_tool_actions=true, hardcoded rules block t("rm -rf /").global(true).is_deny(); t("rm -rf ~").global(true).is_deny(); + t("rm -rf $HOME").global(true).is_deny(); + t("rm -rf .").global(true).is_deny(); + t("rm -rf ..").global(true).is_deny(); } #[test] fn hardcoded_cannot_be_bypassed_by_allow_pattern() { // Even with an allow pattern that matches, hardcoded rules block t("rm -rf /").allow(&[".*"]).is_deny(); + t("rm -rf $HOME").allow(&[".*"]).is_deny(); + t("rm -rf .").allow(&[".*"]).is_deny(); + t("rm -rf ..").allow(&[".*"]).is_deny(); } #[test] @@ -1097,6 +1143,18 @@ mod tests { t("rm -rf /tmp/test") .mode(ToolPermissionMode::Allow) .is_allow(); + t("rm -rf ~/Documents") + .mode(ToolPermissionMode::Allow) + .is_allow(); + t("rm -rf $HOME/Documents") + .mode(ToolPermissionMode::Allow) + .is_allow(); + t("rm -rf ../some_dir") + .mode(ToolPermissionMode::Allow) + .is_allow(); + t("rm -rf .hidden_dir") + .mode(ToolPermissionMode::Allow) + .is_allow(); } #[test] @@ -1105,5 +1163,8 @@ mod tests { t("ls && rm -rf /").is_deny(); t("echo hello; rm -rf ~").is_deny(); t("cargo build && rm -rf /").global(true).is_deny(); + t("echo hello; rm -rf $HOME").is_deny(); + t("echo hello; rm -rf .").is_deny(); + t("echo hello; rm -rf ..").is_deny(); } } diff --git a/crates/settings_content/src/agent.rs b/crates/settings_content/src/agent.rs index a3ea6a9784b0ed2c203bda060caf95367568903f..ad00990f0f1fc38b5068cd538f927f0aa8412b3c 100644 --- a/crates/settings_content/src/agent.rs +++ b/crates/settings_content/src/agent.rs @@ -71,8 +71,9 @@ pub struct AgentSettingsContent { /// that you allow it, always choose to allow it. /// /// **Security note**: Even with this enabled, Zed's built-in security rules - /// still block some tool actions, such as the terminal tool running `rm -rf /` or `rm -rf ~`, - /// to prevent certain classes of failures from happening. + /// still block some tool actions, such as the terminal tool running `rm -rf /`, `rm -rf ~`, + /// `rm -rf $HOME`, `rm -rf .`, or `rm -rf ..`, to prevent certain classes of failures + /// from happening. /// /// This setting has no effect on external agents that support permission modes, such as Claude Code. ///