From 1a19225f8d57c274c9f4df278b47b26256b5860c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=C5=A1an=20Polovina?= Date: Thu, 23 Apr 2026 10:42:52 +0200 Subject: [PATCH 1/4] fix(transfermanager): avoid false positives in path traversal checks for filenames containing '..' when downloading a directory --- .../api_op_DownloadDirectory.go | 2 +- .../download_directory_test.go | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/feature/s3/transfermanager/api_op_DownloadDirectory.go b/feature/s3/transfermanager/api_op_DownloadDirectory.go index c151f49b8aa0..c244ba93fc6d 100644 --- a/feature/s3/transfermanager/api_op_DownloadDirectory.go +++ b/feature/s3/transfermanager/api_op_DownloadDirectory.go @@ -236,7 +236,7 @@ func (d *directoryDownloader) getLocalPath(key string) (string, error) { if err != nil { return "", err } - if relPath == "." || strings.Contains(relPath, "..") { + if relPath == "." || !filepath.IsLocal(relPath) { return "", fmt.Errorf("resolved local path %s is outside of destination %s", path, destination) } diff --git a/feature/s3/transfermanager/download_directory_test.go b/feature/s3/transfermanager/download_directory_test.go index 585bc467c9d4..93d065508df4 100644 --- a/feature/s3/transfermanager/download_directory_test.go +++ b/feature/s3/transfermanager/download_directory_test.go @@ -280,6 +280,27 @@ func TestDownloadDirectory(t *testing.T) { l.expectFailed(t, in, err) }, }, + "object key with double dots in name": { + destination: "double-dots-in-name", + objectsLists: [][]s3types.Object{ + { + { + Key: aws.String("foo/bar..baz"), + }, + { + Key: aws.String("a..b"), + }, + }, + }, + expectTokens: []string{""}, + expectKeys: []string{"foo/bar..baz", "a..b"}, + expectFiles: []string{"foo/bar..baz", "a..b"}, + expectObjectsDownloaded: 2, + listenerValidationFn: func(t *testing.T, l *mockDirectoryListener, in, out any, err error) { + l.expectStart(t, in) + l.expectComplete(t, in, out, 2) + }, + }, "multiple objects with filter applied": { destination: "multiple-objects-with-filter-applied", objectsLists: [][]s3types.Object{ From 89fd80cd8386c759acdab2d2c345c31bcc9f7eb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=C5=A1an=20Polovina?= Date: Tue, 2 Jun 2026 11:46:28 +0200 Subject: [PATCH 2/4] transfermanager test: cover double-dot filenames with path traversal cases --- .../download_directory_test.go | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/feature/s3/transfermanager/download_directory_test.go b/feature/s3/transfermanager/download_directory_test.go index 93d065508df4..3983c864c7f9 100644 --- a/feature/s3/transfermanager/download_directory_test.go +++ b/feature/s3/transfermanager/download_directory_test.go @@ -280,12 +280,12 @@ func TestDownloadDirectory(t *testing.T) { l.expectFailed(t, in, err) }, }, - "object key with double dots in name": { - destination: "double-dots-in-name", + "object key with double dots in name and path": { + destination: "double-dots-in-name-and-path", objectsLists: [][]s3types.Object{ { { - Key: aws.String("foo/bar..baz"), + Key: aws.String("foo/bar/../baz..zoo"), }, { Key: aws.String("a..b"), @@ -293,14 +293,33 @@ func TestDownloadDirectory(t *testing.T) { }, }, expectTokens: []string{""}, - expectKeys: []string{"foo/bar..baz", "a..b"}, - expectFiles: []string{"foo/bar..baz", "a..b"}, + expectKeys: []string{"foo/bar/../baz..zoo", "a..b"}, + expectFiles: []string{"foo/baz..zoo", "a..b"}, expectObjectsDownloaded: 2, listenerValidationFn: func(t *testing.T, l *mockDirectoryListener, in, out any, err error) { l.expectStart(t, in) l.expectComplete(t, in, out, 2) }, }, + "object key with double dots in name and path escaping": { + destination: "double-dots-in-name-and-path-escaping", + objectsLists: [][]s3types.Object{ + { + { + Key: aws.String("foo/../../baz..zoo"), + }, + { + Key: aws.String("a..b"), + }, + }, + }, + expectErr: "outside of destination", + listenerValidationFn: func(t *testing.T, l *mockDirectoryListener, in, out any, err error) { + // only validate failure listener since start listener + // might never be triggerred if the error response is returned first + l.expectFailed(t, in, err) + }, + }, "multiple objects with filter applied": { destination: "multiple-objects-with-filter-applied", objectsLists: [][]s3types.Object{ From 0fec8c21a8d1a685e70a363f67b9b81e9d84bc1f Mon Sep 17 00:00:00 2001 From: wty-Bryant Date: Thu, 4 Jun 2026 15:26:47 -0400 Subject: [PATCH 3/4] add changelog --- .changelog/1c88b192e747436c998a32096006308c.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changelog/1c88b192e747436c998a32096006308c.json diff --git a/.changelog/1c88b192e747436c998a32096006308c.json b/.changelog/1c88b192e747436c998a32096006308c.json new file mode 100644 index 000000000000..1e7c06b8d6f5 --- /dev/null +++ b/.changelog/1c88b192e747436c998a32096006308c.json @@ -0,0 +1,8 @@ +{ + "id": "1c88b192-e747-436c-998a-32096006308c", + "type": "bugfix", + "description": "Loose download directory check to allow downloading objects with double dot", + "modules": [ + "feature/s3/transfermanager" + ] +} \ No newline at end of file From df94760779008529a7569763548e58792569e991 Mon Sep 17 00:00:00 2001 From: wty-Bryant Date: Thu, 4 Jun 2026 15:29:14 -0400 Subject: [PATCH 4/4] update changelog --- .changelog/1c88b192e747436c998a32096006308c.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/1c88b192e747436c998a32096006308c.json b/.changelog/1c88b192e747436c998a32096006308c.json index 1e7c06b8d6f5..6e03dd07889e 100644 --- a/.changelog/1c88b192e747436c998a32096006308c.json +++ b/.changelog/1c88b192e747436c998a32096006308c.json @@ -1,7 +1,7 @@ { "id": "1c88b192-e747-436c-998a-32096006308c", "type": "bugfix", - "description": "Loose download directory check to allow downloading objects with double dot", + "description": "Loose download directory check to allow downloading objects with double dot in filename", "modules": [ "feature/s3/transfermanager" ]