Skip to content

Commit 5ec0de4

Browse files
illia-vCopilot
andauthored
Merge commit from fork
* Remove sensitive headers in proxy pools too * Add a changelog entry * Check retries history in tests Co-authored-by: Copilot <copilot@github.com> --------- Co-authored-by: Copilot <copilot@github.com>
1 parent 2bdcc44 commit 5ec0de4

4 files changed

Lines changed: 88 additions & 0 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fixed HTTP pools created using ``ProxyManager.connection_from_url`` to strip
2+
sensitive headers specified in ``Retry.remove_headers_on_redirect`` when
3+
redirecting to a different host.

dummyserver/asgi_proxy.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ async def absolute_uri(
5656
client_response = await client.request(
5757
method=scope["method"],
5858
url=scope["path"],
59+
params=scope["query_string"].decode(),
5960
headers=list(scope["headers"]),
6061
content=await _read_body(receive),
6162
)

src/urllib3/connectionpool.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,18 @@ def urlopen( # type: ignore[override]
897897
body = None
898898
headers = HTTPHeaderDict(headers)._prepare_for_method_change()
899899

900+
# Strip headers marked as unsafe to forward to the redirected location.
901+
# Check remove_headers_on_redirect to avoid a potential network call within
902+
# self.is_same_host() which may use socket.gethostbyname() in the future.
903+
if retries.remove_headers_on_redirect and not self.is_same_host(
904+
redirect_location
905+
):
906+
new_headers = headers.copy() # type: ignore[union-attr]
907+
for header in headers:
908+
if header.lower() in retries.remove_headers_on_redirect:
909+
new_headers.pop(header, None)
910+
headers = new_headers
911+
900912
try:
901913
retries = retries.increment(method, url, response=response, _pool=self)
902914
except MaxRetryError:

test/with_dummyserver/test_proxy_poolmanager.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
SSLError,
3838
)
3939
from urllib3.poolmanager import ProxyManager, proxy_from_url
40+
from urllib3.util.retry import RequestHistory
4041
from urllib3.util.ssl_ import create_urllib3_context
4142
from urllib3.util.timeout import Timeout
4243

@@ -300,6 +301,77 @@ def test_cross_host_redirect(self) -> None:
300301
assert r._pool is not None
301302
assert r._pool.host != self.http_host_alt
302303

304+
_sensitive_headers = {
305+
"Authorization": "foo",
306+
"Proxy-Authorization": "bar",
307+
"Cookie": "foo=bar",
308+
}
309+
310+
@pytest.mark.parametrize(
311+
"sensitive_headers",
312+
(_sensitive_headers, {k.lower(): v for k, v in _sensitive_headers.items()}),
313+
ids=("capitalized", "lowercase"),
314+
)
315+
def test_cross_host_redirect_remove_headers_via_proxy_manager(
316+
self, sensitive_headers: dict[str, str]
317+
) -> None:
318+
headers_url = f"{self.http_url_alt}/headers"
319+
initial_url = f"{self.http_url}/redirect?target={headers_url}"
320+
with proxy_from_url(self.proxy_url) as proxy_mgr:
321+
r = proxy_mgr.request(
322+
"GET", initial_url, headers=sensitive_headers, retries=1
323+
)
324+
assert r.status == 200
325+
assert r.retries is not None
326+
assert r.retries.history == (
327+
RequestHistory(
328+
method="GET",
329+
url=initial_url,
330+
error=None,
331+
status=303,
332+
redirect_location=headers_url,
333+
),
334+
)
335+
data = r.json()
336+
for header in sensitive_headers:
337+
assert header not in data
338+
339+
@pytest.mark.parametrize(
340+
"sensitive_headers",
341+
(_sensitive_headers, {k.lower(): v for k, v in _sensitive_headers.items()}),
342+
ids=("capitalized", "lowercase"),
343+
)
344+
def test_cross_host_redirect_remove_headers_via_pool(
345+
self, sensitive_headers: dict[str, str]
346+
) -> None:
347+
headers_url = f"{self.http_url_alt}/headers"
348+
initial_url = f"{self.http_url}/redirect?target={headers_url}"
349+
with proxy_from_url(self.proxy_url) as proxy_mgr:
350+
pool = proxy_mgr.connection_from_url(self.http_url)
351+
r = pool.urlopen(
352+
"GET",
353+
initial_url,
354+
headers=sensitive_headers,
355+
retries=1,
356+
redirect=True,
357+
assert_same_host=False,
358+
preload_content=True,
359+
)
360+
assert r.status == 200
361+
assert r.retries is not None
362+
assert r.retries.history == (
363+
RequestHistory(
364+
method="GET",
365+
url=initial_url,
366+
error=None,
367+
status=303,
368+
redirect_location=headers_url,
369+
),
370+
)
371+
data = r.json()
372+
for header in sensitive_headers:
373+
assert header not in data
374+
303375
def test_cross_protocol_redirect(self) -> None:
304376
with proxy_from_url(self.proxy_url, ca_certs=DEFAULT_CA) as http:
305377
cross_protocol_location = f"{self.https_url}/echo?a=b"

0 commit comments

Comments
 (0)