Skip to content

azure.storage.blob.BlobClient upload_blob_from_url errors with CannotVerifyCopySource on valid presigned URLs #40967

@jacobbgarland

Description

@jacobbgarland

This issue started happening on or around 5/5/2025. Prior to that this was working fine. The issue was originally reported over in the azure-storage-azcopy repo (see Issue #3039). Cross-posting the issue since the exact same behavior is being exhibited in the upload_blob_from_url function in azure.storage.blob.BlobClient.

See details below, including the error returned from the upload_blob_from_url call...

Also, having the same issue and it started failing on or around 5/5/2025.

What we've observed so far...

Using an AWS S3 presigned URL as the source URL, both AzCopy and Azure SDK for Python produce the CannotVerifyCopySource error code with a 403 Forbidden status code as the error message.

Using azcopy copy produces:

azcopy copy "https://[bucket].s3.amazonaws.com/foo/bar/baz.jpg?AWSAccessKeyId=[...]&Signature=[...]&x-amz-security-token=[...]&Expires=[...]" "https://mystorageaccount.blob.core.windows.net/foo/bar/baz.jpg" --from-to BlobBlob --overwrite true
INFO: Scanning...
INFO: Failed to create one or more destination container(s). Your transfers may still succeed if the container already exists.
INFO: Any empty folders will not be processed, because source and/or destination doesn't have full folder support

failed to perform copy command due to error: cannot start job due to error: cannot list files due to reason HEAD https://[bucket].s3.amazonaws.com/foo/bar/baz.jpg
--------------------------------------------------------------------------------
RESPONSE 403: 403 Forbidden
ERROR CODE UNAVAILABLE
--------------------------------------------------------------------------------
Response contained no body
--------------------------------------------------------------------------------
.

Using Azure SDK for Python > azure.storage.blob.BlobClient > upload_blob_from_url produces:

Forbidden
RequestId:[...]
Time:2025-05-07T19:26:39.9776804Z
ErrorCode:CannotVerifyCopySource
copysourcestatuscode:403
copysourceerrormessage:Forbidden
Content: 
<?xml version="1.0" encoding="utf-8"?>
<Error>
	<Code>CannotVerifyCopySource</Code>
	<Message>Forbidden
RequestId:[...]
Time:2025-05-07T19:26:39.9776804Z
	</Message>
	<CopySourceStatusCode>403</CopySourceStatusCode>
	<CopySourceErrorMessage>Forbidden</CopySourceErrorMessage>
</Error> 

We verified everything is working as expected in AWS...

  • the presigned URL is valid
  • token is not expired
  • bucket has all the correct policies applied, etc.

So where is the 403 Forbidden coming from?

Before the copy begins, Azure is performing an initial HEAD request on the Source URL (presumably to verify the copy source URL is valid). The error in Azure is originating during this HEAD request.

There’s 2 potential reasons these calls began failing on or around 5/5/2025:

  1. Azure is no longer including the query string parameters in the HEAD request. For presigned URLs, the query string parameters contain the authorization token, so not including the query string parameters causes a 403 Forbidden error.
  2. Azure is including the query string parameter, however AWS presigned URLs which were signed for the action get_object don’t allow both GET and HEAD requests.

To eliminate #2 as an option, we used a CloudFront distribution to ensure that we could serve up a pre-signed URL that allows both GET and HEAD requests. Using the CloudFront presigned URL as the Source URL also resulted in a 403 Forbidden. Therefore, we do not believe that reason #2 is the problem.

Performing an azcopy copy using a CloudFront presigned URL:

azcopy copy "https://[REDACTED].cloudfront.net/foo/bar/baz.jpg?Policy=[REDACTED]&Signature=[REDACTED]&Key-Pair-Id=[REDACTED]" "https://[REDACTED].blob.core.windows.net/foo/bar/baz.jpg?sv=[...]&se=2025-05-08T01%3A25%3A45Z&sr=b&sp=rcw&sig=[REDACTED]&rscc=max-age%3D0" --from-to BlobBlob --overwrite true
INFO: Scanning...
INFO: Failed to create one or more destination container(s). Your transfers may still succeed if the container already exists.
INFO: Any empty folders will not be processed, because source and/or destination doesn't have full folder support

failed to perform copy command due to error: cannot start job due to error: cannot list files due to reason HEAD https://[REDACTED].cloudfront.net/foo/bar/baz.jpg
--------------------------------------------------------------------------------
RESPONSE 403: 403 Forbidden
ERROR CODE UNAVAILABLE
--------------------------------------------------------------------------------
Response contained no body
--------------------------------------------------------------------------------
.

In the error message above, you can see that the HEAD request URL omits the query string parameters.

Performing the HEAD request with the full URL produces a 200 OK response:

curl --head "https://[REDACTED].cloudfront.net/foo/bar/baz.jpg?Policy=[REDACTED]&Signature=[REDACTED]&Key-Pair-Id=[REDACTED]"
HTTP/2 200
[...]

Performing the HEAD request without query string params produces a 403 Forbidden response:

curl --head "https://[REDACTED].cloudfront.net/foo/bar/baz.jpg"
HTTP/1.1 403 Forbidden
[...]

Conclusion

Beginning on or around 5/5/2025, we believe the verification logic Azure uses on Source URLs changed — at least for AWS S3 presigned URLs. Azure is no longer including query string parameters in an initial HEAD request it performs on the Source URL. This causes the HEAD request to fail with a 403 Forbidden status code, resulting in a CannotVerifyCopySource error.

Originally posted by @jacobbgarland in #3039

Metadata

Metadata

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