Merge branch 'main' of https://github.com/SatyamDevv/picoclaw
This commit is contained in:
@@ -4,20 +4,21 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EditFileTool edits a file by replacing old_text with new_text.
|
||||
// The old_text must exist exactly in the file.
|
||||
type EditFileTool struct {
|
||||
allowedDir string // Optional directory restriction for security
|
||||
allowedDir string
|
||||
restrict bool
|
||||
}
|
||||
|
||||
// NewEditFileTool creates a new EditFileTool with optional directory restriction.
|
||||
func NewEditFileTool(allowedDir string) *EditFileTool {
|
||||
func NewEditFileTool(allowedDir string, restrict bool) *EditFileTool {
|
||||
return &EditFileTool{
|
||||
allowedDir: allowedDir,
|
||||
restrict: restrict,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,27 +67,9 @@ func (t *EditFileTool) Execute(ctx context.Context, args map[string]interface{})
|
||||
return "", fmt.Errorf("new_text is required")
|
||||
}
|
||||
|
||||
// Resolve path and enforce directory restriction if configured
|
||||
resolvedPath := path
|
||||
if filepath.IsAbs(path) {
|
||||
resolvedPath = filepath.Clean(path)
|
||||
} else {
|
||||
abs, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to resolve path: %w", err)
|
||||
}
|
||||
resolvedPath = abs
|
||||
}
|
||||
|
||||
// Check directory restriction
|
||||
if t.allowedDir != "" {
|
||||
allowedAbs, err := filepath.Abs(t.allowedDir)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to resolve allowed directory: %w", err)
|
||||
}
|
||||
if !strings.HasPrefix(resolvedPath, allowedAbs) {
|
||||
return "", fmt.Errorf("path %s is outside allowed directory %s", path, t.allowedDir)
|
||||
}
|
||||
resolvedPath, err := validatePath(path, t.allowedDir, t.restrict)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if _, err := os.Stat(resolvedPath); os.IsNotExist(err) {
|
||||
@@ -118,10 +101,13 @@ func (t *EditFileTool) Execute(ctx context.Context, args map[string]interface{})
|
||||
return fmt.Sprintf("Successfully edited %s", path), nil
|
||||
}
|
||||
|
||||
type AppendFileTool struct{}
|
||||
type AppendFileTool struct {
|
||||
workspace string
|
||||
restrict bool
|
||||
}
|
||||
|
||||
func NewAppendFileTool() *AppendFileTool {
|
||||
return &AppendFileTool{}
|
||||
func NewAppendFileTool(workspace string, restrict bool) *AppendFileTool {
|
||||
return &AppendFileTool{workspace: workspace, restrict: restrict}
|
||||
}
|
||||
|
||||
func (t *AppendFileTool) Name() string {
|
||||
@@ -160,9 +146,12 @@ func (t *AppendFileTool) Execute(ctx context.Context, args map[string]interface{
|
||||
return "", fmt.Errorf("content is required")
|
||||
}
|
||||
|
||||
filePath := filepath.Clean(path)
|
||||
resolvedPath, err := validatePath(path, t.workspace, t.restrict)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
f, err := os.OpenFile(resolvedPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to open file: %w", err)
|
||||
}
|
||||
|
||||
@@ -5,9 +5,45 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ReadFileTool struct{}
|
||||
// validatePath ensures the given path is within the workspace if restrict is true.
|
||||
func validatePath(path, workspace string, restrict bool) (string, error) {
|
||||
if workspace == "" {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
absWorkspace, err := filepath.Abs(workspace)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to resolve workspace path: %w", err)
|
||||
}
|
||||
|
||||
var absPath string
|
||||
if filepath.IsAbs(path) {
|
||||
absPath = filepath.Clean(path)
|
||||
} else {
|
||||
absPath, err = filepath.Abs(filepath.Join(absWorkspace, path))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to resolve file path: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if restrict && !strings.HasPrefix(absPath, absWorkspace) {
|
||||
return "", fmt.Errorf("access denied: path is outside the workspace")
|
||||
}
|
||||
|
||||
return absPath, nil
|
||||
}
|
||||
|
||||
type ReadFileTool struct {
|
||||
workspace string
|
||||
restrict bool
|
||||
}
|
||||
|
||||
func NewReadFileTool(workspace string, restrict bool) *ReadFileTool {
|
||||
return &ReadFileTool{workspace: workspace, restrict: restrict}
|
||||
}
|
||||
|
||||
func (t *ReadFileTool) Name() string {
|
||||
return "read_file"
|
||||
@@ -36,7 +72,12 @@ func (t *ReadFileTool) Execute(ctx context.Context, args map[string]interface{})
|
||||
return "", fmt.Errorf("path is required")
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(path)
|
||||
resolvedPath, err := validatePath(path, t.workspace, t.restrict)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(resolvedPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read file: %w", err)
|
||||
}
|
||||
@@ -44,7 +85,14 @@ func (t *ReadFileTool) Execute(ctx context.Context, args map[string]interface{})
|
||||
return string(content), nil
|
||||
}
|
||||
|
||||
type WriteFileTool struct{}
|
||||
type WriteFileTool struct {
|
||||
workspace string
|
||||
restrict bool
|
||||
}
|
||||
|
||||
func NewWriteFileTool(workspace string, restrict bool) *WriteFileTool {
|
||||
return &WriteFileTool{workspace: workspace, restrict: restrict}
|
||||
}
|
||||
|
||||
func (t *WriteFileTool) Name() string {
|
||||
return "write_file"
|
||||
@@ -82,19 +130,31 @@ func (t *WriteFileTool) Execute(ctx context.Context, args map[string]interface{}
|
||||
return "", fmt.Errorf("content is required")
|
||||
}
|
||||
|
||||
dir := filepath.Dir(path)
|
||||
resolvedPath, err := validatePath(path, t.workspace, t.restrict)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
dir := filepath.Dir(resolvedPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return "", fmt.Errorf("failed to create directory: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
|
||||
if err := os.WriteFile(resolvedPath, []byte(content), 0644); err != nil {
|
||||
return "", fmt.Errorf("failed to write file: %w", err)
|
||||
}
|
||||
|
||||
return "File written successfully", nil
|
||||
}
|
||||
|
||||
type ListDirTool struct{}
|
||||
type ListDirTool struct {
|
||||
workspace string
|
||||
restrict bool
|
||||
}
|
||||
|
||||
func NewListDirTool(workspace string, restrict bool) *ListDirTool {
|
||||
return &ListDirTool{workspace: workspace, restrict: restrict}
|
||||
}
|
||||
|
||||
func (t *ListDirTool) Name() string {
|
||||
return "list_dir"
|
||||
@@ -123,7 +183,12 @@ func (t *ListDirTool) Execute(ctx context.Context, args map[string]interface{})
|
||||
path = "."
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(path)
|
||||
resolvedPath, err := validatePath(path, t.workspace, t.restrict)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(resolvedPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read directory: %w", err)
|
||||
}
|
||||
|
||||
@@ -22,14 +22,14 @@ type ExecTool struct {
|
||||
restrictToWorkspace bool
|
||||
}
|
||||
|
||||
func NewExecTool(workingDir string) *ExecTool {
|
||||
func NewExecTool(workingDir string, restrict bool) *ExecTool {
|
||||
denyPatterns := []*regexp.Regexp{
|
||||
regexp.MustCompile(`\brm\s+-[rf]{1,2}\b`),
|
||||
regexp.MustCompile(`\bdel\s+/[fq]\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(`>\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*:`),
|
||||
}
|
||||
@@ -39,7 +39,7 @@ func NewExecTool(workingDir string) *ExecTool {
|
||||
timeout: 60 * time.Second,
|
||||
denyPatterns: denyPatterns,
|
||||
allowPatterns: nil,
|
||||
restrictToWorkspace: false,
|
||||
restrictToWorkspace: restrict,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@ func (t *ExecTool) Execute(ctx context.Context, args map[string]interface{}) (st
|
||||
} else {
|
||||
cmd = exec.CommandContext(cmdCtx, "sh", "-c", command)
|
||||
}
|
||||
|
||||
if cwd != "" {
|
||||
cmd.Dir = cwd
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user