Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/next-release/bugfix-s3-10579.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "bugfix",
"category": "s3",
"description": "Fixed a bug in `s3 cp`/`s3 sync --recursive` where the parent-directory-reference guard failed to detect an S3 key that normalizes to exactly `..` (e.g. `prefix/..`), allowing such objects to bypass the protective warning and crash the download with an unhandled OS error instead of being skipped."
}
2 changes: 1 addition & 1 deletion awscli/customizations/s3/s3handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ def _warn_parent_reference(self, fileinfo):
# Anchor compare_key against '.' (the destination directory root)
# to avoid false negatives on paths like '/../foo'
normalized = os.path.normpath('.' + os.path.sep + fileinfo.compare_key)
escapes_cwd = normalized.startswith(parent_prefix)
escapes_cwd = normalized == '..' or normalized.startswith(parent_prefix)
if escapes_cwd:
warning = create_warning(
fileinfo.compare_key, "File references a parent directory.")
Expand Down
14 changes: 14 additions & 0 deletions tests/unit/customizations/s3/test_s3handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,20 @@ def test_warn_and_ignore_on_double_leading_slash_parent_reference(self):
self.assertIsInstance(warning_result, WarningResult)
self.assert_no_downloads_happened()

def test_warn_and_ignore_on_bare_parent_reference(self):
fileinfo = self.create_file_info('..')
future = self.transfer_request_submitter.submit(fileinfo)
warning_result = self.result_queue.get()
self.assertIsInstance(warning_result, WarningResult)
self.assert_no_downloads_happened()

def test_warn_and_ignore_on_bare_parent_reference_with_prefix(self):
fileinfo = self.create_file_info('a/../..')
future = self.transfer_request_submitter.submit(fileinfo)
warning_result = self.result_queue.get()
self.assertIsInstance(warning_result, WarningResult)
self.assert_no_downloads_happened()

def test_dry_run(self):
self.cli_params['dryrun'] = True
self.transfer_request_submitter = DownloadRequestSubmitter(
Expand Down