diff --git a/pkg/tools/shell.go b/pkg/tools/shell.go index c252e69..4867d0f 100644 --- a/pkg/tools/shell.go +++ b/pkg/tools/shell.go @@ -15,7 +15,7 @@ import ( "clawgo/pkg/logger" ) -var blockedRootWipePattern = regexp.MustCompile(`(?i)(^|[;&|\n])\s*rm\s+-rf\s+/\s*($|[;&|\n])`) +var blockedRootWipePattern = regexp.MustCompile(`(?i)(^|[;&|\n])\s*rm\b[^\n;&|]*\s(?:'/'|"/"|/)(?:\s|$)`) type ExecTool struct { workingDir string @@ -160,7 +160,7 @@ func (t *ExecTool) guardCommand(command, cwd string) string { lower := strings.ToLower(cmd) if blockedRootWipePattern.MatchString(lower) { - return "Command blocked by safety guard (rm -rf / is forbidden)" + return "Command blocked by safety guard (removing root path / is forbidden)" } for _, pattern := range t.denyPatterns { diff --git a/pkg/tools/shell_test.go b/pkg/tools/shell_test.go index 8e8a700..c67dd47 100644 --- a/pkg/tools/shell_test.go +++ b/pkg/tools/shell_test.go @@ -103,3 +103,17 @@ func TestSetAllowPatterns_IsCaseInsensitive(t *testing.T) { t.Fatalf("expected case-insensitive allow pattern to match, got %q", msg) } } + +func TestGuardCommand_BlocksRootWipeVariants(t *testing.T) { + tool := &ExecTool{} + cases := []string{ + "rm -rf /", + "rm -fr /", + "rm --no-preserve-root -rf /", + } + for _, c := range cases { + if msg := tool.guardCommand(c, "."); msg == "" { + t.Fatalf("expected root wipe variant to be blocked: %s", c) + } + } +}