From f8371ea652d4a05a363bd3b52ac15d1648b3600d Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Wed, 17 Jun 2026 11:19:58 -0400 Subject: [PATCH 1/8] Strip sensitive auth info on cross-domain redirect --- .../azure/storage/blob/_shared/base_client.py | 2 + .../storage/blob/_shared/base_client_async.py | 2 + .../azure/storage/blob/_shared/policies.py | 79 ++++++++++++++++++- .../tests/test_sensitive_redirect.py | 63 +++++++++++++++ 4 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 sdk/storage/azure-storage-blob/tests/test_sensitive_redirect.py diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/base_client.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/base_client.py index fb62552c15b4..3b9c4b192929 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/base_client.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/base_client.py @@ -55,6 +55,7 @@ StorageLoggingPolicy, StorageRequestHook, StorageResponseHook, + StorageSensitiveHeaderCleanupPolicy, ) from .request_handlers import serialize_batch_body, _get_batch_request_delimiter from .response_handlers import PartialBatchErrorException, process_storage_error @@ -331,6 +332,7 @@ def _create_pipeline( StorageResponseHook(**kwargs), DistributedTracingPolicy(**kwargs), HttpLoggingPolicy(**kwargs), + StorageSensitiveHeaderCleanupPolicy(**kwargs), ] if kwargs.get("_additional_pipeline_policies"): policies = policies + kwargs.get("_additional_pipeline_policies") # type: ignore diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/base_client_async.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/base_client_async.py index 7169ac25464c..818302725e98 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/base_client_async.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/base_client_async.py @@ -39,6 +39,7 @@ StorageHeadersPolicy, StorageHosts, StorageRequestHook, + StorageSensitiveHeaderCleanupPolicy, ) from .policies_async import ( AsyncStorageBearerTokenCredentialPolicy, @@ -145,6 +146,7 @@ def _create_pipeline( AsyncStorageResponseHook(**kwargs), DistributedTracingPolicy(**kwargs), HttpLoggingPolicy(**kwargs), + StorageSensitiveHeaderCleanupPolicy(**kwargs), ] if kwargs.get("_additional_pipeline_policies"): policies = policies + kwargs.get("_additional_pipeline_policies") # type: ignore diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py index 3a5f0b9d662f..c643053e5408 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py @@ -11,7 +11,7 @@ import uuid from io import BytesIO, SEEK_SET, UnsupportedOperation from time import time -from typing import Any, Dict, Optional, TYPE_CHECKING, Union +from typing import Any, Dict, List, Optional, TypeVar, TYPE_CHECKING, Union from urllib.parse import ( parse_qsl, urlencode, @@ -29,6 +29,12 @@ RequestHistory, SansIOHTTPPolicy, ) +from azure.core.pipeline import PipelineRequest +from azure.core.pipeline.transport import ( + HttpRequest as LegacyHttpRequest, + HttpResponse as LegacyHttpResponse, +) +from azure.core.rest import HttpRequest, HttpResponse from .authentication import AzureSigningError, StorageHttpChallenge from .constants import DEFAULT_OAUTH_SCOPE, DATA_BLOCK_SIZE @@ -54,6 +60,10 @@ ) +HTTPResponseType = TypeVar("HTTPResponseType", HttpResponse, LegacyHttpResponse) +HTTPRequestType = TypeVar("HTTPRequestType", HttpRequest, LegacyHttpRequest) + + _LOGGER = logging.getLogger(__name__) CONTENT_LENGTH_HEADER = "Content-Length" MD5_HEADER = "Content-MD5" @@ -843,3 +853,70 @@ def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") self.authorize_request(request, scope, tenant_id=challenge.tenant_id) return True + + +class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy[HTTPRequestType, HTTPResponseType]): + """A simple policy that cleans up sensitive headers + + :keyword list[str] blocked_redirect_headers: The headers to clean up when redirecting to another domain. + :keyword bool disable_redirect_cleanup: Opt out cleaning up sensitive headers when redirecting to another domain. + """ + + DEFAULT_SENSITIVE_HEADERS = set( + [ + "Authorization", + "x-ms-authorization-auxiliary", + "x-ms-copy-source", + "x-ms-copy-source-authorization", + "x-ms-rename-source", + ] + ) + + DEFAULT_SENSITIVE_QUERY_PARAMS = set( + [ + "sig", + ] + ) + + def __init__( + self, + *, + blocked_redirect_headers: Optional[List[str]] = None, + blocked_query_params: Optional[List[str]] = None, + disable_redirect_cleanup: bool = False, + **kwargs: Any + ) -> None: + self._disable_redirect_cleanup = disable_redirect_cleanup + self._blocked_redirect_headers = ( + StorageSensitiveHeaderCleanupPolicy.DEFAULT_SENSITIVE_HEADERS + if blocked_redirect_headers is None + else blocked_redirect_headers + ) + self._blocked_query_params = ( + StorageSensitiveHeaderCleanupPolicy.DEFAULT_SENSITIVE_QUERY_PARAMS + if blocked_query_params is None + else blocked_query_params + ) + + def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None: + """This is executed before sending the request to the next policy. + + :param request: The PipelineRequest object. + :type request: ~azure.core.pipeline.PipelineRequest + """ + # "insecure_domain_change" is used to indicate that a redirect + # has occurred to a different domain. This tells the SensitiveHeaderCleanupPolicy + # to clean up sensitive headers. + insecure_domain_change = request.context.get("insecure_domain_change", False) + if not self._disable_redirect_cleanup and insecure_domain_change: + # Clean up request query parameters + parsed = urlparse(request.http_request.url) + kept = [ + pair for pair in parsed.query.split("&") + if pair and pair.split("=", 1)[0] not in self._blocked_query_params + ] + request.http_request.url = urlunparse(parsed._replace(query="&".join(kept))) + + # Clean up request headers + for header in self._blocked_redirect_headers: + request.http_request.headers.pop(header, None) diff --git a/sdk/storage/azure-storage-blob/tests/test_sensitive_redirect.py b/sdk/storage/azure-storage-blob/tests/test_sensitive_redirect.py new file mode 100644 index 000000000000..c8435e2a5b30 --- /dev/null +++ b/sdk/storage/azure-storage-blob/tests/test_sensitive_redirect.py @@ -0,0 +1,63 @@ +from azure.core.pipeline import PipelineRequest, PipelineContext +from azure.core.rest import HttpRequest +from azure.storage.blob._shared.policies import StorageSensitiveHeaderCleanupPolicy + + +def make_request(url, headers=None): + http_request = HttpRequest("GET", url, headers=headers or {}) + context = PipelineContext(None) + return PipelineRequest(http_request, context) + + +class TestStorageSensitiveHeaderCleanup: + SENSITIVE_HEADERS = { + "Authorization": "Bearer token", + "x-ms-authorization-auxiliary": "Bearer aux-token", + "x-ms-copy-source": "https://acct.blob.core.windows.net/c/src?sig=SECRET", + "x-ms-copy-source-authorization": "Bearer copy-token", + "x-ms-rename-source": "/c/old", + } + + def test_storage_sensitive_cleanup_on_redirect(self): + headers = dict(self.SENSITIVE_HEADERS) + headers["x-ms-meta-keep"] = "ok" + request = make_request( + "https://acct.blob.core.windows.net/c/b?comp=block&sv=2026-04-06&sig=SECRET", + headers=headers, + ) + request.context["insecure_domain_change"] = True + + StorageSensitiveHeaderCleanupPolicy().on_request(request) + + assert "sig=" not in request.http_request.url + assert "sv=2026-04-06" in request.http_request.url + assert "comp=block" in request.http_request.url + + for header in self.SENSITIVE_HEADERS: + assert header not in request.http_request.headers + + assert request.http_request.headers["x-ms-meta-keep"] == "ok" + + def test_no_cleanup_when_no_redirect(self): + request = make_request( + "https://acct.blob.core.windows.net/c/b?sig=SECRET", + headers=dict(self.SENSITIVE_HEADERS), + ) + StorageSensitiveHeaderCleanupPolicy().on_request(request) + + assert "sig=SECRET" in request.http_request.url + for header, value in self.SENSITIVE_HEADERS.items(): + assert request.http_request.headers[header] == value + + def test_no_cleanup_when_disabled(self): + request = make_request( + "https://acct.blob.core.windows.net/c/b?sig=SECRET", + headers=dict(self.SENSITIVE_HEADERS), + ) + request.context["insecure_domain_change"] = True + + StorageSensitiveHeaderCleanupPolicy(disable_redirect_cleanup=True).on_request(request) + + assert "sig=SECRET" in request.http_request.url + for header, value in self.SENSITIVE_HEADERS.items(): + assert request.http_request.headers[header] == value From 03f9fa846ec93b61795750e7e71883ebb432f74d Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Wed, 17 Jun 2026 11:27:50 -0400 Subject: [PATCH 2/8] Small formatting change --- .../azure/storage/blob/_shared/policies.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py index c643053e5408..9016698164c0 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py @@ -884,7 +884,7 @@ def __init__( blocked_redirect_headers: Optional[List[str]] = None, blocked_query_params: Optional[List[str]] = None, disable_redirect_cleanup: bool = False, - **kwargs: Any + **kwargs: Any, ) -> None: self._disable_redirect_cleanup = disable_redirect_cleanup self._blocked_redirect_headers = ( @@ -912,7 +912,8 @@ def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None: # Clean up request query parameters parsed = urlparse(request.http_request.url) kept = [ - pair for pair in parsed.query.split("&") + pair + for pair in parsed.query.split("&") if pair and pair.split("=", 1)[0] not in self._blocked_query_params ] request.http_request.url = urlunparse(parsed._replace(query="&".join(kept))) From c95a6594f92b925a74c4676240ae37cda63c3dde Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Wed, 17 Jun 2026 12:20:11 -0400 Subject: [PATCH 3/8] Shared code --- .../azure/storage/blob/_shared/policies.py | 2 +- .../filedatalake/_shared/base_client.py | 2 + .../filedatalake/_shared/base_client_async.py | 2 + .../storage/filedatalake/_shared/policies.py | 80 ++++++++++++++++++- .../storage/fileshare/_shared/base_client.py | 2 + .../fileshare/_shared/base_client_async.py | 2 + .../storage/fileshare/_shared/policies.py | 80 ++++++++++++++++++- .../storage/queue/_shared/base_client.py | 2 + .../queue/_shared/base_client_async.py | 2 + .../azure/storage/queue/_shared/policies.py | 80 ++++++++++++++++++- 10 files changed, 250 insertions(+), 4 deletions(-) diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py index 9016698164c0..97377cf019ba 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py @@ -29,10 +29,10 @@ RequestHistory, SansIOHTTPPolicy, ) -from azure.core.pipeline import PipelineRequest from azure.core.pipeline.transport import ( HttpRequest as LegacyHttpRequest, HttpResponse as LegacyHttpResponse, + PipelineRequest, ) from azure.core.rest import HttpRequest, HttpResponse diff --git a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/base_client.py b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/base_client.py index 08e54267f1c7..a8335a3e746c 100644 --- a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/base_client.py +++ b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/base_client.py @@ -55,6 +55,7 @@ StorageLoggingPolicy, StorageRequestHook, StorageResponseHook, + StorageSensitiveHeaderCleanupPolicy, ) from .request_handlers import serialize_batch_body, _get_batch_request_delimiter from .response_handlers import PartialBatchErrorException, process_storage_error @@ -333,6 +334,7 @@ def _create_pipeline( StorageResponseHook(**kwargs), DistributedTracingPolicy(**kwargs), HttpLoggingPolicy(**kwargs), + StorageSensitiveHeaderCleanupPolicy(**kwargs), ] if kwargs.get("_additional_pipeline_policies"): policies = policies + kwargs.get("_additional_pipeline_policies") # type: ignore diff --git a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/base_client_async.py b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/base_client_async.py index 7169ac25464c..818302725e98 100644 --- a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/base_client_async.py +++ b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/base_client_async.py @@ -39,6 +39,7 @@ StorageHeadersPolicy, StorageHosts, StorageRequestHook, + StorageSensitiveHeaderCleanupPolicy, ) from .policies_async import ( AsyncStorageBearerTokenCredentialPolicy, @@ -145,6 +146,7 @@ def _create_pipeline( AsyncStorageResponseHook(**kwargs), DistributedTracingPolicy(**kwargs), HttpLoggingPolicy(**kwargs), + StorageSensitiveHeaderCleanupPolicy(**kwargs), ] if kwargs.get("_additional_pipeline_policies"): policies = policies + kwargs.get("_additional_pipeline_policies") # type: ignore diff --git a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py index b5d0b7d79766..4264b49d2487 100644 --- a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py +++ b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py @@ -11,7 +11,7 @@ import uuid from io import BytesIO, SEEK_SET, UnsupportedOperation from time import time -from typing import Any, Dict, Optional, TYPE_CHECKING, Union +from typing import Any, Dict, List, Optional, TypeVar, TYPE_CHECKING, Union from urllib.parse import ( parse_qsl, urlencode, @@ -29,6 +29,12 @@ RequestHistory, SansIOHTTPPolicy, ) +from azure.core.pipeline.transport import ( + HttpRequest as LegacyHttpRequest, + HttpResponse as LegacyHttpResponse, + PipelineRequest, +) +from azure.core.rest import HttpRequest, HttpResponse from .authentication import AzureSigningError, StorageHttpChallenge from .constants import DEFAULT_OAUTH_SCOPE, DATA_BLOCK_SIZE @@ -54,6 +60,10 @@ ) +HTTPResponseType = TypeVar("HTTPResponseType", HttpResponse, LegacyHttpResponse) +HTTPRequestType = TypeVar("HTTPRequestType", HttpRequest, LegacyHttpRequest) + + _LOGGER = logging.getLogger(__name__) CONTENT_LENGTH_HEADER = "Content-Length" MD5_HEADER = "Content-MD5" @@ -843,3 +853,71 @@ def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") self.authorize_request(request, scope, tenant_id=challenge.tenant_id) return True + + +class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy[HTTPRequestType, HTTPResponseType]): + """A simple policy that cleans up sensitive headers + + :keyword list[str] blocked_redirect_headers: The headers to clean up when redirecting to another domain. + :keyword bool disable_redirect_cleanup: Opt out cleaning up sensitive headers when redirecting to another domain. + """ + + DEFAULT_SENSITIVE_HEADERS = set( + [ + "Authorization", + "x-ms-authorization-auxiliary", + "x-ms-copy-source", + "x-ms-copy-source-authorization", + "x-ms-rename-source", + ] + ) + + DEFAULT_SENSITIVE_QUERY_PARAMS = set( + [ + "sig", + ] + ) + + def __init__( + self, + *, + blocked_redirect_headers: Optional[List[str]] = None, + blocked_query_params: Optional[List[str]] = None, + disable_redirect_cleanup: bool = False, + **kwargs: Any, + ) -> None: + self._disable_redirect_cleanup = disable_redirect_cleanup + self._blocked_redirect_headers = ( + StorageSensitiveHeaderCleanupPolicy.DEFAULT_SENSITIVE_HEADERS + if blocked_redirect_headers is None + else blocked_redirect_headers + ) + self._blocked_query_params = ( + StorageSensitiveHeaderCleanupPolicy.DEFAULT_SENSITIVE_QUERY_PARAMS + if blocked_query_params is None + else blocked_query_params + ) + + def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None: + """This is executed before sending the request to the next policy. + + :param request: The PipelineRequest object. + :type request: ~azure.core.pipeline.PipelineRequest + """ + # "insecure_domain_change" is used to indicate that a redirect + # has occurred to a different domain. This tells the SensitiveHeaderCleanupPolicy + # to clean up sensitive headers. + insecure_domain_change = request.context.get("insecure_domain_change", False) + if not self._disable_redirect_cleanup and insecure_domain_change: + # Clean up request query parameters + parsed = urlparse(request.http_request.url) + kept = [ + pair + for pair in parsed.query.split("&") + if pair and pair.split("=", 1)[0] not in self._blocked_query_params + ] + request.http_request.url = urlunparse(parsed._replace(query="&".join(kept))) + + # Clean up request headers + for header in self._blocked_redirect_headers: + request.http_request.headers.pop(header, None) diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/base_client.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/base_client.py index 08e54267f1c7..a8335a3e746c 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/base_client.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/base_client.py @@ -55,6 +55,7 @@ StorageLoggingPolicy, StorageRequestHook, StorageResponseHook, + StorageSensitiveHeaderCleanupPolicy, ) from .request_handlers import serialize_batch_body, _get_batch_request_delimiter from .response_handlers import PartialBatchErrorException, process_storage_error @@ -333,6 +334,7 @@ def _create_pipeline( StorageResponseHook(**kwargs), DistributedTracingPolicy(**kwargs), HttpLoggingPolicy(**kwargs), + StorageSensitiveHeaderCleanupPolicy(**kwargs), ] if kwargs.get("_additional_pipeline_policies"): policies = policies + kwargs.get("_additional_pipeline_policies") # type: ignore diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/base_client_async.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/base_client_async.py index 7169ac25464c..818302725e98 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/base_client_async.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/base_client_async.py @@ -39,6 +39,7 @@ StorageHeadersPolicy, StorageHosts, StorageRequestHook, + StorageSensitiveHeaderCleanupPolicy, ) from .policies_async import ( AsyncStorageBearerTokenCredentialPolicy, @@ -145,6 +146,7 @@ def _create_pipeline( AsyncStorageResponseHook(**kwargs), DistributedTracingPolicy(**kwargs), HttpLoggingPolicy(**kwargs), + StorageSensitiveHeaderCleanupPolicy(**kwargs), ] if kwargs.get("_additional_pipeline_policies"): policies = policies + kwargs.get("_additional_pipeline_policies") # type: ignore diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py index b5d0b7d79766..4264b49d2487 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py @@ -11,7 +11,7 @@ import uuid from io import BytesIO, SEEK_SET, UnsupportedOperation from time import time -from typing import Any, Dict, Optional, TYPE_CHECKING, Union +from typing import Any, Dict, List, Optional, TypeVar, TYPE_CHECKING, Union from urllib.parse import ( parse_qsl, urlencode, @@ -29,6 +29,12 @@ RequestHistory, SansIOHTTPPolicy, ) +from azure.core.pipeline.transport import ( + HttpRequest as LegacyHttpRequest, + HttpResponse as LegacyHttpResponse, + PipelineRequest, +) +from azure.core.rest import HttpRequest, HttpResponse from .authentication import AzureSigningError, StorageHttpChallenge from .constants import DEFAULT_OAUTH_SCOPE, DATA_BLOCK_SIZE @@ -54,6 +60,10 @@ ) +HTTPResponseType = TypeVar("HTTPResponseType", HttpResponse, LegacyHttpResponse) +HTTPRequestType = TypeVar("HTTPRequestType", HttpRequest, LegacyHttpRequest) + + _LOGGER = logging.getLogger(__name__) CONTENT_LENGTH_HEADER = "Content-Length" MD5_HEADER = "Content-MD5" @@ -843,3 +853,71 @@ def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") self.authorize_request(request, scope, tenant_id=challenge.tenant_id) return True + + +class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy[HTTPRequestType, HTTPResponseType]): + """A simple policy that cleans up sensitive headers + + :keyword list[str] blocked_redirect_headers: The headers to clean up when redirecting to another domain. + :keyword bool disable_redirect_cleanup: Opt out cleaning up sensitive headers when redirecting to another domain. + """ + + DEFAULT_SENSITIVE_HEADERS = set( + [ + "Authorization", + "x-ms-authorization-auxiliary", + "x-ms-copy-source", + "x-ms-copy-source-authorization", + "x-ms-rename-source", + ] + ) + + DEFAULT_SENSITIVE_QUERY_PARAMS = set( + [ + "sig", + ] + ) + + def __init__( + self, + *, + blocked_redirect_headers: Optional[List[str]] = None, + blocked_query_params: Optional[List[str]] = None, + disable_redirect_cleanup: bool = False, + **kwargs: Any, + ) -> None: + self._disable_redirect_cleanup = disable_redirect_cleanup + self._blocked_redirect_headers = ( + StorageSensitiveHeaderCleanupPolicy.DEFAULT_SENSITIVE_HEADERS + if blocked_redirect_headers is None + else blocked_redirect_headers + ) + self._blocked_query_params = ( + StorageSensitiveHeaderCleanupPolicy.DEFAULT_SENSITIVE_QUERY_PARAMS + if blocked_query_params is None + else blocked_query_params + ) + + def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None: + """This is executed before sending the request to the next policy. + + :param request: The PipelineRequest object. + :type request: ~azure.core.pipeline.PipelineRequest + """ + # "insecure_domain_change" is used to indicate that a redirect + # has occurred to a different domain. This tells the SensitiveHeaderCleanupPolicy + # to clean up sensitive headers. + insecure_domain_change = request.context.get("insecure_domain_change", False) + if not self._disable_redirect_cleanup and insecure_domain_change: + # Clean up request query parameters + parsed = urlparse(request.http_request.url) + kept = [ + pair + for pair in parsed.query.split("&") + if pair and pair.split("=", 1)[0] not in self._blocked_query_params + ] + request.http_request.url = urlunparse(parsed._replace(query="&".join(kept))) + + # Clean up request headers + for header in self._blocked_redirect_headers: + request.http_request.headers.pop(header, None) diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/base_client.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/base_client.py index 86734de7a20b..d09be85852c6 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/base_client.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/base_client.py @@ -59,6 +59,7 @@ StorageLoggingPolicy, StorageRequestHook, StorageResponseHook, + StorageSensitiveHeaderCleanupPolicy, ) from .request_handlers import serialize_batch_body, _get_batch_request_delimiter from .response_handlers import PartialBatchErrorException, process_storage_error @@ -364,6 +365,7 @@ def _create_pipeline( StorageResponseHook(**kwargs), DistributedTracingPolicy(**kwargs), HttpLoggingPolicy(**kwargs), + StorageSensitiveHeaderCleanupPolicy(**kwargs), ] if kwargs.get("_additional_pipeline_policies"): policies = policies + kwargs.get("_additional_pipeline_policies") # type: ignore diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/base_client_async.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/base_client_async.py index 993c2cfb354f..6c2373818fd1 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/base_client_async.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/base_client_async.py @@ -39,6 +39,7 @@ StorageHeadersPolicy, StorageHosts, StorageRequestHook, + StorageSensitiveHeaderCleanupPolicy, ) from .policies_async import ( AsyncContentValidationPolicy, @@ -173,6 +174,7 @@ def _create_pipeline( AsyncStorageResponseHook(**kwargs), DistributedTracingPolicy(**kwargs), HttpLoggingPolicy(**kwargs), + StorageSensitiveHeaderCleanupPolicy(**kwargs), ] if kwargs.get("_additional_pipeline_policies"): policies = policies + kwargs.get("_additional_pipeline_policies") # type: ignore diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py index f4f602d1c669..2930c7c30ae9 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py @@ -11,7 +11,7 @@ import uuid from io import BytesIO, SEEK_SET, UnsupportedOperation from time import time -from typing import Any, Dict, Optional, TYPE_CHECKING, Union +from typing import Any, Dict, List, Optional, TypeVar, TYPE_CHECKING, Union from urllib.parse import ( parse_qsl, urlencode, @@ -29,6 +29,12 @@ RequestHistory, SansIOHTTPPolicy, ) +from azure.core.pipeline.transport import ( + HttpRequest as LegacyHttpRequest, + HttpResponse as LegacyHttpResponse, + PipelineRequest, +) +from azure.core.rest import HttpRequest, HttpResponse from .authentication import AzureSigningError, StorageHttpChallenge from .constants import DEFAULT_OAUTH_SCOPE, DATA_BLOCK_SIZE @@ -54,6 +60,10 @@ ) +HTTPResponseType = TypeVar("HTTPResponseType", HttpResponse, LegacyHttpResponse) +HTTPRequestType = TypeVar("HTTPRequestType", HttpRequest, LegacyHttpRequest) + + _LOGGER = logging.getLogger(__name__) CONTENT_LENGTH_HEADER = "Content-Length" MD5_HEADER = "Content-MD5" @@ -849,3 +859,71 @@ def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") self.authorize_request(request, scope, tenant_id=challenge.tenant_id) return True + + +class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy[HTTPRequestType, HTTPResponseType]): + """A simple policy that cleans up sensitive headers + + :keyword list[str] blocked_redirect_headers: The headers to clean up when redirecting to another domain. + :keyword bool disable_redirect_cleanup: Opt out cleaning up sensitive headers when redirecting to another domain. + """ + + DEFAULT_SENSITIVE_HEADERS = set( + [ + "Authorization", + "x-ms-authorization-auxiliary", + "x-ms-copy-source", + "x-ms-copy-source-authorization", + "x-ms-rename-source", + ] + ) + + DEFAULT_SENSITIVE_QUERY_PARAMS = set( + [ + "sig", + ] + ) + + def __init__( + self, + *, + blocked_redirect_headers: Optional[List[str]] = None, + blocked_query_params: Optional[List[str]] = None, + disable_redirect_cleanup: bool = False, + **kwargs: Any, + ) -> None: + self._disable_redirect_cleanup = disable_redirect_cleanup + self._blocked_redirect_headers = ( + StorageSensitiveHeaderCleanupPolicy.DEFAULT_SENSITIVE_HEADERS + if blocked_redirect_headers is None + else blocked_redirect_headers + ) + self._blocked_query_params = ( + StorageSensitiveHeaderCleanupPolicy.DEFAULT_SENSITIVE_QUERY_PARAMS + if blocked_query_params is None + else blocked_query_params + ) + + def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None: + """This is executed before sending the request to the next policy. + + :param request: The PipelineRequest object. + :type request: ~azure.core.pipeline.PipelineRequest + """ + # "insecure_domain_change" is used to indicate that a redirect + # has occurred to a different domain. This tells the SensitiveHeaderCleanupPolicy + # to clean up sensitive headers. + insecure_domain_change = request.context.get("insecure_domain_change", False) + if not self._disable_redirect_cleanup and insecure_domain_change: + # Clean up request query parameters + parsed = urlparse(request.http_request.url) + kept = [ + pair + for pair in parsed.query.split("&") + if pair and pair.split("=", 1)[0] not in self._blocked_query_params + ] + request.http_request.url = urlunparse(parsed._replace(query="&".join(kept))) + + # Clean up request headers + for header in self._blocked_redirect_headers: + request.http_request.headers.pop(header, None) From 8e961ee73ac7384288fc45d7274866e776d90afc Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Wed, 17 Jun 2026 13:54:51 -0400 Subject: [PATCH 4/8] Fixed import error --- .../azure-storage-blob/azure/storage/blob/_shared/policies.py | 2 +- .../azure/storage/filedatalake/_shared/policies.py | 2 +- .../azure/storage/fileshare/_shared/policies.py | 2 +- .../azure-storage-queue/azure/storage/queue/_shared/policies.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py index 97377cf019ba..821b8fefd43a 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py @@ -21,6 +21,7 @@ from wsgiref.handlers import format_date_time from azure.core.exceptions import AzureError, ServiceRequestError, ServiceResponseError +from azure.core.pipeline import PipelineRequest from azure.core.pipeline.policies import ( BearerTokenCredentialPolicy, HeadersPolicy, @@ -32,7 +33,6 @@ from azure.core.pipeline.transport import ( HttpRequest as LegacyHttpRequest, HttpResponse as LegacyHttpResponse, - PipelineRequest, ) from azure.core.rest import HttpRequest, HttpResponse diff --git a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py index 4264b49d2487..874bbd8344c4 100644 --- a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py +++ b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py @@ -21,6 +21,7 @@ from wsgiref.handlers import format_date_time from azure.core.exceptions import AzureError, ServiceRequestError, ServiceResponseError +from azure.core.pipeline import PipelineRequest from azure.core.pipeline.policies import ( BearerTokenCredentialPolicy, HeadersPolicy, @@ -32,7 +33,6 @@ from azure.core.pipeline.transport import ( HttpRequest as LegacyHttpRequest, HttpResponse as LegacyHttpResponse, - PipelineRequest, ) from azure.core.rest import HttpRequest, HttpResponse diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py index 4264b49d2487..874bbd8344c4 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py @@ -21,6 +21,7 @@ from wsgiref.handlers import format_date_time from azure.core.exceptions import AzureError, ServiceRequestError, ServiceResponseError +from azure.core.pipeline import PipelineRequest from azure.core.pipeline.policies import ( BearerTokenCredentialPolicy, HeadersPolicy, @@ -32,7 +33,6 @@ from azure.core.pipeline.transport import ( HttpRequest as LegacyHttpRequest, HttpResponse as LegacyHttpResponse, - PipelineRequest, ) from azure.core.rest import HttpRequest, HttpResponse diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py index 2930c7c30ae9..c2d570cce7da 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py @@ -21,6 +21,7 @@ from wsgiref.handlers import format_date_time from azure.core.exceptions import AzureError, ServiceRequestError, ServiceResponseError +from azure.core.pipeline import PipelineRequest from azure.core.pipeline.policies import ( BearerTokenCredentialPolicy, HeadersPolicy, @@ -32,7 +33,6 @@ from azure.core.pipeline.transport import ( HttpRequest as LegacyHttpRequest, HttpResponse as LegacyHttpResponse, - PipelineRequest, ) from azure.core.rest import HttpRequest, HttpResponse From 3d0b0b007b67c6bda2d82d6f008ac242f81a3a9f Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Wed, 17 Jun 2026 14:11:30 -0400 Subject: [PATCH 5/8] Copilot feedback --- .../azure/storage/blob/_shared/policies.py | 27 +++++++------------ .../storage/filedatalake/_shared/policies.py | 27 +++++++------------ .../storage/fileshare/_shared/policies.py | 27 +++++++------------ .../azure/storage/queue/_shared/policies.py | 27 +++++++------------ 4 files changed, 36 insertions(+), 72 deletions(-) diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py index 821b8fefd43a..a791031d635e 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py @@ -862,24 +862,15 @@ class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy[HTTPRequestType, HTTP :keyword bool disable_redirect_cleanup: Opt out cleaning up sensitive headers when redirecting to another domain. """ - DEFAULT_SENSITIVE_HEADERS = set( - [ - "Authorization", - "x-ms-authorization-auxiliary", - "x-ms-copy-source", - "x-ms-copy-source-authorization", - "x-ms-rename-source", - ] - ) + DEFAULT_SENSITIVE_HEADERS = { + "Authorization", "x-ms-authorization-auxiliary", "x-ms-copy-source", "x-ms-copy-source-authorization", + "x-ms-rename-source" + } - DEFAULT_SENSITIVE_QUERY_PARAMS = set( - [ - "sig", - ] - ) + DEFAULT_SENSITIVE_QUERY_PARAMS = {"sig"} def __init__( - self, + self, # pylint: disable=unused-argument *, blocked_redirect_headers: Optional[List[str]] = None, blocked_query_params: Optional[List[str]] = None, @@ -912,9 +903,9 @@ def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None: # Clean up request query parameters parsed = urlparse(request.http_request.url) kept = [ - pair - for pair in parsed.query.split("&") - if pair and pair.split("=", 1)[0] not in self._blocked_query_params + f"{k}={v}" + for k, v in parse_qsl(parsed.query, keep_blank_values=True) + if k and k not in self._blocked_query_params ] request.http_request.url = urlunparse(parsed._replace(query="&".join(kept))) diff --git a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py index 874bbd8344c4..316c614f13c3 100644 --- a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py +++ b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py @@ -862,24 +862,15 @@ class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy[HTTPRequestType, HTTP :keyword bool disable_redirect_cleanup: Opt out cleaning up sensitive headers when redirecting to another domain. """ - DEFAULT_SENSITIVE_HEADERS = set( - [ - "Authorization", - "x-ms-authorization-auxiliary", - "x-ms-copy-source", - "x-ms-copy-source-authorization", - "x-ms-rename-source", - ] - ) + DEFAULT_SENSITIVE_HEADERS = { + "Authorization", "x-ms-authorization-auxiliary", "x-ms-copy-source", "x-ms-copy-source-authorization", + "x-ms-rename-source" + } - DEFAULT_SENSITIVE_QUERY_PARAMS = set( - [ - "sig", - ] - ) + DEFAULT_SENSITIVE_QUERY_PARAMS = {"sig"} def __init__( - self, + self, # pylint: disable=unused-argument *, blocked_redirect_headers: Optional[List[str]] = None, blocked_query_params: Optional[List[str]] = None, @@ -912,9 +903,9 @@ def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None: # Clean up request query parameters parsed = urlparse(request.http_request.url) kept = [ - pair - for pair in parsed.query.split("&") - if pair and pair.split("=", 1)[0] not in self._blocked_query_params + f"{k}={v}" + for k, v in parse_qsl(parsed.query, keep_blank_values=True) + if k and k not in self._blocked_query_params ] request.http_request.url = urlunparse(parsed._replace(query="&".join(kept))) diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py index 874bbd8344c4..316c614f13c3 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py @@ -862,24 +862,15 @@ class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy[HTTPRequestType, HTTP :keyword bool disable_redirect_cleanup: Opt out cleaning up sensitive headers when redirecting to another domain. """ - DEFAULT_SENSITIVE_HEADERS = set( - [ - "Authorization", - "x-ms-authorization-auxiliary", - "x-ms-copy-source", - "x-ms-copy-source-authorization", - "x-ms-rename-source", - ] - ) + DEFAULT_SENSITIVE_HEADERS = { + "Authorization", "x-ms-authorization-auxiliary", "x-ms-copy-source", "x-ms-copy-source-authorization", + "x-ms-rename-source" + } - DEFAULT_SENSITIVE_QUERY_PARAMS = set( - [ - "sig", - ] - ) + DEFAULT_SENSITIVE_QUERY_PARAMS = {"sig"} def __init__( - self, + self, # pylint: disable=unused-argument *, blocked_redirect_headers: Optional[List[str]] = None, blocked_query_params: Optional[List[str]] = None, @@ -912,9 +903,9 @@ def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None: # Clean up request query parameters parsed = urlparse(request.http_request.url) kept = [ - pair - for pair in parsed.query.split("&") - if pair and pair.split("=", 1)[0] not in self._blocked_query_params + f"{k}={v}" + for k, v in parse_qsl(parsed.query, keep_blank_values=True) + if k and k not in self._blocked_query_params ] request.http_request.url = urlunparse(parsed._replace(query="&".join(kept))) diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py index c2d570cce7da..6d23a71186f7 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py @@ -868,24 +868,15 @@ class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy[HTTPRequestType, HTTP :keyword bool disable_redirect_cleanup: Opt out cleaning up sensitive headers when redirecting to another domain. """ - DEFAULT_SENSITIVE_HEADERS = set( - [ - "Authorization", - "x-ms-authorization-auxiliary", - "x-ms-copy-source", - "x-ms-copy-source-authorization", - "x-ms-rename-source", - ] - ) + DEFAULT_SENSITIVE_HEADERS = { + "Authorization", "x-ms-authorization-auxiliary", "x-ms-copy-source", "x-ms-copy-source-authorization", + "x-ms-rename-source" + } - DEFAULT_SENSITIVE_QUERY_PARAMS = set( - [ - "sig", - ] - ) + DEFAULT_SENSITIVE_QUERY_PARAMS = {"sig"} def __init__( - self, + self, # pylint: disable=unused-argument *, blocked_redirect_headers: Optional[List[str]] = None, blocked_query_params: Optional[List[str]] = None, @@ -918,9 +909,9 @@ def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None: # Clean up request query parameters parsed = urlparse(request.http_request.url) kept = [ - pair - for pair in parsed.query.split("&") - if pair and pair.split("=", 1)[0] not in self._blocked_query_params + f"{k}={v}" + for k, v in parse_qsl(parsed.query, keep_blank_values=True) + if k and k not in self._blocked_query_params ] request.http_request.url = urlunparse(parsed._replace(query="&".join(kept))) From f6404c347d41cf91a3066fe4a7ff877b479af571 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Thu, 18 Jun 2026 06:40:58 -0400 Subject: [PATCH 6/8] PR feedback, pylint, mypy --- .../azure/storage/blob/_shared/policies.py | 24 ++++++++++--------- .../storage/filedatalake/_shared/policies.py | 24 ++++++++++--------- .../storage/fileshare/_shared/policies.py | 24 ++++++++++--------- .../azure/storage/queue/_shared/policies.py | 24 ++++++++++--------- 4 files changed, 52 insertions(+), 44 deletions(-) diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py index a791031d635e..d5d8bf6e445c 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py @@ -21,7 +21,6 @@ from wsgiref.handlers import format_date_time from azure.core.exceptions import AzureError, ServiceRequestError, ServiceResponseError -from azure.core.pipeline import PipelineRequest from azure.core.pipeline.policies import ( BearerTokenCredentialPolicy, HeadersPolicy, @@ -30,7 +29,7 @@ RequestHistory, SansIOHTTPPolicy, ) -from azure.core.pipeline.transport import ( +from azure.core.pipeline.transport import ( # pylint: disable=no-legacy-azure-core-http-response-import HttpRequest as LegacyHttpRequest, HttpResponse as LegacyHttpResponse, ) @@ -855,7 +854,7 @@ def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") return True -class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy[HTTPRequestType, HTTPResponseType]): +class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy): """A simple policy that cleans up sensitive headers :keyword list[str] blocked_redirect_headers: The headers to clean up when redirecting to another domain. @@ -863,7 +862,10 @@ class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy[HTTPRequestType, HTTP """ DEFAULT_SENSITIVE_HEADERS = { - "Authorization", "x-ms-authorization-auxiliary", "x-ms-copy-source", "x-ms-copy-source-authorization", + "Authorization", + "x-ms-authorization-auxiliary", + "x-ms-copy-source", + "x-ms-copy-source-authorization", "x-ms-rename-source" } @@ -873,7 +875,7 @@ def __init__( self, # pylint: disable=unused-argument *, blocked_redirect_headers: Optional[List[str]] = None, - blocked_query_params: Optional[List[str]] = None, + blocked_redirect_query_params: Optional[List[str]] = None, disable_redirect_cleanup: bool = False, **kwargs: Any, ) -> None: @@ -885,11 +887,11 @@ def __init__( ) self._blocked_query_params = ( StorageSensitiveHeaderCleanupPolicy.DEFAULT_SENSITIVE_QUERY_PARAMS - if blocked_query_params is None - else blocked_query_params + if blocked_redirect_query_params is None + else blocked_redirect_query_params ) - def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None: + def on_request(self, request: "PipelineRequest") -> None: """This is executed before sending the request to the next policy. :param request: The PipelineRequest object. @@ -903,9 +905,9 @@ def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None: # Clean up request query parameters parsed = urlparse(request.http_request.url) kept = [ - f"{k}={v}" - for k, v in parse_qsl(parsed.query, keep_blank_values=True) - if k and k not in self._blocked_query_params + pair + for pair in parsed.query.split("&") + if pair and pair.split("=", 1)[0] not in self._blocked_query_params ] request.http_request.url = urlunparse(parsed._replace(query="&".join(kept))) diff --git a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py index 316c614f13c3..c90c8885b894 100644 --- a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py +++ b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py @@ -21,7 +21,6 @@ from wsgiref.handlers import format_date_time from azure.core.exceptions import AzureError, ServiceRequestError, ServiceResponseError -from azure.core.pipeline import PipelineRequest from azure.core.pipeline.policies import ( BearerTokenCredentialPolicy, HeadersPolicy, @@ -30,7 +29,7 @@ RequestHistory, SansIOHTTPPolicy, ) -from azure.core.pipeline.transport import ( +from azure.core.pipeline.transport import ( # pylint: disable=no-legacy-azure-core-http-response-import HttpRequest as LegacyHttpRequest, HttpResponse as LegacyHttpResponse, ) @@ -855,7 +854,7 @@ def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") return True -class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy[HTTPRequestType, HTTPResponseType]): +class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy): """A simple policy that cleans up sensitive headers :keyword list[str] blocked_redirect_headers: The headers to clean up when redirecting to another domain. @@ -863,7 +862,10 @@ class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy[HTTPRequestType, HTTP """ DEFAULT_SENSITIVE_HEADERS = { - "Authorization", "x-ms-authorization-auxiliary", "x-ms-copy-source", "x-ms-copy-source-authorization", + "Authorization", + "x-ms-authorization-auxiliary", + "x-ms-copy-source", + "x-ms-copy-source-authorization", "x-ms-rename-source" } @@ -873,7 +875,7 @@ def __init__( self, # pylint: disable=unused-argument *, blocked_redirect_headers: Optional[List[str]] = None, - blocked_query_params: Optional[List[str]] = None, + blocked_redirect_query_params: Optional[List[str]] = None, disable_redirect_cleanup: bool = False, **kwargs: Any, ) -> None: @@ -885,11 +887,11 @@ def __init__( ) self._blocked_query_params = ( StorageSensitiveHeaderCleanupPolicy.DEFAULT_SENSITIVE_QUERY_PARAMS - if blocked_query_params is None - else blocked_query_params + if blocked_redirect_query_params is None + else blocked_redirect_query_params ) - def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None: + def on_request(self, request: "PipelineRequest") -> None: """This is executed before sending the request to the next policy. :param request: The PipelineRequest object. @@ -903,9 +905,9 @@ def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None: # Clean up request query parameters parsed = urlparse(request.http_request.url) kept = [ - f"{k}={v}" - for k, v in parse_qsl(parsed.query, keep_blank_values=True) - if k and k not in self._blocked_query_params + pair + for pair in parsed.query.split("&") + if pair and pair.split("=", 1)[0] not in self._blocked_query_params ] request.http_request.url = urlunparse(parsed._replace(query="&".join(kept))) diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py index 316c614f13c3..c90c8885b894 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py @@ -21,7 +21,6 @@ from wsgiref.handlers import format_date_time from azure.core.exceptions import AzureError, ServiceRequestError, ServiceResponseError -from azure.core.pipeline import PipelineRequest from azure.core.pipeline.policies import ( BearerTokenCredentialPolicy, HeadersPolicy, @@ -30,7 +29,7 @@ RequestHistory, SansIOHTTPPolicy, ) -from azure.core.pipeline.transport import ( +from azure.core.pipeline.transport import ( # pylint: disable=no-legacy-azure-core-http-response-import HttpRequest as LegacyHttpRequest, HttpResponse as LegacyHttpResponse, ) @@ -855,7 +854,7 @@ def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") return True -class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy[HTTPRequestType, HTTPResponseType]): +class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy): """A simple policy that cleans up sensitive headers :keyword list[str] blocked_redirect_headers: The headers to clean up when redirecting to another domain. @@ -863,7 +862,10 @@ class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy[HTTPRequestType, HTTP """ DEFAULT_SENSITIVE_HEADERS = { - "Authorization", "x-ms-authorization-auxiliary", "x-ms-copy-source", "x-ms-copy-source-authorization", + "Authorization", + "x-ms-authorization-auxiliary", + "x-ms-copy-source", + "x-ms-copy-source-authorization", "x-ms-rename-source" } @@ -873,7 +875,7 @@ def __init__( self, # pylint: disable=unused-argument *, blocked_redirect_headers: Optional[List[str]] = None, - blocked_query_params: Optional[List[str]] = None, + blocked_redirect_query_params: Optional[List[str]] = None, disable_redirect_cleanup: bool = False, **kwargs: Any, ) -> None: @@ -885,11 +887,11 @@ def __init__( ) self._blocked_query_params = ( StorageSensitiveHeaderCleanupPolicy.DEFAULT_SENSITIVE_QUERY_PARAMS - if blocked_query_params is None - else blocked_query_params + if blocked_redirect_query_params is None + else blocked_redirect_query_params ) - def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None: + def on_request(self, request: "PipelineRequest") -> None: """This is executed before sending the request to the next policy. :param request: The PipelineRequest object. @@ -903,9 +905,9 @@ def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None: # Clean up request query parameters parsed = urlparse(request.http_request.url) kept = [ - f"{k}={v}" - for k, v in parse_qsl(parsed.query, keep_blank_values=True) - if k and k not in self._blocked_query_params + pair + for pair in parsed.query.split("&") + if pair and pair.split("=", 1)[0] not in self._blocked_query_params ] request.http_request.url = urlunparse(parsed._replace(query="&".join(kept))) diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py index 6d23a71186f7..f4b06061c1d7 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py @@ -21,7 +21,6 @@ from wsgiref.handlers import format_date_time from azure.core.exceptions import AzureError, ServiceRequestError, ServiceResponseError -from azure.core.pipeline import PipelineRequest from azure.core.pipeline.policies import ( BearerTokenCredentialPolicy, HeadersPolicy, @@ -30,7 +29,7 @@ RequestHistory, SansIOHTTPPolicy, ) -from azure.core.pipeline.transport import ( +from azure.core.pipeline.transport import ( # pylint: disable=no-legacy-azure-core-http-response-import HttpRequest as LegacyHttpRequest, HttpResponse as LegacyHttpResponse, ) @@ -861,7 +860,7 @@ def on_challenge(self, request: "PipelineRequest", response: "PipelineResponse") return True -class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy[HTTPRequestType, HTTPResponseType]): +class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy): """A simple policy that cleans up sensitive headers :keyword list[str] blocked_redirect_headers: The headers to clean up when redirecting to another domain. @@ -869,7 +868,10 @@ class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy[HTTPRequestType, HTTP """ DEFAULT_SENSITIVE_HEADERS = { - "Authorization", "x-ms-authorization-auxiliary", "x-ms-copy-source", "x-ms-copy-source-authorization", + "Authorization", + "x-ms-authorization-auxiliary", + "x-ms-copy-source", + "x-ms-copy-source-authorization", "x-ms-rename-source" } @@ -879,7 +881,7 @@ def __init__( self, # pylint: disable=unused-argument *, blocked_redirect_headers: Optional[List[str]] = None, - blocked_query_params: Optional[List[str]] = None, + blocked_redirect_query_params: Optional[List[str]] = None, disable_redirect_cleanup: bool = False, **kwargs: Any, ) -> None: @@ -891,11 +893,11 @@ def __init__( ) self._blocked_query_params = ( StorageSensitiveHeaderCleanupPolicy.DEFAULT_SENSITIVE_QUERY_PARAMS - if blocked_query_params is None - else blocked_query_params + if blocked_redirect_query_params is None + else blocked_redirect_query_params ) - def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None: + def on_request(self, request: "PipelineRequest") -> None: """This is executed before sending the request to the next policy. :param request: The PipelineRequest object. @@ -909,9 +911,9 @@ def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None: # Clean up request query parameters parsed = urlparse(request.http_request.url) kept = [ - f"{k}={v}" - for k, v in parse_qsl(parsed.query, keep_blank_values=True) - if k and k not in self._blocked_query_params + pair + for pair in parsed.query.split("&") + if pair and pair.split("=", 1)[0] not in self._blocked_query_params ] request.http_request.url = urlunparse(parsed._replace(query="&".join(kept))) From 0953f55893eace4401a475189ef968b462335cd6 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Thu, 18 Jun 2026 06:42:18 -0400 Subject: [PATCH 7/8] Black formatting --- .../azure-storage-blob/azure/storage/blob/_shared/policies.py | 2 +- .../azure/storage/filedatalake/_shared/policies.py | 2 +- .../azure/storage/fileshare/_shared/policies.py | 2 +- .../azure-storage-queue/azure/storage/queue/_shared/policies.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py index d5d8bf6e445c..06a36782a266 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py @@ -866,7 +866,7 @@ class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy): "x-ms-authorization-auxiliary", "x-ms-copy-source", "x-ms-copy-source-authorization", - "x-ms-rename-source" + "x-ms-rename-source", } DEFAULT_SENSITIVE_QUERY_PARAMS = {"sig"} diff --git a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py index c90c8885b894..5695892e33c7 100644 --- a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py +++ b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py @@ -866,7 +866,7 @@ class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy): "x-ms-authorization-auxiliary", "x-ms-copy-source", "x-ms-copy-source-authorization", - "x-ms-rename-source" + "x-ms-rename-source", } DEFAULT_SENSITIVE_QUERY_PARAMS = {"sig"} diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py index c90c8885b894..5695892e33c7 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py @@ -866,7 +866,7 @@ class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy): "x-ms-authorization-auxiliary", "x-ms-copy-source", "x-ms-copy-source-authorization", - "x-ms-rename-source" + "x-ms-rename-source", } DEFAULT_SENSITIVE_QUERY_PARAMS = {"sig"} diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py index f4b06061c1d7..c853bfb48594 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py @@ -872,7 +872,7 @@ class StorageSensitiveHeaderCleanupPolicy(SansIOHTTPPolicy): "x-ms-authorization-auxiliary", "x-ms-copy-source", "x-ms-copy-source-authorization", - "x-ms-rename-source" + "x-ms-rename-source", } DEFAULT_SENSITIVE_QUERY_PARAMS = {"sig"} From 5d0e934a1cf576a55b1fe6521cabb8006bfc5341 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Thu, 18 Jun 2026 19:09:17 -0400 Subject: [PATCH 8/8] PR feedback --- .../azure/storage/blob/_shared/policies.py | 11 +---------- .../azure/storage/filedatalake/_shared/policies.py | 11 +---------- .../azure/storage/fileshare/_shared/policies.py | 11 +---------- .../azure/storage/queue/_shared/policies.py | 11 +---------- 4 files changed, 4 insertions(+), 40 deletions(-) diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py index 06a36782a266..a657c186abda 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py @@ -11,7 +11,7 @@ import uuid from io import BytesIO, SEEK_SET, UnsupportedOperation from time import time -from typing import Any, Dict, List, Optional, TypeVar, TYPE_CHECKING, Union +from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union from urllib.parse import ( parse_qsl, urlencode, @@ -29,11 +29,6 @@ RequestHistory, SansIOHTTPPolicy, ) -from azure.core.pipeline.transport import ( # pylint: disable=no-legacy-azure-core-http-response-import - HttpRequest as LegacyHttpRequest, - HttpResponse as LegacyHttpResponse, -) -from azure.core.rest import HttpRequest, HttpResponse from .authentication import AzureSigningError, StorageHttpChallenge from .constants import DEFAULT_OAUTH_SCOPE, DATA_BLOCK_SIZE @@ -59,10 +54,6 @@ ) -HTTPResponseType = TypeVar("HTTPResponseType", HttpResponse, LegacyHttpResponse) -HTTPRequestType = TypeVar("HTTPRequestType", HttpRequest, LegacyHttpRequest) - - _LOGGER = logging.getLogger(__name__) CONTENT_LENGTH_HEADER = "Content-Length" MD5_HEADER = "Content-MD5" diff --git a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py index 5695892e33c7..af94adbc5186 100644 --- a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py +++ b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/policies.py @@ -11,7 +11,7 @@ import uuid from io import BytesIO, SEEK_SET, UnsupportedOperation from time import time -from typing import Any, Dict, List, Optional, TypeVar, TYPE_CHECKING, Union +from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union from urllib.parse import ( parse_qsl, urlencode, @@ -29,11 +29,6 @@ RequestHistory, SansIOHTTPPolicy, ) -from azure.core.pipeline.transport import ( # pylint: disable=no-legacy-azure-core-http-response-import - HttpRequest as LegacyHttpRequest, - HttpResponse as LegacyHttpResponse, -) -from azure.core.rest import HttpRequest, HttpResponse from .authentication import AzureSigningError, StorageHttpChallenge from .constants import DEFAULT_OAUTH_SCOPE, DATA_BLOCK_SIZE @@ -59,10 +54,6 @@ ) -HTTPResponseType = TypeVar("HTTPResponseType", HttpResponse, LegacyHttpResponse) -HTTPRequestType = TypeVar("HTTPRequestType", HttpRequest, LegacyHttpRequest) - - _LOGGER = logging.getLogger(__name__) CONTENT_LENGTH_HEADER = "Content-Length" MD5_HEADER = "Content-MD5" diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py index 5695892e33c7..af94adbc5186 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/policies.py @@ -11,7 +11,7 @@ import uuid from io import BytesIO, SEEK_SET, UnsupportedOperation from time import time -from typing import Any, Dict, List, Optional, TypeVar, TYPE_CHECKING, Union +from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union from urllib.parse import ( parse_qsl, urlencode, @@ -29,11 +29,6 @@ RequestHistory, SansIOHTTPPolicy, ) -from azure.core.pipeline.transport import ( # pylint: disable=no-legacy-azure-core-http-response-import - HttpRequest as LegacyHttpRequest, - HttpResponse as LegacyHttpResponse, -) -from azure.core.rest import HttpRequest, HttpResponse from .authentication import AzureSigningError, StorageHttpChallenge from .constants import DEFAULT_OAUTH_SCOPE, DATA_BLOCK_SIZE @@ -59,10 +54,6 @@ ) -HTTPResponseType = TypeVar("HTTPResponseType", HttpResponse, LegacyHttpResponse) -HTTPRequestType = TypeVar("HTTPRequestType", HttpRequest, LegacyHttpRequest) - - _LOGGER = logging.getLogger(__name__) CONTENT_LENGTH_HEADER = "Content-Length" MD5_HEADER = "Content-MD5" diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py index c853bfb48594..7119af6547d5 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py @@ -11,7 +11,7 @@ import uuid from io import BytesIO, SEEK_SET, UnsupportedOperation from time import time -from typing import Any, Dict, List, Optional, TypeVar, TYPE_CHECKING, Union +from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union from urllib.parse import ( parse_qsl, urlencode, @@ -29,11 +29,6 @@ RequestHistory, SansIOHTTPPolicy, ) -from azure.core.pipeline.transport import ( # pylint: disable=no-legacy-azure-core-http-response-import - HttpRequest as LegacyHttpRequest, - HttpResponse as LegacyHttpResponse, -) -from azure.core.rest import HttpRequest, HttpResponse from .authentication import AzureSigningError, StorageHttpChallenge from .constants import DEFAULT_OAUTH_SCOPE, DATA_BLOCK_SIZE @@ -59,10 +54,6 @@ ) -HTTPResponseType = TypeVar("HTTPResponseType", HttpResponse, LegacyHttpResponse) -HTTPRequestType = TypeVar("HTTPRequestType", HttpRequest, LegacyHttpRequest) - - _LOGGER = logging.getLogger(__name__) CONTENT_LENGTH_HEADER = "Content-Length" MD5_HEADER = "Content-MD5"