[Storage] az storage blob download-batch: Ensure downloaded blobs stay within the destination directory - #34126
Conversation
…tay within the destination directory
|
Hi Seb C (@Sebby37), |
There was a problem hiding this comment.
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>
|
🔔 Routing this PR to @Azure/act-codegen-extensibility-squad. |
|
🔔 Routing this PR to @Azure/act-platform-engineering-squad. |
Live test results —
|
There was a problem hiding this comment.
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-sensitiveC:\workcontains distinctdestandDESTdirectories, downloading../DEST/payload.txtintoC:\work\destresolves to the siblingC:\work\DEST\payload.txt, but the lowercased containment comparison accepts it. Line 462 returns the original-case path; lines 508–517 pass it todownload_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 Windowscommonpathbeing case-insensitive too; removingnormcasealone is insufficient.
Verify: Extendsrc/azure-cli/azure/cli/command_modules/storage/tests/latest/test_storage_blob_download_batch.pywith native Windows case-sensitive-parent fixtures containing both siblings. Cover normal execution, a valid blob preceding the escaping blob,--dryrun, and--overwritewith an existing sibling-file sentinel. AssertFileOperationError, 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
storagebefore merge.
|
🔔 Routing this PR to @Azure/act-codegen-extensibility-squad. |
|
🔔 Routing this PR to @Azure/act-platform-engineering-squad. |
|
Storage |
…ownload-batch scenario test
Live test results —
|
|
Automated sensitive-information remediation ran on this pull request.
X Engineering Agent does not modify source files. The PR creator must remove or replace each suspected value at the linked line:
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. |
🤖 PR Validation — 🔄 Running
Related command
az storage blob download-batchDescription
az storage blob download-batchbuilds each local file path by joining--destinationwith 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 asC:/...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:
_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 asC:file.txtare rejected on Windows.--dryrun.FileOperationErrornames the offending blob and suggests using--patternto exclude it, oraz storage blob downloadto 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:--dryrun.--overwritedoes not replace existing files outside the destination.ntpathso the test runs on every platform, plus a native Windows-only test.test_storage_blob_batch_download_scenarioslive (preparer switched tokind='StorageV2', sinceStorageaccounts can no longer be created).test_storage_blob_batch_oauth_scenarios(live-only) also passes live.Existing
test_storage_batch_operations.pytests 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--patterncan still be used to download the remaining blobs.Live test evidence
All scenario tests that run
az storage blob download-batchwere run live locally against the latest head:History Notes
This checklist is used to make sure that common guidelines for a pull request are followed.
The PR title and description has followed the guideline in Submitting Pull Requests.
I adhere to the Command Guidelines.
I adhere to the Error Handling Guidelines.