fix: allow http urls in exec workspace guard

This commit is contained in:
Lemonawa 2026-03-06 13:38:32 +08:00
parent 46201fb679
commit 456a302b36
No known key found for this signature in database
GPG key ID: 76DCD7FE33DDFD2C
2 changed files with 37 additions and 2 deletions

View file

@ -79,6 +79,10 @@ var (
// absolutePathPattern matches absolute file paths in commands (Unix and Windows). // absolutePathPattern matches absolute file paths in commands (Unix and Windows).
absolutePathPattern = regexp.MustCompile(`[A-Za-z]:\\[^\\\"']+|/[^\s\"']+`) absolutePathPattern = regexp.MustCompile(`[A-Za-z]:\\[^\\\"']+|/[^\s\"']+`)
// httpURLPattern matches HTTP(S) URLs so they can be excluded from path-based
// workspace checks. URLs are command arguments, not local filesystem paths.
httpURLPattern = regexp.MustCompile(`(?i)\bhttps?://[^\s"'<>]+`)
// safePaths are kernel pseudo-devices that are always safe to reference in // safePaths are kernel pseudo-devices that are always safe to reference in
// commands, regardless of workspace restriction. They contain no user data // commands, regardless of workspace restriction. They contain no user data
// and cannot cause destructive writes. // and cannot cause destructive writes.
@ -327,7 +331,9 @@ func (t *ExecTool) guardCommand(command, cwd string) string {
} }
if t.restrictToWorkspace { if t.restrictToWorkspace {
if strings.Contains(cmd, "..\\") || strings.Contains(cmd, "../") { sanitizedCmd := stripHTTPURLs(cmd)
if strings.Contains(sanitizedCmd, "..\\") || strings.Contains(sanitizedCmd, "../") {
return "Command blocked by safety guard (path traversal detected)" return "Command blocked by safety guard (path traversal detected)"
} }
@ -336,7 +342,7 @@ func (t *ExecTool) guardCommand(command, cwd string) string {
return "" return ""
} }
matches := absolutePathPattern.FindAllString(cmd, -1) matches := absolutePathPattern.FindAllString(sanitizedCmd, -1)
for _, raw := range matches { for _, raw := range matches {
p, err := filepath.Abs(raw) p, err := filepath.Abs(raw)
@ -362,6 +368,12 @@ func (t *ExecTool) guardCommand(command, cwd string) string {
return "" return ""
} }
func stripHTTPURLs(command string) string {
return httpURLPattern.ReplaceAllStringFunc(command, func(match string) string {
return strings.Repeat(" ", len(match))
})
}
func (t *ExecTool) SetTimeout(timeout time.Duration) { func (t *ExecTool) SetTimeout(timeout time.Duration) {
t.timeout = timeout t.timeout = timeout
} }

View file

@ -312,6 +312,29 @@ func TestShellTool_RestrictToWorkspace(t *testing.T) {
} }
} }
// TestShellTool_RestrictToWorkspace_AllowsHTTPURLs verifies that HTTP(S) URLs
// are not mistaken for absolute filesystem paths when workspace restriction is active.
func TestShellTool_RestrictToWorkspace_AllowsHTTPURLs(t *testing.T) {
tmpDir := t.TempDir()
tool, err := NewExecTool(tmpDir, true)
if err != nil {
t.Fatalf("unable to configure exec tool: %s", err)
}
commands := []string{
`echo "http://example.com"`,
`echo "https://test"`,
`echo "https://example.com/path/../still-a-url"`,
}
for _, cmd := range commands {
result := tool.Execute(context.Background(), map[string]any{"command": cmd})
if result.IsError {
t.Fatalf("expected HTTP(S) URL to be allowed, command=%q error=%s", cmd, result.ForLLM)
}
}
}
// TestShellTool_DevNullAllowed verifies that /dev/null redirections are not blocked (issue #964). // TestShellTool_DevNullAllowed verifies that /dev/null redirections are not blocked (issue #964).
func TestShellTool_DevNullAllowed(t *testing.T) { func TestShellTool_DevNullAllowed(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()