mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
fix(security): harden unauthenticated tool-exec paths (#1360)
* fix(security): harden unauthenticated tool-exec paths (GHSA-pv8c-p6jf-3fpp) - Exec tool: channel-based access control (default deny remote) - Cron tool: command scheduling restricted to internal channels - Web fetch: SSRF defense-in-depth (pre-flight + dial-time + redirect checks) - File permissions: session/state dirs 0700, files 0600 - Registry: inject __channel/__chat_id into tool args (replaces racy SetContext) 28 new security regression tests. (cherry picked from commit 191446ae19021604d3d5b0d9376b9655ab749105) * fix(exec): revalidate working_dir before command start * test(web): allow local oversized payload fixture --------- Co-authored-by: xj <gh-xj@users.noreply.github.com>
This commit is contained in:
@@ -301,6 +301,85 @@ func TestShellTool_WorkingDir_SymlinkEscape(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestShellTool_RemoteChannelBlockedByDefault verifies exec is blocked for remote channels
|
||||
func TestShellTool_RemoteChannelBlockedByDefault(t *testing.T) {
|
||||
cfg := &config.Config{}
|
||||
cfg.Tools.Exec.EnableDenyPatterns = true
|
||||
cfg.Tools.Exec.AllowRemote = false
|
||||
|
||||
tool, err := NewExecToolWithConfig("", false, cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("NewExecToolWithConfig() error: %v", err)
|
||||
}
|
||||
ctx := WithToolContext(context.Background(), "telegram", "chat-1")
|
||||
result := tool.Execute(ctx, map[string]any{"command": "echo hi"})
|
||||
|
||||
if !result.IsError {
|
||||
t.Fatal("expected remote-channel exec to be blocked")
|
||||
}
|
||||
if !strings.Contains(result.ForLLM, "restricted to internal channels") {
|
||||
t.Errorf("expected 'restricted to internal channels' message, got: %s", result.ForLLM)
|
||||
}
|
||||
}
|
||||
|
||||
// TestShellTool_InternalChannelAllowed verifies exec is allowed for internal channels
|
||||
func TestShellTool_InternalChannelAllowed(t *testing.T) {
|
||||
cfg := &config.Config{}
|
||||
cfg.Tools.Exec.EnableDenyPatterns = true
|
||||
cfg.Tools.Exec.AllowRemote = false
|
||||
|
||||
tool, err := NewExecToolWithConfig("", false, cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("NewExecToolWithConfig() error: %v", err)
|
||||
}
|
||||
ctx := WithToolContext(context.Background(), "cli", "direct")
|
||||
result := tool.Execute(ctx, map[string]any{"command": "echo hi"})
|
||||
|
||||
if result.IsError {
|
||||
t.Fatalf("expected internal channel exec to succeed, got: %s", result.ForLLM)
|
||||
}
|
||||
if !strings.Contains(result.ForLLM, "hi") {
|
||||
t.Errorf("expected output to contain 'hi', got: %s", result.ForLLM)
|
||||
}
|
||||
}
|
||||
|
||||
// TestShellTool_EmptyChannelBlockedWhenNotAllowRemote verifies fail-closed when no channel context
|
||||
func TestShellTool_EmptyChannelBlockedWhenNotAllowRemote(t *testing.T) {
|
||||
cfg := &config.Config{}
|
||||
cfg.Tools.Exec.EnableDenyPatterns = true
|
||||
cfg.Tools.Exec.AllowRemote = false
|
||||
|
||||
tool, err := NewExecToolWithConfig("", false, cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("NewExecToolWithConfig() error: %v", err)
|
||||
}
|
||||
result := tool.Execute(context.Background(), map[string]any{
|
||||
"command": "echo hi",
|
||||
})
|
||||
|
||||
if !result.IsError {
|
||||
t.Fatal("expected exec with empty channel to be blocked when allowRemote=false")
|
||||
}
|
||||
}
|
||||
|
||||
// TestShellTool_AllowRemoteBypassesChannelCheck verifies allowRemote=true permits any channel
|
||||
func TestShellTool_AllowRemoteBypassesChannelCheck(t *testing.T) {
|
||||
cfg := &config.Config{}
|
||||
cfg.Tools.Exec.EnableDenyPatterns = true
|
||||
cfg.Tools.Exec.AllowRemote = true
|
||||
|
||||
tool, err := NewExecToolWithConfig("", false, cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("NewExecToolWithConfig() error: %v", err)
|
||||
}
|
||||
ctx := WithToolContext(context.Background(), "telegram", "chat-1")
|
||||
result := tool.Execute(ctx, map[string]any{"command": "echo hi"})
|
||||
|
||||
if result.IsError {
|
||||
t.Fatalf("expected allowRemote=true to permit remote channel, got: %s", result.ForLLM)
|
||||
}
|
||||
}
|
||||
|
||||
// TestShellTool_RestrictToWorkspace verifies workspace restriction
|
||||
func TestShellTool_RestrictToWorkspace(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
Reference in New Issue
Block a user