on main help sign in
chore: better naming
2fdbcacKujtim Hoxha
internal/llm/tools/bash.go+93−8
@@ -42,9 +42,74 @@ const (
42)
42)
43
43
44var bannedCommands = []string{
44var bannedCommands = []string{
45− "alias", "curl", "curlie", "wget", "axel", "aria2c",
45+ // Network/Download tools
46− "nc", "telnet", "lynx", "w3m", "links", "httpie", "xh",
46+ "alias",
47− "http-prompt", "chrome", "firefox", "safari", "sudo",
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",
48}
113}
49
114
50// getSafeReadOnlyCommands returns platform-appropriate safe commands
115// getSafeReadOnlyCommands returns platform-appropriate safe commands
@@ -245,13 +310,33 @@ Important:
245- Never update git config`, bannedCommandsStr, MaxOutputLength)
310- Never update git config`, bannedCommandsStr, MaxOutputLength)
246}
311}
247
312
248−func createCommandBlockFuncs() []shell.CommandBlockFunc {
313+func blockFuncs() []shell.BlockFunc {
249− return []shell.CommandBlockFunc{
314+ return []shell.BlockFunc{
250− shell.CreateSimpleCommandBlocker(bannedCommands),
315+ shell.CommandsBlocker(bannedCommands),
251− shell.CreateSubCommandBlocker([][]string{
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
252 {"brew", "install"},
329 {"brew", "install"},
330+ {"cargo", "install"},
331+ {"gem", "install"},
332+ {"go", "install"},
253 {"npm", "install", "-g"},
333 {"npm", "install", "-g"},
254 {"npm", "install", "--global"},
334 {"npm", "install", "--global"},
335+ {"pip", "install", "--user"},
336+ {"pip3", "install", "--user"},
337+ {"pnpm", "add", "-g"},
338+ {"pnpm", "add", "--global"},
339+ {"yarn", "global", "add"},
255 }),
340 }),
256 }
341 }
257}
342}
@@ -259,7 +344,7 @@ func createCommandBlockFuncs() []shell.CommandBlockFunc {
259func NewBashTool(permission permission.Service, workingDir string) BaseTool {
344func NewBashTool(permission permission.Service, workingDir string) BaseTool {
260 // Set up command blocking on the persistent shell
345 // Set up command blocking on the persistent shell
261 persistentShell := shell.GetPersistentShell(workingDir)
346 persistentShell := shell.GetPersistentShell(workingDir)
262− persistentShell.SetBlockFuncs(createCommandBlockFuncs())
347+ persistentShell.SetBlockFuncs(blockFuncs())
263
348
264 return &bashTool{
349 return &bashTool{
265 permissions: permission,
350 permissions: permission,
internal/shell/command_block_test.go+12−12
@@ -10,13 +10,13 @@ import (
10func TestCommandBlocking(t *testing.T) {
10func TestCommandBlocking(t *testing.T) {
11 tests := []struct {
11 tests := []struct {
12 name string
12 name string
13− blockFuncs []CommandBlockFunc
13+ blockFuncs []BlockFunc
14 command string
14 command string
15 shouldBlock bool
15 shouldBlock bool
16 }{
16 }{
17 {
17 {
18 name: "block simple command",
18 name: "block simple command",
19− blockFuncs: []CommandBlockFunc{
19+ blockFuncs: []BlockFunc{
20 func(args []string) bool {
20 func(args []string) bool {
21 return len(args) > 0 && args[0] == "curl"
21 return len(args) > 0 && args[0] == "curl"
22 },
22 },
@@ -26,7 +26,7 @@ func TestCommandBlocking(t *testing.T) {
26 },
26 },
27 {
27 {
28 name: "allow non-blocked command",
28 name: "allow non-blocked command",
29− blockFuncs: []CommandBlockFunc{
29+ blockFuncs: []BlockFunc{
30 func(args []string) bool {
30 func(args []string) bool {
31 return len(args) > 0 && args[0] == "curl"
31 return len(args) > 0 && args[0] == "curl"
32 },
32 },
@@ -36,7 +36,7 @@ func TestCommandBlocking(t *testing.T) {
36 },
36 },
37 {
37 {
38 name: "block subcommand",
38 name: "block subcommand",
39− blockFuncs: []CommandBlockFunc{
39+ blockFuncs: []BlockFunc{
40 func(args []string) bool {
40 func(args []string) bool {
41 return len(args) >= 2 && args[0] == "brew" && args[1] == "install"
41 return len(args) >= 2 && args[0] == "brew" && args[1] == "install"
42 },
42 },
@@ -46,7 +46,7 @@ func TestCommandBlocking(t *testing.T) {
46 },
46 },
47 {
47 {
48 name: "allow different subcommand",
48 name: "allow different subcommand",
49− blockFuncs: []CommandBlockFunc{
49+ blockFuncs: []BlockFunc{
50 func(args []string) bool {
50 func(args []string) bool {
51 return len(args) >= 2 && args[0] == "brew" && args[1] == "install"
51 return len(args) >= 2 && args[0] == "brew" && args[1] == "install"
52 },
52 },
@@ -56,8 +56,8 @@ func TestCommandBlocking(t *testing.T) {
56 },
56 },
57 {
57 {
58 name: "block npm global install with -g",
58 name: "block npm global install with -g",
59− blockFuncs: []CommandBlockFunc{
59+ blockFuncs: []BlockFunc{
60− CreateSubCommandBlocker([][]string{
60+ ArgumentsBlocker([][]string{
61 {"npm", "install", "-g"},
61 {"npm", "install", "-g"},
62 {"npm", "install", "--global"},
62 {"npm", "install", "--global"},
63 }),
63 }),
@@ -67,8 +67,8 @@ func TestCommandBlocking(t *testing.T) {
67 },
67 },
68 {
68 {
69 name: "block npm global install with --global",
69 name: "block npm global install with --global",
70− blockFuncs: []CommandBlockFunc{
70+ blockFuncs: []BlockFunc{
71− CreateSubCommandBlocker([][]string{
71+ ArgumentsBlocker([][]string{
72 {"npm", "install", "-g"},
72 {"npm", "install", "-g"},
73 {"npm", "install", "--global"},
73 {"npm", "install", "--global"},
74 }),
74 }),
@@ -78,8 +78,8 @@ func TestCommandBlocking(t *testing.T) {
78 },
78 },
79 {
79 {
80 name: "allow npm local install",
80 name: "allow npm local install",
81− blockFuncs: []CommandBlockFunc{
81+ blockFuncs: []BlockFunc{
82− CreateSubCommandBlocker([][]string{
82+ ArgumentsBlocker([][]string{
83 {"npm", "install", "-g"},
83 {"npm", "install", "-g"},
84 {"npm", "install", "--global"},
84 {"npm", "install", "--global"},
85 }),
85 }),
@@ -120,4 +120,4 @@ func TestCommandBlocking(t *testing.T) {
120 }
120 }
121 })
121 })
122 }
122 }
123−}
123+}
internal/shell/shell.go+13−12
@@ -44,8 +44,8 @@ type noopLogger struct{}
44
44
45func (noopLogger) InfoPersist(msg string, keysAndValues ...interface{}) {}
45func (noopLogger) InfoPersist(msg string, keysAndValues ...interface{}) {}
46
46
47−// CommandBlockFunc is a function that determines if a command should be blocked
47+// BlockFunc is a function that determines if a command should be blocked
48−type CommandBlockFunc func(args []string) bool
48+type BlockFunc func(args []string) bool
49
49
50// Shell provides cross-platform shell execution with optional state persistence
50// Shell provides cross-platform shell execution with optional state persistence
51type Shell struct {
51type Shell struct {
@@ -53,7 +53,7 @@ type Shell struct {
53 cwd string
53 cwd string
54 mu sync.Mutex
54 mu sync.Mutex
55 logger Logger
55 logger Logger
56− blockFuncs []CommandBlockFunc
56+ blockFuncs []BlockFunc
57}
57}
58
58
59// Options for creating a new shell
59// Options for creating a new shell
@@ -61,7 +61,7 @@ type Options struct {
61 WorkingDir string
61 WorkingDir string
62 Env []string
62 Env []string
63 Logger Logger
63 Logger Logger
64− BlockFuncs []CommandBlockFunc
64+ BlockFuncs []BlockFunc
65}
65}
66
66
67// NewShell creates a new shell instance with the given options
67// NewShell creates a new shell instance with the given options
@@ -159,7 +159,7 @@ func (s *Shell) SetEnv(key, value string) {
159}
159}
160
160
161// SetBlockFuncs sets the command block functions for the shell
161// SetBlockFuncs sets the command block functions for the shell
162−func (s *Shell) SetBlockFuncs(blockFuncs []CommandBlockFunc) {
162+func (s *Shell) SetBlockFuncs(blockFuncs []BlockFunc) {
163 s.mu.Lock()
163 s.mu.Lock()
164 defer s.mu.Unlock()
164 defer s.mu.Unlock()
165 s.blockFuncs = blockFuncs
165 s.blockFuncs = blockFuncs
@@ -216,13 +216,13 @@ func (s *Shell) determineShellType(command string) ShellType {
216 return ShellTypePOSIX
216 return ShellTypePOSIX
217}
217}
218
218
219−// CreateSimpleCommandBlocker creates a CommandBlockFunc that blocks exact command matches
219+// CommandsBlocker creates a BlockFunc that blocks exact command matches
220−func CreateSimpleCommandBlocker(bannedCommands []string) CommandBlockFunc {
220+func CommandsBlocker(bannedCommands []string) BlockFunc {
221 bannedSet := make(map[string]bool)
221 bannedSet := make(map[string]bool)
222 for _, cmd := range bannedCommands {
222 for _, cmd := range bannedCommands {
223 bannedSet[cmd] = true
223 bannedSet[cmd] = true
224 }
224 }
225−
225+
226 return func(args []string) bool {
226 return func(args []string) bool {
227 if len(args) == 0 {
227 if len(args) == 0 {
228 return false
228 return false
@@ -231,8 +231,8 @@ func CreateSimpleCommandBlocker(bannedCommands []string) CommandBlockFunc {
231 }
231 }
232}
232}
233
233
234−// CreateSubCommandBlocker creates a CommandBlockFunc that blocks specific subcommands
234+// ArgumentsBlocker creates a BlockFunc that blocks specific subcommands
235−func CreateSubCommandBlocker(blockedSubCommands [][]string) CommandBlockFunc {
235+func ArgumentsBlocker(blockedSubCommands [][]string) BlockFunc {
236 return func(args []string) bool {
236 return func(args []string) bool {
237 for _, blocked := range blockedSubCommands {
237 for _, blocked := range blockedSubCommands {
238 if len(args) >= len(blocked) {
238 if len(args) >= len(blocked) {
@@ -251,7 +251,8 @@ func CreateSubCommandBlocker(blockedSubCommands [][]string) CommandBlockFunc {
251 return false
251 return false
252 }
252 }
253}
253}
254+
254−func (s *Shell) createCommandBlockHandler() func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {
255+func (s *Shell) blockHandler() func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {
255 return func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {
256 return func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {
256 return func(ctx context.Context, args []string) error {
257 return func(ctx context.Context, args []string) error {
257 if len(args) == 0 {
258 if len(args) == 0 {
@@ -357,7 +358,7 @@ func (s *Shell) execPOSIX(ctx context.Context, command string) (string, string,
357 interp.Interactive(false),
358 interp.Interactive(false),
358 interp.Env(expand.ListEnviron(s.env...)),
359 interp.Env(expand.ListEnviron(s.env...)),
359 interp.Dir(s.cwd),
360 interp.Dir(s.cwd),
360− interp.ExecHandlers(s.createCommandBlockHandler()),
361+ interp.ExecHandlers(s.blockHandler()),
361 )
362 )
362 if err != nil {
363 if err != nil {
363 return "", "", fmt.Errorf("could not run command: %w", err)
364 return "", "", fmt.Errorf("could not run command: %w", err)