confine GCS downloads to destination root#10060
Conversation
There was a problem hiding this comment.
Code Review
This pull request implements path traversal protection in the GCS client by introducing a resolveDestinationPath function to validate that downloaded files remain within the intended destination directory. Corresponding unit tests were added to verify these security constraints. Feedback suggests optimizing performance by resolving the destination root's absolute path outside the file processing loop and removing redundant filepath.Clean calls where filepath.Join or filepath.FromSlash already handle path normalization.
|
|
||
| for uri, localPath := range files { | ||
| fullPath := filepath.Join(dst, localPath) | ||
| fullPath, err := resolveDestinationPath(dst, localPath) |
There was a problem hiding this comment.
The resolveDestinationPath function performs expensive operations like filepath.Abs (which involves a system call to get the current working directory). Since the destination root dst is constant for all files in the loop, you should resolve the absolute path of dst once outside the loop and pass it to a validation helper. This will significantly improve performance when downloading many files.
| } | ||
|
|
||
| func resolveDestinationPath(dst, localPath string) (string, error) { | ||
| normalizedLocalPath := filepath.Clean(filepath.FromSlash(localPath)) |
There was a problem hiding this comment.
The call to filepath.Clean here is redundant because filepath.FromSlash does not introduce uncleaned segments, and filepath.IsAbs (the only consumer of this variable) does not require a cleaned path to function correctly.
| normalizedLocalPath := filepath.Clean(filepath.FromSlash(localPath)) | |
| normalizedLocalPath := filepath.FromSlash(localPath) |
| return "", fmt.Errorf("gcs object path %q escapes destination root %q", localPath, dst) | ||
| } | ||
|
|
||
| joinedPath := filepath.Clean(filepath.Join(dst, localPath)) |
what changed
why
Native.DownloadRecursivederives local download targets from remote object names. before this change, the code joineddstwith the derived path and wrote the result without checking that the cleaned destination still stayed underdst.that meant a crafted object name could resolve outside the intended download root. this patch keeps the existing relative-path behavior for valid objects, but adds an explicit confinement check before any filesystem write.
impact
valid downloads keep their current layout. object names that resolve outside the requested destination root now fail fast instead of creating directories or writing files in unexpected locations.
validation
go test ./pkg/skaffold/gcs/clientgo test -run 'TestResolveDestinationPath|TestDownloadRecursiveRejectsEscapingPaths' -v ./pkg/skaffold/gcs/client