fix: narrow quoted http url masking

This commit is contained in:
Lemonawa 2026-03-06 14:19:29 +08:00
parent dc9da009b6
commit 89e0b1a26b
No known key found for this signature in database
GPG key ID: 76DCD7FE33DDFD2C
2 changed files with 47 additions and 14 deletions

View file

@ -419,21 +419,14 @@ func hasHTTPURLPrefix(command string, start int) bool {
} }
func findHTTPURLEnd(command string, start int, quote byte) int { func findHTTPURLEnd(command string, start int, quote byte) int {
if quote != 0 {
for i := start; i < len(command); i++ { for i := start; i < len(command); i++ {
if quote == '"' && command[i] == '\\' && i+1 < len(command) { if quote != 0 && (command[i] == quote || command[i] == '\'' || command[i] == '"') {
i++
continue
}
if command[i] == quote {
return i return i
} }
if quote == 0 && (strings.ContainsRune(" \t\r\n", rune(command[i])) || isShellURLDelimiter(command[i])) {
return i
} }
return len(command) if !isHTTPURLChar(command[i]) {
}
for i := start; i < len(command); i++ {
if strings.ContainsRune(" \t\r\n", rune(command[i])) || isShellURLDelimiter(command[i]) {
return i return i
} }
} }
@ -441,6 +434,24 @@ func findHTTPURLEnd(command string, start int, quote byte) int {
return len(command) return len(command)
} }
func isHTTPURLChar(ch byte) bool {
switch {
case ch >= 'a' && ch <= 'z':
return true
case ch >= 'A' && ch <= 'Z':
return true
case ch >= '0' && ch <= '9':
return true
}
switch ch {
case ':', '/', '?', '#', '[', ']', '@', '!', '$', '&', '*', '+', ',', ';', '=', '%', '-', '.', '_', '~':
return true
default:
return false
}
}
func isShellURLDelimiter(ch byte) bool { func isShellURLDelimiter(ch byte) bool {
switch ch { switch ch {
case ';', '|', '&', '<', '>', '(', ')', '`': case ';', '|', '&', '<', '>', '(', ')', '`':

View file

@ -359,6 +359,28 @@ func TestShellTool_RestrictToWorkspace_URLDoesNotBypassPathChecks(t *testing.T)
} }
} }
// TestShellTool_RestrictToWorkspace_URLDoesNotMaskQuotedPaths verifies that a
// URL inside a quoted argument does not hide a later absolute path in the same argument.
func TestShellTool_RestrictToWorkspace_URLDoesNotMaskQuotedPaths(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{
`python3 -c "print('https://x,' + open('/etc/passwd').read())"`,
`python3 -c "print('https://x,'+open('/etc/passwd').read())"`,
}
for _, cmd := range commands {
result := tool.Execute(context.Background(), map[string]any{"command": cmd})
if !result.IsError || !strings.Contains(result.ForLLM, "blocked") {
t.Fatalf("expected quoted path access to stay blocked, command=%q output=%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()