From 6fcdf5ca12261591d42d60cabf5ad921410d19ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Renzel?= Date: Wed, 25 Feb 2026 17:15:24 +0100 Subject: [PATCH 1/4] Fix: calling URLs with curl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Calling URLs via curl results in the error: “Command blocked by safety guard (path outside working dir)”. This commit fixes the problem. --- pkg/tools/shell.go | 52 ++++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/pkg/tools/shell.go b/pkg/tools/shell.go index ad1664b5b..dc6cca3fa 100644 --- a/pkg/tools/shell.go +++ b/pkg/tools/shell.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "net/url" "os" "os/exec" "path/filepath" @@ -279,31 +280,42 @@ func (t *ExecTool) guardCommand(command, cwd string) string { } if t.restrictToWorkspace { - if strings.Contains(cmd, "..\\") || strings.Contains(cmd, "../") { - return "Command blocked by safety guard (path traversal detected)" - } - - cwdPath, err := filepath.Abs(cwd) - if err != nil { - return "" - } - - pathPattern := regexp.MustCompile(`[A-Za-z]:\\[^\\\"']+|/[^\s\"']+`) - matches := pathPattern.FindAllString(cmd, -1) - - for _, raw := range matches { - p, err := filepath.Abs(raw) - if err != nil { + for _, part := range strings.Split(cmd, " ") { + if part == "" { continue } - rel, err := filepath.Rel(cwdPath, p) - if err != nil { + _, err := url.ParseRequestURI(strings.ReplaceAll(part, "\"", "")) + if err == nil { continue } - - if strings.HasPrefix(rel, "..") { - return "Command blocked by safety guard (path outside working dir)" + + if strings.Contains(part, "..\\") || strings.Contains(part, "../") { + return "Command blocked by safety guard (path traversal detected)" + } + + cwdPath, err := filepath.Abs(cwd) + if err != nil { + return "" + } + + pathPattern := regexp.MustCompile(`[A-Za-z]:\\[^\\\"']+|/[^\s\"']+`) + matches := pathPattern.FindAllString(part, -1) + + for _, raw := range matches { + p, err := filepath.Abs(raw) + if err != nil { + continue + } + + rel, err := filepath.Rel(cwdPath, p) + if err != nil { + continue + } + + if strings.HasPrefix(rel, "..") { + return "Command blocked by safety guard (path outside working dir)" + } } } } From 247c965d46a1a108332e736c6916e5e5a28bb2d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Renzel?= Date: Thu, 26 Feb 2026 16:59:14 +0100 Subject: [PATCH 2/4] Fix pr review feedback fix Incorrect Command Line Parsing fix URL Detection Logic Has a Security Vulnerability fix Incomplete Quote Handling --- pkg/tools/shell.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/tools/shell.go b/pkg/tools/shell.go index dc6cca3fa..9287d10e7 100644 --- a/pkg/tools/shell.go +++ b/pkg/tools/shell.go @@ -12,6 +12,7 @@ import ( "regexp" "runtime" "strings" + "strconv" "time" "github.com/sipeed/picoclaw/pkg/config" @@ -280,13 +281,17 @@ func (t *ExecTool) guardCommand(command, cwd string) string { } if t.restrictToWorkspace { - for _, part := range strings.Split(cmd, " ") { - if part == "" { - continue + splitPattern := regexp.MustCompile(`("[^"]*"|'[^']*'|[\S]+)+`) + parts := splitPattern.FindAllString(cmd, -1) + + for _, part := range parts { + unquoted, err := strconv.Unquote(part) + if err != nil { + unquoted = part } - _, err := url.ParseRequestURI(strings.ReplaceAll(part, "\"", "")) - if err == nil { + u, err := url.ParseRequestURI(unquoted) + if err == nil && u.Scheme != "" && u.Host != "" { continue } From 4f1ec1ea4c75ccecde4c5a13cbd73bc98440a9de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Renzel?= Date: Thu, 26 Feb 2026 17:29:23 +0100 Subject: [PATCH 3/4] improve performance --- pkg/tools/shell.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/tools/shell.go b/pkg/tools/shell.go index 9287d10e7..edafbc856 100644 --- a/pkg/tools/shell.go +++ b/pkg/tools/shell.go @@ -281,7 +281,9 @@ func (t *ExecTool) guardCommand(command, cwd string) string { } if t.restrictToWorkspace { + pathPattern := regexp.MustCompile(`[A-Za-z]:\\[^\\\"']+|/[^\s\"']+`) splitPattern := regexp.MustCompile(`("[^"]*"|'[^']*'|[\S]+)+`) + parts := splitPattern.FindAllString(cmd, -1) for _, part := range parts { @@ -304,7 +306,6 @@ func (t *ExecTool) guardCommand(command, cwd string) string { return "" } - pathPattern := regexp.MustCompile(`[A-Za-z]:\\[^\\\"']+|/[^\s\"']+`) matches := pathPattern.FindAllString(part, -1) for _, raw := range matches { From 24dd48b437011101e4769212bca27c2427cb2203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Renzel?= Date: Thu, 26 Feb 2026 18:12:00 +0100 Subject: [PATCH 4/4] perfomance improvement --- pkg/tools/shell.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/tools/shell.go b/pkg/tools/shell.go index edafbc856..5fc63116e 100644 --- a/pkg/tools/shell.go +++ b/pkg/tools/shell.go @@ -284,8 +284,12 @@ func (t *ExecTool) guardCommand(command, cwd string) string { pathPattern := regexp.MustCompile(`[A-Za-z]:\\[^\\\"']+|/[^\s\"']+`) splitPattern := regexp.MustCompile(`("[^"]*"|'[^']*'|[\S]+)+`) - parts := splitPattern.FindAllString(cmd, -1) + cwdPath, err := filepath.Abs(cwd) + if err != nil { + cwdPath = "" + } + parts := splitPattern.FindAllString(cmd, -1) for _, part := range parts { unquoted, err := strconv.Unquote(part) if err != nil { @@ -301,13 +305,11 @@ func (t *ExecTool) guardCommand(command, cwd string) string { return "Command blocked by safety guard (path traversal detected)" } - cwdPath, err := filepath.Abs(cwd) - if err != nil { - return "" + if cwdPath == "" { + continue } matches := pathPattern.FindAllString(part, -1) - for _, raw := range matches { p, err := filepath.Abs(raw) if err != nil {