@@ -38,8 +38,38 @@ var BannedCommands = []string{
3838}
3939
4040var SafeReadOnlyCommands = []string{
41+ // Basic shell commands
4142 "ls", "echo", "pwd", "date", "cal", "uptime", "whoami", "id", "groups", "env", "printenv", "set", "unset", "which", "type", "whereis",
42โ "whatis", //...
43+ "whatis", "uname", "hostname", "df", "du", "free", "top", "ps", "kill", "killall", "nice", "nohup", "time", "timeout",
44+
45+ // Git read-only commands
46+ "git status", "git log", "git diff", "git show", "git branch", "git tag", "git remote", "git ls-files", "git ls-remote",
47+ "git rev-parse", "git config --get", "git config --list", "git describe", "git blame", "git grep", "git shortlog",
48+
49+ // Go commands
50+ "go version", "go list", "go env", "go doc", "go vet", "go fmt", "go mod", "go test", "go build", "go run", "go install", "go clean",
51+
52+ // Node.js commands
53+ "node", "npm", "npx", "yarn", "pnpm",
54+
55+ // Python commands
56+ "python", "python3", "pip", "pip3", "pytest", "pylint", "mypy", "black", "isort", "flake8", "ruff",
57+
58+ // Docker commands
59+ "docker ps", "docker images", "docker volume", "docker network", "docker info", "docker version",
60+ "docker-compose ps", "docker-compose config",
61+
62+ // Kubernetes commands
63+ "kubectl get", "kubectl describe", "kubectl logs", "kubectl version", "kubectl config",
64+
65+ // Rust commands
66+ "cargo", "rustc", "rustup",
67+
68+ // Java commands
69+ "java", "javac", "mvn", "gradle",
70+
71+ // Misc development tools
72+ "make", "cmake", "bazel", "terraform plan", "terraform validate", "ansible",
4373}
4474
4575func (b *bashTool) Info() ToolInfo {
@@ -77,17 +107,26 @@ func (b *bashTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
77107 return NewTextErrorResponse("missing command"), nil
78108 }
79109
110+ // Check for banned commands (first word only)
80111 baseCmd := strings.Fields(params.Command)[0]
81112 for _, banned := range BannedCommands {
82113 if strings.EqualFold(baseCmd, banned) {
83114 return NewTextErrorResponse(fmt.Sprintf("command '%s' is not allowed", baseCmd)), nil
84115 }
85116 }
117+
118+ // Check for safe commands (can be multi-word)
86119 isSafeReadOnly := false
120+ cmdLower := strings.ToLower(params.Command)
121+
87122 for _, safe := range SafeReadOnlyCommands {
88โ if strings.EqualFold(baseCmd, safe) {
89โ isSafeReadOnly = true
90โ break
123+ // Check if command starts with the safe command pattern
124+ if strings.HasPrefix(cmdLower, strings.ToLower(safe)) {
125+ // Make sure it's either an exact match or followed by a space or flag
126+ if len(cmdLower) == len(safe) || cmdLower[len(safe)] == ' ' || cmdLower[len(safe)] == '-' {
127+ isSafeReadOnly = true
128+ break
129+ }
91130 }
92131 }
93132 if !isSafeReadOnly {