mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-06-13 14:53:11 +08:00
feat: implement shell sandbox and security policies inspired by goclaw
This commit is contained in:
BIN
clawgo_test
BIN
clawgo_test
Binary file not shown.
@@ -44,7 +44,7 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
|
|||||||
toolsRegistry.Register(&tools.ReadFileTool{})
|
toolsRegistry.Register(&tools.ReadFileTool{})
|
||||||
toolsRegistry.Register(&tools.WriteFileTool{})
|
toolsRegistry.Register(&tools.WriteFileTool{})
|
||||||
toolsRegistry.Register(&tools.ListDirTool{})
|
toolsRegistry.Register(&tools.ListDirTool{})
|
||||||
toolsRegistry.Register(tools.NewExecTool(workspace))
|
toolsRegistry.Register(tools.NewExecTool(cfg.Tools.Shell, workspace))
|
||||||
|
|
||||||
if cs != nil {
|
if cs != nil {
|
||||||
toolsRegistry.Register(tools.NewRemindTool(cs))
|
toolsRegistry.Register(tools.NewRemindTool(cs))
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/caarlos0/env/v11"
|
"github.com/caarlos0/env/v11"
|
||||||
)
|
)
|
||||||
@@ -112,8 +113,30 @@ type WebToolsConfig struct {
|
|||||||
Search WebSearchConfig `json:"search"`
|
Search WebSearchConfig `json:"search"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ShellConfig struct {
|
||||||
|
Enabled bool `json:"enabled" env:"CLAWGO_TOOLS_SHELL_ENABLED"`
|
||||||
|
WorkingDir string `json:"working_dir" env:"CLAWGO_TOOLS_SHELL_WORKING_DIR"`
|
||||||
|
Timeout time.Duration `json:"timeout" env:"CLAWGO_TOOLS_SHELL_TIMEOUT"`
|
||||||
|
DeniedCmds []string `json:"denied_cmds" env:"CLAWGO_TOOLS_SHELL_DENIED_CMDS"`
|
||||||
|
AllowedCmds []string `json:"allowed_cmds" env:"CLAWGO_TOOLS_SHELL_ALLOWED_CMDS"`
|
||||||
|
Sandbox SandboxConfig `json:"sandbox"`
|
||||||
|
RestrictPath bool `json:"restrict_path" env:"CLAWGO_TOOLS_SHELL_RESTRICT_PATH"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SandboxConfig struct {
|
||||||
|
Enabled bool `json:"enabled" env:"CLAWGO_TOOLS_SHELL_SANDBOX_ENABLED"`
|
||||||
|
Image string `json:"image" env:"CLAWGO_TOOLS_SHELL_SANDBOX_IMAGE"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FilesystemConfig struct {
|
||||||
|
AllowedPaths []string `json:"allowed_paths" env:"CLAWGO_TOOLS_FILESYSTEM_ALLOWED_PATHS"`
|
||||||
|
DeniedPaths []string `json:"denied_paths" env:"CLAWGO_TOOLS_FILESYSTEM_DENIED_PATHS"`
|
||||||
|
}
|
||||||
|
|
||||||
type ToolsConfig struct {
|
type ToolsConfig struct {
|
||||||
Web WebToolsConfig `json:"web"`
|
Web WebToolsConfig `json:"web"`
|
||||||
|
Shell ShellConfig `json:"shell"`
|
||||||
|
Filesystem FilesystemConfig `json:"filesystem"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -212,6 +235,21 @@ func DefaultConfig() *Config {
|
|||||||
MaxResults: 5,
|
MaxResults: 5,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Shell: ShellConfig{
|
||||||
|
Enabled: true,
|
||||||
|
Timeout: 60 * time.Second,
|
||||||
|
DeniedCmds: []string{
|
||||||
|
"rm -rf /", "dd if=", "mkfs", "shutdown", "reboot",
|
||||||
|
},
|
||||||
|
Sandbox: SandboxConfig{
|
||||||
|
Enabled: false,
|
||||||
|
Image: "golang:alpine",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Filesystem: FilesystemConfig{
|
||||||
|
AllowedPaths: []string{},
|
||||||
|
DeniedPaths: []string{"/etc/shadow", "/etc/passwd"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"clawgo/pkg/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ExecTool struct {
|
type ExecTool struct {
|
||||||
@@ -18,26 +20,23 @@ type ExecTool struct {
|
|||||||
denyPatterns []*regexp.Regexp
|
denyPatterns []*regexp.Regexp
|
||||||
allowPatterns []*regexp.Regexp
|
allowPatterns []*regexp.Regexp
|
||||||
restrictToWorkspace bool
|
restrictToWorkspace bool
|
||||||
|
sandboxEnabled bool
|
||||||
|
sandboxImage string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewExecTool(workingDir string) *ExecTool {
|
func NewExecTool(cfg config.ShellConfig, workspace string) *ExecTool {
|
||||||
denyPatterns := []*regexp.Regexp{
|
denyPatterns := make([]*regexp.Regexp, 0)
|
||||||
regexp.MustCompile(`\brm\s+-[rf]{1,2}\b`),
|
for _, p := range cfg.DeniedCmds {
|
||||||
regexp.MustCompile(`\bdel\s+/[fq]\b`),
|
denyPatterns = append(denyPatterns, regexp.MustCompile(`\b`+regexp.QuoteMeta(p)+`\b`))
|
||||||
regexp.MustCompile(`\brmdir\s+/s\b`),
|
|
||||||
regexp.MustCompile(`\b(format|mkfs|diskpart)\b\s`), // Match disk wiping commands (must be followed by space/args)
|
|
||||||
regexp.MustCompile(`\bdd\s+if=`),
|
|
||||||
regexp.MustCompile(`>\s*/dev/sd[a-z]\b`), // Block writes to disk devices (but allow /dev/null)
|
|
||||||
regexp.MustCompile(`\b(shutdown|reboot|poweroff)\b`),
|
|
||||||
regexp.MustCompile(`:\(\)\s*\{.*\};\s*:`),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ExecTool{
|
return &ExecTool{
|
||||||
workingDir: workingDir,
|
workingDir: workspace,
|
||||||
timeout: 60 * time.Second,
|
timeout: cfg.Timeout,
|
||||||
denyPatterns: denyPatterns,
|
denyPatterns: denyPatterns,
|
||||||
allowPatterns: nil,
|
restrictToWorkspace: cfg.RestrictPath,
|
||||||
restrictToWorkspace: false,
|
sandboxEnabled: cfg.Sandbox.Enabled,
|
||||||
|
sandboxImage: cfg.Sandbox.Image,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +87,10 @@ func (t *ExecTool) Execute(ctx context.Context, args map[string]interface{}) (st
|
|||||||
return fmt.Sprintf("Error: %s", guardError), nil
|
return fmt.Sprintf("Error: %s", guardError), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if t.sandboxEnabled {
|
||||||
|
return t.executeInSandbox(ctx, command, cwd)
|
||||||
|
}
|
||||||
|
|
||||||
cmdCtx, cancel := context.WithTimeout(ctx, t.timeout)
|
cmdCtx, cancel := context.WithTimeout(ctx, t.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -125,6 +128,38 @@ func (t *ExecTool) Execute(ctx context.Context, args map[string]interface{}) (st
|
|||||||
return output, nil
|
return output, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *ExecTool) executeInSandbox(ctx context.Context, command, cwd string) (string, error) {
|
||||||
|
// 实现 Docker 沙箱执行逻辑
|
||||||
|
absCwd, _ := filepath.Abs(cwd)
|
||||||
|
dockerArgs := []string{
|
||||||
|
"run", "--rm",
|
||||||
|
"-v", fmt.Sprintf("%s:/app", absCwd),
|
||||||
|
"-w", "/app",
|
||||||
|
t.sandboxImage,
|
||||||
|
"sh", "-c", command,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdCtx, cancel := context.WithTimeout(ctx, t.timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(cmdCtx, "docker", dockerArgs...)
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
cmd.Stdout = &stdout
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
|
||||||
|
err := cmd.Run()
|
||||||
|
output := stdout.String()
|
||||||
|
if stderr.Len() > 0 {
|
||||||
|
output += "\nSTDERR:\n" + stderr.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
output += fmt.Sprintf("\nSandbox Exit code: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (t *ExecTool) guardCommand(command, cwd string) string {
|
func (t *ExecTool) guardCommand(command, cwd string) string {
|
||||||
cmd := strings.TrimSpace(command)
|
cmd := strings.TrimSpace(command)
|
||||||
lower := strings.ToLower(cmd)
|
lower := strings.ToLower(cmd)
|
||||||
|
|||||||
Reference in New Issue
Block a user