11package acp
22
33import (
4+ "os"
45 "path/filepath"
6+ "runtime"
57 "testing"
68
79 "github.com/stretchr/testify/assert"
@@ -17,7 +19,7 @@ func TestResolvePath(t *testing.T) {
1719 workingDir : workingDir ,
1820 }
1921
20- absWorkingDir , err := filepath .Abs (workingDir )
22+ absWorkingDir , err := filepath .EvalSymlinks (workingDir )
2123 require .NoError (t , err )
2224
2325 tests := []struct {
@@ -85,3 +87,81 @@ func TestNormalizePathForComparison(t *testing.T) {
8587 // The exact behavior depends on the platform.
8688 assert .NotEmpty (t , result )
8789}
90+
91+ func TestResolvePath_SymlinkEscape (t * testing.T ) {
92+ t .Parallel ()
93+
94+ if runtime .GOOS == "windows" {
95+ t .Skip ("symlink test not reliable on Windows" )
96+ }
97+
98+ workingDir := t .TempDir ()
99+ outsideDir := t .TempDir ()
100+
101+ // Create a secret file outside the working directory.
102+ secretFile := filepath .Join (outsideDir , "secret.txt" )
103+ require .NoError (t , os .WriteFile (secretFile , []byte ("secret" ), 0o644 ))
104+
105+ // Create a symlink inside the working directory pointing outside.
106+ symlink := filepath .Join (workingDir , "escape" )
107+ require .NoError (t , os .Symlink (outsideDir , symlink ))
108+
109+ ts := & FilesystemToolset {workingDir : workingDir }
110+
111+ // Accessing a file through the symlink should be blocked.
112+ _ , err := ts .resolvePath ("escape/secret.txt" )
113+ require .Error (t , err )
114+ assert .Contains (t , err .Error (), "escapes the working directory" )
115+
116+ // The symlink target itself should also be blocked.
117+ _ , err = ts .resolvePath ("escape" )
118+ require .Error (t , err )
119+ assert .Contains (t , err .Error (), "escapes the working directory" )
120+ }
121+
122+ func TestResolvePath_SymlinkWithinWorkingDir (t * testing.T ) {
123+ t .Parallel ()
124+
125+ if runtime .GOOS == "windows" {
126+ t .Skip ("symlink test not reliable on Windows" )
127+ }
128+
129+ workingDir := t .TempDir ()
130+
131+ // Create a subdirectory and a symlink to it within the working dir.
132+ subdir := filepath .Join (workingDir , "real" )
133+ require .NoError (t , os .Mkdir (subdir , 0o755 ))
134+ require .NoError (t , os .WriteFile (filepath .Join (subdir , "file.txt" ), []byte ("ok" ), 0o644 ))
135+
136+ link := filepath .Join (workingDir , "link" )
137+ require .NoError (t , os .Symlink (subdir , link ))
138+
139+ ts := & FilesystemToolset {workingDir : workingDir }
140+
141+ // Symlink within working dir should be allowed.
142+ resolved , err := ts .resolvePath ("link/file.txt" )
143+ require .NoError (t , err )
144+ assert .Contains (t , resolved , "real/file.txt" )
145+ }
146+
147+ func TestResolvePath_NonExistentPathWithSymlinkAncestor (t * testing.T ) {
148+ t .Parallel ()
149+
150+ if runtime .GOOS == "windows" {
151+ t .Skip ("symlink test not reliable on Windows" )
152+ }
153+
154+ workingDir := t .TempDir ()
155+ outsideDir := t .TempDir ()
156+
157+ // Symlink inside working dir pointing outside.
158+ symlink := filepath .Join (workingDir , "escape" )
159+ require .NoError (t , os .Symlink (outsideDir , symlink ))
160+
161+ ts := & FilesystemToolset {workingDir : workingDir }
162+
163+ // Even for a non-existent file under the symlink, traversal should be blocked.
164+ _ , err := ts .resolvePath ("escape/nonexistent.txt" )
165+ require .Error (t , err )
166+ assert .Contains (t , err .Error (), "escapes the working directory" )
167+ }
0 commit comments