Skip to content

[Storage] az storage blob download-batch: Ensure downloaded blobs stay within the destination directory - #34126

Open
Seb C (Sebby37) wants to merge 5 commits into
Azure:devfrom
Sebby37:fix/storage-blob-download-batch-path-traversal
Open

Seb C (Sebby37) wants to merge 5 commits into
Azure:devfrom
Sebby37:fix/storage-blob-download-batch-path-traversal

Conversation

@Sebby37

@Sebby37 Seb C (Sebby37) commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

🤖 PR Validation — ️✔️ All clear

Breaking Changes Tests
️✔️ None ️✔️ 130/130

Related command
az storage blob download-batch

Description
az storage blob download-batch builds each local file path by joining --destination with the blob name returned by the service. The resulting path was never checked against the destination, so a blob whose name is an absolute path (for example a drive-qualified path such as C:/... on Windows) or resolves above the destination directory could be written outside the directory the user specified.

This PR adds a containment check before any file is written:

  • A new helper, _get_blob_download_path(), only accepts blob names that normalize to a plain relative path (no drive, root, or leading ./.. component) and joins them onto the destination, so the result always stays inside it. This avoids case-insensitive path comparisons, which Windows per-directory case sensitivity could bypass. Existing symlinks/junctions under the destination are still followed. Drive-relative names such as C:file.txt are rejected on Windows.
  • All blob names are validated before any download starts, so the command fails cleanly instead of partially downloading the batch. The check also applies to --dryrun.
  • On rejection, a FileOperationError names the offending blob and suggests using --pattern to exclude it, or az storage blob download to download it to an explicit file path.

Downloads where every blob resolves inside the destination are unchanged. Existing symlinks/junctions under the destination are still followed; only the blob name itself is constrained.

Testing Guide
Added test_storage_blob_download_batch.py (mock-based, no recordings required), covering:

  • Normal, nested, leading-slash and self-normalizing blob names download inside the destination.
  • Names that resolve outside the destination are rejected and nothing is written, including when mixed with valid blobs and with --dryrun.
  • Symlink (Linux/macOS) and junction (Windows) escapes.
  • --overwrite does not replace existing files outside the destination.
  • Windows path semantics, simulated with ntpath so the test runs on every platform, plus a native Windows-only test.
  • Re-recorded test_storage_blob_batch_download_scenarios live (preparer switched to kind='StorageV2', since Storage accounts can no longer be created).
  • test_storage_blob_batch_oauth_scenarios (live-only) also passes live.
python -m pytest src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py

Existing test_storage_batch_operations.py tests pass in playback. Also manually verified on Linux and on Windows (against the released 2.90.0 install) that affected blob names are rejected with nothing written outside the destination, and that --pattern can still be used to download the remaining blobs.

Live test evidence
All scenario tests that run az storage blob download-batch were run live locally against the latest head:

test_storage_batch_operations.py::StorageBatchOperationScenarios::test_storage_blob_batch_download_scenarios PASSED
test_storage_batch_operations.py::StorageBatchOperationScenarios::test_storage_blob_batch_sas_scenarios PASSED
================= 2 passed, 9 deselected in 403.16s (0:06:43) ==================
test_storage_oauth_track2.py::StorageOauthTests::test_storage_blob_batch_oauth_scenarios PASSED
================= 1 passed, 10 deselected in 143.32s (0:02:23) =================

History Notes


This checklist is used to make sure that common guidelines for a pull request are followed.

@Sebby37
Seb C (Sebby37) requested a review from a team as a code owner September 24, 2026 06:01
Copilot AI lite review requested due to automatic review settings September 24, 2026 06:01
@azure-client-tools-bot-prd

Copy link
Copy Markdown

Hi Seb C (@Sebby37),
Since the current milestone time is less than 7 days, this pr will be reviewed in the next milestone.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

No unresolved blocking issues were identified.

Review effort: Lite
Findings: None

What changed in this PR

Adds path-containment validation to az storage blob download-batch, preventing downloads from escaping the destination directory.

Changes:

  • Validates resolved paths, including symlink and Windows drive escapes.
  • Validates all blobs before downloading, including dry runs.
  • Adds cross-platform tests for safe and rejected paths.
File Description
src/​azure-cli/​azure/​cli/​command_modules/​storage/​tests/​latest/​test_storage_blob_download_batch.py Tests containment, traversal, symlink, and Windows path scenarios.
src/​azure-cli/​azure/​cli/​command_modules/​storage/​operations/​blob.py Implements secure download-path validation.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jsntcy Yu Chen (jsntcy) added the Request X Engineering Agent Request X Engineering Agent testing and review label Sep 24, 2026
@microsoft-github-policy-service

Copy link
Copy Markdown
Contributor

🔔 Routing this PR to @Azure/act-codegen-extensibility-squad.

@microsoft-github-policy-service

Copy link
Copy Markdown
Contributor

🔔 Routing this PR to @Azure/act-platform-engineering-squad.

@x-engineering-agent

Copy link
Copy Markdown
Contributor

Live test results — azdev test --live --series (changed test files only)

✅ PASS

Selectors: storage.test_storage_blob_download_batch (module)
PR head ref: fix/storage-blob-download-batch-path-traversal
PR head sha: 49e1782309a86f5b8734f5de172fa5e115e3627a
PR base ref: dev
New test files in PR: true

Changed test files run
src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py

New test files
src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py

Workflow run: https://github.com/Azure/issue-sentinel/actions/runs/35981436602

Last 80 lines of azdev output

=============
| Run Tests |
=============


test index found: /home/runner/.azdev/env_config/home/runner/work/issue-sentinel/issue-sentinel/.venv/test_index/latest.json

Test on modules: storage.test_storage_blob_download_batch

WARNING: RUNNING TESTS LIVE
The tests are set to run against current profile "latest"
============================= test session starts ==============================
platform linux -- Python 3.12.14, pytest-9.1.1, pluggy-1.6.0 -- /home/runner/work/issue-sentinel/issue-sentinel/.venv/bin/python
cachedir: .pytest_cache
rootdir: /home/runner/work/issue-sentinel/issue-sentinel/azure-cli/src/azure-cli
plugins: forked-1.7.5, xdist-3.8.0
collecting ... collected 8 items

azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py::TestStorageBlobDownloadBatch::test_download_batch_dryrun_rejects_path_traversal_blob_names PASSED [ 12%]
azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py::TestStorageBlobDownloadBatch::test_download_batch_rejects_before_downloading_any_blob PASSED [ 25%]
azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py::TestStorageBlobDownloadBatch::test_download_batch_rejects_directory_link_escape PASSED [ 37%]
azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py::TestStorageBlobDownloadBatch::test_download_batch_rejects_path_traversal_blob_names PASSED [ 50%]
azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py::TestStorageBlobDownloadBatch::test_download_batch_rejects_windows_blob_names_with_simulated_windows_paths PASSED [ 62%]
azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py::TestStorageBlobDownloadBatch::test_download_batch_rejects_windows_traversal_blob_names SKIPPED [ 75%]
azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py::TestStorageBlobDownloadBatch::test_download_batch_with_overwrite_does_not_replace_files_outside_destination PASSED [ 87%]
azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py::TestStorageBlobDownloadBatch::test_download_batch_writes_blobs_inside_destination PASSED [100%]

- generated xml file: /home/runner/work/issue-sentinel/issue-sentinel/test-output/results.xml -
========================= 7 passed, 1 skipped in 0.27s =========================

Posted by the X Engineering Agent live-test workflow.

@x-engineering-agent x-engineering-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seb C (@Sebby37)

Review

Reviewed 49e1782309a86f5b8734f5de172fa5e115e3627a. One Windows filesystem-containment issue needs attention; this is a COMMENT, not an approval.

Upstream CI

All 51/51 checks passed at this head. Reported CI/live-test failures: 0 PR-related, 0 unrelated, 0 uncertain.

Confirmed semantic finding

  • P2 — Case-sensitive Windows directories can bypass containment. In src/azure-cli/azure/cli/command_modules/storage/operations/blob.py:450–454, both resolved paths are lowercased before comparison. Windows supports per-directory case sensitivity. If a case-sensitive C:\work contains distinct dest and DEST directories, downloading ../DEST/payload.txt into C:\work\dest resolves to the sibling C:\work\DEST\payload.txt, but the lowercased containment comparison accepts it. Line 462 returns the original-case path; lines 508–517 pass it to download_blob, which opens that path at line 749. Consequently both validation passes accept a write outside the selected destination. This conclusion follows from the changed-line/data-flow inspection; no local reproduction was run.
    Remediation: Check resolved ancestor directory identity using filesystem-aware comparisons that preserve case-sensitive distinctions. Account for Windows commonpath being case-insensitive too; removing normcase alone is insufficient.
    Verify: Extend src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py with native Windows case-sensitive-parent fixtures containing both siblings. Cover normal execution, a valid blob preceding the escaping blob, --dryrun, and --overwrite with an existing sibling-file sentinel. Assert FileOperationError, no download calls, and no sibling creation/overwrite; retain successful mixed-case paths on ordinary case-insensitive Windows. Run this focused file on Windows and retain its POSIX checks.

Test validation

  • Live test: Passed. Workflow run
  • Regression coverage: Present for storage: 1 focused test file(s) changed.

Review scope and recording assessment

All seven skills were accounted for: release artifact and scope match the two-file change; generated ownership and command/help are not applicable; user intent was traced through filtering before preflight and preservation of original service blob names; test strength and domain edge cases were inspected above. No deterministic repository-policy findings were emitted.

The added tests exercise actual local writes with mocked downloads, including traversal rejection, batch preflight, links, dry-run, and overwrite protection. No recordings changed. Service request/response shapes and successful-download result accumulation are unchanged, so these filesystem checks do not require new service recordings. The case-sensitive Windows regression above is still missing; green CI/live tests and the module-level coverage signal do not establish complete platform coverage.

Risk assessment

26/100 · Medium · High confidence

The Medium rating is driven by security-sensitive behavior.

  • Change scope: 2 changed files, 163 changed lines (+162 / -1), including 1 production file.
  • Affected components: storage
  • Risk drivers: security-sensitive behavior (+28)
  • Regression evidence: Changed regression tests are included, reducing risk.
  • Confidence: High because changed-line patches were available for every production file.
  • Required review: Owning-squad review is required for storage before merge.

@x-engineering-agent x-engineering-agent Bot added the X Engineering Agent Reviewed Pull request reviewed by X Engineering Agent label Sep 24, 2026
@microsoft-github-policy-service

Copy link
Copy Markdown
Contributor

🔔 Routing this PR to @Azure/act-codegen-extensibility-squad.

@microsoft-github-policy-service

Copy link
Copy Markdown
Contributor

🔔 Routing this PR to @Azure/act-platform-engineering-squad.

@yonzhan

Copy link
Copy Markdown
Collaborator

Storage

@x-engineering-agent

Copy link
Copy Markdown
Contributor

Live test results — azdev test --live --series (changed test files only)

❌ FAIL (exit 1)

Selectors: storage.test_storage_batch_operations storage.test_storage_blob_download_batch storage.test_storage_oauth_track2 (module)
PR head ref: fix/storage-blob-download-batch-path-traversal
PR head sha: f116f590a4910615264740f32c056655e5840342
PR base ref: dev
New test files in PR: true

Changed test files run
src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_batch_operations.py
src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py
src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_oauth_track2.py

New test files
src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py

Workflow run: https://github.com/Azure/issue-sentinel/actions/runs/36078066072

