- Package Name: azure-storage-blob
- Package Version: 12.30.0 (regression from 12.29.0)
- Operating System: Windows 11 10.0.26200 (controlled reproduction)
- Python Version: 3.14.3 (controlled reproduction)
Describe the bug
BlobClient 12.30.0 drops the existing path component from an account_url when the host matches a standard Azure Storage endpoint. Version 12.29.0 preserved that path.
This changes the behavior of a directory-scoped SAS usage pattern where a full directory SAS URL is passed as account_url together with container_name=".". With 12.29.0, the generated URL remains under the signed directory and the upload succeeds. With 12.30.0, the signed directory path is removed, the request resolves to the account root, and Azure Storage returns HTTP 403 AuthenticationFailed / Signature did not match.
We recognize that this usage does not strictly follow the documented constructor contract: account_url is documented as the storage account URI. However, upgrading between stable package versions silently changes the URL and produces a remote authentication error rather than a clear client-side validation error. The change is also not identified as breaking in the changelog.
To Reproduce
Prerequisite: an HNS-enabled storage account and a valid directory-scoped SAS URL (sr=d, sdd=1) for <container>/<directory>. Do not include a real SAS in logs or issue text.
import os
from urllib.parse import urlsplit, urlunsplit
from azure.storage.blob import BlobClient
def without_query(url: str) -> str:
parsed = urlsplit(url)
return urlunsplit((parsed.scheme, parsed.netloc, parsed.path, "", ""))
directory_sas_url = os.environ["DIRECTORY_SAS_URL"]
client = BlobClient(
account_url=directory_sas_url,
container_name=".",
blob_name="probe.txt",
api_version="2024-11-04",
)
print(without_query(client.url))
client.upload_blob(b"test", overwrite=False)
Run the same script with the following package versions while keeping the transitive dependencies unchanged:
azure-storage-blob==12.29.0
azure-storage-blob==12.30.0
azure-core==1.41.0
requests==2.34.2
Observed result with 12.29.0:
https://<account>.blob.core.windows.net/<container>/<directory>/./probe.txt
Upload succeeds.
Observed result with 12.30.0:
https://<account>.blob.core.windows.net/./probe.txt
HTTP 403 AuthenticationFailed: Signature did not match.
As a control, using the same directory SAS with the account URL, actual container name, and <directory>/probe.txt as the blob name succeeds on 12.30.0.
Expected behavior
Please confirm whether dropping the path is intentional.
- If it is not intentional, 12.30.x should preserve the path as 12.29.0 did.
- If resource-level URLs passed as
account_url are intentionally unsupported, the constructor should preferably fail fast with a clear ValueError, and the behavior change should be documented. It should not silently redirect the request to a different resource path and surface only as a Storage authentication failure.
Additional context
The behavior appears to have changed in the endpoint construction added for IPv6/dual-stack support:
-
In 12.29.0, primary_hostname included parsed_url.path:
primary_hostname = (parsed_url.netloc + parsed_url.path).rstrip("/")
-
In 12.30.0, recognized Storage endpoints are passed to _construct_endpoints(parsed_url.netloc, account[0]), which constructs the endpoint from netloc only.
Relevant change:
The 12.30.0b1 changelog mentions support for -ipv6 and -dualstack account URLs, but does not mention removal of an existing URL path or a breaking behavior change.
The issue can also be observed without sending a request: inspecting client.url with a placeholder standard Blob endpoint shows that 12.29.0 preserves /container/directory, while 12.30.0 removes it. This indicates a client-side URL-construction change rather than a Storage service behavior difference.
Describe the bug
BlobClient12.30.0 drops the existing path component from anaccount_urlwhen the host matches a standard Azure Storage endpoint. Version 12.29.0 preserved that path.This changes the behavior of a directory-scoped SAS usage pattern where a full directory SAS URL is passed as
account_urltogether withcontainer_name=".". With 12.29.0, the generated URL remains under the signed directory and the upload succeeds. With 12.30.0, the signed directory path is removed, the request resolves to the account root, and Azure Storage returns HTTP 403AuthenticationFailed/Signature did not match.We recognize that this usage does not strictly follow the documented constructor contract:
account_urlis documented as the storage account URI. However, upgrading between stable package versions silently changes the URL and produces a remote authentication error rather than a clear client-side validation error. The change is also not identified as breaking in the changelog.To Reproduce
Prerequisite: an HNS-enabled storage account and a valid directory-scoped SAS URL (
sr=d,sdd=1) for<container>/<directory>. Do not include a real SAS in logs or issue text.Run the same script with the following package versions while keeping the transitive dependencies unchanged:
azure-storage-blob==12.29.0azure-storage-blob==12.30.0azure-core==1.41.0requests==2.34.2Observed result with 12.29.0:
Observed result with 12.30.0:
As a control, using the same directory SAS with the account URL, actual container name, and
<directory>/probe.txtas the blob name succeeds on 12.30.0.Expected behavior
Please confirm whether dropping the path is intentional.
account_urlare intentionally unsupported, the constructor should preferably fail fast with a clearValueError, and the behavior change should be documented. It should not silently redirect the request to a different resource path and surface only as a Storage authentication failure.Additional context
The behavior appears to have changed in the endpoint construction added for IPv6/dual-stack support:
In 12.29.0,
primary_hostnameincludedparsed_url.path:In 12.30.0, recognized Storage endpoints are passed to
_construct_endpoints(parsed_url.netloc, account[0]), which constructs the endpoint fromnetloconly.Relevant change:
The 12.30.0b1 changelog mentions support for
-ipv6and-dualstackaccount URLs, but does not mention removal of an existing URL path or a breaking behavior change.The issue can also be observed without sending a request: inspecting
client.urlwith a placeholder standard Blob endpoint shows that 12.29.0 preserves/container/directory, while 12.30.0 removes it. This indicates a client-side URL-construction change rather than a Storage service behavior difference.