mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-28 04:57:29 +08:00
fix safety
This commit is contained in:
@@ -1,119 +1,36 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
)
|
||||
|
||||
func TestApplyRiskGate_DryRunCanBeBypassedWithForce(t *testing.T) {
|
||||
tool := &ExecTool{riskCfg: config.RiskConfig{
|
||||
Enabled: true,
|
||||
AllowDestructive: true,
|
||||
RequireDryRun: true,
|
||||
RequireForceFlag: false,
|
||||
}}
|
||||
|
||||
msg, dryRun := tool.applyRiskGate("git clean -fd", true)
|
||||
if msg != "" || dryRun != "" {
|
||||
t.Fatalf("expected force=true to allow execution after dry-run step, got msg=%q dryRun=%q", msg, dryRun)
|
||||
func TestExecToolExecuteBasicCommand(t *testing.T) {
|
||||
tool := NewExecTool(config.ShellConfig{Timeout: 2 * time.Second}, ".")
|
||||
out, err := tool.Execute(context.Background(), map[string]interface{}{
|
||||
"command": "echo hello",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("execute failed: %v", err)
|
||||
}
|
||||
if !strings.Contains(out, "hello") {
|
||||
t.Fatalf("expected output to contain hello, got %q", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyRiskGate_RequiresDryRunWithoutForce(t *testing.T) {
|
||||
tool := &ExecTool{riskCfg: config.RiskConfig{
|
||||
Enabled: true,
|
||||
AllowDestructive: true,
|
||||
RequireDryRun: true,
|
||||
RequireForceFlag: false,
|
||||
}}
|
||||
|
||||
msg, dryRun := tool.applyRiskGate("git clean -fd", false)
|
||||
if msg == "" {
|
||||
t.Fatal("expected dry-run block message")
|
||||
func TestExecToolExecuteTimeout(t *testing.T) {
|
||||
tool := NewExecTool(config.ShellConfig{Timeout: 20 * time.Millisecond}, ".")
|
||||
out, err := tool.Execute(context.Background(), map[string]interface{}{
|
||||
"command": "sleep 1",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("execute failed: %v", err)
|
||||
}
|
||||
if dryRun == "" {
|
||||
t.Fatal("expected dry-run command")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssessCommandRisk_GitCleanIsDestructive(t *testing.T) {
|
||||
assessment := assessCommandRisk("git clean -fd")
|
||||
if assessment.Level != RiskDestructive {
|
||||
t.Fatalf("expected git clean to be destructive, got %s", assessment.Level)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewExecTool_LoadsAllowedCmdsIntoAllowPatterns(t *testing.T) {
|
||||
tool := NewExecTool(config.ShellConfig{AllowedCmds: []string{"echo"}}, ".")
|
||||
if len(tool.allowPatterns) != 1 {
|
||||
t.Fatalf("expected one allow pattern, got %d", len(tool.allowPatterns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuardCommand_BlocksCommandNotInAllowlist(t *testing.T) {
|
||||
tool := NewExecTool(config.ShellConfig{AllowedCmds: []string{"echo"}}, ".")
|
||||
if msg := tool.guardCommand("ls -la", "."); msg == "" {
|
||||
t.Fatal("expected allowlist to block command not in allowed_cmds")
|
||||
}
|
||||
|
||||
if msg := tool.guardCommand("echo hi", "."); msg != "" {
|
||||
t.Fatalf("expected allowed command to pass guard, got %q", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuardCommand_AllowlistIsCaseInsensitive(t *testing.T) {
|
||||
tool := NewExecTool(config.ShellConfig{AllowedCmds: []string{"ECHO"}}, ".")
|
||||
if msg := tool.guardCommand("echo hi", "."); msg != "" {
|
||||
t.Fatalf("expected case-insensitive allowlist match, got %q", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuardCommand_DenylistIsCaseInsensitive(t *testing.T) {
|
||||
tool := NewExecTool(config.ShellConfig{DeniedCmds: []string{"RM"}}, ".")
|
||||
if msg := tool.guardCommand("rm -f tmp.txt", "."); msg == "" {
|
||||
t.Fatal("expected case-insensitive denylist match to block command")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyRiskGate_RequireDryRunWithoutStrategyStillBlocks(t *testing.T) {
|
||||
tool := &ExecTool{riskCfg: config.RiskConfig{
|
||||
Enabled: true,
|
||||
AllowDestructive: true,
|
||||
RequireDryRun: true,
|
||||
RequireForceFlag: false,
|
||||
}}
|
||||
|
||||
msg, dryRun := tool.applyRiskGate("rm -rf tmp", false)
|
||||
if msg == "" {
|
||||
t.Fatal("expected destructive command without dry-run strategy to be blocked")
|
||||
}
|
||||
if dryRun != "" {
|
||||
t.Fatalf("expected no dry-run command for rm -rf, got %q", dryRun)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetAllowPatterns_IsCaseInsensitive(t *testing.T) {
|
||||
tool := &ExecTool{}
|
||||
if err := tool.SetAllowPatterns([]string{`^ECHO\b`}); err != nil {
|
||||
t.Fatalf("SetAllowPatterns returned error: %v", err)
|
||||
}
|
||||
|
||||
if msg := tool.guardCommand("echo hi", "."); msg != "" {
|
||||
t.Fatalf("expected case-insensitive allow pattern to match, got %q", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuardCommand_BlocksRootWipeVariants(t *testing.T) {
|
||||
tool := &ExecTool{}
|
||||
cases := []string{
|
||||
"rm -rf /",
|
||||
"rm -fr /",
|
||||
"rm --no-preserve-root -rf /",
|
||||
}
|
||||
for _, c := range cases {
|
||||
if msg := tool.guardCommand(c, "."); msg == "" {
|
||||
t.Fatalf("expected root wipe variant to be blocked: %s", c)
|
||||
}
|
||||
if !strings.Contains(out, "timed out") {
|
||||
t.Fatalf("expected timeout message, got %q", out)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user