Skip to content

generate_blob_sas omits backslash->forward-slash normalization in SAS canonical resource; tokens rejected for blob names containing '\' (Go/.NET normalize; JS has same gap) #48690

Description

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ClientThis issue points to a problem in the data-plane of the library.Service AttentionWorkflow: This issue is responsible by Azure service team.StorageStorage Service (Queues, Blobs, Files)customer-reportedIssues that are reported by GitHub users external to the Azure organization.needs-team-attentionWorkflow: This issue needs attention from Azure service team or SDK teamquestionThe issue doesn't require a change to the product in order to be resolved. Most issues start as that

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions