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. ///