Last 80 lines of azdev output
           ^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.12/site-packages/azure/mgmt/storage/operations/_operations.py:6851: in begin_create
    raw_result = self._create_initial(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <azure.mgmt.storage.operations._operations.StorageAccountsOperations object at 0x7fc9f2face30>
resource_group_name = 'clitest.rgbctpvdvu4bojepe5vexw7vrbevm7qafhyhnkthsn2hbelggde3ii7wpp32kyvrm5p'
account_name = 'clitest5epnop3s57dj7zms4'
parameters = {'sku': {'name': 'Standard_LRS'}, 'kind': 'Storage', 'location': 'westus', 'properties': {'encryption': {'keySource': 'Microsoft.Storage', 'services': {'blob': {}}}, 'supportsHttpsTrafficOnly': True, 'allowBlobPublicAccess': False}}
kwargs = {}

    def _create_initial(
        self,
        resource_group_name: str,
        account_name: str,
        parameters: Union[_models.StorageAccountCreateParameters, JSON, IO[bytes]],
        **kwargs: Any
    ) -> Iterator[bytes]:
        error_map: MutableMapping = {
            401: ClientAuthenticationError,
            404: ResourceNotFoundError,
            409: ResourceExistsError,
            304: ResourceNotModifiedError,
        }
        error_map.update(kwargs.pop("error_map", {}) or {})
    
        _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
        _params = kwargs.pop("params", {}) or {}
    
        content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None))
        cls: ClsType[Iterator[bytes]] = kwargs.pop("cls", None)
    
        content_type = content_type or "application/json"
        _content = None
        if isinstance(parameters, (IOBase, bytes)):
            _content = parameters
        else:
            _content = json.dumps(parameters, cls=SdkJSONEncoder, exclude_readonly=True)  # type: ignore
    
        _request = build_storage_accounts_create_request(
            resource_group_name=resource_group_name,
            account_name=account_name,
            subscription_id=self._config.subscription_id,
            content_type=content_type,
            api_version=self._config.api_version,
            content=_content,
            headers=_headers,
            params=_params,
        )
        path_format_arguments = {
            "endpoint": self._serialize.url("self._config.base_url", self._config.base_url, "str", skip_quote=True),
        }
        _request.url = self._client.format_url(_request.url, **path_format_arguments)
    
        _decompress = kwargs.pop("decompress", True)
        _stream = True
        pipeline_response: PipelineResponse = self._client._pipeline.run(  # pylint: disable=protected-access
            _request, stream=_stream, **kwargs
        )
    
        response = pipeline_response.http_response
    
        if response.status_code not in [200, 202]:
            try:
                response.read()  # Load the body in memory and close the socket
            except (StreamConsumedError, StreamClosedError):
                pass
            map_error(status_code=response.status_code, response=response, error_map=error_map)
            error = _failsafe_deserialize(
                _models.ErrorResponse,
                response,
            )
>           raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat)
E           azure.core.exceptions.HttpResponseError: (AccountKindNotSupported) Account Kind Storage is not supported.
E           Code: AccountKindNotSupported
E           Message: Account Kind Storage is not supported.

.venv/lib/python3.12/site-packages/azure/mgmt/storage/operations/_operations.py:6700: HttpResponseError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
============================== 1 failed in 6.63s ===============================

Posted by the X Engineering Agent live-test workflow.

@x-engineering-agent

Copy link
Copy Markdown
Contributor

Automated sensitive-information remediation ran on this pull request.

  • Detected categories: payment card
  • Replaced with typed [REDACTED:category] placeholders in: no PR metadata fields
  • Comment/review owners notified because X Engineering Agent cannot edit another user's text: @x-engineering-agent[bot]

X Engineering Agent does not modify source files. The PR creator must remove or replace each suspected value at the linked line:

  • No changed-file findings

If a credential was exposed, rotate or revoke it immediately. Detected values are never copied into this comment.

✅ Confirm the finding · ❌ Dispute the finding

GitHub only supports a fixed reaction set, so 👍 represents ✅ and 👎 represents ❌. The bot-created reactions are only poll choices.

Comment thread src/azure-cli/azure/cli/command_modules/storage/operations/blob.py Outdated
@x-engineering-agent

Copy link
Copy Markdown
Contributor

Live test results — azdev test --live --series (changed test files only)

❌ FAIL (exit 1)

Selectors: storage.test_storage_blob_batch_download_scenarios storage.test_storage_blob_batch_sas_scenarios storage.test_storage_blob_download_batch storage.test_storage_blob_batch_oauth_scenarios (module)
PR head ref: fix/storage-blob-download-batch-path-traversal
PR head sha: 080e93fac5f1b4916f63c1de0686a91c09d1a111
PR base ref: dev
New test files in PR: true

Changed test files run
src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_batch_operations.py
src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py
src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_oauth_track2.py

New test files
src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py

Workflow run: https://github.com/Azure/issue-sentinel/actions/runs/36223166328

Live-test recordings: 3 regenerated — archived in workflow artifact live-test-pr-34126 (recordings/).

⚠️ The recording was not committed because the live test failed. Fix the test failure before publishing its regenerated cassette.

Recording files
src/azure-cli/azure/cli/command_modules/storage/tests/latest/recordings/test_storage_blob_batch_download_scenarios.yaml
src/azure-cli/azure/cli/command_modules/storage/tests/latest/recordings/test_storage_blob_batch_oauth_scenarios.yaml
src/azure-cli/azure/cli/command_modules/storage/tests/latest/recordings/test_storage_blob_batch_sas_scenarios.yaml
Last 80 lines of azdev output
        src_container = self.create_container(storage_account_info)
    
        # upload test files to storage account when precondition failed
