Expand hardcoded agent terminal security rules (#48399)

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 ~`
This commit is contained in:
Richard Feldman authored and GitHub committed 2026-02-04 20:52:14 +00:00
1 parent 8f67296991
commit 7b6057de46
2 files changed
+69 -7

No files matched your search

+66 -5
View File
@@ -17,12 +17,22 @@ pub struct HardcodedSecurityRules {
pub static HARDCODED_SECURITY_RULES: LazyLock<HardcodedSecurityRules> = 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();
}
}
+3 -2
View File
@@ -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.
///