Library name and version
azure-storage-blob 12.30.0 (behavior also present on main)
Describe the bug
When building the SAS string-to-sign, the SDK inserts the blob name verbatim into the canonical resource, without normalizing backslash (\) to forward slash (/):
# azure/storage/blob/_shared_access_signature.py (generate_blob path)
resource_path = container_name + "/" + blob_name # blob_name used as-is
# azure/storage/blob/_shared_access_signature.py _BlobSharedAccessHelper.add_resource_signature
canonicalized_resource = "/blob/" + account_name + path + "\n" # path used as-is
Two sibling first-party SDKs do normalize \ → / in the same canonical-resource step, and the Azure Storage service treats \ as /:
- Go (
azure-sdk-for-go, sdk/storage/azblob/sas/service.go, getCanonicalName): strings.ReplaceAll(blobName, "\\", "/")
- .NET (
azure-sdk-for-net, Azure.Storage.Blobs/src/Sas/BlobSasBuilder.cs, GetCanonicalName): blobName.Replace("\\", "/")
- JS (
azure-sdk-for-js, storage-blob/src/sas/BlobSASSignatureValues.ts, getCanonicalName): inserts the blob name verbatim — same gap as Python
Because Python signs the canonical resource with the literal \ while the service canonicalizes it to /, the computed signature never matches the service's, and the generated SAS is rejected (HTTP 403) for any blob name containing a backslash.
Reproduction (executed, offline via Azurite)
Azurite (the official emulator) normalizes \ → / on both upload and SAS validation. Generating a SAS for a blob whose name contains \ and requesting it:
GET .../testc/dir%5Cfile?<SAS from generate_blob_sas(blob_name="dir\\file")> -> 403
(Python signed "/blob/acct/testc/dir\file")
GET .../testc/dir%5Cfile?<SAS whose canonical resource normalized "\"->"/"> -> 200
(matches the service's "/blob/acct/testc/dir/file"; this is what Go/.NET produce)
The first token is what generate_blob_sas(..., blob_name="dir\\file", ...) produces; it is rejected. The second is what the Go/.NET SDKs produce (and what the service accepts). A minimal runnable script (Python + Azurite) is available on request.
Expected behavior
A SAS generated for a blob name containing \ should validate against the service, consistent with the Go and .NET SDKs. The canonical resource should normalize \ → /.
Impact / scope (stated honestly)
Correctness / availability, not a security issue: a SAS the caller intends for a backslash-containing blob name is rejected by the service (the delegate receives 403). No privilege escalation. Blob names with backslashes are uncommon but valid, and the four first-party SDKs are inconsistent (Go/.NET normalize; Python/JS do not).
Suggested fix
Normalize the blob name in the canonical resource, matching Go/.NET:
# in the resource-path / add_resource_signature construction
path = path.replace("\\", "/")
canonicalized_resource = "/blob/" + account_name + path + "\n"
(The same fix applies to azure-sdk-for-js getCanonicalName.)
Note on verification
Verified against Azurite (the official Storage emulator). Production Azure Storage is expected to behave identically — the Go and .NET SDKs independently implement the \ → / normalization specifically to match the service, which is strong corroboration that the service requires it.
Library name and version
azure-storage-blob12.30.0 (behavior also present onmain)Describe the bug
When building the SAS string-to-sign, the SDK inserts the blob name verbatim into the canonical resource, without normalizing backslash (
\) to forward slash (/):Two sibling first-party SDKs do normalize
\→/in the same canonical-resource step, and the Azure Storage service treats\as/:azure-sdk-for-go,sdk/storage/azblob/sas/service.go,getCanonicalName):strings.ReplaceAll(blobName, "\\", "/")azure-sdk-for-net,Azure.Storage.Blobs/src/Sas/BlobSasBuilder.cs,GetCanonicalName):blobName.Replace("\\", "/")azure-sdk-for-js,storage-blob/src/sas/BlobSASSignatureValues.ts,getCanonicalName): inserts the blob name verbatim — same gap as PythonBecause Python signs the canonical resource with the literal
\while the service canonicalizes it to/, the computed signature never matches the service's, and the generated SAS is rejected (HTTP 403) for any blob name containing a backslash.Reproduction (executed, offline via Azurite)
Azurite (the official emulator) normalizes
\→/on both upload and SAS validation. Generating a SAS for a blob whose name contains\and requesting it:The first token is what
generate_blob_sas(..., blob_name="dir\\file", ...)produces; it is rejected. The second is what the Go/.NET SDKs produce (and what the service accepts). A minimal runnable script (Python + Azurite) is available on request.Expected behavior
A SAS generated for a blob name containing
\should validate against the service, consistent with the Go and .NET SDKs. The canonical resource should normalize\→/.Impact / scope (stated honestly)
Correctness / availability, not a security issue: a SAS the caller intends for a backslash-containing blob name is rejected by the service (the delegate receives 403). No privilege escalation. Blob names with backslashes are uncommon but valid, and the four first-party SDKs are inconsistent (Go/.NET normalize; Python/JS do not).
Suggested fix
Normalize the blob name in the canonical resource, matching Go/.NET:
(The same fix applies to
azure-sdk-for-jsgetCanonicalName.)Note on verification
Verified against Azurite (the official Storage emulator). Production Azure Storage is expected to behave identically — the Go and .NET SDKs independently implement the
\→/normalization specifically to match the service, which is strong corroboration that the service requires it.