From 8a8554c18928097ed22710ff9d1ff5fc0158026e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=8E=E9=BE=99=200668001470?=
Date: Mon, 16 Mar 2026 08:41:20 +0800
Subject: [PATCH] fix(exec): ignore web URL path segments in workspace guard
---
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 576360184..c6bc8f7e8 100644
--- a/pkg/tools/shell.go
+++ b/pkg/tools/shell.go
@@ -376,9 +376,39 @@ func (t *ExecTool) guardCommand(command, cwd string) string {
return ""
}
- matches := findAbsolutePathMatches(cmd)
+ // Web URL schemes whose path components (starting with //) should be exempt
+ // from workspace sandbox checks. file: is intentionally excluded so that
+ // file:// URIs are still validated against the workspace boundary.
+ webSchemes := []string{"http:", "https:", "ftp:", "ftps:", "sftp:", "ssh:", "git:"}
+
+ matchIndices := absolutePathPattern.FindAllStringIndex(cmd, -1)
+
+ for _, loc := range matchIndices {
+ if !isPathBoundary(cmd, loc[0]) {
+ continue
+ }
+
+ raw := cmd[loc[0]:loc[1]]
+
+ // Skip URL path components that look like they're from web URLs.
+ // When a URL like "https://github.com" is parsed, the regex captures
+ // "//github.com" as a match (the path portion after "https:").
+ if strings.HasPrefix(raw, "//") && loc[0] > 0 {
+ before := cmd[:loc[0]]
+ isWebURL := false
+
+ for _, scheme := range webSchemes {
+ if strings.HasSuffix(before, scheme) {
+ isWebURL = true
+ break
+ }
+ }
+
+ if isWebURL {
+ continue
+ }
+ }
- for _, raw := range matches {
p, err := filepath.Abs(raw)
if err != nil {
continue
@@ -402,24 +432,6 @@ func (t *ExecTool) guardCommand(command, cwd string) string {
return ""
}
-func findAbsolutePathMatches(command string) []string {
- indexes := absolutePathPattern.FindAllStringIndex(command, -1)
- if len(indexes) == 0 {
- return nil
- }
-
- matches := make([]string, 0, len(indexes))
- for _, idx := range indexes {
- start := idx[0]
- if !isPathBoundary(command, start) {
- continue
- }
- matches = append(matches, command[start:idx[1]])
- }
-
- return matches
-}
-
func isPathBoundary(command string, start int) bool {
if start <= 0 {
return true