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