fix(exec): keep deny patterns active for custom allow rules
This commit is contained in:
parent
a75b3d15bb
commit
9721c36e55
3 changed files with 56 additions and 22 deletions
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"maps"
|
"maps"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
|
||||||
|
|
@ -1135,36 +1135,35 @@ func expandPowerShellEnvVars(cmd string) string {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *ExecTool) commandMatchesAllowPattern(lower string) bool {
|
||||||
|
for _, pattern := range t.allowPatterns {
|
||||||
|
if pattern.MatchString(lower) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, pattern := range t.customAllowPatterns {
|
||||||
|
if pattern.MatchString(lower) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (t *ExecTool) guardCommand(command, cwd string) string {
|
func (t *ExecTool) guardCommand(command, cwd string) string {
|
||||||
cmd := strings.TrimSpace(command)
|
cmd := strings.TrimSpace(command)
|
||||||
lower := strings.ToLower(cmd)
|
lower := strings.ToLower(cmd)
|
||||||
|
|
||||||
// Custom allow patterns exempt a command from deny checks.
|
// Deny patterns always apply, even when a command matches a custom allow rule.
|
||||||
explicitlyAllowed := false
|
// Custom allow rules can permit a command, but must not disable secret-safety
|
||||||
for _, pattern := range t.customAllowPatterns {
|
// deny rules such as jq env access checks (#3079).
|
||||||
|
for _, pattern := range t.denyPatterns {
|
||||||
if pattern.MatchString(lower) {
|
if pattern.MatchString(lower) {
|
||||||
explicitlyAllowed = true
|
return "Command blocked by safety guard (dangerous pattern detected)"
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !explicitlyAllowed {
|
if len(t.allowPatterns) > 0 || len(t.customAllowPatterns) > 0 {
|
||||||
for _, pattern := range t.denyPatterns {
|
if !t.commandMatchesAllowPattern(lower) {
|
||||||
if pattern.MatchString(lower) {
|
|
||||||
return "Command blocked by safety guard (dangerous pattern detected)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(t.allowPatterns) > 0 {
|
|
||||||
allowed := false
|
|
||||||
for _, pattern := range t.allowPatterns {
|
|
||||||
if pattern.MatchString(lower) {
|
|
||||||
allowed = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !allowed {
|
|
||||||
return "Command blocked by safety guard (not in allowlist)"
|
return "Command blocked by safety guard (not in allowlist)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1936,3 +1936,37 @@ func TestShellTool_SchemelessURLDetection(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShellTool_CustomAllowDoesNotBypassDenyPatterns(t *testing.T) {
|
||||||
|
cfg := &config.Config{}
|
||||||
|
cfg.Tools.Exec.EnableDenyPatterns = true
|
||||||
|
cfg.Tools.Exec.CustomAllowPatterns = []string{`^jq\b`}
|
||||||
|
cfg.Tools.Exec.CustomDenyPatterns = []string{`\$env\b`, `(^|[^.$a-z0-9_])env([^a-z0-9_]|$)`}
|
||||||
|
|
||||||
|
tool, err := NewExecToolWithConfig(t.TempDir(), false, cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewExecToolWithConfig() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := tool.guardCommand(`jq -n '$ENV.PICOCLAW_VARIANT_CANARY'`, t.TempDir())
|
||||||
|
if !strings.Contains(got, "dangerous pattern detected") {
|
||||||
|
t.Fatalf("custom allow should not bypass deny patterns, got: %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShellTool_CustomAllowStillPermitsSafeMatch(t *testing.T) {
|
||||||
|
cfg := &config.Config{}
|
||||||
|
cfg.Tools.Exec.EnableDenyPatterns = true
|
||||||
|
cfg.Tools.Exec.CustomAllowPatterns = []string{`^jq\b`}
|
||||||
|
cfg.Tools.Exec.CustomDenyPatterns = []string{`\$env\b`, `(^|[^.$a-z0-9_])env([^a-z0-9_]|$)`}
|
||||||
|
|
||||||
|
tool, err := NewExecToolWithConfig(t.TempDir(), false, cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewExecToolWithConfig() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := tool.guardCommand(`jq -n '"ok"'`, t.TempDir())
|
||||||
|
if got != "" {
|
||||||
|
t.Fatalf("safe custom-allowed command should pass guard, got: %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue