@@ -4,6 +4,7 @@ import (
44 "context"
55 "encoding/json"
66 "fmt"
7+ "log/slog"
78 "runtime"
89 "strings"
910 "time"
@@ -41,9 +42,74 @@ const (
4142)
4243
4344var bannedCommands = []string{
44− "alias", "curl", "curlie", "wget", "axel", "aria2c",
45− "nc", "telnet", "lynx", "w3m", "links", "httpie", "xh",
46− "http-prompt", "chrome", "firefox", "safari",
45+ // Network/Download tools
46+ "alias",
47+ "aria2c",
48+ "axel",
49+ "chrome",
50+ "curl",
51+ "curlie",
52+ "firefox",
53+ "http-prompt",
54+ "httpie",
55+ "links",
56+ "lynx",
57+ "nc",
58+ "safari",
59+ "telnet",
60+ "w3m",
61+ "wget",
62+ "xh",
63+
64+ // System administration
65+ "doas",
66+ "su",
67+ "sudo",
68+
69+ // Package managers
70+ "apk",
71+ "apt",
72+ "apt-cache",
73+ "apt-get",
74+ "dnf",
75+ "dpkg",
76+ "emerge",
77+ "home-manager",
78+ "makepkg",
79+ "opkg",
80+ "pacman",
81+ "paru",
82+ "pkg",
83+ "pkg_add",
84+ "pkg_delete",
85+ "portage",
86+ "rpm",
87+ "yay",
88+ "yum",
89+ "zypper",
90+
91+ // System modification
92+ "at",
93+ "batch",
94+ "chkconfig",
95+ "crontab",
96+ "fdisk",
97+ "mkfs",
98+ "mount",
99+ "parted",
100+ "service",
101+ "systemctl",
102+ "umount",
103+
104+ // Network configuration
105+ "firewall-cmd",
106+ "ifconfig",
107+ "ip",
108+ "iptables",
109+ "netstat",
110+ "pfctl",
111+ "route",
112+ "ufw",
47113}
48114
49115// getSafeReadOnlyCommands returns platform-appropriate safe commands
@@ -244,7 +310,42 @@ Important:
244310- Never update git config`, bannedCommandsStr, MaxOutputLength)
245311}
246312
313+func blockFuncs() []shell.BlockFunc {
314+ return []shell.BlockFunc{
315+ shell.CommandsBlocker(bannedCommands),
316+ shell.ArgumentsBlocker([][]string{
317+ // System package managers
318+ {"apk", "add"},
319+ {"apt", "install"},
320+ {"apt-get", "install"},
321+ {"dnf", "install"},
322+ {"emerge"},
323+ {"pacman", "-S"},
324+ {"pkg", "install"},
325+ {"yum", "install"},
326+ {"zypper", "install"},
327+
328+ // Language-specific package managers
329+ {"brew", "install"},
330+ {"cargo", "install"},
331+ {"gem", "install"},
332+ {"go", "install"},
333+ {"npm", "install", "-g"},
334+ {"npm", "install", "--global"},
335+ {"pip", "install", "--user"},
336+ {"pip3", "install", "--user"},
337+ {"pnpm", "add", "-g"},
338+ {"pnpm", "add", "--global"},
339+ {"yarn", "global", "add"},
340+ }),
341+ }
342+}
343+
247344func NewBashTool(permission permission.Service, workingDir string) BaseTool {
345+ // Set up command blocking on the persistent shell
346+ persistentShell := shell.GetPersistentShell(workingDir)
347+ persistentShell.SetBlockFuncs(blockFuncs())
348+
248349 return &bashTool{
249350 permissions: permission,
250351 workingDir: workingDir,
@@ -289,13 +390,6 @@ func (b *bashTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
289390 return NewTextErrorResponse("missing command"), nil
290391 }
291392
292− baseCmd := strings.Fields(params.Command)[0]
293− for _, banned := range bannedCommands {
294− if strings.EqualFold(baseCmd, banned) {
295− return NewTextErrorResponse(fmt.Sprintf("command '%s' is not allowed", baseCmd)), nil
296− }
297− }
298−
299393 isSafeReadOnly := false
300394 cmdLower := strings.ToLower(params.Command)
301395
@@ -349,7 +443,20 @@ func (b *bashTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
349443 stdout = truncateOutput(stdout)
350444 stderr = truncateOutput(stderr)
351445
446+ slog.Info("Bash command executed",
447+ "command", params.Command,
448+ "stdout", stdout,
449+ "stderr", stderr,
450+ "exit_code", exitCode,
451+ "interrupted", interrupted,
452+ "err", err,
453+ )
454+
352455 errorMessage := stderr
456+ if errorMessage == "" && err != nil {
457+ errorMessage = err.Error()
458+ }
459+
353460 if interrupted {
354461 if errorMessage != "" {
355462 errorMessage += "\n"