1package tools
2
3import (
4 "errors"
5 "path/filepath"
6 "strings"
7 "testing"
8
9 "github.com/patriceckhart/zot/packages/core"
10)
11
12func TestPermissionSetEmptyScopesDenyAccess(t *testing.T) {
13 s := NewSandbox(t.TempDir())
14 s.SetPermissions(&PermissionSet{})
15 if err := s.CheckReadPath(filepath.Join(s.Root, "a")); err == nil {
16 t.Fatal("empty read scope allowed access")
17 }
18 if err := s.CheckWritePath(filepath.Join(s.Root, "a")); err == nil {
19 t.Fatal("empty write scope allowed access")
20 }
21}
22
23func TestPermissionSetScopedAccess(t *testing.T) {
24 root := t.TempDir()
25 var p PermissionSet
26 p.FS.Read = []string{"${workspace}/src"}
27 p.FS.Write = []string{"${agent_data}"}
28 expanded := p.Expand(root, filepath.Join(root, "data"))
29 s := NewSandbox(root)
30 s.SetPermissions(&expanded)
31 if err := s.CheckReadPath(filepath.Join(root, "src", "a.go")); err != nil {
32 t.Fatalf("read inside scope: %v", err)
33 }
34 if err := s.CheckReadPath(filepath.Join(root, "secret")); err == nil {
35 t.Fatal("read outside scope was allowed")
36 }
37 if err := s.CheckWritePath(filepath.Join(root, "data", "state.json")); err != nil {
38 t.Fatalf("write inside scope: %v", err)
39 }
40}
41
42func TestPermissionExpandDoesNotMutateManifest(t *testing.T) {
43 var p PermissionSet
44 p.FS.Read = []string{"${workspace}"}
45 _ = p.Expand("/first", "/data")
46 if got := p.FS.Read[0]; got != "${workspace}" {
47 t.Fatalf("manifest mutated to %q", got)
48 }
49}
50
51func TestBashAllowlistRejectsShellEscapes(t *testing.T) {
52 var p PermissionSet
53 p.Bash.Mode = "allowlist"
54 p.Bash.Allow = []string{"echo"}
55 s := NewSandbox(t.TempDir())
56 s.SetPermissions(&p)
57 for _, cmd := range []string{"echo $(uname)", "echo hi > /tmp/out", "echo `uname`", "echo ok\nrm -rf /"} {
58 if err := s.CheckBashPermission(cmd); err == nil || !strings.Contains(err.Error(), "does not permit") {
59 t.Errorf("command %q was not rejected: %v", cmd, err)
60 }
61 }
62 if err := s.CheckBashPermission("echo ok"); err != nil {
63 t.Fatalf("allowed command rejected: %v", err)
64 }
65 if err := s.CheckBashPermission("rm file"); err == nil {
66 t.Fatal("unlisted command was allowed")
67 }
68}
69
70func TestBashModeNoneDenies(t *testing.T) {
71 var p PermissionSet
72 p.Bash.Mode = "none"
73 s := NewSandbox(t.TempDir())
74 s.SetPermissions(&p)
75 if err := s.CheckBashPermission("echo ok"); err == nil {
76 t.Fatal("none mode allowed bash")
77 }
78}
79
80func TestBashModeAskAllows(t *testing.T) {
81 var p PermissionSet
82 p.Bash.Mode = "ask"
83 s := NewSandbox(t.TempDir())
84 s.SetPermissions(&p)
85 if err := s.CheckBashPermission("rm -rf /tmp/x"); err != nil {
86 t.Fatalf("ask mode rejected command: %v", err)
87 }
88}
89
90func TestPolicyRefusalsCarryTypedOutcomes(t *testing.T) {
91 s := NewSandbox(t.TempDir())
92 s.SetPermissions(&PermissionSet{})
93 for _, err := range []error{
94 s.CheckReadPath(filepath.Join(s.Root, "file")),
95 s.CheckWritePath(filepath.Join(s.Root, "file")),
96 s.CheckBashPermission("echo ok"),
97 (&PowerShellTool{Sandbox: s}).checkPermission("Write-Output ok"),
98 } {
99 var policy *core.ToolPolicyError
100 if !errors.As(err, &policy) {
101 t.Fatalf("untyped policy refusal: %v", err)
102 }
103 }
104 s.Lock()
105 for _, err := range []error{s.CheckPath(t.TempDir()), s.CheckCommand("sudo true"), (&PowerShellTool{Sandbox: s}).checkPermission("")} {
106 var policy *core.ToolPolicyError
107 if !errors.As(err, &policy) {
108 t.Fatalf("untyped jail refusal: %v", err)
109 }
110 }
111}