@@ -38,8 +38,38 @@ var BannedCommands = []string{
40var SafeReadOnlyCommands = []string{
40var SafeReadOnlyCommands = []string{
41+ // Basic shell commands
41 "ls", "echo", "pwd", "date", "cal", "uptime", "whoami", "id", "groups", "env", "printenv", "set", "unset", "which", "type", "whereis",
42 "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",
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",
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",
53+ "node", "npm", "npx", "yarn", "pnpm",
56+ "python", "python3", "pip", "pip3", "pytest", "pylint", "mypy", "black", "isort", "flake8", "ruff",
59+ "docker ps", "docker images", "docker volume", "docker network", "docker info", "docker version",
60+ "docker-compose ps", "docker-compose config",
62+ // Kubernetes commands
63+ "kubectl get", "kubectl describe", "kubectl logs", "kubectl version", "kubectl config",
66+ "cargo", "rustc", "rustup",
69+ "java", "javac", "mvn", "gradle",
71+ // Misc development tools
72+ "make", "cmake", "bazel", "terraform plan", "terraform validate", "ansible",
45func (b *bashTool) Info() ToolInfo {
75func (b *bashTool) Info() ToolInfo {
@@ -77,17 +107,26 @@ func (b *bashTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
77 return NewTextErrorResponse("missing command"), nil
107 return NewTextErrorResponse("missing command"), nil
110+ // Check for banned commands (first word only)
80 baseCmd := strings.Fields(params.Command)[0]
111 baseCmd := strings.Fields(params.Command)[0]
81 for _, banned := range BannedCommands {
112 for _, banned := range BannedCommands {
82 if strings.EqualFold(baseCmd, banned) {
113 if strings.EqualFold(baseCmd, banned) {
83 return NewTextErrorResponse(fmt.Sprintf("command '%s' is not allowed", baseCmd)), nil
114 return NewTextErrorResponse(fmt.Sprintf("command '%s' is not allowed", baseCmd)), nil
118+ // Check for safe commands (can be multi-word)
86 isSafeReadOnly := false
119 isSafeReadOnly := false
120+ cmdLower := strings.ToLower(params.Command)
87 for _, safe := range SafeReadOnlyCommands {
122 for _, safe := range SafeReadOnlyCommands {
88− if strings.EqualFold(baseCmd, safe) {
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)] == '-' {
89− isSafeReadOnly = true
127+ isSafeReadOnly = true
93 if !isSafeReadOnly {
132 if !isSafeReadOnly {