@@ -68,6 +68,95 @@ func TestEvaluateFiles_Bypassed(t *testing.T) {
6868 }
6969}
7070
71+ func TestEvaluateScope_InScope (t * testing.T ) {
72+ engine := NewEngine (config.PolicyConfig {
73+ AllowedDirectories : []string {"/home/user/project" },
74+ })
75+
76+ tests := []struct {
77+ name string
78+ paths []string
79+ allowed bool
80+ }{
81+ {"file in project" , []string {"/home/user/project/main.go" }, true },
82+ {"nested file in project" , []string {"/home/user/project/src/app.go" }, true },
83+ {"file outside project" , []string {"/etc/passwd" }, false },
84+ {"home dir file" , []string {"/home/user/.ssh/id_rsa" }, false },
85+ {"sibling project" , []string {"/home/user/other-project/main.go" }, false },
86+ {"parent traversal" , []string {"/home/user/project/../.ssh/id_rsa" }, false },
87+ {"mix in and out of scope" , []string {"/home/user/project/main.go" , "/etc/passwd" }, false },
88+ {"empty list" , []string {}, true },
89+ {"exact dir match" , []string {"/home/user/project" }, true },
90+ }
91+
92+ for _ , tt := range tests {
93+ t .Run (tt .name , func (t * testing.T ) {
94+ decision := engine .EvaluateScope (tt .paths )
95+ if decision .Allowed != tt .allowed {
96+ t .Errorf ("EvaluateScope(%v) = allowed:%v, want allowed:%v (reason: %s)" ,
97+ tt .paths , decision .Allowed , tt .allowed , decision .Reason )
98+ }
99+ })
100+ }
101+ }
102+
103+ func TestEvaluateScope_MultipleAllowedDirs (t * testing.T ) {
104+ engine := NewEngine (config.PolicyConfig {
105+ AllowedDirectories : []string {"/home/user/project-a" , "/home/user/project-b" },
106+ })
107+
108+ tests := []struct {
109+ name string
110+ paths []string
111+ allowed bool
112+ }{
113+ {"file in project-a" , []string {"/home/user/project-a/main.go" }, true },
114+ {"file in project-b" , []string {"/home/user/project-b/main.go" }, true },
115+ {"file in neither" , []string {"/home/user/project-c/main.go" }, false },
116+ }
117+
118+ for _ , tt := range tests {
119+ t .Run (tt .name , func (t * testing.T ) {
120+ decision := engine .EvaluateScope (tt .paths )
121+ if decision .Allowed != tt .allowed {
122+ t .Errorf ("EvaluateScope(%v) = allowed:%v, want allowed:%v (reason: %s)" ,
123+ tt .paths , decision .Allowed , tt .allowed , decision .Reason )
124+ }
125+ })
126+ }
127+ }
128+
129+ func TestEvaluateScope_NoDirectoriesConfigured (t * testing.T ) {
130+ engine := NewEngine (config.PolicyConfig {})
131+ decision := engine .EvaluateScope ([]string {"/anywhere/file.go" })
132+ if ! decision .Allowed {
133+ t .Error ("expected allowed when no directories configured" )
134+ }
135+ }
136+
137+ func TestEvaluateScope_Bypassed (t * testing.T ) {
138+ engine := NewEngine (config.PolicyConfig {
139+ AllowedDirectories : []string {"/home/user/project" },
140+ })
141+ engine .SetBypassed (true )
142+ decision := engine .EvaluateScope ([]string {"/etc/passwd" })
143+ if ! decision .Allowed {
144+ t .Error ("expected allowed when policy bypassed" )
145+ }
146+ }
147+
148+ func TestEvaluateScope_ParentTraversal (t * testing.T ) {
149+ engine := NewEngine (config.PolicyConfig {
150+ AllowedDirectories : []string {"/home/user/project" },
151+ })
152+
153+ // ../.. traversal should be caught after path cleaning
154+ decision := engine .EvaluateScope ([]string {"/home/user/project/../../etc/passwd" })
155+ if decision .Allowed {
156+ t .Error ("expected blocked for parent traversal escaping allowed directory" )
157+ }
158+ }
159+
71160func TestMatchFilePattern (t * testing.T ) {
72161 tests := []struct {
73162 path string
0 commit comments