@@ -108,11 +108,20 @@ func Unzip(zipFilePath, destDir string) error {
108108
109109 // Extract each file
110110 for _ , file := range reader .File {
111+ entryPath := filepath .FromSlash (file .Name )
112+
111113 // Create the full destination path
112- destPath := filepath .Join (destDir , file . Name )
114+ destPath := filepath .Join (cleanDestDir , entryPath )
113115
114116 // Check for directory traversal vulnerabilities
115- if ! strings .HasPrefix (destPath , cleanDestDir + string (os .PathSeparator )) {
117+ if ! isPathWithinDir (cleanDestDir , destPath ) {
118+ return fmt .Errorf ("illegal file path: %s" , file .Name )
119+ }
120+ resolvedDestPath , err := resolvePathWithSymlinks (cleanDestDir , entryPath )
121+ if err != nil {
122+ return fmt .Errorf ("failed to resolve destination path %s: %w" , file .Name , err )
123+ }
124+ if ! isPathWithinDir (cleanDestDir , resolvedDestPath ) {
116125 return fmt .Errorf ("illegal file path: %s" , file .Name )
117126 }
118127
@@ -143,8 +152,15 @@ func Unzip(zipFilePath, destDir string) error {
143152 }
144153 targetPath := string (target )
145154 if ! filepath .IsAbs (targetPath ) {
146- resolvedTarget := filepath .Clean (filepath .Join (filepath .Dir (destPath ), targetPath ))
147- if resolvedTarget != cleanDestDir && ! strings .HasPrefix (resolvedTarget , cleanDestDir + string (os .PathSeparator )) {
155+ resolvedParentPath , err := resolvePathWithSymlinks (cleanDestDir , filepath .Dir (entryPath ))
156+ if err != nil {
157+ return fmt .Errorf ("failed to resolve symlink parent path: %w" , err )
158+ }
159+ resolvedTarget , err := resolvePathWithSymlinks (resolvedParentPath , targetPath )
160+ if err != nil {
161+ return fmt .Errorf ("failed to resolve symlink target: %w" , err )
162+ }
163+ if ! isPathWithinDir (cleanDestDir , resolvedTarget ) {
148164 return fmt .Errorf ("illegal symlink target: %s -> %s" , file .Name , targetPath )
149165 }
150166 }
@@ -172,3 +188,34 @@ func Unzip(zipFilePath, destDir string) error {
172188
173189 return nil
174190}
191+
192+ func isPathWithinDir (baseDir , path string ) bool {
193+ return path == baseDir || strings .HasPrefix (path , baseDir + string (os .PathSeparator ))
194+ }
195+
196+ func resolvePathWithSymlinks (baseDir , relPath string ) (string , error ) {
197+ currentPath := filepath .Clean (baseDir )
198+ for _ , part := range strings .Split (filepath .FromSlash (relPath ), string (os .PathSeparator )) {
199+ switch part {
200+ case "" , "." :
201+ continue
202+ case ".." :
203+ currentPath = filepath .Dir (currentPath )
204+ continue
205+ }
206+
207+ nextPath := filepath .Join (currentPath , part )
208+ resolvedPath , err := filepath .EvalSymlinks (nextPath )
209+ if err == nil {
210+ currentPath = resolvedPath
211+ continue
212+ }
213+ if ! os .IsNotExist (err ) {
214+ return "" , fmt .Errorf ("evaluate symlinks for %s: %w" , nextPath , err )
215+ }
216+
217+ currentPath = nextPath
218+ }
219+
220+ return filepath .Clean (currentPath ), nil
221+ }
0 commit comments