>       self.oauth_cmd('storage blob list -c {} --account-name {} '.format(src_container, storage_account))\
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
            .assert_with_checks(JMESPathCheck('length(@)', 0))

azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_oauth_track2.py:332: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/storage_test_util.py:41: in oauth_cmd
    return self.cmd(cmd + ' --auth-mode login', *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
azure-cli/src/azure-cli-testsdk/azure/cli/testsdk/base.py:177: in cmd
    return execute(self.cli_ctx, command, expect_failure=expect_failure).assert_with_checks(checks)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
azure-cli/src/azure-cli-testsdk/azure/cli/testsdk/base.py:252: in __init__
    self._in_process_execute(cli_ctx, command, expect_failure=expect_failure)
azure-cli/src/azure-cli-testsdk/azure/cli/testsdk/base.py:315: in _in_process_execute
    raise ex.exception
.venv/lib/python3.12/site-packages/knack/cli.py:233: in invoke
    cmd_result = self.invocation.execute(args)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
azure-cli/src/azure-cli-core/azure/cli/core/commands/__init__.py:677: in execute
    raise ex
azure-cli/src/azure-cli-core/azure/cli/core/commands/__init__.py:820: in _run_jobs_serially
    results.append(self._run_job(expanded_arg, cmd_copy))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
azure-cli/src/azure-cli-core/azure/cli/core/commands/__init__.py:812: in _run_job
    return cmd_copy.exception_handler(ex)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
azure-cli/src/azure-cli/azure/cli/command_modules/storage/__init__.py:357: in new_handler
    first(ex)
azure-cli/src/azure-cli/azure/cli/command_modules/storage/__init__.py:356: in new_handler
    raise ex
azure-cli/src/azure-cli-core/azure/cli/core/commands/__init__.py:789: in _run_job
    result = cmd_copy(params)
             ^^^^^^^^^^^^^^^^
azure-cli/src/azure-cli-core/azure/cli/core/commands/__init__.py:335: in __call__
    return self.handler(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
azure-cli/src/azure-cli-core/azure/cli/core/commands/command_operation.py:120: in handler
    return op(**command_args)
           ^^^^^^^^^^^^^^^^^^
azure-cli/src/azure-cli/azure/cli/command_modules/storage/operations/blob.py:202: in list_blobs
    result = list_generator(pages=pages, num_results=num_results)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
azure-cli/src/azure-cli/azure/cli/command_modules/storage/track2_util.py:74: in list_generator
    page = list(next(pages))
                ^^^^^^^^^^^
.venv/lib/python3.12/site-packages/azure/core/paging.py:82: in __next__
    self._response = self._get_next(self.continuation_token)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.12/site-packages/azure/storage/blob/_list_blobs_helper.py:96: in _get_next_cb
    process_storage_error(error)
.venv/lib/python3.12/site-packages/azure/storage/blob/_shared/response_handlers.py:195: in process_storage_error
    exec("raise error from None")  # pylint: disable=exec-used # nosec
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

>   ???
E   azure.core.exceptions.HttpResponseError: 
E   You do not have the required permissions needed to perform this operation.
E   Depending on your operation, you may need to be assigned one of the following roles:
E       "Storage Blob Data Owner"
E       "Storage Blob Data Contributor"
E       "Storage Blob Data Reader"
E       "Storage Queue Data Contributor"
E       "Storage Queue Data Reader"
E       "Storage Table Data Contributor"
E       "Storage Table Data Reader"
E   
E   If you want to use the old authentication method and allow querying for the right account key, please use the "--auth-mode" parameter and "key" value.
E                       
E   Content: <?xml version="1.0" encoding="utf-8"?><Error><Code>AuthorizationPermissionMismatch</Code><Message>This request is not authorized to perform this operation using this permission.
E   RequestId:7fd5fcd7-801e-0035-5c7f-4d51a5000000
E   Time:2026-09-26T06:21:10.0877790Z</Message></Error>

<string>:1: HttpResponseError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
============== 1 failed, 9 passed, 2 skipped in 142.56s (0:02:22) ==============

Posted by the X Engineering Agent live-test workflow.

@x-engineering-agent

Copy link
Copy Markdown
Contributor

Live test results — azdev test --live --series (changed test files only)

✅ PASS

Selectors: storage.test_storage_blob_batch_download_scenarios storage.test_storage_blob_batch_sas_scenarios storage.test_storage_blob_download_batch storage.test_storage_blob_batch_oauth_scenarios (module)
PR head ref: fix/storage-blob-download-batch-path-traversal
PR head sha: 080e93fac5f1b4916f63c1de0686a91c09d1a111
PR base ref: dev
New test files in PR: true

Changed test files run
src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_batch_operations.py
src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py
src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_oauth_track2.py

New test files
src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py

Workflow run: https://github.com/Azure/issue-sentinel/actions/runs/36225691786

Live-test recordings: 3 regenerated — archived in workflow artifact live-test-pr-34126 (recordings/).

ℹ️ This PR is from a fork, so recordings were not pushed automatically. Download them from the artifact and commit to your branch.

Recording files
src/azure-cli/azure/cli/command_modules/storage/tests/latest/recordings/test_storage_blob_batch_download_scenarios.yaml
src/azure-cli/azure/cli/command_modules/storage/tests/latest/recordings/test_storage_blob_batch_oauth_scenarios.yaml
src/azure-cli/azure/cli/command_modules/storage/tests/latest/recordings/test_storage_blob_batch_sas_scenarios.yaml
Last 80 lines of azdev output

=============
| Run Tests |
=============


test index found: /home/runner/.azdev/env_config/home/runner/work/issue-sentinel/issue-sentinel/.venv/test_index/latest.json

Test on modules: storage.test_storage_blob_batch_download_scenarios, storage.test_storage_blob_batch_sas_scenarios, storage.test_storage_blob_download_batch, storage.test_storage_blob_batch_oauth_scenarios

WARNING: RUNNING TESTS LIVE
The tests are set to run against current profile "latest"
============================= test session starts ==============================
platform linux -- Python 3.12.14, pytest-9.1.1, pluggy-1.6.0 -- /home/runner/work/issue-sentinel/issue-sentinel/.venv/bin/python
cachedir: .pytest_cache
rootdir: /home/runner/work/issue-sentinel/issue-sentinel/azure-cli/src/azure-cli
plugins: forked-1.7.5, xdist-3.8.0
collecting ... collected 12 items

azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_batch_operations.py::StorageBatchOperationScenarios::test_storage_blob_batch_download_scenarios PASSED [  8%]
azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_batch_operations.py::StorageBatchOperationScenarios::test_storage_blob_batch_sas_scenarios PASSED [ 16%]
azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py::TestStorageBlobDownloadBatch::test_download_batch_dryrun_rejects_path_traversal_blob_names PASSED [ 25%]
azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py::TestStorageBlobDownloadBatch::test_download_batch_follows_existing_directory_links PASSED [ 33%]
azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py::TestStorageBlobDownloadBatch::test_download_batch_rejects_before_downloading_any_blob PASSED [ 41%]
azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py::TestStorageBlobDownloadBatch::test_download_batch_rejects_case_variant_sibling_in_case_sensitive_directory SKIPPED [ 50%]
azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py::TestStorageBlobDownloadBatch::test_download_batch_rejects_path_traversal_blob_names PASSED [ 58%]
azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py::TestStorageBlobDownloadBatch::test_download_batch_rejects_windows_blob_names_with_simulated_windows_paths PASSED [ 66%]
azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py::TestStorageBlobDownloadBatch::test_download_batch_rejects_windows_traversal_blob_names SKIPPED [ 75%]
azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py::TestStorageBlobDownloadBatch::test_download_batch_with_overwrite_does_not_replace_files_outside_destination PASSED [ 83%]
azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.py::TestStorageBlobDownloadBatch::test_download_batch_writes_blobs_inside_destination PASSED [ 91%]
azure-cli/src/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_oauth_track2.py::StorageOauthTests::test_storage_blob_batch_oauth_scenarios PASSED [100%]

- generated xml file: /home/runner/work/issue-sentinel/issue-sentinel/test-output/results.xml -
================== 10 passed, 2 skipped in 272.28s (0:04:32) ===================

Posted by the X Engineering Agent live-test workflow.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

act-codegen-extensibility-squad act-platform-engineering-squad Auto-Assign Auto assign by bot Installation Request X Engineering Agent Request X Engineering Agent testing and review Storage az storage X Engineering Agent Reviewed Pull request reviewed by X Engineering Agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants